dsh-context 0.40.1 → 0.41.0

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 CHANGED
@@ -37,10 +37,11 @@ window.__ModuleLoader__.load({
37
37
  "timing.title": "耗时统计",
38
38
  "timing.hint": "整个会话的活跃时长分布",
39
39
  "timing.total": "总活跃时长",
40
- "timing.lm": "模型调用",
40
+ "timing.ttft": "模型等待",
41
+ "timing.gen": "模型生成",
41
42
  "timing.tools": "工具执行",
42
43
  "timing.other": "其他开销",
43
- "timing.lmTimes": "{n}次",
44
+ "timing.callTimes": "{n}次",
44
45
  "timing.toolTimes": "{n}次",
45
46
  "timing.empty": "暂无耗时数据 · 对话开始后这里会显示时长分布",
46
47
  "tokens.title": "Token 统计",
@@ -312,10 +313,11 @@ window.__ModuleLoader__.load({
312
313
  "timing.title": "Timing Stats",
313
314
  "timing.hint": "Whole-session active-time split",
314
315
  "timing.total": "Active Time",
315
- "timing.lm": "Model calls",
316
+ "timing.ttft": "TTFT",
317
+ "timing.gen": "LLM Gen",
316
318
  "timing.tools": "Tool runs",
317
319
  "timing.other": "Overhead",
318
- "timing.lmTimes": "{n} calls",
320
+ "timing.callTimes": "{n} calls",
319
321
  "timing.toolTimes": "{n} runs",
320
322
  "timing.empty": "No timing data yet — durations appear as the conversation runs",
321
323
  "tokens.title": "Token Stats",
@@ -997,7 +999,8 @@ window.__ModuleLoader__.load({
997
999
  const t = value;
998
1000
  for (const k of [
999
1001
  "wallMs",
1000
- "lmMs",
1002
+ "ttftMs",
1003
+ "genMs",
1001
1004
  "calls",
1002
1005
  "toolsMs",
1003
1006
  "toolCalls"
@@ -1038,7 +1041,8 @@ window.__ModuleLoader__.load({
1038
1041
  }
1039
1042
  return {
1040
1043
  wallMs: msNumOf(data.wallMs),
1041
- lmMs: msNumOf(data.lmMs),
1044
+ ttftMs: msNumOf(data.ttftMs),
1045
+ genMs: msNumOf(data.genMs),
1042
1046
  calls: msNumOf(data.calls),
1043
1047
  toolsMs: msNumOf(data.toolsMs),
1044
1048
  toolCalls: msNumOf(data.toolCalls),
@@ -5059,7 +5063,7 @@ window.__ModuleLoader__.load({
5059
5063
  return function PluginInfo() {
5060
5064
  const [latest, setLatest] = React.useState(null);
5061
5065
  React.useEffect(() => {
5062
- if ("0.40.1".includes("-dev")) return;
5066
+ if ("0.41.0".includes("-dev")) return;
5063
5067
  let on = true;
5064
5068
  fetchLatestVersion().then((v) => {
5065
5069
  if (on && v) setLatest(v);
@@ -5068,8 +5072,8 @@ window.__ModuleLoader__.load({
5068
5072
  on = false;
5069
5073
  };
5070
5074
  }, []);
5071
- const update = latest !== null && isNewerVersion(latest, "0.40.1") ? latest : null;
5072
- const nameText = "dsh-context (v0.40.1)";
5075
+ const update = latest !== null && isNewerVersion(latest, "0.41.0") ? latest : null;
5076
+ const nameText = "dsh-context (v0.41.0)";
5073
5077
  const nameValue = [nameText];
5074
5078
  if (update) nameValue.push(/* @__PURE__ */ React.createElement("span", {
5075
5079
  key: "update",
@@ -5477,20 +5481,27 @@ window.__ModuleLoader__.load({
5477
5481
  let segments = [];
5478
5482
  let rows = [];
5479
5483
  if (timing !== null && (wall > 0 || timing.calls > 0 || timing.toolCalls > 0)) {
5480
- const lm = Math.min(timing.lmMs, wall);
5481
- const toolRing = Math.max(0, Math.min(timing.toolsMs, wall - lm));
5482
- const other = Math.max(0, wall - lm - toolRing);
5484
+ const ttft = Math.min(timing.ttftMs, wall);
5485
+ const gen = Math.max(0, Math.min(timing.genMs, wall - ttft));
5486
+ const toolRing = Math.max(0, Math.min(timing.toolsMs, wall - ttft - gen));
5487
+ const other = Math.max(0, wall - ttft - gen - toolRing);
5483
5488
  const share = (ms) => wall > 0 ? ms / wall : 0;
5484
5489
  const countOf = (ms, times) => {
5485
5490
  const dur = fmtDuration(ms, lang);
5486
5491
  if (times === void 0) return dur;
5487
5492
  return ms > 0 ? `${dur} · ${times}` : times;
5488
5493
  };
5494
+ const callTimes = t("timing.callTimes", { n: fmt(timing.calls) });
5489
5495
  segments = [
5490
5496
  {
5491
- key: "lm",
5497
+ key: "ttft",
5492
5498
  color: "#3b82f6",
5493
- value: share(lm)
5499
+ value: share(ttft)
5500
+ },
5501
+ {
5502
+ key: "gen",
5503
+ color: "#8b5cf6",
5504
+ value: share(gen)
5494
5505
  },
5495
5506
  {
5496
5507
  key: "tools",
@@ -5505,12 +5516,20 @@ window.__ModuleLoader__.load({
5505
5516
  ];
5506
5517
  rows = [
5507
5518
  {
5508
- key: "lm",
5519
+ key: "ttft",
5509
5520
  color: "#3b82f6",
5510
- label: t("timing.lm"),
5511
- dim: timing.lmMs === 0,
5512
- pct: fmtShare(timing.lmMs, wall),
5513
- count: countOf(timing.lmMs, t("timing.lmTimes", { n: fmt(timing.calls) }))
5521
+ label: t("timing.ttft"),
5522
+ dim: timing.ttftMs === 0,
5523
+ pct: fmtShare(timing.ttftMs, wall),
5524
+ count: countOf(timing.ttftMs, callTimes)
5525
+ },
5526
+ {
5527
+ key: "gen",
5528
+ color: "#8b5cf6",
5529
+ label: t("timing.gen"),
5530
+ dim: timing.genMs === 0,
5531
+ pct: fmtShare(timing.genMs, wall),
5532
+ count: countOf(timing.genMs, callTimes)
5514
5533
  },
5515
5534
  {
5516
5535
  key: "tools",
@@ -6431,7 +6450,7 @@ window.__ModuleLoader__.load({
6431
6450
  }
6432
6451
  //#endregion
6433
6452
  //#region \0dsh-global-css:/home/runner/work/dsh-context/dsh-context/src/client/styles/base.css.mjs
6434
- const css$13 = ".lc-root{box-sizing:border-box;height:100%;color:var(--dsw-alias-label-primary);padding:16px 20px 32px;font-size:13px;overflow-y:auto}.lc-card{background:var(--dsw-alias-bg-layer-1);border:1px solid var(--dsw-alias-border-l2);border-radius:10px;margin-bottom:14px;padding:14px 16px}.lc-card-title{flex-wrap:wrap;align-items:baseline;gap:8px;margin-bottom:10px;font-weight:600;display:flex}.lc-card-title-text{white-space:nowrap;flex:none}.lc-gran,.lc-kinds{background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l2);border-radius:6px;gap:2px;margin-left:auto;padding:1px;display:flex}.lc-trend-ctl{align-items:center;gap:6px;margin-left:auto;display:flex}.lc-trend-ctl .lc-gran{margin-left:0}.lc-gran-btn{color:var(--dsw-alias-label-secondary);cursor:pointer;transition:color var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out);background:0 0;border:0;border-radius:5px;padding:3px 8px;font-family:inherit;font-size:11px;line-height:1}.lc-gran-btn:hover{color:var(--dsw-alias-label-primary)}.lc-gran-on,.lc-gran-on:hover{background:var(--dsw-alias-button-primary-fill);color:var(--dsw-alias-label-primary-foreground)}.lc-tip{z-index:6;background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l1);width:max-content;color:var(--dsw-alias-label-primary);box-shadow:var(--dsw-shadow-lv3,0 2px 8px #0000002e);pointer-events:none;opacity:0;transition:opacity var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out);border-radius:6px;padding:6px 10px;font-size:12px;position:absolute}.lc-events,.lc-fa-list{flex-direction:column;gap:2px;height:320px;display:flex;overflow-y:auto}.lc-cols{flex-wrap:wrap;gap:14px;margin-bottom:14px;display:flex}.lc-col{flex:1;min-width:280px}.lc-cols>.lc-card,.lc-col>.lc-card:last-child{margin-bottom:0}.lc-col-browser{flex-direction:column;display:flex}.lc-col-browser>.lc-card{flex:1}.lc-head>.lc-card{flex:1 1 0;min-width:0;container:lc-head-card/inline-size}.lc-head>.lc-card:first-child{flex-direction:column;display:flex}.lc-head>.lc-card:first-child .lc-stats{flex:1;align-content:stretch}.lc-head>.lc-card:first-child .lc-stat{justify-content:center}.lc-empty{color:var(--dsw-alias-label-secondary);text-align:center;padding:18px 0}.lc-error{flex-direction:column;align-items:center;gap:8px;padding:40px 16px;display:flex}.lc-error-msg{font-family:var(--ds-font-family-code,ui-monospace, SFMono-Regular, Menlo, monospace);color:var(--dsw-alias-state-error-primary);background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l1);overflow-wrap:anywhere;border-radius:6px;max-width:100%;padding:4px 8px;font-size:12px}.lc-error-retry{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-primary);font:inherit;cursor:pointer;transition:border-color var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out);border-radius:6px;padding:4px 14px;font-size:12px}.lc-error-retry:hover{border-color:var(--dsw-alias-label-primary)}.lc-foot{color:var(--dsw-alias-label-secondary);margin-top:4px;font-size:12px}[data-conversation-scroll]:has(.lc-root)>[data-composer-seat]:not(:has([data-approval-key],[data-question-key],[data-plan-review-key])){display:none}";
6453
+ const css$13 = ".lc-root{box-sizing:border-box;height:100%;color:var(--dsw-alias-label-primary);padding:16px 20px 32px;font-size:13px;overflow-y:auto}.lc-card{background:var(--dsw-alias-bg-layer-1);border:1px solid var(--dsw-alias-border-l2);border-radius:10px;margin-bottom:14px;padding:14px 16px}.lc-root .lc-card{margin-bottom:7px;padding:7px 8px}.lc-root .lc-cols{gap:7px;margin-bottom:7px}.lc-card-title{flex-wrap:wrap;align-items:baseline;gap:8px;margin-bottom:10px;font-weight:600;display:flex}.lc-card-title-text{white-space:nowrap;flex:none}.lc-gran,.lc-kinds{background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l2);border-radius:6px;gap:2px;margin-left:auto;padding:1px;display:flex}.lc-trend-ctl{align-items:center;gap:6px;margin-left:auto;display:flex}.lc-trend-ctl .lc-gran{margin-left:0}.lc-gran-btn{color:var(--dsw-alias-label-secondary);cursor:pointer;transition:color var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out);background:0 0;border:0;border-radius:5px;padding:3px 8px;font-family:inherit;font-size:11px;line-height:1}.lc-gran-btn:hover{color:var(--dsw-alias-label-primary)}.lc-gran-on,.lc-gran-on:hover{background:var(--dsw-alias-button-primary-fill);color:var(--dsw-alias-label-primary-foreground)}.lc-tip{z-index:6;background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l1);width:max-content;color:var(--dsw-alias-label-primary);box-shadow:var(--dsw-shadow-lv3,0 2px 8px #0000002e);pointer-events:none;opacity:0;transition:opacity var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out);border-radius:6px;padding:6px 10px;font-size:12px;position:absolute}.lc-events,.lc-fa-list{flex-direction:column;gap:2px;height:320px;display:flex;overflow-y:auto}.lc-cols{flex-wrap:wrap;gap:14px;margin-bottom:14px;display:flex}.lc-col{flex:1;min-width:280px}.lc-cols>.lc-card,.lc-col>.lc-card:last-child{margin-bottom:0}.lc-col-browser{flex-direction:column;display:flex}.lc-col-browser>.lc-card{flex:1}.lc-head>.lc-card{flex:1 1 0;min-width:0;container:lc-head-card/inline-size}.lc-head>.lc-card:first-child{flex-direction:column;display:flex}.lc-head>.lc-card:first-child .lc-stats{flex:1;align-content:stretch}.lc-head>.lc-card:first-child .lc-stat{justify-content:center}.lc-empty{color:var(--dsw-alias-label-secondary);text-align:center;padding:18px 0}.lc-error{flex-direction:column;align-items:center;gap:8px;padding:40px 16px;display:flex}.lc-error-msg{font-family:var(--ds-font-family-code,ui-monospace, SFMono-Regular, Menlo, monospace);color:var(--dsw-alias-state-error-primary);background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l1);overflow-wrap:anywhere;border-radius:6px;max-width:100%;padding:4px 8px;font-size:12px}.lc-error-retry{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-primary);font:inherit;cursor:pointer;transition:border-color var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out);border-radius:6px;padding:4px 14px;font-size:12px}.lc-error-retry:hover{border-color:var(--dsw-alias-label-primary)}.lc-foot{color:var(--dsw-alias-label-secondary);margin-top:4px;font-size:12px}[data-conversation-scroll]:has(.lc-root)>[data-composer-seat]:not(:has([data-approval-key],[data-question-key],[data-plan-review-key])){display:none}";
6435
6454
  const tagId$13 = "dsh-context/base.css";
6436
6455
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$13) + "]") === null) {
6437
6456
  const tag = document.createElement("style");
package/lib/index.d.ts CHANGED
@@ -112,14 +112,18 @@ interface TimelineState {
112
112
  timing?: TimingTotals;
113
113
  /**
114
114
  * The open step's start instant, armed by `step/start` and consumed by the
115
- * `assistant/message` (LM-call time) and `step/end` (wall time) that follow
116
- * it. One slot, not a map: steps are sequential in the log, so the newest
117
- * `step/start` is the one those events close a hostile interleaved log
118
- * degrades to skipped durations, never to unbounded state. Same
119
- * arm/remove lifecycle as `pendingShadowedSeqs`.
115
+ * `assistant/message` (TTFT/generation split) and `step/end` (wall time)
116
+ * that follow it; `assistant/chunk` stamps `firstToken` on the step's first
117
+ * token delta absent when the stream carried none (legacy or aborted
118
+ * steps), which leaves that call's model time unattributed. One slot, not a
119
+ * map: steps are sequential in the log, so the newest `step/start` is the
120
+ * one those events close — a hostile interleaved log degrades to skipped
121
+ * durations, never to unbounded state. Same arm/remove lifecycle as
122
+ * `pendingShadowedSeqs`.
120
123
  */
121
124
  stepStart?: {
122
125
  time: number;
126
+ firstToken?: number;
123
127
  };
124
128
  /**
125
129
  * Tool callId → the call's name and start instant, armed by `tool/call` and
@@ -288,18 +292,22 @@ interface ToolTimingTotals {
288
292
  }
289
293
  /**
290
294
  * Whole-session timing totals, host-folded from the durable `step/start` /
291
- `step/end` / `tool/call` / `tool/result` lifecycle (running totals over the
292
- * COMPLETE session log — the same never-trimmed framing as `cost`). Durations
293
- * are wall-clock milliseconds: `wallMs` sums whole steps, `lmMs` the
294
- * step-start → assistant-message slice (the model call), `toolsMs` the sum of
295
- * per-call tool durations (parallel calls each count, so it can overlap).
295
+ `step/end` / `assistant/chunk` / `tool/call` / `tool/result` lifecycle
296
+ * (running totals over the COMPLETE session log — the same never-trimmed
297
+ * framing as `cost`). Durations are wall-clock milliseconds: `wallMs` sums
298
+ * whole steps, `ttftMs` the step-start → first-token slice (the model wait)
299
+ * and `genMs` the first-token assistant-message slice (the generation)
300
+ * both only over calls whose stream carried a token delta, `toolsMs` the sum
301
+ * of per-call tool durations (parallel calls each count, so it can overlap).
296
302
  * Absent until the first step lifecycle completes in the log.
297
303
  */
298
304
  interface TimingTotals {
299
305
  /** Summed wall time of completed steps (the session's active time). */
300
306
  wallMs: number;
301
- /** Summed step-start → assistant-message time (model-call time). */
302
- lmMs: number;
307
+ /** Summed step-start → first-token time (the model wait, TTFT). */
308
+ ttftMs: number;
309
+ /** Summed first-token → assistant-message time (the generation). */
310
+ genMs: number;
303
311
  /** Completed model calls (assistant messages folded). */
304
312
  calls: number;
305
313
  /** Summed per-call durations of completed tool calls. */
package/lib/index.js CHANGED
@@ -993,6 +993,21 @@ function durOf(from, to) {
993
993
  return Math.max(0, to - from);
994
994
  }
995
995
  /**
996
+ * Whether a stream chunk carries a non-empty token delta — the first-token
997
+ * marker the TTFT fold waits for (the same rule as the harness's own
998
+ * session-stats fold). Shape-guarded: a malformed chunk is just not a token.
999
+ */
1000
+ function isTokenDelta(chunk) {
1001
+ if (chunk === null || typeof chunk !== "object") return false;
1002
+ const c = chunk;
1003
+ switch (c.type) {
1004
+ case "text-delta":
1005
+ case "reasoning-delta": return typeof c.text === "string" && c.text !== "";
1006
+ case "tool-call-delta": return typeof c.argumentsDelta === "string" && c.argumentsDelta !== "" || c.name !== void 0;
1007
+ default: return false;
1008
+ }
1009
+ }
1010
+ /**
996
1011
  * The fold's private timing accumulator: created on first use, and CLONED on
997
1012
  * every later ensure() (see `applyTimeline`) — the object left in the
998
1013
  * persisted previous state is never written into in place.
@@ -1000,7 +1015,8 @@ function durOf(from, to) {
1000
1015
  function ensureTiming(st) {
1001
1016
  if (st.timing === void 0) st.timing = {
1002
1017
  wallMs: 0,
1003
- lmMs: 0,
1018
+ ttftMs: 0,
1019
+ genMs: 0,
1004
1020
  calls: 0,
1005
1021
  toolsMs: 0,
1006
1022
  toolCalls: 0,
@@ -1091,6 +1107,17 @@ function applyTimeline(state, event, bounds) {
1091
1107
  };
1092
1108
  }
1093
1109
  break;
1110
+ case "assistant/chunk": {
1111
+ const start = state.stepStart;
1112
+ if (start === void 0 || start.firstToken !== void 0) return state;
1113
+ if (!isTokenDelta(data?.chunk)) return state;
1114
+ const s = ensure();
1115
+ s.stepStart = {
1116
+ time: start.time,
1117
+ firstToken: event.time
1118
+ };
1119
+ break;
1120
+ }
1094
1121
  case "step/start": {
1095
1122
  const s = ensure();
1096
1123
  s.stepStart = { time: event.time };
@@ -1177,7 +1204,10 @@ function applyTimeline(state, event, bounds) {
1177
1204
  const timing = ensureTiming(s);
1178
1205
  timing.calls += 1;
1179
1206
  const stepStart = state.stepStart;
1180
- if (stepStart !== void 0) timing.lmMs += durOf(stepStart.time, event.time);
1207
+ if (stepStart !== void 0 && stepStart.firstToken !== void 0) {
1208
+ timing.ttftMs += durOf(stepStart.time, stepStart.firstToken);
1209
+ timing.genMs += durOf(stepStart.firstToken, event.time);
1210
+ }
1181
1211
  const asstMsg = deriveEventMessage(event);
1182
1212
  applySurface(s, event, event.type, data, asstMsg);
1183
1213
  break;
@@ -1404,7 +1434,8 @@ const toolTimingSchema = z.object({
1404
1434
  }).strict();
1405
1435
  const timingTotalsSchema = z.object({
1406
1436
  wallMs: z.number().nonnegative(),
1407
- lmMs: z.number().nonnegative(),
1437
+ ttftMs: z.number().nonnegative(),
1438
+ genMs: z.number().nonnegative(),
1408
1439
  calls: z.number().int().nonnegative(),
1409
1440
  toolsMs: z.number().nonnegative(),
1410
1441
  toolCalls: z.number().int().nonnegative(),
@@ -1460,7 +1491,10 @@ const timelineStateSchema = z.object({
1460
1491
  }).strict().optional(),
1461
1492
  archiveFloor: z.number().optional(),
1462
1493
  timing: timingTotalsSchema.optional(),
1463
- stepStart: z.object({ time: z.number() }).strict().optional(),
1494
+ stepStart: z.object({
1495
+ time: z.number(),
1496
+ firstToken: z.number().optional()
1497
+ }).strict().optional(),
1464
1498
  callNames: z.record(z.string(), z.object({
1465
1499
  name: z.string(),
1466
1500
  start: z.number()
@@ -1499,7 +1533,7 @@ function createContextTimelineDefinition(config) {
1499
1533
  },
1500
1534
  init: () => createTimelineState(),
1501
1535
  apply: (state, event) => applyTimeline(state, event, bounds),
1502
- stateVersion: 11
1536
+ stateVersion: 12
1503
1537
  };
1504
1538
  }
1505
1539
  //#endregion
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-context",
3
- "version": "0.40.1",
3
+ "version": "0.41.0",
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
  "author": "bowenliang123",
6
6
  "repository": {