dsh-context 0.21.1 → 0.22.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.
Files changed (3) hide show
  1. package/README.md +8 -0
  2. package/lib/client.js +197 -27
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -86,6 +86,14 @@ Six collapsible category sections (system prompt, tool schemas, user messages, i
86
86
  - **Linked with the history chart** — hover any bar in the History card and the browser previews that step instantly; leave the chart and it returns to your own pick. Keep a category open while scrubbing to compare one category across steps.
87
87
  - **Honest about coverage** — steps before a compaction are reconstructed from the removed-message archive, and the card says so when a step's makeup is only approximate. Elements older than the loaded chat window page older history in automatically when you expand them, and live injections (AGENTS.md, session-start context, …) are always listed — never a token sum without its items.
88
88
 
89
+ ### 🖼 Multimodal — image attachments in full view (DeepSeek Harness 0.1.1+)
90
+
91
+ Fully adapted to DeepSeek Harness 0.1.1's multimodal pipeline and the vision capability of **DeepSeek-V4-Flash-Vision-Exp**. A user message carrying images expands into a card layout — prose in the text card (with the usual raw/Markdown toggle), each image attachment as a thumbnail card with its name, normalized dimensions (plus the pre-normalization size when 0.1.1's image pipeline downscaled it) and stored size, and anything unrecognized as raw content:
92
+
93
+ ![Context browser rendering image attachments](https://raw.githubusercontent.com/bowenliang123/dsh-context/main/docs/context-browser-images.png)
94
+
95
+ Images load through the harness's own session-authorized loader — the same one the chat history uses — and degrade to metadata-only cards when it is unavailable. Image blocks in assistant messages and tool results (e.g. `read_image` output) now render too, instead of being silently dropped.
96
+
89
97
  ## Like it?
90
98
 
91
99
  If `dsh-context` helped you understand what your agent is carrying around, a ⭐ on [GitHub](https://github.com/bowenliang123/dsh-context) is much appreciated — and issues/PRs are welcome!
package/lib/client.js CHANGED
@@ -116,7 +116,22 @@ var DICT_ZH = {
116
116
  "rich.raw": "\u539F\u6587",
117
117
  "rich.md": "Markdown",
118
118
  "rich.toMd": "\u6309 Markdown \u6E32\u67D3\u67E5\u770B",
119
- "rich.toRaw": "\u67E5\u770B\u539F\u59CB\u6587\u672C"
119
+ "rich.toRaw": "\u67E5\u770B\u539F\u59CB\u6587\u672C",
120
+ // Assistant-message block titles inside the detail card: the reasoning
121
+ // trace and the response each carry a small label (Reasoning / Response,
122
+ // mirroring the harness's own block + locale vocabulary).
123
+ "block.thinking": "\u601D\u8003",
124
+ "block.answer": "\u56DE\u7B54",
125
+ // Attachment cards in a message detail: image refs render as thumbnail
126
+ // cards (name + normalized dims + stored size), anything unrecognized
127
+ // falls into the generic raw-JSON card.
128
+ "attach.images": "\u56FE\u7247\u9644\u4EF6",
129
+ "attach.other": "\u5176\u4ED6\u5185\u5BB9",
130
+ "attach.image": "\u56FE\u7247",
131
+ "attach.open": "\u67E5\u770B\u539F\u56FE",
132
+ "attach.loading": "\u2026",
133
+ "attach.loadFailed": "\u52A0\u8F7D\u5931\u8D25 \xB7 \u70B9\u51FB\u91CD\u8BD5",
134
+ "attach.orig": "\u539F\u56FE {d}"
120
135
  };
121
136
  var DICT_EN = {
122
137
  "tab": "Context",
@@ -227,7 +242,22 @@ var DICT_EN = {
227
242
  "rich.raw": "Raw",
228
243
  "rich.md": "Markdown",
229
244
  "rich.toMd": "View as Markdown",
230
- "rich.toRaw": "View Raw Text"
245
+ "rich.toRaw": "View Raw Text",
246
+ // Assistant-message block titles inside the detail card: the reasoning
247
+ // trace and the response each carry a small label (Reasoning / Response,
248
+ // mirroring the harness's own block + locale vocabulary).
249
+ "block.thinking": "Reasoning",
250
+ "block.answer": "Response",
251
+ // Attachment cards in a message detail: image refs render as thumbnail
252
+ // cards (name + normalized dims + stored size), anything unrecognized
253
+ // falls into the generic raw-JSON card.
254
+ "attach.images": "Images",
255
+ "attach.other": "Other content",
256
+ "attach.image": "Image",
257
+ "attach.open": "Open full image",
258
+ "attach.loading": "\u2026",
259
+ "attach.loadFailed": "Load failed \xB7 click to retry",
260
+ "attach.orig": "original {d}"
231
261
  };
232
262
 
233
263
  // src/client/modalStore.ts
@@ -467,6 +497,96 @@ function makeNodeList(kit) {
467
497
  };
468
498
  }
469
499
 
500
+ // src/client/format.ts
501
+ function fmt(n) {
502
+ if (n === void 0 || n === null || isNaN(n)) return "\u2014";
503
+ if (n >= 1e6) return (n / 1e6).toFixed(1) + "M";
504
+ if (n >= 1e3) return (n / 1e3).toFixed(1) + "k";
505
+ return String(Math.round(n));
506
+ }
507
+ function fmtBytes(n) {
508
+ if (n === void 0 || n === null || isNaN(n) || n < 0) return "\u2014";
509
+ if (n >= 1e6) return (n / 1e6).toFixed(1) + " MB";
510
+ if (n >= 1e3) return (n / 1e3).toFixed(1) + " kB";
511
+ return String(Math.round(n)) + " B";
512
+ }
513
+ function fmtTime(t) {
514
+ const d = new Date(t);
515
+ if (isNaN(d.getTime())) return "\u2014";
516
+ return d.toLocaleTimeString("en-GB", { hour12: false });
517
+ }
518
+
519
+ // src/client/components/images.tsx
520
+ function imageRefOf(block) {
521
+ if (block === null || typeof block !== "object") return null;
522
+ const b = block;
523
+ if (b.type !== "image" && b.kind !== "image") return null;
524
+ const a = b.attachment;
525
+ if (a === null || typeof a !== "object") return null;
526
+ const r = a;
527
+ if (typeof r.attachmentId !== "string" || r.attachmentId === "") return null;
528
+ const num = (v) => typeof v === "number" && Number.isFinite(v) && v > 0 ? v : void 0;
529
+ const orig = r.originalDimensions !== null && typeof r.originalDimensions === "object" ? r.originalDimensions : void 0;
530
+ const origDims = orig !== void 0 && num(orig.width) !== void 0 && num(orig.height) !== void 0 ? { width: num(orig.width), height: num(orig.height) } : void 0;
531
+ return {
532
+ attachmentId: r.attachmentId,
533
+ ...typeof r.name === "string" && r.name !== "" ? { name: r.name } : {},
534
+ ...num(r.bytes) !== void 0 ? { bytes: num(r.bytes) } : {},
535
+ ...num(r.width) !== void 0 ? { width: num(r.width) } : {},
536
+ ...num(r.height) !== void 0 ? { height: num(r.height) } : {},
537
+ ...origDims !== void 0 ? { originalDimensions: origDims } : {}
538
+ };
539
+ }
540
+ function makeImageCard(kit) {
541
+ const { t } = kit;
542
+ return function ImageCard(props) {
543
+ const { attachment, load } = props;
544
+ const [src, setSrc] = React.useState(null);
545
+ const [error, setError] = React.useState(false);
546
+ const [attempt, setAttempt] = React.useState(0);
547
+ React.useEffect(() => {
548
+ if (load === void 0) return;
549
+ let live = true;
550
+ setError(false);
551
+ setSrc(null);
552
+ void load(attachment).then((url) => {
553
+ if (live) setSrc(url);
554
+ }).catch(() => {
555
+ if (live) setError(true);
556
+ });
557
+ return () => {
558
+ live = false;
559
+ };
560
+ }, [attachment, load, attempt]);
561
+ const name = attachment.name ?? t("attach.image");
562
+ const dims = attachment.width !== void 0 && attachment.height !== void 0 ? attachment.width + "\xD7" + attachment.height : null;
563
+ const orig = attachment.originalDimensions;
564
+ const facts = [];
565
+ if (dims !== null) facts.push(dims);
566
+ if (orig !== void 0) facts.push(t("attach.orig", { d: orig.width + "\xD7" + orig.height }));
567
+ if (attachment.bytes !== void 0) facts.push(fmtBytes(attachment.bytes));
568
+ const open = () => {
569
+ if (src === null) return;
570
+ try {
571
+ window.open(src, "_blank", "noopener");
572
+ } catch {
573
+ }
574
+ };
575
+ return /* @__PURE__ */ React.createElement("div", { className: "lc-att-item" }, /* @__PURE__ */ React.createElement(
576
+ "button",
577
+ {
578
+ type: "button",
579
+ className: "lc-att-thumb",
580
+ title: error ? t("attach.loadFailed") : t("attach.open"),
581
+ onClick: error ? () => {
582
+ setAttempt((a) => a + 1);
583
+ } : open
584
+ },
585
+ src !== null ? /* @__PURE__ */ React.createElement("img", { src, alt: name }) : /* @__PURE__ */ React.createElement("span", { className: error ? "lc-att-err" : "lc-att-ph" }, error ? "\u26A0" : load === void 0 ? "\u{1F5BC}" : t("attach.loading"))
586
+ ), /* @__PURE__ */ React.createElement("div", { className: "lc-att-meta" }, /* @__PURE__ */ React.createElement("span", { className: "lc-att-name", title: name }, name), facts.length > 0 ? /* @__PURE__ */ React.createElement("span", { className: "lc-att-dims" }, facts.join(" \xB7 ")) : null));
587
+ };
588
+ }
589
+
470
590
  // src/client/components/richText.tsx
471
591
  var import_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
472
592
  function makeRichText(kit) {
@@ -601,7 +721,7 @@ function ToolSchema(props) {
601
721
  ), jsonOpen ? /* @__PURE__ */ React.createElement("pre", { className: "lc-br-pre lc-br-dim" }, schemaJson) : null) : null);
602
722
  }
