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.
- package/README.md +2 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +27 -0
- package/dist/cli.js.map +1 -1
- package/dist/config/dependency-registry.d.ts +42 -0
- package/dist/config/dependency-registry.d.ts.map +1 -0
- package/dist/config/dependency-registry.js +104 -0
- package/dist/config/dependency-registry.js.map +1 -0
- package/dist/config/workflow-config.d.ts +24 -0
- package/dist/config/workflow-config.d.ts.map +1 -0
- package/dist/config/workflow-config.js +24 -0
- package/dist/config/workflow-config.js.map +1 -0
- package/dist/hooks-config.d.ts +17 -0
- package/dist/hooks-config.d.ts.map +1 -1
- package/dist/hooks-config.js +39 -0
- package/dist/hooks-config.js.map +1 -1
- package/dist/subagent-watcher.d.ts +34 -0
- package/dist/subagent-watcher.d.ts.map +1 -1
- package/dist/subagent-watcher.js +147 -4
- package/dist/subagent-watcher.js.map +1 -1
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +1 -0
- package/dist/types/index.js.map +1 -1
- package/dist/types/workflow-run.d.ts +130 -0
- package/dist/types/workflow-run.d.ts.map +1 -0
- package/dist/types/workflow-run.js +20 -0
- package/dist/types/workflow-run.js.map +1 -0
- package/dist/utils/dependency-checker.d.ts +42 -0
- package/dist/utils/dependency-checker.d.ts.map +1 -0
- package/dist/utils/dependency-checker.js +167 -0
- package/dist/utils/dependency-checker.js.map +1 -0
- package/dist/utils/dependency-report.d.ts +26 -0
- package/dist/utils/dependency-report.d.ts.map +1 -0
- package/dist/utils/dependency-report.js +68 -0
- package/dist/utils/dependency-report.js.map +1 -0
- package/dist/web/middleware/auth.d.ts +4 -8
- package/dist/web/middleware/auth.d.ts.map +1 -1
- package/dist/web/middleware/auth.js +14 -22
- package/dist/web/middleware/auth.js.map +1 -1
- package/dist/web/public/api-client.c9b1cddc.js.gz +0 -0
- package/dist/web/public/app.eaa14cdd.js +37 -0
- package/dist/web/public/app.eaa14cdd.js.br +0 -0
- package/dist/web/public/app.eaa14cdd.js.gz +0 -0
- package/dist/web/public/{constants.00fa5405.js → constants.1c779517.js} +20 -0
- package/dist/web/public/constants.1c779517.js.br +0 -0
- package/dist/web/public/constants.1c779517.js.gz +0 -0
- package/dist/web/public/image-input.0ea86695.js.gz +0 -0
- package/dist/web/public/index.html +48 -10
- package/dist/web/public/index.html.br +0 -0
- package/dist/web/public/index.html.gz +0 -0
- package/dist/web/public/input-cjk.b8686b5e.js.gz +0 -0
- package/dist/web/public/keyboard-accessory.bc753cc7.js.gz +0 -0
- package/dist/web/public/{mobile-handlers.763a7439.js → mobile-handlers.db3dc3c8.js} +3 -0
- package/dist/web/public/mobile-handlers.db3dc3c8.js.br +0 -0
- package/dist/web/public/mobile-handlers.db3dc3c8.js.gz +0 -0
- package/dist/web/public/mobile.06b38d3a.css.gz +0 -0
- package/dist/web/public/notification-manager.9c984ac2.js.gz +0 -0
- package/dist/web/public/orchestrator-panel.js.gz +0 -0
- package/dist/web/public/{panels-ui.2f467969.js → panels-ui.f3f08e26.js} +48 -48
- package/dist/web/public/panels-ui.f3f08e26.js.br +0 -0
- package/dist/web/public/panels-ui.f3f08e26.js.gz +0 -0
- package/dist/web/public/ralph-panel.6de2d0f8.js.gz +0 -0
- package/dist/web/public/ralph-wizard.13a1831e.js.gz +0 -0
- package/dist/web/public/respawn-ui.2d249da9.js.gz +0 -0
- package/dist/web/public/sanitize-html.bc7078d6.js +1 -0
- package/dist/web/public/sanitize-html.bc7078d6.js.br +0 -0
- package/dist/web/public/sanitize-html.bc7078d6.js.gz +0 -0
- package/dist/web/public/session-ui.1463b824.js.gz +0 -0
- package/dist/web/public/settings-ui.601c30c1.js +55 -0
- package/dist/web/public/settings-ui.601c30c1.js.br +0 -0
- package/dist/web/public/settings-ui.601c30c1.js.gz +0 -0
- package/dist/web/public/styles.7e612fc4.css +1 -0
- package/dist/web/public/styles.7e612fc4.css.br +0 -0
- package/dist/web/public/styles.7e612fc4.css.gz +0 -0
- package/dist/web/public/subagent-windows.a366a4ad.js.gz +0 -0
- package/dist/web/public/sw.js.gz +0 -0
- package/dist/web/public/terminal-ui.a7e046da.js +3 -0
- package/dist/web/public/terminal-ui.a7e046da.js.br +0 -0
- package/dist/web/public/terminal-ui.a7e046da.js.gz +0 -0
- package/dist/web/public/ultracode-panel.js +304 -0
- package/dist/web/public/ultracode-panel.js.br +0 -0
- package/dist/web/public/ultracode-panel.js.gz +0 -0
- package/dist/web/public/upload.html.gz +0 -0
- package/dist/web/public/vendor/dompurify.min.js +3 -0
- package/dist/web/public/vendor/dompurify.min.js.br +0 -0
- package/dist/web/public/vendor/dompurify.min.js.gz +0 -0
- package/dist/web/public/vendor/marked.min.js.gz +0 -0
- package/dist/web/public/vendor/xterm-addon-fit.min.js.gz +0 -0
- package/dist/web/public/vendor/xterm-addon-serialize.min.js.gz +0 -0
- package/dist/web/public/vendor/xterm-addon-unicode11.min.js.gz +0 -0
- package/dist/web/public/vendor/xterm-addon-webgl.min.js.gz +0 -0
- package/dist/web/public/vendor/xterm-zerolag-input.137ad9f0.js.gz +0 -0
- package/dist/web/public/vendor/xterm.css.gz +0 -0
- package/dist/web/public/vendor/xterm.min.js.gz +0 -0
- package/dist/web/public/voice-input.085e9e73.js.gz +0 -0
- package/dist/web/routes/file-routes.d.ts.map +1 -1
- package/dist/web/routes/file-routes.js +80 -24
- package/dist/web/routes/file-routes.js.map +1 -1
- package/dist/web/routes/session-routes.d.ts.map +1 -1
- package/dist/web/routes/session-routes.js +13 -1
- package/dist/web/routes/session-routes.js.map +1 -1
- package/dist/web/routes/system-routes.d.ts.map +1 -1
- package/dist/web/routes/system-routes.js +21 -0
- package/dist/web/routes/system-routes.js.map +1 -1
- package/dist/web/schemas.d.ts +1 -0
- package/dist/web/schemas.d.ts.map +1 -1
- package/dist/web/schemas.js +2 -0
- package/dist/web/schemas.js.map +1 -1
- package/dist/web/server.d.ts +12 -0
- package/dist/web/server.d.ts.map +1 -1
- package/dist/web/server.js +56 -1
- package/dist/web/server.js.map +1 -1
- package/dist/web/sse-events.d.ts +10 -0
- package/dist/web/sse-events.d.ts.map +1 -1
- package/dist/web/sse-events.js +12 -0
- package/dist/web/sse-events.js.map +1 -1
- package/dist/workflow-run-watcher.d.ts +76 -0
- package/dist/workflow-run-watcher.d.ts.map +1 -0
- package/dist/workflow-run-watcher.js +327 -0
- package/dist/workflow-run-watcher.js.map +1 -0
- package/package.json +1 -1
- package/dist/web/public/app.9daf49ad.js +0 -37
- package/dist/web/public/app.9daf49ad.js.br +0 -0
- package/dist/web/public/app.9daf49ad.js.gz +0 -0
- package/dist/web/public/constants.00fa5405.js.br +0 -0
- package/dist/web/public/constants.00fa5405.js.gz +0 -0
- package/dist/web/public/mobile-handlers.763a7439.js.br +0 -0
- package/dist/web/public/mobile-handlers.763a7439.js.gz +0 -0
- package/dist/web/public/panels-ui.2f467969.js.br +0 -0
- package/dist/web/public/panels-ui.2f467969.js.gz +0 -0
- package/dist/web/public/settings-ui.44b99ce0.js +0 -55
- package/dist/web/public/settings-ui.44b99ce0.js.br +0 -0
- package/dist/web/public/settings-ui.44b99ce0.js.gz +0 -0
- package/dist/web/public/styles.c13845d5.css +0 -1
- package/dist/web/public/styles.c13845d5.css.br +0 -0
- package/dist/web/public/styles.c13845d5.css.gz +0 -0
- package/dist/web/public/terminal-ui.5bf97f7e.js +0 -3
- package/dist/web/public/terminal-ui.5bf97f7e.js.br +0 -0
- 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
|
|
Binary file
|
|
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
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
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,
|
|
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
|
-
//
|
|
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
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
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
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
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:
|
|
456
|
+
type: mediaType ?? 'binary',
|
|
435
457
|
extension: ext,
|
|
436
|
-
url:
|
|
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 =
|
|
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
|
};
|