dsh-context 0.46.1 → 0.47.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/README.md CHANGED
@@ -10,6 +10,7 @@
10
10
 
11
11
  [`dsh-context`](https://www.npmjs.com/package/dsh-context) provides full context lifecycle management features.
12
12
  - **Context tab** — an UI context dashboard for DeepSeek Harness's context stats, composition, trend, events, and messages.
13
+ - **Context panel** — the same dashboard as a right-sidebar tab (dsh 0.1.5+): pick **Context** on the sidebar's guide page and the panel opens beside the chat.
13
14
  - **`/context` command** — the slash command shows the context model for current context composition and recent context evolution.
14
15
 
15
16
  ## Install / Update
@@ -140,7 +141,7 @@ In **Settings → Plugins → Plugin configuration**, the **Context** card holds
140
141
  ## Good to know
141
142
 
142
143
  - **Estimates vs actuals** — category figures use dsh's own fixed-density heuristic (the same one as its built-in token meter); the pinned trend details and Token/Timing rings show provider-reported actuals next to them.
143
- - **Compatibility** — works on `@deepseek-ai/dsh` **0.1.2-rc1+**. The per-release matrix and how it is verified: [docs/compatibility.md](docs/compatibility.md).
144
+ - **Compatibility** — works on `@deepseek-ai/dsh` **0.1.2-rc1+**, across the V0 (0.1.2-rc.x), V2 (0.1.3-alpha.x), and V3 (0.1.5-alpha.x+) session-log generations. The per-release matrix and how it is verified: [docs/compatibility.md](docs/compatibility.md).
144
145
  - **I18n** — UI in English and 简体中文.
145
146
 
146
147
  ## Like it?
package/lib/client.js CHANGED
@@ -10,6 +10,7 @@ window.__ModuleLoader__.load({
10
10
  //#region src/client/i18n.ts
11
11
  const DICT_ZH = {
12
12
  "tab": "上下文",
13
+ "sidebar.guideDescription": "查看这个会话的上下文构成、每步变化与消息列表。",
13
14
  "cat.system": "系统提示词",
14
15
  "cat.tools": "工具定义",
15
16
  "cat.user": "用户消息",
@@ -296,6 +297,7 @@ window.__ModuleLoader__.load({
296
297
  };
297
298
  const DICT_EN = {
298
299
  "tab": "Context",
300
+ "sidebar.guideDescription": "Inspect this session's context composition, per-step history, and messages.",
299
301
  "cat.system": "System Prompt",
300
302
  "cat.tools": "Tool Schemas",
301
303
  "cat.user": "User Messages",
@@ -954,7 +956,7 @@ window.__ModuleLoader__.load({
954
956
  "assistant",
955
957
  "tool",
956
958
  "total"
957
- ].every((k) => typeof current[k] === "number") && recordsOnly(data.requests) && recordsOnly(data.events) && recordsOnly(data.nodes) && recordsOnly(data.archive) && timingFastOk(data.timing)) return data;
959
+ ].every((k) => typeof current[k] === "number") && recordsOnly(data.requests) && recordsOnly(data.events) && recordsOnly(data.nodes) && recordsOnly(data.archive) && systemsFastOk(data.systems) && timingFastOk(data.timing)) return data;
958
960
  const safeCurrent = current !== null && typeof current === "object" ? current : {};
959
961
  const cost = typeof data.cost === "object" && data.cost !== null && !Array.isArray(data.cost) ? data.cost : void 0;
960
962
  const timing = timingOf(data.timing);
@@ -988,6 +990,7 @@ window.__ModuleLoader__.load({
988
990
  ...typeof data.detailRev === "number" && Number.isFinite(data.detailRev) ? { detailRev: data.detailRev } : {},
989
991
  ...cost !== void 0 ? { cost } : {},
990
992
  ...timing !== null ? { timing } : {},
993
+ ...data.systems !== void 0 ? { systems: systemsOf(data.systems) } : {},
991
994
  ...typeof data.surfaceFloor === "number" ? { surfaceFloor: data.surfaceFloor } : {},
992
995
  ...typeof data.archiveFloor === "number" ? { archiveFloor: data.archiveFloor } : {},
993
996
  ...data.fileOps !== void 0 ? { fileOps: objectsOf(data.fileOps) } : {},
@@ -995,6 +998,45 @@ window.__ModuleLoader__.load({
995
998
  };
996
999
  }
997
1000
  /**
1001
+ * The live system-prompt nodes, re-proved per entry and sorted by seq: an
1002
+ * entry missing a finite seq/time/tokens drops out (the browser then falls
1003
+ * back to the header epoch), so a hostile collection can never produce a NaN
1004
+ * prompt figure or an unfetchable seq. Absent or empty stays absent.
1005
+ */
1006
+ function systemsOf(value) {
1007
+ const list = objectsOf(value);
1008
+ const out = [];
1009
+ for (const entry of list) {
1010
+ const { seq, time, tokens } = entry;
1011
+ if (typeof seq !== "number" || !Number.isFinite(seq)) continue;
1012
+ if (typeof time !== "number" || !Number.isFinite(time)) continue;
1013
+ if (typeof tokens !== "number" || !Number.isFinite(tokens)) continue;
1014
+ out.push({
1015
+ seq,
1016
+ time,
1017
+ tokens
1018
+ });
1019
+ }
1020
+ return out.sort((a, b) => a.seq - b.seq);
1021
+ }
1022
+ /**
1023
+ * The fast path's check for the live system-prompt nodes: every entry must
1024
+ * carry the three finite numbers the browser reads — `seq` for the per-step
1025
+ * resolution, `time` for the DNA band, `tokens` for its width. A primitive
1026
+ * entry, or one whose fields are not numbers, sends the payload down the
1027
+ * sanitizing slow path (`systemsOf` drops it) instead of leaking `undefined`
1028
+ * into the bar math. An absent list is fine.
1029
+ */
1030
+ function systemsFastOk(value) {
1031
+ if (value === void 0) return true;
1032
+ if (!Array.isArray(value)) return false;
1033
+ return value.every((entry) => {
1034
+ if (entry === null || typeof entry !== "object") return false;
1035
+ const { seq, time, tokens } = entry;
1036
+ return typeof seq === "number" && Number.isFinite(seq) && typeof time === "number" && Number.isFinite(time) && typeof tokens === "number" && Number.isFinite(tokens);
1037
+ });
1038
+ }
1039
+ /**
998
1040
  * The split head's count figures, re-proved field by field: a present-but-
999
1041
  * partial record zeroes its unreadable fields (the stats board's no-NaN
1000
1042
  * guarantee), an absent or non-record value stays absent (legacy generation
@@ -1323,6 +1365,25 @@ window.__ModuleLoader__.load({
1323
1365
  return out.trim() === "" ? null : out;
1324
1366
  }
1325
1367
  /**
1368
+ * The system prompt's exact rendered text — every text block joined with NO
1369
+ * normalization: a whitespace-only prompt is still the prompt the model
1370
+ * received (the host prices it as text), so it must render rather than read
1371
+ * as absent. Null when the content carries no text block at all.
1372
+ */
1373
+ function systemTextOf(blocks) {
1374
+ if (!Array.isArray(blocks)) return null;
1375
+ let out = "";
1376
+ let seen = false;
1377
+ for (const b of blocks) {
1378
+ const text = b !== null && typeof b === "object" ? b.text : void 0;
1379
+ if (typeof text === "string") {
1380
+ out += text;
1381
+ seen = true;
1382
+ }
1383
+ }
1384
+ return seen ? out : null;
1385
+ }
1386
+ /**
1326
1387
  * One assistant content block → the snapshot block vocabulary the browser
1327
1388
  * already renders (`kind`: text/reasoning/image/tool-call); unmappable
1328
1389
  * blocks pass through raw and degrade to the generic JSON section.
@@ -1548,13 +1609,23 @@ window.__ModuleLoader__.load({
1548
1609
  };
1549
1610
  }
1550
1611
  /**
1551
- * Map one raw `request/header` event into the epoch content the browser
1552
- * renders the full system prompt text plus each tool's producer
1553
- * description and raw schema, mirroring the host fold's per-entry guards
1554
- * (a null or primitive tool entry degrades to an unnamed row instead of
1555
- * throwing the read). Null when the envelope carries no usable header.
1612
+ * Map one raw durable event into the epoch content the browser renders. A
1613
+ * `request/header` yields the full system prompt text (V0/V2 envelope) plus
1614
+ * each tool's producer description and raw schema; a V3 `system/message`
1615
+ * yields the prompt text alone (its tools live in the request header). Both
1616
+ * mirror the host fold's per-entry guards a null or primitive tool entry
1617
+ * degrades to an unnamed row instead of throwing the read. Null when the
1618
+ * envelope carries neither.
1556
1619
  */
1557
- function headerContentOf(data) {
1620
+ function headerContentOf(event) {
1621
+ const { type, data } = event;
1622
+ if (type === "system/message") {
1623
+ const system = systemTextOf((data.message !== null && typeof data.message === "object" ? data.message : null)?.content);
1624
+ return system === null ? null : {
1625
+ system,
1626
+ tools: []
1627
+ };
1628
+ }
1558
1629
  const rawHeader = data.header !== null && typeof data.header === "object" ? data.header : null;
1559
1630
  if (rawHeader === null) return null;
1560
1631
  const toolsRaw = Array.isArray(rawHeader.tools) ? rawHeader.tools : [];
@@ -1573,15 +1644,15 @@ window.__ModuleLoader__.load({
1573
1644
  };
1574
1645
  }
1575
1646
  /**
1576
- * The on-demand CONTENT fetch for `contextHeaders` epochs the lazy
1577
- * counterpart of the node fetcher above. One seq-anchored history read off
1578
- * the epoch's `seq` returns the page holding that epoch's `request/header`
1579
- * event (non-message events ride the page verbatim); the raw header is
1580
- * mapped client-side into the renderable content. Epochs cache per session
1581
- * (history is immutable), and OLDER epochs sharing the page cache for free —
1582
- * stepping back through epochs walks the same pages. Undefined when no
1583
- * history face exists — the browser keeps a metadata-only degradation
1584
- * instead.
1647
+ * The on-demand CONTENT fetch for the browser's System and Tools sections —
1648
+ * the lazy counterpart of the node fetcher above. One seq-anchored history
1649
+ * read off the requested seq returns the page holding that event (non-message
1650
+ * events ride the page verbatim); a `request/header` maps to the epoch's
1651
+ * tools (and its V0/V2 system text), a `system/message` to a V3 prompt's
1652
+ * text. Landed content caches per seq (history is immutable), and OLDER
1653
+ * epochs sharing the page cache for free stepping back through epochs walks
1654
+ * the same pages. Undefined when no history face exists — the browser keeps a
1655
+ * metadata-only degradation instead.
1585
1656
  */
1586
1657
  function makeHeaderFetcher(sessionId) {
1587
1658
  const read = pageReaderOf(sessionId);
@@ -1594,8 +1665,8 @@ window.__ModuleLoader__.load({
1594
1665
  let picked = null;
1595
1666
  for (const entry of rows) {
1596
1667
  const ev = eventOf(entry);
1597
- if (ev === null || ev.type !== "request/header") continue;
1598
- const content = headerContentOf(ev.data);
1668
+ if (ev === null || ev.type !== "request/header" && ev.type !== "system/message") continue;
1669
+ const content = headerContentOf(ev);
1599
1670
  if (content === null) continue;
1600
1671
  cache.set(ev.seq, content);
1601
1672
  if (ev.seq === seq) picked = content;
@@ -1872,6 +1943,30 @@ window.__ModuleLoader__.load({
1872
1943
  for (let i = headers.headers.length - 1; i >= 0; i--) if (headers.headers[i].seq < seq) return headers.headers[i];
1873
1944
  return null;
1874
1945
  }
1946
+ /**
1947
+ * The system prompt in force at `seq` (null = none) — the LAST live system
1948
+ * node at or before it carrying tokens, which is exactly the host fold's
1949
+ * "last nonempty surviving system" rule and therefore agrees with the
1950
+ * per-step `system` figure the fold recorded. Rows folded before `systems`
1951
+ * existed carry none; the header epoch's envelope figure stands in for them
1952
+ * (the pre-V3 wire shape).
1953
+ */
1954
+ function systemAt(data, header, seq) {
1955
+ const systems = data.systems;
1956
+ if (systems !== void 0 && systems.length > 0) {
1957
+ for (let i = systems.length - 1; i >= 0; i--) {
1958
+ const node = systems[i];
1959
+ if (node.tokens <= 0) continue;
1960
+ if (seq === null || node.seq < seq) return node;
1961
+ }
1962
+ return null;
1963
+ }
1964
+ return header !== null && header.systemTokens !== void 0 ? {
1965
+ seq: header.seq,
1966
+ time: header.time,
1967
+ tokens: header.systemTokens
1968
+ } : null;
1969
+ }
1875
1970
  function assemble(data, headers, seq) {
1876
1971
  const live = seq === null;
1877
1972
  let nodes;
@@ -1888,9 +1983,11 @@ window.__ModuleLoader__.load({
1888
1983
  if (live || data.surfaceFloor !== void 0 && seq > data.surfaceFloor) missingLive = data.droppedNodes;
1889
1984
  }
1890
1985
  const approximate = !live && data.archiveFloor !== void 0 && seq < data.archiveFloor;
1986
+ const header = headerAt(headers, seq);
1891
1987
  return {
1892
1988
  live,
1893
- header: headerAt(headers, seq),
1989
+ header,
1990
+ system: systemAt(data, header, seq),
1894
1991
  nodes,
1895
1992
  missingLive,
1896
1993
  approximate
@@ -1900,19 +1997,17 @@ window.__ModuleLoader__.load({
1900
1997
  //#region src/client/dna.ts
1901
1998
  function dnaOf(view) {
1902
1999
  const items = [];
1903
- if (view.header !== null) {
1904
- if (view.header.systemTokens !== void 0) items.push({
1905
- key: "sys",
1906
- cat: "system",
1907
- tokens: view.header.systemTokens,
1908
- time: view.header.time
1909
- });
1910
- for (const tool of view.header.tools) items.push({
1911
- key: "tool:" + tool.name,
1912
- cat: "tools",
1913
- tokens: tool.tokens
1914
- });
1915
- }
2000
+ if (view.system !== null) items.push({
2001
+ key: "sys",
2002
+ cat: "system",
2003
+ tokens: view.system.tokens,
2004
+ time: view.system.time
2005
+ });
2006
+ if (view.header !== null) for (const tool of view.header.tools) items.push({
2007
+ key: "tool:" + tool.name,
2008
+ cat: "tools",
2009
+ tokens: tool.tokens
2010
+ });
1916
2011
  for (const n of view.nodes) items.push({
1917
2012
  key: "n" + String(n.seq),
1918
2013
  cat: n.cat,
@@ -3217,7 +3312,7 @@ window.__ModuleLoader__.load({
3217
3312
  return m;
3218
3313
  }
3219
3314
  function countOf(asm, byCat, c) {
3220
- if (c === "system") return asm.header !== null && asm.header.systemTokens !== void 0 ? 1 : 0;
3315
+ if (c === "system") return asm.system !== null ? 1 : 0;
3221
3316
  if (c === "tools") return asm.header !== null ? asm.header.tools.length : 0;
3222
3317
  return byCat[c]?.length ?? 0;
3223
3318
  }
@@ -3302,7 +3397,8 @@ window.__ModuleLoader__.load({
3302
3397
  const view = assemble(data, headers, seq);
3303
3398
  const fetchHeader = props.fetchHeader;
3304
3399
  const headerSeq = view.header !== null ? view.header.seq : null;
3305
- const epoch = useFetchOnMiss(headerSeq !== null && (openCat === "system" || openCat === "tools") ? headerSeq : null, fetchHeader, "dsh-context: header content fetch failed");
3400
+ const systemSeq = view.system !== null ? view.system.seq : null;
3401
+ const epoch = useFetchOnMiss(openCat === "system" ? systemSeq : openCat === "tools" ? headerSeq : null, fetchHeader, "dsh-context: header content fetch failed");
3306
3402
  const headerContent = epoch.values;
3307
3403
  const headerNote = fetchMissNote(t, fetchHeader, epoch.state, epoch.retry, "browser.headerMetaOnly");
3308
3404
  const breakdown = req !== null ? req : data.current;
@@ -3348,7 +3444,7 @@ window.__ModuleLoader__.load({
3348
3444
  };
3349
3445
  const toolCount = (c) => countOf(view, byCat, c);
3350
3446
  const singleKeyOf = (c) => {
3351
- if (c === "system") return view.header?.systemTokens !== void 0 ? "sys" : null;
3447
+ if (c === "system") return view.system !== null ? "sys" : null;
3352
3448
  if (c === "tools") {
3353
3449
  const tools = view.header?.tools;
3354
3450
  return tools !== void 0 && tools.length === 1 ? "tool:" + tools[0].name : null;
@@ -3418,11 +3514,12 @@ window.__ModuleLoader__.load({
3418
3514
  };
3419
3515
  const catBody = (c) => {
3420
3516
  if (c === "system") {
3421
- if (view.header === null) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3517
+ const sys = view.system;
3518
+ if (sys === null) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3422
3519
  className: "lc-br-note",
3423
3520
  children: t(headers === null ? "browser.noHeader" : "browser.noEpoch")
3424
3521
  });
3425
- const content = headerContent.get(view.header.seq);
3522
+ const content = headerContent.get(sys.seq);
3426
3523
  if (content === void 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3427
3524
  className: "lc-br-note",
3428
3525
  children: headerNote
@@ -6482,7 +6579,7 @@ window.__ModuleLoader__.load({
6482
6579
  return function PluginInfo() {
6483
6580
  const [latest, setLatest] = (0, react.useState)(null);
6484
6581
  (0, react.useEffect)(() => {
6485
- if ("0.46.1".includes("-dev")) return;
6582
+ if ("0.47.0".includes("-dev")) return;
6486
6583
  let on = true;
6487
6584
  fetchLatestVersion().then((v) => {
6488
6585
  if (on && v) setLatest(v);
@@ -6491,8 +6588,8 @@ window.__ModuleLoader__.load({
6491
6588
  on = false;
6492
6589
  };
6493
6590
  }, []);
6494
- const update = latest !== null && isNewerVersion(latest, "0.46.1") ? latest : null;
6495
- const nameText = "dsh-context (v0.46.1)";
6591
+ const update = latest !== null && isNewerVersion(latest, "0.47.0") ? latest : null;
6592
+ const nameText = "dsh-context (v0.47.0)";
6496
6593
  const nameValue = [nameText];
6497
6594
  if (update) nameValue.push(/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6498
6595
  className: "lc-pi-update",
@@ -8067,6 +8164,7 @@ window.__ModuleLoader__.load({
8067
8164
  const ErrorBoundary = makeErrorBoundary(t);
8068
8165
  function ContextViewBody(props) {
8069
8166
  const sessionId = props.sessionId;
8167
+ const inSidebar = props.host === "sidebar";
8070
8168
  const source = useTimelineSource(ctx, props);
8071
8169
  const data = source.data;
8072
8170
  const pressure = projectionOf(props, "contextPressure", contextPressureOf);
@@ -8261,6 +8359,132 @@ window.__ModuleLoader__.load({
8261
8359
  const activeLocale = localeSvc !== void 0 && typeof localeSvc.getLocale === "function" ? localeSvc.getLocale().active : "en";
8262
8360
  const subtitle = (data.model ?? "") + (data.provider ? " · " + data.provider : "");
8263
8361
  const gate = unsupportedOf(data.unsupported);
8362
+ const compositionCard = /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CurrentComposition, {
8363
+ head,
8364
+ subtitle,
8365
+ hoverKey: hoverCat,
8366
+ onHoverKey: setHoverCat
8367
+ });
8368
+ const trendCard = /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
8369
+ className: "lc-card",
8370
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
8371
+ className: "lc-card-title",
8372
+ children: [
8373
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
8374
+ className: "lc-card-title-text",
8375
+ children: t("trend.title")
8376
+ }),
8377
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
8378
+ className: "lc-gran lc-trend-adaptive",
8379
+ role: "group",
8380
+ title: t("trend.adaptiveHint"),
8381
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
8382
+ type: "button",
8383
+ className: "lc-gran-btn" + (adaptive ? " lc-gran-on" : ""),
8384
+ onClick: () => {
8385
+ setAdaptive((on) => !on);
8386
+ },
8387
+ children: t("trend.adaptive")
8388
+ })
8389
+ }),
8390
+ focusCat !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
8391
+ className: "lc-card-sub",
8392
+ children: t("trend.focus", { cat: kit.catLabel(focusCat) })
8393
+ }) : null,
8394
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
8395
+ className: "lc-trend-ctl",
8396
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
8397
+ className: "lc-gran",
8398
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
8399
+ className: "lc-gran-btn" + (granularity === "step" ? " lc-gran-on" : ""),
8400
+ onClick: () => {
8401
+ setGranularity("step");
8402
+ },
8403
+ children: t("gran.step")
8404
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
8405
+ className: "lc-gran-btn" + (granularity === "turn" ? " lc-gran-on" : ""),
8406
+ onClick: () => {
8407
+ setGranularity("turn");
8408
+ },
8409
+ children: t("gran.turn")
8410
+ })]
8411
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
8412
+ className: "lc-gran",
8413
+ title: t("gran.modeHint"),
8414
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
8415
+ className: "lc-gran-btn" + (trendMode === "total" ? " lc-gran-on" : ""),
8416
+ onClick: () => {
8417
+ setTrendMode("total");
8418
+ },
8419
+ children: t("gran.total")
8420
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
8421
+ className: "lc-gran-btn" + (trendMode === "delta" ? " lc-gran-on" : ""),
8422
+ onClick: () => {
8423
+ setTrendMode("delta");
8424
+ },
8425
+ children: t("gran.delta")
8426
+ })]
8427
+ })]
8428
+ })
8429
+ ]
8430
+ }), displayRequests.length === 0 ? detailReady ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
8431
+ className: "lc-empty",
8432
+ children: t("trend.empty")
8433
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DetailNote, {
8434
+ state: source.detailState === "failed" ? "failed" : "loading",
8435
+ onRetry: source.retryDetail
8436
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(TrendChart, {
8437
+ requests: displayRequests,
8438
+ markers,
8439
+ selectedSeq: pinnedReq ? pinnedReq.seq : null,
8440
+ hoveredSeq,
8441
+ activeTurn,
8442
+ granularity,
8443
+ mode: trendMode,
8444
+ focusTurn,
8445
+ hoverCat: trendHoverCat,
8446
+ focusCat,
8447
+ adaptive,
8448
+ onSelect: setSelectedSeq,
8449
+ onHover: setHoveredSeq,
8450
+ onHoverTurn: setHoverTurn,
8451
+ onPickTurn: (turn) => {
8452
+ setGranularity("turn");
8453
+ setFocusTurn(turn);
8454
+ },
8455
+ onFocusTurnHandled: () => {
8456
+ setFocusTurn(null);
8457
+ }
8458
+ }, sessionId), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RequestDetail, {
8459
+ request: activeReq,
8460
+ prev: trendMode === "delta" && activeIdx >= 0 ? activeIdx > 0 ? displayRequests[activeIdx - 1] : null : void 0,
8461
+ /* v8 ignore next 1 -- RequestDetail renders only when
8462
+ displayRequests.length > 0, which forces activeReq
8463
+ non-null via the activeIdx fallback above. */
8464
+ marker: activeReq !== null ? markerOf(activeReq) : void 0,
8465
+ brief,
8466
+ convOf,
8467
+ onLocate: locateNode,
8468
+ hoverKey: trendHoverCat
8469
+ })] })]
8470
+ });
8471
+ const browserCard = /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ContextBrowser, {
8472
+ data,
8473
+ headers,
8474
+ convNodes,
8475
+ fetchContent,
8476
+ fetchHeader,
8477
+ previewSeq: hoveredSeq,
8478
+ pinSeq: pinnedReq !== null ? pinnedReq.seq : null,
8479
+ hoverKey: hoverCat,
8480
+ onHoverKey: setHoverCat,
8481
+ onOpenCat: setFocusCat,
8482
+ nodeFocus,
8483
+ onNodeFocusHandled: clearNodeFocus,
8484
+ loadImage,
8485
+ detailState: source.detailState,
8486
+ onDetailRetry: source.retryDetail
8487
+ });
8264
8488
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
8265
8489
  className: "lc-root",
8266
8490
  ref: rootRef,
@@ -8268,7 +8492,7 @@ window.__ModuleLoader__.load({
8268
8492
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
8269
8493
  className: "lc-cols lc-head",
8270
8494
  children: [
8271
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(StatsContext, {
8495
+ inSidebar ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(StatsContext, {
8272
8496
  counts: data.counts ?? countsOfRecords(requests, events),
8273
8497
  toolCalls: data.toolCalls,
8274
8498
  images: data.images,
@@ -8280,141 +8504,31 @@ window.__ModuleLoader__.load({
8280
8504
  timing: data.timing ?? null,
8281
8505
  locale: activeLocale
8282
8506
  }),
8283
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PluginInfo, {})
8507
+ inSidebar ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PluginInfo, {})
8284
8508
  ]
8285
8509
  }),
8286
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
8287
- className: "lc-cols",
8288
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
8510
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
8511
+ className: "lc-cols lc-cols-main",
8512
+ children: inSidebar ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
8513
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
8514
+ className: "lc-col",
8515
+ children: compositionCard
8516
+ }),
8517
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
8518
+ className: "lc-col lc-col-browser",
8519
+ children: browserCard
8520
+ }),
8521
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
8522
+ className: "lc-col",
8523
+ children: trendCard
8524
+ })
8525
+ ] }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
8289
8526
  className: "lc-col",
8290
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(CurrentComposition, {
8291
- head,
8292
- subtitle,
8293
- hoverKey: hoverCat,
8294
- onHoverKey: setHoverCat
8295
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
8296
- className: "lc-card",
8297
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
8298
- className: "lc-card-title",
8299
- children: [
8300
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
8301
- className: "lc-card-title-text",
8302
- children: t("trend.title")
8303
- }),
8304
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
8305
- className: "lc-gran lc-trend-adaptive",
8306
- role: "group",
8307
- title: t("trend.adaptiveHint"),
8308
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
8309
- type: "button",
8310
- className: "lc-gran-btn" + (adaptive ? " lc-gran-on" : ""),
8311
- onClick: () => {
8312
- setAdaptive((on) => !on);
8313
- },
8314
- children: t("trend.adaptive")
8315
- })
8316
- }),
8317
- focusCat !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
8318
- className: "lc-card-sub",
8319
- children: t("trend.focus", { cat: kit.catLabel(focusCat) })
8320
- }) : null,
8321
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
8322
- className: "lc-trend-ctl",
8323
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
8324
- className: "lc-gran",
8325
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
8326
- className: "lc-gran-btn" + (granularity === "step" ? " lc-gran-on" : ""),
8327
- onClick: () => {
8328
- setGranularity("step");
8329
- },
8330
- children: t("gran.step")
8331
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
8332
- className: "lc-gran-btn" + (granularity === "turn" ? " lc-gran-on" : ""),
8333
- onClick: () => {
8334
- setGranularity("turn");
8335
- },
8336
- children: t("gran.turn")
8337
- })]
8338
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
8339
- className: "lc-gran",
8340
- title: t("gran.modeHint"),
8341
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
8342
- className: "lc-gran-btn" + (trendMode === "total" ? " lc-gran-on" : ""),
8343
- onClick: () => {
8344
- setTrendMode("total");
8345
- },
8346
- children: t("gran.total")
8347
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
8348
- className: "lc-gran-btn" + (trendMode === "delta" ? " lc-gran-on" : ""),
8349
- onClick: () => {
8350
- setTrendMode("delta");
8351
- },
8352
- children: t("gran.delta")
8353
- })]
8354
- })]
8355
- })
8356
- ]
8357
- }), displayRequests.length === 0 ? detailReady ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
8358
- className: "lc-empty",
8359
- children: t("trend.empty")
8360
- }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DetailNote, {
8361
- state: source.detailState === "failed" ? "failed" : "loading",
8362
- onRetry: source.retryDetail
8363
- }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(TrendChart, {
8364
- requests: displayRequests,
8365
- markers,
8366
- selectedSeq: pinnedReq ? pinnedReq.seq : null,
8367
- hoveredSeq,
8368
- activeTurn,
8369
- granularity,
8370
- mode: trendMode,
8371
- focusTurn,
8372
- hoverCat: trendHoverCat,
8373
- focusCat,
8374
- adaptive,
8375
- onSelect: setSelectedSeq,
8376
- onHover: setHoveredSeq,
8377
- onHoverTurn: setHoverTurn,
8378
- onPickTurn: (turn) => {
8379
- setGranularity("turn");
8380
- setFocusTurn(turn);
8381
- },
8382
- onFocusTurnHandled: () => {
8383
- setFocusTurn(null);
8384
- }
8385
- }, sessionId), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RequestDetail, {
8386
- request: activeReq,
8387
- prev: trendMode === "delta" && activeIdx >= 0 ? activeIdx > 0 ? displayRequests[activeIdx - 1] : null : void 0,
8388
- /* v8 ignore next 1 -- RequestDetail renders only when
8389
- displayRequests.length > 0, which forces activeReq
8390
- non-null via the activeIdx fallback above. */
8391
- marker: activeReq !== null ? markerOf(activeReq) : void 0,
8392
- brief,
8393
- convOf,
8394
- onLocate: locateNode,
8395
- hoverKey: trendHoverCat
8396
- })] })]
8397
- })]
8527
+ children: [compositionCard, trendCard]
8398
8528
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
8399
8529
  className: "lc-col lc-col-browser",
