@imgly/codesign-mcp 0.1.7 → 0.1.8
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/assets/docs/api/index.d.ts +9 -3
- package/assets/docs/guide/SKILL.md +2 -2
- package/assets/docs/guide/api/node/classes/creativeengine.md +13 -13
- package/assets/docs/guide/edit-image/transform/scale.md +1 -3
- package/assets/docs/guide/filters-and-effects/duotone.md +1 -1
- package/assets/docs/guide/get-started/bun.md +0 -2
- package/assets/docs/guide/get-started/deno.md +0 -1
- package/assets/docs/guide/get-started/vanilla.md +0 -2
- package/assets/docs/guide/import-media/default-assets.md +20 -24
- package/assets/docs/guide/plugins/print-ready-pdf.md +0 -2
- package/assets/docs/guide/serve-assets.md +80 -23
- package/assets/docs/handbook/SKILL.md +6 -7
- package/dist/assets/inline.generated.js +12 -12
- package/dist/assets/inline.generated.js.map +1 -1
- package/dist/cli/http.d.ts.map +1 -1
- package/dist/cli/http.js +17 -3
- package/dist/cli/http.js.map +1 -1
- package/dist/cli/stdio.d.ts.map +1 -1
- package/dist/cli/stdio.js +44 -15
- package/dist/cli/stdio.js.map +1 -1
- package/dist/cli/trial-license.generated.d.ts +1 -1
- package/dist/cli/trial-license.generated.js +1 -1
- package/dist/cli/trial-license.generated.js.map +1 -1
- package/dist/config.d.ts +6 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +8 -0
- package/dist/config.js.map +1 -1
- package/dist/diagnostics/bundle.d.ts +3 -0
- package/dist/diagnostics/bundle.d.ts.map +1 -1
- package/dist/diagnostics/bundle.js +25 -22
- package/dist/diagnostics/bundle.js.map +1 -1
- package/dist/diagnostics/prompt.d.ts +3 -0
- package/dist/diagnostics/prompt.d.ts.map +1 -0
- package/dist/diagnostics/prompt.js +21 -0
- package/dist/diagnostics/prompt.js.map +1 -0
- package/dist/diagnostics/router.d.ts +12 -0
- package/dist/diagnostics/router.d.ts.map +1 -0
- package/dist/diagnostics/router.js +64 -0
- package/dist/diagnostics/router.js.map +1 -0
- package/dist/diagnostics/spa-assets.generated.d.ts +2 -0
- package/dist/diagnostics/spa-assets.generated.d.ts.map +1 -0
- package/dist/diagnostics/spa-assets.generated.js +1231 -0
- package/dist/diagnostics/spa-assets.generated.js.map +1 -0
- package/dist/diagnostics/status.d.ts +38 -0
- package/dist/diagnostics/status.d.ts.map +1 -0
- package/dist/diagnostics/status.js +53 -0
- package/dist/diagnostics/status.js.map +1 -0
- package/dist/diagnostics/summary.d.ts +48 -0
- package/dist/diagnostics/summary.d.ts.map +1 -0
- package/dist/diagnostics/summary.js +116 -0
- package/dist/diagnostics/summary.js.map +1 -0
- package/dist/diagnostics/tool.d.ts +16 -0
- package/dist/diagnostics/tool.d.ts.map +1 -0
- package/dist/diagnostics/tool.js +90 -0
- package/dist/diagnostics/tool.js.map +1 -0
- package/dist/engine/asset-sources.d.ts +8 -0
- package/dist/engine/asset-sources.d.ts.map +1 -1
- package/dist/engine/asset-sources.js +9 -2
- package/dist/engine/asset-sources.js.map +1 -1
- package/dist/engine/uri-resolver.js.map +1 -1
- package/dist/server.d.ts +25 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +24 -4
- package/dist/server.js.map +1 -1
- package/dist/tools/engine.d.ts +79 -14
- package/dist/tools/engine.d.ts.map +1 -1
- package/dist/tools/engine.js +119 -20
- package/dist/tools/engine.js.map +1 -1
- package/dist/tools/export.d.ts.map +1 -1
- package/dist/tools/export.js +34 -4
- package/dist/tools/export.js.map +1 -1
- package/dist/tools/index.js +2 -2
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/load.d.ts.map +1 -1
- package/dist/tools/load.js +26 -19
- package/dist/tools/load.js.map +1 -1
- package/dist/tools/preview.d.ts.map +1 -1
- package/dist/tools/preview.js +60 -40
- package/dist/tools/preview.js.map +1 -1
- package/dist/tools/{open-editor.d.ts → view.d.ts} +3 -3
- package/dist/tools/view.d.ts.map +1 -0
- package/dist/tools/{open-editor.js → view.js} +20 -18
- package/dist/tools/view.js.map +1 -0
- package/dist/viewer/listener.d.ts +2 -0
- package/dist/viewer/listener.d.ts.map +1 -1
- package/dist/viewer/listener.js +2 -0
- package/dist/viewer/listener.js.map +1 -1
- package/dist/viewer/presigned-r2-url-builder.d.ts +1 -1
- package/dist/viewer/presigned-r2-url-builder.js +1 -1
- package/dist/viewer/presigned-r2-url-builder.js.map +1 -1
- package/dist/viewer/router.d.ts.map +1 -1
- package/dist/viewer/router.js +8 -17
- package/dist/viewer/router.js.map +1 -1
- package/dist/viewer/session-mount.js.map +1 -1
- package/dist/viewer/spa-assets.generated.d.ts.map +1 -1
- package/dist/viewer/spa-assets.generated.js +750 -869
- package/dist/viewer/spa-assets.generated.js.map +1 -1
- package/dist/viewer/url-builder.d.ts +4 -3
- package/dist/viewer/url-builder.d.ts.map +1 -1
- package/dist/viewer/url-builder.js +4 -4
- package/dist/viewer/url-builder.js.map +1 -1
- package/package.json +6 -5
- package/dist/tools/open-editor.d.ts.map +0 -1
- package/dist/tools/open-editor.js.map +0 -1
|
@@ -0,0 +1,1231 @@
|
|
|
1
|
+
// AUTO-GENERATED by scripts/generate-spa-assets.mjs. Do not edit by hand.
|
|
2
|
+
export const SPA_ASSETS = {
|
|
3
|
+
"index.html": `<!doctype html>
|
|
4
|
+
<html lang="en">
|
|
5
|
+
<head>
|
|
6
|
+
<meta charset="utf-8" />
|
|
7
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
8
|
+
<title>codesign diagnostics</title>
|
|
9
|
+
<style>
|
|
10
|
+
:root {
|
|
11
|
+
color-scheme: light dark;
|
|
12
|
+
--c-lock_wait: #e67e22;
|
|
13
|
+
--c-workspace_read: #3498db;
|
|
14
|
+
--c-engine_scene_load: #9b59b6;
|
|
15
|
+
--c-engine_execute: #2ecc71;
|
|
16
|
+
--c-engine_scene_save: #1abc9c;
|
|
17
|
+
--c-engine_scene_thumbnail: #f39c12;
|
|
18
|
+
--c-workspace_write: #e91e63;
|
|
19
|
+
--line: #8884;
|
|
20
|
+
--line-soft: #8882;
|
|
21
|
+
}
|
|
22
|
+
* {
|
|
23
|
+
box-sizing: border-box;
|
|
24
|
+
}
|
|
25
|
+
body {
|
|
26
|
+
font:
|
|
27
|
+
14px/1.5 system-ui,
|
|
28
|
+
sans-serif;
|
|
29
|
+
margin: 0 auto;
|
|
30
|
+
padding: 1.5rem;
|
|
31
|
+
max-width: 64rem;
|
|
32
|
+
}
|
|
33
|
+
h1 {
|
|
34
|
+
font-size: 1.15rem;
|
|
35
|
+
margin: 0 0 0.25rem;
|
|
36
|
+
}
|
|
37
|
+
h2 {
|
|
38
|
+
font-size: 0.82rem;
|
|
39
|
+
text-transform: uppercase;
|
|
40
|
+
letter-spacing: 0.05em;
|
|
41
|
+
opacity: 0.7;
|
|
42
|
+
margin: 1.6rem 0 0.6rem;
|
|
43
|
+
}
|
|
44
|
+
#status {
|
|
45
|
+
font-size: 0.8rem;
|
|
46
|
+
opacity: 0.7;
|
|
47
|
+
margin-left: 0.5rem;
|
|
48
|
+
font-weight: normal;
|
|
49
|
+
}
|
|
50
|
+
#meta {
|
|
51
|
+
font-size: 0.8rem;
|
|
52
|
+
opacity: 0.75;
|
|
53
|
+
margin-bottom: 1.2rem;
|
|
54
|
+
font-variant-numeric: tabular-nums;
|
|
55
|
+
}
|
|
56
|
+
#meta .sep {
|
|
57
|
+
opacity: 0.4;
|
|
58
|
+
margin: 0 0.5rem;
|
|
59
|
+
}
|
|
60
|
+
.muted {
|
|
61
|
+
opacity: 0.6;
|
|
62
|
+
}
|
|
63
|
+
.err {
|
|
64
|
+
color: #c0392b;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/* Drop zone */
|
|
68
|
+
#drop-zone {
|
|
69
|
+
border: 2px dashed var(--line);
|
|
70
|
+
border-radius: 8px;
|
|
71
|
+
padding: 2rem;
|
|
72
|
+
text-align: center;
|
|
73
|
+
margin: 1rem 0 1.5rem;
|
|
74
|
+
cursor: pointer;
|
|
75
|
+
}
|
|
76
|
+
#drop-zone.over {
|
|
77
|
+
background: #3498db22;
|
|
78
|
+
border-color: #3498db;
|
|
79
|
+
}
|
|
80
|
+
#file-input {
|
|
81
|
+
display: none;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/* Summary cards */
|
|
85
|
+
.cards {
|
|
86
|
+
display: flex;
|
|
87
|
+
flex-wrap: wrap;
|
|
88
|
+
gap: 0.75rem;
|
|
89
|
+
margin-bottom: 0.5rem;
|
|
90
|
+
}
|
|
91
|
+
.card {
|
|
92
|
+
border: 1px solid var(--line);
|
|
93
|
+
border-radius: 8px;
|
|
94
|
+
padding: 0.6rem 0.9rem;
|
|
95
|
+
min-width: 8rem;
|
|
96
|
+
flex: 1 1 8rem;
|
|
97
|
+
}
|
|
98
|
+
.card .label {
|
|
99
|
+
font-size: 0.7rem;
|
|
100
|
+
opacity: 0.65;
|
|
101
|
+
text-transform: uppercase;
|
|
102
|
+
letter-spacing: 0.04em;
|
|
103
|
+
}
|
|
104
|
+
.card .value {
|
|
105
|
+
font-size: 1.35rem;
|
|
106
|
+
font-variant-numeric: tabular-nums;
|
|
107
|
+
line-height: 1.3;
|
|
108
|
+
}
|
|
109
|
+
.card .sub {
|
|
110
|
+
font-size: 0.75rem;
|
|
111
|
+
opacity: 0.6;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/* Phase legend */
|
|
115
|
+
.legend {
|
|
116
|
+
display: flex;
|
|
117
|
+
flex-wrap: wrap;
|
|
118
|
+
gap: 0.25rem 1rem;
|
|
119
|
+
font-size: 0.75rem;
|
|
120
|
+
opacity: 0.85;
|
|
121
|
+
margin: 0 0 0.5rem;
|
|
122
|
+
}
|
|
123
|
+
.legend span {
|
|
124
|
+
display: inline-flex;
|
|
125
|
+
align-items: center;
|
|
126
|
+
gap: 0.35rem;
|
|
127
|
+
}
|
|
128
|
+
.sw {
|
|
129
|
+
width: 0.7rem;
|
|
130
|
+
height: 0.7rem;
|
|
131
|
+
border-radius: 2px;
|
|
132
|
+
display: inline-block;
|
|
133
|
+
flex: none;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/* Shared stacked phase bar */
|
|
137
|
+
.phase-bar {
|
|
138
|
+
display: flex;
|
|
139
|
+
height: 1.1rem;
|
|
140
|
+
border-radius: 4px;
|
|
141
|
+
overflow: hidden;
|
|
142
|
+
}
|
|
143
|
+
.phase-seg {
|
|
144
|
+
height: 100%;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/* Per-tool aggregate table */
|
|
148
|
+
.table-wrap {
|
|
149
|
+
overflow-x: auto;
|
|
150
|
+
}
|
|
151
|
+
table {
|
|
152
|
+
border-collapse: collapse;
|
|
153
|
+
width: 100%;
|
|
154
|
+
}
|
|
155
|
+
th,
|
|
156
|
+
td {
|
|
157
|
+
text-align: right;
|
|
158
|
+
padding: 0.35rem 0.7rem;
|
|
159
|
+
border-bottom: 1px solid var(--line-soft);
|
|
160
|
+
font-variant-numeric: tabular-nums;
|
|
161
|
+
white-space: nowrap;
|
|
162
|
+
}
|
|
163
|
+
th {
|
|
164
|
+
font-size: 0.72rem;
|
|
165
|
+
text-transform: uppercase;
|
|
166
|
+
letter-spacing: 0.03em;
|
|
167
|
+
opacity: 0.6;
|
|
168
|
+
font-weight: 600;
|
|
169
|
+
}
|
|
170
|
+
th:first-child,
|
|
171
|
+
td:first-child {
|
|
172
|
+
text-align: left;
|
|
173
|
+
}
|
|
174
|
+
td.bar-cell {
|
|
175
|
+
width: 40%;
|
|
176
|
+
min-width: 12rem;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/* Tabs */
|
|
180
|
+
.tabs {
|
|
181
|
+
display: flex;
|
|
182
|
+
gap: 0.25rem;
|
|
183
|
+
border-bottom: 1px solid var(--line);
|
|
184
|
+
margin: 1.6rem 0 0.9rem;
|
|
185
|
+
}
|
|
186
|
+
.tab {
|
|
187
|
+
background: none;
|
|
188
|
+
border: none;
|
|
189
|
+
border-bottom: 2px solid transparent;
|
|
190
|
+
margin-bottom: -1px;
|
|
191
|
+
padding: 0.5rem 0.9rem;
|
|
192
|
+
font: inherit;
|
|
193
|
+
font-size: 0.85rem;
|
|
194
|
+
color: inherit;
|
|
195
|
+
opacity: 0.6;
|
|
196
|
+
cursor: pointer;
|
|
197
|
+
}
|
|
198
|
+
.tab:hover {
|
|
199
|
+
opacity: 0.9;
|
|
200
|
+
}
|
|
201
|
+
.tab.active {
|
|
202
|
+
opacity: 1;
|
|
203
|
+
font-weight: 600;
|
|
204
|
+
border-bottom-color: currentColor;
|
|
205
|
+
}
|
|
206
|
+
.tabpanel[hidden] {
|
|
207
|
+
display: none;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/* Per-call accordion */
|
|
211
|
+
.calls-head,
|
|
212
|
+
.call-row {
|
|
213
|
+
display: grid;
|
|
214
|
+
grid-template-columns: 5.5rem 7rem 1fr 5rem 4.5rem;
|
|
215
|
+
gap: 0.6rem;
|
|
216
|
+
align-items: center;
|
|
217
|
+
}
|
|
218
|
+
.calls-head {
|
|
219
|
+
font-size: 0.72rem;
|
|
220
|
+
text-transform: uppercase;
|
|
221
|
+
letter-spacing: 0.03em;
|
|
222
|
+
opacity: 0.6;
|
|
223
|
+
padding: 0 0.4rem 0.35rem;
|
|
224
|
+
border-bottom: 1px solid var(--line);
|
|
225
|
+
}
|
|
226
|
+
.call {
|
|
227
|
+
border-bottom: 1px solid var(--line-soft);
|
|
228
|
+
}
|
|
229
|
+
.call-row {
|
|
230
|
+
cursor: pointer;
|
|
231
|
+
list-style: none;
|
|
232
|
+
padding: 0.4rem;
|
|
233
|
+
font-size: 0.85rem;
|
|
234
|
+
font-variant-numeric: tabular-nums;
|
|
235
|
+
}
|
|
236
|
+
.call-row::-webkit-details-marker {
|
|
237
|
+
display: none;
|
|
238
|
+
}
|
|
239
|
+
.call-row:hover,
|
|
240
|
+
.call[open] .call-row {
|
|
241
|
+
background: #8881;
|
|
242
|
+
}
|
|
243
|
+
.c-time {
|
|
244
|
+
opacity: 0.6;
|
|
245
|
+
}
|
|
246
|
+
.c-tool {
|
|
247
|
+
font-weight: 600;
|
|
248
|
+
white-space: nowrap;
|
|
249
|
+
overflow: hidden;
|
|
250
|
+
text-overflow: ellipsis;
|
|
251
|
+
}
|
|
252
|
+
.call-row .phase-bar {
|
|
253
|
+
height: 0.9rem;
|
|
254
|
+
}
|
|
255
|
+
.num {
|
|
256
|
+
text-align: right;
|
|
257
|
+
}
|
|
258
|
+
.c-status {
|
|
259
|
+
display: inline-flex;
|
|
260
|
+
align-items: center;
|
|
261
|
+
gap: 0.4rem;
|
|
262
|
+
justify-content: flex-end;
|
|
263
|
+
}
|
|
264
|
+
.chevron {
|
|
265
|
+
opacity: 0.5;
|
|
266
|
+
transition: transform 0.12s ease;
|
|
267
|
+
}
|
|
268
|
+
.call[open] .chevron {
|
|
269
|
+
transform: rotate(90deg);
|
|
270
|
+
}
|
|
271
|
+
.ok-badge {
|
|
272
|
+
font-size: 0.7rem;
|
|
273
|
+
opacity: 0.5;
|
|
274
|
+
}
|
|
275
|
+
.err-badge {
|
|
276
|
+
font-size: 0.7rem;
|
|
277
|
+
background: #c0392b;
|
|
278
|
+
color: #fff;
|
|
279
|
+
border-radius: 3px;
|
|
280
|
+
padding: 0 4px;
|
|
281
|
+
}
|
|
282
|
+
.call-body {
|
|
283
|
+
padding: 0.3rem 0.6rem 0.9rem;
|
|
284
|
+
}
|
|
285
|
+
.kv-label {
|
|
286
|
+
font-size: 0.68rem;
|
|
287
|
+
text-transform: uppercase;
|
|
288
|
+
letter-spacing: 0.04em;
|
|
289
|
+
opacity: 0.55;
|
|
290
|
+
margin: 0.8rem 0 0.35rem;
|
|
291
|
+
}
|
|
292
|
+
.phase-list {
|
|
293
|
+
display: flex;
|
|
294
|
+
flex-direction: column;
|
|
295
|
+
gap: 0.15rem;
|
|
296
|
+
}
|
|
297
|
+
.pl-row {
|
|
298
|
+
display: grid;
|
|
299
|
+
grid-template-columns: 11rem 1fr 5rem;
|
|
300
|
+
gap: 0.6rem;
|
|
301
|
+
align-items: center;
|
|
302
|
+
font-size: 0.8rem;
|
|
303
|
+
font-variant-numeric: tabular-nums;
|
|
304
|
+
}
|
|
305
|
+
.pl-name {
|
|
306
|
+
display: inline-flex;
|
|
307
|
+
align-items: center;
|
|
308
|
+
gap: 0.4rem;
|
|
309
|
+
white-space: nowrap;
|
|
310
|
+
overflow: hidden;
|
|
311
|
+
text-overflow: ellipsis;
|
|
312
|
+
}
|
|
313
|
+
.pl-track {
|
|
314
|
+
background: #8882;
|
|
315
|
+
border-radius: 2px;
|
|
316
|
+
height: 0.7rem;
|
|
317
|
+
overflow: hidden;
|
|
318
|
+
}
|
|
319
|
+
.pl-fill {
|
|
320
|
+
display: block;
|
|
321
|
+
height: 100%;
|
|
322
|
+
}
|
|
323
|
+
.meta-kv {
|
|
324
|
+
opacity: 0.7;
|
|
325
|
+
font-size: 0.78rem;
|
|
326
|
+
margin-top: 0.5rem;
|
|
327
|
+
}
|
|
328
|
+
.meta-kv code {
|
|
329
|
+
font-family: monospace;
|
|
330
|
+
}
|
|
331
|
+
pre.args {
|
|
332
|
+
margin: 0;
|
|
333
|
+
padding: 0.5rem 0.6rem;
|
|
334
|
+
background: #0002;
|
|
335
|
+
border-radius: 6px;
|
|
336
|
+
max-height: 18rem;
|
|
337
|
+
overflow: auto;
|
|
338
|
+
white-space: pre-wrap;
|
|
339
|
+
word-break: break-word;
|
|
340
|
+
font-size: 0.78rem;
|
|
341
|
+
line-height: 1.45;
|
|
342
|
+
}
|
|
343
|
+
pre.args.err {
|
|
344
|
+
color: #c0392b;
|
|
345
|
+
}
|
|
346
|
+
pre.code {
|
|
347
|
+
margin: 0;
|
|
348
|
+
padding: 0.6rem 0.7rem;
|
|
349
|
+
background: #0003;
|
|
350
|
+
border-left: 3px solid var(--c-engine_execute);
|
|
351
|
+
border-radius: 4px;
|
|
352
|
+
max-height: 26rem;
|
|
353
|
+
overflow: auto;
|
|
354
|
+
white-space: pre;
|
|
355
|
+
font-size: 0.78rem;
|
|
356
|
+
line-height: 1.5;
|
|
357
|
+
tab-size: 2;
|
|
358
|
+
}
|
|
359
|
+
.result-img {
|
|
360
|
+
display: block;
|
|
361
|
+
max-width: 360px;
|
|
362
|
+
max-height: 360px;
|
|
363
|
+
margin-top: 0.5rem;
|
|
364
|
+
border: 1px solid var(--line);
|
|
365
|
+
border-radius: 6px;
|
|
366
|
+
background: repeating-conic-gradient(#8882 0 25%, #0000 0 50%) 0 / 16px
|
|
367
|
+
16px;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
/* Timeline */
|
|
371
|
+
.timeline {
|
|
372
|
+
list-style: none;
|
|
373
|
+
padding: 0;
|
|
374
|
+
margin: 0 0 1rem;
|
|
375
|
+
max-height: 22rem;
|
|
376
|
+
overflow: auto;
|
|
377
|
+
font-variant-numeric: tabular-nums;
|
|
378
|
+
}
|
|
379
|
+
.timeline li {
|
|
380
|
+
display: flex;
|
|
381
|
+
flex-wrap: wrap;
|
|
382
|
+
gap: 0.5rem;
|
|
383
|
+
padding: 0.2rem 0;
|
|
384
|
+
border-bottom: 1px solid var(--line-soft);
|
|
385
|
+
font-size: 0.85rem;
|
|
386
|
+
}
|
|
387
|
+
.tl-ts {
|
|
388
|
+
opacity: 0.6;
|
|
389
|
+
min-width: 5rem;
|
|
390
|
+
}
|
|
391
|
+
.tl-start {
|
|
392
|
+
font-weight: 600;
|
|
393
|
+
}
|
|
394
|
+
.tl-err {
|
|
395
|
+
color: #c0392b;
|
|
396
|
+
width: 100%;
|
|
397
|
+
padding-left: 5.5rem;
|
|
398
|
+
font-family: monospace;
|
|
399
|
+
font-size: 0.8rem;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
/* Engine log */
|
|
403
|
+
.log-tail {
|
|
404
|
+
font-family: monospace;
|
|
405
|
+
font-size: 0.8rem;
|
|
406
|
+
max-height: 24rem;
|
|
407
|
+
overflow: auto;
|
|
408
|
+
background: #0002;
|
|
409
|
+
border-radius: 6px;
|
|
410
|
+
padding: 0.5rem;
|
|
411
|
+
margin-bottom: 1.5rem;
|
|
412
|
+
}
|
|
413
|
+
.log-line {
|
|
414
|
+
display: flex;
|
|
415
|
+
gap: 0.5rem;
|
|
416
|
+
padding: 0.05rem 0;
|
|
417
|
+
}
|
|
418
|
+
.log-text {
|
|
419
|
+
white-space: pre-wrap;
|
|
420
|
+
word-break: break-all;
|
|
421
|
+
}
|
|
422
|
+
.lvl {
|
|
423
|
+
min-width: 3.5rem;
|
|
424
|
+
font-weight: bold;
|
|
425
|
+
opacity: 0.8;
|
|
426
|
+
}
|
|
427
|
+
.lvl-warn {
|
|
428
|
+
color: #e67e22;
|
|
429
|
+
}
|
|
430
|
+
.lvl-error {
|
|
431
|
+
color: #c0392b;
|
|
432
|
+
}
|
|
433
|
+
.lvl-debug {
|
|
434
|
+
opacity: 0.5;
|
|
435
|
+
}
|
|
436
|
+
.lvl-trace {
|
|
437
|
+
opacity: 0.4;
|
|
438
|
+
}
|
|
439
|
+
</style>
|
|
440
|
+
<!-- CODE-62: served-mode injection. The MCP router replaces the whole
|
|
441
|
+
delimited placeholder with window.__DIAGNOSTICS_STREAM__/BUNDLE__
|
|
442
|
+
assignments; the standalone serve.mjs blanks it. Unreplaced (raw
|
|
443
|
+
file://) it is an empty comment — a valid no-op. -->
|
|
444
|
+
<script>
|
|
445
|
+
/*__DIAGNOSTICS_INJECT__*/
|
|
446
|
+
</script>
|
|
447
|
+
</head>
|
|
448
|
+
<body>
|
|
449
|
+
<h1>
|
|
450
|
+
codesign diagnostics
|
|
451
|
+
<span id="status">drop a bundle (.zip) or .ndjson, or use ?file=</span>
|
|
452
|
+
<a
|
|
453
|
+
id="download-zip"
|
|
454
|
+
href="#"
|
|
455
|
+
download
|
|
456
|
+
style="display: none; margin-left: 12px; font-weight: 600"
|
|
457
|
+
>⬇ Download ZIP (send to IMG.LY)</a
|
|
458
|
+
>
|
|
459
|
+
</h1>
|
|
460
|
+
<div id="meta"></div>
|
|
461
|
+
|
|
462
|
+
<div id="drop-zone">
|
|
463
|
+
<label for="file-input"
|
|
464
|
+
>Drop a diagnostics <b>.zip</b> (or a raw <b>.ndjson</b>) here, or
|
|
465
|
+
<u>click to browse</u></label
|
|
466
|
+
>
|
|
467
|
+
<input type="file" id="file-input" accept=".zip,.ndjson,.txt,.json" />
|
|
468
|
+
</div>
|
|
469
|
+
|
|
470
|
+
<div class="cards" id="cards"></div>
|
|
471
|
+
<div class="legend" id="legend"></div>
|
|
472
|
+
|
|
473
|
+
<h2>Per-tool</h2>
|
|
474
|
+
<div class="table-wrap">
|
|
475
|
+
<table>
|
|
476
|
+
<thead>
|
|
477
|
+
<tr>
|
|
478
|
+
<th>tool</th>
|
|
479
|
+
<th>calls</th>
|
|
480
|
+
<th>err%</th>
|
|
481
|
+
<th>mean ms</th>
|
|
482
|
+
<th>max ms</th>
|
|
483
|
+
<th>phase breakdown</th>
|
|
484
|
+
</tr>
|
|
485
|
+
</thead>
|
|
486
|
+
<tbody id="tool-agg">
|
|
487
|
+
<tr>
|
|
488
|
+
<td colspan="6" class="muted">no events loaded</td>
|
|
489
|
+
</tr>
|
|
490
|
+
</tbody>
|
|
491
|
+
</table>
|
|
492
|
+
</div>
|
|
493
|
+
|
|
494
|
+
<div class="tabs" role="tablist">
|
|
495
|
+
<button
|
|
496
|
+
class="tab active"
|
|
497
|
+
data-tab="calls"
|
|
498
|
+
role="tab"
|
|
499
|
+
aria-selected="true"
|
|
500
|
+
>
|
|
501
|
+
Per-call detail
|
|
502
|
+
</button>
|
|
503
|
+
<button class="tab" data-tab="timeline" role="tab" aria-selected="false">
|
|
504
|
+
Timeline
|
|
505
|
+
</button>
|
|
506
|
+
<button class="tab" data-tab="log" role="tab" aria-selected="false">
|
|
507
|
+
Engine log
|
|
508
|
+
</button>
|
|
509
|
+
</div>
|
|
510
|
+
|
|
511
|
+
<div class="tabpanel" id="panel-calls" role="tabpanel">
|
|
512
|
+
<div id="tool-calls"><p class="muted">no events loaded</p></div>
|
|
513
|
+
</div>
|
|
514
|
+
<div class="tabpanel" id="panel-timeline" role="tabpanel" hidden>
|
|
515
|
+
<ul class="timeline" id="timeline">
|
|
516
|
+
<li class="muted">no events loaded</li>
|
|
517
|
+
</ul>
|
|
518
|
+
</div>
|
|
519
|
+
<div class="tabpanel" id="panel-log" role="tabpanel" hidden>
|
|
520
|
+
<div class="log-tail" id="log-tail">
|
|
521
|
+
<span class="muted">no events loaded</span>
|
|
522
|
+
</div>
|
|
523
|
+
</div>
|
|
524
|
+
|
|
525
|
+
<script>
|
|
526
|
+
// ---- Untrusted-text hardening: escape every event/meta-derived string
|
|
527
|
+
// before it reaches innerHTML (bundles come from a user's machine).
|
|
528
|
+
const esc = (s) =>
|
|
529
|
+
String(s)
|
|
530
|
+
.replace(/&/g, '&')
|
|
531
|
+
.replace(/</g, '<')
|
|
532
|
+
.replace(/>/g, '>')
|
|
533
|
+
.replace(/"/g, '"')
|
|
534
|
+
.replace(/'/g, ''');
|
|
535
|
+
|
|
536
|
+
const PHASE_ORDER = [
|
|
537
|
+
'lock_wait',
|
|
538
|
+
'workspace_read',
|
|
539
|
+
'engine_scene_load',
|
|
540
|
+
'engine_execute',
|
|
541
|
+
'engine_scene_save',
|
|
542
|
+
'engine_scene_thumbnail',
|
|
543
|
+
'workspace_write'
|
|
544
|
+
];
|
|
545
|
+
const OS_NAMES = { darwin: 'macOS', linux: 'Linux', win32: 'Windows' };
|
|
546
|
+
const TAB_LABELS = {
|
|
547
|
+
calls: 'Per-call detail',
|
|
548
|
+
timeline: 'Timeline',
|
|
549
|
+
log: 'Engine log'
|
|
550
|
+
};
|
|
551
|
+
|
|
552
|
+
const safePhase = (p) => String(p).replace(/[^a-z0-9_]/gi, '_');
|
|
553
|
+
const fmtTs = (n) => new Date(n).toLocaleTimeString();
|
|
554
|
+
const round = (n) => Math.round(n).toLocaleString();
|
|
555
|
+
|
|
556
|
+
// Humanize a millisecond duration: ms → s → m → h.
|
|
557
|
+
function fmtDur(ms) {
|
|
558
|
+
if (ms == null || !isFinite(ms)) return '—';
|
|
559
|
+
if (ms < 1000) return Math.round(ms) + ' ms';
|
|
560
|
+
const s = ms / 1000;
|
|
561
|
+
if (s < 60) return s.toFixed(1) + ' s';
|
|
562
|
+
const m = s / 60;
|
|
563
|
+
if (m < 60) return m.toFixed(m < 10 ? 1 : 0) + ' m';
|
|
564
|
+
return (m / 60).toFixed(1) + ' h';
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
function setTabCount(name, n) {
|
|
568
|
+
const btn = document.querySelector('.tab[data-tab="' + name + '"]');
|
|
569
|
+
if (btn) btn.textContent = TAB_LABELS[name] + ' (' + n + ')';
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
function parseNdjson(text) {
|
|
573
|
+
const events = [];
|
|
574
|
+
for (const line of text.split('\\n')) {
|
|
575
|
+
const t = line.trim();
|
|
576
|
+
if (!t) continue;
|
|
577
|
+
try {
|
|
578
|
+
events.push(JSON.parse(t));
|
|
579
|
+
} catch {
|
|
580
|
+
/* skip malformed line */
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
return events;
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
// ---- Minimal in-browser ZIP reader for the bundles \`diagnose\` writes.
|
|
587
|
+
// Writer contract (src/diagnostics/bundle.ts): flags=0, method=8 (raw
|
|
588
|
+
// deflate), sizes in each local header — so we walk local headers in order
|
|
589
|
+
// and inflate with DecompressionStream('deflate-raw').
|
|
590
|
+
async function unzip(arrayBuffer) {
|
|
591
|
+
const dv = new DataView(arrayBuffer);
|
|
592
|
+
const bytes = new Uint8Array(arrayBuffer);
|
|
593
|
+
const td = new TextDecoder();
|
|
594
|
+
const out = {};
|
|
595
|
+
let pos = 0;
|
|
596
|
+
while (
|
|
597
|
+
pos + 4 <= dv.byteLength &&
|
|
598
|
+
dv.getUint32(pos, true) === 0x04034b50
|
|
599
|
+
) {
|
|
600
|
+
const method = dv.getUint16(pos + 8, true);
|
|
601
|
+
const compSize = dv.getUint32(pos + 18, true);
|
|
602
|
+
const nameLen = dv.getUint16(pos + 26, true);
|
|
603
|
+
const extraLen = dv.getUint16(pos + 28, true);
|
|
604
|
+
const nameStart = pos + 30;
|
|
605
|
+
const name = td.decode(
|
|
606
|
+
bytes.subarray(nameStart, nameStart + nameLen)
|
|
607
|
+
);
|
|
608
|
+
const dataStart = nameStart + nameLen + extraLen;
|
|
609
|
+
const comp = bytes.subarray(dataStart, dataStart + compSize);
|
|
610
|
+
let content;
|
|
611
|
+
if (method === 0) {
|
|
612
|
+
content = comp.slice();
|
|
613
|
+
} else {
|
|
614
|
+
const stream = new Response(comp).body.pipeThrough(
|
|
615
|
+
new DecompressionStream('deflate-raw')
|
|
616
|
+
);
|
|
617
|
+
content = new Uint8Array(await new Response(stream).arrayBuffer());
|
|
618
|
+
}
|
|
619
|
+
out[name] = content;
|
|
620
|
+
pos = dataStart + compSize;
|
|
621
|
+
}
|
|
622
|
+
return out;
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
// ---- Derived summaries -------------------------------------------------
|
|
626
|
+
|
|
627
|
+
function engineVersion(meta, events) {
|
|
628
|
+
if (meta && meta.cesdkEngine && meta.cesdkEngine !== 'unknown')
|
|
629
|
+
return meta.cesdkEngine;
|
|
630
|
+
const line = events.find(
|
|
631
|
+
(e) =>
|
|
632
|
+
e.kind === 'engine_log' && /\\[CreativeEngine\\]/.test(e.text || '')
|
|
633
|
+
);
|
|
634
|
+
const m = line && (line.text || '').match(/v?(\\d+\\.\\d+\\.\\d+)/);
|
|
635
|
+
return m ? m[1] : null;
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
// Engine init ≈ time from an \`engine_init_start\` to the first "Engine has
|
|
639
|
+
// been initialized" log within that run's window. The NDJSON accumulates
|
|
640
|
+
// across restarts, so we scan runs newest-first and return the most recent
|
|
641
|
+
// one that actually completed (the newest start may have no captured
|
|
642
|
+
// completion — e.g. the still-booting server).
|
|
643
|
+
function engineInitMs(events) {
|
|
644
|
+
const starts = events
|
|
645
|
+
.filter(
|
|
646
|
+
(e) => e.kind === 'lifecycle' && e.text === 'engine_init_start'
|
|
647
|
+
)
|
|
648
|
+
.map((e) => e.timestamp)
|
|
649
|
+
.sort((a, b) => a - b);
|
|
650
|
+
if (!starts.length) return null;
|
|
651
|
+
const readies = events
|
|
652
|
+
.filter(
|
|
653
|
+
(e) =>
|
|
654
|
+
e.kind === 'engine_log' &&
|
|
655
|
+
/Engine has been initialized/i.test(e.text || '')
|
|
656
|
+
)
|
|
657
|
+
.map((e) => e.timestamp)
|
|
658
|
+
.sort((a, b) => a - b);
|
|
659
|
+
for (let i = starts.length - 1; i >= 0; i--) {
|
|
660
|
+
const start = starts[i];
|
|
661
|
+
const next = i + 1 < starts.length ? starts[i + 1] : Infinity;
|
|
662
|
+
const ready = readies.find((t) => t >= start && t < next);
|
|
663
|
+
if (ready != null) return ready - start;
|
|
664
|
+
}
|
|
665
|
+
return null;
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
function aggregateTools(events) {
|
|
669
|
+
const map = new Map();
|
|
670
|
+
for (const e of events) {
|
|
671
|
+
if (e.kind !== 'tool_call') continue;
|
|
672
|
+
let a = map.get(e.tool);
|
|
673
|
+
if (!a) {
|
|
674
|
+
a = {
|
|
675
|
+
tool: e.tool,
|
|
676
|
+
count: 0,
|
|
677
|
+
errors: 0,
|
|
678
|
+
sum: 0,
|
|
679
|
+
max: 0,
|
|
680
|
+
phases: {}
|
|
681
|
+
};
|
|
682
|
+
map.set(e.tool, a);
|
|
683
|
+
}
|
|
684
|
+
a.count++;
|
|
685
|
+
a.sum += e.totalMs || 0;
|
|
686
|
+
if ((e.totalMs || 0) > a.max) a.max = e.totalMs || 0;
|
|
687
|
+
if (e.succeeded === false) a.errors++;
|
|
688
|
+
for (const sp of e.spans || [])
|
|
689
|
+
a.phases[sp.phase] = (a.phases[sp.phase] || 0) + (sp.ms || 0);
|
|
690
|
+
}
|
|
691
|
+
return [...map.values()].sort((x, y) => y.sum - x.sum);
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
// Stacked, normalized phase bar (used by the per-tool table and each
|
|
695
|
+
// per-call summary row).
|
|
696
|
+
function phaseSegs(phaseSums) {
|
|
697
|
+
const names = Object.keys(phaseSums);
|
|
698
|
+
if (!names.length)
|
|
699
|
+
return '<div class="phase-seg" style="width:100%;background:#8884" title="no spans"></div>';
|
|
700
|
+
const ordered = PHASE_ORDER.filter((p) => phaseSums[p] != null).concat(
|
|
701
|
+
names.filter((n) => !PHASE_ORDER.includes(n))
|
|
702
|
+
);
|
|
703
|
+
const total = ordered.reduce((s, n) => s + phaseSums[n], 0) || 1;
|
|
704
|
+
return ordered
|
|
705
|
+
.map((n) => {
|
|
706
|
+
const pct = ((phaseSums[n] / total) * 100).toFixed(2);
|
|
707
|
+
return (
|
|
708
|
+
\`<div class="phase-seg" style="width:\${pct}%;\` +
|
|
709
|
+
\`background:var(--c-\${safePhase(n)},#888)" \` +
|
|
710
|
+
\`title="\${esc(n)}: \${round(phaseSums[n])}ms"></div>\`
|
|
711
|
+
);
|
|
712
|
+
})
|
|
713
|
+
.join('');
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
// Per-phase rows (one bar each) for an expanded call.
|
|
717
|
+
function phaseList(spans) {
|
|
718
|
+
if (!spans.length) return '<div class="muted">no spans</div>';
|
|
719
|
+
const total = spans.reduce((s, sp) => s + (sp.ms || 0), 0) || 1;
|
|
720
|
+
return (
|
|
721
|
+
'<div class="phase-list">' +
|
|
722
|
+
spans
|
|
723
|
+
.map((sp) => {
|
|
724
|
+
const pct = (((sp.ms || 0) / total) * 100).toFixed(1);
|
|
725
|
+
const c = 'var(--c-' + safePhase(sp.phase) + ',#888)';
|
|
726
|
+
return (
|
|
727
|
+
'<div class="pl-row">' +
|
|
728
|
+
'<span class="pl-name"><span class="sw" style="background:' +
|
|
729
|
+
c +
|
|
730
|
+
'"></span>' +
|
|
731
|
+
esc(sp.phase) +
|
|
732
|
+
'</span>' +
|
|
733
|
+
'<span class="pl-track"><span class="pl-fill" style="width:' +
|
|
734
|
+
pct +
|
|
735
|
+
'%;background:' +
|
|
736
|
+
c +
|
|
737
|
+
'"></span></span>' +
|
|
738
|
+
'<span class="num">' +
|
|
739
|
+
round(sp.ms || 0) +
|
|
740
|
+
' ms</span>' +
|
|
741
|
+
'</div>'
|
|
742
|
+
);
|
|
743
|
+
})
|
|
744
|
+
.join('') +
|
|
745
|
+
'</div>'
|
|
746
|
+
);
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
// ---- Renderers ---------------------------------------------------------
|
|
750
|
+
|
|
751
|
+
function renderMeta(meta, events) {
|
|
752
|
+
const parts = [];
|
|
753
|
+
if (meta && meta.buildId) parts.push('build ' + esc(meta.buildId));
|
|
754
|
+
if (meta && meta.os)
|
|
755
|
+
parts.push(
|
|
756
|
+
esc(
|
|
757
|
+
(OS_NAMES[meta.os] || meta.os) +
|
|
758
|
+
(meta.arch ? ' ' + meta.arch : '')
|
|
759
|
+
)
|
|
760
|
+
);
|
|
761
|
+
if (meta && meta.node) parts.push('node ' + esc(meta.node));
|
|
762
|
+
const ev = engineVersion(meta, events);
|
|
763
|
+
if (ev) parts.push('engine ' + esc(ev));
|
|
764
|
+
document.getElementById('meta').innerHTML = parts.length
|
|
765
|
+
? parts.join('<span class="sep">·</span>')
|
|
766
|
+
: '<span class="muted">no meta.json in this source</span>';
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
function card(label, value, sub) {
|
|
770
|
+
return (
|
|
771
|
+
'<div class="card"><div class="label">' +
|
|
772
|
+
esc(label) +
|
|
773
|
+
'</div><div class="value">' +
|
|
774
|
+
value +
|
|
775
|
+
'</div>' +
|
|
776
|
+
(sub ? '<div class="sub">' + sub + '</div>' : '') +
|
|
777
|
+
'</div>'
|
|
778
|
+
);
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
function renderCards(events) {
|
|
782
|
+
const calls = events.filter((e) => e.kind === 'tool_call');
|
|
783
|
+
const errors = calls.filter((e) => e.succeeded === false).length;
|
|
784
|
+
const starts = events.filter(
|
|
785
|
+
(e) => e.kind === 'lifecycle' && e.text === 'engine_init_start'
|
|
786
|
+
).length;
|
|
787
|
+
const stamps = events.map((e) => e.timestamp).filter((n) => n != null);
|
|
788
|
+
const span = stamps.length
|
|
789
|
+
? Math.max(...stamps) - Math.min(...stamps)
|
|
790
|
+
: null;
|
|
791
|
+
const slowest = calls.reduce(
|
|
792
|
+
(best, e) => ((e.totalMs || 0) > (best?.totalMs || 0) ? e : best),
|
|
793
|
+
null
|
|
794
|
+
);
|
|
795
|
+
const init = engineInitMs(events);
|
|
796
|
+
document.getElementById('cards').innerHTML = [
|
|
797
|
+
card('tool calls', String(calls.length)),
|
|
798
|
+
card(
|
|
799
|
+
'errors',
|
|
800
|
+
errors ? '<span class="err">' + errors + '</span>' : '0'
|
|
801
|
+
),
|
|
802
|
+
card('server starts', String(starts)),
|
|
803
|
+
card(
|
|
804
|
+
'log span',
|
|
805
|
+
esc(fmtDur(span)),
|
|
806
|
+
starts > 1 ? esc(starts + ' runs') : ''
|
|
807
|
+
),
|
|
808
|
+
card(
|
|
809
|
+
'engine init',
|
|
810
|
+
esc(fmtDur(init)),
|
|
811
|
+
init != null ? 'last start' : ''
|
|
812
|
+
),
|
|
813
|
+
card(
|
|
814
|
+
'slowest call',
|
|
815
|
+
slowest ? esc(fmtDur(slowest.totalMs)) : '—',
|
|
816
|
+
slowest ? esc(slowest.tool) : ''
|
|
817
|
+
)
|
|
818
|
+
].join('');
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
function renderLegend() {
|
|
822
|
+
document.getElementById('legend').innerHTML = PHASE_ORDER.map(
|
|
823
|
+
(p) =>
|
|
824
|
+
'<span><span class="sw" style="background:var(--c-' +
|
|
825
|
+
p +
|
|
826
|
+
')"></span>' +
|
|
827
|
+
esc(p) +
|
|
828
|
+
'</span>'
|
|
829
|
+
).join('');
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
function renderToolAgg(events) {
|
|
833
|
+
const rows = aggregateTools(events);
|
|
834
|
+
const el = document.getElementById('tool-agg');
|
|
835
|
+
if (!rows.length) {
|
|
836
|
+
el.innerHTML =
|
|
837
|
+
'<tr><td colspan="6" class="muted">no tool_call events</td></tr>';
|
|
838
|
+
return;
|
|
839
|
+
}
|
|
840
|
+
el.innerHTML = rows
|
|
841
|
+
.map((a) => {
|
|
842
|
+
const errPct = a.count ? Math.round((a.errors / a.count) * 100) : 0;
|
|
843
|
+
return (
|
|
844
|
+
'<tr><td>' +
|
|
845
|
+
esc(a.tool) +
|
|
846
|
+
'</td><td>' +
|
|
847
|
+
a.count +
|
|
848
|
+
'</td><td' +
|
|
849
|
+
(a.errors ? ' class="err"' : '') +
|
|
850
|
+
'>' +
|
|
851
|
+
errPct +
|
|
852
|
+
'%</td><td>' +
|
|
853
|
+
round(a.sum / a.count) +
|
|
854
|
+
'</td><td>' +
|
|
855
|
+
round(a.max) +
|
|
856
|
+
'</td><td class="bar-cell"><div class="phase-bar">' +
|
|
857
|
+
phaseSegs(a.phases) +
|
|
858
|
+
'</div></td></tr>'
|
|
859
|
+
);
|
|
860
|
+
})
|
|
861
|
+
.join('');
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
// Only these mime types are rendered as inline images; a base64 \`data\`
|
|
865
|
+
// is only trusted if it is pure base64 (defends the data: URI we build).
|
|
866
|
+
const RESULT_IMG_MIME = /^image\\/(png|webp|jpe?g|gif|avif)$/i;
|
|
867
|
+
const BASE64_RE = /^[A-Za-z0-9+/=\\s]+$/;
|
|
868
|
+
|
|
869
|
+
// Result content parts that carry a real, renderable base64 image.
|
|
870
|
+
function resultImages(result) {
|
|
871
|
+
if (
|
|
872
|
+
!result ||
|
|
873
|
+
typeof result !== 'object' ||
|
|
874
|
+
!Array.isArray(result.content)
|
|
875
|
+
)
|
|
876
|
+
return [];
|
|
877
|
+
return result.content.filter(
|
|
878
|
+
(p) =>
|
|
879
|
+
p &&
|
|
880
|
+
typeof p === 'object' &&
|
|
881
|
+
typeof p.mimeType === 'string' &&
|
|
882
|
+
RESULT_IMG_MIME.test(p.mimeType) &&
|
|
883
|
+
typeof p.data === 'string' &&
|
|
884
|
+
BASE64_RE.test(p.data)
|
|
885
|
+
);
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
// A copy of the result with kept base64 blobs collapsed to a marker, so
|
|
889
|
+
// the JSON dump stays readable — the image bytes render as <img> below.
|
|
890
|
+
function markerizeResult(result) {
|
|
891
|
+
if (
|
|
892
|
+
!result ||
|
|
893
|
+
typeof result !== 'object' ||
|
|
894
|
+
!Array.isArray(result.content)
|
|
895
|
+
)
|
|
896
|
+
return result;
|
|
897
|
+
const content = result.content.map((p) => {
|
|
898
|
+
if (
|
|
899
|
+
p &&
|
|
900
|
+
typeof p === 'object' &&
|
|
901
|
+
typeof p.data === 'string' &&
|
|
902
|
+
p.data.length > 256 &&
|
|
903
|
+
!/ omitted>$/.test(p.data)
|
|
904
|
+
) {
|
|
905
|
+
const where = RESULT_IMG_MIME.test(p.mimeType || '')
|
|
906
|
+
? 'rendered below'
|
|
907
|
+
: 'omitted';
|
|
908
|
+
return { ...p, data: \`<\${p.data.length} base64 chars — \${where}>\` };
|
|
909
|
+
}
|
|
910
|
+
return p;
|
|
911
|
+
});
|
|
912
|
+
return { ...result, content };
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
// Chronological accordion: one <details> row per call; expand for the
|
|
916
|
+
// per-phase breakdown, timestamp/revision, error, and raw arguments.
|
|
917
|
+
function callRow(e) {
|
|
918
|
+
const failed = e.succeeded === false;
|
|
919
|
+
const spans = e.spans || [];
|
|
920
|
+
const spanSums = {};
|
|
921
|
+
for (const sp of spans)
|
|
922
|
+
spanSums[sp.phase] = (spanSums[sp.phase] || 0) + (sp.ms || 0);
|
|
923
|
+
const status = failed
|
|
924
|
+
? '<span class="err-badge">FAIL</span>'
|
|
925
|
+
: '<span class="ok-badge">ok</span>';
|
|
926
|
+
const rev = e.revision
|
|
927
|
+
? ' · revision <code>' + esc(e.revision) + '</code>'
|
|
928
|
+
: '';
|
|
929
|
+
const errBlock = e.error
|
|
930
|
+
? '<div class="kv-label err">error</div><pre class="args err">' +
|
|
931
|
+
esc(
|
|
932
|
+
(e.error.message || '') +
|
|
933
|
+
(e.error.stack ? '\\n' + e.error.stack : '')
|
|
934
|
+
) +
|
|
935
|
+
'</pre>'
|
|
936
|
+
: '';
|
|
937
|
+
// \`code\` is a long multiline string — noise as escaped JSON. Redact it
|
|
938
|
+
// to a length marker in the arguments dump and render it properly in a
|
|
939
|
+
// dedicated Code section below.
|
|
940
|
+
const hasCode =
|
|
941
|
+
e.args &&
|
|
942
|
+
typeof e.args === 'object' &&
|
|
943
|
+
typeof e.args.code === 'string' &&
|
|
944
|
+
e.args.code.length > 0;
|
|
945
|
+
const argsForJson = hasCode
|
|
946
|
+
? {
|
|
947
|
+
...e.args,
|
|
948
|
+
code: '<' + e.args.code.length + ' chars — see Code below>'
|
|
949
|
+
}
|
|
950
|
+
: e.args;
|
|
951
|
+
const argsBlock =
|
|
952
|
+
e.args !== undefined
|
|
953
|
+
? '<div class="kv-label">arguments</div><pre class="args">' +
|
|
954
|
+
esc(JSON.stringify(argsForJson, null, 2)) +
|
|
955
|
+
'</pre>'
|
|
956
|
+
: '<div class="kv-label">arguments</div><div class="muted">none recorded</div>';
|
|
957
|
+
const codeBlock = hasCode
|
|
958
|
+
? '<div class="kv-label">code</div><pre class="code">' +
|
|
959
|
+
esc(e.args.code) +
|
|
960
|
+
'</pre>'
|
|
961
|
+
: '';
|
|
962
|
+
const resultImgs = resultImages(e.result)
|
|
963
|
+
.map(
|
|
964
|
+
(p) =>
|
|
965
|
+
'<img class="result-img" alt="result image" src="data:' +
|
|
966
|
+
esc(p.mimeType) +
|
|
967
|
+
';base64,' +
|
|
968
|
+
esc(p.data.replace(/\\s+/g, '')) +
|
|
969
|
+
'">'
|
|
970
|
+
)
|
|
971
|
+
.join('');
|
|
972
|
+
const resultBlock =
|
|
973
|
+
e.result !== undefined
|
|
974
|
+
? '<div class="kv-label">result</div><pre class="args">' +
|
|
975
|
+
esc(JSON.stringify(markerizeResult(e.result), null, 2)) +
|
|
976
|
+
'</pre>' +
|
|
977
|
+
resultImgs
|
|
978
|
+
: '';
|
|
979
|
+
return (
|
|
980
|
+
'<details class="call' +
|
|
981
|
+
(failed ? ' failed' : '') +
|
|
982
|
+
'">' +
|
|
983
|
+
'<summary class="call-row">' +
|
|
984
|
+
'<span class="c-time">' +
|
|
985
|
+
esc(fmtTs(e.timestamp)) +
|
|
986
|
+
'</span>' +
|
|
987
|
+
'<span class="c-tool">' +
|
|
988
|
+
esc(e.tool) +
|
|
989
|
+
'</span>' +
|
|
990
|
+
'<span class="phase-bar">' +
|
|
991
|
+
phaseSegs(spanSums) +
|
|
992
|
+
'</span>' +
|
|
993
|
+
'<span class="num">' +
|
|
994
|
+
round(e.totalMs || 0) +
|
|
995
|
+
' ms</span>' +
|
|
996
|
+
'<span class="c-status">' +
|
|
997
|
+
status +
|
|
998
|
+
'<span class="chevron">▸</span></span>' +
|
|
999
|
+
'</summary>' +
|
|
1000
|
+
'<div class="call-body">' +
|
|
1001
|
+
'<div class="kv-label">phases</div>' +
|
|
1002
|
+
phaseList(spans) +
|
|
1003
|
+
'<div class="meta-kv">' +
|
|
1004
|
+
esc(new Date(e.timestamp).toLocaleString()) +
|
|
1005
|
+
rev +
|
|
1006
|
+
'</div>' +
|
|
1007
|
+
errBlock +
|
|
1008
|
+
argsBlock +
|
|
1009
|
+
codeBlock +
|
|
1010
|
+
resultBlock +
|
|
1011
|
+
'</div>' +
|
|
1012
|
+
'</details>'
|
|
1013
|
+
);
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
function renderPerCall(events) {
|
|
1017
|
+
const calls = events
|
|
1018
|
+
.filter((e) => e.kind === 'tool_call')
|
|
1019
|
+
.slice()
|
|
1020
|
+
.sort((a, b) => a.timestamp - b.timestamp);
|
|
1021
|
+
setTabCount('calls', calls.length);
|
|
1022
|
+
const el = document.getElementById('tool-calls');
|
|
1023
|
+
if (!calls.length) {
|
|
1024
|
+
el.innerHTML = '<p class="muted">no tool_call events</p>';
|
|
1025
|
+
return;
|
|
1026
|
+
}
|
|
1027
|
+
el.innerHTML =
|
|
1028
|
+
'<div class="calls-head"><span>time</span><span>tool</span>' +
|
|
1029
|
+
'<span>phase breakdown</span><span class="num">total</span>' +
|
|
1030
|
+
'<span></span></div>' +
|
|
1031
|
+
calls.map(callRow).join('');
|
|
1032
|
+
}
|
|
1033
|
+
|
|
1034
|
+
function renderTimeline(events) {
|
|
1035
|
+
const el = document.getElementById('timeline');
|
|
1036
|
+
const entries = events.filter(
|
|
1037
|
+
(e) => e.kind === 'lifecycle' || (e.kind === 'tool_call' && e.error)
|
|
1038
|
+
);
|
|
1039
|
+
setTabCount('timeline', entries.length);
|
|
1040
|
+
if (!entries.length) {
|
|
1041
|
+
el.innerHTML = '<li class="muted">no lifecycle or error events</li>';
|
|
1042
|
+
return;
|
|
1043
|
+
}
|
|
1044
|
+
el.innerHTML = entries
|
|
1045
|
+
.map((e) => {
|
|
1046
|
+
const ts = fmtTs(e.timestamp);
|
|
1047
|
+
const isStart = e.text === 'engine_init_start';
|
|
1048
|
+
const text =
|
|
1049
|
+
e.text != null
|
|
1050
|
+
? e.text
|
|
1051
|
+
: e.kind === 'tool_call'
|
|
1052
|
+
? 'tool error: ' + e.tool
|
|
1053
|
+
: e.kind;
|
|
1054
|
+
const errLine = e.error
|
|
1055
|
+
? \`<div class="tl-err">\${esc(e.error.message)}</div>\`
|
|
1056
|
+
: '';
|
|
1057
|
+
return (
|
|
1058
|
+
\`<li><span class="tl-ts">\${esc(ts)}</span>\` +
|
|
1059
|
+
\`<span\${isStart ? ' class="tl-start"' : ''}>\${esc(text)}</span>\${errLine}</li>\`
|
|
1060
|
+
);
|
|
1061
|
+
})
|
|
1062
|
+
.join('');
|
|
1063
|
+
}
|
|
1064
|
+
|
|
1065
|
+
function renderEngineLog(events) {
|
|
1066
|
+
const el = document.getElementById('log-tail');
|
|
1067
|
+
const logs = events.filter((e) => e.kind === 'engine_log');
|
|
1068
|
+
setTabCount('log', logs.length);
|
|
1069
|
+
const tail = logs.slice(-200);
|
|
1070
|
+
if (!tail.length) {
|
|
1071
|
+
el.innerHTML = '<span class="muted">no engine_log events</span>';
|
|
1072
|
+
return;
|
|
1073
|
+
}
|
|
1074
|
+
el.innerHTML = tail
|
|
1075
|
+
.map((e) => {
|
|
1076
|
+
const lvl = /^[a-z]+$/i.test(String(e.level)) ? e.level : 'unknown';
|
|
1077
|
+
return \`<div class="log-line"><span class="lvl lvl-\${lvl}">\${esc(e.level)}</span><span class="log-text">\${esc(e.text)}</span></div>\`;
|
|
1078
|
+
})
|
|
1079
|
+
.join('');
|
|
1080
|
+
el.scrollTop = el.scrollHeight;
|
|
1081
|
+
}
|
|
1082
|
+
|
|
1083
|
+
function render(events, meta) {
|
|
1084
|
+
renderMeta(meta, events);
|
|
1085
|
+
renderCards(events);
|
|
1086
|
+
renderLegend();
|
|
1087
|
+
renderToolAgg(events);
|
|
1088
|
+
renderPerCall(events);
|
|
1089
|
+
renderTimeline(events);
|
|
1090
|
+
renderEngineLog(events);
|
|
1091
|
+
document.getElementById('status').textContent =
|
|
1092
|
+
'loaded ' + events.length + ' event(s)';
|
|
1093
|
+
document.getElementById('drop-zone').style.display = 'none';
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1096
|
+
// ---- Tabs --------------------------------------------------------------
|
|
1097
|
+
document.querySelectorAll('.tab').forEach((btn) => {
|
|
1098
|
+
btn.addEventListener('click', () => {
|
|
1099
|
+
const tab = btn.dataset.tab;
|
|
1100
|
+
document.querySelectorAll('.tab').forEach((b) => {
|
|
1101
|
+
const on = b === btn;
|
|
1102
|
+
b.classList.toggle('active', on);
|
|
1103
|
+
b.setAttribute('aria-selected', on ? 'true' : 'false');
|
|
1104
|
+
});
|
|
1105
|
+
document.querySelectorAll('.tabpanel').forEach((p) => {
|
|
1106
|
+
p.hidden = p.id !== 'panel-' + tab;
|
|
1107
|
+
});
|
|
1108
|
+
if (tab === 'log') {
|
|
1109
|
+
const el = document.getElementById('log-tail');
|
|
1110
|
+
el.scrollTop = el.scrollHeight;
|
|
1111
|
+
}
|
|
1112
|
+
});
|
|
1113
|
+
});
|
|
1114
|
+
|
|
1115
|
+
// ---- Loading -----------------------------------------------------------
|
|
1116
|
+
|
|
1117
|
+
function safeJson(text) {
|
|
1118
|
+
try {
|
|
1119
|
+
return JSON.parse(text);
|
|
1120
|
+
} catch {
|
|
1121
|
+
return null;
|
|
1122
|
+
}
|
|
1123
|
+
}
|
|
1124
|
+
|
|
1125
|
+
async function loadArrayBuffer(buf) {
|
|
1126
|
+
const b = new Uint8Array(buf);
|
|
1127
|
+
// ZIP magic 'PK\\x03\\x04' → unzip the bundle; otherwise treat as NDJSON.
|
|
1128
|
+
if (b[0] === 0x50 && b[1] === 0x4b) {
|
|
1129
|
+
const files = await unzip(buf);
|
|
1130
|
+
const td = new TextDecoder();
|
|
1131
|
+
const ndName = Object.keys(files).find((n) => /\\.ndjson$/i.test(n));
|
|
1132
|
+
const text = ndName ? td.decode(files[ndName]) : '';
|
|
1133
|
+
const meta = files['meta.json']
|
|
1134
|
+
? safeJson(td.decode(files['meta.json']))
|
|
1135
|
+
: null;
|
|
1136
|
+
render(parseNdjson(text), meta);
|
|
1137
|
+
} else {
|
|
1138
|
+
render(parseNdjson(new TextDecoder().decode(b)), null);
|
|
1139
|
+
}
|
|
1140
|
+
}
|
|
1141
|
+
|
|
1142
|
+
function readFileAsBuffer(file) {
|
|
1143
|
+
const reader = new FileReader();
|
|
1144
|
+
reader.onload = (e) =>
|
|
1145
|
+
loadArrayBuffer(e.target.result).catch((err) => {
|
|
1146
|
+
document.getElementById('status').textContent =
|
|
1147
|
+
'error: ' + err.message;
|
|
1148
|
+
});
|
|
1149
|
+
reader.readAsArrayBuffer(file);
|
|
1150
|
+
}
|
|
1151
|
+
|
|
1152
|
+
document.getElementById('file-input').addEventListener('change', (ev) => {
|
|
1153
|
+
const file = ev.target.files[0];
|
|
1154
|
+
if (file) readFileAsBuffer(file);
|
|
1155
|
+
});
|
|
1156
|
+
|
|
1157
|
+
const zone = document.getElementById('drop-zone');
|
|
1158
|
+
zone.addEventListener('dragover', (e) => {
|
|
1159
|
+
e.preventDefault();
|
|
1160
|
+
zone.classList.add('over');
|
|
1161
|
+
});
|
|
1162
|
+
zone.addEventListener('dragleave', () => zone.classList.remove('over'));
|
|
1163
|
+
zone.addEventListener('drop', (e) => {
|
|
1164
|
+
e.preventDefault();
|
|
1165
|
+
zone.classList.remove('over');
|
|
1166
|
+
const file = e.dataTransfer.files[0];
|
|
1167
|
+
if (file) readFileAsBuffer(file);
|
|
1168
|
+
});
|
|
1169
|
+
|
|
1170
|
+
// Served mode (CODE-62): the MCP router injects an endpoint. Auto-load the
|
|
1171
|
+
// NDJSON snapshot over HTTP (no browser unzip) and reveal Download-ZIP.
|
|
1172
|
+
const streamUrl = window.__DIAGNOSTICS_STREAM__;
|
|
1173
|
+
const bundleUrl = window.__DIAGNOSTICS_BUNDLE__;
|
|
1174
|
+
const fileParam = new URLSearchParams(location.search).get('file');
|
|
1175
|
+
if (streamUrl) {
|
|
1176
|
+
const dl = document.getElementById('download-zip');
|
|
1177
|
+
if (bundleUrl && dl) {
|
|
1178
|
+
dl.href = bundleUrl;
|
|
1179
|
+
dl.style.display = '';
|
|
1180
|
+
}
|
|
1181
|
+
document.getElementById('status').textContent = 'loading…';
|
|
1182
|
+
fetch(streamUrl, { cache: 'no-store' })
|
|
1183
|
+
.then((r) => {
|
|
1184
|
+
if (!r.ok) throw new Error('HTTP ' + r.status);
|
|
1185
|
+
return r.text();
|
|
1186
|
+
})
|
|
1187
|
+
.then((text) => render(parseNdjson(text), null))
|
|
1188
|
+
.catch((e) => {
|
|
1189
|
+
document.getElementById('status').textContent =
|
|
1190
|
+
'error: ' + e.message;
|
|
1191
|
+
});
|
|
1192
|
+
} else if (fileParam) {
|
|
1193
|
+
document.getElementById('status').textContent = 'loading…';
|
|
1194
|
+
const isZip = /\\.zip$/i.test(fileParam);
|
|
1195
|
+
fetch('/_file?path=' + encodeURIComponent(fileParam), {
|
|
1196
|
+
cache: 'no-store'
|
|
1197
|
+
})
|
|
1198
|
+
.then((r) => {
|
|
1199
|
+
if (!r.ok) throw new Error('HTTP ' + r.status);
|
|
1200
|
+
return r.arrayBuffer();
|
|
1201
|
+
})
|
|
1202
|
+
.then(async (buf) => {
|
|
1203
|
+
if (isZip) return loadArrayBuffer(buf);
|
|
1204
|
+
const text = new TextDecoder().decode(new Uint8Array(buf));
|
|
1205
|
+
const events = parseNdjson(text);
|
|
1206
|
+
const metaPath = fileParam.replace(/[^/\\\\]*$/, 'meta.json');
|
|
1207
|
+
let meta = null;
|
|
1208
|
+
if (metaPath !== fileParam) {
|
|
1209
|
+
meta = await fetch(
|
|
1210
|
+
'/_file?path=' + encodeURIComponent(metaPath),
|
|
1211
|
+
{
|
|
1212
|
+
cache: 'no-store'
|
|
1213
|
+
}
|
|
1214
|
+
)
|
|
1215
|
+
.then((r) => (r.ok ? r.text() : null))
|
|
1216
|
+
.then((t) => (t ? safeJson(t) : null))
|
|
1217
|
+
.catch(() => null);
|
|
1218
|
+
}
|
|
1219
|
+
render(events, meta);
|
|
1220
|
+
})
|
|
1221
|
+
.catch((e) => {
|
|
1222
|
+
document.getElementById('status').textContent =
|
|
1223
|
+
'error: ' + e.message;
|
|
1224
|
+
});
|
|
1225
|
+
}
|
|
1226
|
+
</script>
|
|
1227
|
+
</body>
|
|
1228
|
+
</html>
|
|
1229
|
+
`,
|
|
1230
|
+
};
|
|
1231
|
+
//# sourceMappingURL=spa-assets.generated.js.map
|