603
723
  function RawBlocks(props) {
604
- const { rich } = props;
724
+ const { rich, img } = props;
605
725
  return /* @__PURE__ */ React.createElement(React.Fragment, null, props.blocks.map((b, i) => {
606
726
  const blk = b;
607
727
  if (blk !== null && typeof blk === "object" && (blk.type === "text" || blk.type === "reasoning") && typeof blk.text === "string") {
@@ -610,14 +730,16 @@ function RawBlocks(props) {
610
730
  }
611
731
  return /* @__PURE__ */ React.createElement(rich.RichText, { key: i, text: blk.text, mode: props.mode });
612
732
  }
733
+ const image = imageRefOf(b);
734
+ if (image !== null) return /* @__PURE__ */ React.createElement(img.Card, { key: i, attachment: image, load: img.load });
613
735
  if (blk !== null && typeof blk === "object" && blk.type === "tool-result" && Array.isArray(blk.content)) {
614
- return /* @__PURE__ */ React.createElement(RawBlocks, { key: i, blocks: blk.content, mode: props.mode, rich });
736
+ return /* @__PURE__ */ React.createElement(RawBlocks, { key: i, blocks: blk.content, mode: props.mode, rich, img });
615
737
  }
616
738
  return /* @__PURE__ */ React.createElement("pre", { key: i, className: "lc-br-pre lc-br-dim" }, JSON.stringify(b, null, 2));
617
739
  }));
618
740
  }
