aicodeman 1.1.0 → 1.1.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 (140) hide show
  1. package/README.md +2 -1
  2. package/dist/cli.d.ts.map +1 -1
  3. package/dist/cli.js +27 -0
  4. package/dist/cli.js.map +1 -1
  5. package/dist/config/dependency-registry.d.ts +42 -0
  6. package/dist/config/dependency-registry.d.ts.map +1 -0
  7. package/dist/config/dependency-registry.js +104 -0
  8. package/dist/config/dependency-registry.js.map +1 -0
  9. package/dist/config/workflow-config.d.ts +24 -0
  10. package/dist/config/workflow-config.d.ts.map +1 -0
  11. package/dist/config/workflow-config.js +24 -0
  12. package/dist/config/workflow-config.js.map +1 -0
  13. package/dist/hooks-config.d.ts +17 -0
  14. package/dist/hooks-config.d.ts.map +1 -1
  15. package/dist/hooks-config.js +39 -0
  16. package/dist/hooks-config.js.map +1 -1
  17. package/dist/subagent-watcher.d.ts +34 -0
  18. package/dist/subagent-watcher.d.ts.map +1 -1
  19. package/dist/subagent-watcher.js +147 -4
  20. package/dist/subagent-watcher.js.map +1 -1
  21. package/dist/types/index.d.ts +1 -0
  22. package/dist/types/index.d.ts.map +1 -1
  23. package/dist/types/index.js +1 -0
  24. package/dist/types/index.js.map +1 -1
  25. package/dist/types/workflow-run.d.ts +130 -0
  26. package/dist/types/workflow-run.d.ts.map +1 -0
  27. package/dist/types/workflow-run.js +20 -0
  28. package/dist/types/workflow-run.js.map +1 -0
  29. package/dist/utils/dependency-checker.d.ts +42 -0
  30. package/dist/utils/dependency-checker.d.ts.map +1 -0
  31. package/dist/utils/dependency-checker.js +167 -0
  32. package/dist/utils/dependency-checker.js.map +1 -0
  33. package/dist/utils/dependency-report.d.ts +26 -0
  34. package/dist/utils/dependency-report.d.ts.map +1 -0
  35. package/dist/utils/dependency-report.js +68 -0
  36. package/dist/utils/dependency-report.js.map +1 -0
  37. package/dist/web/middleware/auth.d.ts +4 -8
  38. package/dist/web/middleware/auth.d.ts.map +1 -1
  39. package/dist/web/middleware/auth.js +14 -22
  40. package/dist/web/middleware/auth.js.map +1 -1
  41. package/dist/web/public/api-client.c9b1cddc.js.gz +0 -0
  42. package/dist/web/public/app.eaa14cdd.js +37 -0
  43. package/dist/web/public/app.eaa14cdd.js.br +0 -0
  44. package/dist/web/public/app.eaa14cdd.js.gz +0 -0
  45. package/dist/web/public/{constants.00fa5405.js → constants.1c779517.js} +20 -0
  46. package/dist/web/public/constants.1c779517.js.br +0 -0
  47. package/dist/web/public/constants.1c779517.js.gz +0 -0
  48. package/dist/web/public/image-input.0ea86695.js.gz +0 -0
  49. package/dist/web/public/index.html +48 -10
  50. package/dist/web/public/index.html.br +0 -0
  51. package/dist/web/public/index.html.gz +0 -0
  52. package/dist/web/public/input-cjk.b8686b5e.js.gz +0 -0
  53. package/dist/web/public/keyboard-accessory.bc753cc7.js.gz +0 -0
  54. package/dist/web/public/{mobile-handlers.763a7439.js → mobile-handlers.db3dc3c8.js} +3 -0
  55. package/dist/web/public/mobile-handlers.db3dc3c8.js.br +0 -0
  56. package/dist/web/public/mobile-handlers.db3dc3c8.js.gz +0 -0
  57. package/dist/web/public/mobile.06b38d3a.css.gz +0 -0
  58. package/dist/web/public/notification-manager.9c984ac2.js.gz +0 -0
  59. package/dist/web/public/orchestrator-panel.js.gz +0 -0
  60. package/dist/web/public/{panels-ui.2f467969.js → panels-ui.f3f08e26.js} +48 -48
  61. package/dist/web/public/panels-ui.f3f08e26.js.br +0 -0
  62. package/dist/web/public/panels-ui.f3f08e26.js.gz +0 -0
  63. package/dist/web/public/ralph-panel.6de2d0f8.js.gz +0 -0
  64. package/dist/web/public/ralph-wizard.13a1831e.js.gz +0 -0
  65. package/dist/web/public/respawn-ui.2d249da9.js.gz +0 -0
  66. package/dist/web/public/sanitize-html.bc7078d6.js +1 -0
  67. package/dist/web/public/sanitize-html.bc7078d6.js.br +0 -0
  68. package/dist/web/public/sanitize-html.bc7078d6.js.gz +0 -0
  69. package/dist/web/public/session-ui.1463b824.js.gz +0 -0
  70. package/dist/web/public/settings-ui.601c30c1.js +55 -0
  71. package/dist/web/public/settings-ui.601c30c1.js.br +0 -0
  72. package/dist/web/public/settings-ui.601c30c1.js.gz +0 -0
  73. package/dist/web/public/styles.7e612fc4.css +1 -0
  74. package/dist/web/public/styles.7e612fc4.css.br +0 -0
  75. package/dist/web/public/styles.7e612fc4.css.gz +0 -0
  76. package/dist/web/public/subagent-windows.a366a4ad.js.gz +0 -0
  77. package/dist/web/public/sw.js.gz +0 -0
  78. package/dist/web/public/terminal-ui.a7e046da.js +3 -0
  79. package/dist/web/public/terminal-ui.a7e046da.js.br +0 -0
  80. package/dist/web/public/terminal-ui.a7e046da.js.gz +0 -0
  81. package/dist/web/public/ultracode-panel.js +304 -0
  82. package/dist/web/public/ultracode-panel.js.br +0 -0
  83. package/dist/web/public/ultracode-panel.js.gz +0 -0
  84. package/dist/web/public/upload.html.gz +0 -0
  85. package/dist/web/public/vendor/dompurify.min.js +3 -0
  86. package/dist/web/public/vendor/dompurify.min.js.br +0 -0
  87. package/dist/web/public/vendor/dompurify.min.js.gz +0 -0
  88. package/dist/web/public/vendor/marked.min.js.gz +0 -0
  89. package/dist/web/public/vendor/xterm-addon-fit.min.js.gz +0 -0
  90. package/dist/web/public/vendor/xterm-addon-serialize.min.js.gz +0 -0
  91. package/dist/web/public/vendor/xterm-addon-unicode11.min.js.gz +0 -0
  92. package/dist/web/public/vendor/xterm-addon-webgl.min.js.gz +0 -0
  93. package/dist/web/public/vendor/xterm-zerolag-input.137ad9f0.js.gz +0 -0
  94. package/dist/web/public/vendor/xterm.css.gz +0 -0
  95. package/dist/web/public/vendor/xterm.min.js.gz +0 -0
  96. package/dist/web/public/voice-input.085e9e73.js.gz +0 -0
  97. package/dist/web/routes/file-routes.d.ts.map +1 -1
  98. package/dist/web/routes/file-routes.js +80 -24
  99. package/dist/web/routes/file-routes.js.map +1 -1
  100. package/dist/web/routes/session-routes.d.ts.map +1 -1
  101. package/dist/web/routes/session-routes.js +13 -1
  102. package/dist/web/routes/session-routes.js.map +1 -1
  103. package/dist/web/routes/system-routes.d.ts.map +1 -1
  104. package/dist/web/routes/system-routes.js +21 -0
  105. package/dist/web/routes/system-routes.js.map +1 -1
  106. package/dist/web/schemas.d.ts +1 -0
  107. package/dist/web/schemas.d.ts.map +1 -1
  108. package/dist/web/schemas.js +2 -0
  109. package/dist/web/schemas.js.map +1 -1
  110. package/dist/web/server.d.ts +12 -0
  111. package/dist/web/server.d.ts.map +1 -1
  112. package/dist/web/server.js +56 -1
  113. package/dist/web/server.js.map +1 -1
  114. package/dist/web/sse-events.d.ts +10 -0
  115. package/dist/web/sse-events.d.ts.map +1 -1
  116. package/dist/web/sse-events.js +12 -0
  117. package/dist/web/sse-events.js.map +1 -1
  118. package/dist/workflow-run-watcher.d.ts +76 -0
  119. package/dist/workflow-run-watcher.d.ts.map +1 -0
  120. package/dist/workflow-run-watcher.js +327 -0
  121. package/dist/workflow-run-watcher.js.map +1 -0
  122. package/package.json +1 -1
  123. package/dist/web/public/app.9daf49ad.js +0 -37
  124. package/dist/web/public/app.9daf49ad.js.br +0 -0
  125. package/dist/web/public/app.9daf49ad.js.gz +0 -0
  126. package/dist/web/public/constants.00fa5405.js.br +0 -0
  127. package/dist/web/public/constants.00fa5405.js.gz +0 -0
  128. package/dist/web/public/mobile-handlers.763a7439.js.br +0 -0
  129. package/dist/web/public/mobile-handlers.763a7439.js.gz +0 -0
  130. package/dist/web/public/panels-ui.2f467969.js.br +0 -0
  131. package/dist/web/public/panels-ui.2f467969.js.gz +0 -0
  132. package/dist/web/public/settings-ui.44b99ce0.js +0 -55
  133. package/dist/web/public/settings-ui.44b99ce0.js.br +0 -0
  134. package/dist/web/public/settings-ui.44b99ce0.js.gz +0 -0
  135. package/dist/web/public/styles.c13845d5.css +0 -1
  136. package/dist/web/public/styles.c13845d5.css.br +0 -0
  137. package/dist/web/public/styles.c13845d5.css.gz +0 -0
  138. package/dist/web/public/terminal-ui.5bf97f7e.js +0 -3
  139. package/dist/web/public/terminal-ui.5bf97f7e.js.br +0 -0
  140. package/dist/web/public/terminal-ui.5bf97f7e.js.gz +0 -0
