dsh-context 0.54.4 → 0.55.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/client.js CHANGED
@@ -78,6 +78,7 @@ window.__ModuleLoader__.load({
78
78
  "timing.other": "其他开销",
79
79
  "timing.callTimes": "{n}次",
80
80
  "timing.toolTimes": "{n}次",
81
+ "timing.blockTimes": "{n}次",
81
82
  "timing.empty": "暂无耗时数据 · 对话开始后这里会显示时长分布",
82
83
  "timing.tps": "{tps} tok/s",
83
84
  "timing.tpsTip": "输出速度(TPS):累计解码输出 tokens ÷ 累计解码时长,与 DeepSeek Harness 会话统计同口径。",
@@ -90,7 +91,7 @@ window.__ModuleLoader__.load({
90
91
  "plugin.name": "插件",
91
92
  "plugin.github": "GitHub",
92
93
  "plugin.settings": "设置",
93
- "plugin.settingsOpen": "打开设置",
94
+ "plugin.settingsOpen": "打开偏好设置",
94
95
  "trend.title": "上下文趋势",
95
96
  "gran.step": "步骤",
96
97
  "gran.turn": "轮次",
@@ -450,6 +451,7 @@ window.__ModuleLoader__.load({
450
451
  "timing.other": "Overhead",
451
452
  "timing.callTimes": "{n} calls",
452
453
  "timing.toolTimes": "{n} runs",
454
+ "timing.blockTimes": "{n} blocks",
453
455
  "timing.empty": "No timing data yet — durations appear as the conversation runs",
454
456
  "timing.tps": "{tps} tok/s",
455
457
  "timing.tpsTip": "Output speed (TPS): summed decoded output tokens over summed decode time — the same pairing as the harness session stats.",
@@ -462,7 +464,7 @@ window.__ModuleLoader__.load({
462
464
  "plugin.name": "Plugin",
463
465
  "plugin.github": "GitHub",
464
466
  "plugin.settings": "Settings",
465
- "plugin.settingsOpen": "Open in Settings",
467
+ "plugin.settingsOpen": "Open preferences",
466
468
  "trend.title": "Context Trend",
467
469
  "gran.step": "Step",
468
470
  "gran.turn": "Turn",
@@ -776,20 +778,23 @@ window.__ModuleLoader__.load({
776
778
  "ov.time.d": "{n}d ago"
777
779
  };
778
780
  //#endregion
781
+ //#region icon.svg?raw
782
+ var icon_default = "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 1024 1024\">\n <path d=\"M899.984 19.873h-3.452c-26.123 0-47.296 21.172-47.296 47.296v888.508c0 26.127 21.173 47.298 47.296 47.298h3.452c26.119 0 47.297-21.171 47.297-47.298V67.169c0-26.124-21.177-47.296-47.297-47.296z\" fill=\"#4A5699\"/>\n <path d=\"M132.643 19.873h-3.449c-26.12 0-47.296 21.172-47.296 47.296v888.508c0 26.127 21.177 47.298 47.296 47.298h3.449c26.123 0 47.299-21.171 47.299-47.298V67.169c0-26.124-21.176-47.296-47.299-47.296z\" fill=\"#C45FA0\"/>\n <path d=\"M899.463 19.873H129.194c-26.12 0-47.296 21.172-47.296 47.296v3.377c0 26.12 21.177 47.299 47.296 47.299h770.269c26.123 0 47.296-21.179 47.296-47.299v-3.377c0-26.124-21.173-47.296-47.296-47.296z\" fill=\"#6277BA\"/>\n <path d=\"M899.463 905.006H129.194c-26.12 0-47.296 21.17-47.296 47.29v3.381c0 26.127 21.177 47.298 47.296 47.298h770.269c26.123 0 47.296-21.171 47.296-47.298v-3.381c0-26.12-21.173-47.29-47.296-47.29z\" fill=\"#C45FA0\"/>\n <path d=\"M717.962 543.153H542.047c-26.121 0-47.298 21.175-47.298 47.297v3.724c0 26.123 21.177 47.293 47.298 47.293h175.915c26.121 0 47.297-21.17 47.297-47.293v-3.724c0-26.122-21.176-47.297-47.297-47.297z\" fill=\"#E5594F\"/>\n <path d=\"M689.268 198.849H513.355c-26.122 0-47.298 21.175-47.298 47.297v3.722c0 26.12 21.176 47.297 47.298 47.297h175.912c26.122 0 47.298-21.177 47.298-47.297v-3.722c0-26.122-21.175-47.297-47.297-47.297z\" fill=\"#F0D043\"/>\n <path d=\"M757.789 353.081H261.17c-26.121 0-47.297 21.172-47.297 47.296v3.377c0 26.121 21.177 47.299 47.297 47.299h496.619c26.121 0 47.296-21.178 47.296-47.299v-3.377c0-26.125-21.175-47.296-47.296-47.296z\" fill=\"#E5594F\"/>\n <path d=\"M762.638 726.225h-496.62c-26.12 0-47.294 21.18-47.294 47.301v3.377c0 26.12 21.174 47.3 47.294 47.3h496.62c26.122 0 47.296-21.18 47.296-47.3v-3.377c0-26.122-21.174-47.301-47.296-47.301z\" fill=\"#6277BA\"/>\n <path d=\"M355.734 543.328H281.41c-26.122 0-47.297 21.17-47.297 47.293v3.378c0 26.118 21.175 47.297 47.297 47.297h74.324c26.123 0 47.296-21.179 47.296-47.297v-3.378c0-26.123-21.174-47.293-47.296-47.293z\" fill=\"#F39A2B\"/>\n <path d=\"M334.85 248.006m-48.986 0a48.986 48.986 0 1 0 97.972 0 48.986 48.986 0 1 0-97.972 0Z\" fill=\"#F39A2B\"/>\n</svg>\n";
783
+ //#endregion
779
784
  //#region src/client/icon.tsx
780
- /** The sheet's strokes, in paint order, as `[path, fill]` pairs. */
781
- const SHEET_PATHS = [
782
- ["M899.984 19.873h-3.452c-26.123 0-47.296 21.172-47.296 47.296v888.508c0 26.127 21.173 47.298 47.296 47.298h3.452c26.119 0 47.297-21.171 47.297-47.298V67.169c0-26.124-21.177-47.296-47.297-47.296z", "#4A5699"],
783
- ["M132.643 19.873h-3.449c-26.12 0-47.296 21.172-47.296 47.296v888.508c0 26.127 21.177 47.298 47.296 47.298h3.449c26.123 0 47.299-21.171 47.299-47.298V67.169c0-26.124-21.176-47.296-47.299-47.296z", "#C45FA0"],
784
- ["M899.463 19.873H129.194c-26.12 0-47.296 21.172-47.296 47.296v3.377c0 26.12 21.177 47.299 47.296 47.299h770.269c26.123 0 47.296-21.179 47.296-47.299v-3.377c0-26.124-21.173-47.296-47.296-47.296z", "#6277BA"],
785
- ["M899.463 905.006H129.194c-26.12 0-47.296 21.17-47.296 47.29v3.381c0 26.127 21.177 47.298 47.296 47.298h770.269c26.123 0 47.296-21.171 47.296-47.298v-3.381c0-26.12-21.173-47.29-47.296-47.29z", "#C45FA0"],
786
- ["M717.962 543.153H542.047c-26.121 0-47.298 21.175-47.298 47.297v3.724c0 26.123 21.177 47.293 47.298 47.293h175.915c26.121 0 47.297-21.17 47.297-47.293v-3.724c0-26.122-21.176-47.297-47.297-47.297z", "#E5594F"],
787
- ["M689.268 198.849H513.355c-26.122 0-47.298 21.175-47.298 47.297v3.722c0 26.12 21.176 47.297 47.298 47.297h175.912c26.122 0 47.298-21.177 47.298-47.297v-3.722c0-26.122-21.175-47.297-47.297-47.297z", "#F0D043"],
788
- ["M757.789 353.081H261.17c-26.121 0-47.297 21.172-47.297 47.296v3.377c0 26.121 21.177 47.299 47.297 47.299h496.619c26.121 0 47.296-21.178 47.296-47.299v-3.377c0-26.125-21.175-47.296-47.296-47.296z", "#E5594F"],
789
- ["M762.638 726.225h-496.62c-26.12 0-47.294 21.18-47.294 47.301v3.377c0 26.12 21.174 47.3 47.294 47.3h496.62c26.122 0 47.296-21.18 47.296-47.3v-3.377c0-26.122-21.174-47.301-47.296-47.301z", "#6277BA"],
790
- ["M355.734 543.328H281.41c-26.122 0-47.297 21.17-47.297 47.293v3.378c0 26.118 21.175 47.297 47.297 47.297h74.324c26.123 0 47.296-21.179 47.296-47.297v-3.378c0-26.123-21.174-47.293-47.296-47.293z", "#F39A2B"],
791
- ["M334.85 248.006m-48.986 0a48.986 48.986 0 1 0 97.972 0 48.986 48.986 0 1 0-97.972 0Z", "#F39A2B"]
792
- ];
785
+ /**
786
+ * The Context emblem: the colourful document sheet, bundled rather than read
787
+ * off the harness primitives so the plugin's identity is self-contained.
788
+ *
789
+ * The single graphic source is the package-root `icon.svg` — the same file
790
+ * package.json `icon` hands to the Host's package-meta reader, which serves
791
+ * it to the Plugins page's package cards. The client bundle inlines the
792
+ * file's markup at build time (the `?raw` channel in tsdown.config.ts) and
793
+ * re-renders it at every requested size. Fixed fills, never `currentColor` —
794
+ * the sheet is deliberately polychrome on both light and dark chrome.
795
+ */
796
+ /** Everything between the file's `<svg>` tags: the sheet's strokes in paint order, whitespace-folded. */
797
+ const SHEET_MARKUP = icon_default.slice(icon_default.indexOf(">") + 1, icon_default.lastIndexOf("</svg>")).trim().replace(/>\s+</g, "><");
793
798
  /** The colourful document sheet at the requested square edge. */
794
799
  function ContextIcon({ size = 20, className }) {
795
800
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
@@ -799,10 +804,7 @@ window.__ModuleLoader__.load({
799
804
  className,
800
805
  "aria-hidden": "true",
801
806
  xmlns: "http://www.w3.org/2000/svg",
802
- children: SHEET_PATHS.map(([d, fill]) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
803
- d,
804
- fill
805
- }, d))
807
+ dangerouslySetInnerHTML: { __html: SHEET_MARKUP }
806
808
  });
807
809
  }
808
810
  /**
@@ -1462,11 +1464,12 @@ window.__ModuleLoader__.load({
1462
1464
  return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : 0;
1463
1465
  }
1464
1466
  /**
1465
- * The OPTIONAL timing scalars (the generation split): a real non-negative
1466
- * number passes, anything else — including absence — reads as undefined so the
1467
- * field stays absent on the narrowed value (see `timingOf`).
1467
+ * The OPTIONAL timing scalars (the generation split's spans and block counts):
1468
+ * a real non-negative number passes, anything else — including absence — reads
1469
+ * as undefined so the field stays absent on the narrowed value (see
1470
+ * `timingOf`).
1468
1471
  */
1469
- function optMsNumOf(value) {
1472
+ function optNumOf(value) {
1470
1473
  return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : void 0;
1471
1474
  }
1472
1475
  /**
@@ -1490,7 +1493,10 @@ window.__ModuleLoader__.load({
1490
1493
  for (const k of [
1491
1494
  "reasoningMs",
1492
1495
  "textMs",
1493
- "toolArgMs"
1496
+ "toolArgMs",
1497
+ "reasoningBlocks",
1498
+ "textBlocks",
1499
+ "toolArgBlocks"
1494
1500
  ]) {
1495
1501
  const v = t[k];
1496
1502
  if (v !== void 0 && (typeof v !== "number" || !Number.isFinite(v) || v < 0)) return false;
@@ -1538,12 +1544,18 @@ window.__ModuleLoader__.load({
1538
1544
  toolCalls: msNumOf(data.toolCalls),
1539
1545
  tools
1540
1546
  };
1541
- const reasoning = optMsNumOf(data.reasoningMs);
1547
+ const reasoning = optNumOf(data.reasoningMs);
1542
1548
  if (reasoning !== void 0) totals.reasoningMs = reasoning;
1543
- const textMs = optMsNumOf(data.textMs);
1549
+ const reasoningBlocks = optNumOf(data.reasoningBlocks);
1550
+ if (reasoningBlocks !== void 0) totals.reasoningBlocks = reasoningBlocks;
1551
+ const textMs = optNumOf(data.textMs);
1544
1552
  if (textMs !== void 0) totals.textMs = textMs;
1545
- const toolArgMs = optMsNumOf(data.toolArgMs);
1553
+ const textBlocks = optNumOf(data.textBlocks);
1554
+ if (textBlocks !== void 0) totals.textBlocks = textBlocks;
1555
+ const toolArgMs = optNumOf(data.toolArgMs);
1546
1556
  if (toolArgMs !== void 0) totals.toolArgMs = toolArgMs;
1557
+ const toolArgBlocks = optNumOf(data.toolArgBlocks);
1558
+ if (toolArgBlocks !== void 0) totals.toolArgBlocks = toolArgBlocks;
1547
1559
  return totals;
1548
1560
  }
1549
1561
  /**
@@ -2468,6 +2480,16 @@ window.__ModuleLoader__.load({
2468
2480
  return kindOfTool(tool);
2469
2481
  }
2470
2482
  /**
2483
+ * Whether a settled call's arguments can yield an op at all: the file tools
2484
+ * plus `str_replace_editor`, whose purpose follows its `command`. The public
2485
+ * face of the gate {@link opsOfCall} short-circuits on — callers that decide
2486
+ * whether to parse or to KEEP a call's raw arguments ask here instead of
2487
+ * restating the tool list.
2488
+ */
2489
+ function opBearingTool(tool) {
2490
+ return tool === "str_replace_editor" || kindOfTool(tool) !== null;
2491
+ }
2492
+ /**
2471
2493
  * The operation's target path: the path-ish argument of read/write tools;
2472
2494
  * for searches the narrowing `path`, else the pattern itself (a pathless
2473
2495
  * grep/glob's target IS the pattern — the workspace-wide search text).
@@ -2606,7 +2628,7 @@ window.__ModuleLoader__.load({
2606
2628
  * resolve no target) rows nothing.
2607
2629
  */
2608
2630
  function opsOfCall(input) {
2609
- const args = parseCallArgs(input.argsRaw);
2631
+ const args = opBearingTool(input.tool) ? parseCallArgs(input.argsRaw) : null;
2610
2632
  const kind = kindOfCall(input.tool, args);
2611
2633
  if (kind === null) return [];
2612
2634
  const stamp = {
@@ -7297,18 +7319,28 @@ window.__ModuleLoader__.load({
7297
7319
  //#endregion
7298
7320
  //#region src/client/settingsJump.ts
7299
7321
  /**
7300
- * Best-effort jump to this plugin's settings page (Settings → Plugins →
7301
- * Plugin configuration). The harness settings panel keeps its open state
7302
- * inside the shell component — no plugin-facing open face exists — so the
7303
- * jump drives the real chrome: click the sidebar's settings trigger, then
7304
- * the Plugins section nav row, both found by their shipped attributes and
7305
- * labels. EVERY step is individually guarded: a shell that doesn't match
7306
- * (older host, different layout, missing sidebar) degrades the whole jump
7307
- * to a silent no-op — this path must never throw into the caller's render.
7308
- *
7322
+ * Best-effort jump to this plugin's preferences. Two generations of chrome:
7323
+ * - dsh 0.1.7+ (Config-form generation): the preferences live on the
7324
+ * bundle's page of the Plugins main panel — the jump clicks the panel's
7325
+ * sidebar entry (its shipped aria-label), then follows the page with a
7326
+ * bounded poll for the bundle card's open control, found by the shipped,
7327
+ * locale-independent `data-plugin-package` list identity: the card lists
7328
+ * once the page's package load settles, and inside it the one button
7329
+ * that is not the enable switch is the title that opens the page. A
7330
+ * poll that never finds the card (still loading, plugin absent, or the
7331
+ * page already open on the detail view — the list unmounts there)
7332
+ * degrades to the card list, and every step stays silent.
7333
+ * - older lines: Settings → Plugins → Plugin configuration — the settings
7334
+ * panel keeps its open state inside the shell component (no plugin-facing
7335
+ * open face exists), so the jump drives the real chrome: click the
7336
+ * sidebar's settings trigger, then the Plugins section nav row, both
7337
+ * found by their shipped attributes and labels.
7309
7338
  * The last leg — the card itself — is ours: the module also carries a
7310
- * short-lived in-bundle expand request that the plugin's settings card
7311
- * consumes on mount, so the jump lands on the configuration already open.
7339
+ * short-lived in-bundle expand request that the settings-section card
7340
+ * consumes on mount, so that jump lands on the configuration already open.
7341
+ * EVERY step is individually guarded: a shell that doesn't match (different
7342
+ * layout, missing sidebar) degrades the whole jump to a silent no-op — this
7343
+ * path must never throw into the caller's render.
7312
7344
  */
7313
7345
  let requestedAt = 0;
7314
7346
  /** Raise a short-lived request for the plugin's settings card to mount expanded. */
@@ -7321,24 +7353,66 @@ window.__ModuleLoader__.load({
7321
7353
  requestedAt = 0;
7322
7354
  return fresh;
7323
7355
  }
7324
- /** The Plugins settings section's shipped nav labels (en/zh locales). */
7325
- const SECTION_LABELS = /* @__PURE__ */ new Set(["Plugins", "插件"]);
7356
+ /**
7357
+ * The shipped Plugins label in both locales — the main-panel entry's
7358
+ * aria-label and the settings section's nav row text are the same word.
7359
+ */
7360
+ const PLUGIN_LABELS = /* @__PURE__ */ new Set(["Plugins", "插件"]);
7361
+ /** The bundle card's shipped list identity (locale-independent). */
7362
+ const PACKAGE_KEY = "dsh-context";
7363
+ /** How long the card poll follows the panel: 40 ticks at the 100ms tick, enough for the page's package load. */
7364
+ const POLL_TICKS = 40;
7326
7365
  /** Buttons the jump may operate on, in document order. */
7327
7366
  function buttonsOf(doc) {
7328
7367
  return [...doc.querySelectorAll("button")];
7329
7368
  }
7369
+ /** The Plugins main-panel entry, matched by its shipped aria-label. */
7370
+ function findPanelEntry(doc) {
7371
+ return buttonsOf(doc).find((b) => PLUGIN_LABELS.has(b.getAttribute("aria-label") ?? ""));
7372
+ }
7373
+ /**
7374
+ * The bundle card's open control: the one button inside the card that is not
7375
+ * the enable switch (the primitives switch carries `role="switch"`); the
7376
+ * title button's own aria-label is localized.
7377
+ */
7378
+ function findCardOpenControl(doc) {
7379
+ const card = doc.querySelector(`[data-plugin-package="${PACKAGE_KEY}"]`);
7380
+ if (card === null) return void 0;
7381
+ const open = [...card.querySelectorAll("button")].filter((b) => b.getAttribute("role") !== "switch");
7382
+ return open.length === 1 ? open[0] : void 0;
7383
+ }
7330
7384
  /** The sidebar's settings triggers: dialog semantics plus an expanded flag. */
7331
7385
  function findTriggers(doc) {
7332
7386
  return buttonsOf(doc).filter((b) => b.getAttribute("aria-haspopup") === "dialog" && b.hasAttribute("aria-expanded"));
7333
7387
  }
7334
- /** The Plugins section's nav row, matched by its shipped label. */
7388
+ /** The Plugins section's nav row, matched by its shipped label text. */
7335
7389
  function findSectionRow(doc) {
7336
- return buttonsOf(doc).find((b) => SECTION_LABELS.has(b.textContent.trim()));
7390
+ return buttonsOf(doc).find((b) => PLUGIN_LABELS.has(b.textContent.trim()));
7337
7391
  }
7338
7392
  function openPluginSettings(doc = document, schedule = (run, ms) => {
7339
7393
  window.setTimeout(run, ms);
7340
7394
  }) {
7341
7395
  try {
7396
+ const panel = findPanelEntry(doc);
7397
+ if (panel !== void 0) {
7398
+ panel.click();
7399
+ const poll = (left) => {
7400
+ try {
7401
+ const open = findCardOpenControl(doc);
7402
+ if (open !== void 0) {
7403
+ open.click();
7404
+ return;
7405
+ }
7406
+ } catch {}
7407
+ if (left > 0) schedule(() => {
7408
+ poll(left - 1);
7409
+ }, 100);
7410
+ };
7411
+ schedule(() => {
7412
+ poll(POLL_TICKS);
7413
+ }, 200);
7414
+ return;
7415
+ }
7342
7416
  const triggers = findTriggers(doc);
7343
7417
  if (triggers.find((b) => b.getAttribute("aria-expanded") === "true") === void 0 && triggers.length === 0) return;
7344
7418
  triggers.find((b) => b.getAttribute("aria-expanded") !== "true")?.click();
@@ -7353,13 +7427,17 @@ window.__ModuleLoader__.load({
7353
7427
  //#endregion
7354
7428
  //#region src/client/components/settingsCard.tsx
7355
7429
  /**
7356
- * The dsh-context card in Settings → Plugins → Plugin configuration, registered on the framework's `settings.plugin.item` slot keyed on
7357
- * the
7358
- * Host-served `dsh-context` settings namespace — the section itself supplies nothing; it renders nothing while the namespace is
7359
- * unavailable
7360
- * (a deployment without the Host half, or a remote browser, shows no trace).
7361
- * Mounts expanded when the Plugin Info card's "Open in Settings" jump left a
7362
- * fresh expand request (settingsJump.ts), scrolling itself into view.
7430
+ * The dsh-context preference cards — two seats over the same six rows. The
7431
+ * settings-section card (`settings.plugin.item`, the older harness lines)
7432
+ * renders a collapsible list item in Settings → Plugins → Plugin
7433
+ * configuration; the Plugins-page card (`plugins.bundle.config`, the
7434
+ * Config-form generation) renders the rows flat inside the section chrome the
7435
+ * Plugins page draws for the bundle. Both are keyed on the Host-served
7436
+ * `dsh-context` namespace and render nothing while it is unavailable (a
7437
+ * deployment without the Host half, or a remote browser, shows no trace).
7438
+ * The settings-section card mounts expanded when the Plugin Info card's
7439
+ * "Open preferences" jump left a fresh expand request (settingsJump.ts),
7440
+ * scrolling itself into view.
7363
7441
  */
7364
7442
  function PrefRow(props) {
7365
7443
  const [open, setOpen] = (0, react.useState)(false);
@@ -7396,6 +7474,124 @@ window.__ModuleLoader__.load({
7396
7474
  })]
7397
7475
  });
7398
7476
  }
7477
+ /** The six preference rows shared by both seats. */
7478
+ function PreferenceRows(props) {
7479
+ const { t, state, set } = props;
7480
+ const disabled = state.status !== "ready" || !state.writable;
7481
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
7482
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PrefRow, {
7483
+ label: t("settings.placement"),
7484
+ value: state.placement,
7485
+ disabled,
7486
+ options: [
7487
+ {
7488
+ id: "all",
7489
+ label: t("placement.all")
7490
+ },
7491
+ {
7492
+ id: "tab",
7493
+ label: t("placement.tab")
7494
+ },
7495
+ {
7496
+ id: "sidebar",
7497
+ label: t("placement.sidebar")
7498
+ }
7499
+ ],
7500
+ onPick: (id) => {
7501
+ set?.("defaultPlacement", id);
7502
+ }
7503
+ }),
7504
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PrefRow, {
7505
+ label: t("settings.insightsEntry"),
7506
+ value: state.insightsEntry,
7507
+ disabled,
7508
+ options: [{
7509
+ id: "show",
7510
+ label: t("insightsEntry.show")
7511
+ }, {
7512
+ id: "hide",
7513
+ label: t("insightsEntry.hide")
7514
+ }],
7515
+ onPick: (id) => {
7516
+ set?.("insightsEntry", id);
7517
+ }
7518
+ }),
7519
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PrefRow, {
7520
+ label: t("settings.gran"),
7521
+ value: state.granularity,
7522
+ disabled,
7523
+ options: [{
7524
+ id: "step",
7525
+ label: t("gran.step")
7526
+ }, {
7527
+ id: "turn",
7528
+ label: t("gran.turn")
7529
+ }],
7530
+ onPick: (id) => {
7531
+ set?.("defaultGranularity", id);
7532
+ }
7533
+ }),
7534
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PrefRow, {
7535
+ label: t("settings.mode"),
7536
+ value: state.mode,
7537
+ disabled,
7538
+ options: [{
7539
+ id: "total",
7540
+ label: t("gran.total")
7541
+ }, {
7542
+ id: "delta",
7543
+ label: t("gran.delta")
7544
+ }],
7545
+ onPick: (id) => {
7546
+ set?.("defaultTrendMode", id);
7547
+ }
7548
+ }),
7549
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PrefRow, {
7550
+ label: t("settings.toolSort"),
7551
+ value: state.toolSort,
7552
+ disabled,
7553
+ options: [
7554
+ {
7555
+ id: "size",
7556
+ label: t("tool.sort.size")
7557
+ },
7558
+ {
7559
+ id: "count",
7560
+ label: t("tool.sort.count")
7561
+ },
7562
+ {
7563
+ id: "name",
7564
+ label: t("tool.sort.name")
7565
+ }
7566
+ ],
7567
+ onPick: (id) => {
7568
+ set?.("defaultToolSort", id);
7569
+ }
7570
+ }),
7571
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PrefRow, {
7572
+ label: t("settings.fileSort"),
7573
+ value: state.fileSort,
7574
+ disabled,
7575
+ options: [
7576
+ {
7577
+ id: "count",
7578
+ label: t("files.sort.count")
7579
+ },
7580
+ {
7581
+ id: "latest",
7582
+ label: t("files.sort.latest")
7583
+ },
7584
+ {
7585
+ id: "path",
7586
+ label: t("files.sort.path")
7587
+ }
7588
+ ],
7589
+ onPick: (id) => {
7590
+ set?.("defaultFileSort", id);
7591
+ }
7592
+ })
7593
+ ] });
7594
+ }
7399
7595
  function makeSettingsCard(kit) {
7400
7596
  const { t } = kit;
7401
7597
  return function SettingsCard(props) {
@@ -7410,7 +7606,6 @@ window.__ModuleLoader__.load({
7410
7606
  }, []);
7411
7607
  const state = typeof props.useContextSettings === "function" ? props.useContextSettings((s) => s) : void 0;
7412
7608
  if (state === void 0 || state.status === "unavailable") return null;
7413
- const disabled = state.status !== "ready" || !state.writable;
7414
7609
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
7415
7610
  ref: itemRef,
7416
7611
  className: "lc-settings-card" + (open ? " lc-settings-open" : ""),
@@ -7434,128 +7629,44 @@ window.__ModuleLoader__.load({
7434
7629
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconChevronDown, { className: "lc-settings-chevron" })]
7435
7630
  }), open ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7436
7631
  className: "lc-settings-body",
7437
- children: [
7438
- !state.writable && state.status === "ready" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
7439
- className: "lc-settings-note",
7440
- role: "status",
7441
- children: t("settings.readOnly")
7442
- }) : null,
7443
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PrefRow, {
7444
- label: t("settings.placement"),
7445
- value: state.placement,
7446
- disabled,
7447
- options: [
7448
- {
7449
- id: "all",
7450
- label: t("placement.all")
7451
- },
7452
- {
7453
- id: "tab",
7454
- label: t("placement.tab")
7455
- },
7456
- {
7457
- id: "sidebar",
7458
- label: t("placement.sidebar")
7459
- }
7460
- ],
7461
- onPick: (id) => {
7462
- props.set?.("defaultPlacement", id);
7463
- }
7464
- }),
7465
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PrefRow, {
7466
- label: t("settings.insightsEntry"),
7467
- value: state.insightsEntry,
7468
- disabled,
7469
- options: [{
7470
- id: "show",
7471
- label: t("insightsEntry.show")
7472
- }, {
7473
- id: "hide",
7474
- label: t("insightsEntry.hide")
7475
- }],
7476
- onPick: (id) => {
7477
- props.set?.("insightsEntry", id);
7478
- }
7479
- }),
7480
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PrefRow, {
7481
- label: t("settings.gran"),
7482
- value: state.granularity,
7483
- disabled,
7484
- options: [{
7485
- id: "step",
7486
- label: t("gran.step")
7487
- }, {
7488
- id: "turn",
7489
- label: t("gran.turn")
7490
- }],
7491
- onPick: (id) => {
7492
- props.set?.("defaultGranularity", id);
7493
- }
7494
- }),
7495
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PrefRow, {
7496
- label: t("settings.mode"),
7497
- value: state.mode,
7498
- disabled,
7499
- options: [{
7500
- id: "total",
7501
- label: t("gran.total")
7502
- }, {
7503
- id: "delta",
7504
- label: t("gran.delta")
7505
- }],
7506
- onPick: (id) => {
7507
- props.set?.("defaultTrendMode", id);
7508
- }
7509
- }),
7510
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PrefRow, {
7511
- label: t("settings.toolSort"),
7512
- value: state.toolSort,
7513
- disabled,
7514
- options: [
7515
- {
7516
- id: "size",
7517
- label: t("tool.sort.size")
7518
- },
7519
- {
7520
- id: "count",
7521
- label: t("tool.sort.count")
7522
- },
7523
- {
7524
- id: "name",
7525
- label: t("tool.sort.name")
7526
- }
7527
- ],
7528
- onPick: (id) => {
7529
- props.set?.("defaultToolSort", id);
7530
- }
7531
- }),
7532
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PrefRow, {
7533
- label: t("settings.fileSort"),
7534
- value: state.fileSort,
7535
- disabled,
7536
- options: [
7537
- {
7538
- id: "count",
7539
- label: t("files.sort.count")
7540
- },
7541
- {
7542
- id: "latest",
7543
- label: t("files.sort.latest")
7544
- },
7545
- {
7546
- id: "path",
7547
- label: t("files.sort.path")
7548
- }
7549
- ],
7550
- onPick: (id) => {
7551
- props.set?.("defaultFileSort", id);
7552
- }
7553
- })
7554
- ]
7632
+ children: [!state.writable && state.status === "ready" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
7633
+ className: "lc-settings-note",
7634
+ role: "status",
7635
+ children: t("settings.readOnly")
7636
+ }) : null, /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PreferenceRows, {
7637
+ t,
7638
+ state,
7639
+ set: props.set
7640
+ })]
7555
7641
  }) : null]