619
741
  function NodeContent(props) {
620
- const { node, conv, rich } = props;
742
+ const { node, conv, rich, img } = props;
621
743
  const richable = node.cat === "user" || node.cat === "assistant" || node.cat === "inject";
622
744
  const wrap = (render) => richable ? /* @__PURE__ */ React.createElement(rich.RichCard, { render }) : /* @__PURE__ */ React.createElement("div", { className: "lc-br-content" }, render("raw"));
623
745
  if (conv === void 0) {
@@ -631,25 +753,52 @@ function NodeContent(props) {
631
753
  return wrap((mode) => /* @__PURE__ */ React.createElement(React.Fragment, null, blocks.map((b, i) => {
632
754
  const blk = b;
633
755
  if (blk.kind === "text" && typeof blk.text === "string") {
634
- return /* @__PURE__ */ React.createElement(rich.RichText, { key: i, text: blk.text, mode });
756
+ return /* @__PURE__ */ React.createElement("div", { key: i, className: "lc-br-blk" }, /* @__PURE__ */ React.createElement("div", { className: "lc-br-blk-label" }, props.labels.answer), /* @__PURE__ */ React.createElement(rich.RichText, { text: blk.text, mode }));
635
757
  }
636
758
  if (blk.kind === "reasoning" && typeof blk.text === "string") {
637
- return /* @__PURE__ */ React.createElement("pre", { key: i, className: "lc-br-pre lc-br-dim" }, blk.text);
759
+ return /* @__PURE__ */ React.createElement("div", { key: i, className: "lc-br-blk" }, /* @__PURE__ */ React.createElement("div", { className: "lc-br-blk-label" }, props.labels.thinking), /* @__PURE__ */ React.createElement("pre", { className: "lc-br-pre lc-br-dim" }, blk.text));
638
760
  }
639
761
  if (blk.kind === "tool-call") {
640
762
  return /* @__PURE__ */ React.createElement("div", { key: i, className: "lc-br-call" }, /* @__PURE__ */ React.createElement("span", { className: "lc-br-tag" }, "\u2192 " + String(blk.name ?? "?")), typeof blk.argsRaw === "string" && blk.argsRaw !== "" ? /* @__PURE__ */ React.createElement("pre", { className: "lc-br-pre lc-br-dim" }, blk.argsRaw) : null);
641
763
  }
764
+ const image = imageRefOf(b);
765
+ if (image !== null) return /* @__PURE__ */ React.createElement(img.Card, { key: i, attachment: image, load: img.load });
642
766
  return null;
643
767
  })));
644
768
  }
645
769
  if (conv.kind === "tool-result") {
646
- return /* @__PURE__ */ React.createElement("div", { className: "lc-br-content" }, conv.call != null ? /* @__PURE__ */ React.createElement("div", { className: "lc-br-call" }, /* @__PURE__ */ React.createElement("span", { className: "lc-br-tag" }, "\u2190 " + conv.call.name), conv.call.argsRaw !== "" ? /* @__PURE__ */ React.createElement("pre", { className: "lc-br-pre lc-br-dim" }, conv.call.argsRaw) : null) : null, Array.isArray(conv.content) ? /* @__PURE__ */ React.createElement(RawBlocks, { blocks: conv.content, mode: "raw", rich }) : null);
770
+ return /* @__PURE__ */ React.createElement("div", { className: "lc-br-content" }, conv.call != null ? /* @__PURE__ */ React.createElement("div", { className: "lc-br-call" }, /* @__PURE__ */ React.createElement("span", { className: "lc-br-tag" }, "\u2190 " + conv.call.name), conv.call.argsRaw !== "" ? /* @__PURE__ */ React.createElement("pre", { className: "lc-br-pre lc-br-dim" }, conv.call.argsRaw) : null) : null, Array.isArray(conv.content) ? /* @__PURE__ */ React.createElement(RawBlocks, { blocks: conv.content, mode: "raw", rich, img }) : null);
647
771
  }
648
772
  if (conv.kind === "compaction") {
649
773
  return wrap((mode) => /* @__PURE__ */ React.createElement(React.Fragment, null, typeof conv.summary === "string" && conv.summary !== "" ? /* @__PURE__ */ React.createElement(rich.RichText, { text: conv.summary, mode }) : null));
650
774
  }
651
775
  if (Array.isArray(conv.content)) {
652
- return wrap((mode) => /* @__PURE__ */ React.createElement(RawBlocks, { blocks: conv.content, mode, rich }));
776
+ const blocks = conv.content;
777
+ if (node.cat !== "user") {
778
+ return wrap((mode) => /* @__PURE__ */ React.createElement(RawBlocks, { blocks, mode, rich, img }));
779
+ }
780
+ const texts = [];
781
+ const images = [];
782
+ const others = [];
783
+ for (const b of blocks) {
784
+ const blk = b;
785
+ if (blk !== null && typeof blk === "object" && blk.type === "text" && typeof blk.text === "string") {
786
+ texts.push(blk.text);
787
+ continue;
788
+ }
789
+ const image = imageRefOf(b);
790
+ if (image !== null) {
791
+ images.push(image);
792
+ continue;
793
+ }
794
+ others.push(b);
795
+ }
796
+ return /* @__PURE__ */ React.createElement("div", { className: "lc-br-content" }, texts.length > 0 ? (
797
+ // Un-padded RichCard: the outer lc-br-content already owns the
798
+ // body indent, so the text card aligns with the attachment cards
799
+ // instead of sitting one indent deeper.
800
+ /* @__PURE__ */ React.createElement(rich.RichCard, { className: "lc-br-rich-inline", render: (mode) => /* @__PURE__ */ React.createElement(React.Fragment, null, texts.map((text, i) => /* @__PURE__ */ React.createElement(rich.RichText, { key: i, text, mode }))) })
801
+ ) : null, images.length > 0 ? /* @__PURE__ */ React.createElement("div", { className: "lc-ts-card" }, /* @__PURE__ */ React.createElement("div", { className: "lc-ts-card-head" }, /* @__PURE__ */ React.createElement("b", null, props.labels.images), /* @__PURE__ */ React.createElement("span", { className: "lc-ts-card-count" }, images.length)), /* @__PURE__ */ React.createElement("div", { className: "lc-att-grid" }, images.map((a, i) => /* @__PURE__ */ React.createElement(img.Card, { key: a.attachmentId + ":" + i, attachment: a, load: img.load })))) : null, others.length > 0 ? /* @__PURE__ */ React.createElement("div", { className: "lc-ts-card" }, /* @__PURE__ */ React.createElement("div", { className: "lc-ts-card-head" }, /* @__PURE__ */ React.createElement("b", null, props.labels.other), /* @__PURE__ */ React.createElement("span", { className: "lc-ts-card-count" }, others.length)), others.map((b, i) => /* @__PURE__ */ React.createElement("pre", { key: i, className: "lc-br-pre lc-br-dim" }, JSON.stringify(b, null, 2)))) : null);
653
802
  }
654
803
  return /* @__PURE__ */ React.createElement("div", { className: "lc-br-content" }, /* @__PURE__ */ React.createElement("div", { className: "lc-br-note" }, props.hint));
655
804
  }
@@ -672,6 +821,7 @@ function makeContextBrowser(kit, StackedBar) {
672
821
  const { t, fmt: fmt3, fmtTime: fmtTime2, catLabel } = kit;
673
822
  const nodeText = makeNodeText(kit);
674
823
  const rich = makeRichText(kit);
824
+ const ImageCard = makeImageCard(kit);
675
825
  const catColor = {};
676
826
  for (const c of CATS) catColor[c.key] = c.color;
677
827
  const MAX_AUTO_PAGES = 20;
@@ -816,6 +966,13 @@ function makeContextBrowser(kit, StackedBar) {
816
966
  node: n,
817
967
  conv: bySeq.get(n.seq),
818
968
  rich,
969
+ img: { Card: ImageCard, load: props.loadImage },
970
+ labels: {
971
+ thinking: t("block.thinking"),
972
+ answer: t("block.answer"),
973
+ images: t("attach.images"),
974
+ other: t("attach.other")
975
+ },
819
976
  hint: bySeq.get(n.seq) === void 0 && awaiting ? t("browser.loading") : t("browser.noContent")
820
977
  }
821
978
  )
@@ -1422,6 +1579,11 @@ var STYLES = [
1422
1579
  ".lc-br-pre { margin: 0; padding: 8px 10px; background: var(--dsw-alias-bg-layer-2); border: 1px solid var(--dsw-alias-border-l1); border-radius: 6px; color: var(--dsw-alias-label-primary); font-size: 12px; line-height: 1.55; white-space: pre-wrap; word-break: break-word; max-height: 320px; overflow-y: auto; scrollbar-width: thin; }",
1423
1580
  ".lc-br-dim { color: var(--dsw-alias-label-secondary); }",
1424
1581
  ".lc-br-call { display: flex; flex-direction: column; gap: 4px; align-items: flex-start; }",
1582
+ // Assistant-message block title: a slim label line above each block
1583
+ // (thinking / answer) so a mixed reply stays scannable — same weight and
1584
+ // tone as the tool-schema card heads, without an extra bordered card.
1585
+ ".lc-br-blk { display: flex; flex-direction: column; gap: 4px; }",
1586
+ ".lc-br-blk-label { font-size: 11px; font-weight: 600; color: var(--dsw-alias-label-secondary); }",
1425
1587
  // ---- Tool schema body (browser card, tools category). The description
1426
1588
  // and parameter table share one "card with a small title" chrome — a
1427
1589
  // labeled bordered block so a reader can scan three stacked sections
@@ -1481,26 +1643,27 @@ var STYLES = [
1481
1643
  ".lc-br-md div p, .lc-ts-desc-md div p { margin: 6px 0; }",
1482
1644
  ".lc-br-md div pre, .lc-ts-desc-md div pre { margin: 6px 0; }",
1483
1645
  ".lc-br-md div :not(pre) > code, .lc-ts-desc-md div :not(pre) > code { font-size: 1em !important; line-height: 1.3; padding: 1px 5px; box-decoration-break: clone; }",
1484
- ".lc-br-md div table code, .lc-ts-desc-md div table code { font-size: inherit; }"
1646
+ ".lc-br-md div table code, .lc-ts-desc-md div table code { font-size: inherit; }",
1647
+ // Un-padded RichCard variant: used when the text card is one section
1648
+ // among several (attachment cards) inside an already-indented body.
1649
+ ".lc-br-rich-inline { display: flex; flex-direction: column; gap: 6px; }",
1650
+ // ---- Attachment cards (image refs in a message detail). The card chrome
1651
+ // reuses the tool-schema sections' `lc-ts-card`/`lc-ts-card-head`; only the
1652
+ // image grid and tile are new. Each image is a 64px cover tile (the chat
1653
+ // history's tile size) beside a name + dims + size column. ----
1654
+ ".lc-ts-card .lc-br-pre { margin: 8px 10px; }",
1655
+ ".lc-att-grid { display: flex; flex-wrap: wrap; gap: 10px; padding: 8px 10px; }",
1656
+ ".lc-att-item { display: flex; gap: 8px; align-items: center; min-width: 0; }",
1657
+ ".lc-att-thumb { width: 64px; height: 64px; flex: none; padding: 0; display: flex; align-items: center; justify-content: center; border: 1px solid var(--dsw-alias-border-l1); border-radius: 6px; overflow: hidden; background: var(--dsw-alias-bg-layer-1); cursor: pointer; font: inherit; }",
1658
+ ".lc-att-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }",
1659
+ ".lc-att-ph, .lc-att-err { font-size: 10px; color: var(--dsw-alias-label-secondary); text-align: center; padding: 2px; }",
1660
+ ".lc-att-meta { display: flex; flex-direction: column; gap: 2px; min-width: 0; }",
1661
+ ".lc-att-name { font-size: 12px; color: var(--dsw-alias-label-primary); max-width: 200px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }",
1662
+ ".lc-att-dims { font-size: 11px; color: var(--dsw-alias-label-secondary); }"
1485
1663
  ].join("\n");
1486
1664
 
1487
1665
  // src/client/components/events.tsx
1488
1666
  var import_dsh_client_ui_primitives2 = require("@deepseek-ai/dsh-client-ui-primitives");
1489
-
1490
- // src/client/format.ts
1491
- function fmt(n) {
1492
- if (n === void 0 || n === null || isNaN(n)) return "\u2014";
1493
- if (n >= 1e6) return (n / 1e6).toFixed(1) + "M";
1494
- if (n >= 1e3) return (n / 1e3).toFixed(1) + "k";
1495
- return String(Math.round(n));
1496
- }
1497
- function fmtTime(t) {
1498
- const d = new Date(t);
1499
- if (isNaN(d.getTime())) return "\u2014";
1500
- return d.toLocaleTimeString("en-GB", { hour12: false });
1501
- }
1502
-
1503
- // src/client/components/events.tsx
1504
1667
  var EVENT_ICONS = { compaction: "\u2702", prune: "\u2702", inject: "\uFF0B", model: "\u21C4" };
1505
1668
  function makeEventText(t) {
1506
1669
  function eventLabel(ev) {
@@ -1563,7 +1726,7 @@ function makeEventList(kit) {
1563
1726
 
1564
1727
  // src/client/meta.ts
1565
1728
  var PLUGIN_NAME = "dsh-context";
1566
- var PLUGIN_VERSION = true ? "0.21.1" : "0.0.0-dev";
1729
+ var PLUGIN_VERSION = true ? "0.22.0" : "0.0.0-dev";
1567
1730
  var PLUGIN_REPO = true ? "https://github.com/bowenliang123/dsh-context" : "https://github.com/bowenliang123/dsh-context";
1568
1731
  var PLUGIN_REPO_SHORT = PLUGIN_REPO.replace(/^https?:\/\/github\.com\//, "");
1569
1732
 
@@ -1930,6 +2093,12 @@ function makeContextView(ctx, kit) {
1930
2093
  const clearToolFocus = React.useCallback(() => {
1931
2094
  setToolFocus(null);
1932
2095
  }, []);
2096
+ const loadImage = React.useMemo(() => {
2097
+ if (typeof sessionId !== "string" || sessionId === "") return void 0;
2098
+ const conversation = ctx.get("conversation");
2099
+ if (conversation === void 0 || typeof conversation.resolveImage !== "function") return void 0;
2100
+ return (attachment) => conversation.resolveImage(sessionId, attachment);
2101
+ }, [sessionId]);
1933
2102
  const rootRef = React.useRef(null);
1934
2103
  const scrollerRef = React.useRef(null);
1935
2104
  const restoredRef = React.useRef(null);
@@ -2041,7 +2210,8 @@ function makeContextView(ctx, kit) {
2041
2210
  hoverKey: hoverCat,
2042
2211
  onHoverKey: setHoverCat,
2043
2212
  toolFocus,
2044
- onToolFocusHandled: clearToolFocus
2213
+ onToolFocusHandled: clearToolFocus,
2214
+ loadImage
2045
2215
  }
2046
2216
  ))), /* @__PURE__ */ React.createElement("div", { className: "lc-cols" }, /* @__PURE__ */ React.createElement("div", { className: "lc-card lc-col" }, /* @__PURE__ */ React.createElement("div", { className: "lc-card-title" }, /* @__PURE__ */ React.createElement("span", { className: "lc-card-title-text" }, t("events.title")), /* @__PURE__ */ React.createElement("div", { className: "lc-kinds" }, EVENT_KINDS.map((k) => /* @__PURE__ */ React.createElement(
2047
2217
  "button",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-context",
3
- "version": "0.21.1",
3
+ "version": "0.22.0",
4
4
  "description": "A DeepSeek Harness plugin for context insight and management, with context dashboard and context command, for understanding how the context is made of, and how it evolves.",
5
5
  "author": "bowenliang123",
6
6
  "repository": {