@@ -0,0 +1,304 @@
1
+ /**
2
+ * @fileoverview Ultracode / Workflow run visualization — master-detail dock panel.
3
+ *
4
+ * Mirrors Claude Code's "working agents" TUI: LEFT pane = runs and their phases
5
+ * (selectable "tasks"), RIGHT pane = the selected run's agents with model, live
6
+ * state, TOKENS burned, and TOOL CALLS. Opt-in via the `showUltracodeAgents`
7
+ * setting; the launcher button + panel are hidden until enabled.
8
+ *
9
+ * Data: run SUMMARIES arrive via getLightState (`data.workflowRuns`) and the
10
+ * `workflow:run_*` SSE events (LEFT list). The full run (with agents[]) is fetched
11
+ * per-run from GET /api/workflows/:runId when a run is selected (RIGHT pane).
12
+ *
13
+ * Standalone: reads only the workflow-run endpoints; never touches subagent state.
14
+ *
15
+ * @mixin Extends CodemanApp.prototype via Object.assign
16
+ * @loadorder 11.5 (after panels-ui.js, before session-ui.js)
17
+ */
18
+ /* global CodemanApp, SSE_EVENTS, escapeHtml */
19
+
20
+ Object.assign(CodemanApp.prototype, {
21
+ /** Ensure workflow state maps exist (lazy — constructor also seeds them). */
22
+ _ensureWorkflowState() {
23
+ if (!this.workflowRuns) this.workflowRuns = new Map(); // runId -> summary
24
+ if (!this.workflowRunDetails) this.workflowRunDetails = new Map(); // runId -> full run (with agents)
25
+ if (this.activeWorkflowRunId === undefined) this.activeWorkflowRunId = null;
26
+ if (this.activeWorkflowPhaseIndex === undefined) this.activeWorkflowPhaseIndex = null;
27
+ },
28
+
29
+ /** Seed the LEFT list from a getLightState snapshot (array of run summaries). */
30
+ seedWorkflowRuns(summaries) {
31
+ this._ensureWorkflowState();
32
+ this.workflowRuns.clear();
33
+ (summaries || []).forEach((s) => this.workflowRuns.set(s.runId, s));
34
+ this.renderUltracodeAgentsPanel();
35
+ },
36
+
37
+ // ----- SSE handlers (wired in app.js _SSE_HANDLER_MAP) -----
38
+ _onWorkflowRunDiscovered(data) {
39
+ this._upsertWorkflowRun(data);
40
+ },
41
+ _onWorkflowRunUpdated(data) {
42
+ this._upsertWorkflowRun(data);
43
+ },
44
+ _onWorkflowRunRemoved(data) {
45
+ this._ensureWorkflowState();
46
+ if (!data || !data.runId) return;
47
+ this.workflowRuns.delete(data.runId);
48
+ this.workflowRunDetails.delete(data.runId);
49
+ if (this.activeWorkflowRunId === data.runId) this.activeWorkflowRunId = null;
50
+ this.renderUltracodeAgentsPanel();
51
+ },
52
+
53
+ _upsertWorkflowRun(summary) {
54
+ this._ensureWorkflowState();
55
+ if (!summary || !summary.runId) return;
56
+ this.workflowRuns.set(summary.runId, summary);
57
+ // If the live-updating run is the one open in the detail pane, refresh its agents.
58
+ if (this.activeWorkflowRunId === summary.runId) {
59
+ this._fetchWorkflowRunDetail(summary.runId);
60
+ }
61
+ this.renderUltracodeAgentsPanel();
62
+ },
63
+
64
+ // ----- Panel open/close -----
65
+ toggleUltracodeAgentsPanel() {
66
+ const panel = document.getElementById('ultracodeAgentsPanel');
67
+ if (!panel) return;
68
+ panel.classList.remove('hidden');
69
+ panel.classList.toggle('open');
70
+ if (panel.classList.contains('open')) this.renderUltracodeAgentsPanel();
71
+ },
72
+ closeUltracodeAgentsPanel() {
73
+ const panel = document.getElementById('ultracodeAgentsPanel');
74
+ if (panel) panel.classList.remove('open');
75
+ },
76
+
77
+ // ----- Selection -----
78
+ selectWorkflowRun(runId) {
79
+ this._ensureWorkflowState();
80
+ this.activeWorkflowRunId = runId;
81
+ this.activeWorkflowPhaseIndex = null; // reset phase filter on run change
82
+ this._fetchWorkflowRunDetail(runId);
83
+ this.renderUltracodeAgentsPanel();
84
+ },
85
+ selectWorkflowPhase(phaseIndex) {
86
+ this._ensureWorkflowState();
87
+ // phaseIndex null => show all phases
88
+ this.activeWorkflowPhaseIndex = phaseIndex === null || phaseIndex === undefined ? null : Number(phaseIndex);
89
+ this._renderUltracodeDetail();
90
+ },
91
+
92
+ async _fetchWorkflowRunDetail(runId) {
93
+ try {
94
+ const res = await fetch(`/api/workflows/${encodeURIComponent(runId)}`);
95
+ const env = await res.json();
96
+ const run = env && env.success ? env.data : null;
97
+ if (run) {
98
+ this.workflowRunDetails.set(runId, run);
99
+ if (this.activeWorkflowRunId === runId) this._renderUltracodeDetail();
100
+ }
101
+ } catch {
102
+ /* transient — next update retries */
103
+ }
104
+ },
105
+
106
+ // Phase 4: open an agent's live transcript by agentId. The workflow agent's
107
+ // agentId is byte-identical to the agent-<id>.jsonl stem already tracked by
108
+ // subagent-watcher, so we reuse the existing transcript route — no watcher edits.
109
+ // Graceful when the agent isn't tracked yet / aged out / tracking disabled.
110
+ async openWorkflowAgentTranscript(agentId) {
111
+ if (!agentId) return;
112
+ let data = null;
113
+ try {
114
+ const res = await fetch(`/api/subagents/${encodeURIComponent(agentId)}/transcript?format=formatted`);
115
+ data = await res.json();
116
+ } catch {
117
+ data = null;
118
+ }
119
+ const ok = data && data.success && data.data;
120
+ const formatted = ok ? data.data.formatted : null;
121
+ const entryCount = ok ? data.data.entryCount || 0 : 0;
122
+ if (!formatted || !entryCount) {
123
+ alert(
124
+ 'No transcript available for this agent yet — it may be queued, aged out of tracking, or subagent tracking is disabled.'
125
+ );
126
+ return;
127
+ }
128
+ const win = window.open('', '_blank', 'width=860,height=640');
129
+ if (!win) return; // popup blocked
130
+ win.document.write(
131
+ `<html><head><title>Workflow agent ${escapeHtml(agentId)} transcript</title>` +
132
+ `<style>body{background:#1a1a2e;color:#eee;font-family:monospace;padding:20px}pre{white-space:pre-wrap;word-wrap:break-word}</style>` +
133
+ `</head><body><h2>Workflow agent ${escapeHtml(agentId)} (${entryCount} entries)</h2>` +
134
+ `<pre>${escapeHtml(formatted.join('\n'))}</pre></body></html>`
135
+ );
136
+ win.document.close();
137
+ },
138
+
139
+ // ----- Render (debounced) -----
140
+ renderUltracodeAgentsPanel() {
141
+ clearTimeout(this._ultracodeRenderTimer);
142
+ this._ultracodeRenderTimer = setTimeout(() => this._renderUltracodeAgentsPanelImmediate(), 150);
143
+ },
144
+
145
+ _renderUltracodeAgentsPanelImmediate() {
146
+ this._ensureWorkflowState();
147
+ const panel = document.getElementById('ultracodeAgentsPanel');
148
+ if (!panel) return;
149
+ const badge = document.getElementById('ultracodeCountBadge');
150
+ if (badge) badge.textContent = this.workflowRuns.size ? String(this.workflowRuns.size) : '';
151
+ this._renderUltracodeRunList();
152
+ this._renderUltracodeDetail();
153
+ },
154
+
155
+ _renderUltracodeRunList() {
156
+ const list = document.getElementById('ultracodeRunList');
157
+ if (!list) return;
158
+ const runs = Array.from(this.workflowRuns.values()).sort(
159
+ (a, b) => (b.lastActivityAt || 0) - (a.lastActivityAt || 0)
160
+ );
161
+ if (!runs.length) {
162
+ list.innerHTML = '<div class="subagent-empty">No ultracode runs detected</div>';
163
+ return;
164
+ }
165
+ list.innerHTML = runs.map((r) => this._workflowRunRowHtml(r)).join('');
166
+ },
167
+
168
+ _workflowRunRowHtml(r) {
169
+ const active = r.runId === this.activeWorkflowRunId;
170
+ const name = escapeHtml(r.workflowName || r.summary || r.runId);
171
+ const status = String(r.status || '');
172
+ const statusCls = this._workflowStatusClass(status);
173
+ const stats = `${r.agentCount ?? 0} agents · ${this._fmtNum(r.totalTokens)} tok · ${r.totalToolCalls ?? 0} tools`;
174
+ let phasesHtml = '';
175
+ if (active && Array.isArray(r.phases) && r.phases.length) {
176
+ const allActive = this.activeWorkflowPhaseIndex === null ? ' selected' : '';
177
+ const chips = [
178
+ `<div class="ultracode-phase-chip${allActive}" onclick="event.stopPropagation();app.selectWorkflowPhase(null)">All</div>`,
179
+ ];
180
+ r.phases.forEach((p, i) => {
181
+ const sel = this.activeWorkflowPhaseIndex === i + 1 ? ' selected' : '';
182
+ chips.push(
183
+ `<div class="ultracode-phase-chip${sel}" title="${escapeHtml(p.detail || '')}" onclick="event.stopPropagation();app.selectWorkflowPhase(${i + 1})">${escapeHtml(p.title || 'Phase ' + (i + 1))}</div>`
184
+ );
185
+ });
186
+ phasesHtml = `<div class="ultracode-phase-list">${chips.join('')}</div>`;
187
+ }
188
+ return (
189
+ `<div class="ultracode-run-item${active ? ' selected' : ''}" onclick="app.selectWorkflowRun('${escapeHtml(r.runId)}')">` +
190
+ `<div class="ultracode-run-head"><span class="ultracode-run-name">${name}</span>` +
191
+ `<span class="ultracode-status ${statusCls}">${escapeHtml(status || '—')}</span></div>` +
192
+ `<div class="ultracode-run-stats">${escapeHtml(stats)}</div>` +
193
+ phasesHtml +
194
+ `</div>`
195
+ );
196
+ },
197
+
198
+ _renderUltracodeDetail() {
199
+ const detail = document.getElementById('ultracodeAgentGrid');
200
+ if (!detail) return;
201
+ const runId = this.activeWorkflowRunId;
202
+ if (!runId) {
203
+ detail.innerHTML = '<div class="subagent-empty">Select a run to view its agents</div>';
204
+ return;
205
+ }
206
+ const run = this.workflowRunDetails.get(runId);
207
+ if (!run) {
208
+ detail.innerHTML = '<div class="subagent-empty">Loading agents…</div>';
209
+ return;
210
+ }
211
+ const phases = Array.isArray(run.phases) ? run.phases : [];
212
+ let agents = Array.isArray(run.agents) ? run.agents : [];
213
+ if (this.activeWorkflowPhaseIndex !== null) {
214
+ agents = agents.filter((a) => a.phaseIndex === this.activeWorkflowPhaseIndex);
215
+ }
216
+ if (!agents.length) {
217
+ detail.innerHTML = '<div class="subagent-empty">No agents in this view</div>';
218
+ return;
219
+ }
220
+ // Group agents by phaseIndex, in phase order.
221
+ const groups = new Map();
222
+ agents.forEach((a) => {
223
+ const key = a.phaseIndex || 0;
224
+ if (!groups.has(key)) groups.set(key, []);
225
+ groups.get(key).push(a);
226
+ });
227
+ const orderedKeys = Array.from(groups.keys()).sort((a, b) => a - b);
228
+ const html = orderedKeys
229
+ .map((key) => {
230
+ const group = groups.get(key);
231
+ const title = (phases[key - 1] && phases[key - 1].title) || `Phase ${key}`;
232
+ const tok = group.reduce((s, a) => s + (a.tokens || 0), 0);
233
+ const tools = group.reduce((s, a) => s + (a.toolCalls || 0), 0);
234
+ const header =
235
+ `<div class="ultracode-phase-header"><span>${escapeHtml(title)}</span>` +
236
+ `<span class="ultracode-phase-sub">${this._fmtNum(tok)} tok · ${tools} tools</span></div>`;
237
+ return header + group.map((a) => this._workflowAgentCardHtml(a)).join('');
238
+ })
239
+ .join('');
240
+ detail.innerHTML = html;
241
+ },
242
+
243
+ _workflowAgentCardHtml(a) {
244
+ const state = String(a.state || 'start');
245
+ const stateCls = this._workflowAgentStateClass(state);
246
+ const stateLabel = state === 'start' ? 'queued' : state === 'progress' ? 'running' : state;
247
+ const model = this._modelShort(a.model);
248
+ const tokens = a.tokens === undefined ? '—' : this._fmtNum(a.tokens);
249
+ const tools = a.toolCalls === undefined ? '—' : String(a.toolCalls);
250
+ let secondary = '';
251
+ if (state === 'done' && a.resultPreview) {
252
+ secondary = escapeHtml(a.resultPreview);
253
+ } else if (a.lastToolName) {
254
+ secondary = escapeHtml(a.lastToolName + (a.lastToolSummary ? ' · ' + a.lastToolSummary : ''));
255
+ }
256
+ // Phase 4: cards with an agentId open the live transcript (the agentId is byte-identical
257
+ // to the agent-<id>.jsonl stem already tracked by subagent-watcher). 'start' agents have
258
+ // no agentId yet, so they stay non-clickable.
259
+ const clickable = !!a.agentId;
260
+ const cardAttrs = clickable
261
+ ? ` class="ultracode-agent-card ultracode-agent-card--clickable" role="button" tabindex="0"` +
262
+ ` title="View transcript" onclick="app.openWorkflowAgentTranscript('${escapeHtml(a.agentId)}')"`
263
+ : ` class="ultracode-agent-card"`;
264
+ return (
265
+ `<div${cardAttrs}>` +
266
+ `<div class="ultracode-agent-top">` +
267
+ `<span class="ultracode-agent-label">${escapeHtml(a.label || 'agent')}</span>` +
268
+ `<span class="ultracode-agent-state ${stateCls}">${escapeHtml(stateLabel)}</span>` +
269
+ `</div>` +
270
+ `<div class="ultracode-agent-meta">` +
271
+ `<span class="ultracode-chip" title="model">${escapeHtml(model)}</span>` +
272
+ `<span class="ultracode-chip ultracode-chip-tok" title="tokens burned">${tokens} tok</span>` +
273
+ `<span class="ultracode-chip ultracode-chip-tool" title="tool calls">${tools} tools</span>` +
274
+ `</div>` +
275
+ (secondary ? `<div class="ultracode-agent-sub">${secondary}</div>` : '') +
276
+ `</div>`
277
+ );
278
+ },
279
+
280
+ // ----- helpers -----
281
+ _workflowStatusClass(status) {
282
+ if (status === 'completed') return 'completed';
283
+ if (status === 'running') return 'active';
284
+ if (status === 'killed' || status === 'failed') return 'failed';
285
+ return '';
286
+ },
287
+ _workflowAgentStateClass(state) {
288
+ if (state === 'done') return 'completed';
289
+ if (state === 'progress') return 'active';
290
+ return 'idle'; // start / queued
291
+ },
292
+ _modelShort(model) {
293
+ if (!model) return '';
294
+ return String(model)
295
+ .replace(/^claude-/, '')
296
+ .replace(/-\d{8}$/, '');
297
+ },
298
+ _fmtNum(n) {
299
+ if (n === undefined || n === null) return '0';
300
+ if (n >= 1_000_000) return (n / 1_000_000).toFixed(1) + 'M';
301
+ if (n >= 1000) return (n / 1000).toFixed(1) + 'k';
302
+ return String(n);
303
+ },
304
+ });
Binary file
@@ -0,0 +1,3 @@
1
+ /*! @license DOMPurify 3.4.8 | (c) Cure53 and other contributors | Released under the Apache license 2.0 and Mozilla Public License 2.0 | github.com/cure53/DOMPurify/blob/3.4.8/LICENSE */
2
+ !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e="undefined"!=typeof globalThis?globalThis:e||self).DOMPurify=t()}(this,function(){"use strict";function e(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,o=Array(t);n<t;n++)o[n]=e[n];return o}function t(t,n){return function(e){if(Array.isArray(e))return e}(t)||function(e,t){var n=null==e?null:"undefined"!=typeof Symbol&&e[Symbol.iterator]||e["@@iterator"];if(null!=n){var o,r,i,a,l=[],c=!0,s=!1;try{if(i=(n=n.call(e)).next,0===t);else for(;!(c=(o=i.call(n)).done)&&(l.push(o.value),l.length!==t);c=!0);}catch(e){s=!0,r=e}finally{try{if(!c&&null!=n.return&&(a=n.return(),Object(a)!==a))return}finally{if(s)throw r}}return l}}(t,n)||function(t,n){if(t){if("string"==typeof t)return e(t,n);var o={}.toString.call(t).slice(8,-1);return"Object"===o&&t.constructor&&(o=t.constructor.name),"Map"===o||"Set"===o?Array.from(t):"Arguments"===o||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(o)?e(t,n):void 0}}(t,n)||function(){throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()}const n=Object.entries,o=Object.setPrototypeOf,r=Object.isFrozen,i=Object.getPrototypeOf,a=Object.getOwnPropertyDescriptor;let l=Object.freeze,c=Object.seal,s=Object.create,u="undefined"!=typeof Reflect&&Reflect,f=u.apply,m=u.construct;l||(l=function(e){return e}),c||(c=function(e){return e}),f||(f=function(e,t){for(var n=arguments.length,o=new Array(n>2?n-2:0),r=2;r<n;r++)o[r-2]=arguments[r];return e.apply(t,o)}),m||(m=function(e){for(var t=arguments.length,n=new Array(t>1?t-1:0),o=1;o<t;o++)n[o-1]=arguments[o];return new e(...n)});const p=L(Array.prototype.forEach),d=L(Array.prototype.lastIndexOf),h=L(Array.prototype.pop),g=L(Array.prototype.push),y=L(Array.prototype.splice),T=Array.isArray,b=L(String.prototype.toLowerCase),A=L(String.prototype.toString),S=L(String.prototype.match),E=L(String.prototype.replace),_=L(String.prototype.indexOf),N=L(String.prototype.trim),O=L(Number.prototype.toString),D=L(Boolean.prototype.toString),R="undefined"==typeof BigInt?null:L(BigInt.prototype.toString),w="undefined"==typeof Symbol?null:L(Symbol.prototype.toString),I=L(Object.prototype.hasOwnProperty),v=L(Object.prototype.toString),C=L(RegExp.prototype.test),x=(k=TypeError,function(){for(var e=arguments.length,t=new Array(e),n=0;n<e;n++)t[n]=arguments[n];return m(k,t)});var k;function L(e){return function(t){t instanceof RegExp&&(t.lastIndex=0);for(var n=arguments.length,o=new Array(n>1?n-1:0),r=1;r<n;r++)o[r-1]=arguments[r];return f(e,t,o)}}function M(e,t){let n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:b;if(o&&o(e,null),!T(t))return e;let i=t.length;for(;i--;){let o=t[i];if("string"==typeof o){const e=n(o);e!==o&&(r(t)||(t[i]=e),o=e)}e[o]=!0}return e}function P(e){for(let t=0;t<e.length;t++){I(e,t)||(e[t]=null)}return e}function z(e){const o=s(null);for(const i of n(e)){var r=t(i,2);const n=r[0],a=r[1];I(e,n)&&(T(a)?o[n]=P(a):a&&"object"==typeof a&&a.constructor===Object?o[n]=z(a):o[n]=a)}return o}function U(e,t){for(;null!==e;){const n=a(e,t);if(n){if(n.get)return L(n.get);if("function"==typeof n.value)return L(n.value)}e=i(e)}return function(){return null}}const F=l(["a","abbr","acronym","address","area","article","aside","audio","b","bdi","bdo","big","blink","blockquote","body","br","button","canvas","caption","center","cite","code","col","colgroup","content","data","datalist","dd","decorator","del","details","dfn","dialog","dir","div","dl","dt","element","em","fieldset","figcaption","figure","font","footer","form","h1","h2","h3","h4","h5","h6","head","header","hgroup","hr","html","i","img","input","ins","kbd","label","legend","li","main","map","mark","marquee","menu","menuitem","meter","nav","nobr","ol","optgroup","option","output","p","picture","pre","progress","q","rp","rt","ruby","s","samp","search","section","select","shadow","slot","small","source","spacer","span","strike","strong","style","sub","summary","sup","table","tbody","td","template","textarea","tfoot","th","thead","time","tr","track","tt","u","ul","var","video","wbr"]),H=l(["svg","a","altglyph","altglyphdef","altglyphitem","animatecolor","animatemotion","animatetransform","circle","clippath","defs","desc","ellipse","enterkeyhint","exportparts","filter","font","g","glyph","glyphref","hkern","image","inputmode","line","lineargradient","marker","mask","metadata","mpath","part","path","pattern","polygon","polyline","radialgradient","rect","stop","style","switch","symbol","text","textpath","title","tref","tspan","view","vkern"]),B=l(["feBlend","feColorMatrix","feComponentTransfer","feComposite","feConvolveMatrix","feDiffuseLighting","feDisplacementMap","feDistantLight","feDropShadow","feFlood","feFuncA","feFuncB","feFuncG","feFuncR","feGaussianBlur","feImage","feMerge","feMergeNode","feMorphology","feOffset","fePointLight","feSpecularLighting","feSpotLight","feTile","feTurbulence"]),j=l(["animate","color-profile","cursor","discard","font-face","font-face-format","font-face-name","font-face-src","font-face-uri","foreignobject","hatch","hatchpath","mesh","meshgradient","meshpatch","meshrow","missing-glyph","script","set","solidcolor","unknown","use"]),G=l(["math","menclose","merror","mfenced","mfrac","mglyph","mi","mlabeledtr","mmultiscripts","mn","mo","mover","mpadded","mphantom","mroot","mrow","ms","mspace","msqrt","mstyle","msub","msup","msubsup","mtable","mtd","mtext","mtr","munder","munderover","mprescripts"]),W=l(["maction","maligngroup","malignmark","mlongdiv","mscarries","mscarry","msgroup","mstack","msline","msrow","semantics","annotation","annotation-xml","mprescripts","none"]),Y=l(["#text"]),q=l(["accept","action","align","alt","autocapitalize","autocomplete","autopictureinpicture","autoplay","background","bgcolor","border","capture","cellpadding","cellspacing","checked","cite","class","clear","color","cols","colspan","command","commandfor","controls","controlslist","coords","crossorigin","datetime","decoding","default","dir","disabled","disablepictureinpicture","disableremoteplayback","download","draggable","enctype","enterkeyhint","exportparts","face","for","headers","height","hidden","high","href","hreflang","id","inert","inputmode","integrity","ismap","kind","label","lang","list","loading","loop","low","max","maxlength","media","method","min","minlength","multiple","muted","name","nonce","noshade","novalidate","nowrap","open","optimum","part","pattern","placeholder","playsinline","popover","popovertarget","popovertargetaction","poster","preload","pubdate","radiogroup","readonly","rel","required","rev","reversed","role","rows","rowspan","spellcheck","scope","selected","shape","size","sizes","slot","span","srclang","start","src","srcset","step","style","summary","tabindex","title","translate","type","usemap","valign","value","width","wrap","xmlns"]),X=l(["accent-height","accumulate","additive","alignment-baseline","amplitude","ascent","attributename","attributetype","azimuth","basefrequency","baseline-shift","begin","bias","by","class","clip","clippathunits","clip-path","clip-rule","color","color-interpolation","color-interpolation-filters","color-profile","color-rendering","cx","cy","d","dx","dy","diffuseconstant","direction","display","divisor","dur","edgemode","elevation","end","exponent","fill","fill-opacity","fill-rule","filter","filterunits","flood-color","flood-opacity","font-family","font-size","font-size-adjust","font-stretch","font-style","font-variant","font-weight","fx","fy","g1","g2","glyph-name","glyphref","gradientunits","gradienttransform","height","href","id","image-rendering","in","in2","intercept","k","k1","k2","k3","k4","kerning","keypoints","keysplines","keytimes","lang","lengthadjust","letter-spacing","kernelmatrix","kernelunitlength","lighting-color","local","marker-end","marker-mid","marker-start","markerheight","markerunits","markerwidth","maskcontentunits","maskunits","max","mask","mask-type","media","method","mode","min","name","numoctaves","offset","operator","opacity","order","orient","orientation","origin","overflow","paint-order","path","pathlength","patterncontentunits","patterntransform","patternunits","points","preservealpha","preserveaspectratio","primitiveunits","r","rx","ry","radius","refx","refy","repeatcount","repeatdur","restart","result","rotate","scale","seed","shape-rendering","slope","specularconstant","specularexponent","spreadmethod","startoffset","stddeviation","stitchtiles","stop-color","stop-opacity","stroke-dasharray","stroke-dashoffset","stroke-linecap","stroke-linejoin","stroke-miterlimit","stroke-opacity","stroke","stroke-width","style","surfacescale","systemlanguage","tabindex","tablevalues","targetx","targety","transform","transform-origin","text-anchor","text-decoration","text-rendering","textlength","type","u1","u2","unicode","values","viewbox","visibility","version","vert-adv-y","vert-origin-x","vert-origin-y","width","word-spacing","wrap","writing-mode","xchannelselector","ychannelselector","x","x1","x2","xmlns","y","y1","y2","z","zoomandpan"]),$=l(["accent","accentunder","align","bevelled","close","columnalign","columnlines","columnspacing","columnspan","denomalign","depth","dir","display","displaystyle","encoding","fence","frame","height","href","id","largeop","length","linethickness","lquote","lspace","mathbackground","mathcolor","mathsize","mathvariant","maxsize","minsize","movablelimits","notation","numalign","open","rowalign","rowlines","rowspacing","rowspan","rspace","rquote","scriptlevel","scriptminsize","scriptsizemultiplier","selection","separator","separators","stretchy","subscriptshift","supscriptshift","symmetric","voffset","width","xmlns"]),K=l(["xlink:href","xml:id","xlink:title","xml:space","xmlns:xlink"]),V=c(/{{[\w\W]*|^[\w\W]*}}/g),Z=c(/<%[\w\W]*|^[\w\W]*%>/g),J=c(/\${[\w\W]*/g),Q=c(/^data-[\-\w.\u00B7-\uFFFF]+$/),ee=c(/^aria-[\-\w]+$/),te=c(/^(?:(?:(?:f|ht)tps?|mailto|tel|callto|sms|cid|xmpp|matrix):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i),ne=c(/^(?:\w+script|data):/i),oe=c(/[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205F\u3000]/g),re=c(/^html$/i),ie=c(/^[a-z][.\w]*(-[.\w]+)+$/i),ae=1,le=3,ce=7,se=8,ue=9,fe=11,me=function(){return"undefined"==typeof window?null:window};var pe=function e(){let t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:me();const o=t=>e(t);if(o.version="3.4.8",o.removed=[],!t||!t.document||t.document.nodeType!==ue||!t.Element)return o.isSupported=!1,o;let r=t.document;const i=r,a=i.currentScript;t.DocumentFragment;const c=t.HTMLTemplateElement,u=t.Node,f=t.Element,m=t.NodeFilter,k=t.NamedNodeMap;void 0===k&&(t.NamedNodeMap||t.MozNamedAttrMap),t.HTMLFormElement;const L=t.DOMParser,P=t.trustedTypes,pe=f.prototype,de=U(pe,"cloneNode"),he=U(pe,"remove"),ge=U(pe,"nextSibling"),ye=U(pe,"childNodes"),Te=U(pe,"parentNode"),be=U(pe,"shadowRoot"),Ae=U(pe,"attributes"),Se=u&&u.prototype?U(u.prototype,"nodeType"):null,Ee=u&&u.prototype?U(u.prototype,"nodeName"):null;if("function"==typeof c){const e=r.createElement("template");e.content&&e.content.ownerDocument&&(r=e.content.ownerDocument)}let _e,Ne="",Oe=0;const De=function(e){if(Oe>0)throw x('The configured TRUSTED_TYPES_POLICY.createHTML must not call DOMPurify.sanitize, as that causes infinite recursion. Do not pass a policy whose createHTML wraps DOMPurify as TRUSTED_TYPES_POLICY; see the "DOMPurify and Trusted Types" section of the README.');Oe++;try{return _e.createHTML(e)}finally{Oe--}},Re=r,we=Re.implementation,Ie=Re.createNodeIterator,ve=Re.createDocumentFragment,Ce=Re.getElementsByTagName,xe=i.importNode;let ke={afterSanitizeAttributes:[],afterSanitizeElements:[],afterSanitizeShadowDOM:[],beforeSanitizeAttributes:[],beforeSanitizeElements:[],beforeSanitizeShadowDOM:[],uponSanitizeAttribute:[],uponSanitizeElement:[],uponSanitizeShadowNode:[]};o.isSupported="function"==typeof n&&"function"==typeof Te&&we&&void 0!==we.createHTMLDocument;const Le=V,Me=Z,Pe=J,ze=Q,Ue=ee,Fe=ne,He=oe,Be=ie;let je=te,Ge=null;const We=M({},[...F,...H,...B,...G,...Y]);let Ye=null;const qe=M({},[...q,...X,...$,...K]);let Xe=Object.seal(s(null,{tagNameCheck:{writable:!0,configurable:!1,enumerable:!0,value:null},attributeNameCheck:{writable:!0,configurable:!1,enumerable:!0,value:null},allowCustomizedBuiltInElements:{writable:!0,configurable:!1,enumerable:!0,value:!1}})),$e=null,Ke=null;const Ve=Object.seal(s(null,{tagCheck:{writable:!0,configurable:!1,enumerable:!0,value:null},attributeCheck:{writable:!0,configurable:!1,enumerable:!0,value:null}}));let Ze=!0,Je=!0,Qe=!1,et=!0,tt=!1,nt=!0,ot=!1,rt=!1,it=!1,at=!1,lt=!1,ct=!1,st=!0,ut=!1;const ft="user-content-";let mt=!0,pt=!1,dt={},ht=null;const gt=M({},["annotation-xml","audio","colgroup","desc","foreignobject","head","iframe","math","mi","mn","mo","ms","mtext","noembed","noframes","noscript","plaintext","script","style","svg","template","thead","title","video","xmp"]);let yt=null;const Tt=M({},["audio","video","img","source","image","track"]);let bt=null;const At=M({},["alt","class","for","id","label","name","pattern","placeholder","role","summary","title","value","style","xmlns"]),St="http://www.w3.org/1998/Math/MathML",Et="http://www.w3.org/2000/svg",_t="http://www.w3.org/1999/xhtml";let Nt=_t,Ot=!1,Dt=null;const Rt=M({},[St,Et,_t],A);let wt=M({},["mi","mo","mn","ms","mtext"]),It=M({},["annotation-xml"]);const vt=M({},["title","style","font","a","script"]);let Ct=null;const xt=["application/xhtml+xml","text/html"];let kt=null,Lt=null;const Mt=r.createElement("form"),Pt=function(e){return e instanceof RegExp||e instanceof Function},zt=function(){let e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};if(Lt&&Lt===e)return;e&&"object"==typeof e||(e={}),e=z(e),Ct=-1===xt.indexOf(e.PARSER_MEDIA_TYPE)?"text/html":e.PARSER_MEDIA_TYPE,kt="application/xhtml+xml"===Ct?A:b,Ge=I(e,"ALLOWED_TAGS")&&T(e.ALLOWED_TAGS)?M({},e.ALLOWED_TAGS,kt):We,Ye=I(e,"ALLOWED_ATTR")&&T(e.ALLOWED_ATTR)?M({},e.ALLOWED_ATTR,kt):qe,Dt=I(e,"ALLOWED_NAMESPACES")&&T(e.ALLOWED_NAMESPACES)?M({},e.ALLOWED_NAMESPACES,A):Rt,bt=I(e,"ADD_URI_SAFE_ATTR")&&T(e.ADD_URI_SAFE_ATTR)?M(z(At),e.ADD_URI_SAFE_ATTR,kt):At,yt=I(e,"ADD_DATA_URI_TAGS")&&T(e.ADD_DATA_URI_TAGS)?M(z(Tt),e.ADD_DATA_URI_TAGS,kt):Tt,ht=I(e,"FORBID_CONTENTS")&&T(e.FORBID_CONTENTS)?M({},e.FORBID_CONTENTS,kt):gt,$e=I(e,"FORBID_TAGS")&&T(e.FORBID_TAGS)?M({},e.FORBID_TAGS,kt):z({}),Ke=I(e,"FORBID_ATTR")&&T(e.FORBID_ATTR)?M({},e.FORBID_ATTR,kt):z({}),dt=!!I(e,"USE_PROFILES")&&(e.USE_PROFILES&&"object"==typeof e.USE_PROFILES?z(e.USE_PROFILES):e.USE_PROFILES),Ze=!1!==e.ALLOW_ARIA_ATTR,Je=!1!==e.ALLOW_DATA_ATTR,Qe=e.ALLOW_UNKNOWN_PROTOCOLS||!1,et=!1!==e.ALLOW_SELF_CLOSE_IN_ATTR,tt=e.SAFE_FOR_TEMPLATES||!1,nt=!1!==e.SAFE_FOR_XML,ot=e.WHOLE_DOCUMENT||!1,at=e.RETURN_DOM||!1,lt=e.RETURN_DOM_FRAGMENT||!1,ct=e.RETURN_TRUSTED_TYPE||!1,it=e.FORCE_BODY||!1,st=!1!==e.SANITIZE_DOM,ut=e.SANITIZE_NAMED_PROPS||!1,mt=!1!==e.KEEP_CONTENT,pt=e.IN_PLACE||!1,je=function(e){try{return C(e,""),!0}catch(e){return!1}}(e.ALLOWED_URI_REGEXP)?e.ALLOWED_URI_REGEXP:te,Nt="string"==typeof e.NAMESPACE?e.NAMESPACE:_t,wt=I(e,"MATHML_TEXT_INTEGRATION_POINTS")&&e.MATHML_TEXT_INTEGRATION_POINTS&&"object"==typeof e.MATHML_TEXT_INTEGRATION_POINTS?z(e.MATHML_TEXT_INTEGRATION_POINTS):M({},["mi","mo","mn","ms","mtext"]),It=I(e,"HTML_INTEGRATION_POINTS")&&e.HTML_INTEGRATION_POINTS&&"object"==typeof e.HTML_INTEGRATION_POINTS?z(e.HTML_INTEGRATION_POINTS):M({},["annotation-xml"]);const t=I(e,"CUSTOM_ELEMENT_HANDLING")&&e.CUSTOM_ELEMENT_HANDLING&&"object"==typeof e.CUSTOM_ELEMENT_HANDLING?z(e.CUSTOM_ELEMENT_HANDLING):s(null);if(Xe=s(null),I(t,"tagNameCheck")&&Pt(t.tagNameCheck)&&(Xe.tagNameCheck=t.tagNameCheck),I(t,"attributeNameCheck")&&Pt(t.attributeNameCheck)&&(Xe.attributeNameCheck=t.attributeNameCheck),I(t,"allowCustomizedBuiltInElements")&&"boolean"==typeof t.allowCustomizedBuiltInElements&&(Xe.allowCustomizedBuiltInElements=t.allowCustomizedBuiltInElements),tt&&(Je=!1),lt&&(at=!0),dt&&(Ge=M({},Y),Ye=s(null),!0===dt.html&&(M(Ge,F),M(Ye,q)),!0===dt.svg&&(M(Ge,H),M(Ye,X),M(Ye,K)),!0===dt.svgFilters&&(M(Ge,B),M(Ye,X),M(Ye,K)),!0===dt.mathMl&&(M(Ge,G),M(Ye,$),M(Ye,K))),Ve.tagCheck=null,Ve.attributeCheck=null,I(e,"ADD_TAGS")&&("function"==typeof e.ADD_TAGS?Ve.tagCheck=e.ADD_TAGS:T(e.ADD_TAGS)&&(Ge===We&&(Ge=z(Ge)),M(Ge,e.ADD_TAGS,kt))),I(e,"ADD_ATTR")&&("function"==typeof e.ADD_ATTR?Ve.attributeCheck=e.ADD_ATTR:T(e.ADD_ATTR)&&(Ye===qe&&(Ye=z(Ye)),M(Ye,e.ADD_ATTR,kt))),I(e,"ADD_URI_SAFE_ATTR")&&T(e.ADD_URI_SAFE_ATTR)&&M(bt,e.ADD_URI_SAFE_ATTR,kt),I(e,"FORBID_CONTENTS")&&T(e.FORBID_CONTENTS)&&(ht===gt&&(ht=z(ht)),M(ht,e.FORBID_CONTENTS,kt)),I(e,"ADD_FORBID_CONTENTS")&&T(e.ADD_FORBID_CONTENTS)&&(ht===gt&&(ht=z(ht)),M(ht,e.ADD_FORBID_CONTENTS,kt)),mt&&(Ge["#text"]=!0),ot&&M(Ge,["html","head","body"]),Ge.table&&(M(Ge,["tbody"]),delete $e.tbody),e.TRUSTED_TYPES_POLICY){if("function"!=typeof e.TRUSTED_TYPES_POLICY.createHTML)throw x('TRUSTED_TYPES_POLICY configuration option must provide a "createHTML" hook.');if("function"!=typeof e.TRUSTED_TYPES_POLICY.createScriptURL)throw x('TRUSTED_TYPES_POLICY configuration option must provide a "createScriptURL" hook.');const t=_e;_e=e.TRUSTED_TYPES_POLICY;try{Ne=De("")}catch(e){throw _e=t,e}}else void 0===_e&&null!==e.TRUSTED_TYPES_POLICY&&(_e=function(e,t){if("object"!=typeof e||"function"!=typeof e.createPolicy)return null;let n=null;const o="data-tt-policy-suffix";t&&t.hasAttribute(o)&&(n=t.getAttribute(o));const r="dompurify"+(n?"#"+n:"");try{return e.createPolicy(r,{createHTML:e=>e,createScriptURL:e=>e})}catch(e){return console.warn("TrustedTypes policy "+r+" could not be created."),null}}(P,a)),_e&&"string"==typeof Ne&&(Ne=De(""));(ke.uponSanitizeElement.length>0||ke.uponSanitizeAttribute.length>0)&&Ge===We&&(Ge=z(Ge)),ke.uponSanitizeAttribute.length>0&&Ye===qe&&(Ye=z(Ye)),l&&l(e),Lt=e},Ut=M({},[...H,...B,...j]),Ft=M({},[...G,...W]),Ht=function(e){g(o.removed,{element:e});try{Te(e).removeChild(e)}catch(t){he(e)}},Bt=function(e,t){try{g(o.removed,{attribute:t.getAttributeNode(e),from:t})}catch(e){g(o.removed,{attribute:null,from:t})}if(t.removeAttribute(e),"is"===e)if(at||lt)try{Ht(t)}catch(e){}else try{t.setAttribute(e,"")}catch(e){}},jt=function(e){let t=null,n=null;if(it)e="<remove></remove>"+e;else{const t=S(e,/^[\r\n\t ]+/);n=t&&t[0]}"application/xhtml+xml"===Ct&&Nt===_t&&(e='<html xmlns="http://www.w3.org/1999/xhtml"><head></head><body>'+e+"</body></html>");const o=_e?De(e):e;if(Nt===_t)try{t=(new L).parseFromString(o,Ct)}catch(e){}if(!t||!t.documentElement){t=we.createDocument(Nt,"template",null);try{t.documentElement.innerHTML=Ot?Ne:o}catch(e){}}const i=t.body||t.documentElement;return e&&n&&i.insertBefore(r.createTextNode(n),i.childNodes[0]||null),Nt===_t?Ce.call(t,ot?"html":"body")[0]:ot?t.documentElement:i},Gt=function(e){return Ie.call(e.ownerDocument||e,e,m.SHOW_ELEMENT|m.SHOW_COMMENT|m.SHOW_TEXT|m.SHOW_PROCESSING_INSTRUCTION|m.SHOW_CDATA_SECTION,null)},Wt=function(e){var t,n;e.normalize();const o=Ie.call(e.ownerDocument||e,e,m.SHOW_TEXT|m.SHOW_COMMENT|m.SHOW_CDATA_SECTION|m.SHOW_PROCESSING_INSTRUCTION,null);let r=o.nextNode();for(;r;){let e=r.data;p([Le,Me,Pe],t=>{e=E(e,t," ")}),r.data=e,r=o.nextNode()}const i=null!==(t=null===(n=e.querySelectorAll)||void 0===n?void 0:n.call(e,"template"))&&void 0!==t?t:[];p(Array.from(i),e=>{qt(e.content)&&Wt(e.content)})},Yt=function(e){const t=Ee?Ee(e):null;return"string"==typeof t&&("form"===kt(t)&&("string"!=typeof e.nodeName||"string"!=typeof e.textContent||"function"!=typeof e.removeChild||e.attributes!==Ae(e)||"function"!=typeof e.removeAttribute||"function"!=typeof e.setAttribute||"string"!=typeof e.namespaceURI||"function"!=typeof e.insertBefore||"function"!=typeof e.hasChildNodes||e.nodeType!==Se(e)||e.childNodes!==ye(e)))},qt=function(e){if(!Se||"object"!=typeof e||null===e)return!1;try{return Se(e)===fe}catch(e){return!1}},Xt=function(e){if(!Se||"object"!=typeof e||null===e)return!1;try{return"number"==typeof Se(e)}catch(e){return!1}};function $t(e,t,n){p(e,e=>{e.call(o,t,n,Lt)})}const Kt=function(e){let t=null;if($t(ke.beforeSanitizeElements,e,null),Yt(e))return Ht(e),!0;const n=kt(Ee?Ee(e):e.nodeName);if($t(ke.uponSanitizeElement,e,{tagName:n,allowedTags:Ge}),nt&&e.hasChildNodes()&&!Xt(e.firstElementChild)&&C(/<[/\w!]/g,e.innerHTML)&&C(/<[/\w!]/g,e.textContent))return Ht(e),!0;if(nt&&e.namespaceURI===_t&&"style"===n&&Xt(e.firstElementChild))return Ht(e),!0;if(e.nodeType===ce)return Ht(e),!0;if(nt&&e.nodeType===se&&C(/<[/\w]/g,e.data))return Ht(e),!0;if($e[n]||!(Ve.tagCheck instanceof Function&&Ve.tagCheck(n))&&!Ge[n]){if(!$e[n]&&Jt(n)){if(Xe.tagNameCheck instanceof RegExp&&C(Xe.tagNameCheck,n))return!1;if(Xe.tagNameCheck instanceof Function&&Xe.tagNameCheck(n))return!1}if(mt&&!ht[n]){const t=Te(e),n=ye(e);if(n&&t){for(let o=n.length-1;o>=0;--o){const r=de(n[o],!0);t.insertBefore(r,ge(e))}}}return Ht(e),!0}return((Se?Se(e):e.nodeType)!==ae||function(e){let t=Te(e);t&&t.tagName||(t={namespaceURI:Nt,tagName:"template"});const n=b(e.tagName),o=b(t.tagName);return!!Dt[e.namespaceURI]&&(e.namespaceURI===Et?t.namespaceURI===_t?"svg"===n:t.namespaceURI===St?"svg"===n&&("annotation-xml"===o||wt[o]):Boolean(Ut[n]):e.namespaceURI===St?t.namespaceURI===_t?"math"===n:t.namespaceURI===Et?"math"===n&&It[o]:Boolean(Ft[n]):e.namespaceURI===_t?!(t.namespaceURI===Et&&!It[o])&&!(t.namespaceURI===St&&!wt[o])&&!Ft[n]&&(vt[n]||!Ut[n]):!("application/xhtml+xml"!==Ct||!Dt[e.namespaceURI]))}(e))&&("noscript"!==n&&"noembed"!==n&&"noframes"!==n||!C(/<\/no(script|embed|frames)/i,e.innerHTML))?(tt&&e.nodeType===le&&(t=e.textContent,p([Le,Me,Pe],e=>{t=E(t,e," ")}),e.textContent!==t&&(g(o.removed,{element:e.cloneNode()}),e.textContent=t)),$t(ke.afterSanitizeElements,e,null),!1):(Ht(e),!0)},Vt=function(e,t,n){if(Ke[t])return!1;if(st&&("id"===t||"name"===t)&&(n in r||n in Mt))return!1;const o=Ye[t]||Ve.attributeCheck instanceof Function&&Ve.attributeCheck(t,e);if(Je&&!Ke[t]&&C(ze,t));else if(Ze&&C(Ue,t));else if(!o||Ke[t]){if(!(Jt(e)&&(Xe.tagNameCheck instanceof RegExp&&C(Xe.tagNameCheck,e)||Xe.tagNameCheck instanceof Function&&Xe.tagNameCheck(e))&&(Xe.attributeNameCheck instanceof RegExp&&C(Xe.attributeNameCheck,t)||Xe.attributeNameCheck instanceof Function&&Xe.attributeNameCheck(t,e))||"is"===t&&Xe.allowCustomizedBuiltInElements&&(Xe.tagNameCheck instanceof RegExp&&C(Xe.tagNameCheck,n)||Xe.tagNameCheck instanceof Function&&Xe.tagNameCheck(n))))return!1}else if(bt[t]);else if(C(je,E(n,He,"")));else if("src"!==t&&"xlink:href"!==t&&"href"!==t||"script"===e||0!==_(n,"data:")||!yt[e]){if(Qe&&!C(Fe,E(n,He,"")));else if(n)return!1}else;return!0},Zt=M({},["annotation-xml","color-profile","font-face","font-face-format","font-face-name","font-face-src","font-face-uri","missing-glyph"]),Jt=function(e){return!Zt[b(e)]&&C(Be,e)},Qt=function(e){$t(ke.beforeSanitizeAttributes,e,null);const t=e.attributes;if(!t||Yt(e))return;const n={attrName:"",attrValue:"",keepAttr:!0,allowedAttributes:Ye,forceKeepAttr:void 0};let r=t.length;for(;r--;){const i=t[r],a=i.name,l=i.namespaceURI,c=i.value,s=kt(a),u=c;let f="value"===a?u:N(u);if(n.attrName=s,n.attrValue=f,n.keepAttr=!0,n.forceKeepAttr=void 0,$t(ke.uponSanitizeAttribute,e,n),f=n.attrValue,!ut||"id"!==s&&"name"!==s||0===_(f,ft)||(Bt(a,e),f=ft+f),nt&&C(/((--!?|])>)|<\/(style|script|title|xmp|textarea|noscript|iframe|noembed|noframes)/i,f)){Bt(a,e);continue}if("attributename"===s&&S(f,"href")){Bt(a,e);continue}if(n.forceKeepAttr)continue;if(!n.keepAttr){Bt(a,e);continue}if(!et&&C(/\/>/i,f)){Bt(a,e);continue}tt&&p([Le,Me,Pe],e=>{f=E(f,e," ")});const m=kt(e.nodeName);if(Vt(m,s,f)){if(_e&&"object"==typeof P&&"function"==typeof P.getAttributeType)if(l);else switch(P.getAttributeType(m,s)){case"TrustedHTML":f=De(f);break;case"TrustedScriptURL":f=_e.createScriptURL(f)}if(f!==u)try{l?e.setAttributeNS(l,a,f):e.setAttribute(a,f),Yt(e)?Ht(e):h(o.removed)}catch(t){Bt(a,e)}}else Bt(a,e)}$t(ke.afterSanitizeAttributes,e,null)},en=function(e){let t=null;const n=Gt(e);for($t(ke.beforeSanitizeShadowDOM,e,null);t=n.nextNode();){$t(ke.uponSanitizeShadowNode,t,null),Kt(t),Qt(t),qt(t.content)&&en(t.content);if((Se?Se(t):t.nodeType)===ae){const e=be?be(t):t.shadowRoot;qt(e)&&(tn(e),en(e))}}$t(ke.afterSanitizeShadowDOM,e,null)},tn=function(e){const t=Se?Se(e):e.nodeType;if(t===ae){const t=be?be(e):e.shadowRoot;qt(t)&&(tn(t),en(t))}const n=ye?ye(e):e.childNodes;if(!n)return;const o=[];p(n,e=>{g(o,e)});for(const e of o)tn(e);if(t===ae){const t=Ee?Ee(e):null;if("string"==typeof t&&"template"===kt(t)){const t=e.content;qt(t)&&tn(t)}}};return o.sanitize=function(e){let t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},n=null,r=null,a=null,l=null;if(Ot=!e,Ot&&(e="\x3c!--\x3e"),"string"!=typeof e&&!Xt(e)&&"string"!=typeof(e=function(e){switch(typeof e){case"string":return e;case"number":return O(e);case"boolean":return D(e);case"bigint":return R?R(e):"0";case"symbol":return w?w(e):"Symbol()";case"undefined":default:return v(e);case"function":case"object":{if(null===e)return v(e);const t=e,n=U(t,"toString");if("function"==typeof n){const e=n(t);return"string"==typeof e?e:v(e)}return v(e)}}}(e)))throw x("dirty is not a string, aborting");if(!o.isSupported)return e;if(rt||zt(t),o.removed=[],"string"==typeof e&&(pt=!1),pt){const t=Ee?Ee(e):e.nodeName;if("string"==typeof t){const e=kt(t);if(!Ge[e]||$e[e])throw x("root node is forbidden and cannot be sanitized in-place")}if(Yt(e))throw x("root node is clobbered and cannot be sanitized in-place");tn(e)}else if(Xt(e))n=jt("\x3c!----\x3e"),r=n.ownerDocument.importNode(e,!0),r.nodeType===ae&&"BODY"===r.nodeName||"HTML"===r.nodeName?n=r:n.appendChild(r),tn(r);else{if(!at&&!tt&&!ot&&-1===e.indexOf("<"))return _e&&ct?De(e):e;if(n=jt(e),!n)return at?null:ct?Ne:""}n&&it&&Ht(n.firstChild);const c=Gt(pt?e:n);for(;a=c.nextNode();)Kt(a),Qt(a),qt(a.content)&&en(a.content);if(pt)return tt&&Wt(e),e;if(at){if(tt&&Wt(n),lt)for(l=ve.call(n.ownerDocument);n.firstChild;)l.appendChild(n.firstChild);else l=n;return(Ye.shadowroot||Ye.shadowrootmode)&&(l=xe.call(i,l,!0)),l}let s=ot?n.outerHTML:n.innerHTML;return ot&&Ge["!doctype"]&&n.ownerDocument&&n.ownerDocument.doctype&&n.ownerDocument.doctype.name&&C(re,n.ownerDocument.doctype.name)&&(s="<!DOCTYPE "+n.ownerDocument.doctype.name+">\n"+s),tt&&p([Le,Me,Pe],e=>{s=E(s,e," ")}),_e&&ct?De(s):s},o.setConfig=function(){zt(arguments.length>0&&void 0!==arguments[0]?arguments[0]:{}),rt=!0},o.clearConfig=function(){Lt=null,rt=!1},o.isValidAttribute=function(e,t,n){Lt||zt({});const o=kt(e),r=kt(t);return Vt(o,r,n)},o.addHook=function(e,t){"function"==typeof t&&g(ke[e],t)},o.removeHook=function(e,t){if(void 0!==t){const n=d(ke[e],t);return-1===n?void 0:y(ke[e],n,1)[0]}return h(ke[e])},o.removeHooks=function(e){ke[e]=[]},o.removeAllHooks=function(){ke={afterSanitizeAttributes:[],afterSanitizeElements:[],afterSanitizeShadowDOM:[],beforeSanitizeAttributes:[],beforeSanitizeElements:[],beforeSanitizeShadowDOM:[],uponSanitizeAttribute:[],uponSanitizeElement:[],uponSanitizeShadowNode:[]}},o}();return pe});
3
+ //# sourceMappingURL=purify.min.js.map
Binary file
@@ -1 +1 @@
1
- {"version":3,"file":"file-routes.d.ts","sourceRoot":"","sources":["../../../src/web/routes/file-routes.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,eAAe,EAAqB,MAAM,SAAS,CAAC;AAuB7D,OAAO,KAAK,EAAE,UAAU,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAoV5E,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,eAAe,EAAE,GAAG,EAAE,WAAW,GAAG,SAAS,GAAG,UAAU,GAAG,IAAI,CA6pBxG"}
1
+ {"version":3,"file":"file-routes.d.ts","sourceRoot":"","sources":["../../../src/web/routes/file-routes.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,eAAe,EAAqB,MAAM,SAAS,CAAC;AAuB7D,OAAO,KAAK,EAAE,UAAU,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAoV5E,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,eAAe,EAAE,GAAG,EAAE,WAAW,GAAG,SAAS,GAAG,UAAU,GAAG,IAAI,CAytBxG"}
@@ -392,48 +392,70 @@ export function registerFileRoutes(app, ctx) {
392
392
  const { resolvedPath } = validated;
393
393
  try {
394
394
  const stat = await fs.stat(resolvedPath);
395
- // Check if it's a binary/media file
395
+ // Classify by extension. Known media types render with a dedicated player;
396
+ // other known-binary types are flagged so the client offers a download
397
+ // affordance instead of trying to decode the bytes as text. Matches the
398
+ // breadth of formats the attachments viewer renders (image/audio/video/pdf)
399
+ // so the file viewer can open the same files.
396
400
  const ext = filePath.split('.').pop()?.toLowerCase() || '';
397
- const binaryExts = new Set([
398
- 'png',
399
- 'jpg',
400
- 'jpeg',
401
- 'gif',
402
- 'webp',
403
- 'ico',
404
- 'svg',
405
- 'bmp',
406
- 'mp4',
407
- 'webm',
408
- 'mov',
409
- 'avi',
410
- 'mp3',
411
- 'wav',
412
- 'ogg',
401
+ const imageExts = new Set(['png', 'jpg', 'jpeg', 'gif', 'webp', 'svg', 'bmp', 'ico']);
402
+ const videoExts = new Set(['mp4', 'webm', 'mov', 'm4v', 'ogv']);
403
+ const audioExts = new Set(['mp3', 'wav', 'ogg', 'oga', 'm4a', 'aac', 'flac', 'opus']);
404
+ const otherBinaryExts = new Set([
413
405
  'pdf',
414
406
  'zip',
415
407
  'tar',
416
408
  'gz',
409
+ 'bz2',
410
+ 'xz',
411
+ '7z',
412
+ 'rar',
417
413
  'exe',
418
414
  'dll',
419
415
  'so',
416
+ 'dylib',
417
+ 'bin',
418
+ 'wasm',
419
+ 'class',
420
+ 'o',
421
+ 'a',
420
422
  'woff',
421
423
  'woff2',
422
424
  'ttf',
423
425
  'eot',
426
+ 'otf',
427
+ 'xlsx',
428
+ 'xls',
429
+ 'doc',
430
+ 'docx',
431
+ 'ppt',
432
+ 'pptx',
433
+ 'odt',
434
+ 'ods',
435
+ 'odp',
436
+ 'avi',
437
+ 'mkv',
438
+ 'wmv',
439
+ 'flv',
424
440
  ]);
425
- const imageExts = new Set(['png', 'jpg', 'jpeg', 'gif', 'webp', 'svg', 'bmp', 'ico']);
426
- const videoExts = new Set(['mp4', 'webm', 'mov', 'avi']);
427
- if (raw === 'true' || binaryExts.has(ext)) {
428
- // Return metadata for binary files
441
+ const mediaType = imageExts.has(ext)
442
+ ? 'image'
443
+ : videoExts.has(ext)
444
+ ? 'video'
445
+ : audioExts.has(ext)
446
+ ? 'audio'
447
+ : null;
448
+ const fileRawUrl = `/api/sessions/${id}/file-raw?path=${encodeURIComponent(filePath)}`;
449
+ if (raw === 'true' || mediaType || otherBinaryExts.has(ext)) {
450
+ // Return metadata for media/binary files (no text body)
429
451
  return {
430
452
  success: true,
431
453
  data: {
432
454
  path: filePath,
433
455
  size: stat.size,
434
- type: imageExts.has(ext) ? 'image' : videoExts.has(ext) ? 'video' : 'binary',
456
+ type: mediaType ?? 'binary',
435
457
  extension: ext,
436
- url: `/api/sessions/${id}/file-raw?path=${encodeURIComponent(filePath)}`,
458
+ url: fileRawUrl,
437
459
  },
438
460
  };
439
461
  }
@@ -442,10 +464,37 @@ export function registerFileRoutes(app, ctx) {
442
464
  if (stat.size > MAX_TEXT_FILE_SIZE) {
443
465
  return createErrorResponse(ApiErrorCode.INVALID_INPUT, `File too large (${Math.round(stat.size / 1024 / 1024)}MB > ${MAX_TEXT_FILE_SIZE / 1024 / 1024}MB limit)`);
444
466
  }
467
+ // Read as raw bytes so we can sniff for binary content before decoding. An
468
+ // unrecognized extension (none at all, or a format not listed above) that
469
+ // is actually binary would otherwise be dumped to the viewer as UTF-8
470
+ // mojibake; a NUL byte in the first 8KB is a reliable binary signal that
471
+ // (unlike a static extension list) catches arbitrary binary formats.
472
+ const fileBuffer = await fs.readFile(resolvedPath);
473
+ const buf = Buffer.isBuffer(fileBuffer) ? fileBuffer : Buffer.from(String(fileBuffer));
474
+ const sniffLength = Math.min(buf.length, 8192);
475
+ let looksBinary = false;
476
+ for (let i = 0; i < sniffLength; i++) {
477
+ if (buf[i] === 0) {
478
+ looksBinary = true;
479
+ break;
480
+ }
481
+ }
482
+ if (looksBinary) {
483
+ return {
484
+ success: true,
485
+ data: {
486
+ path: filePath,
487
+ size: stat.size,
488
+ type: 'binary',
489
+ extension: ext,
490
+ url: fileRawUrl,
491
+ },
492
+ };
493
+ }
445
494
  // Read text file with line limit (bounded to prevent DoS)
446
495
  const MAX_LINES_LIMIT = 10000;
447
496
  const maxLines = Math.min(parseInt(lines || '500', 10) || 500, MAX_LINES_LIMIT);
448
- const content = await fs.readFile(resolvedPath, 'utf-8');
497
+ const content = buf.toString('utf-8');
449
498
  const allLines = content.split('\n');
450
499
  const truncatedContent = allLines.length > maxLines;
451
500
  const displayContent = truncatedContent ? allLines.slice(0, maxLines).join('\n') : content;
@@ -503,9 +552,16 @@ export function registerFileRoutes(app, ctx) {
503
552
  mp4: 'video/mp4',
504
553
  webm: 'video/webm',
505
554
  mov: 'video/quicktime',
555
+ m4v: 'video/mp4',
556
+ ogv: 'video/ogg',
506
557
  mp3: 'audio/mpeg',
507
558
  wav: 'audio/wav',
508
559
  ogg: 'audio/ogg',
560
+ oga: 'audio/ogg',
561
+ opus: 'audio/ogg',
562
+ m4a: 'audio/mp4',
563
+ aac: 'audio/aac',
564
+ flac: 'audio/flac',
509
565
  pdf: 'application/pdf',
510
566
  json: 'application/json',
511
567
  };