dsh-context 0.53.3 → 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.
Files changed (2) hide show
  1. package/lib/client.js +207 -128
  2. 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 tab",
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
- /** Off-peak DeepSeek rates are half the peak rates (the official list). */
5387
- const OFF_PEAK_FACTOR = .5;
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 half-price off-peak rate (the tooltip's `peak | off` pair). */
5393
- function offPeakOf(rate) {
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 * OFF_PEAK_FACTOR,
5396
- miss: rate.miss * OFF_PEAK_FACTOR,
5397
- write: rate.write * OFF_PEAK_FACTOR,
5398
- out: rate.out * OFF_PEAK_FACTOR
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; `off` buckets (the
5452
- * Host splits DeepSeek's period-based list at fold time) price at half.
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 offPeak = isDeepSeekProvider(provider);
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 += offPeak && period === "off" ? price * OFF_PEAK_FACTOR : price;
5474
+ total += deepseek && period === "peak" ? price * PEAK_FACTOR : price;
5473
5475
  any = true;
5474
5476
  }
5475
5477
  }
@@ -9492,7 +9494,7 @@ window.__ModuleLoader__.load({
9492
9494
  return function PluginInfo() {
9493
9495
  const [latest, setLatest] = (0, react.useState)(null);
9494
9496
  (0, react.useEffect)(() => {
9495
- if ("0.53.3".includes("-dev")) return;
9497
+ if ("0.53.4".includes("-dev")) return;
9496
9498
  let on = true;
9497
9499
  fetchLatestVersion().then((v) => {
9498
9500
  if (on && v) setLatest(v);
@@ -9501,8 +9503,8 @@ window.__ModuleLoader__.load({
9501
9503
  on = false;
9502
9504
  };
9503
9505
  }, []);
9504
- const update = latest !== null && isNewerVersion(latest, "0.53.3") ? latest : null;
9505
- const nameText = "dsh-context (v0.53.3)";
9506
+ const update = latest !== null && isNewerVersion(latest, "0.53.4") ? latest : null;
9507
+ const nameText = "dsh-context (v0.53.4)";
9506
9508
  const nameValue = [nameText];
9507
9509
  if (update) nameValue.push(/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
9508
9510
  className: "lc-pi-update",
@@ -9986,12 +9988,13 @@ window.__ModuleLoader__.load({
9986
9988
  const rate = priceOf(prices, provider, model);
9987
9989
  if (rate === null) continue;
9988
9990
  const periods = asRecord(models[model]);
9989
- const off = isDeepSeekProvider(provider) && periods !== null && periods.off !== void 0 ? offPeakOf(rate) : void 0;
9991
+ const deepseek = isDeepSeekProvider(provider);
9992
+ const billedOff = deepseek && periods !== null && periods.off !== void 0;
9990
9993
  rows.push({
9991
9994
  key: provider + "/" + model,
9992
9995
  label: multi && provider !== "" ? `${model} · ${provider}` : model,
9993
- rate,
9994
- ...off !== void 0 ? { offRate: off } : {}
9996
+ rate: deepseek ? peakOf(rate) : rate,
9997
+ ...billedOff ? { offRate: rate } : {}
9995
9998
  });
9996
9999
  }
9997
10000
  }
@@ -10438,17 +10441,130 @@ window.__ModuleLoader__.load({
10438
10441
  };
10439
10442
  }
10440
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
10441
10538
  //#region src/client/viewFocus.ts
10442
10539
  /**
10443
10540
  * Chat → Context jump relay. The assistant-message action records the clicked
10444
- * reply's request seq here and activates the Context tab; the Context view
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
10445
10543
  * consumes the one-shot focus once its projection data is in. Module-level
10446
10544
  * per-session map — the same life pattern as the modal open-state stores.
10447
10545
  */
10448
10546
  const pendingFocus = /* @__PURE__ */ new Map();
10449
- /** Record the Context step (request seq) to reveal for `sessionId` — replaces any unconsumed request. */
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
+ */
10450
10553
  function requestContextFocus(sessionId, seq) {
10451
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
+ };
10452
10568
  }
10453
10569
  /** Take the pending focus request, if any — one-shot, the map entry is consumed. */
10454
10570
  function takeContextFocus(sessionId) {
@@ -10458,6 +10574,24 @@ window.__ModuleLoader__.load({
10458
10574
  return seq;
10459
10575
  }
10460
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
+ /**
10461
10595
  * Activate the Context tab by clicking its own tab-bar button (the semantic
10462
10596
  * `button[role="tab"]` chrome the conversation shell renders for every view).
10463
10597
  * The harness hands `openView` only to the ACTIVE view entry, so a nested
@@ -10475,6 +10609,33 @@ window.__ModuleLoader__.load({
10475
10609
  return false;
10476
10610
  }
10477
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
10478
10639
  //#region src/client/components/contextView.tsx
10479
10640
  /**
10480
10641
  * Context tab root: renders the `contextTimeline` projection and composes stats, composition, history, events and messages.
@@ -10580,8 +10741,12 @@ window.__ModuleLoader__.load({
10580
10741
  const markers = (0, react.useMemo)(() => attachMarkers(displayRequests, events), [displayRequests, events]);
10581
10742
  (0, react.useEffect)(() => {
10582
10743
  if (typeof sessionId !== "string" || sessionId === "") return;
10583
- const seq = takeContextFocus(sessionId);
10584
- if (seq !== null) setJumpSeq(seq);
10744
+ const take = () => {
10745
+ const seq = takeContextFocus(sessionId);
10746
+ if (seq !== null) setJumpSeq(seq);
10747
+ };
10748
+ take();
10749
+ return subscribeContextFocus(take);
10585
10750
  }, [sessionId]);
10586
10751
  const detailReady = source.detailState === "ready" || source.detailState === "legacy";
10587
10752
  (0, react.useEffect)(() => {
@@ -10592,7 +10757,10 @@ window.__ModuleLoader__.load({
10592
10757
  setGranularity("turn");
10593
10758
  setSelectedSeq(target.seq);
10594
10759
  setFocusTurn(target.turn ?? 0);
10595
- if (scrollerRef.current !== null) scrollerRef.current.scrollTop = 0;
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);
10596
10764
  }, [
10597
10765
  jumpSeq,
10598
10766
  data,
@@ -10954,14 +11122,18 @@ window.__ModuleLoader__.load({
10954
11122
  //#endregion
10955
11123
  //#region src/client/components/contextJump.tsx
10956
11124
  /**
10957
- * The assistant-message action that jumps to the Context tab at this reply's
11125
+ * The assistant-message action that opens the Context view at this reply's
10958
11126
  * turn. Registered on the harness `conversation.chat.assistant-actions` seat
10959
11127
  * (the icon row beside copy/branch), it receives the finalized reply's durable
10960
11128
  * message id, resolves the matching assistant node's seq off the `useChat`
10961
- * node seat, records it in the viewFocus relay, and activates the Context
10962
- * tab — where the jump pins the reply's TURN (see contextView's leg 2). An
10963
- * unresolvable seq still switches tabs, just without a pin; a message id that
10964
- * is not a plain string renders nothing at all.
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.
10965
11137
  */
10966
11138
  /**
10967
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
@@ -11012,16 +11184,17 @@ window.__ModuleLoader__.load({
11012
11184
  ]
11013
11185
  });
11014
11186
  }
11015
- function makeContextJumpButton(kit) {
11187
+ function makeContextJumpButton(ctx, kit) {
11016
11188
  const { t } = kit;
11017
11189
  return function ContextJump(props) {
11190
+ const nodes = conversationNodesOf(props);
11018
11191
  const messageId = props.messageId;
11019
11192
  if (typeof messageId !== "string" || messageId === "") return null;
11193
+ const seq = seqOfMessageId(nodes, messageId);
11020
11194
  const jump = () => {
11021
- const seq = seqOfMessageId(conversationNodesOf(props), messageId);
11022
11195
  const sessionId = props.sessionId;
11023
11196
  if (seq !== null && typeof sessionId === "string" && sessionId !== "") requestContextFocus(sessionId, seq);
11024
- activateContextTab(t("tab"));
11197
+ if (!openContextSidebar(ctx)) activateContextTab(t("tab"));
11025
11198
  };
11026
11199
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
11027
11200
  label: t("jump.title"),
@@ -11073,100 +11246,6 @@ window.__ModuleLoader__.load({
11073
11246
  };
11074
11247
  }
11075
11248
  //#endregion
11076
- //#region src/client/sidebar.ts
11077
- /**
11078
- * The right Sidebar's Context tab (dsh 0.1.5-rc.1+).
11079
- *
11080
- * The tab reuses the Context conversation-view component VERBATIM: the
11081
- * `sidebar.right.pane.tab` seat is session-scoped and delivers the same
11082
- * framework standard kit (`sessionId`, `useProjection`, `useChat`, the locale
11083
- * `t` seat) the `conversation.view` seat does, so the panel and the tab are
11084
- * one component with one data path. The tab type contributes a guide entry, so
11085
- * the sidebar's guide page offers "Context" and picking it opens the panel —
11086
- * the product's own path, exactly as the shipped Files type does: a capsule of
11087
- * glyph, title, and description line, plus the chip-title seat that puts the
11088
- * same glyph beside the label once the tab is open (`icon.tsx`).
11089
- *
11090
- * OPTIONAL BY CONTRACT. `ctx.sidebarRightTabs` and the seat ship only on the
11091
- * 0.1.5 line (0.1.5-rc.1+ supported); the registration therefore rides a
11092
- * DEFERRED inject (the plugin's hard injects stay `slots` + `locale`), so on
11093
- * every older supported line the callback never fires, the plugin fiber never
11094
- * pends, and nothing is registered. The registry is re-proved structurally and
11095
- * the whole registration is guarded: a foreign or hostile registry (a throwing
11096
- * `register`, a taken id/kind) leaves the sidebar without the tab instead of
11097
- * taking the browser down.
11098
- *
11099
- * @module dsh-context/client/sidebar
11100
- */
11101
- /** The tab type's identity in the sidebar's tab system (also its body-seat key). */
11102
- const SIDEBAR_CONTEXT_ID = "dsh-context";
11103
- /**
11104
- * The kind `openTab` names. Namespaced rather than the bare `context`: the
11105
- * registry THROWS when a kind collides with another registration in a
11106
- * non-coexisting band, and a foreign plugin may well own `context`.
11107
- */
11108
- const SIDEBAR_CONTEXT_KIND = "dsh-context";
11109
- /** The guide capsule's position: after the shipped Files entry (order 10). */
11110
- const GUIDE_ORDER = 20;
11111
- /**
11112
- * Register the Context tab type, its body, and its chip title on the right
11113
- * Sidebar, if — and only if — this harness serves the sidebar tab registry.
11114
- * @param ctx - client root context carrying `slots` and the locale service.
11115
- * @param view - the Context view component factory result (the same one the
11116
- * conversation tab mounts).
11117
- * @param t - the plugin-namespace translate; the label thunks read the active
11118
- * locale at call time, so a language switch relabels the guide entry.
11119
- * @param ns - the plugin's locale namespace, put on the body registration so
11120
- * the framework synthesizes the `t` seat for the panel too.
11121
- * @returns the deferred inject's disposer (placement toggling calls it to
11122
- * take the mount down; a no-op on faces that return no handle).
11123
- */
11124
- function watchSidebarContextTab(ctx, view, t, ns) {
11125
- const handle = ctx.inject(["sidebarRightTabs"], (raw) => {
11126
- const injected = raw;
11127
- const disposers = [];
11128
- const own = (result) => {
11129
- if (typeof result === "function") disposers.push(result);
11130
- };
11131
- try {
11132
- const tabs = injected.sidebarRightTabs;
11133
- if (tabs === void 0 || typeof tabs.register !== "function") return;
11134
- own(tabs.register({
11135
- id: SIDEBAR_CONTEXT_ID,
11136
- kind: SIDEBAR_CONTEXT_KIND,
11137
- title: () => t("tab"),
11138
- guide: [{
11139
- order: GUIDE_ORDER,
11140
- title: () => t("tab"),
11141
- description: () => t("sidebar.guideDescription"),
11142
- icon: ContextIcon
11143
- }]
11144
- }));
11145
- own(injected.slots.inject("sidebar.right.pane.tab", () => injected.slots.register({
11146
- name: "sidebar.right.pane.tab",
11147
- key: SIDEBAR_CONTEXT_ID,
11148
- locale: ns
11149
- }, (props) => view({
11150
- ...props,
11151
- host: "sidebar"
11152
- }))));
11153
- own(injected.slots.inject("sidebar.right.pane.tab.title", () => injected.slots.register({
11154
- name: "sidebar.right.pane.tab.title",
11155
- key: SIDEBAR_CONTEXT_ID
11156
- }, makeContextTabTitle(t))));
11157
- } catch {
11158
- for (const dispose of disposers) dispose();
11159
- return;
11160
- }
11161
- return () => {
11162
- for (const dispose of disposers) dispose();
11163
- };
11164
- });
11165
- return () => {
11166
- handle?.dispose?.();
11167
- };
11168
- }
11169
- //#endregion
11170
11249
  //#region src/client/viewkit.ts
11171
11250
  function makeViewKit(t) {
11172
11251
  const { eventLabel, eventAt } = makeEventText(t);
@@ -11407,7 +11486,7 @@ window.__ModuleLoader__.load({
11407
11486
  }),
11408
11487
  sidebar: () => watchSidebarContextTab(ctx, ContextView, t, NS)
11409
11488
  }), "dsh-context: placement");
11410
- const ContextJump = makeContextJumpButton(kit);
11489
+ const ContextJump = makeContextJumpButton(ctx, kit);
11411
11490
  ctx.slots.inject("conversation.chat.assistant-actions", () => {
11412
11491
  return ctx.slots.register({
11413
11492
  name: "conversation.chat.assistant-actions",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-context",
3
- "version": "0.53.3",
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": {