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 +2 -1
- package/lib/client.js +380 -172
- package/lib/index.d.ts +48 -3
- package/lib/index.js +243 -32
- package/package.json +6 -3
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
|
|
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
|
|
1552
|
-
*
|
|
1553
|
-
* description and raw schema
|
|
1554
|
-
*
|
|
1555
|
-
*
|
|
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(
|
|
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
|
|
1577
|
-
* counterpart of the node fetcher above. One seq-anchored history
|
|
1578
|
-
* the
|
|
1579
|
-
*
|
|
1580
|
-
*
|
|
1581
|
-
* (history is immutable), and OLDER
|
|
1582
|
-
*
|
|
1583
|
-
* history face exists — the browser keeps a
|
|
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
|
|
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
|
|
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.
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
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.
|
|
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
|
|
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.
|
|
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
|
-
|
|
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(
|
|
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.
|
|
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.
|
|
6495
|
-
const nameText = "dsh-context (v0.
|
|
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.
|
|
8287
|
-
className: "lc-cols",
|
|
8288
|
-
children:
|
|
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: [
|
|
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:
|
|
8401
|
-
|
|
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(
|
|
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` / `
|
|
454
|
-
* (running totals over the COMPLETE session log — the same
|
|
455
|
-
* framing as `cost`).
|
|
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
|
|
1181
|
+
const op = replaceRangeOf(ev.surfaceOp);
|
|
1182
|
+
if (op !== null) {
|
|
1007
1183
|
if (Array.isArray(shadowedSeqs) && shadowedSeqs.length > 0) {
|
|
1008
|
-
const
|
|
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
|
-
|
|
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 (!
|
|
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
|
|
1414
|
-
|
|
1415
|
-
|
|
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.
|
|
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
|
},
|