dsh-context 0.61.0 → 0.62.1
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 +151 -21
- package/lib/index.d.ts +52 -0
- package/lib/index.js +127 -4
- package/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -83,6 +83,7 @@ window.__ModuleLoader__.load({
|
|
|
83
83
|
"timing.empty": "暂无耗时数据 · 对话开始后这里会显示时长分布",
|
|
84
84
|
"timing.tps": "{tps} tok/s",
|
|
85
85
|
"timing.tpsTip": "输出速度(TPS):累计解码输出 tokens ÷ 累计解码时长,与 DeepSeek Harness 会话统计同口径。",
|
|
86
|
+
"timing.strip": "耗时时间轴:各段按发生次序紧凑铺排,宽度为耗时占比,刻度为零、四分位与全程",
|
|
86
87
|
"tokens.title": "Token 统计",
|
|
87
88
|
"tokens.total": "总用量",
|
|
88
89
|
"tokens.output": "输出",
|
|
@@ -480,6 +481,7 @@ window.__ModuleLoader__.load({
|
|
|
480
481
|
"timing.empty": "No timing data yet — durations appear as the conversation runs",
|
|
481
482
|
"timing.tps": "{tps} tok/s",
|
|
482
483
|
"timing.tpsTip": "Output speed (TPS): summed decoded output tokens over summed decode time — the same pairing as the harness session stats.",
|
|
484
|
+
"timing.strip": "Session-time strip: slices packed in occurrence order, width as share of the active time; ticks at zero, the quartiles, and the full span",
|
|
483
485
|
"tokens.title": "Token Stats",
|
|
484
486
|
"tokens.total": "Total",
|
|
485
487
|
"tokens.output": "Output",
|
|
@@ -1376,7 +1378,8 @@ window.__ModuleLoader__.load({
|
|
|
1376
1378
|
...typeof data.surfaceFloor === "number" ? { surfaceFloor: data.surfaceFloor } : {},
|
|
1377
1379
|
...typeof data.archiveFloor === "number" ? { archiveFloor: data.archiveFloor } : {},
|
|
1378
1380
|
...data.fileOps !== void 0 ? { fileOps: objectsOf(data.fileOps) } : {},
|
|
1379
|
-
...typeof data.fileOpsFloor === "number" ? { fileOpsFloor: data.fileOpsFloor } : {}
|
|
1381
|
+
...typeof data.fileOpsFloor === "number" ? { fileOpsFloor: data.fileOpsFloor } : {},
|
|
1382
|
+
...data.spans !== void 0 ? { spans: objectsOf(data.spans) } : {}
|
|
1380
1383
|
};
|
|
1381
1384
|
}
|
|
1382
1385
|
/**
|
|
@@ -2238,7 +2241,8 @@ window.__ModuleLoader__.load({
|
|
|
2238
2241
|
...typeof data.surfaceFloor === "number" ? { surfaceFloor: data.surfaceFloor } : {},
|
|
2239
2242
|
...typeof data.archiveFloor === "number" ? { archiveFloor: data.archiveFloor } : {},
|
|
2240
2243
|
...data.fileOps !== void 0 ? { fileOps: objectsOf(data.fileOps) } : {},
|
|
2241
|
-
...typeof data.fileOpsFloor === "number" ? { fileOpsFloor: data.fileOpsFloor } : {}
|
|
2244
|
+
...typeof data.fileOpsFloor === "number" ? { fileOpsFloor: data.fileOpsFloor } : {},
|
|
2245
|
+
...data.spans !== void 0 ? { spans: objectsOf(data.spans) } : {}
|
|
2242
2246
|
};
|
|
2243
2247
|
}
|
|
2244
2248
|
/**
|
|
@@ -2446,7 +2450,8 @@ window.__ModuleLoader__.load({
|
|
|
2446
2450
|
...detail.surfaceFloor !== void 0 ? { surfaceFloor: detail.surfaceFloor } : {},
|
|
2447
2451
|
...detail.archiveFloor !== void 0 ? { archiveFloor: detail.archiveFloor } : {},
|
|
2448
2452
|
...detail.fileOps !== void 0 ? { fileOps: detail.fileOps } : {},
|
|
2449
|
-
...detail.fileOpsFloor !== void 0 ? { fileOpsFloor: detail.fileOpsFloor } : {}
|
|
2453
|
+
...detail.fileOpsFloor !== void 0 ? { fileOpsFloor: detail.fileOpsFloor } : {},
|
|
2454
|
+
...detail.spans !== void 0 ? { spans: detail.spans } : {}
|
|
2450
2455
|
},
|
|
2451
2456
|
detailState: "ready",
|
|
2452
2457
|
retryDetail: retry
|
|
@@ -5518,7 +5523,7 @@ window.__ModuleLoader__.load({
|
|
|
5518
5523
|
* widening after this many slots and late bands simply join within the cap (stackedBar.css delays by
|
|
5519
5524
|
* `--lc-i`); the composition bar's few category segments never reach it.
|
|
5520
5525
|
*/
|
|
5521
|
-
const STAGGER_CAP = 8;
|
|
5526
|
+
const STAGGER_CAP$1 = 8;
|
|
5522
5527
|
function makeStackedBar(kit) {
|
|
5523
5528
|
const { t, fmt, catLabel } = kit;
|
|
5524
5529
|
return function StackedBar(props) {
|
|
@@ -5601,7 +5606,7 @@ window.__ModuleLoader__.load({
|
|
|
5601
5606
|
style: {
|
|
5602
5607
|
width: `${widths[i]}%`,
|
|
5603
5608
|
backgroundColor: p.color,
|
|
5604
|
-
"--lc-i": Math.min(i, STAGGER_CAP)
|
|
5609
|
+
"--lc-i": Math.min(i, STAGGER_CAP$1)
|
|
5605
5610
|
},
|
|
5606
5611
|
onMouseEnter: () => {
|
|
5607
5612
|
if (props.onHoverKey !== void 0) props.onHoverKey(p.key);
|
|
@@ -7988,6 +7993,126 @@ window.__ModuleLoader__.load({
|
|
|
7988
7993
|
};
|
|
7989
7994
|
}
|
|
7990
7995
|
//#endregion
|
|
7996
|
+
//#region src/client/components/timingStrip.tsx
|
|
7997
|
+
/**
|
|
7998
|
+
* The timing card's session-time strip: the fold's painted spans (every
|
|
7999
|
+
* completed step's TTFT wait, decode blocks in stream order, tool-run
|
|
8000
|
+
* windows, and in-step residue) packed shoulder-to-shoulder in occurrence
|
|
8001
|
+
* order — the strip reads as WHERE THE TIME WENT, not when: each band's
|
|
8002
|
+
* width is its share of the session's cumulative active time (the donut's
|
|
8003
|
+
* Active Time), and idle time between steps takes no track at all. The DNA
|
|
8004
|
+
* idiom applied to time consumed. The axis runs 0 → the total active time
|
|
8005
|
+
* with quartile ticks between (0, ¼, ½, ¾, full). Hover relays the span's
|
|
8006
|
+
* KIND through the card's shared hover key, so every span of a category
|
|
8007
|
+
* lights together with its donut arc and legend row (the DNA
|
|
8008
|
+
* lifetime-highlight idiom); the floating tip answers only the strip's OWN
|
|
8009
|
+
* pointer — a mirrored legend/donut hover lights the matching bands without
|
|
8010
|
+
* floating a second tip over a surface the pointer is not on (the stacked
|
|
8011
|
+
* bar's mirror rule).
|
|
8012
|
+
*/
|
|
8013
|
+
/** The strip's palette: the timing card's own slice colors, keyed by span kind (statsTiming.tsx shares it). */
|
|
8014
|
+
const TIMING_COLOR = {
|
|
8015
|
+
ttft: "var(--color-blue-500)",
|
|
8016
|
+
reasoning: "var(--color-violet-500)",
|
|
8017
|
+
text: "var(--color-pink-500)",
|
|
8018
|
+
toolarg: "var(--color-amber-500)",
|
|
8019
|
+
tools: "var(--color-teal-500)",
|
|
8020
|
+
other: "var(--color-slate-400)"
|
|
8021
|
+
};
|
|
8022
|
+
/** The legend-label key of each span kind (the timing card's own vocabulary). */
|
|
8023
|
+
const KIND_LABEL = {
|
|
8024
|
+
ttft: "timing.ttft",
|
|
8025
|
+
reasoning: "timing.reasoning",
|
|
8026
|
+
text: "timing.text",
|
|
8027
|
+
toolarg: "timing.toolArgs",
|
|
8028
|
+
tools: "timing.tools",
|
|
8029
|
+
other: "timing.other"
|
|
8030
|
+
};
|
|
8031
|
+
/** Entrance stagger cap (the stacked bar's own rule): late bands join within the cap so a long span list settles fast. */
|
|
8032
|
+
const STAGGER_CAP = 8;
|
|
8033
|
+
/** The axis's fixed tick fractions: zero, the three quartiles, and the full span. */
|
|
8034
|
+
const TICK_FRACS = [
|
|
8035
|
+
0,
|
|
8036
|
+
.25,
|
|
8037
|
+
.5,
|
|
8038
|
+
.75,
|
|
8039
|
+
1
|
|
8040
|
+
];
|
|
8041
|
+
function makeTimingStrip(kit) {
|
|
8042
|
+
const { t, fmtDuration, fmtTime } = kit;
|
|
8043
|
+
return function TimingStrip(props) {
|
|
8044
|
+
const [tipKey, setTipKey] = (0, react.useState)(null);
|
|
8045
|
+
const valid = props.spans.filter((s) => Object.hasOwn(TIMING_COLOR, s.kind) && Number.isFinite(s.start) && Number.isFinite(s.end) && s.end > s.start);
|
|
8046
|
+
if (valid.length === 0) return null;
|
|
8047
|
+
let total = 0;
|
|
8048
|
+
for (const s of valid) total += s.end - s.start;
|
|
8049
|
+
let cursor = 0;
|
|
8050
|
+
const bands = valid.map((s, i) => {
|
|
8051
|
+
const leftPct = cursor / total * 100;
|
|
8052
|
+
cursor += s.end - s.start;
|
|
8053
|
+
return {
|
|
8054
|
+
key: i,
|
|
8055
|
+
kind: s.kind,
|
|
8056
|
+
leftPct,
|
|
8057
|
+
widthPct: (s.end - s.start) / total * 100,
|
|
8058
|
+
tip: `${t(KIND_LABEL[s.kind])} ${fmtDuration(s.end - s.start)} · ${fmtTime(s.start)}`
|
|
8059
|
+
};
|
|
8060
|
+
});
|
|
8061
|
+
const hoverKey = props.hoverKey;
|
|
8062
|
+
const hovering = hoverKey !== null && hoverKey !== void 0 && bands.some((b) => b.kind === hoverKey);
|
|
8063
|
+
let tip = null;
|
|
8064
|
+
for (const b of bands) if (b.key === tipKey) {
|
|
8065
|
+
tip = {
|
|
8066
|
+
text: b.tip,
|
|
8067
|
+
leftPct: Math.max(10, Math.min(b.leftPct + b.widthPct / 2, 90))
|
|
8068
|
+
};
|
|
8069
|
+
break;
|
|
8070
|
+
}
|
|
8071
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
8072
|
+
className: "lc-tstrip",
|
|
8073
|
+
role: "img",
|
|
8074
|
+
"aria-label": t("timing.strip"),
|
|
8075
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
8076
|
+
className: "lc-tstrip-wrap",
|
|
8077
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
8078
|
+
className: "lc-tstrip-bar" + (hovering ? " lc-tstrip-dim" : ""),
|
|
8079
|
+
onMouseLeave: () => {
|
|
8080
|
+
setTipKey(null);
|
|
8081
|
+
props.onHoverKey?.(null);
|
|
8082
|
+
},
|
|
8083
|
+
children: bands.map((b, i) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
8084
|
+
className: "lc-tstrip-seg animate-lc-stacked-in motion-reduce:animate-none" + (hoverKey === b.kind ? " lc-tstrip-seg-on" : ""),
|
|
8085
|
+
style: {
|
|
8086
|
+
left: `${b.leftPct}%`,
|
|
8087
|
+
width: `${b.widthPct}%`,
|
|
8088
|
+
background: TIMING_COLOR[b.kind],
|
|
8089
|
+
"--lc-i": Math.min(i, STAGGER_CAP)
|
|
8090
|
+
},
|
|
8091
|
+
onMouseEnter: () => {
|
|
8092
|
+
setTipKey(b.key);
|
|
8093
|
+
props.onHoverKey?.(b.kind);
|
|
8094
|
+
}
|
|
8095
|
+
}, b.key))
|
|
8096
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
8097
|
+
className: "lc-tip lc-bar-tip" + (tip !== null ? " lc-bar-tip-on" : ""),
|
|
8098
|
+
style: { left: `${tip !== null ? tip.leftPct : 50}%` },
|
|
8099
|
+
children: tip !== null ? tip.text : ""
|
|
8100
|
+
})]
|
|
8101
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
8102
|
+
className: "lc-tstrip-axis",
|
|
8103
|
+
children: TICK_FRACS.map((frac, i) => {
|
|
8104
|
+
const ms = total * frac;
|
|
8105
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
8106
|
+
className: "lc-tstrip-tick" + (i === 0 ? " lc-tstrip-tick-first" : i === TICK_FRACS.length - 1 ? " lc-tstrip-tick-last" : ""),
|
|
8107
|
+
style: { left: `${frac * 100}%` },
|
|
8108
|
+
children: ms === 0 ? "0" : fmtDuration(ms)
|
|
8109
|
+
}, frac);
|
|
8110
|
+
})
|
|
8111
|
+
})]
|
|
8112
|
+
});
|
|
8113
|
+
};
|
|
8114
|
+
}
|
|
8115
|
+
//#endregion
|
|
7991
8116
|
//#region src/client/components/statsTiming.tsx
|
|
7992
8117
|
/**
|
|
7993
8118
|
* The Timing card: where the session's ACTIVE time went. The donut splits
|
|
@@ -8006,16 +8131,13 @@ window.__ModuleLoader__.load({
|
|
|
8006
8131
|
* read as "no time, many calls". The donut and the rows sit side by side so
|
|
8007
8132
|
* the head row stays half-height. Parallel tool calls each count, so the tools
|
|
8008
8133
|
* figure can overlap — the ring clamps it into the post-model window while the
|
|
8009
|
-
* row numbers stay true.
|
|
8134
|
+
* row numbers stay true. Below them the session-time strip packs every
|
|
8135
|
+
* completed step's slices gapless in occurrence order (the DNA idiom applied
|
|
8136
|
+
* to time consumed — each band's width is its share of the cumulative active
|
|
8137
|
+
* time, idle time takes no track), joining the same hover link by KIND.
|
|
8010
8138
|
*/
|
|
8011
|
-
|
|
8012
|
-
|
|
8013
|
-
reasoning: "var(--color-violet-500)",
|
|
8014
|
-
text: "var(--color-pink-500)",
|
|
8015
|
-
toolarg: "var(--color-amber-500)",
|
|
8016
|
-
tools: "var(--color-teal-500)",
|
|
8017
|
-
other: "var(--color-slate-400)"
|
|
8018
|
-
};
|
|
8139
|
+
/** The card's slice palette (timingStrip.tsx owns it — the strip paints the same kinds). */
|
|
8140
|
+
const COLOR = TIMING_COLOR;
|
|
8019
8141
|
/**
|
|
8020
8142
|
* The decode-throughput figure, formatted exactly as the harness's own
|
|
8021
8143
|
* session-stats seat formats it: whole tokens from ten up, one decimal below.
|
|
@@ -8027,6 +8149,7 @@ window.__ModuleLoader__.load({
|
|
|
8027
8149
|
function makeStatsTiming(kit, Donut) {
|
|
8028
8150
|
const { t, fmt, fmtDuration, fmtShare } = kit;
|
|
8029
8151
|
const SliceList = makeSliceList(kit);
|
|
8152
|
+
const TimingStrip = makeTimingStrip(kit);
|
|
8030
8153
|
return function StatsTiming(props) {
|
|
8031
8154
|
const [hoverKey, setHoverKey] = (0, react.useState)(null);
|
|
8032
8155
|
const timing = props.timing;
|
|
@@ -8158,7 +8281,7 @@ window.__ModuleLoader__.load({
|
|
|
8158
8281
|
}), rows.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
8159
8282
|
className: "lc-empty",
|
|
8160
8283
|
children: t("timing.empty")
|
|
8161
|
-
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
8284
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
8162
8285
|
className: "lc-donut-row flex items-center justify-start gap-3 min-w-0 @max-[320px]/lc-card:gap-2 @max-[240px]/lc-card:flex-wrap",
|
|
8163
8286
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Donut, {
|
|
8164
8287
|
segments,
|
|
@@ -8172,7 +8295,11 @@ window.__ModuleLoader__.load({
|
|
|
8172
8295
|
hoverKey,
|
|
8173
8296
|
onHoverKey: setHoverKey
|
|
8174
8297
|
})]
|
|
8175
|
-
})
|
|
8298
|
+
}), props.spans !== void 0 && props.spans.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TimingStrip, {
|
|
8299
|
+
spans: props.spans,
|
|
8300
|
+
hoverKey,
|
|
8301
|
+
onHoverKey: setHoverKey
|
|
8302
|
+
}) : null] })]
|
|
8176
8303
|
});
|
|
8177
8304
|
};
|
|
8178
8305
|
}
|
|
@@ -11277,7 +11404,7 @@ window.__ModuleLoader__.load({
|
|
|
11277
11404
|
return function PluginInfo() {
|
|
11278
11405
|
const [latest, setLatest] = (0, react.useState)(null);
|
|
11279
11406
|
(0, react.useEffect)(() => {
|
|
11280
|
-
if ("0.
|
|
11407
|
+
if ("0.62.1".includes("-dev")) return;
|
|
11281
11408
|
let on = true;
|
|
11282
11409
|
fetchLatestVersion().then((v) => {
|
|
11283
11410
|
if (on && v) setLatest(v);
|
|
@@ -11286,8 +11413,8 @@ window.__ModuleLoader__.load({
|
|
|
11286
11413
|
on = false;
|
|
11287
11414
|
};
|
|
11288
11415
|
}, []);
|
|
11289
|
-
const update = latest !== null && isNewerVersion(latest, "0.
|
|
11290
|
-
const nameText = "dsh-context (v0.
|
|
11416
|
+
const update = latest !== null && isNewerVersion(latest, "0.62.1") ? latest : null;
|
|
11417
|
+
const nameText = "dsh-context (v0.62.1)";
|
|
11291
11418
|
const nameValue = [nameText];
|
|
11292
11419
|
if (update) nameValue.push(/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
11293
11420
|
className: "lc-pi-update",
|
|
@@ -12643,7 +12770,10 @@ window.__ModuleLoader__.load({
|
|
|
12643
12770
|
usage,
|
|
12644
12771
|
current: data.current,
|
|
12645
12772
|
breakdown
|
|
12646
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(StatsTiming, {
|
|
12773
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(StatsTiming, {
|
|
12774
|
+
timing: data.timing ?? null,
|
|
12775
|
+
spans: data.spans
|
|
12776
|
+
})]
|
|
12647
12777
|
}),
|
|
12648
12778
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
12649
12779
|
className: "lc-cols lc-cols-main",
|
|
@@ -12886,7 +13016,7 @@ window.__ModuleLoader__.load({
|
|
|
12886
13016
|
}
|
|
12887
13017
|
//#endregion
|
|
12888
13018
|
//#region \0dsh-global-css:/home/runner/work/dsh-context/dsh-context/src/client/styles/stats.css.mjs
|
|
12889
|
-
const css$13 = ".lc-stat{background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l2);border-radius:8px;flex-direction:column;gap:2px;min-width:0;padding:6px;display:flex}a.lc-stat{color:inherit;cursor:pointer;text-decoration:none}a.lc-stat:hover{border-color:var(--dsw-alias-label-secondary)}.lc-stats .lc-stat{padding:6px 3px}.lc-stats .lc-stat-q{margin:0;position:absolute;top:3px;right:4px}.lc-stat-label{color:var(--dsw-alias-label-secondary);white-space:nowrap;text-overflow:ellipsis;font-size:11px;font-weight:600;overflow:hidden}.lc-stat-value{color:var(--dsw-alias-label-primary);white-space:nowrap;text-overflow:ellipsis;text-align:center;font-size:14px;font-weight:600;overflow:hidden}.lc-stat-sub{color:var(--dsw-alias-label-secondary);white-space:nowrap;text-overflow:ellipsis;font-size:11px;overflow:hidden}.lc-stat-tipped{position:relative}.lc-stat-q{text-align:center;width:11px;height:11px;color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-bg-layer-1);border:1px solid var(--dsw-alias-border-l2);cursor:help;transition:color var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out), border-color var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out);border-radius:50%;margin-left:4px;font-size:9px;font-style:normal;font-weight:700;line-height:11px;display:inline-block}.lc-stat-tip{max-width:280px;font-weight:400;line-height:1.5;top:calc(100% + 6px);left:0}.lc-stat-tip-prices{border-top:1px dashed var(--dsw-alias-border-l1);margin-top:5px;padding-top:5px;display:block}.lc-stat-tip-head{font-weight:600;display:block}.lc-timing-tps{background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l2);font-variant-numeric:tabular-nums;cursor:default;border-radius:999px;flex:none;margin-left:auto;padding:1px 8px;font-size:12px;line-height:18px}.lc-stat-tip-row{margin-top:2px;display:block}.lc-stat-tip-band{justify-content:space-between;gap:2rem;display:flex}.lc-stat-tip-band i{font-style:normal}.lc-stat-tip-by{color:var(--dsw-alias-label-secondary);margin-top:3px;display:block}.lc-card-sub{color:var(--dsw-alias-label-secondary);margin-left:auto;font-size:12px;font-weight:400}.lc-overview-num{align-items:baseline;gap:6px;margin-bottom:8px;display:flex}.lc-overview-num>b{font-size:20px}.lc-overview-num span{color:var(--dsw-alias-label-secondary)}.lc-overview-pct{margin-left:auto;font-size:11px}.lc-overview-pct b{color:var(--dsw-alias-label-primary);margin-right:4px;font-size:20px}.lc-stat-head{color:var(--dsw-alias-label-secondary);margin:12px 0 8px;font-size:11px;font-weight:600}.lc-col-donut{flex-direction:column;display:flex}.lc-col-donut .lc-donut-row{flex:1}.lc-donut{flex:none;position:relative}.lc-donut svg{display:block}.lc-donut-track{stroke:color-mix(in srgb, var(--color-neutral-500) 18%, transparent)}.lc-donut-seg{transition:opacity var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out)}.lc-donut-dim .lc-donut-seg{opacity:.35}.lc-donut-dim .lc-donut-seg-on{opacity:1}.lc-donut-center{pointer-events:none;text-align:center;box-sizing:border-box;flex-direction:column;justify-content:center;align-items:center;gap:1px;padding:0 7px;display:flex;position:absolute;inset:0}.lc-donut-center b{white-space:nowrap;text-overflow:ellipsis;max-width:100%;line-height:1.15;overflow:hidden}.lc-donut-center span{color:var(--dsw-alias-label-secondary);white-space:nowrap}.lc-sl{grid-template-columns:repeat(auto-fill,minmax(160px,1fr));gap:4px 20px;min-width:0;display:grid;overflow:hidden}.lc-sl-row{min-width:0;transition:background-color var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out);border-radius:6px;flex-direction:column;gap:1px;margin:0 -6px;padding:1px 6px;font-size:12px;display:flex}.lc-sl-row-on{background:var(--dsw-alias-interactive-bg-hover)}.lc-sl-row-dim{opacity:.55}.lc-sl-main{align-items:center;gap:6px;min-width:0;display:flex}.lc-sl-dot{border-radius:2px;flex:none;width:8px;height:8px}.lc-sl-label{white-space:nowrap;text-overflow:ellipsis;flex:auto;min-width:0;font-weight:600;line-height:1.25;overflow:hidden}.lc-sl-pct{text-align:right;font-variant-numeric:tabular-nums;flex:none;min-width:34px;font-weight:700}.lc-sl-sub{color:var(--dsw-alias-label-secondary);font-variant-numeric:tabular-nums;white-space:nowrap;text-overflow:ellipsis;margin-left:14px;font-size:11px;line-height:1.3;overflow:hidden}.lc-pi-grid{grid-template-columns:1fr;gap:8px;display:grid}.lc-pi-row-btn{appearance:none;font:inherit;color:inherit;text-align:left;cursor:pointer;background:0 0;border:0;width:100%;padding:0}.lc-pi-row{min-width:0;color:inherit;flex-direction:row;justify-content:space-between;align-items:baseline;gap:12px;text-decoration:none;display:flex}.lc-pi-hint{text-decoration:none}.lc-pi-update{color:var(--dsw-alias-state-warn-primary);white-space:nowrap;margin-left:6px;font-size:11px}.lc-pi-label{color:var(--dsw-alias-label-secondary);white-space:nowrap;text-overflow:ellipsis;flex:none;font-size:11px;font-weight:600;overflow:hidden}.lc-pi-labelicon{vertical-align:-2px;margin-right:4px;display:inline-block}.lc-pi-value{min-width:0;color:var(--dsw-alias-label-primary);white-space:nowrap;text-overflow:ellipsis;text-align:right;font-size:13px;font-weight:600;overflow:hidden}";
|
|
13019
|
+
const css$13 = ".lc-stat{background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l2);border-radius:8px;flex-direction:column;gap:2px;min-width:0;padding:6px;display:flex}a.lc-stat{color:inherit;cursor:pointer;text-decoration:none}a.lc-stat:hover{border-color:var(--dsw-alias-label-secondary)}.lc-stats .lc-stat{padding:6px 3px}.lc-stats .lc-stat-q{margin:0;position:absolute;top:3px;right:4px}.lc-stat-label{color:var(--dsw-alias-label-secondary);white-space:nowrap;text-overflow:ellipsis;font-size:11px;font-weight:600;overflow:hidden}.lc-stat-value{color:var(--dsw-alias-label-primary);white-space:nowrap;text-overflow:ellipsis;text-align:center;font-size:14px;font-weight:600;overflow:hidden}.lc-stat-sub{color:var(--dsw-alias-label-secondary);white-space:nowrap;text-overflow:ellipsis;font-size:11px;overflow:hidden}.lc-stat-tipped{position:relative}.lc-stat-q{text-align:center;width:11px;height:11px;color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-bg-layer-1);border:1px solid var(--dsw-alias-border-l2);cursor:help;transition:color var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out), border-color var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out);border-radius:50%;margin-left:4px;font-size:9px;font-style:normal;font-weight:700;line-height:11px;display:inline-block}.lc-stat-tip{max-width:280px;font-weight:400;line-height:1.5;top:calc(100% + 6px);left:0}.lc-stat-tip-prices{border-top:1px dashed var(--dsw-alias-border-l1);margin-top:5px;padding-top:5px;display:block}.lc-stat-tip-head{font-weight:600;display:block}.lc-timing-tps{background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l2);font-variant-numeric:tabular-nums;cursor:default;border-radius:999px;flex:none;margin-left:auto;padding:1px 8px;font-size:12px;line-height:18px}.lc-stat-tip-row{margin-top:2px;display:block}.lc-stat-tip-band{justify-content:space-between;gap:2rem;display:flex}.lc-stat-tip-band i{font-style:normal}.lc-stat-tip-by{color:var(--dsw-alias-label-secondary);margin-top:3px;display:block}.lc-card-sub{color:var(--dsw-alias-label-secondary);margin-left:auto;font-size:12px;font-weight:400}.lc-overview-num{align-items:baseline;gap:6px;margin-bottom:8px;display:flex}.lc-overview-num>b{font-size:20px}.lc-overview-num span{color:var(--dsw-alias-label-secondary)}.lc-overview-pct{margin-left:auto;font-size:11px}.lc-overview-pct b{color:var(--dsw-alias-label-primary);margin-right:4px;font-size:20px}.lc-stat-head{color:var(--dsw-alias-label-secondary);margin:12px 0 8px;font-size:11px;font-weight:600}.lc-col-donut{flex-direction:column;display:flex}.lc-col-donut .lc-donut-row{flex:1}.lc-donut{flex:none;position:relative}.lc-donut svg{display:block}.lc-donut-track{stroke:color-mix(in srgb, var(--color-neutral-500) 18%, transparent)}.lc-donut-seg{transition:opacity var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out)}.lc-donut-dim .lc-donut-seg{opacity:.35}.lc-donut-dim .lc-donut-seg-on{opacity:1}.lc-donut-center{pointer-events:none;text-align:center;box-sizing:border-box;flex-direction:column;justify-content:center;align-items:center;gap:1px;padding:0 7px;display:flex;position:absolute;inset:0}.lc-donut-center b{white-space:nowrap;text-overflow:ellipsis;max-width:100%;line-height:1.15;overflow:hidden}.lc-donut-center span{color:var(--dsw-alias-label-secondary);white-space:nowrap}.lc-sl{grid-template-columns:repeat(auto-fill,minmax(160px,1fr));gap:4px 20px;min-width:0;display:grid;overflow:hidden}.lc-sl-row{min-width:0;transition:background-color var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out);border-radius:6px;flex-direction:column;gap:1px;margin:0 -6px;padding:1px 6px;font-size:12px;display:flex}.lc-sl-row-on{background:var(--dsw-alias-interactive-bg-hover)}.lc-sl-row-dim{opacity:.55}.lc-sl-main{align-items:center;gap:6px;min-width:0;display:flex}.lc-sl-dot{border-radius:2px;flex:none;width:8px;height:8px}.lc-sl-label{white-space:nowrap;text-overflow:ellipsis;flex:auto;min-width:0;font-weight:600;line-height:1.25;overflow:hidden}.lc-sl-pct{text-align:right;font-variant-numeric:tabular-nums;flex:none;min-width:34px;font-weight:700}.lc-sl-sub{color:var(--dsw-alias-label-secondary);font-variant-numeric:tabular-nums;white-space:nowrap;text-overflow:ellipsis;margin-left:14px;font-size:11px;line-height:1.3;overflow:hidden}.lc-pi-grid{grid-template-columns:1fr;gap:8px;display:grid}.lc-pi-row-btn{appearance:none;font:inherit;color:inherit;text-align:left;cursor:pointer;background:0 0;border:0;width:100%;padding:0}.lc-pi-row{min-width:0;color:inherit;flex-direction:row;justify-content:space-between;align-items:baseline;gap:12px;text-decoration:none;display:flex}.lc-pi-hint{text-decoration:none}.lc-pi-update{color:var(--dsw-alias-state-warn-primary);white-space:nowrap;margin-left:6px;font-size:11px}.lc-pi-label{color:var(--dsw-alias-label-secondary);white-space:nowrap;text-overflow:ellipsis;flex:none;font-size:11px;font-weight:600;overflow:hidden}.lc-pi-labelicon{vertical-align:-2px;margin-right:4px;display:inline-block}.lc-pi-value{min-width:0;color:var(--dsw-alias-label-primary);white-space:nowrap;text-overflow:ellipsis;text-align:right;font-size:13px;font-weight:600;overflow:hidden}.lc-tstrip{margin-top:10px}.lc-tstrip-wrap{position:relative}.lc-tstrip-bar{background:color-mix(in srgb, var(--color-neutral-500) 18%, transparent);border-radius:4px;width:100%;height:12px;position:relative;overflow:hidden}.lc-tstrip-seg{transform-origin:0;height:100%;transition:opacity var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out), filter var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out);position:absolute;top:0}.lc-tstrip-seg-on{filter:brightness(1.18)}.lc-tstrip-dim .lc-tstrip-seg{opacity:.35}.lc-tstrip-dim .lc-tstrip-seg-on{opacity:1}.lc-tstrip-axis{height:13px;color:var(--dsw-alias-label-secondary);font-variant-numeric:tabular-nums;margin-top:5px;font-size:10px;line-height:13px;position:relative}.lc-tstrip-tick{white-space:nowrap;position:absolute;transform:translate(-50%)}.lc-tstrip-tick:before{content:\"\";background:var(--dsw-alias-border-l1);width:1px;height:3px;position:absolute;top:-4px;left:50%}.lc-tstrip-tick-first{transform:none}.lc-tstrip-tick-first:before{left:0}.lc-tstrip-tick-last{transform:translate(-100%)}.lc-tstrip-tick-last:before{left:auto;right:0}";
|
|
12890
13020
|
const tagId$13 = "dsh-context/stats.css";
|
|
12891
13021
|
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$13) + "]") === null) {
|
|
12892
13022
|
const tag = document.createElement("style");
|
package/lib/index.d.ts
CHANGED
|
@@ -157,6 +157,28 @@ interface TimelineState {
|
|
|
157
157
|
time: number;
|
|
158
158
|
firstToken?: number;
|
|
159
159
|
};
|
|
160
|
+
/**
|
|
161
|
+
* The timing strip's painted spans (shared/types.ts TimingSpan): every
|
|
162
|
+
* completed step's time slices in log order, stamped with their REAL
|
|
163
|
+
* instants — the TTFT wait, the decode blocks in stream order, the tool-run
|
|
164
|
+
* windows, and the in-step residue. The client packs them gapless, each
|
|
165
|
+
* band's width its share of the cumulative active time (idle time BETWEEN
|
|
166
|
+
* steps carries no span and takes no track). Bounded by TIMING_SPANS_MAX
|
|
167
|
+
* (newest tail kept). Flushed per step at `step/end` from the `stepSpans`
|
|
168
|
+
* accumulator.
|
|
169
|
+
*/
|
|
170
|
+
spans: TimingSpan[];
|
|
171
|
+
/**
|
|
172
|
+
* The open step's accumulated painted spans: the assistant message pushes
|
|
173
|
+
* the model slices (TTFT + the decode blocks), each paired tool result its
|
|
174
|
+
* run window; `step/end` clamps them into the step window, de-overlaps
|
|
175
|
+
* first-wins (parallel runs paint their union), fills the holes with the
|
|
176
|
+
* residue kind, and flushes the tiling into `spans`. Absent until the first
|
|
177
|
+
* in-step span; DELETED at the flush (the plain-JSON precondition). Same
|
|
178
|
+
* arm/remove lifecycle as `stepStart` — never written in place, always
|
|
179
|
+
* replaced, so no CloneKey covers it.
|
|
180
|
+
*/
|
|
181
|
+
stepSpans?: TimingSpan[];
|
|
160
182
|
/**
|
|
161
183
|
* Tool callId → the call's name, start instant, and raw arguments, armed by
|
|
162
184
|
* `tool/call` and DELETED when its `tool/result` folds in (one result per
|
|
@@ -469,6 +491,13 @@ interface Snapshot {
|
|
|
469
491
|
*/
|
|
470
492
|
fileOps?: FileOpRecord[];
|
|
471
493
|
fileOpsFloor?: number;
|
|
494
|
+
/**
|
|
495
|
+
* The timing strip's painted spans (see TimingSpan) — present on the INLINE
|
|
496
|
+
* wire value and on the detail payload, absent from the slim head (they ride
|
|
497
|
+
* the detail channel there) and from rows folded before the collection
|
|
498
|
+
* existed (older hosts; the client then shows no strip).
|
|
499
|
+
*/
|
|
500
|
+
spans?: TimingSpan[];
|
|
472
501
|
}
|
|
473
502
|
/**
|
|
474
503
|
* The on-demand DETAIL payload of the split `contextTimeline` generation —
|
|
@@ -509,6 +538,13 @@ interface ContextTimelineDetail {
|
|
|
509
538
|
fileOps?: FileOpRecord[];
|
|
510
539
|
/** The newest dropped op's seq when the op log trimmed (coverage honesty, same family as archiveFloor). */
|
|
511
540
|
fileOpsFloor?: number;
|
|
541
|
+
/**
|
|
542
|
+
* The timing strip's painted spans (see TimingSpan): the completed steps'
|
|
543
|
+
* time slices in log order, positioned by their real instants. Additive-
|
|
544
|
+
* optional: a payload folded before the collection existed (older hosts)
|
|
545
|
+
* serves without it, and the timing card shows no strip then.
|
|
546
|
+
*/
|
|
547
|
+
spans?: TimingSpan[];
|
|
512
548
|
}
|
|
513
549
|
/**
|
|
514
550
|
* One executed file operation (a settled file-tool call with a resolved
|
|
@@ -559,6 +595,22 @@ interface FileOpRecord {
|
|
|
559
595
|
* delivered projection is by definition available); kept for wire compatibility with the snapshot shape.
|
|
560
596
|
*/
|
|
561
597
|
type ContextTimeline = Snapshot;
|
|
598
|
+
/**
|
|
599
|
+
* One painted span of the timing card's session-time strip (the card's bottom
|
|
600
|
+
* band): a completed step's time slice — the TTFT wait, one decode block (in
|
|
601
|
+
* stream order), one tool-run window, or an in-step residue gap — folded
|
|
602
|
+
* host-side at `step/end`. The instants are the slice's REAL occurrence times
|
|
603
|
+
* (the hover tip reads them); the strip packs the spans gapless in log order,
|
|
604
|
+
* each band's width its share of the session's cumulative active time (idle
|
|
605
|
+
* time BETWEEN steps carries no span and takes no track). `end` > `start`
|
|
606
|
+
* always (the fold drops zero/negative slices).
|
|
607
|
+
*/
|
|
608
|
+
interface TimingSpan {
|
|
609
|
+
/** The slice kind — the timing card's own slice vocabulary (same keys, same colors). */
|
|
610
|
+
kind: 'ttft' | 'reasoning' | 'text' | 'toolarg' | 'tools' | 'other';
|
|
611
|
+
start: number;
|
|
612
|
+
end: number;
|
|
613
|
+
}
|
|
562
614
|
/**
|
|
563
615
|
* Cumulative billed-token totals for one pricing bucket of the session-cost
|
|
564
616
|
* estimate (host-folded, never trimmed — running totals over the COMPLETE
|
package/lib/index.js
CHANGED
|
@@ -414,6 +414,43 @@ function decodeTallyOfStream(stream, endTime) {
|
|
|
414
414
|
return tally;
|
|
415
415
|
}
|
|
416
416
|
/**
|
|
417
|
+
* The ORDERED decode spans of one embedded assistant stream — the same
|
|
418
|
+
* ownership rule as {@link decodeTallyOfStream} (each `block-start` marker
|
|
419
|
+
* owns the interval up to the next one, the last one up to `endTime`), but
|
|
420
|
+
* keeping the sequence: the timing strip paints the decode window in stream
|
|
421
|
+
* order. Total over untrusted input, mirroring the tally: a malformed record
|
|
422
|
+
* is skipped whole, an unknown `blockType` leaves its interval unspanned
|
|
423
|
+
* (the caller's residue fill owns it), a zero/negative-length span drops
|
|
424
|
+
* out, and a stream with no marker (or not an array) yields none.
|
|
425
|
+
*/
|
|
426
|
+
function decodeSpansOfStream(stream, endTime) {
|
|
427
|
+
const spans = [];
|
|
428
|
+
if (!Array.isArray(stream) || !Number.isFinite(endTime)) return spans;
|
|
429
|
+
let kind;
|
|
430
|
+
let since = 0;
|
|
431
|
+
const close = (to) => {
|
|
432
|
+
if (kind !== void 0 && to > since) spans.push({
|
|
433
|
+
kind,
|
|
434
|
+
start: since,
|
|
435
|
+
end: to
|
|
436
|
+
});
|
|
437
|
+
};
|
|
438
|
+
for (const record of stream) {
|
|
439
|
+
if (record === null || typeof record !== "object") continue;
|
|
440
|
+
const r = record;
|
|
441
|
+
if (r.type !== "chunk" || r.chunk === null || typeof r.chunk !== "object") continue;
|
|
442
|
+
const chunk = r.chunk;
|
|
443
|
+
if (chunk.type !== "block-start") continue;
|
|
444
|
+
const time = r.time;
|
|
445
|
+
if (typeof time !== "number" || !Number.isFinite(time)) continue;
|
|
446
|
+
close(time);
|
|
447
|
+
kind = decodeKindOfBlock(chunk.blockType);
|
|
448
|
+
since = time;
|
|
449
|
+
}
|
|
450
|
+
close(endTime);
|
|
451
|
+
return spans;
|
|
452
|
+
}
|
|
453
|
+
/**
|
|
417
454
|
* The first token's instant inside one PACKED delta run (`text-chunks` /
|
|
418
455
|
* `reasoning-chunks` / `tool-call-chunks`): the run's base time plus the
|
|
419
456
|
* accumulated inter-member deltas, taken at the first qualifying member —
|
|
@@ -758,6 +795,7 @@ function trimState(st, bounds) {
|
|
|
758
795
|
st.turnRuns = countTurnRuns(st.requests);
|
|
759
796
|
}
|
|
760
797
|
if (st.events.length > bounds.maxEvents) st.events = st.events.slice(-bounds.maxEvents);
|
|
798
|
+
if (st.spans.length > TIMING_SPANS_MAX) st.spans = st.spans.slice(-TIMING_SPANS_MAX);
|
|
761
799
|
if (st.fileOps.length > bounds.maxFileOps) {
|
|
762
800
|
const drop = st.fileOps.length - bounds.maxFileOps;
|
|
763
801
|
st.fileOpsFloor = Math.max(st.fileOpsFloor ?? 0, st.fileOps[drop - 1].seq);
|
|
@@ -775,6 +813,13 @@ function trimState(st, bounds) {
|
|
|
775
813
|
}
|
|
776
814
|
}
|
|
777
815
|
}
|
|
816
|
+
/**
|
|
817
|
+
* Bound on the timing strip's painted spans (TimelineState.spans): a count
|
|
818
|
+
* cap, newest tail kept — the same hardcoded-cap family as SYSTEM_NODES_MAX.
|
|
819
|
+
* At ~6–8 spans per step the cap covers a few hundred recent steps, far past
|
|
820
|
+
* the strip's pixel resolution on any real pane.
|
|
821
|
+
*/
|
|
822
|
+
const TIMING_SPANS_MAX = 2e3;
|
|
778
823
|
function createTimelineState() {
|
|
779
824
|
return {
|
|
780
825
|
surface: [],
|
|
@@ -792,7 +837,8 @@ function createTimelineState() {
|
|
|
792
837
|
events: [],
|
|
793
838
|
archived: [],
|
|
794
839
|
callNames: {},
|
|
795
|
-
fileOps: []
|
|
840
|
+
fileOps: [],
|
|
841
|
+
spans: []
|
|
796
842
|
};
|
|
797
843
|
}
|
|
798
844
|
function categoryOf(type, message) {
|
|
@@ -969,6 +1015,11 @@ function applySurface(st, ev, type, data, message) {
|
|
|
969
1015
|
timing.toolsMs += dur;
|
|
970
1016
|
timing.toolCalls += 1;
|
|
971
1017
|
bumpToolTotals(timing, toolEntry.name, dur);
|
|
1018
|
+
if (st.stepStart !== void 0) st.stepSpans = [...st.stepSpans ?? [], {
|
|
1019
|
+
kind: "tools",
|
|
1020
|
+
start: toolEntry.start,
|
|
1021
|
+
end: ev.time
|
|
1022
|
+
}];
|
|
972
1023
|
}
|
|
973
1024
|
if (typeof srcId === "string" || typeof blockId === "string") {
|
|
974
1025
|
const kept = {};
|
|
@@ -1345,6 +1396,7 @@ function applyTimeline(state, event, bounds) {
|
|
|
1345
1396
|
case "step/start": {
|
|
1346
1397
|
const s = ensure([]);
|
|
1347
1398
|
s.stepStart = { time: event.time };
|
|
1399
|
+
delete s.stepSpans;
|
|
1348
1400
|
break;
|
|
1349
1401
|
}
|
|
1350
1402
|
case "step/end": {
|
|
@@ -1352,7 +1404,43 @@ function applyTimeline(state, event, bounds) {
|
|
|
1352
1404
|
if (start === void 0) return state;
|
|
1353
1405
|
const s = ensure(["timing"]);
|
|
1354
1406
|
ensureTiming(s).wallMs += durOf(start.time, event.time);
|
|
1407
|
+
const painted = [];
|
|
1408
|
+
for (const span of state.stepSpans ?? []) {
|
|
1409
|
+
const from = Math.max(start.time, span.start);
|
|
1410
|
+
const to = Math.min(event.time, span.end);
|
|
1411
|
+
if (to > from) painted.push({
|
|
1412
|
+
kind: span.kind,
|
|
1413
|
+
start: from,
|
|
1414
|
+
end: to
|
|
1415
|
+
});
|
|
1416
|
+
}
|
|
1417
|
+
painted.sort((a, b) => a.start - b.start || a.end - b.end);
|
|
1418
|
+
const flushed = [];
|
|
1419
|
+
let cursor = start.time;
|
|
1420
|
+
for (const span of painted) {
|
|
1421
|
+
const from = Math.max(cursor, span.start);
|
|
1422
|
+
if (span.end <= from) continue;
|
|
1423
|
+
if (from > cursor) flushed.push({
|
|
1424
|
+
kind: "other",
|
|
1425
|
+
start: cursor,
|
|
1426
|
+
end: from
|
|
1427
|
+
});
|
|
1428
|
+
flushed.push({
|
|
1429
|
+
kind: span.kind,
|
|
1430
|
+
start: from,
|
|
1431
|
+
end: span.end
|
|
1432
|
+
});
|
|
1433
|
+
cursor = span.end;
|
|
1434
|
+
}
|
|
1435
|
+
if (event.time > cursor) flushed.push({
|
|
1436
|
+
kind: "other",
|
|
1437
|
+
start: cursor,
|
|
1438
|
+
end: event.time
|
|
1439
|
+
});
|
|
1440
|
+
s.spans = [...state.spans, ...flushed];
|
|
1355
1441
|
delete s.stepStart;
|
|
1442
|
+
delete s.stepSpans;
|
|
1443
|
+
bumpDetailRev(s);
|
|
1356
1444
|
break;
|
|
1357
1445
|
}
|
|
1358
1446
|
case "user/message": {
|
|
@@ -1506,6 +1594,24 @@ function applyTimeline(state, event, bounds) {
|
|
|
1506
1594
|
if (firstToken !== void 0) {
|
|
1507
1595
|
timing.ttftMs += durOf(stepStart.time, firstToken);
|
|
1508
1596
|
timing.genMs += durOf(firstToken, event.time);
|
|
1597
|
+
const decodeBlocks = decodeSpansOfStream(data?.stream, event.time);
|
|
1598
|
+
let decodeStart = firstToken;
|
|
1599
|
+
for (const block of decodeBlocks) decodeStart = Math.min(decodeStart, block.start);
|
|
1600
|
+
const modelSpans = [{
|
|
1601
|
+
kind: "ttft",
|
|
1602
|
+
start: stepStart.time,
|
|
1603
|
+
end: decodeStart
|
|
1604
|
+
}];
|
|
1605
|
+
for (const block of decodeBlocks) {
|
|
1606
|
+
const to = Math.min(event.time, block.end);
|
|
1607
|
+
if (to <= block.start) continue;
|
|
1608
|
+
modelSpans.push({
|
|
1609
|
+
kind: block.kind,
|
|
1610
|
+
start: modelSpans.length === 1 ? decodeStart : block.start,
|
|
1611
|
+
end: to
|
|
1612
|
+
});
|
|
1613
|
+
}
|
|
1614
|
+
s.stepSpans = [...s.stepSpans ?? [], ...modelSpans];
|
|
1509
1615
|
if (output !== null) {
|
|
1510
1616
|
timing.speedMs = (timing.speedMs ?? 0) + durOf(firstToken, event.time);
|
|
1511
1617
|
timing.speedTokens = (timing.speedTokens ?? 0) + output;
|
|
@@ -1641,7 +1747,8 @@ function detailCollectionsOf(state, bounds) {
|
|
|
1641
1747
|
droppedNodes: 0,
|
|
1642
1748
|
archive: state.archived.map((n) => ({ ...n })),
|
|
1643
1749
|
fileOps: state.fileOps.map((o) => ({ ...o })),
|
|
1644
|
-
...state.fileOpsFloor !== void 0 ? { fileOpsFloor: state.fileOpsFloor } : {}
|
|
1750
|
+
...state.fileOpsFloor !== void 0 ? { fileOpsFloor: state.fileOpsFloor } : {},
|
|
1751
|
+
spans: state.spans.map((s) => ({ ...s }))
|
|
1645
1752
|
};
|
|
1646
1753
|
const overflowCount = Math.max(0, state.surface.length - bounds.maxNodes);
|
|
1647
1754
|
const overflow = state.surface.slice(0, overflowCount);
|
|
@@ -3103,6 +3210,19 @@ const fileOpSchema = z.object({
|
|
|
3103
3210
|
pattern: z.literal(true).optional(),
|
|
3104
3211
|
gone: z.number().int().nonnegative().optional()
|
|
3105
3212
|
}).strict();
|
|
3213
|
+
/** One painted span of the timing strip (shared/types.ts TimingSpan). */
|
|
3214
|
+
const timingSpanSchema = z.object({
|
|
3215
|
+
kind: z.enum([
|
|
3216
|
+
"ttft",
|
|
3217
|
+
"reasoning",
|
|
3218
|
+
"text",
|
|
3219
|
+
"toolarg",
|
|
3220
|
+
"tools",
|
|
3221
|
+
"other"
|
|
3222
|
+
]),
|
|
3223
|
+
start: z.number(),
|
|
3224
|
+
end: z.number()
|
|
3225
|
+
}).strict();
|
|
3106
3226
|
const currentSchema = z.object({
|
|
3107
3227
|
system: z.number().int().nonnegative(),
|
|
3108
3228
|
tools: z.number().int().nonnegative(),
|
|
@@ -3198,7 +3318,8 @@ const contextTimelineSchema = z.object({
|
|
|
3198
3318
|
surfaceFloor: z.number().int().nonnegative().optional(),
|
|
3199
3319
|
archiveFloor: z.number().int().nonnegative().optional(),
|
|
3200
3320
|
fileOps: z.array(fileOpSchema).optional(),
|
|
3201
|
-
fileOpsFloor: z.number().int().nonnegative().optional()
|
|
3321
|
+
fileOpsFloor: z.number().int().nonnegative().optional(),
|
|
3322
|
+
spans: z.array(timingSpanSchema).optional()
|
|
3202
3323
|
}).strict();
|
|
3203
3324
|
/**
|
|
3204
3325
|
* The persisted fold-state schema (the registry's `stateSchema`
|
|
@@ -3245,6 +3366,8 @@ const timelineStateSchema = z.object({
|
|
|
3245
3366
|
detailRev: z.number().int().nonnegative().optional(),
|
|
3246
3367
|
fileOps: z.array(fileOpSchema),
|
|
3247
3368
|
fileOpsFloor: z.number().int().nonnegative().optional(),
|
|
3369
|
+
spans: z.array(timingSpanSchema),
|
|
3370
|
+
stepSpans: z.array(timingSpanSchema).optional(),
|
|
3248
3371
|
pendingCodeOps: z.record(z.string(), z.array(fileOpSchema)).optional()
|
|
3249
3372
|
});
|
|
3250
3373
|
/**
|
|
@@ -3292,7 +3415,7 @@ function createContextTimelineDefinition(config, slim) {
|
|
|
3292
3415
|
},
|
|
3293
3416
|
init: () => createTimelineState(),
|
|
3294
3417
|
apply: (state, event) => applyTimeline(state, event, bounds),
|
|
3295
|
-
stateVersion:
|
|
3418
|
+
stateVersion: 23
|
|
3296
3419
|
};
|
|
3297
3420
|
}
|
|
3298
3421
|
//#endregion
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-context",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.62.1",
|
|
4
4
|
"description": "A DeepSeek Harness plugin for context insight and management, with context dashboard and context command, for understanding how the context is made of, and how it evolves.",
|
|
5
5
|
"icon": "icon.svg",
|
|
6
6
|
"author": "bowenliang123",
|