7556
7642
  });
7557
7643
  };
7558
7644
  }
7645
+ /**
7646
+ * The Plugins-page card (the Config-form generation's `plugins.bundle.config`
7647
+ * seat, `view: 'page'`): the page owns the bundle's page and section chrome,
7648
+ * so the rows render flat. Same unserved/absent degradation as the
7649
+ * settings-section card.
7650
+ */
7651
+ function makePluginConfigCard(kit) {
7652
+ const { t } = kit;
7653
+ return function PluginConfigCard(props) {
7654
+ const state = typeof props.useContextSettings === "function" ? props.useContextSettings((s) => s) : void 0;
7655
+ if (state === void 0 || state.status === "unavailable") return null;
7656
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7657
+ className: "lc-settings-prefs",
7658
+ children: [!state.writable && state.status === "ready" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
7659
+ className: "lc-settings-note",
7660
+ role: "status",
7661
+ children: t("settings.readOnly")
7662
+ }) : null, /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PreferenceRows, {
7663
+ t,
7664
+ state,
7665
+ set: props.set
7666
+ })]
7667
+ });
7668
+ };
7669
+ }
7559
7670
  //#endregion
7560
7671
  //#region src/client/settings.ts
7561
7672
  function prefsOf(value) {
@@ -9793,7 +9904,7 @@ window.__ModuleLoader__.load({
9793
9904
  return function PluginInfo() {
9794
9905
  const [latest, setLatest] = (0, react.useState)(null);
9795
9906
  (0, react.useEffect)(() => {
9796
- if ("0.54.4".includes("-dev")) return;
9907
+ if ("0.55.0".includes("-dev")) return;
9797
9908
  let on = true;
9798
9909
  fetchLatestVersion().then((v) => {
9799
9910
  if (on && v) setLatest(v);
@@ -9802,8 +9913,8 @@ window.__ModuleLoader__.load({
9802
9913
  on = false;
9803
9914
  };
9804
9915
  }, []);
9805
- const update = latest !== null && isNewerVersion(latest, "0.54.4") ? latest : null;
9806
- const nameText = "dsh-context (v0.54.4)";
9916
+ const update = latest !== null && isNewerVersion(latest, "0.55.0") ? latest : null;
9917
+ const nameText = "dsh-context (v0.55.0)";
9807
9918
  const nameValue = [nameText];
9808
9919
  if (update) nameValue.push(/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
9809
9920
  className: "lc-pi-update",
@@ -10519,10 +10630,10 @@ window.__ModuleLoader__.load({
10519
10630
  * lands in the residue. When the host folded the generation split (see
10520
10631
  * TimingTotals), the generation slice expands into what was being decoded:
10521
10632
  * thinking, answer text, and tool-call arguments. The slice rows lead with
10522
- * the true duration and qualify it with the call count on the secondary line —
10523
- * except the decode slices, which count BLOCKS (zero to many per call, so a
10524
- * call count would be a false tally) and therefore carry no qualifier. A slice
10525
- * that never happened (zero time) is omitted entirely — the ring already skips
10633
+ * the true duration and qualify it with a tally on the secondary line — the
10634
+ * model calls (TTFT), the tool runs, and the decode slices' own BLOCK counts
10635
+ * (zero to many per call, so a call count would be a false tally there). A
10636
+ * slice that never happened (zero time) is omitted entirely — the ring already skips
10526
10637
  * its arc, and a zero row would otherwise borrow the session's call count and
10527
10638
  * read as "no time, many calls". The donut and the rows sit side by side so
10528
10639
  * the head row stays half-height. Parallel tool calls each count, so the tools
@@ -10564,31 +10675,36 @@ window.__ModuleLoader__.load({
10564
10675
  ms: timing.ttftMs,
10565
10676
  times: callTimes
10566
10677
  }];
10678
+ const blockTimes = (blocks) => blocks !== void 0 && blocks > 0 ? t("timing.blockTimes", { n: fmt(blocks) }) : void 0;
10567
10679
  const buckets = [
10568
10680
  [
10569
10681
  "reasoning",
10570
10682
  COLOR.reasoning,
10571
10683
  t("timing.reasoning"),
10572
- timing.reasoningMs ?? 0
10684
+ timing.reasoningMs ?? 0,
10685
+ blockTimes(timing.reasoningBlocks)
10573
10686
  ],
10574
10687
  [
10575
10688
  "text",
10576
10689
  COLOR.text,
10577
10690
  t("timing.text"),
10578
- timing.textMs ?? 0
10691
+ timing.textMs ?? 0,
10692
+ blockTimes(timing.textBlocks)
10579
10693
  ],
10580
10694
  [
10581
10695
  "toolarg",
10582
10696
  COLOR.toolarg,
10583
10697
  t("timing.toolArgs"),
10584
- timing.toolArgMs ?? 0
10698
+ timing.toolArgMs ?? 0,
10699
+ blockTimes(timing.toolArgBlocks)
10585
10700
  ]
10586
10701
  ];
10587
- if (buckets.some(([, , , ms]) => ms > 0)) for (const [key, color, label, ms] of buckets) modelSlices.push({
10702
+ if (buckets.some(([, , , ms]) => ms > 0)) for (const [key, color, label, ms, times] of buckets) modelSlices.push({
10588
10703
  key,
10589
10704
  color,
10590
10705
  label,
10591
- ms
10706
+ ms,
10707
+ ...times !== void 0 ? { times } : {}
10592
10708
  });
10593
10709
  else modelSlices.push({
10594
10710
  key: "gen",
@@ -11621,7 +11737,7 @@ window.__ModuleLoader__.load({
11621
11737
  }
11622
11738
  //#endregion
11623
11739
  //#region \0dsh-global-css:/home/runner/work/dsh-context/dsh-context/src/client/styles/settings.css.mjs
11624
- const css$11 = ".lc-settings-card{background:var(--dsw-alias-bg-layer-3);border:1px solid var(--dsw-alias-border-l2);transition:border-color var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out), background-color var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out);border-radius:12px}.lc-settings-card:hover{border-color:var(--dsw-alias-label-dimmed)}.lc-settings-open,.lc-settings-open:hover{background:var(--dsw-alias-bg-layer-2);border-color:var(--dsw-alias-label-dimmed)}.lc-settings-head{appearance:none;width:100%;font:inherit;color:inherit;text-align:left;cursor:pointer;background:0 0;border:0;border-radius:12px;align-items:center;gap:12px;padding:14px 16px;display:flex}.lc-settings-headtext{flex-direction:column;flex:1;gap:4px;min-width:0;display:flex}.lc-settings-name{color:var(--dsw-alias-label-primary);font-size:15px;font-weight:600;line-height:1.4}.lc-settings-desc{color:var(--dsw-alias-label-tertiary);font-size:13px;line-height:1.5}.lc-settings-chevron{color:var(--dsw-alias-label-tertiary);transition:transform var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out);flex:none}.lc-settings-open .lc-settings-chevron{transform:rotate(180deg)}.lc-settings-body{border-top:1px solid var(--dsw-alias-border-l2);margin:0 16px;padding:4px 0 12px}.lc-settings-row{align-items:center;gap:8px;padding:8px 0;display:flex}.lc-settings-label{min-width:0;color:var(--dsw-alias-label-primary);flex:1;font-size:14px}.lc-settings-select{background:var(--dsw-alias-bg-module-platform);height:36px;font:inherit;color:var(--dsw-alias-label-primary);cursor:pointer;transition:background-color var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out);border:none;border-radius:18px;align-items:center;gap:12px;padding:0 14px;font-size:14px;line-height:22px;display:inline-flex}.lc-settings-note{color:var(--dsw-alias-label-tertiary);margin:12px 0 4px;font-size:12px;line-height:1.5}";
11740
+ const css$11 = ".lc-settings-card{background:var(--dsw-alias-bg-layer-3);border:1px solid var(--dsw-alias-border-l2);transition:border-color var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out), background-color var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out);border-radius:12px}.lc-settings-card:hover{border-color:var(--dsw-alias-label-dimmed)}.lc-settings-open,.lc-settings-open:hover{background:var(--dsw-alias-bg-layer-2);border-color:var(--dsw-alias-label-dimmed)}.lc-settings-head{appearance:none;width:100%;font:inherit;color:inherit;text-align:left;cursor:pointer;background:0 0;border:0;border-radius:12px;align-items:center;gap:12px;padding:14px 16px;display:flex}.lc-settings-headtext{flex-direction:column;flex:1;gap:4px;min-width:0;display:flex}.lc-settings-name{color:var(--dsw-alias-label-primary);font-size:15px;font-weight:600;line-height:1.4}.lc-settings-desc{color:var(--dsw-alias-label-tertiary);font-size:13px;line-height:1.5}.lc-settings-chevron{color:var(--dsw-alias-label-tertiary);transition:transform var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out);flex:none}.lc-settings-open .lc-settings-chevron{transform:rotate(180deg)}.lc-settings-body{border-top:1px solid var(--dsw-alias-border-l2);margin:0 16px;padding:4px 0 12px}.lc-settings-prefs{padding:4px 0}.lc-settings-row{align-items:center;gap:8px;padding:8px 0;display:flex}.lc-settings-label{min-width:0;color:var(--dsw-alias-label-primary);flex:1;font-size:14px}.lc-settings-select{background:var(--dsw-alias-bg-module-platform);height:36px;font:inherit;color:var(--dsw-alias-label-primary);cursor:pointer;transition:background-color var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out);border:none;border-radius:18px;align-items:center;gap:12px;padding:0 14px;font-size:14px;line-height:22px;display:inline-flex}.lc-settings-note{color:var(--dsw-alias-label-tertiary);margin:12px 0 4px;font-size:12px;line-height:1.5}";
11625
11741
  const tagId$11 = "dsh-context/settings.css";
11626
11742
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$11) + "]") === null) {
11627
11743
  const tag = document.createElement("style");
@@ -11839,6 +11955,14 @@ window.__ModuleLoader__.load({
11839
11955
  locale: NS
11840
11956
  }, (props) => (0, react.createElement)(OverviewPanel, props));
11841
11957
  });
11958
+ /** The injected face both preference cards ride: the settings store as the
11959
+ * framework's hooks-compartment `useContextSettings` seat, plus the set verb. */
11960
+ const cardFace = () => ({
11961
+ hooks: { contextSettings: settings.store },
11962
+ set: (field, value) => {
11963
+ settings.set(field, value);
11964
+ }
11965
+ });
11842
11966
  ctx.inject(["settingsScope"], (raw) => {
11843
11967
  const c = raw;
11844
11968
  const binder = c.settingsScope;
@@ -11850,15 +11974,27 @@ window.__ModuleLoader__.load({
11850
11974
  name: "settings.plugin.item",
11851
11975
  key: NS,
11852
11976
  locale: NS,
11853
- inject: () => ({
11854
- hooks: { contextSettings: settings.store },
11855
- set: (field, value) => {
11856
- settings.set(field, value);
11857
- }
11858
- })
11977
+ inject: cardFace
11859
11978
  }, (props) => (0, react.createElement)(SettingsCard, props));
11860
11979
  });
11861
11980
  });
11981
+ ctx.inject(["configForms"], (raw) => {
11982
+ const c = raw;
11983
+ const forms = c.configForms;
11984
+ if (forms === void 0 || typeof forms.get !== "function" || typeof forms.whileServed !== "function") return;
11985
+ c.effect(() => settings.attach(forms.get(NS)), "dsh-context: config forms");
11986
+ const PluginConfigCard = makePluginConfigCard(kit);
11987
+ c.effect(() => forms.whileServed([NS], () => {
11988
+ return c.slots.inject("plugins.bundle.config", () => {
11989
+ return c.slots.register({
11990
+ name: "plugins.bundle.config",
11991
+ key: NS,
11992
+ locale: NS,
11993
+ inject: cardFace
11994
+ }, (props) => (0, react.createElement)(PluginConfigCard, props));
11995
+ });
11996
+ }), "dsh-context: plugins-page card");
11997
+ });
11862
11998
  }
11863
11999
  module.exports = {
11864
12000
  name: "dsh-context",