8400
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ContextBrowser, {
8401
- data,
8402
- headers,
8403
- convNodes,
8404
- fetchContent,
8405
- fetchHeader,
8406
- previewSeq: hoveredSeq,
8407
- pinSeq: pinnedReq !== null ? pinnedReq.seq : null,
8408
- hoverKey: hoverCat,
8409
- onHoverKey: setHoverCat,
8410
- onOpenCat: setFocusCat,
8411
- nodeFocus,
8412
- onNodeFocusHandled: clearNodeFocus,
8413
- loadImage,
8414
- detailState: source.detailState,
8415
- onDetailRetry: source.retryDetail
8416
- })
8417
- })]
8530
+ children: browserCard
8531
+ })] })
8418
8532
  }),
8419
8533
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
8420
8534
  className: "lc-cols",
@@ -8560,6 +8674,99 @@ window.__ModuleLoader__.load({
8560
8674
  };
8561
8675
  }
8562
8676
  //#endregion
8677
+ //#region src/client/sidebar.ts
8678
+ /**
8679
+ * The right Sidebar's Context tab (dsh 0.1.5-alpha.1+).
8680
+ *
8681
+ * The tab reuses the Context conversation-view component VERBATIM: the
8682
+ * `sidebar.right.pane.tab` seat is session-scoped and delivers the same
8683
+ * framework standard kit (`sessionId`, `useProjection`, `useChat`, the locale
8684
+ * `t` seat) the `conversation.view` seat does, so the panel and the tab are
8685
+ * one component with one data path. The tab type contributes a guide entry, so
8686
+ * the sidebar's guide page offers "Context" and picking it opens the panel —
8687
+ * the product's own path, exactly as the shipped Files type does.
8688
+ *
8689
+ * OPTIONAL BY CONTRACT. `ctx.sidebarRightTabs` and the seat exist only from
8690
+ * dsh 0.1.5-alpha.1; the registration therefore rides a DEFERRED inject (the
8691
+ * plugin's hard injects stay `slots` + `locale`), so on every older supported
8692
+ * line the callback never fires, the plugin fiber never pends, and nothing is
8693
+ * registered. The registry is re-proved structurally and the whole
8694
+ * registration is guarded: a foreign or hostile registry (a throwing
8695
+ * `register`, a taken id/kind) leaves the sidebar without the tab instead of
8696
+ * taking the browser down.
8697
+ *
8698
+ * @module dsh-context/client/sidebar
8699
+ */
8700
+ /** The tab type's identity in the sidebar's tab system (also its body-seat key). */
8701
+ const SIDEBAR_CONTEXT_ID = "dsh-context";
8702
+ /**
8703
+ * The kind `openTab` names. Namespaced rather than the bare `context`: the
8704
+ * registry THROWS when a kind collides with another registration in a
8705
+ * non-coexisting band, and a foreign plugin may well own `context`.
8706
+ */
8707
+ const SIDEBAR_CONTEXT_KIND = "dsh-context";
8708
+ /** The guide box's position: after the shipped Files entry (order 10). */
8709
+ const GUIDE_ORDER = 20;
8710
+ /**
8711
+ * The guide box's glyph, read defensively: the icon is OPTIONAL in the entry,
8712
+ * and a primitives module that does not serve it — or whose namespace THROWS
8713
+ * on the read (an interop/mock shape does exactly that) — must cost the glyph,
8714
+ * never the whole tab.
8715
+ * @returns the icon component, or undefined to register the tab without one.
8716
+ */
8717
+ function guideIcon() {
8718
+ try {
8719
+ return _deepseek_ai_dsh_client_ui_primitives.IconContextInjectionOutline16;
8720
+ } catch {
8721
+ return;
8722
+ }
8723
+ }
8724
+ /**
8725
+ * Register the Context tab type and its body on the right Sidebar, if — and
8726
+ * only if — this harness serves the sidebar tab registry.
8727
+ * @param ctx - client root context carrying `slots` and the locale service.
8728
+ * @param view - the Context view component factory result (the same one the
8729
+ * conversation tab mounts).
8730
+ * @param t - the plugin-namespace translate; the label thunks read the active
8731
+ * locale at call time, so a language switch relabels the guide entry.
8732
+ * @param ns - the plugin's locale namespace, put on the body registration so
8733
+ * the framework synthesizes the `t` seat for the panel too.
8734
+ */
8735
+ function watchSidebarContextTab(ctx, view, t, ns) {
8736
+ ctx.inject(["sidebarRightTabs"], (raw) => {
8737
+ const injected = raw;
8738
+ try {
8739
+ const tabs = injected.sidebarRightTabs;
8740
+ if (tabs === void 0 || typeof tabs.register !== "function") return;
8741
+ const disposeType = tabs.register({
8742
+ id: SIDEBAR_CONTEXT_ID,
8743
+ kind: SIDEBAR_CONTEXT_KIND,
8744
+ title: () => t("tab"),
8745
+ guide: [{
8746
+ order: GUIDE_ORDER,
8747
+ title: () => t("tab"),
8748
+ description: () => t("sidebar.guideDescription"),
8749
+ icon: guideIcon()
8750
+ }]
8751
+ });
8752
+ const disposeBody = injected.slots.inject("sidebar.right.pane.tab", () => injected.slots.register({
8753
+ name: "sidebar.right.pane.tab",
8754
+ key: SIDEBAR_CONTEXT_ID,
8755
+ locale: ns
8756
+ }, (props) => view({
8757
+ ...props,
8758
+ host: "sidebar"
8759
+ })));
8760
+ return () => {
8761
+ disposeType();
8762
+ if (typeof disposeBody === "function") disposeBody();
8763
+ };
8764
+ } catch {
8765
+ return;
8766
+ }
8767
+ });
8768
+ }
8769
+ //#endregion
8563
8770
  //#region src/client/viewkit.ts
