dsh-turn-outline 0.2.5

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/lib/client.js ADDED
@@ -0,0 +1,1865 @@
1
+ window.__ModuleLoader__.load({
2
+ id: 'dsh-turn-outline',
3
+ factory: (require) => {
4
+ var module = { exports: {} };
5
+ var exports = module.exports;
6
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
7
+
8
+ "use strict";
9
+ var __create = Object.create;
10
+ var __defProp = Object.defineProperty;
11
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
12
+ var __getOwnPropNames = Object.getOwnPropertyNames;
13
+ var __getProtoOf = Object.getPrototypeOf;
14
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
15
+ var __export = (target, all) => {
16
+ for (var name in all)
17
+ __defProp(target, name, { get: all[name], enumerable: true });
18
+ };
19
+ var __copyProps = (to, from, except, desc) => {
20
+ if (from && typeof from === "object" || typeof from === "function") {
21
+ for (let key of __getOwnPropNames(from))
22
+ if (!__hasOwnProp.call(to, key) && key !== except)
23
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
24
+ }
25
+ return to;
26
+ };
27
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
28
+ // If the importer is in node compatibility mode or this is not an ESM
29
+ // file that has been converted to a CommonJS file using a Babel-
30
+ // compatible transform (i.e. "__esModule" has not been set), then set
31
+ // "default" to the CommonJS "module.exports" for node compatibility.
32
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
33
+ mod
34
+ ));
35
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
36
+
37
+ // src/client/index.tsx
38
+ var index_exports = {};
39
+ __export(index_exports, {
40
+ apply: () => apply,
41
+ inject: () => inject
42
+ });
43
+ module.exports = __toCommonJS(index_exports);
44
+ var React3 = __toESM(require("react"), 1);
45
+
46
+ // src/client/styles.ts
47
+ var trailCss = `
48
+ .ttl-root {
49
+ box-sizing: border-box;
50
+ position: relative;
51
+ height: 100%;
52
+ min-width: 0;
53
+ display: flex;
54
+ flex-direction: column;
55
+ overflow: hidden;
56
+ background: var(--dsw-alias-bg-layer-1, #232324);
57
+ color: var(--dsw-alias-label-primary, #f9fafb);
58
+ font-family: var(--dsw-font-family, -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "Helvetica Neue", Helvetica, Arial, sans-serif);
59
+ font-size: 12px;
60
+ line-height: 18px;
61
+ container: trail-tab / inline-size;
62
+ }
63
+
64
+ /* \u2500\u2500 Header (36px, hairline) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
65
+ .ttl-header {
66
+ box-sizing: border-box;
67
+ display: flex;
68
+ align-items: center;
69
+ gap: 8px;
70
+ flex: none;
71
+ height: 36px;
72
+ padding: 0 8px 0 12px;
73
+ border-bottom: 1px solid var(--dsw-alias-border-l1, rgba(255, 255, 255, 0.06));
74
+ }
75
+ .ttl-title {
76
+ flex: none;
77
+ min-width: 0;
78
+ max-width: 55%;
79
+ margin: 0;
80
+ font-size: 13px;
81
+ font-weight: 500;
82
+ line-height: 20px;
83
+ color: var(--dsw-alias-label-primary, #f9fafb);
84
+ white-space: nowrap;
85
+ overflow: hidden;
86
+ text-overflow: ellipsis;
87
+ }
88
+ .ttl-count {
89
+ flex: none;
90
+ color: var(--dsw-alias-label-tertiary, #adb2b8);
91
+ font-size: 12px;
92
+ line-height: 18px;
93
+ font-variant-numeric: tabular-nums;
94
+ }
95
+ .ttl-headerSpacer { flex: 1; min-width: 0; }
96
+ /* Follow-latest: native ghost button + status dot (no checkbox pill). */
97
+ .ttl-toggle {
98
+ box-sizing: border-box;
99
+ flex: none;
100
+ height: 24px;
101
+ display: inline-flex;
102
+ align-items: center;
103
+ gap: 6px;
104
+ padding: 0 8px;
105
+ border: none;
106
+ border-radius: 6px;
107
+ background: transparent;
108
+ color: var(--dsw-alias-label-secondary, #cfd3d6);
109
+ font: inherit;
110
+ font-size: 12px;
111
+ line-height: 18px;
112
+ cursor: pointer;
113
+ white-space: nowrap;
114
+ }
115
+ .ttl-toggle:hover {
116
+ background: var(--dsw-alias-interactive-bg-hover, rgba(255, 255, 255, 0.08));
117
+ color: var(--dsw-alias-label-primary, #f9fafb);
118
+ }
119
+ .ttl-toggleDot {
120
+ flex: none;
121
+ width: 6px;
122
+ height: 6px;
123
+ border-radius: 50%;
124
+ background: transparent;
125
+ box-shadow: inset 0 0 0 1px var(--dsw-alias-label-tertiary, #adb2b8);
126
+ }
127
+ .ttl-toggle[aria-pressed="true"] { color: var(--dsw-alias-label-primary, #f9fafb); }
128
+ .ttl-toggle[aria-pressed="true"] .ttl-toggleDot {
129
+ background: var(--dsw-alias-state-success-primary, #22c55e);
130
+ box-shadow: none;
131
+ }
132
+
133
+ /* \u2500\u2500 Filter row (flat text chips + semantic dots) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
134
+ .ttl-filterRow {
135
+ box-sizing: border-box;
136
+ display: flex;
137
+ align-items: center;
138
+ flex-wrap: nowrap;
139
+ gap: 2px;
140
+ flex: none;
141
+ padding: 4px 8px;
142
+ border-bottom: 1px solid var(--dsw-alias-border-l1, rgba(255, 255, 255, 0.06));
143
+ overflow-x: auto;
144
+ scrollbar-width: thin;
145
+ scrollbar-color: var(--dsw-alias-scrollbar-bg-l2, #545557) transparent;
146
+ }
147
+ .ttl-filterRow::-webkit-scrollbar { height: 4px; }
148
+ .ttl-filterRow::-webkit-scrollbar-thumb {
149
+ background: var(--dsw-alias-scrollbar-bg-l2, #545557);
150
+ border-radius: 2px;
151
+ }
152
+ .ttl-chip {
153
+ box-sizing: border-box;
154
+ flex: none;
155
+ height: 22px;
156
+ display: inline-flex;
157
+ align-items: center;
158
+ gap: 5px;
159
+ padding: 0 8px;
160
+ border: none;
161
+ border-radius: 4px;
162
+ background: transparent;
163
+ color: var(--dsw-alias-label-secondary, #cfd3d6);
164
+ font: inherit;
165
+ font-size: 12px;
166
+ line-height: 16px;
167
+ white-space: nowrap;
168
+ cursor: pointer;
169
+ }
170
+ .ttl-chip:hover {
171
+ background: var(--dsw-alias-interactive-bg-hover, rgba(255, 255, 255, 0.08));
172
+ color: var(--dsw-alias-label-primary, #f9fafb);
173
+ }
174
+ /* Active chip = business-blue TEXT on the hover wash (native detailTab). */
175
+ .ttl-chip[aria-pressed="true"] {
176
+ background: var(--dsw-alias-interactive-bg-hover, rgba(255, 255, 255, 0.08));
177
+ color: var(--dsw-alias-state-business-primary, #4176e6);
178
+ }
179
+ .ttl-chipNum {
180
+ color: var(--dsw-alias-label-tertiary, #adb2b8);
181
+ font-variant-numeric: tabular-nums;
182
+ font-weight: 400;
183
+ }
184
+ .ttl-chip[aria-pressed="true"] .ttl-chipNum { color: inherit; opacity: 0.72; }
185
+ .ttl-chipDot {
186
+ flex: none;
187
+ width: 6px;
188
+ height: 6px;
189
+ border-radius: 50%;
190
+ }
191
+ .ttl-dot-success { background: var(--dsw-alias-state-success-primary, #22c55e); }
192
+ .ttl-dot-warn { background: var(--dsw-alias-state-warn-primary, #f59e0b); }
193
+ .ttl-dot-neutral { background: var(--dsw-alias-label-tertiary, #adb2b8); }
194
+ .ttl-moreChip { color: var(--dsw-alias-label-tertiary, #adb2b8); }
195
+ .ttl-moreArrow {
196
+ flex: none;
197
+ display: inline-flex;
198
+ transition: transform 120ms var(--ds-ease-in-out, ease);
199
+ }
200
+ .ttl-moreArrowOpen { transform: rotate(180deg); }
201
+ /* "More" dropdown: native elevated menu (layer-2 + hairline + 8px + shadow). */
202
+ .ttl-toolDropdown {
203
+ box-sizing: border-box;
204
+ position: absolute;
205
+ z-index: 9;
206
+ top: 64px;
207
+ left: 8px;
208
+ display: flex;
209
+ flex-wrap: wrap;
210
+ gap: 2px;
211
+ width: max-content;
212
+ max-width: calc(100% - 16px);
213
+ max-height: 180px;
214
+ overflow-y: auto;
215
+ padding: 6px;
216
+ border: 1px solid var(--dsw-alias-border-l2, rgba(255, 255, 255, 0.12));
217
+ border-radius: 8px;
218
+ background: var(--dsw-alias-bg-layer-2, #2c2c2e);
219
+ box-shadow: 0 4px 16px var(--dsw-alias-bg-mask-1, rgba(0, 0, 0, 0.24));
220
+ scrollbar-width: thin;
221
+ scrollbar-color: var(--dsw-alias-scrollbar-bg-l2, #545557) transparent;
222
+ }
223
+
224
+ /* \u2500\u2500 Turn list (flat rows, hairline separators) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
225
+ .ttl-list {
226
+ flex: 1;
227
+ min-height: 0;
228
+ overflow-y: auto;
229
+ padding: 0 8px 8px;
230
+ scrollbar-width: thin;
231
+ scrollbar-color: var(--dsw-alias-scrollbar-bg-l2, #545557) transparent;
232
+ }
233
+ .ttl-list::-webkit-scrollbar { width: 6px; }
234
+ .ttl-list::-webkit-scrollbar-thumb {
235
+ background: var(--dsw-alias-scrollbar-bg-l2, #545557);
236
+ border-radius: 3px;
237
+ }
238
+ .ttl-status {
239
+ margin: 14px;
240
+ color: var(--dsw-alias-label-tertiary, #adb2b8);
241
+ font-size: 12px;
242
+ line-height: 18px;
243
+ }
244
+ .ttl-error {
245
+ margin: 14px;
246
+ color: var(--dsw-alias-state-error-primary, #f25a5a);
247
+ font-size: 12px;
248
+ line-height: 18px;
249
+ }
250
+
251
+ /* \u2500\u2500 Turn row (no card; 2px rail marks the located turn) \u2500\u2500\u2500 */
252
+ .ttl-turnRow {
253
+ position: relative;
254
+ border-top: 1px solid var(--dsw-alias-border-l1, rgba(255, 255, 255, 0.06));
255
+ background: transparent;
256
+ }
257
+ .ttl-turnRow:first-child { border-top: none; }
258
+ .ttl-turnRow[data-active="true"]::before {
259
+ content: "";
260
+ position: absolute;
261
+ left: 0;
262
+ top: 0;
263
+ bottom: 0;
264
+ width: 2px;
265
+ background: color-mix(in srgb, var(--dsw-static-blue-500, #3b82f6) 22%, var(--dsw-alias-bg-layer-1, #232324));
266
+ }
267
+ .ttl-turnRow[data-active="true"][data-error="true"]::before {
268
+ background: color-mix(in srgb, var(--dsw-alias-state-error-primary, #f25a5a) 22%, var(--dsw-alias-bg-layer-1, #232324));
269
+ }
270
+ .ttl-turnHead {
271
+ box-sizing: border-box;
272
+ display: flex;
273
+ align-items: center;
274
+ gap: 0;
275
+ min-height: 32px;
276
+ padding: 0 6px 0 10px;
277
+ }
278
+ .ttl-turnHead:hover {
279
+ background: var(--dsw-alias-interactive-bg-hover, rgba(255, 255, 255, 0.08));
280
+ }
281
+ .ttl-turnToggle {
282
+ box-sizing: border-box;
283
+ min-width: 0;
284
+ flex: 1;
285
+ display: flex;
286
+ align-items: center;
287
+ gap: 7px;
288
+ padding: 4px 0;
289
+ border: none;
290
+ background: transparent;
291
+ color: inherit;
292
+ font: inherit;
293
+ text-align: left;
294
+ cursor: pointer;
295
+ }
296
+ .ttl-chevron {
297
+ flex: none;
298
+ color: var(--dsw-alias-label-tertiary, #adb2b8);
299
+ transition: transform 120ms var(--ds-ease-in-out, ease);
300
+ }
301
+ .ttl-chevronOpen { transform: rotate(90deg); }
302
+ /* "\u8F6E\u6B21 N": native turnLabel-style micro tag (mono, module bg, 2px radius). */
303
+ .ttl-turnTag {
304
+ box-sizing: border-box;
305
+ flex: none;
306
+ padding: 1px 5px;
307
+ border-radius: 2px;
308
+ background: var(--dsw-alias-bg-module-platform, #353638);
309
+ color: var(--dsw-alias-label-tertiary, #adb2b8);
310
+ font-family: var(--ds-font-family-code, "SF Mono", "JetBrains Mono", "Fira Code", Consolas, "Liberation Mono", Menlo, Courier, "PingFang SC", "Microsoft YaHei", monospace);
311
+ font-size: 10px;
312
+ font-weight: 500;
313
+ line-height: 12px;
314
+ font-variant-numeric: tabular-nums;
315
+ white-space: nowrap;
316
+ user-select: none;
317
+ }
318
+ .ttl-turnRow[data-active="true"] .ttl-turnTag {
319
+ color: color-mix(in srgb, var(--dsw-static-blue-500, #3b82f6) 55%, var(--dsw-alias-label-tertiary, #adb2b8));
320
+ background: color-mix(in srgb, var(--dsw-static-blue-500, #3b82f6) 14%, var(--dsw-alias-bg-layer-1, #232324));
321
+ }
322
+ .ttl-turnPreview {
323
+ flex: 1;
324
+ min-width: 0;
325
+ color: var(--dsw-alias-label-secondary, #cfd3d6);
326
+ font-size: 12px;
327
+ line-height: 18px;
328
+ white-space: nowrap;
329
+ overflow: hidden;
330
+ text-overflow: ellipsis;
331
+ }
332
+ .ttl-turnMeta {
333
+ flex: none;
334
+ display: flex;
335
+ align-items: center;
336
+ gap: 6px;
337
+ margin-left: 8px;
338
+ }
339
+ /* Step count: a bare tabular number (never a colored badge). */
340
+ .ttl-stepsNum {
341
+ flex: none;
342
+ color: var(--dsw-alias-label-tertiary, #adb2b8);
343
+ font-size: 11px;
344
+ line-height: 16px;
345
+ font-variant-numeric: tabular-nums;
346
+ }
347
+ .ttl-errorDot {
348
+ flex: none;
349
+ width: 6px;
350
+ height: 6px;
351
+ border-radius: 50%;
352
+ background: var(--dsw-alias-state-error-primary, #f25a5a);
353
+ }
354
+ .ttl-locateBtn {
355
+ box-sizing: border-box;
356
+ width: 22px;
357
+ height: 22px;
358
+ display: inline-flex;
359
+ align-items: center;
360
+ justify-content: center;
361
+ border: none;
362
+ border-radius: 6px;
363
+ background: transparent;
364
+ color: var(--dsw-alias-label-tertiary, #adb2b8);
365
+ cursor: pointer;
366
+ padding: 0;
367
+ }
368
+ .ttl-locateBtn:hover {
369
+ background: var(--dsw-alias-interactive-bg-hover, rgba(255, 255, 255, 0.08));
370
+ color: var(--dsw-alias-label-primary, #f9fafb);
371
+ }
372
+
373
+ /* \u2500\u2500 Expanded body (transparent, indented under the rail) \u2500\u2500 */
374
+ .ttl-turnBody {
375
+ box-sizing: border-box;
376
+ padding: 2px 6px 6px 34px;
377
+ background: transparent;
378
+ }
379
+ /* \u2500\u2500 Unified row anatomy: [semantic dot] + [name] + [content] \u2500\u2500
380
+ * One skeleton for every row. The 6px dot carries the whole color channel
381
+ * (blue = user input, violet = assistant output, green/amber/neutral = tool
382
+ * category matching the filter chips' dots, red = failure \u2014 replacing \u2717/\u2713),
383
+ * the name names the row (monospace for tools), content follows. The name
384
+ * column has a narrow 30px min-width: common short names (bash/edit/read,
385
+ * \u8F93\u5165/\u8F93\u51FA) align without dead space; long names just push content. */
386
+ .ttl-row {
387
+ box-sizing: border-box;
388
+ width: 100%;
389
+ display: flex;
390
+ align-items: flex-start;
391
+ gap: 7px;
392
+ padding: 3px 6px;
393
+ border: none;
394
+ border-radius: 4px;
395
+ background: transparent;
396
+ color: var(--dsw-alias-label-primary, #f9fafb);
397
+ font: inherit;
398
+ font-size: 12px;
399
+ line-height: 18px;
400
+ text-align: left;
401
+ cursor: pointer;
402
+ }
403
+ .ttl-row:hover {
404
+ background: var(--dsw-alias-interactive-bg-hover, rgba(255, 255, 255, 0.08));
405
+ }
406
+ .ttl-rowDot {
407
+ flex: none;
408
+ width: 6px;
409
+ height: 6px;
410
+ border-radius: 50%;
411
+ margin-top: 6px;
412
+ }
413
+ .ttl-dot-blue {
414
+ background: var(--dsw-alias-state-business-primary, #4176e6);
415
+ }
416
+ .ttl-dot-violet {
417
+ background: color-mix(in srgb, var(--dsw-alias-brand-primary-new-colorprimary-new-color, #5686fe) 60%, var(--dsw-alias-state-error-secondary, #f25a5a));
418
+ }
419
+ .ttl-dot-red {
420
+ background: var(--dsw-alias-state-error-primary, #f25a5a);
421
+ }
422
+ .ttl-rowName {
423
+ flex: none;
424
+ min-width: 30px;
425
+ max-width: 40%;
426
+ overflow: hidden;
427
+ text-overflow: ellipsis;
428
+ white-space: nowrap;
429
+ color: var(--dsw-alias-label-secondary, #cfd3d6);
430
+ }
431
+ .ttl-rowNameMono {
432
+ font-family: var(--ds-font-family-code, "SF Mono", "JetBrains Mono", "Fira Code", Consolas, "Liberation Mono", Menlo, Courier, "PingFang SC", "Microsoft YaHei", monospace);
433
+ }
434
+ .ttl-rowText {
435
+ flex: 1;
436
+ min-width: 0;
437
+ color: var(--dsw-alias-label-primary, #f9fafb);
438
+ white-space: pre-wrap;
439
+ overflow-wrap: anywhere;
440
+ display: -webkit-box;
441
+ -webkit-box-orient: vertical;
442
+ -webkit-line-clamp: 3;
443
+ line-clamp: 3;
444
+ overflow: hidden;
445
+ }
446
+ .ttl-rowText2 {
447
+ -webkit-line-clamp: 2;
448
+ line-clamp: 2;
449
+ }
450
+ .ttl-rowTextMuted { color: var(--dsw-alias-label-tertiary, #adb2b8); }
451
+ .ttl-rowArgs {
452
+ flex: 1;
453
+ min-width: 0;
454
+ font-family: var(--ds-font-family-code, "SF Mono", "JetBrains Mono", "Fira Code", Consolas, "Liberation Mono", Menlo, Courier, "PingFang SC", "Microsoft YaHei", monospace);
455
+ font-size: 12px;
456
+ line-height: 18px;
457
+ color: var(--dsw-alias-label-secondary, #cfd3d6);
458
+ white-space: nowrap;
459
+ overflow: hidden;
460
+ text-overflow: ellipsis;
461
+ }
462
+ /* Error rows read red across dot and payload. */
463
+ .ttl-row[data-step-kind="error"] .ttl-rowArgs {
464
+ color: var(--dsw-alias-state-error-primary, #f25a5a);
465
+ }
466
+ .ttl-emptyInline {
467
+ padding: 6px 8px;
468
+ color: var(--dsw-alias-label-tertiary, #adb2b8);
469
+ font-size: 11px;
470
+ line-height: 16px;
471
+ }
472
+ .ttl-showEarlier {
473
+ box-sizing: border-box;
474
+ width: 100%;
475
+ height: 26px;
476
+ margin: 2px 0 4px;
477
+ padding: 0 8px;
478
+ border: none;
479
+ border-bottom: 1px dashed var(--dsw-alias-border-l2, rgba(255, 255, 255, 0.12));
480
+ border-radius: 0;
481
+ background: transparent;
482
+ color: var(--dsw-alias-label-tertiary, #adb2b8);
483
+ font: inherit;
484
+ font-size: 11px;
485
+ line-height: 16px;
486
+ text-align: left;
487
+ cursor: pointer;
488
+ }
489
+ .ttl-showEarlier:hover {
490
+ background: var(--dsw-alias-interactive-bg-hover, rgba(255, 255, 255, 0.08));
491
+ color: var(--dsw-alias-label-secondary, #cfd3d6);
492
+ }
493
+ /* \u2500\u2500 Status bar / jump feedback \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
494
+ .ttl-statusBar {
495
+ box-sizing: border-box;
496
+ flex: none;
497
+ display: flex;
498
+ align-items: center;
499
+ gap: 8px;
500
+ min-height: 30px;
501
+ padding: 0 14px;
502
+ border-top: 1px solid var(--dsw-alias-border-l1, rgba(255, 255, 255, 0.06));
503
+ color: var(--dsw-alias-label-tertiary, #adb2b8);
504
+ font-size: 12px;
505
+ line-height: 18px;
506
+ }
507
+ .ttl-spin {
508
+ display: inline-flex;
509
+ color: var(--dsw-alias-state-business-primary, #4176e6);
510
+ animation: ttl-spin 1s linear infinite;
511
+ }
512
+ @keyframes ttl-spin { to { transform: rotate(360deg); } }
513
+ .ttl-jumpBanner {
514
+ position: absolute;
515
+ top: 8px;
516
+ left: 50%;
517
+ transform: translateX(-50%);
518
+ z-index: 8;
519
+ display: flex;
520
+ align-items: center;
521
+ gap: 7px;
522
+ max-width: 90%;
523
+ padding: 5px 10px;
524
+ border: 1px solid var(--dsw-alias-border-l2, rgba(255, 255, 255, 0.12));
525
+ border-radius: 8px;
526
+ background: var(--dsw-alias-button-floating-fill, #2c2c2e);
527
+ color: var(--dsw-alias-label-primary, #f9fafb);
528
+ font-size: 12px;
529
+ font-weight: 400;
530
+ line-height: 18px;
531
+ white-space: nowrap;
532
+ box-shadow: 0 4px 16px var(--dsw-alias-bg-mask-1, rgba(0, 0, 0, 0.24));
533
+ }
534
+ .ttl-toBottom {
535
+ position: absolute;
536
+ right: 12px;
537
+ bottom: 12px;
538
+ z-index: 5;
539
+ box-sizing: border-box;
540
+ height: 28px;
541
+ padding: 0 12px;
542
+ border: 1px solid var(--dsw-alias-border-l2, rgba(255, 255, 255, 0.12));
543
+ border-radius: 8px;
544
+ background: var(--dsw-alias-button-floating-fill, #2c2c2e);
545
+ color: var(--dsw-alias-label-secondary, #cfd3d6);
546
+ font: inherit;
547
+ font-size: 12px;
548
+ line-height: 18px;
549
+ cursor: pointer;
550
+ }
551
+ .ttl-toBottom:hover {
552
+ background: var(--dsw-alias-interactive-bg-hover-solid, #353638);
553
+ color: var(--dsw-alias-label-primary, #f9fafb);
554
+ }
555
+
556
+ /* \u2500\u2500 Jump highlight flash (on the main conversation) \u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
557
+ .ttl-flash { animation: ttl-flash 1.6s ease-out; }
558
+ @keyframes ttl-flash {
559
+ 0% { box-shadow: 0 0 0 3px var(--dsw-alias-state-business-primary, #4176e6); }
560
+ 100% { box-shadow: 0 0 0 0 rgba(0, 0, 0, 0); }
561
+ }
562
+
563
+ @media (prefers-reduced-motion: reduce) {
564
+ .ttl-spin { animation: none; }
565
+ }
566
+ `;
567
+
568
+ // src/client/TrailTab.tsx
569
+ var React2 = __toESM(require("react"), 1);
570
+
571
+ // src/trail-model.ts
572
+ var PREVIEW_MAX = 120;
573
+ function stepsOf(turn) {
574
+ const out = [];
575
+ for (const item of turn.items) {
576
+ if (item.kind === "tool") out.push(item.step);
577
+ }
578
+ return out;
579
+ }
580
+ function compact(value, max = PREVIEW_MAX) {
581
+ const oneLine = value.replace(/\s+/g, " ").trim();
582
+ if (oneLine.length <= max) return oneLine;
583
+ return `${oneLine.slice(0, max - 1).trimEnd()}\u2026`;
584
+ }
585
+ function contentPreview(blocks) {
586
+ if (typeof blocks === "string") return compact(blocks);
587
+ if (!Array.isArray(blocks)) return "";
588
+ for (const block of blocks) {
589
+ if (block !== null && typeof block === "object" && block.type === "text" && typeof block.text === "string" && block.text.trim().length > 0) {
590
+ return compact(block.text);
591
+ }
592
+ }
593
+ return "";
594
+ }
595
+ function lastTextBlock(blocks) {
596
+ if (typeof blocks === "string") return blocks;
597
+ if (!Array.isArray(blocks)) return "";
598
+ let last = "";
599
+ for (const block of blocks) {
600
+ if (block !== null && typeof block === "object" && block.type === "text" && typeof block.text === "string" && block.text.trim().length > 0) {
601
+ last = block.text;
602
+ }
603
+ }
604
+ return last;
605
+ }
606
+ function objectData(event) {
607
+ return event.data !== null && typeof event.data === "object" ? event.data : {};
608
+ }
609
+ function sourceKindOf(event, data) {
610
+ const message = data.message !== null && typeof data.message === "object" ? data.message : {};
611
+ const source = message !== null && typeof message.source === "object" ? message.source : data.source !== null && typeof data.source === "object" ? data.source : null;
612
+ if (source !== null && typeof source.kind === "string") return source.kind;
613
+ return void 0;
614
+ }
615
+ function toolCallIdOf(data) {
616
+ if (typeof data.callId === "string" && data.callId.length > 0) return data.callId;
617
+ const message = data.message !== null && typeof data.message === "object" ? data.message : {};
618
+ const source = message !== null && typeof message.source === "object" ? message.source : null;
619
+ if (source !== null && typeof source.callId === "string" && source.callId.length > 0) {
620
+ return source.callId;
621
+ }
622
+ return null;
623
+ }
624
+ function newBuilder(turn) {
625
+ return {
626
+ turn,
627
+ startSeq: null,
628
+ endSeq: null,
629
+ input: "",
630
+ inputSeq: null,
631
+ output: "",
632
+ outputSeq: null,
633
+ items: [],
634
+ steps: [],
635
+ contexts: [],
636
+ hasError: false
637
+ };
638
+ }
639
+ function appendItem(builder, item) {
640
+ builder.items.push(item);
641
+ if (item.kind === "tool") {
642
+ builder.steps.push(item.step);
643
+ if (item.step.kind === "error") builder.hasError = true;
644
+ }
645
+ }
646
+ function finishBuilder(builder, index, boundarySeq) {
647
+ const anchorSeq = builder.inputSeq !== null ? builder.inputSeq : boundarySeq;
648
+ const status = builder.hasError ? "error" : builder.endSeq === null ? builder.items.length > 0 ? "running" : "pending" : "done";
649
+ return {
650
+ index,
651
+ turn: builder.turn,
652
+ anchorSeq,
653
+ inputPreview: compact(builder.input),
654
+ outputPreview: compact(builder.output),
655
+ status,
656
+ ...builder.startedAt !== void 0 ? { startedAt: builder.startedAt } : {},
657
+ ...builder.endedAt !== void 0 ? { endedAt: builder.endedAt } : {},
658
+ items: builder.items,
659
+ stepsCount: builder.steps.length,
660
+ contextCount: builder.contexts.length,
661
+ hasError: builder.hasError
662
+ };
663
+ }
664
+ function firstItemSeq(items) {
665
+ for (const item of items) {
666
+ if (item.kind === "tool") return item.step.seq;
667
+ return item.seq;
668
+ }
669
+ return 0;
670
+ }
671
+ function finishUserTurn(builder, index) {
672
+ const boundarySeq = builder.inputSeq !== null ? builder.inputSeq : builder.startSeq ?? 0;
673
+ return finishBuilder(builder, index, boundarySeq);
674
+ }
675
+ var TrailFolder = class {
676
+ constructor() {
677
+ this.mode = "unknown";
678
+ // turn-mode state
679
+ this.builders = /* @__PURE__ */ new Map();
680
+ this.order = [];
681
+ this.frozen = [];
682
+ this.pendingUser = [];
683
+ this.current = null;
684
+ // user-mode state
685
+ this.userClosed = [];
686
+ this.userActive = null;
687
+ this.userActiveTurn = null;
688
+ this.userCounter = 0;
689
+ this.lastSeq = -1;
690
+ this.total = 0;
691
+ this.failed = 0;
692
+ this.byTool = /* @__PURE__ */ new Map();
693
+ }
694
+ /** Highest event seq consumed so far (duplicates / re-pages are skipped). */
695
+ get latestSeq() {
696
+ return this.lastSeq;
697
+ }
698
+ push(event) {
699
+ if (event === null || typeof event !== "object" || typeof event.type !== "string") return;
700
+ const seq = typeof event.seq === "number" ? event.seq : Number.NaN;
701
+ if (!Number.isFinite(seq) || seq <= this.lastSeq) return;
702
+ const data = objectData(event);
703
+ const time = typeof event.time === "number" ? event.time : void 0;
704
+ this.lastSeq = seq;
705
+ if (this.mode === "turn") {
706
+ this.applyTurnEvent(event.type, data, seq, time);
707
+ return;
708
+ }
709
+ if (this.mode === "user") {
710
+ if (event.type === "turn/start" && typeof data.turn === "number") {
711
+ this.flipToTurnMode();
712
+ this.applyTurnEvent(event.type, data, seq, time);
713
+ } else {
714
+ this.applyUserEvent(event.type, data, seq, time);
715
+ }
716
+ return;
717
+ }
718
+ if (event.type === "turn/start" && typeof data.turn === "number") {
719
+ this.mode = "turn";
720
+ this.applyTurnEvent(event.type, data, seq, time);
721
+ return;
722
+ }
723
+ if (event.type === "user/message" && sourceKindOf(event, data) === "user") {
724
+ this.mode = "user";
725
+ this.applyUserEvent(event.type, data, seq, time);
726
+ return;
727
+ }
728
+ }
729
+ /** The current turn list; closed turns keep their object identity. */
730
+ snapshot() {
731
+ if (this.mode === "unknown") return [];
732
+ if (this.mode === "user") {
733
+ const out2 = this.userClosed.slice();
734
+ if (this.userActive !== null) {
735
+ if (this.userActiveTurn === null) {
736
+ this.userActiveTurn = finishUserTurn(this.userActive, this.userClosed.length);
737
+ }
738
+ out2.push(this.userActiveTurn);
739
+ }
740
+ return out2;
741
+ }
742
+ const out = [];
743
+ for (let i = 0; i < this.order.length; i += 1) {
744
+ const materialized = this.frozen[i];
745
+ if (materialized !== null) {
746
+ out.push(materialized);
747
+ continue;
748
+ }
749
+ const builder = this.builders.get(this.order[i]);
750
+ if (builder === void 0) continue;
751
+ out.push(this.materializeTurn(builder, i));
752
+ }
753
+ let counter = this.order.length;
754
+ for (const pending of this.pendingUser) {
755
+ const builder = newBuilder(null);
756
+ builder.input = pending.text;
757
+ builder.inputSeq = pending.seq;
758
+ appendItem(builder, { kind: "input", seq: pending.seq, text: pending.text, preview: compact(pending.text) });
759
+ out.push(finishBuilder(builder, counter, pending.seq));
760
+ counter += 1;
761
+ }
762
+ return out;
763
+ }
764
+ /** Incrementally maintained step statistics (total / failed / by-tool). */
765
+ stats() {
766
+ const tools = [...this.byTool.entries()].sort((a, b) => b[1] - a[1]).map(([name, count]) => ({ name, count }));
767
+ return { total: this.total, failed: this.failed, tools };
768
+ }
769
+ // ── turn-mode application (mirrors foldTurns' switch verbatim) ─────────
770
+ builderOf(turn) {
771
+ let builder = this.builders.get(turn);
772
+ if (builder === void 0) {
773
+ builder = newBuilder(turn);
774
+ builder.position = this.order.length;
775
+ this.builders.set(turn, builder);
776
+ this.order.push(turn);
777
+ this.frozen.push(null);
778
+ }
779
+ return builder;
780
+ }
781
+ markDirty(builder) {
782
+ if (this.mode === "user") {
783
+ if (builder === this.userActive) this.userActiveTurn = null;
784
+ return;
785
+ }
786
+ const position = builder.position;
787
+ if (position !== void 0 && position >= 0 && position < this.frozen.length) {
788
+ this.frozen[position] = null;
789
+ }
790
+ }
791
+ addStep(builder, item) {
792
+ appendItem(builder, item);
793
+ this.total += 1;
794
+ if (item.step.kind === "error") this.failed += 1;
795
+ else this.byTool.set(item.step.name, (this.byTool.get(item.step.name) ?? 0) + 1);
796
+ this.markDirty(builder);
797
+ }
798
+ materializeTurn(builder, position) {
799
+ const boundarySeq = builder.startSeq !== null ? builder.startSeq : builder.inputSeq ?? firstItemSeq(builder.items);
800
+ const materialized = finishBuilder(builder, position, boundarySeq);
801
+ this.frozen[position] = materialized;
802
+ return materialized;
803
+ }
804
+ applyTurnEvent(type, data, seq, time) {
805
+ switch (type) {
806
+ case "turn/start": {
807
+ const raw = data.turn;
808
+ if (typeof raw !== "number") break;
809
+ const builder = this.builderOf(raw);
810
+ if (builder.startSeq === null) builder.startSeq = seq;
811
+ if (builder.startedAt === void 0) builder.startedAt = time;
812
+ for (const pending of this.pendingUser.splice(0)) {
813
+ builder.input = pending.text;
814
+ builder.inputSeq = pending.seq;
815
+ builder.items.unshift({ kind: "input", seq: pending.seq, text: pending.text, preview: compact(pending.text) });
816
+ }
817
+ this.current = builder;
818
+ this.markDirty(builder);
819
+ break;
820
+ }
821
+ case "turn/end": {
822
+ const raw = data.turn;
823
+ const target = typeof raw === "number" ? this.builderOf(raw) : this.current;
824
+ if (target !== null) {
825
+ target.endSeq = seq;
826
+ target.endedAt = time;
827
+ this.current = null;
828
+ this.markDirty(target);
829
+ }
830
+ break;
831
+ }
832
+ case "user/message": {
833
+ const preview = contentPreview(data.content);
834
+ if (preview.length === 0) break;
835
+ const kind = sourceKindOf({ type, seq, data }, data);
836
+ const text = typeof data.content === "string" ? data.content : typeof data.message === "object" && data.message !== null && typeof data.message.content === "string" ? data.message.content : preview;
837
+ if (kind === "user") {
838
+ if (this.current === null) {
839
+ this.pendingUser.push({ seq, text });
840
+ } else {
841
+ this.current.input = text;
842
+ this.current.inputSeq = seq;
843
+ appendItem(this.current, { kind: "input", seq, text, preview });
844
+ this.markDirty(this.current);
845
+ }
846
+ } else if (this.current !== null) {
847
+ this.current.contexts.push({ seq, text, preview });
848
+ this.markDirty(this.current);
849
+ }
850
+ break;
851
+ }
852
+ case "assistant/message": {
853
+ const message = data.message !== null && typeof data.message === "object" ? data.message : {};
854
+ const content = typeof message.content !== "undefined" ? message.content : data.content;
855
+ const text = lastTextBlock(content);
856
+ if (text.length === 0) break;
857
+ if (this.current !== null) {
858
+ this.current.output = text;
859
+ this.current.outputSeq = seq;
860
+ appendItem(this.current, { kind: "output", seq, text, preview: compact(text) });
861
+ this.markDirty(this.current);
862
+ }
863
+ break;
864
+ }
865
+ case "tool/call": {
866
+ if (this.current === null) break;
867
+ const name = typeof data.name === "string" && data.name.length > 0 ? data.name : "tool";
868
+ const args = typeof data.arguments === "string" && data.arguments.length > 0 ? data.arguments : "";
869
+ const step = {
870
+ kind: "tool",
871
+ seq,
872
+ name,
873
+ argsPreview: compact(args),
874
+ callId: toolCallIdOf(data),
875
+ error: ""
876
+ };
877
+ this.addStep(this.current, { kind: "tool", step });
878
+ break;
879
+ }
880
+ case "tool/result": {
881
+ const error = data.error !== null && typeof data.error === "object" ? data.error : null;
882
+ if (error === null) break;
883
+ if (this.current === null) break;
884
+ const name = typeof error.name === "string" && error.name.length > 0 ? error.name : "tool";
885
+ const detail = typeof error.code === "string" && error.code.length > 0 ? ` \xB7 ${error.code}` : "";
886
+ const step = {
887
+ kind: "error",
888
+ seq,
889
+ name,
890
+ argsPreview: "",
891
+ callId: toolCallIdOf(data),
892
+ error: compact(`${name}${detail}`)
893
+ };
894
+ this.addStep(this.current, { kind: "tool", step });
895
+ break;
896
+ }
897
+ case "compaction/summary": {
898
+ const preview = contentPreview(data.summary);
899
+ if (preview.length === 0) break;
900
+ if (this.current !== null) {
901
+ appendItem(this.current, { kind: "compaction", seq, preview });
902
+ this.markDirty(this.current);
903
+ }
904
+ break;
905
+ }
906
+ default:
907
+ break;
908
+ }
909
+ }
910
+ // ── user-mode application (mirrors foldByUserBoundaries verbatim) ───────
911
+ closeUserTurn() {
912
+ if (this.userActive === null) return;
913
+ this.userClosed.push(finishUserTurn(this.userActive, this.userCounter));
914
+ this.userCounter += 1;
915
+ this.userActive = null;
916
+ this.userActiveTurn = null;
917
+ }
918
+ applyUserEvent(type, data, seq, time) {
919
+ if (type === "user/message") {
920
+ const kind = sourceKindOf({ type, seq, data }, data);
921
+ if (kind !== "user") return;
922
+ this.closeUserTurn();
923
+ const text = typeof data.content === "string" ? data.content : contentPreview(data.content);
924
+ if (text.length === 0) return;
925
+ const builder = newBuilder(null);
926
+ builder.startSeq = seq;
927
+ builder.startedAt = time;
928
+ builder.input = text;
929
+ builder.inputSeq = seq;
930
+ appendItem(builder, { kind: "input", seq, text, preview: compact(text) });
931
+ this.userActive = builder;
932
+ this.userActiveTurn = null;
933
+ return;
934
+ }
935
+ if (this.userActive === null) return;
936
+ if (type === "assistant/message") {
937
+ const message = data.message !== null && typeof data.message === "object" ? data.message : {};
938
+ const text = lastTextBlock(message.content ?? data.content);
939
+ if (text.length === 0) return;
940
+ this.userActive.output = text;
941
+ this.userActive.outputSeq = seq;
942
+ appendItem(this.userActive, { kind: "output", seq, text, preview: compact(text) });
943
+ this.userActiveTurn = null;
944
+ return;
945
+ }
946
+ if (type === "tool/call") {
947
+ const name = typeof data.name === "string" && data.name.length > 0 ? data.name : "tool";
948
+ const args = typeof data.arguments === "string" && data.arguments.length > 0 ? data.arguments : "";
949
+ const step = {
950
+ kind: "tool",
951
+ seq,
952
+ name,
953
+ argsPreview: compact(args),
954
+ callId: toolCallIdOf(data),
955
+ error: ""
956
+ };
957
+ this.addStep(this.userActive, { kind: "tool", step });
958
+ return;
959
+ }
960
+ if (type === "tool/result") {
961
+ const error = data.error !== null && typeof data.error === "object" ? data.error : null;
962
+ if (error === null) return;
963
+ const name = typeof error.name === "string" && error.name.length > 0 ? error.name : "tool";
964
+ const detail = typeof error.code === "string" && error.code.length > 0 ? ` \xB7 ${error.code}` : "";
965
+ const step = {
966
+ kind: "error",
967
+ seq,
968
+ name,
969
+ argsPreview: "",
970
+ callId: toolCallIdOf(data),
971
+ error: compact(`${name}${detail}`)
972
+ };
973
+ this.addStep(this.userActive, { kind: "tool", step });
974
+ return;
975
+ }
976
+ }
977
+ /**
978
+ * A turn/start arrived after user-boundary turns were folded. Reconstruct
979
+ * what a from-scratch turn-mode fold of the consumed prefix would hold:
980
+ * every user input becomes a pending input (in arrival order), and the
981
+ * pre-turn outputs / tool steps are dropped — turn-mode replay attaches
982
+ * nothing while no turn is open. Statistics reset accordingly.
983
+ */
984
+ flipToTurnMode() {
985
+ const pending = [];
986
+ const take = (turn) => {
987
+ for (const item of turn.items) {
988
+ if (item.kind === "input") {
989
+ pending.push({ seq: item.seq, text: item.text });
990
+ return;
991
+ }
992
+ }
993
+ };
994
+ for (const turn of this.userClosed) take(turn);
995
+ if (this.userActive !== null && this.userActive.inputSeq !== null) {
996
+ pending.push({ seq: this.userActive.inputSeq, text: this.userActive.input });
997
+ }
998
+ this.userClosed.length = 0;
999
+ this.userActive = null;
1000
+ this.userActiveTurn = null;
1001
+ this.userCounter = 0;
1002
+ this.pendingUser.length = 0;
1003
+ this.pendingUser.push(...pending);
1004
+ this.total = 0;
1005
+ this.failed = 0;
1006
+ this.byTool.clear();
1007
+ this.mode = "turn";
1008
+ }
1009
+ };
1010
+
1011
+ // src/client/locales.ts
1012
+ function detectLang() {
1013
+ if (typeof navigator === "undefined") return "en";
1014
+ const raw = navigator.language ?? "";
1015
+ return raw.toLowerCase().startsWith("zh") ? "zh" : "en";
1016
+ }
1017
+ var zh = {
1018
+ tabTitle: "\u8F6E\u6B21\u8F68\u8FF9",
1019
+ turnsCount: (n) => `${n} \u8F6E`,
1020
+ followLatest: "\u8DDF\u968F\u6700\u65B0",
1021
+ followPaused: "\u5DF2\u6682\u505C\u8DDF\u968F",
1022
+ newUpdates: "\u6709\u65B0\u66F4\u65B0 \u2193",
1023
+ filterAll: "\u5168\u90E8",
1024
+ filterFailed: "\u5931\u8D25",
1025
+ filterMore: "\u66F4\u591A",
1026
+ clearFilter: "\u6E05\u7A7A\u7B5B\u9009",
1027
+ emptyNoTurns: "\u4F1A\u8BDD\u8FD8\u6CA1\u6709\u8F6E\u6B21",
1028
+ emptyFiltered: "\u6CA1\u6709\u7B26\u5408\u7B5B\u9009\u7684\u6B65\u9AA4",
1029
+ loadFailed: "\u4F1A\u8BDD\u6570\u636E\u52A0\u8F7D\u5931\u8D25",
1030
+ loading: "\u6B63\u5728\u52A0\u8F7D\u4F1A\u8BDD\u6570\u636E\u2026",
1031
+ retry: "\u91CD\u8BD5",
1032
+ stepsCount: (n) => `${n} \u6B65\u9AA4`,
1033
+ toolCalls: (n) => `${n} \u6B21\u8C03\u7528`,
1034
+ input: "\u8F93\u5165",
1035
+ output: "\u8F93\u51FA",
1036
+ context: "\u4E0A\u4E0B\u6587",
1037
+ contextCount: (n) => `${n} \u6761`,
1038
+ compaction: "\u538B\u7F29\u68C0\u67E5\u70B9",
1039
+ compactionTag: "\u538B\u7F29",
1040
+ jumpProgress: (n) => `\u6B63\u5728\u5B9A\u4F4D\u66F4\u65E9\u7684\u5BF9\u8BDD\uFF08\u5DF2\u52A0\u8F7D ${n} \u9875\uFF09\u2026`,
1041
+ jumpDone: "\u5DF2\u5B9A\u4F4D",
1042
+ jumpFailed: "\u65E0\u6CD5\u5B9A\u4F4D\u8BE5\u6D88\u606F",
1043
+ jumpTrajectory: "\u5DF2\u5728\u8F68\u8FF9\u89C6\u56FE\u5B9A\u4F4D",
1044
+ previewExpand: "\u5C55\u5F00",
1045
+ previewClose: "\u5173\u95ED",
1046
+ previewTitle: (label) => label,
1047
+ copyTurn: "\u590D\u5236\u672C\u8F6E",
1048
+ copied: "\u5DF2\u590D\u5236",
1049
+ running: "\u8FDB\u884C\u4E2D",
1050
+ error: "\u9519\u8BEF",
1051
+ turnPrefix: (n) => `\u8F6E\u6B21 ${n}`,
1052
+ expandAll: "\u5C55\u5F00\u5168\u90E8",
1053
+ collapseAll: "\u5168\u90E8\u6298\u53E0",
1054
+ locate: "\u5B9A\u4F4D",
1055
+ stepsLabel: "\u5DE5\u5177\u6B65\u9AA4\u6570",
1056
+ filterCollapse: "\u6536\u8D77",
1057
+ showEarlierSteps: (n) => `\u663E\u793A\u66F4\u65E9 ${n} \u6761`
1058
+ };
1059
+ var en = {
1060
+ tabTitle: "Turn Outline",
1061
+ turnsCount: (n) => `${n} turns`,
1062
+ followLatest: "Follow latest",
1063
+ followPaused: "Follow paused",
1064
+ newUpdates: "New updates \u2193",
1065
+ filterAll: "All",
1066
+ filterFailed: "Failed",
1067
+ filterMore: "More",
1068
+ clearFilter: "Clear filter",
1069
+ emptyNoTurns: "No turns in this session yet",
1070
+ emptyFiltered: "No steps match the filter",
1071
+ loadFailed: "Failed to load session data",
1072
+ loading: "Loading session data\u2026",
1073
+ retry: "Retry",
1074
+ stepsCount: (n) => `${n} steps`,
1075
+ toolCalls: (n) => `${n} calls`,
1076
+ input: "Input",
1077
+ output: "Output",
1078
+ context: "Context",
1079
+ contextCount: (n) => `${n} items`,
1080
+ compaction: "Compaction checkpoint",
1081
+ compactionTag: "Fold",
1082
+ jumpProgress: (n) => `Locating earlier conversation (loaded ${n} page${n === 1 ? "" : "s"})\u2026`,
1083
+ jumpDone: "Located",
1084
+ jumpFailed: "Could not locate that message",
1085
+ jumpTrajectory: "Located in the trajectory view",
1086
+ previewExpand: "Expand",
1087
+ previewClose: "Close",
1088
+ previewTitle: (label) => label,
1089
+ copyTurn: "Copy turn",
1090
+ copied: "Copied",
1091
+ running: "running",
1092
+ error: "error",
1093
+ turnPrefix: (n) => `Turn ${n}`,
1094
+ expandAll: "Expand all",
1095
+ collapseAll: "Collapse all",
1096
+ locate: "Locate",
1097
+ stepsLabel: "tool steps",
1098
+ filterCollapse: "Collapse",
1099
+ showEarlierSteps: (n) => `Show ${n} earlier`
1100
+ };
1101
+ var dicts = { zh, en };
1102
+ function t(lang, key) {
1103
+ return dicts[lang][key];
1104
+ }
1105
+
1106
+ // src/client/useTrail.ts
1107
+ var React = __toESM(require("react"), 1);
1108
+ var MAX_PULL_PAGES = 64;
1109
+ function fetchTrailEvents(sessionId, afterSeq, base) {
1110
+ const url = new URL("/turntrail/api/events", base ?? window.location.origin);
1111
+ return fetch(url.toString(), {
1112
+ method: "POST",
1113
+ headers: { "content-type": "application/json", accept: "application/json" },
1114
+ body: JSON.stringify({ sessionId, afterSeq })
1115
+ }).then(async (response) => {
1116
+ const data = await response.json();
1117
+ if (!response.ok || data === null || typeof data !== "object" || !Array.isArray(data.events)) {
1118
+ throw new Error(data !== null && typeof data === "object" && typeof data.error === "string" ? data.error : `HTTP ${response.status}`);
1119
+ }
1120
+ return { events: data.events, hasMore: data.hasMore === true };
1121
+ });
1122
+ }
1123
+ var EMPTY = [];
1124
+ var EMPTY_STATS = { total: 0, failed: 0, tools: [] };
1125
+ function useTrail(sessionId, visible, fetchEvents, sessionsService) {
1126
+ const [turns, setTurns] = React.useState(EMPTY);
1127
+ const [stats, setStats] = React.useState(EMPTY_STATS);
1128
+ const [status, setStatus] = React.useState("idle");
1129
+ const [error, setError] = React.useState(null);
1130
+ const folderRef = React.useRef(null);
1131
+ const seqRef = React.useRef(0);
1132
+ const sessionIdRef = React.useRef(sessionId);
1133
+ const fetchRef = React.useRef(fetchEvents);
1134
+ const requestGen = React.useRef(0);
1135
+ const lastPullAt = React.useRef(0);
1136
+ const throttleTimer = React.useRef(null);
1137
+ React.useEffect(() => {
1138
+ fetchRef.current = fetchEvents;
1139
+ }, [fetchEvents]);
1140
+ React.useEffect(() => () => {
1141
+ if (throttleTimer.current !== null) window.clearTimeout(throttleTimer.current);
1142
+ }, []);
1143
+ const pull = React.useCallback(async () => {
1144
+ const generation = ++requestGen.current;
1145
+ const sid = sessionIdRef.current;
1146
+ try {
1147
+ for (let page = 0; page < MAX_PULL_PAGES; page += 1) {
1148
+ const { events, hasMore } = await fetchRef.current(sid, seqRef.current);
1149
+ if (generation !== requestGen.current || sid !== sessionIdRef.current) return;
1150
+ if (events.length > 0) {
1151
+ let folder = folderRef.current;
1152
+ if (folder === null) folder = folderRef.current = new TrailFolder();
1153
+ let anyPushed = false;
1154
+ for (const event of events) {
1155
+ const before = folder.latestSeq;
1156
+ folder.push(event);
1157
+ if (folder.latestSeq !== before) anyPushed = true;
1158
+ }
1159
+ if (anyPushed) {
1160
+ const last = events[events.length - 1];
1161
+ if (typeof last.seq === "number" && last.seq > seqRef.current) seqRef.current = last.seq;
1162
+ setTurns(folder.snapshot());
1163
+ setStats(folder.stats());
1164
+ }
1165
+ }
1166
+ if (hasMore !== true || events.length === 0) break;
1167
+ }
1168
+ setError(null);
1169
+ setStatus("ready");
1170
+ } catch (reason) {
1171
+ if (generation !== requestGen.current) return;
1172
+ setError(reason instanceof Error ? reason.message : String(reason));
1173
+ setStatus((current) => current === "ready" ? current : "error");
1174
+ }
1175
+ }, []);
1176
+ const refresh = React.useCallback(() => {
1177
+ void pull();
1178
+ }, [pull]);
1179
+ const schedulePull = React.useCallback(() => {
1180
+ const MIN_GAP = 120;
1181
+ const now = Date.now();
1182
+ const since = now - lastPullAt.current;
1183
+ if (since >= MIN_GAP) {
1184
+ lastPullAt.current = now;
1185
+ void pull();
1186
+ return;
1187
+ }
1188
+ if (throttleTimer.current !== null) return;
1189
+ throttleTimer.current = window.setTimeout(() => {
1190
+ throttleTimer.current = null;
1191
+ lastPullAt.current = Date.now();
1192
+ void pull();
1193
+ }, MIN_GAP - since);
1194
+ }, [pull]);
1195
+ React.useEffect(() => {
1196
+ sessionIdRef.current = sessionId;
1197
+ folderRef.current = null;
1198
+ seqRef.current = 0;
1199
+ setTurns(EMPTY);
1200
+ setStats(EMPTY_STATS);
1201
+ setError(null);
1202
+ setStatus(sessionId.length > 0 ? "loading" : "idle");
1203
+ }, [sessionId]);
1204
+ React.useEffect(() => {
1205
+ if (sessionId.length === 0 || !visible) return void 0;
1206
+ void pull();
1207
+ const binding = sessionsService?.binding(sessionId);
1208
+ const session = binding !== void 0 && binding.session !== void 0 ? binding.session : void 0;
1209
+ if (session === void 0 || typeof session.subscribe !== "function") return void 0;
1210
+ return session.subscribe(() => {
1211
+ schedulePull();
1212
+ });
1213
+ }, [sessionId, visible, pull, schedulePull, sessionsService]);
1214
+ return { turns, stats, status, error, refresh };
1215
+ }
1216
+
1217
+ // src/client/trail-jump.ts
1218
+ function hasSeqInWindow(snapshot, seq) {
1219
+ const store = snapshot.chat?.nodes;
1220
+ if (store === void 0 || typeof store.values !== "function") return false;
1221
+ let min = Number.POSITIVE_INFINITY;
1222
+ let max = Number.NEGATIVE_INFINITY;
1223
+ for (const node of store.values()) {
1224
+ if (node === null || typeof node !== "object" || typeof node.anchorSeq !== "number") continue;
1225
+ if (node.anchorSeq < min) min = node.anchorSeq;
1226
+ if (node.anchorSeq > max) max = node.anchorSeq;
1227
+ }
1228
+ return min <= seq && seq <= max;
1229
+ }
1230
+ function findNodeForSeq(snapshot, seq) {
1231
+ const store = snapshot.chat?.nodes;
1232
+ if (store === void 0 || typeof store.values !== "function") return null;
1233
+ let exact = null;
1234
+ let next = null;
1235
+ let prev = null;
1236
+ for (const node of store.values()) {
1237
+ if (node === null || typeof node !== "object" || node.visibility === "hidden") continue;
1238
+ const anchor = node.anchorSeq;
1239
+ if (typeof anchor !== "number") continue;
1240
+ if (Math.abs(anchor - seq) < 0.5) {
1241
+ if (exact === null) exact = node;
1242
+ continue;
1243
+ }
1244
+ if (anchor >= seq && (next === null || anchor < next.anchorSeq)) next = node;
1245
+ if (anchor <= seq && (prev === null || anchor > prev.anchorSeq)) prev = node;
1246
+ }
1247
+ return exact !== null ? exact : next !== null ? next : prev;
1248
+ }
1249
+ function isTrajectoryViewActive(document2) {
1250
+ return document2.querySelector("[data-trajectory-scroll]") !== null;
1251
+ }
1252
+ function trajectoryRecordIdFor(item, snapshot) {
1253
+ const views = snapshot.views;
1254
+ if (views === void 0 || typeof views.get !== "function") return null;
1255
+ const trajectory = views.get("trajectory");
1256
+ if (trajectory === null || typeof trajectory !== "object") return null;
1257
+ const view = trajectory;
1258
+ const nodes = Array.isArray(view.eventNodes) ? view.eventNodes : [];
1259
+ const requests = Array.isArray(view.requests) ? view.requests : [];
1260
+ const nodeWithKind = (kinds, seq) => {
1261
+ const matches = nodes.filter((node) => node !== null && typeof node === "object" && kinds.includes(String(node.kind)) && typeof node.seq === "number");
1262
+ return matches.find((node) => node.seq === seq) ?? matches.filter((node) => node.seq >= seq).sort((a, b) => a.seq - b.seq)[0];
1263
+ };
1264
+ if (item.kind === "user" || item.kind === "context" || item.kind === "input") {
1265
+ const target = nodeWithKind(["user", "steering", "context"], item.seq);
1266
+ if (target === void 0 || typeof target.seq !== "number") return null;
1267
+ const rowKind = target.kind === "steering" ? "user" : target.kind;
1268
+ return `${rowKind}\0seq\0${target.seq}`;
1269
+ }
1270
+ if (item.kind === "assistant" || item.kind === "output") {
1271
+ const target = nodeWithKind(["assistant"], item.seq);
1272
+ if (target === void 0 || typeof target.turn !== "number" || typeof target.step !== "number") return null;
1273
+ return `assistant\0${target.turn}\0${target.step}`;
1274
+ }
1275
+ if (item.kind === "tool" || item.kind === "error") {
1276
+ if (typeof item.callId === "string" && item.callId.length > 0) return `tool\0call\0${item.callId}`;
1277
+ const target = nodeWithKind(["tool-result"], item.seq);
1278
+ if (target === void 0 || typeof target.callId !== "string") return null;
1279
+ return `tool\0call\0${target.callId}`;
1280
+ }
1281
+ if (item.kind === "compaction") {
1282
+ const compactions = [];
1283
+ for (const request of requests) {
1284
+ if (request === null || typeof request !== "object") continue;
1285
+ const record = request;
1286
+ if (record.purpose === "compaction" && typeof record.startSeq === "number") {
1287
+ compactions.push({ purpose: record.purpose, startSeq: record.startSeq, resultSeq: typeof record.resultSeq === "number" ? record.resultSeq : void 0 });
1288
+ }
1289
+ }
1290
+ const target = compactions.find((request) => request.resultSeq === item.seq) ?? compactions.filter((request) => (request.resultSeq ?? -1) >= item.seq).sort((a, b) => (a.resultSeq ?? -1) - (b.resultSeq ?? -1))[0];
1291
+ if (target === void 0) return null;
1292
+ return `compacted\0seq\0${target.startSeq}`;
1293
+ }
1294
+ return null;
1295
+ }
1296
+ function findTrajectoryRow(document2, key) {
1297
+ const encoded = encodeURIComponent(key);
1298
+ const rows = document2.querySelectorAll("tr[data-trajectory-row-key]");
1299
+ for (let index = 0; index < rows.length; index += 1) {
1300
+ const row = rows[index];
1301
+ const value = row.dataset !== void 0 ? row.dataset.trajectoryRowKey : void 0;
1302
+ if (value === encoded || value === key) return row;
1303
+ }
1304
+ return null;
1305
+ }
1306
+ function findTrajectoryRowBySeq(document2, item) {
1307
+ const rows = document2.querySelectorAll("tr[data-trajectory-row-key]");
1308
+ for (let index = 0; index < rows.length; index += 1) {
1309
+ const row = rows[index];
1310
+ const raw = row.dataset.trajectoryRowKey ?? "";
1311
+ let id = "";
1312
+ try {
1313
+ id = decodeURIComponent(raw);
1314
+ } catch {
1315
+ continue;
1316
+ }
1317
+ if (id.includes(`\0seq\0${item.seq}`)) return row;
1318
+ if (typeof item.callId === "string" && id.includes(`\0call\0${item.callId}`)) return row;
1319
+ }
1320
+ return null;
1321
+ }
1322
+ function expandCollapsedSummaries(document2) {
1323
+ const rows = document2.querySelectorAll("tr[data-collapsed-summary]");
1324
+ for (let index = 0; index < rows.length; index += 1) {
1325
+ const row = rows[index];
1326
+ if (typeof row.click === "function") row.click();
1327
+ }
1328
+ }
1329
+ function waitForNextFrame() {
1330
+ return new Promise((resolve) => {
1331
+ if (typeof requestAnimationFrame === "function") {
1332
+ requestAnimationFrame(() => resolve());
1333
+ } else {
1334
+ resolve();
1335
+ }
1336
+ });
1337
+ }
1338
+ async function jumpToItem(session, item, options) {
1339
+ const guard = Number.isFinite(Number(options.guard)) && (options.guard ?? 0) > 0 ? Math.min(500, Math.max(1, Math.floor(options.guard ?? 200))) : 200;
1340
+ const document2 = options.document;
1341
+ try {
1342
+ let snapshot = session.getSnapshot();
1343
+ const trajectoryActive = isTrajectoryViewActive(document2);
1344
+ if (trajectoryActive) {
1345
+ let key = trajectoryRecordIdFor(item, snapshot);
1346
+ let row2 = key === null ? null : findTrajectoryRow(document2, key);
1347
+ let pages2 = 0;
1348
+ if (row2 === null && snapshot.hasMore === true) {
1349
+ while (snapshot.hasMore === true && pages2 < guard) {
1350
+ await session.loadOlder();
1351
+ snapshot = session.getSnapshot();
1352
+ pages2 += 1;
1353
+ options.onProgress?.(pages2);
1354
+ }
1355
+ key = trajectoryRecordIdFor(item, snapshot);
1356
+ row2 = key === null ? null : findTrajectoryRow(document2, key);
1357
+ }
1358
+ if (row2 === null) {
1359
+ expandCollapsedSummaries(document2);
1360
+ await waitForNextFrame();
1361
+ row2 = key === null ? null : findTrajectoryRow(document2, key);
1362
+ }
1363
+ if (row2 === null) {
1364
+ row2 = findTrajectoryRowBySeq(document2, item);
1365
+ }
1366
+ if (row2 === null) return { ok: false, error: "trajectory-row-not-rendered" };
1367
+ row2.scrollIntoView({ behavior: "smooth", block: "center" });
1368
+ return { ok: true, mode: "trajectory" };
1369
+ }
1370
+ let pages = 0;
1371
+ while (snapshot.hasMore === true && !hasSeqInWindow(snapshot, item.seq) && pages < guard) {
1372
+ await session.loadOlder();
1373
+ snapshot = session.getSnapshot();
1374
+ pages += 1;
1375
+ options.onProgress?.(pages);
1376
+ }
1377
+ const node = findNodeForSeq(snapshot, item.seq);
1378
+ if (node === null) return { ok: false, error: "seq-not-rendered" };
1379
+ const selector = `[data-chat-anchor-key="${cssEscape(node.key)}"]`;
1380
+ let row = document2.querySelector(selector);
1381
+ let retries = 0;
1382
+ while (row === null && retries < 6) {
1383
+ await waitForNextFrame();
1384
+ row = document2.querySelector(selector);
1385
+ retries += 1;
1386
+ }
1387
+ if (row === null) return { ok: false, error: "row-not-in-chat-view" };
1388
+ row.scrollIntoView({ behavior: "smooth", block: "center" });
1389
+ if (row.classList !== void 0 && typeof row.classList.add === "function") {
1390
+ row.classList.add("ttl-flash");
1391
+ }
1392
+ return { ok: true, mode: "chat" };
1393
+ } catch (reason) {
1394
+ return { ok: false, error: reason instanceof Error ? reason.message : String(reason) };
1395
+ }
1396
+ }
1397
+ function cssEscape(value) {
1398
+ if (typeof CSS !== "undefined" && typeof CSS.escape === "function") return CSS.escape(value);
1399
+ return value.replace(/["\\]/g, "\\$&");
1400
+ }
1401
+
1402
+ // src/client/TrailTab.tsx
1403
+ function sessionsOf(ctx) {
1404
+ if (ctx === void 0 || typeof ctx.get !== "function") return void 0;
1405
+ const service = ctx.get("sessions");
1406
+ return service !== void 0 && service !== null && typeof service.binding === "function" ? service : void 0;
1407
+ }
1408
+ function turnByIdentity(turn) {
1409
+ return turn.turn !== null ? `turn:${turn.turn}` : `input:${turn.anchorSeq}`;
1410
+ }
1411
+ function formatCount(n) {
1412
+ if (n < 1e3) return String(n);
1413
+ const k = n / 1e3;
1414
+ return `${k >= 10 ? String(Math.round(k)) : String(Math.round(k * 10) / 10)}k`;
1415
+ }
1416
+ var STEP_CAP = 250;
1417
+ var TITLE_CAP = 512;
1418
+ function clipTitle(value) {
1419
+ return value.length > TITLE_CAP ? `${value.slice(0, TITLE_CAP)}\u2026` : value;
1420
+ }
1421
+ function toolDotClass(name) {
1422
+ const n = name.toLowerCase();
1423
+ if (n.includes("edit") || n.includes("write") || n.includes("patch")) return "warn";
1424
+ if (n.includes("bash") || n.includes("command") || n.includes("exec") || n.includes("run")) return "success";
1425
+ return "neutral";
1426
+ }
1427
+ function keyOfItem(item) {
1428
+ return `seq:${item.kind === "tool" ? item.step.seq : item.seq}`;
1429
+ }
1430
+ function targetFor(item) {
1431
+ if (item.kind === "tool") {
1432
+ return {
1433
+ seq: item.step.seq,
1434
+ kind: item.step.kind === "error" ? "error" : "tool",
1435
+ ...item.step.callId !== null ? { callId: item.step.callId } : {}
1436
+ };
1437
+ }
1438
+ return { seq: item.seq, kind: item.kind };
1439
+ }
1440
+ var TurnRow = React2.memo(function TurnRow2(props) {
1441
+ const { turn, index, expanded, active, showFull, filter, isToolFilter, lang, onToggle, onShowEarlier, onJump } = props;
1442
+ const id = turnByIdentity(turn);
1443
+ const stepMatches = (step) => {
1444
+ if (filter === "all") return true;
1445
+ if (filter === "failed") return step.kind === "error";
1446
+ return step.kind === "tool" && step.name === filter;
1447
+ };
1448
+ const items = turn.items.filter((item) => {
1449
+ if (item.kind === "tool") return stepMatches(item.step);
1450
+ if (item.kind === "input") return false;
1451
+ return !isToolFilter;
1452
+ });
1453
+ const turnTag = t(lang, "turnPrefix")(turn.turn ?? index + 1);
1454
+ const preview = turn.inputPreview;
1455
+ const hasError = turn.hasError;
1456
+ const inputItem = turn.items.find((item) => item.kind === "input");
1457
+ const dot = (tone) => /* @__PURE__ */ React2.createElement("span", { className: `ttl-rowDot ttl-dot-${tone}`, "aria-hidden": "true" });
1458
+ const name = (label, mono) => /* @__PURE__ */ React2.createElement("span", { className: `ttl-rowName${mono ? " ttl-rowNameMono" : ""}` }, label);
1459
+ const renderInputRow = (item) => /* @__PURE__ */ React2.createElement(
1460
+ "button",
1461
+ {
1462
+ type: "button",
1463
+ className: "ttl-row",
1464
+ "data-testid": "ttl-item-input",
1465
+ title: clipTitle(item.text),
1466
+ onClick: () => {
1467
+ onJump({ seq: item.seq, kind: "input" }, turn);
1468
+ }
1469
+ },
1470
+ dot("blue"),
1471
+ name(t(lang, "input"), false),
1472
+ /* @__PURE__ */ React2.createElement("span", { className: "ttl-rowText" }, item.text)
1473
+ );
1474
+ const renderStep = (step, key) => {
1475
+ const isError = step.kind === "error";
1476
+ const text = isError ? step.error : step.argsPreview;
1477
+ const tone = isError ? "red" : toolDotClass(step.name);
1478
+ return /* @__PURE__ */ React2.createElement(
1479
+ "button",
1480
+ {
1481
+ type: "button",
1482
+ className: "ttl-row",
1483
+ key,
1484
+ "data-testid": "ttl-step",
1485
+ "data-step-kind": step.kind,
1486
+ title: text,
1487
+ onClick: () => {
1488
+ onJump(targetFor({ kind: "tool", step }), turn);
1489
+ }
1490
+ },
1491
+ dot(tone),
1492
+ name(step.name, true),
1493
+ /* @__PURE__ */ React2.createElement("span", { className: "ttl-rowArgs" }, text)
1494
+ );
1495
+ };
1496
+ const renderItem = (item, key) => {
1497
+ if (item.kind === "tool") return renderStep(item.step, key);
1498
+ if (item.kind === "input") return null;
1499
+ if (item.kind === "output") {
1500
+ return /* @__PURE__ */ React2.createElement(
1501
+ "button",
1502
+ {
1503
+ type: "button",
1504
+ key,
1505
+ className: "ttl-row",
1506
+ "data-testid": "ttl-item-output",
1507
+ title: clipTitle(item.text),
1508
+ onClick: () => {
1509
+ onJump(targetFor(item), turn);
1510
+ }
1511
+ },
1512
+ dot("violet"),
1513
+ name(t(lang, "output"), false),
1514
+ /* @__PURE__ */ React2.createElement("span", { className: "ttl-rowText ttl-rowText2" }, item.text)
1515
+ );
1516
+ }
1517
+ return /* @__PURE__ */ React2.createElement(
1518
+ "button",
1519
+ {
1520
+ type: "button",
1521
+ key,
1522
+ className: "ttl-row",
1523
+ "data-testid": "ttl-item-compaction",
1524
+ title: clipTitle(item.preview),
1525
+ onClick: () => {
1526
+ onJump(targetFor(item), turn);
1527
+ }
1528
+ },
1529
+ dot("neutral"),
1530
+ name(t(lang, "compactionTag"), false),
1531
+ /* @__PURE__ */ React2.createElement("span", { className: "ttl-rowText ttl-rowText2 ttl-rowTextMuted" }, item.preview)
1532
+ );
1533
+ };
1534
+ return /* @__PURE__ */ React2.createElement(
1535
+ "section",
1536
+ {
1537
+ className: "ttl-turnRow",
1538
+ "data-testid": "ttl-turn",
1539
+ "data-collapsed": expanded ? "false" : "true",
1540
+ "data-active": active ? "true" : void 0,
1541
+ "data-error": hasError ? "true" : void 0
1542
+ },
1543
+ /* @__PURE__ */ React2.createElement("div", { className: "ttl-turnHead" }, /* @__PURE__ */ React2.createElement(
1544
+ "button",
1545
+ {
1546
+ type: "button",
1547
+ className: "ttl-turnToggle",
1548
+ "aria-expanded": expanded,
1549
+ onClick: () => {
1550
+ onToggle(id);
1551
+ }
1552
+ },
1553
+ /* @__PURE__ */ React2.createElement("span", { className: `ttl-chevron${expanded ? " ttl-chevronOpen" : ""}` }, chevronIcon()),
1554
+ /* @__PURE__ */ React2.createElement("span", { className: "ttl-turnTag", "data-testid": "ttl-turn-tag" }, turnTag),
1555
+ preview.length > 0 ? /* @__PURE__ */ React2.createElement("span", { className: "ttl-turnPreview", title: clipTitle(preview) }, preview) : null
1556
+ ), /* @__PURE__ */ React2.createElement("span", { className: "ttl-turnMeta" }, hasError ? /* @__PURE__ */ React2.createElement("span", { className: "ttl-errorDot", "data-testid": "ttl-error-dot", title: t(lang, "error") }) : null, /* @__PURE__ */ React2.createElement(
1557
+ "span",
1558
+ {
1559
+ className: "ttl-stepsNum",
1560
+ "data-testid": "ttl-steps-count",
1561
+ title: t(lang, "stepsLabel")
1562
+ },
1563
+ formatCount(turn.stepsCount)
1564
+ ), /* @__PURE__ */ React2.createElement(
1565
+ "button",
1566
+ {
1567
+ type: "button",
1568
+ className: "ttl-locateBtn",
1569
+ "data-testid": "ttl-locate",
1570
+ title: t(lang, "locate"),
1571
+ onClick: () => {
1572
+ onJump({ seq: turn.anchorSeq, kind: "input" }, turn);
1573
+ }
1574
+ },
1575
+ targetIcon()
1576
+ ))),
1577
+ expanded ? (() => {
1578
+ const truncated = items.length > STEP_CAP && !showFull;
1579
+ const shownItems = truncated ? items.slice(items.length - STEP_CAP) : items;
1580
+ const hiddenCount = items.length - shownItems.length;
1581
+ return /* @__PURE__ */ React2.createElement("div", { className: "ttl-turnBody" }, filter === "all" && inputItem !== void 0 ? renderInputRow(inputItem) : null, hiddenCount > 0 ? /* @__PURE__ */ React2.createElement(
1582
+ "button",
1583
+ {
1584
+ type: "button",
1585
+ className: "ttl-showEarlier",
1586
+ "data-testid": "ttl-show-earlier",
1587
+ onClick: () => {
1588
+ onShowEarlier(id);
1589
+ }
1590
+ },
1591
+ t(lang, "showEarlierSteps")(hiddenCount)
1592
+ ) : null, items.length === 0 ? /* @__PURE__ */ React2.createElement("div", { className: "ttl-emptyInline" }, t(lang, "emptyFiltered")) : shownItems.map((item) => renderItem(item, keyOfItem(item))));
1593
+ })() : null
1594
+ );
1595
+ });
1596
+ function TrailTab(props) {
1597
+ const lang = detectLang();
1598
+ const sessionId = props.scope?.sessionId ?? "";
1599
+ const visible = props.visible === true;
1600
+ const sessionsService = React2.useMemo(() => sessionsOf(props.ctx), [props.ctx]);
1601
+ const fetchEvents = React2.useMemo(
1602
+ () => props.fetchEventsOverride !== void 0 ? props.fetchEventsOverride : (sid, afterSeq) => fetchTrailEvents(sid, afterSeq),
1603
+ [props.fetchEventsOverride]
1604
+ );
1605
+ const { turns, stats, status, error, refresh } = useTrail(sessionId, visible, fetchEvents, sessionsService);
1606
+ const [filter, setFilter] = React2.useState("all");
1607
+ const [expandedTurns, setExpandedTurns] = React2.useState(null);
1608
+ const [jumping, setJumping] = React2.useState(null);
1609
+ const [jumpPages, setJumpPages] = React2.useState(0);
1610
+ const [jumpMessage, setJumpMessage] = React2.useState(null);
1611
+ const [activeTurnId, setActiveTurnId] = React2.useState(null);
1612
+ const [followLatest, setFollowLatest] = React2.useState(true);
1613
+ const [followPaused, setFollowPaused] = React2.useState(false);
1614
+ const [moreToolsOpen, setMoreToolsOpen] = React2.useState(false);
1615
+ const [showFullTurn, setShowFullTurn] = React2.useState(/* @__PURE__ */ new Set());
1616
+ const listRef = React2.useRef(null);
1617
+ const jumpingRef = React2.useRef(false);
1618
+ const onScroll = React2.useCallback(() => {
1619
+ const el = listRef.current;
1620
+ if (el === null) return;
1621
+ const nearBottom = el.scrollHeight - el.scrollTop - el.clientHeight < 32;
1622
+ setFollowPaused(!nearBottom);
1623
+ }, []);
1624
+ React2.useEffect(() => {
1625
+ if (!followLatest || followPaused) return void 0;
1626
+ const el = listRef.current;
1627
+ if (el === null) return void 0;
1628
+ if (typeof window.requestAnimationFrame === "function") {
1629
+ const raf = window.requestAnimationFrame(() => {
1630
+ el.scrollTop = el.scrollHeight;
1631
+ });
1632
+ return () => window.cancelAnimationFrame(raf);
1633
+ }
1634
+ const timeout = window.setTimeout(() => {
1635
+ el.scrollTop = el.scrollHeight;
1636
+ }, 0);
1637
+ return () => window.clearTimeout(timeout);
1638
+ }, [turns, followLatest, followPaused]);
1639
+ const scrollToBottom = React2.useCallback(() => {
1640
+ const el = listRef.current;
1641
+ if (el === null) return;
1642
+ el.scrollTop = el.scrollHeight;
1643
+ setFollowPaused(false);
1644
+ }, []);
1645
+ const latestIdRef = React2.useRef("");
1646
+ React2.useEffect(() => {
1647
+ latestIdRef.current = turns.length > 0 ? turnByIdentity(turns[turns.length - 1]) : "";
1648
+ }, [turns]);
1649
+ const isTurnExpanded = React2.useCallback((id, index) => {
1650
+ if (expandedTurns === null) return index === turns.length - 1;
1651
+ return expandedTurns.has(id);
1652
+ }, [expandedTurns, turns.length]);
1653
+ const toggleTurn = React2.useCallback((id) => {
1654
+ setExpandedTurns((current) => {
1655
+ if (current === null) {
1656
+ const latestId = latestIdRef.current;
1657
+ const next2 = /* @__PURE__ */ new Set();
1658
+ if (latestId.length > 0) next2.add(latestId);
1659
+ if (next2.has(id)) next2.delete(id);
1660
+ else next2.add(id);
1661
+ return next2;
1662
+ }
1663
+ const next = new Set(current);
1664
+ if (next.has(id)) next.delete(id);
1665
+ else next.add(id);
1666
+ return next;
1667
+ });
1668
+ }, []);
1669
+ const toggleShowFull = React2.useCallback((id) => {
1670
+ setShowFullTurn((current) => {
1671
+ const next = new Set(current);
1672
+ if (next.has(id)) next.delete(id);
1673
+ else next.add(id);
1674
+ return next;
1675
+ });
1676
+ }, []);
1677
+ const sessionForJump = React2.useMemo(() => {
1678
+ if (sessionsService === void 0) return null;
1679
+ const binding = sessionsService.binding(sessionId);
1680
+ const session = binding !== void 0 && binding.session !== void 0 ? binding.session : void 0;
1681
+ if (session === void 0) return null;
1682
+ const face = session;
1683
+ if (typeof face.loadOlder !== "function" || typeof face.getSnapshot !== "function") return null;
1684
+ return session;
1685
+ }, [sessionsService, sessionId]);
1686
+ const doJump = React2.useCallback(async (target, turn) => {
1687
+ if (jumpingRef.current) return;
1688
+ setActiveTurnId(turnByIdentity(turn));
1689
+ if (sessionForJump === null) {
1690
+ setJumpMessage(t(lang, "jumpFailed"));
1691
+ return;
1692
+ }
1693
+ jumpingRef.current = true;
1694
+ setJumping(target.seq);
1695
+ setJumpPages(0);
1696
+ setJumpMessage(null);
1697
+ const outcome = await jumpToItem(sessionForJump, target, {
1698
+ document: window.document,
1699
+ onProgress: (pages) => setJumpPages(pages)
1700
+ });
1701
+ jumpingRef.current = false;
1702
+ setJumping(null);
1703
+ if (outcome.ok) {
1704
+ setJumpMessage(t(lang, outcome.mode === "trajectory" ? "jumpTrajectory" : "jumpDone"));
1705
+ } else {
1706
+ setJumpMessage(t(lang, "jumpFailed"));
1707
+ }
1708
+ }, [sessionForJump, lang]);
1709
+ const stepMatches = (step) => {
1710
+ if (filter === "all") return true;
1711
+ if (filter === "failed") return step.kind === "error";
1712
+ return step.kind === "tool" && step.name === filter;
1713
+ };
1714
+ const visibleTurns = React2.useMemo(
1715
+ () => filter === "all" || filter === "failed" ? turns : turns.filter((turn) => stepsOf(turn).some((step) => stepMatches(step))),
1716
+ // eslint-disable-next-line react-hooks/exhaustive-deps
1717
+ [turns, filter]
1718
+ );
1719
+ const toolChips = stats.tools.slice(0, 4);
1720
+ const hasMoreTools = stats.tools.length > 4;
1721
+ const isToolFilter = filter !== "all" && filter !== "failed";
1722
+ if (!visible) return null;
1723
+ const chipButton = (value, label, count, dot) => /* @__PURE__ */ React2.createElement(
1724
+ "button",
1725
+ {
1726
+ type: "button",
1727
+ key: value,
1728
+ className: "ttl-chip",
1729
+ "aria-pressed": filter === value ? "true" : "false",
1730
+ "data-testid": `ttl-chip-${value}`,
1731
+ onClick: () => {
1732
+ setFilter((current) => current === value ? "all" : value);
1733
+ }
1734
+ },
1735
+ dot ? /* @__PURE__ */ React2.createElement("span", { className: `ttl-chipDot ttl-dot-${dot}`, "aria-hidden": "true" }) : null,
1736
+ /* @__PURE__ */ React2.createElement("span", null, label),
1737
+ count > 0 ? /* @__PURE__ */ React2.createElement("span", { className: "ttl-chipNum" }, String(count)) : null
1738
+ );
1739
+ const header = /* @__PURE__ */ React2.createElement("div", { className: "ttl-header" }, /* @__PURE__ */ React2.createElement("h2", { className: "ttl-title" }, t(lang, "tabTitle")), /* @__PURE__ */ React2.createElement("span", { className: "ttl-count", "data-testid": "ttl-count" }, t(lang, "turnsCount")(turns.length)), /* @__PURE__ */ React2.createElement("span", { className: "ttl-headerSpacer" }), /* @__PURE__ */ React2.createElement(
1740
+ "button",
1741
+ {
1742
+ type: "button",
1743
+ className: "ttl-toggle",
1744
+ "aria-pressed": followLatest ? "true" : "false",
1745
+ "data-testid": "ttl-follow-toggle",
1746
+ onClick: () => {
1747
+ setFollowLatest((current) => !current);
1748
+ }
1749
+ },
1750
+ /* @__PURE__ */ React2.createElement("span", { className: "ttl-toggleDot", "aria-hidden": "true" }),
1751
+ t(lang, "followLatest")
1752
+ ));
1753
+ const chips = /* @__PURE__ */ React2.createElement(React2.Fragment, null, /* @__PURE__ */ React2.createElement("div", { className: "ttl-filterRow", role: "group", "data-testid": "ttl-chips" }, chipButton("all", t(lang, "filterAll"), stats.total), stats.failed > 0 ? chipButton("failed", t(lang, "filterFailed"), stats.failed) : null, toolChips.map((tool) => chipButton(tool.name, tool.name, tool.count, toolDotClass(tool.name))), hasMoreTools ? /* @__PURE__ */ React2.createElement(
1754
+ "button",
1755
+ {
1756
+ type: "button",
1757
+ key: "more",
1758
+ className: "ttl-chip ttl-moreChip",
1759
+ "aria-expanded": moreToolsOpen ? "true" : "false",
1760
+ "data-testid": "ttl-chip-more",
1761
+ onClick: () => {
1762
+ setMoreToolsOpen((current) => !current);
1763
+ }
1764
+ },
1765
+ t(lang, moreToolsOpen ? "filterCollapse" : "filterMore"),
1766
+ /* @__PURE__ */ React2.createElement("span", { className: `ttl-moreArrow${moreToolsOpen ? " ttl-moreArrowOpen" : ""}` }, chevronIcon())
1767
+ ) : null), moreToolsOpen && hasMoreTools ? /* @__PURE__ */ React2.createElement("div", { className: "ttl-toolDropdown", "data-testid": "ttl-tool-dropdown" }, stats.tools.map((tool) => chipButton(tool.name, tool.name, tool.count, toolDotClass(tool.name)))) : null);
1768
+ let body;
1769
+ if (status === "loading") {
1770
+ body = /* @__PURE__ */ React2.createElement("div", { className: "ttl-status", "data-testid": "ttl-status" }, t(lang, "loading"));
1771
+ } else if (status === "error" && turns.length === 0) {
1772
+ body = /* @__PURE__ */ React2.createElement("div", null, /* @__PURE__ */ React2.createElement("div", { className: "ttl-error", "data-testid": "ttl-error" }, error ?? t(lang, "loadFailed")), /* @__PURE__ */ React2.createElement("div", { className: "ttl-filterRow" }, /* @__PURE__ */ React2.createElement("button", { type: "button", className: "ttl-chip", onClick: refresh, "data-testid": "ttl-retry" }, t(lang, "retry"))));
1773
+ } else if (turns.length === 0) {
1774
+ body = /* @__PURE__ */ React2.createElement("div", { className: "ttl-status", "data-testid": "ttl-empty" }, t(lang, "emptyNoTurns"));
1775
+ } else if (visibleTurns.length === 0) {
1776
+ body = /* @__PURE__ */ React2.createElement("div", { className: "ttl-status", "data-testid": "ttl-empty-filtered" }, t(lang, "emptyFiltered"), /* @__PURE__ */ React2.createElement("button", { type: "button", className: "ttl-chip", style: { marginLeft: 8 }, onClick: () => {
1777
+ setFilter("all");
1778
+ } }, t(lang, "clearFilter")));
1779
+ } else {
1780
+ body = /* @__PURE__ */ React2.createElement("div", { className: "ttl-list", ref: listRef, onScroll, "data-testid": "ttl-list" }, visibleTurns.map((turn, index) => /* @__PURE__ */ React2.createElement(
1781
+ TurnRow,
1782
+ {
1783
+ key: turnByIdentity(turn),
1784
+ turn,
1785
+ index,
1786
+ expanded: isTurnExpanded(turnByIdentity(turn), index),
1787
+ active: activeTurnId === turnByIdentity(turn),
1788
+ showFull: showFullTurn.has(turnByIdentity(turn)),
1789
+ filter,
1790
+ isToolFilter,
1791
+ lang,
1792
+ onToggle: toggleTurn,
1793
+ onShowEarlier: toggleShowFull,
1794
+ onJump: doJump
1795
+ }
1796
+ )));
1797
+ }
1798
+ const statusBar = jumping !== null || jumpMessage !== null ? /* @__PURE__ */ React2.createElement("div", { className: "ttl-statusBar", "data-testid": "ttl-statusbar" }, jumping !== null ? /* @__PURE__ */ React2.createElement("span", { className: "ttl-spin" }, spinnerIcon()) : null, /* @__PURE__ */ React2.createElement("span", null, jumping !== null ? `${t(lang, "jumpProgress")(jumpPages)} ` : "", jumpMessage ?? "")) : null;
1799
+ return /* @__PURE__ */ React2.createElement("div", { className: "ttl-root", role: "complementary", "aria-label": t(lang, "tabTitle"), "data-testid": "ttl-root" }, header, chips, body, statusBar, jumping !== null ? /* @__PURE__ */ React2.createElement("div", { className: "ttl-jumpBanner", "data-testid": "ttl-jump-banner", role: "status" }, /* @__PURE__ */ React2.createElement("span", { className: "ttl-spin" }, spinnerIcon()), t(lang, "jumpProgress")(jumpPages)) : null, followPaused && turns.length > 0 ? /* @__PURE__ */ React2.createElement("button", { type: "button", className: "ttl-toBottom", "data-testid": "ttl-to-bottom", onClick: scrollToBottom }, t(lang, "newUpdates")) : null);
1800
+ }
1801
+ function chevronIcon() {
1802
+ return /* @__PURE__ */ React2.createElement("svg", { viewBox: "0 0 16 16", width: "12", height: "12", "aria-hidden": "true" }, /* @__PURE__ */ React2.createElement("path", { d: "M6 4l4 4-4 4", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round" }));
1803
+ }
1804
+ function spinnerIcon() {
1805
+ return /* @__PURE__ */ React2.createElement("svg", { viewBox: "0 0 16 16", width: "12", height: "12", "aria-hidden": "true" }, /* @__PURE__ */ React2.createElement("path", { d: "M13.5 8a5.5 5.5 0 1 1-1.6-3.9M13.5 2.5v3h-3", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round" }));
1806
+ }
1807
+ function targetIcon() {
1808
+ return /* @__PURE__ */ React2.createElement("svg", { viewBox: "0 0 16 16", width: "13", height: "13", "aria-hidden": "true" }, /* @__PURE__ */ React2.createElement("circle", { cx: "8", cy: "8", r: "5", fill: "none", stroke: "currentColor", strokeWidth: "1.5" }), /* @__PURE__ */ React2.createElement("circle", { cx: "8", cy: "8", r: "1.6", fill: "currentColor" }));
1809
+ }
1810
+
1811
+ // src/client/index.tsx
1812
+ var primitives = (() => {
1813
+ try {
1814
+ return require("@deepseek-ai/dsh-client-ui-primitives");
1815
+ } catch {
1816
+ return null;
1817
+ }
1818
+ })();
1819
+ function listIcon() {
1820
+ const path = React3.createElement("path", {
1821
+ d: "M3 3.5h10M3 8h10M3 12.5h6",
1822
+ fill: "none",
1823
+ stroke: "currentColor",
1824
+ strokeWidth: "1.5",
1825
+ strokeLinecap: "round",
1826
+ strokeLinejoin: "round"
1827
+ });
1828
+ return React3.createElement("svg", { viewBox: "0 0 16 16", width: "14", height: "14", "aria-hidden": true }, path);
1829
+ }
1830
+ function tabIcon(size) {
1831
+ const IconList = primitives !== null && typeof primitives.IconBranchOutline16 === "function" ? primitives.IconBranchOutline16 : null;
1832
+ if (IconList !== null) return React3.createElement(IconList, { size });
1833
+ return listIcon();
1834
+ }
1835
+ var inject = ["sessions", "betterSidebar"];
1836
+ function apply(ctx) {
1837
+ const style = document.createElement("style");
1838
+ style.dataset.plugin = "dsh-turn-outline";
1839
+ style.textContent = trailCss;
1840
+ document.head.appendChild(style);
1841
+ ctx.effect(() => () => style.remove());
1842
+ const betterSidebar = ctx.get("betterSidebar");
1843
+ if (betterSidebar === void 0 || betterSidebar === null || typeof betterSidebar.registerTab !== "function") {
1844
+ return;
1845
+ }
1846
+ ctx.effect(() => betterSidebar.registerTab({
1847
+ id: "turn-outline",
1848
+ title: () => typeof navigator !== "undefined" && navigator.language.toLowerCase().startsWith("zh") ? "\u8F6E\u6B21\u8F68\u8FF9" : "Turn Outline",
1849
+ icon: (size) => tabIcon(size),
1850
+ order: 46,
1851
+ single: true,
1852
+ component: (props) => React3.createElement(TrailTab, {
1853
+ ctx: props.ctx,
1854
+ scope: props.scope,
1855
+ visible: props.visible === true,
1856
+ tab: props.tab
1857
+ })
1858
+ }));
1859
+ }
1860
+
1861
+ return module.exports;
1862
+ },
1863
+ });
1864
+
1865
+ //# sourceMappingURL=client.js.map