dsh-context 0.53.2 → 0.53.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/client.js +225 -129
- package/lib/index.js +81 -12
- package/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -105,7 +105,7 @@ window.__ModuleLoader__.load({
|
|
|
105
105
|
"brief.more": "+{n}",
|
|
106
106
|
"brief.noInputs": "(无新增)",
|
|
107
107
|
"brief.locate": "在上下文浏览器中查看",
|
|
108
|
-
"jump.title": "
|
|
108
|
+
"jump.title": "在上下文视图中查看此轮",
|
|
109
109
|
"brief.turnTip": "这一轮开场时用户发送的消息;回合内的每一步都会回显同一条",
|
|
110
110
|
"brief.inputTip": "上一步回复之后、本次请求发出前新进入上下文的内容——通常是上一步所调工具的结果;每轮的第一步无新增",
|
|
111
111
|
"brief.replyTip": "这一步模型返回的内容(文本回复或工具调用);此步发起的调用,其结果会出现在下一柱的「输入」行",
|
|
@@ -471,7 +471,7 @@ window.__ModuleLoader__.load({
|
|
|
471
471
|
"brief.more": "+{n}",
|
|
472
472
|
"brief.noInputs": "(nothing new)",
|
|
473
473
|
"brief.locate": "Reveal in Context Browser",
|
|
474
|
-
"jump.title": "View this turn in the Context
|
|
474
|
+
"jump.title": "View this turn in the Context view",
|
|
475
475
|
"brief.turnTip": "The user message that opened this turn — every step of the turn recalls it",
|
|
476
476
|
"brief.inputTip": "What entered after the previous reply and before this request went out — usually the previous tool-call results; a turn opener has nothing new",
|
|
477
477
|
"brief.replyTip": "What the model returned on this step (a text reply or tool calls) — results of calls made here appear in the In row of the next bar",
|
|
@@ -5069,6 +5069,7 @@ window.__ModuleLoader__.load({
|
|
|
5069
5069
|
} : void 0;
|
|
5070
5070
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5071
5071
|
className: "lc-card",
|
|
5072
|
+
"data-lc-current": true,
|
|
5072
5073
|
children: [
|
|
5073
5074
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5074
5075
|
className: "lc-card-title",
|
|
@@ -5383,19 +5384,19 @@ window.__ModuleLoader__.load({
|
|
|
5383
5384
|
//#region src/client/cost.ts
|
|
5384
5385
|
/** 1 CNY = 0.15 USD — the fixed CNY-display conversion rate. */
|
|
5385
5386
|
const USD_PER_CNY = .15;
|
|
5386
|
-
/**
|
|
5387
|
-
const
|
|
5387
|
+
/** DeepSeek's peak rates are twice the off-peak rates (the official list). */
|
|
5388
|
+
const PEAK_FACTOR = 2;
|
|
5388
5389
|
/** A USD amount in the display currency (CNY divides the fixed rate). */
|
|
5389
5390
|
function toCurrency(usd, currency) {
|
|
5390
5391
|
return currency === "cny" ? usd / USD_PER_CNY : usd;
|
|
5391
5392
|
}
|
|
5392
|
-
/** One rate triple at the
|
|
5393
|
-
function
|
|
5393
|
+
/** One rate triple at the doubled peak rate (the tooltip's `peak | off` pair). */
|
|
5394
|
+
function peakOf(rate) {
|
|
5394
5395
|
return {
|
|
5395
|
-
hit: rate.hit *
|
|
5396
|
-
miss: rate.miss *
|
|
5397
|
-
write: rate.write *
|
|
5398
|
-
out: rate.out *
|
|
5396
|
+
hit: rate.hit * PEAK_FACTOR,
|
|
5397
|
+
miss: rate.miss * PEAK_FACTOR,
|
|
5398
|
+
write: rate.write * PEAK_FACTOR,
|
|
5399
|
+
out: rate.out * PEAK_FACTOR
|
|
5399
5400
|
};
|
|
5400
5401
|
}
|
|
5401
5402
|
/** One book branch (a provider's models), as far as runtime can prove it. */
|
|
@@ -5448,8 +5449,9 @@ window.__ModuleLoader__.load({
|
|
|
5448
5449
|
/**
|
|
5449
5450
|
* Price the session's cumulative billed-token totals. Cache reads bill at
|
|
5450
5451
|
* the hit rate, uncached input at the miss rate, cache writes at the write
|
|
5451
|
-
* rate, output (reasoning included) at the out rate; `
|
|
5452
|
-
*
|
|
5452
|
+
* rate, output (reasoning included) at the out rate; `peak` buckets price
|
|
5453
|
+
* at twice the book rate for DeepSeek (the book lists that provider's
|
|
5454
|
+
* off-peak rates — the Host splits the period-based list at fold time).
|
|
5453
5455
|
* Null when nothing was priced (no usage folded, no book yet, or no model
|
|
5454
5456
|
* the book prices), so the cell can show a dash.
|
|
5455
5457
|
*/
|
|
@@ -5460,7 +5462,7 @@ window.__ModuleLoader__.load({
|
|
|
5460
5462
|
for (const provider of Object.keys(usage)) {
|
|
5461
5463
|
const models = asRecord(usage[provider]);
|
|
5462
5464
|
if (models === null) continue;
|
|
5463
|
-
const
|
|
5465
|
+
const deepseek = isDeepSeekProvider(provider);
|
|
5464
5466
|
for (const model of Object.keys(models)) {
|
|
5465
5467
|
const rate = priceOf(prices, provider, model);
|
|
5466
5468
|
const periods = asRecord(models[model]);
|
|
@@ -5469,7 +5471,7 @@ window.__ModuleLoader__.load({
|
|
|
5469
5471
|
const bucket = asRecord(periods[period]);
|
|
5470
5472
|
if (bucket === null) continue;
|
|
5471
5473
|
const price = (numOf(bucket.cacheRead) * rate.hit + numOf(bucket.uncached) * rate.miss + numOf(bucket.cacheWrite) * rate.write + numOf(bucket.output) * rate.out) / 1e6;
|
|
5472
|
-
total +=
|
|
5474
|
+
total += deepseek && period === "peak" ? price * PEAK_FACTOR : price;
|
|
5473
5475
|
any = true;
|
|
5474
5476
|
}
|
|
5475
5477
|
}
|
|
@@ -6168,6 +6170,20 @@ window.__ModuleLoader__.load({
|
|
|
6168
6170
|
Promise.resolve(sessions.refresh()).catch(() => {});
|
|
6169
6171
|
} catch {}
|
|
6170
6172
|
}
|
|
6173
|
+
const BACKFILL_ROUTE = "/api/dsh-context/backfill";
|
|
6174
|
+
/**
|
|
6175
|
+
* Summon the host's projection warm-up (host/backfill.ts): the dashboard is
|
|
6176
|
+
* the rows' only reader, so the host defers the corpus-wide cold reads until
|
|
6177
|
+
* this surface first opens (one pass per host process — later opens no-op
|
|
6178
|
+
* server-side, and the host answers at once). Fire-and-forget: an older host
|
|
6179
|
+
* without the route, or a transport hiccup, just leaves the panel on the
|
|
6180
|
+
* rows it already has.
|
|
6181
|
+
*/
|
|
6182
|
+
function requestActivityBackfill() {
|
|
6183
|
+
try {
|
|
6184
|
+
fetch(BACKFILL_ROUTE, { method: "POST" }).catch(() => {});
|
|
6185
|
+
} catch {}
|
|
6186
|
+
}
|
|
6171
6187
|
/**
|
|
6172
6188
|
* The row's relative-activity label ("3m ago"), unit-stepped: under a
|
|
6173
6189
|
* minute reads "just now", then minutes, hours, days. A future or invalid
|
|
@@ -6668,7 +6684,10 @@ window.__ModuleLoader__.load({
|
|
|
6668
6684
|
const rows = (0, react.useMemo)(() => rowsOfSnapshot(snapshot, wsSnapshot), [snapshot, wsSnapshot]);
|
|
6669
6685
|
const groups = (0, react.useMemo)(() => sessionGroupsOf(wsSnapshot), [wsSnapshot]);
|
|
6670
6686
|
(0, react.useEffect)(() => {
|
|
6671
|
-
if (open)
|
|
6687
|
+
if (open) {
|
|
6688
|
+
requestActivityBackfill();
|
|
6689
|
+
refreshSessions(ctx);
|
|
6690
|
+
}
|
|
6672
6691
|
}, [open]);
|
|
6673
6692
|
(0, react.useEffect)(() => {
|
|
6674
6693
|
setPage(0);
|
|
@@ -9475,7 +9494,7 @@ window.__ModuleLoader__.load({
|
|
|
9475
9494
|
return function PluginInfo() {
|
|
9476
9495
|
const [latest, setLatest] = (0, react.useState)(null);
|
|
9477
9496
|
(0, react.useEffect)(() => {
|
|
9478
|
-
if ("0.53.
|
|
9497
|
+
if ("0.53.4".includes("-dev")) return;
|
|
9479
9498
|
let on = true;
|
|
9480
9499
|
fetchLatestVersion().then((v) => {
|
|
9481
9500
|
if (on && v) setLatest(v);
|
|
@@ -9484,8 +9503,8 @@ window.__ModuleLoader__.load({
|
|
|
9484
9503
|
on = false;
|
|
9485
9504
|
};
|
|
9486
9505
|
}, []);
|
|
9487
|
-
const update = latest !== null && isNewerVersion(latest, "0.53.
|
|
9488
|
-
const nameText = "dsh-context (v0.53.
|
|
9506
|
+
const update = latest !== null && isNewerVersion(latest, "0.53.4") ? latest : null;
|
|
9507
|
+
const nameText = "dsh-context (v0.53.4)";
|
|
9489
9508
|
const nameValue = [nameText];
|
|
9490
9509
|
if (update) nameValue.push(/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
9491
9510
|
className: "lc-pi-update",
|
|
@@ -9969,12 +9988,13 @@ window.__ModuleLoader__.load({
|
|
|
9969
9988
|
const rate = priceOf(prices, provider, model);
|
|
9970
9989
|
if (rate === null) continue;
|
|
9971
9990
|
const periods = asRecord(models[model]);
|
|
9972
|
-
const
|
|
9991
|
+
const deepseek = isDeepSeekProvider(provider);
|
|
9992
|
+
const billedOff = deepseek && periods !== null && periods.off !== void 0;
|
|
9973
9993
|
rows.push({
|
|
9974
9994
|
key: provider + "/" + model,
|
|
9975
9995
|
label: multi && provider !== "" ? `${model} · ${provider}` : model,
|
|
9976
|
-
rate,
|
|
9977
|
-
...
|
|
9996
|
+
rate: deepseek ? peakOf(rate) : rate,
|
|
9997
|
+
...billedOff ? { offRate: rate } : {}
|
|
9978
9998
|
});
|
|
9979
9999
|
}
|
|
9980
10000
|
}
|
|
@@ -10421,17 +10441,130 @@ window.__ModuleLoader__.load({
|
|
|
10421
10441
|
};
|
|
10422
10442
|
}
|
|
10423
10443
|
//#endregion
|
|
10444
|
+
//#region src/client/sidebar.ts
|
|
10445
|
+
/**
|
|
10446
|
+
* The right Sidebar's Context tab (dsh 0.1.5-rc.1+).
|
|
10447
|
+
*
|
|
10448
|
+
* The tab reuses the Context conversation-view component VERBATIM: the
|
|
10449
|
+
* `sidebar.right.pane.tab` seat is session-scoped and delivers the same
|
|
10450
|
+
* framework standard kit (`sessionId`, `useProjection`, `useChat`, the locale
|
|
10451
|
+
* `t` seat) the `conversation.view` seat does, so the panel and the tab are
|
|
10452
|
+
* one component with one data path. The tab type contributes a guide entry, so
|
|
10453
|
+
* the sidebar's guide page offers "Context" and picking it opens the panel —
|
|
10454
|
+
* the product's own path, exactly as the shipped Files type does: a capsule of
|
|
10455
|
+
* glyph, title, and description line, plus the chip-title seat that puts the
|
|
10456
|
+
* same glyph beside the label once the tab is open (`icon.tsx`).
|
|
10457
|
+
*
|
|
10458
|
+
* OPTIONAL BY CONTRACT. `ctx.sidebarRightTabs` and the seat ship only on the
|
|
10459
|
+
* 0.1.5 line (0.1.5-rc.1+ supported); the registration therefore rides a
|
|
10460
|
+
* DEFERRED inject (the plugin's hard injects stay `slots` + `locale`), so on
|
|
10461
|
+
* every older supported line the callback never fires, the plugin fiber never
|
|
10462
|
+
* pends, and nothing is registered. The registry is re-proved structurally and
|
|
10463
|
+
* the whole registration is guarded: a foreign or hostile registry (a throwing
|
|
10464
|
+
* `register`, a taken id/kind) leaves the sidebar without the tab instead of
|
|
10465
|
+
* taking the browser down.
|
|
10466
|
+
*
|
|
10467
|
+
* @module dsh-context/client/sidebar
|
|
10468
|
+
*/
|
|
10469
|
+
/** The tab type's identity in the sidebar's tab system (also its body-seat key). */
|
|
10470
|
+
const SIDEBAR_CONTEXT_ID = "dsh-context";
|
|
10471
|
+
/**
|
|
10472
|
+
* The kind `openTab` names. Namespaced rather than the bare `context`: the
|
|
10473
|
+
* registry THROWS when a kind collides with another registration in a
|
|
10474
|
+
* non-coexisting band, and a foreign plugin may well own `context`.
|
|
10475
|
+
*/
|
|
10476
|
+
const SIDEBAR_CONTEXT_KIND = "dsh-context";
|
|
10477
|
+
/** The guide capsule's position: after the shipped Files entry (order 10). */
|
|
10478
|
+
const GUIDE_ORDER = 20;
|
|
10479
|
+
/**
|
|
10480
|
+
* Register the Context tab type, its body, and its chip title on the right
|
|
10481
|
+
* Sidebar, if — and only if — this harness serves the sidebar tab registry.
|
|
10482
|
+
* @param ctx - client root context carrying `slots` and the locale service.
|
|
10483
|
+
* @param view - the Context view component factory result (the same one the
|
|
10484
|
+
* conversation tab mounts).
|
|
10485
|
+
* @param t - the plugin-namespace translate; the label thunks read the active
|
|
10486
|
+
* locale at call time, so a language switch relabels the guide entry.
|
|
10487
|
+
* @param ns - the plugin's locale namespace, put on the body registration so
|
|
10488
|
+
* the framework synthesizes the `t` seat for the panel too.
|
|
10489
|
+
* @returns the deferred inject's disposer (placement toggling calls it to
|
|
10490
|
+
* take the mount down; a no-op on faces that return no handle).
|
|
10491
|
+
*/
|
|
10492
|
+
function watchSidebarContextTab(ctx, view, t, ns) {
|
|
10493
|
+
const handle = ctx.inject(["sidebarRightTabs"], (raw) => {
|
|
10494
|
+
const injected = raw;
|
|
10495
|
+
const disposers = [];
|
|
10496
|
+
const own = (result) => {
|
|
10497
|
+
if (typeof result === "function") disposers.push(result);
|
|
10498
|
+
};
|
|
10499
|
+
try {
|
|
10500
|
+
const tabs = injected.sidebarRightTabs;
|
|
10501
|
+
if (tabs === void 0 || typeof tabs.register !== "function") return;
|
|
10502
|
+
own(tabs.register({
|
|
10503
|
+
id: SIDEBAR_CONTEXT_ID,
|
|
10504
|
+
kind: SIDEBAR_CONTEXT_KIND,
|
|
10505
|
+
title: () => t("tab"),
|
|
10506
|
+
guide: [{
|
|
10507
|
+
order: GUIDE_ORDER,
|
|
10508
|
+
title: () => t("tab"),
|
|
10509
|
+
description: () => t("sidebar.guideDescription"),
|
|
10510
|
+
icon: ContextIcon
|
|
10511
|
+
}]
|
|
10512
|
+
}));
|
|
10513
|
+
own(injected.slots.inject("sidebar.right.pane.tab", () => injected.slots.register({
|
|
10514
|
+
name: "sidebar.right.pane.tab",
|
|
10515
|
+
key: SIDEBAR_CONTEXT_ID,
|
|
10516
|
+
locale: ns
|
|
10517
|
+
}, (props) => view({
|
|
10518
|
+
...props,
|
|
10519
|
+
host: "sidebar"
|
|
10520
|
+
}))));
|
|
10521
|
+
own(injected.slots.inject("sidebar.right.pane.tab.title", () => injected.slots.register({
|
|
10522
|
+
name: "sidebar.right.pane.tab.title",
|
|
10523
|
+
key: SIDEBAR_CONTEXT_ID
|
|
10524
|
+
}, makeContextTabTitle(t))));
|
|
10525
|
+
} catch {
|
|
10526
|
+
for (const dispose of disposers) dispose();
|
|
10527
|
+
return;
|
|
10528
|
+
}
|
|
10529
|
+
return () => {
|
|
10530
|
+
for (const dispose of disposers) dispose();
|
|
10531
|
+
};
|
|
10532
|
+
});
|
|
10533
|
+
return () => {
|
|
10534
|
+
handle?.dispose?.();
|
|
10535
|
+
};
|
|
10536
|
+
}
|
|
10537
|
+
//#endregion
|
|
10424
10538
|
//#region src/client/viewFocus.ts
|
|
10425
10539
|
/**
|
|
10426
10540
|
* Chat → Context jump relay. The assistant-message action records the clicked
|
|
10427
|
-
* reply's request seq here and
|
|
10541
|
+
* reply's request seq here and opens the Context view — the right Sidebar's
|
|
10542
|
+
* tab when this harness serves one, else the conversation tab; the view
|
|
10428
10543
|
* consumes the one-shot focus once its projection data is in. Module-level
|
|
10429
10544
|
* per-session map — the same life pattern as the modal open-state stores.
|
|
10430
10545
|
*/
|
|
10431
10546
|
const pendingFocus = /* @__PURE__ */ new Map();
|
|
10432
|
-
/**
|
|
10547
|
+
/** The mounted Context views re-pinning on later records (repeat jumps). */
|
|
10548
|
+
const focusListeners = /* @__PURE__ */ new Set();
|
|
10549
|
+
/**
|
|
10550
|
+
* Record the Context step (request seq) to reveal for `sessionId` — replaces
|
|
10551
|
+
* any unconsumed request and wakes the mounted views.
|
|
10552
|
+
*/
|
|
10433
10553
|
function requestContextFocus(sessionId, seq) {
|
|
10434
10554
|
pendingFocus.set(sessionId, seq);
|
|
10555
|
+
for (const listener of focusListeners) listener();
|
|
10556
|
+
}
|
|
10557
|
+
/**
|
|
10558
|
+
* Wake on every record while subscribed: a Context view that is already
|
|
10559
|
+
* mounted (the sidebar landing keeps it mounted across jumps) re-pins when a
|
|
10560
|
+
* record lands — the listener takes its OWN session's entry, so other
|
|
10561
|
+
* sessions' records leave their entries pending for their views.
|
|
10562
|
+
*/
|
|
10563
|
+
function subscribeContextFocus(listener) {
|
|
10564
|
+
focusListeners.add(listener);
|
|
10565
|
+
return () => {
|
|
10566
|
+
focusListeners.delete(listener);
|
|
10567
|
+
};
|
|
10435
10568
|
}
|
|
10436
10569
|
/** Take the pending focus request, if any — one-shot, the map entry is consumed. */
|
|
10437
10570
|
function takeContextFocus(sessionId) {
|
|
@@ -10441,6 +10574,24 @@ window.__ModuleLoader__.load({
|
|
|
10441
10574
|
return seq;
|
|
10442
10575
|
}
|
|
10443
10576
|
/**
|
|
10577
|
+
* Open the Context tab on the right Sidebar over `ctx.sidebarRight.openTab`
|
|
10578
|
+
* (the column expands in the same step). OPTIONAL seam, re-proved at call
|
|
10579
|
+
* time: a harness without the service (every line older than 0.1.5-rc.1), a
|
|
10580
|
+
* placement that registered no tab type, no mounted session surface, or a
|
|
10581
|
+
* hostile face all report false so the caller keeps its conversation-tab
|
|
10582
|
+
* fallback.
|
|
10583
|
+
*/
|
|
10584
|
+
function openContextSidebar(ctx) {
|
|
10585
|
+
try {
|
|
10586
|
+
const face = asRecord(ctx.get("sidebarRight"));
|
|
10587
|
+
if (face === null || typeof face.openTab !== "function") return false;
|
|
10588
|
+
face.openTab.call(face, SIDEBAR_CONTEXT_KIND);
|
|
10589
|
+
return true;
|
|
10590
|
+
} catch {
|
|
10591
|
+
return false;
|
|
10592
|
+
}
|
|
10593
|
+
}
|
|
10594
|
+
/**
|
|
10444
10595
|
* Activate the Context tab by clicking its own tab-bar button (the semantic
|
|
10445
10596
|
* `button[role="tab"]` chrome the conversation shell renders for every view).
|
|
10446
10597
|
* The harness hands `openView` only to the ACTIVE view entry, so a nested
|
|
@@ -10458,6 +10609,33 @@ window.__ModuleLoader__.load({
|
|
|
10458
10609
|
return false;
|
|
10459
10610
|
}
|
|
10460
10611
|
//#endregion
|
|
10612
|
+
//#region src/client/revealScroll.ts
|
|
10613
|
+
/**
|
|
10614
|
+
* Scroll an element's nearest scrollable ancestor so the element tops that
|
|
10615
|
+
* scrollport. The jump's landing reveal needs this host-agnostic: the
|
|
10616
|
+
* conversation tab scrolls the shared `[data-conversation-scroll]` container
|
|
10617
|
+
* while the right Sidebar scrolls dockkit's pane body (a CSS-module class,
|
|
10618
|
+
* no stable attribute of its own), so the walk takes whichever ancestor
|
|
10619
|
+
* actually scrolls — the rect delta lands the anchor flush regardless of the
|
|
10620
|
+
* container's current position. `document.body` ends the walk: the page
|
|
10621
|
+
* itself never scrolls. False — never a throw — when no ancestor scrolls (the
|
|
10622
|
+
* layout already shows the element) or the chain turns hostile.
|
|
10623
|
+
*
|
|
10624
|
+
* @module dsh-context/client/revealScroll
|
|
10625
|
+
*/
|
|
10626
|
+
function revealInScrollParent(anchor) {
|
|
10627
|
+
try {
|
|
10628
|
+
for (let el = anchor.parentElement; el !== null && el !== document.body; el = el.parentElement) {
|
|
10629
|
+
const oy = window.getComputedStyle(el).overflowY;
|
|
10630
|
+
if (oy === "auto" || oy === "scroll") {
|
|
10631
|
+
el.scrollTop += anchor.getBoundingClientRect().top - el.getBoundingClientRect().top;
|
|
10632
|
+
return true;
|
|
10633
|
+
}
|
|
10634
|
+
}
|
|
10635
|
+
} catch {}
|
|
10636
|
+
return false;
|
|
10637
|
+
}
|
|
10638
|
+
//#endregion
|
|
10461
10639
|
//#region src/client/components/contextView.tsx
|
|
10462
10640
|
/**
|
|
10463
10641
|
* Context tab root: renders the `contextTimeline` projection and composes stats, composition, history, events and messages.
|
|
@@ -10563,8 +10741,12 @@ window.__ModuleLoader__.load({
|
|
|
10563
10741
|
const markers = (0, react.useMemo)(() => attachMarkers(displayRequests, events), [displayRequests, events]);
|
|
10564
10742
|
(0, react.useEffect)(() => {
|
|
10565
10743
|
if (typeof sessionId !== "string" || sessionId === "") return;
|
|
10566
|
-
const
|
|
10567
|
-
|
|
10744
|
+
const take = () => {
|
|
10745
|
+
const seq = takeContextFocus(sessionId);
|
|
10746
|
+
if (seq !== null) setJumpSeq(seq);
|
|
10747
|
+
};
|
|
10748
|
+
take();
|
|
10749
|
+
return subscribeContextFocus(take);
|
|
10568
10750
|
}, [sessionId]);
|
|
10569
10751
|
const detailReady = source.detailState === "ready" || source.detailState === "legacy";
|
|
10570
10752
|
(0, react.useEffect)(() => {
|
|
@@ -10575,7 +10757,10 @@ window.__ModuleLoader__.load({
|
|
|
10575
10757
|
setGranularity("turn");
|
|
10576
10758
|
setSelectedSeq(target.seq);
|
|
10577
10759
|
setFocusTurn(target.turn ?? 0);
|
|
10578
|
-
|
|
10760
|
+
/* v8 ignore next 2 -- the effect only fires while mounted, and both render paths attach
|
|
10761
|
+
rootRef and render the composition card. */
|
|
10762
|
+
const anchor = rootRef.current?.querySelector("[data-lc-current]") ?? null;
|
|
10763
|
+
if (anchor !== null) revealInScrollParent(anchor);
|
|
10579
10764
|
}, [
|
|
10580
10765
|
jumpSeq,
|
|
10581
10766
|
data,
|
|
@@ -10937,14 +11122,18 @@ window.__ModuleLoader__.load({
|
|
|
10937
11122
|
//#endregion
|
|
10938
11123
|
//#region src/client/components/contextJump.tsx
|
|
10939
11124
|
/**
|
|
10940
|
-
* The assistant-message action that
|
|
11125
|
+
* The assistant-message action that opens the Context view at this reply's
|
|
10941
11126
|
* turn. Registered on the harness `conversation.chat.assistant-actions` seat
|
|
10942
11127
|
* (the icon row beside copy/branch), it receives the finalized reply's durable
|
|
10943
11128
|
* message id, resolves the matching assistant node's seq off the `useChat`
|
|
10944
|
-
* node seat
|
|
10945
|
-
*
|
|
10946
|
-
*
|
|
10947
|
-
*
|
|
11129
|
+
* node seat AT RENDER TIME (the seat is a real React hook — render-only; a
|
|
11130
|
+
* click-time read throws the dispatcher guard and the jump would land
|
|
11131
|
+
* unpinned), records it in the viewFocus relay on click, and opens the
|
|
11132
|
+
* Context tab on the right Sidebar — where the jump pins the reply's TURN
|
|
11133
|
+
* (see contextView's leg 2). A harness or placement without the sidebar tab
|
|
11134
|
+
* falls back to the conversation tab; an unresolvable seq still opens the
|
|
11135
|
+
* view, just without a pin; a message id that is not a plain string renders
|
|
11136
|
+
* nothing at all.
|
|
10948
11137
|
*/
|
|
10949
11138
|
/**
|
|
10950
11139
|
* The reply's request seq by its durable message id, or null when no served node proves the pair. Join/log nodes are untrusted input: each
|
|
@@ -10995,16 +11184,17 @@ window.__ModuleLoader__.load({
|
|
|
10995
11184
|
]
|
|
10996
11185
|
});
|
|
10997
11186
|
}
|
|
10998
|
-
function makeContextJumpButton(kit) {
|
|
11187
|
+
function makeContextJumpButton(ctx, kit) {
|
|
10999
11188
|
const { t } = kit;
|
|
11000
11189
|
return function ContextJump(props) {
|
|
11190
|
+
const nodes = conversationNodesOf(props);
|
|
11001
11191
|
const messageId = props.messageId;
|
|
11002
11192
|
if (typeof messageId !== "string" || messageId === "") return null;
|
|
11193
|
+
const seq = seqOfMessageId(nodes, messageId);
|
|
11003
11194
|
const jump = () => {
|
|
11004
|
-
const seq = seqOfMessageId(conversationNodesOf(props), messageId);
|
|
11005
11195
|
const sessionId = props.sessionId;
|
|
11006
11196
|
if (seq !== null && typeof sessionId === "string" && sessionId !== "") requestContextFocus(sessionId, seq);
|
|
11007
|
-
activateContextTab(t("tab"));
|
|
11197
|
+
if (!openContextSidebar(ctx)) activateContextTab(t("tab"));
|
|
11008
11198
|
};
|
|
11009
11199
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
|
|
11010
11200
|
label: t("jump.title"),
|
|
@@ -11056,100 +11246,6 @@ window.__ModuleLoader__.load({
|
|
|
11056
11246
|
};
|
|
11057
11247
|
}
|
|
11058
11248
|
//#endregion
|
|
11059
|
-
//#region src/client/sidebar.ts
|
|
11060
|
-
/**
|
|
11061
|
-
* The right Sidebar's Context tab (dsh 0.1.5-rc.1+).
|
|
11062
|
-
*
|
|
11063
|
-
* The tab reuses the Context conversation-view component VERBATIM: the
|
|
11064
|
-
* `sidebar.right.pane.tab` seat is session-scoped and delivers the same
|
|
11065
|
-
* framework standard kit (`sessionId`, `useProjection`, `useChat`, the locale
|
|
11066
|
-
* `t` seat) the `conversation.view` seat does, so the panel and the tab are
|
|
11067
|
-
* one component with one data path. The tab type contributes a guide entry, so
|
|
11068
|
-
* the sidebar's guide page offers "Context" and picking it opens the panel —
|
|
11069
|
-
* the product's own path, exactly as the shipped Files type does: a capsule of
|
|
11070
|
-
* glyph, title, and description line, plus the chip-title seat that puts the
|
|
11071
|
-
* same glyph beside the label once the tab is open (`icon.tsx`).
|
|
11072
|
-
*
|
|
11073
|
-
* OPTIONAL BY CONTRACT. `ctx.sidebarRightTabs` and the seat ship only on the
|
|
11074
|
-
* 0.1.5 line (0.1.5-rc.1+ supported); the registration therefore rides a
|
|
11075
|
-
* DEFERRED inject (the plugin's hard injects stay `slots` + `locale`), so on
|
|
11076
|
-
* every older supported line the callback never fires, the plugin fiber never
|
|
11077
|
-
* pends, and nothing is registered. The registry is re-proved structurally and
|
|
11078
|
-
* the whole registration is guarded: a foreign or hostile registry (a throwing
|
|
11079
|
-
* `register`, a taken id/kind) leaves the sidebar without the tab instead of
|
|
11080
|
-
* taking the browser down.
|
|
11081
|
-
*
|
|
11082
|
-
* @module dsh-context/client/sidebar
|
|
11083
|
-
*/
|
|
11084
|
-
/** The tab type's identity in the sidebar's tab system (also its body-seat key). */
|
|
11085
|
-
const SIDEBAR_CONTEXT_ID = "dsh-context";
|
|
11086
|
-
/**
|
|
11087
|
-
* The kind `openTab` names. Namespaced rather than the bare `context`: the
|
|
11088
|
-
* registry THROWS when a kind collides with another registration in a
|
|
11089
|
-
* non-coexisting band, and a foreign plugin may well own `context`.
|
|
11090
|
-
*/
|
|
11091
|
-
const SIDEBAR_CONTEXT_KIND = "dsh-context";
|
|
11092
|
-
/** The guide capsule's position: after the shipped Files entry (order 10). */
|
|
11093
|
-
const GUIDE_ORDER = 20;
|
|
11094
|
-
/**
|
|
11095
|
-
* Register the Context tab type, its body, and its chip title on the right
|
|
11096
|
-
* Sidebar, if — and only if — this harness serves the sidebar tab registry.
|
|
11097
|
-
* @param ctx - client root context carrying `slots` and the locale service.
|
|
11098
|
-
* @param view - the Context view component factory result (the same one the
|
|
11099
|
-
* conversation tab mounts).
|
|
11100
|
-
* @param t - the plugin-namespace translate; the label thunks read the active
|
|
11101
|
-
* locale at call time, so a language switch relabels the guide entry.
|
|
11102
|
-
* @param ns - the plugin's locale namespace, put on the body registration so
|
|
11103
|
-
* the framework synthesizes the `t` seat for the panel too.
|
|
11104
|
-
* @returns the deferred inject's disposer (placement toggling calls it to
|
|
11105
|
-
* take the mount down; a no-op on faces that return no handle).
|
|
11106
|
-
*/
|
|
11107
|
-
function watchSidebarContextTab(ctx, view, t, ns) {
|
|
11108
|
-
const handle = ctx.inject(["sidebarRightTabs"], (raw) => {
|
|
11109
|
-
const injected = raw;
|
|
11110
|
-
const disposers = [];
|
|
11111
|
-
const own = (result) => {
|
|
11112
|
-
if (typeof result === "function") disposers.push(result);
|
|
11113
|
-
};
|
|
11114
|
-
try {
|
|
11115
|
-
const tabs = injected.sidebarRightTabs;
|
|
11116
|
-
if (tabs === void 0 || typeof tabs.register !== "function") return;
|
|
11117
|
-
own(tabs.register({
|
|
11118
|
-
id: SIDEBAR_CONTEXT_ID,
|
|
11119
|
-
kind: SIDEBAR_CONTEXT_KIND,
|
|
11120
|
-
title: () => t("tab"),
|
|
11121
|
-
guide: [{
|
|
11122
|
-
order: GUIDE_ORDER,
|
|
11123
|
-
title: () => t("tab"),
|
|
11124
|
-
description: () => t("sidebar.guideDescription"),
|
|
11125
|
-
icon: ContextIcon
|
|
11126
|
-
}]
|
|
11127
|
-
}));
|
|
11128
|
-
own(injected.slots.inject("sidebar.right.pane.tab", () => injected.slots.register({
|
|
11129
|
-
name: "sidebar.right.pane.tab",
|
|
11130
|
-
key: SIDEBAR_CONTEXT_ID,
|
|
11131
|
-
locale: ns
|
|
11132
|
-
}, (props) => view({
|
|
11133
|
-
...props,
|
|
11134
|
-
host: "sidebar"
|
|
11135
|
-
}))));
|
|
11136
|
-
own(injected.slots.inject("sidebar.right.pane.tab.title", () => injected.slots.register({
|
|
11137
|
-
name: "sidebar.right.pane.tab.title",
|
|
11138
|
-
key: SIDEBAR_CONTEXT_ID
|
|
11139
|
-
}, makeContextTabTitle(t))));
|
|
11140
|
-
} catch {
|
|
11141
|
-
for (const dispose of disposers) dispose();
|
|
11142
|
-
return;
|
|
11143
|
-
}
|
|
11144
|
-
return () => {
|
|
11145
|
-
for (const dispose of disposers) dispose();
|
|
11146
|
-
};
|
|
11147
|
-
});
|
|
11148
|
-
return () => {
|
|
11149
|
-
handle?.dispose?.();
|
|
11150
|
-
};
|
|
11151
|
-
}
|
|
11152
|
-
//#endregion
|
|
11153
11249
|
//#region src/client/viewkit.ts
|
|
11154
11250
|
function makeViewKit(t) {
|
|
11155
11251
|
const { eventLabel, eventAt } = makeEventText(t);
|
|
@@ -11390,7 +11486,7 @@ window.__ModuleLoader__.load({
|
|
|
11390
11486
|
}),
|
|
11391
11487
|
sidebar: () => watchSidebarContextTab(ctx, ContextView, t, NS)
|
|
11392
11488
|
}), "dsh-context: placement");
|
|
11393
|
-
const ContextJump = makeContextJumpButton(kit);
|
|
11489
|
+
const ContextJump = makeContextJumpButton(ctx, kit);
|
|
11394
11490
|
ctx.slots.inject("conversation.chat.assistant-actions", () => {
|
|
11395
11491
|
return ctx.slots.register({
|
|
11396
11492
|
name: "conversation.chat.assistant-actions",
|
package/lib/index.js
CHANGED
|
@@ -870,7 +870,7 @@ function removeSurfaceSeqs(st, claimed, goneSeq) {
|
|
|
870
870
|
* rather than through `deriveEventMessage`, whose 0.1.2-rc.1 generation knows
|
|
871
871
|
* nothing of the V3 `system/message` variant. A malformed payload reads null.
|
|
872
872
|
*/
|
|
873
|
-
function messageOf(data) {
|
|
873
|
+
function messageOf$1(data) {
|
|
874
874
|
const message = data?.message;
|
|
875
875
|
return message !== null && typeof message === "object" ? message : null;
|
|
876
876
|
}
|
|
@@ -1230,7 +1230,7 @@ function applyTimeline(state, event, bounds) {
|
|
|
1230
1230
|
pushSystem(s, {
|
|
1231
1231
|
seq: event.seq,
|
|
1232
1232
|
time: event.time,
|
|
1233
|
-
tokens: estimateSystemContent(messageOf(data)?.content)
|
|
1233
|
+
tokens: estimateSystemContent(messageOf$1(data)?.content)
|
|
1234
1234
|
});
|
|
1235
1235
|
break;
|
|
1236
1236
|
}
|
|
@@ -2058,8 +2058,17 @@ function createToolAttribution(ctx) {
|
|
|
2058
2058
|
}
|
|
2059
2059
|
//#endregion
|
|
2060
2060
|
//#region src/host/backfill.ts
|
|
2061
|
-
/**
|
|
2062
|
-
const
|
|
2061
|
+
/** The plugin's warm-up trigger route, under the authenticated `/api` fence. */
|
|
2062
|
+
const BACKFILL_ROUTE = "/api/dsh-context/backfill";
|
|
2063
|
+
/**
|
|
2064
|
+
* Inter-session pacing so a multi-hundred-session backfill never starve the
|
|
2065
|
+
* host: each cold read is a full durable-log decode, and a boot-time host
|
|
2066
|
+
* under sustained read pressure stalls its own client handshake (the
|
|
2067
|
+
* workspace sidebar rides it), so the pass breathes between sessions.
|
|
2068
|
+
*/
|
|
2069
|
+
const YIELD_MS = 100;
|
|
2070
|
+
/** The `name` dsh raises for a durable log its migration surface refuses. */
|
|
2071
|
+
const UNSUPPORTED_MIGRATION = "SessionFormatUnsupportedMigrationError";
|
|
2063
2072
|
function asRecord(value) {
|
|
2064
2073
|
return value !== null && typeof value === "object" ? value : null;
|
|
2065
2074
|
}
|
|
@@ -2114,11 +2123,63 @@ function isLive(sessions, id) {
|
|
|
2114
2123
|
}
|
|
2115
2124
|
}
|
|
2116
2125
|
/**
|
|
2117
|
-
*
|
|
2118
|
-
*
|
|
2126
|
+
* Whether the cold read failed because the running harness refuses to
|
|
2127
|
+
* migrate this log — a permanent, source-side refusal (the artifact is left
|
|
2128
|
+
* unchanged, so retrying cannot succeed). Matched by the error's documented
|
|
2129
|
+
* `name`, so no dsh symbol needs importing and every supported baseline
|
|
2130
|
+
* classifies alike: an unknown error shape just falls to the warn path.
|
|
2131
|
+
*/
|
|
2132
|
+
function isUnsupportedMigration(error) {
|
|
2133
|
+
try {
|
|
2134
|
+
return asRecord(error)?.name === UNSUPPORTED_MIGRATION;
|
|
2135
|
+
} catch {
|
|
2136
|
+
return false;
|
|
2137
|
+
}
|
|
2138
|
+
}
|
|
2139
|
+
/** The error's printable form — a hostile error may throw on its own toString. */
|
|
2140
|
+
function messageOf(error) {
|
|
2141
|
+
try {
|
|
2142
|
+
return String(error);
|
|
2143
|
+
} catch {
|
|
2144
|
+
return "unprintable error";
|
|
2145
|
+
}
|
|
2146
|
+
}
|
|
2147
|
+
/**
|
|
2148
|
+
* Arm the warm-up behind its trigger route. The route flips `requested`
|
|
2149
|
+
* when the dashboard first opens; the cold-path faces compose `launch`.
|
|
2150
|
+
* Either half may land first; the first request after both launches the one
|
|
2151
|
+
* pass, and every later request is a no-op. Returns the deferred injects'
|
|
2152
|
+
* disposer (abort on unload).
|
|
2119
2153
|
*/
|
|
2120
2154
|
function watchActivityBackfill(ctx) {
|
|
2121
|
-
|
|
2155
|
+
let requested = false;
|
|
2156
|
+
let started = false;
|
|
2157
|
+
let launch = null;
|
|
2158
|
+
const tryLaunch = () => {
|
|
2159
|
+
if (!requested || started || launch === null) return;
|
|
2160
|
+
started = true;
|
|
2161
|
+
launch();
|
|
2162
|
+
};
|
|
2163
|
+
const routeFiber = ctx.inject(["connection"], (c) => {
|
|
2164
|
+
const connection = c.get("connection");
|
|
2165
|
+
const register = typeof connection?.fetch?.register === "function" ? connection.fetch.register.bind(connection.fetch) : void 0;
|
|
2166
|
+
if (register === void 0) return;
|
|
2167
|
+
try {
|
|
2168
|
+
c.effect(() => register({
|
|
2169
|
+
path: BACKFILL_ROUTE,
|
|
2170
|
+
methods: ["POST"],
|
|
2171
|
+
requestBody: "buffered",
|
|
2172
|
+
fetch: () => {
|
|
2173
|
+
requested = true;
|
|
2174
|
+
tryLaunch();
|
|
2175
|
+
return Response.json({ ok: true }, { headers: { "cache-control": "no-store" } });
|
|
2176
|
+
}
|
|
2177
|
+
}), "dsh-context: backfill route");
|
|
2178
|
+
} catch {
|
|
2179
|
+
return;
|
|
2180
|
+
}
|
|
2181
|
+
});
|
|
2182
|
+
const runnerFiber = ctx.inject([
|
|
2122
2183
|
"sessionQuery",
|
|
2123
2184
|
"sessionProjectionCache",
|
|
2124
2185
|
"sessionPersistence",
|
|
@@ -2137,6 +2198,7 @@ function watchActivityBackfill(ctx) {
|
|
|
2137
2198
|
const listed = await query.listSessions(abort.signal);
|
|
2138
2199
|
if (!Array.isArray(listed)) return;
|
|
2139
2200
|
let folded = 0;
|
|
2201
|
+
let refused = 0;
|
|
2140
2202
|
for (const record of listed) {
|
|
2141
2203
|
if (abort.signal.aborted) return;
|
|
2142
2204
|
const header = headerOf(record);
|
|
@@ -2148,21 +2210,28 @@ function watchActivityBackfill(ctx) {
|
|
|
2148
2210
|
cache.coldSnapshot(log.header, log.inheritedEventCount, log.events);
|
|
2149
2211
|
folded++;
|
|
2150
2212
|
} catch (error) {
|
|
2151
|
-
|
|
2213
|
+
if (isUnsupportedMigration(error)) {
|
|
2214
|
+
refused++;
|
|
2215
|
+
ctx.logger.debug(`dsh-context: projection backfill skipped "${header.id}" (${messageOf(error)})`);
|
|
2216
|
+
} else ctx.logger.warn(`dsh-context: projection backfill skipped "${header.id}" (${messageOf(error)})`);
|
|
2152
2217
|
}
|
|
2153
2218
|
await new Promise((resolve) => setTimeout(resolve, YIELD_MS));
|
|
2154
2219
|
}
|
|
2155
2220
|
if (folded > 0) ctx.logger.info(`dsh-context: projection rows backfilled for ${folded} session(s)`);
|
|
2221
|
+
if (refused > 0) ctx.logger.info(`dsh-context: projection backfill skipped ${refused} session(s) (legacy log format is not migratable; sources left unchanged)`);
|
|
2156
2222
|
};
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2223
|
+
launch = () => {
|
|
2224
|
+
run().catch((error) => {
|
|
2225
|
+
if (!abort.signal.aborted) ctx.logger.warn(`dsh-context: projection backfill stopped early (${messageOf(error)})`);
|
|
2226
|
+
});
|
|
2227
|
+
};
|
|
2228
|
+
tryLaunch();
|
|
2160
2229
|
return () => {
|
|
2161
2230
|
abort.abort();
|
|
2162
2231
|
};
|
|
2163
2232
|
});
|
|
2164
2233
|
return () => {
|
|
2165
|
-
|
|
2234
|
+
for (const fiber of [routeFiber, runnerFiber]) fiber?.dispose?.();
|
|
2166
2235
|
};
|
|
2167
2236
|
}
|
|
2168
2237
|
//#endregion
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-context",
|
|
3
|
-
"version": "0.53.
|
|
3
|
+
"version": "0.53.4",
|
|
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": {
|