8564
8771
  function makeViewKit(t) {
8565
8772
  const { eventLabel, eventAt } = makeEventText(t);
@@ -8576,7 +8783,7 @@ window.__ModuleLoader__.load({
8576
8783
  }
8577
8784
  //#endregion
8578
8785
  //#region \0dsh-global-css:/home/runner/work/dsh-context/dsh-context/src/client/styles/base.css.mjs
8579
- 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;container:lc-card/inline-size}.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-trend-adaptive{flex:none;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:min(280px,100%)}.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{container:lc-head-row/inline-size}.lc-head>.lc-card{flex:1 1 0;min-width:0}@container lc-head-row (width<=1045px){.lc-head>.lc-card{flex-basis:40%}}@container lc-head-row (width<=480px){.lc-head>.lc-card{flex-basis:100%}}.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])),[data-conversation-scroll]:has(.lc-root,.lc-modal-backdrop)~[data-width-handle]{display:none}";
8786
+ 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}[data-sidebar-right-open] .lc-root,[data-sidebar-right-float-host] .lc-root{height:auto;padding:0;overflow:visible}.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;container:lc-card/inline-size}.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-trend-adaptive{flex:none;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:min(360px,100%)}.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{container:lc-head-row/inline-size}.lc-head>.lc-card{flex:1 1 0;min-width:0}@container lc-head-row (width<=1045px){.lc-head>.lc-card{flex-basis:40%}}@container lc-head-row (width<=480px){.lc-head>.lc-card{flex-basis:100%}}.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])),[data-conversation-scroll]:has(.lc-root,.lc-modal-backdrop)~[data-width-handle]{display:none}";
8580
8787
  const tagId$13 = "dsh-context/base.css";
8581
8788
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$13) + "]") === null) {
8582
8789
  const tag = document.createElement("style");
@@ -8775,6 +8982,7 @@ window.__ModuleLoader__.load({
8775
8982
  label: () => t("tab")
8776
8983
  }, (props) => (0, react.createElement)(ContextView, props));
8777
8984
  });
8985
+ watchSidebarContextTab(ctx, ContextView, t, NS);
8778
8986
  const ContextJump = makeContextJumpButton(kit);
8779
8987
  ctx.slots.inject("conversation.chat.assistant-actions", () => {
8780
8988
  return ctx.slots.register({
package/lib/index.d.ts CHANGED
@@ -72,6 +72,27 @@ interface TimelineState {
72
72
  surface: SurfaceNode[];
73
73
  sums: Record<Category, number>;
74
74
  systemTokens: number;
75
+ /**
76
+ * The live system-prompt nodes, oldest first — a V3 log's `system/message`
77
+ * surface nodes, or the single entry a V0/V2 `request/header.header.system`
78
+ * envelope defines. `systemTokens` is the LAST entry with tokens > 0 (the
79
+ * harness's own "last nonempty surviving system" rule), so an empty dormant
80
+ * node keeps its position without clearing the prompt. Bounded by
81
+ * SYSTEM_NODES_MAX. ABSENT on rows folded before this field existed — the
82
+ * wire then serves no `systems` and the client falls back to the header
83
+ * epoch's own envelope figure.
84
+ */
85
+ systems?: SystemPromptNode[];
86
+ /**
87
+ * Whether `systems` was built from the V0/V2 request ENVELOPE
88
+ * (`header.system`) rather than from V3 `system/message` events. Only then
89
+ * may a system-less header CLEAR the list: its canonical V0 meaning is
90
+ * "this request has no system prompt", while a V3 header never carries one
91
+ * (its prompt lives in the message history). Absent = log-sourced, and
92
+ * never materialized as an `undefined`-valued property (plain-JSON
93
+ * precondition — see the note above `model`).
94
+ */
95
+ systemsFromHeader?: true;
75
96
  toolsTokens: number;
76
97
  /**
77
98
  * The projection-cache precondition is plain JSON: a property whose value
@@ -221,6 +242,19 @@ declare module '@deepseek-ai/dsh-session-projection/types' {
221
242
  }
222
243
  }
223
244
  type Category = 'user' | 'inject' | 'assistant' | 'tool';
245
+ /**
246
+ * One live system-prompt node (Snapshot.systems) — the harness models the
247
+ * system prompt as a surface node, so its TEXT is fetched on demand from the
248
+ * event at `seq`: a V3 `system/message` event, or the V0/V2 `request/header`
249
+ * whose envelope carried `header.system`. `tokens` is the node's heuristic
250
+ * price (0 for a dormant empty node, which the harness reads as "no system
251
+ * prompt"); the effective figure is the LAST node with `tokens > 0`.
252
+ */
253
+ interface SystemPromptNode {
254
+ seq: number;
255
+ time: number;
256
+ tokens: number;
257
+ }
224
258
  /**
225
259
  * The stats board's count figures, precomputed host-side over the RETAINED
226
260
  * request/event records (the same set the detail payload serves). Carried by
@@ -316,6 +350,13 @@ interface Snapshot {
316
350
  * one — clients treat absence as an empty timing card).
317
351
  */
318
352
  timing?: TimingTotals;
353
+ /**
354
+ * The live system-prompt nodes, oldest first — the browser's per-step source
355
+ * for the System section. Absent when the log carried no system prompt, and
356
+ * on older plugin builds (the client then falls back to the header epoch's
357
+ * own `systemTokens`, the pre-V3 shape).
358
+ */
359
+ systems?: SystemPromptNode[];
319
360
  /**
320
361
  * The served live surface: the newest `maxNodes` tail PLUS every live inject node older than the tail (injections land first and are
321
362
  * few,
@@ -450,9 +491,13 @@ interface ToolTimingTotals {
450
491
  }
451
492
  /**
452
493
  * Whole-session timing totals, host-folded from the durable `step/start` /
453
- `step/end` / `assistant/chunk` / `tool/call` / `tool/result` lifecycle
454
- * (running totals over the COMPLETE session log — the same never-trimmed
455
- * framing as `cost`). Durations are wall-clock milliseconds: `wallMs` sums
494
+ * `step/end` / `tool/call` / `tool/result` lifecycle plus the model call's
495
+ * first token (running totals over the COMPLETE session log — the same
496
+ * never-trimmed framing as `cost`). The first token comes from a V0
497
+ * `assistant/chunk` delta or from the call's own embedded stream
498
+ * (`assistant/message.data.stream` / `assistant/attempt.data.stream`, the
499
+ * V2+ settlement) — whichever the log carries, matching the harness's own
500
+ * session-stats fold. Durations are wall-clock milliseconds: `wallMs` sums
456
501
  * whole steps, `ttftMs` the step-start → first-token slice (the model wait)
457
502
  * and `genMs` the first-token → assistant-message slice (the generation) —
458
503
  * both only over calls whose stream carried a token delta, `toolsMs` the sum
package/lib/index.js CHANGED
@@ -308,6 +308,30 @@ function estimateSystemTokens(text) {
308
308
  if (typeof text !== "string" || text.length === 0) return 0;
309
309
  return Math.ceil(text.length / CHARS_PER_TOKEN$1) + ROLE_OVERHEAD$1;
310
310
  }
311
+ /**
312
+ * Price a `system/message` payload's content exactly like the harness's
313
+ * token-meter (`estimateSystemMessage`): text density over EVERY text block
314
+ * plus role framing, with no per-block overhead — an adapter serializes the
315
+ * prompt as plain text, so a text block costs its characters alone. Any other
316
+ * block (or a hostile element) falls back to its JSON length. 0 for empty
317
+ * content, which the harness reads as "no system prompt".
318
+ */
319
+ function estimateSystemContent(blocks) {
320
+ if (!Array.isArray(blocks) || blocks.length === 0) return 0;
321
+ let characters = 0;
322
+ for (const block of blocks) {
323
+ const text = block !== null && typeof block === "object" && block.type === "text" ? block.text : void 0;
324
+ if (typeof text === "string") {
325
+ characters += text.length;
326
+ continue;
327
+ }
328
+ try {
329
+ const json = JSON.stringify(block);
330
+ if (typeof json === "string") characters += json.length;
331
+ } catch {}
332
+ }
333
+ return Math.ceil(characters / CHARS_PER_TOKEN$1) + ROLE_OVERHEAD$1;
334
+ }
311
335
  //#endregion
312
336
  //#region src/shared/imageTokens.ts
313
337
  /**
@@ -565,6 +589,111 @@ function isInjection(source) {
565
589
  return source !== null && source !== void 0 && (typeof source.kind === "string" && source.kind !== "" && source.kind !== "user" || typeof source.form === "string");
566
590
  }
567
591
  //#endregion
592
+ //#region src/host/logShapes.ts
593
+ /**
594
+ * Shape-driven readers over the durable session-event vocabulary — the ONE
595
+ * place the plugin reconciles the two supported log generations:
596
+ *
597
+ * - V0 (dsh 0.1.2-rc.1): `request/header.header.system`, `assistant/chunk`
598
+ * stream events, `SurfaceOp { start, end }`, `tool/code-dispatch`.
599
+ * - V3 (dsh 0.1.5-alpha.1+): `system/message` surface nodes,
600
+ * `assistant/message.data.stream` / `assistant/attempt.data.stream`,
601
+ * `SurfaceOp { startSeq, endSeq }`, `tool/ptc-dispatch`.
602
+ *
603
+ * The fold reads SHAPES, never a detected harness version: a session log is
604
+ * written by exactly one generation, the two spellings are mutually
605
+ * exclusive within it, and a deployment's version probe can be wrong (a
606
+ * healed profile mirror may name a different release than the running
607
+ * harness). Every reader is total over untrusted input — a malformed record
608
+ * yields "nothing here", never a throw (the projection registry drives the
609
+ * fold without an error boundary; one throw stalls the unit's push feed and
610
+ * the browser waits on "loading" forever).
611
+ *
612
+ * @module dsh-context/host/log-shapes
613
+ */
614
+ /**
615
+ * Whether one raw stream chunk carries a token delta — the first-token marker
616
+ * both generations share. Mirrors dsh-llm's `isTokenDelta` (a non-empty text
617
+ * or reasoning fragment, or any Tool-call delta carrying arguments or a name);
618
+ * a malformed chunk is simply not a token.
619
+ */
620
+ function isTokenChunk(chunk) {
621
+ if (chunk === null || typeof chunk !== "object") return false;
622
+ const c = chunk;
623
+ switch (c.type) {
624
+ case "text-delta":
625
+ case "reasoning-delta": return typeof c.text === "string" && c.text !== "";
626
+ case "tool-call-delta": return typeof c.argumentsDelta === "string" && c.argumentsDelta !== "" || c.name !== void 0;
627
+ default: return false;
628
+ }
629
+ }
630
+ /**
631
+ * The first token's instant inside one PACKED delta run (`text-chunks` /
632
+ * `reasoning-chunks` / `tool-call-chunks`): the run's base time plus the
633
+ * accumulated inter-member deltas, taken at the first qualifying member —
634
+ * a name-bearing Tool-call run starts at its first member. Mirrors dsh-llm's
635
+ * `runFirstTokenTime`; a non-finite base or delta yields undefined rather
636
+ * than a NaN instant.
637
+ */
638
+ function runFirstTokenTime(record) {
639
+ const time0 = record.time0;
640
+ if (typeof time0 !== "number" || !Number.isFinite(time0)) return void 0;
641
+ if (record.type === "tool-call-chunks" && record.name !== void 0) return time0;
642
+ const fragments = record.type === "tool-call-chunks" ? record.args : record.texts;
643
+ if (!Array.isArray(fragments)) return void 0;
644
+ const dt = Array.isArray(record.dt) ? record.dt : [];
645
+ let time = time0;
646
+ for (const [index, fragment] of fragments.entries()) {
647
+ if (index > 0) {
648
+ const step = dt[index - 1];
649
+ if (typeof step !== "number" || !Number.isFinite(step)) return void 0;
650
+ time += step;
651
+ }
652
+ if (typeof fragment === "string" && fragment !== "") return time;
653
+ }
654
+ }
655
+ /**
656
+ * The first token's instant inside an embedded assistant stream
657
+ * (`assistant/message.data.stream`, `assistant/attempt.data.stream` — the V2+
658
+ * settlement that replaced the V0 `assistant/chunk` events), or undefined
659
+ * when the stream carries no token. Mirrors dsh-llm's
660
+ * `assistantStreamFirstTokenTime` over the compact record union.
661
+ */
662
+ function firstTokenTimeOfStream(stream) {
663
+ if (!Array.isArray(stream)) return void 0;
664
+ for (const record of stream) {
665
+ if (record === null || typeof record !== "object") continue;
666
+ const r = record;
667
+ if (r.type === "chunk") {
668
+ const time = r.time;
669
+ if (typeof time === "number" && Number.isFinite(time) && isTokenChunk(r.chunk)) return time;
670
+ continue;
671
+ }
672
+ const time = runFirstTokenTime(r);
673
+ if (time !== void 0) return time;
674
+ }
675
+ }
676
+ /**
677
+ * The inclusive surface range a replacement op covers, or null for `append`
678
+ * and for any unrecognized/hostile op (which the fold treats as an append).
679
+ * Reads BOTH endpoint spellings: V3's `startSeq`/`endSeq` first, then V0's
680
+ * `start`/`end` — each accepted only as a finite number, so a hostile op
681
+ * with one good and one malformed endpoint degrades to append.
682
+ */
683
+ function replaceRangeOf(surfaceOp) {
684
+ if (surfaceOp === null || typeof surfaceOp !== "object") return null;
685
+ const op = surfaceOp;
686
+ if (op.op !== "replace") return null;
687
+ const start = typeof op.startSeq === "number" ? op.startSeq : op.start;
688
+ const end = typeof op.endSeq === "number" ? op.endSeq : op.end;
689
+ if (typeof start !== "number" || !Number.isFinite(start)) return null;
690
+ if (typeof end !== "number" || !Number.isFinite(end)) return null;
691
+ return {
692
+ start,
693
+ end
694
+ };
695
+ }
696
+ //#endregion
568
697
  //#region src/shared/fileOps.ts
569
698
  /** Parse a call's raw JSON arguments; non-string/malformed/non-record inputs yield null. */
570
699
  function parseCallArgs(raw) {
@@ -873,6 +1002,24 @@ function bumpDetailRev(st) {
873
1002
  st.detailRev = (st.detailRev ?? 0) + 1;
874
1003
  }
875
1004
  /**
1005
+ * Bound on the live system-prompt nodes (TimelineState.systems). The
1006
+ * effective figure is the LAST nonempty node, so dropping the oldest can only
1007
+ * under-report a pathological log whose newest SYSTEM_NODES_MAX nodes are all
1008
+ * empty while an older one still carried text.
1009
+ */
1010
+ const SYSTEM_NODES_MAX = 8;
1011
+ /** The effective system-prompt price: the last nonempty node, else 0 (the harness's own rule). */
1012
+ function systemTokensOf(systems) {
1013
+ for (let i = systems.length - 1; i >= 0; i--) if (systems[i].tokens > 0) return systems[i].tokens;
1014
+ return 0;
1015
+ }
1016
+ /** Append one system-prompt node, bounding the list (see SYSTEM_NODES_MAX). */
1017
+ function pushSystem(st, node) {
1018
+ const systems = [...st.systems ?? [], node];
1019
+ st.systems = systems.length > SYSTEM_NODES_MAX ? systems.slice(-8) : systems;
1020
+ st.systemTokens = systemTokensOf(st.systems);
1021
+ }
1022
+ /**
876
1023
  * Bound on the buffered nested Code-Mode ops (TimelineState.pendingCodeOps)
877
1024
  * — a hostile log that dispatches without settling the parent run_code
878
1025
  * cannot grow the persisted state past this.
@@ -919,6 +1066,35 @@ function archiveRemoved(st, removed, goneSeq) {
919
1066
  });
920
1067
  }
921
1068
  /**
1069
+ * Remove every live surface node whose seq the replacement claims, keeping the
1070
+ * per-category sums equal to the surviving nodes and archiving the removals.
1071
+ * Removal follows the SEQ list, not the declared range: pruned replacement
1072
+ * nodes keep their own seqs beyond the range end, so a range-based removal
1073
+ * would leave them behind and overcount. Returns the removed nodes.
1074
+ */
1075
+ function removeSurfaceSeqs(st, claimed, goneSeq) {
1076
+ if (claimed.size === 0) return [];
1077
+ const kept = [];
1078
+ const removed = [];
1079
+ for (const n of st.surface) if (claimed.has(n.seq)) {
1080
+ st.sums[n.cat] -= n.tokens;
1081
+ removed.push(n);
1082
+ } else kept.push(n);
1083
+ archiveRemoved(st, removed, goneSeq);
1084
+ st.surface = kept;
1085
+ return removed;
1086
+ }
1087
+ /**
1088
+ * The message nested under an event payload's `message` field
1089
+ * (`system/message`, `assistant/message`, `tool/result`) — read structurally
1090
+ * rather than through `deriveEventMessage`, whose 0.1.2-rc.1 generation knows
1091
+ * nothing of the V3 `system/message` variant. A malformed payload reads null.
1092
+ */
1093
+ function messageOf(data) {
1094
+ const message = data?.message;
1095
+ return message !== null && typeof message === "object" ? message : null;
1096
+ }
1097
+ /**
922
1098
  * The first full text block, recursing through nested content blocks (a tool
923
1099
  * result wraps its text in a `tool-result` block). Unlike `firstText` this
924
1100
  * must NOT truncate/normalize: the skill name is matched off the raw
@@ -1002,18 +1178,10 @@ function applySurface(st, ev, type, data, message) {
1002
1178
  const shadowEventSeq = st.pendingShadowEventSeq;
1003
1179
  delete st.pendingShadowedSeqs;
1004
1180
  delete st.pendingShadowEventSeq;
1005
- const op = ev.surfaceOp;
1006
- if (op !== null && typeof op === "object" && op.op === "replace") {
1181
+ const op = replaceRangeOf(ev.surfaceOp);
1182
+ if (op !== null) {
1007
1183
  if (Array.isArray(shadowedSeqs) && shadowedSeqs.length > 0) {
1008
- const shadowed = new Set(shadowedSeqs);
1009
- const kept = [];
1010
- const removed = [];
1011
- for (const n of st.surface) if (shadowed.has(n.seq)) {
1012
- st.sums[n.cat] -= n.tokens;
1013
- removed.push(n);
1014
- } else kept.push(n);
1015
- archiveRemoved(st, removed, ev.seq);
1016
- st.surface = kept;
1184
+ const removed = removeSurfaceSeqs(st, new Set(shadowedSeqs), ev.seq);
1017
1185
  st.sums[cat] += node.tokens;
1018
1186
  st.surface.push(node);
1019
1187
  if (shadowEventSeq !== void 0) {
@@ -1138,21 +1306,6 @@ function durOf(from, to) {
1138
1306
  return Math.max(0, to - from);
1139
1307
  }
1140
1308
  /**
1141
- * Whether a stream chunk carries a non-empty token delta — the first-token
1142
- * marker the TTFT fold waits for (the same rule as the harness's own
1143
- * session-stats fold). Shape-guarded: a malformed chunk is just not a token.
1144
- */
1145
- function isTokenDelta(chunk) {
1146
- if (chunk === null || typeof chunk !== "object") return false;
1147
- const c = chunk;
1148
- switch (c.type) {
1149
- case "text-delta":
1150
- case "reasoning-delta": return typeof c.text === "string" && c.text !== "";
1151
- case "tool-call-delta": return typeof c.argumentsDelta === "string" && c.argumentsDelta !== "" || c.name !== void 0;
1152
- default: return false;
1153
- }
1154
- }
1155
- /**
1156
1309
  * The fold's private timing accumulator: created on first use, and CLONED on
1157
1310
  * every later ensure() (see `applyTimeline`) — the object left in the
1158
1311
  * persisted previous state is never written into in place.
@@ -1225,7 +1378,20 @@ function applyTimeline(state, event, bounds) {
1225
1378
  const tools = Array.isArray(header.tools) ? header.tools : [];
1226
1379
  const s = ensure();
1227
1380
  s.toolsTokens = estimateToolsTotal(tools);
1228
- s.systemTokens = estimateSystemTokens(header.system);
1381
+ const systemText = header.system;
1382
+ if (typeof systemText === "string" && systemText !== "") {
1383
+ s.systems = [{
1384
+ seq: event.seq,
1385
+ time: event.time,
1386
+ tokens: estimateSystemTokens(systemText)
1387
+ }];
1388
+ s.systemsFromHeader = true;
1389
+ s.systemTokens = systemTokensOf(s.systems);
1390
+ } else if (s.systemsFromHeader === true) {
1391
+ s.systems = [];
1392
+ delete s.systemsFromHeader;
1393
+ s.systemTokens = 0;
1394
+ }
1229
1395
  if (header.config && typeof header.config.model === "string") s.model = header.config.model;
1230
1396
  if (header.config && typeof header.config.provider === "string") s.provider = header.config.provider;
1231
1397
  if ((data?.reason === "change" || data?.reason === "resume") && s.model && s.lastModel && s.model !== s.lastModel) {
@@ -1241,6 +1407,25 @@ function applyTimeline(state, event, bounds) {
1241
1407
  if (s.model) s.lastModel = s.model;
1242
1408
  break;
1243
1409
  }
1410
+ case "system/message": {
1411
+ const s = ensure();
1412
+ delete s.pendingShadowedSeqs;
1413
+ delete s.pendingShadowEventSeq;
1414
+ const op = replaceRangeOf(event.surfaceOp);
1415
+ if (op !== null) {
1416
+ s.systems = (s.systems ?? []).filter((n) => n.seq < op.start || n.seq > op.end);
1417
+ const claimed = /* @__PURE__ */ new Set();
1418
+ for (const n of s.surface) if (n.seq >= op.start && n.seq <= op.end) claimed.add(n.seq);
1419
+ if (removeSurfaceSeqs(s, claimed, event.seq).length > 0) bumpDetailRev(s);
1420
+ }
1421
+ delete s.systemsFromHeader;
1422
+ pushSystem(s, {
1423
+ seq: event.seq,
1424
+ time: event.time,
1425
+ tokens: estimateSystemContent(messageOf(data)?.content)
1426
+ });
1427
+ break;
1428
+ }
1244
1429
  case "request/context": {
1245
1430
  const s = ensure();
1246
1431
  if (data && typeof data.contextWindow === "number") s.contextWindow = data.contextWindow;
@@ -1259,7 +1444,8 @@ function applyTimeline(state, event, bounds) {
1259
1444
  };
1260
1445
  }
1261
1446
  break;
1262
- case "tool/code-dispatch": {
1447
+ case "tool/code-dispatch":
1448
+ case "tool/ptc-dispatch": {
1263
1449
  const rootCallId = data?.rootCallId;
1264
1450
  const name = data?.name;
1265
1451
  if (typeof rootCallId === "string" && typeof name === "string") {
@@ -1277,7 +1463,7 @@ function applyTimeline(state, event, bounds) {
1277
1463
  case "assistant/chunk": {
1278
1464
  const start = state.stepStart;
1279
1465
  if (start === void 0 || start.firstToken !== void 0) return state;
1280
- if (!isTokenDelta(data?.chunk)) return state;
1466
+ if (!isTokenChunk(data?.chunk)) return state;
1281
1467
  const s = ensure();
1282
1468
  s.stepStart = {
1283
1469
  time: start.time,
@@ -1285,6 +1471,18 @@ function applyTimeline(state, event, bounds) {
1285
1471
  };
1286
1472
  break;
1287
1473
  }
1474
+ case "assistant/attempt": {
1475
+ const start = state.stepStart;
1476
+ if (start === void 0 || start.firstToken !== void 0) return state;
1477
+ const first = firstTokenTimeOfStream(data?.stream);
1478
+ if (first === void 0) return state;
1479
+ const s = ensure();
1480
+ s.stepStart = {
1481
+ time: start.time,
1482
+ firstToken: first
1483
+ };
1484
+ break;
1485
+ }
1288
1486
  case "step/start": {
1289
1487
  const s = ensure();
1290
1488
  s.stepStart = { time: event.time };
@@ -1410,9 +1608,12 @@ function applyTimeline(state, event, bounds) {
1410
1608
  const timing = ensureTiming(s);
1411
1609
  timing.calls += 1;
1412
1610
  const stepStart = state.stepStart;
1413
- if (stepStart !== void 0 && stepStart.firstToken !== void 0) {
1414
- timing.ttftMs += durOf(stepStart.time, stepStart.firstToken);
1415
- timing.genMs += durOf(stepStart.firstToken, event.time);
1611
+ if (stepStart !== void 0) {
1612
+ const firstToken = stepStart.firstToken ?? firstTokenTimeOfStream(data?.stream);
1613
+ if (firstToken !== void 0) {
1614
+ timing.ttftMs += durOf(stepStart.time, firstToken);
1615
+ timing.genMs += durOf(firstToken, event.time);
1616
+ }
1416
1617
  }
1417
1618
  const asstMsg = deriveEventMessage(event);
1418
1619
  applySurface(s, event, event.type, data, asstMsg);
@@ -1514,6 +1715,7 @@ function headFieldsOf(state) {
1514
1715
  tools
1515
1716
  };
1516
1717
  }
1718
+ if (state.systems !== void 0 && state.systems.length > 0) result.systems = state.systems.map((n) => ({ ...n }));
1517
1719
  return result;
1518
1720
  }
1519
1721
  /**
@@ -1951,6 +2153,12 @@ const surfaceNodeSchema = z.object({
1951
2153
  skill: z.string().optional(),
1952
2154
  calls: z.array(z.string()).optional()
1953
2155
  }).strict();
2156
+ /** One live system-prompt node (shared/types.ts SystemPromptNode). */
2157
+ const systemPromptNodeSchema = z.object({
2158
+ seq: z.number().int().nonnegative(),
2159
+ time: z.number(),
2160
+ tokens: z.number().int().nonnegative()
2161
+ }).strict();
1954
2162
  const requestRecordSchema = z.object({
1955
2163
  turn: z.number().optional(),
1956
2164
  step: z.number().optional(),
@@ -2097,6 +2305,7 @@ const contextTimelineSchema = z.object({
2097
2305
  pro: costFamilySchema.optional()
2098
2306
  }).strict().optional(),
2099
2307
  timing: timingTotalsSchema.optional(),
2308
+ systems: z.array(systemPromptNodeSchema).optional(),
2100
2309
  nodes: z.array(surfaceNodeSchema).optional(),
2101
2310
  droppedNodes: z.number().int().nonnegative().optional(),
2102
2311
  archive: z.array(surfaceNodeSchema).optional(),
@@ -2120,6 +2329,8 @@ const timelineStateSchema = z.object({
2120
2329
  tool: z.number().int().nonnegative()
2121
2330
  }).strict(),
2122
2331
  systemTokens: z.number().int().nonnegative(),
2332
+ systems: z.array(systemPromptNodeSchema).optional(),
2333
+ systemsFromHeader: z.literal(true).optional(),
2123
2334
  toolsTokens: z.number().int().nonnegative(),
2124
2335
  model: z.string().optional(),
2125
2336
  provider: z.string().optional(),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-context",
3
- "version": "0.46.1",
3
+ "version": "0.47.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": {
@@ -57,13 +57,16 @@
57
57
  "@deepseek-ai/dsh-client-connection",
58
58
  "@deepseek-ai/dsh-client-locale",
59
59
  "@deepseek-ai/dsh-client-ui-conversation",
60
- "@deepseek-ai/dsh-client-ui-settings"
60
+ "@deepseek-ai/dsh-client-ui-settings",
61
+ "@deepseek-ai/dsh-client-ui-sidebar-right"
61
62
  ],
62
63
  "platform": "web"
63
64
  },
64
65
  "compatibility": {
65
66
  "dshReleases": {
66
- "0.1.2-rc.1": "compatible"
67
+ "0.1.2-rc.1": "compatible",
68
+ "0.1.3-alpha.2": "compatible",
69
+ "0.1.5-alpha.1": "compatible"
67
70
  }
68
71
  }
69
72
  },