dsh-custom-mode 1.9.15 → 1.9.17

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 +1 -1
  2. package/client.js +199 -76
  3. package/package.json +9 -2
package/README.md CHANGED
@@ -47,7 +47,7 @@ The mode is **web-profile only**: `agent-presets`, the service that mounts prese
47
47
 
48
48
  ## Requirements
49
49
 
50
- - **dsh `>=0.1.5-rc.2 <0.2.0-0 || >=0.1.6-alpha.1 <0.2.0-0 || >=0.1.7-alpha.1 <0.2.0-0`**(latest stable `0.1.5-rc.2` and the preview line `0.1.6-alpha.2` / `0.1.7-alpha.*`)** — declared in `engines.dsh` and as an *optional* peer range. This package never
50
+ - **dsh `>=0.1.5-rc.2 <0.2.0-0 || >=0.1.6-alpha.1 <0.2.0-0 || >=0.1.7-alpha.1 <0.2.0-0`**(两条线:最新正式版 `0.1.5-rc.3` 与最新预览版 `0.1.7-rc.2`;更早的 `0.1.6-alpha.*` 与正式版同一套机制,仍在范围内但不单独测)** — declared in `engines.dsh` and as an *optional* peer range. This package never
51
51
  imports `@deepseek-ai/dsh`; it only reads the host services dsh injects.
52
52
  - **No build step, no dependencies**: the source is published as-is and uses only Node's standard library.
53
53
  - The package version is its own line (`1.0.0`, `1.0.1`, …). Which dsh it supports is declared in
package/client.js CHANGED
@@ -610,11 +610,57 @@ try {
610
610
  return { Button, Input, Switch, Tag, Pill }
611
611
  }
612
612
 
613
+ /**
614
+ * Whether an atom can be handed to `createElement`.
615
+ *
616
+ * ★ **This is where the page's native look was lost.** The probe used to be
617
+ * `typeof atoms.Button === "function"`, but the shell's components are `forwardRef(...)` /
618
+ * `memo(...)` **objects** (`Button = forwardRef(function Button(){…})`, measured), so the check
619
+ * failed and *every* control silently fell back to the plain stand-ins — hand-painted buttons,
620
+ * `input[type=checkbox]` instead of the shell's switch, a native `window.confirm` instead of its
621
+ * risk dialog. Measured on 0.1.7-rc.2 (2026-09-25): `require(ATOMS_MODULE)` returns **279 exports**
622
+ * including `Button`, `RiskConfirmation`, `Modal`, `Menu`, `SegmentedControl` and the icon set —
623
+ * the module was in the platform seed table the whole time.
624
+ * React renders any object carrying `$$typeof`, so that is what "usable" means here.
625
+ */
626
+ const renderable = (atom) =>
627
+ typeof atom === "function" || (atom !== null && typeof atom === "object" && atom.$$typeof !== undefined)
628
+
629
+ /**
630
+ * The page asks for icons under short names (`IconPlusOutline16`); the shell exports
631
+ * `IconPlusOutlineRegular`. Without this map the calls were `undefined` and **no icon ever
632
+ * rendered** — which is part of why the header row looked hand-made.
633
+ */
634
+ const ICON_ALIAS = {
635
+ IconChevronDownOutline14: "IconChevronDownOutlineRegular",
636
+ IconChevronUpOutline14: "IconChevronUpOutlineRegular",
637
+ IconChevronLeftOutline14: "IconChevronLeftOutlineRegular",
638
+ IconPlusOutline16: "IconPlusOutlineRegular",
639
+ IconCheckOutline14: "IconCheckOutlineRegular",
640
+ IconCopyOutline16: "IconCopyOutlineRegular",
641
+ IconDownloadOutline16: "IconDownloadOutlineRegular",
642
+ IconRefreshOutline16: "IconRefreshOutlineRegular",
643
+ IconTrashOutline16: "IconTrashOutlineRegular",
644
+ }
645
+
646
+ function withIconAliases(atoms) {
647
+ const out = Object.assign({}, atoms)
648
+ for (const short of Object.keys(ICON_ALIAS)) {
649
+ if (out[short] === undefined && renderable(atoms[ICON_ALIAS[short]])) out[short] = atoms[ICON_ALIAS[short]]
650
+ }
651
+ return out
652
+ }
653
+
613
654
  /** Resolve the atom set once, at factory time. */
614
655
  function loadAtoms() {
615
656
  try {
616
657
  const atoms = require(ATOMS_MODULE)
617
- if (atoms !== null && typeof atoms === "object" && typeof atoms.Button === "function") return atoms
658
+ if (atoms !== null && typeof atoms === "object" && renderable(atoms.Button)) return withIconAliases(atoms)
659
+ console.info(
660
+ "dsh-custom-mode: 壳提供了 " +
661
+ ATOMS_MODULE +
662
+ ",但里面没有可用的 Button,改用内置的朴素控件(功能一致,外观更简)。",
663
+ )
618
664
  } catch (error) {
619
665
  console.info(
620
666
  "dsh-custom-mode: 当前壳没有在种子表里提供 " +
@@ -638,44 +684,54 @@ try {
638
684
  */
639
685
  const CSS = [
640
686
  ".cpfe{--g:8px;display:flex;flex-direction:column;gap:24px;width:100%;max-width:900px;box-sizing:border-box;padding-bottom:16px}",
641
- ".cpfe-h{margin:0 0 4px;font-size:15px;line-height:22px;color:var(--dsw-alias-label-primary)}",
642
- ".cpfe-sub{margin:0 0 12px;font-size:13px;line-height:20px;color:var(--dsw-alias-label-secondary)}",
643
- // 一行提示 + 详情下拉(与插件行同一套语言)
644
- ".cpfe-hint{display:flex;flex-wrap:wrap;align-items:center;gap:6px;margin:0 0 10px}",
645
- ".cpfe-hint-line{flex:1;min-width:0;font-size:12px;line-height:17px;color:var(--dsw-alias-label-secondary);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}",
646
- ".cpfe-hint-toggle{flex:0 0 auto;width:20px;height:20px;padding:0;border:0;border-radius:6px;background:transparent;color:var(--dsw-alias-label-secondary);cursor:pointer;font-size:11px;line-height:1}",
647
- ".cpfe-hint-toggle:hover{background:var(--dsw-alias-bg-layer-2)}",
648
- ".cpfe-hint-detail{flex:1 0 100%;margin:0;font-size:12px;line-height:18px;color:var(--dsw-alias-label-secondary)}",
687
+ // ★ 区块标题对齐官方设置页的度量(实测 0.1.7-rc.2:区块标题 14px/22px w500,引言 12px/18px
688
+ // tertiary)。原来是 15px w700 —— 在官方页面里那看起来就是"另一个产品"。
689
+ ".cpfe-h{margin:0 0 2px;font-size:14px;line-height:22px;font-weight:500;color:var(--dsw-alias-label-primary)}",
690
+ ".cpfe-sub{margin:0 0 8px;font-size:12px;line-height:18px;color:var(--dsw-alias-label-tertiary)}",
691
+ // 区块引言:官方是标题下的一行 12px 灰字 —— 没有折叠按钮,也没有那个蓝色圆点。
692
+ ".cpfe-hint{display:flex;flex-direction:column;gap:2px;margin:0 0 4px}",
693
+ ".cpfe-hint-line{font-size:12px;line-height:18px;color:var(--dsw-alias-label-tertiary)}",
694
+ ".cpfe-hint-detail{margin:0;font-size:12px;line-height:18px;color:var(--dsw-alias-label-tertiary)}",
649
695
  // 描述:多行、自适应高度(没有多行输入组件,所以用 textarea + 同一批语义变量)
650
696
  ".cpfe-desc{box-sizing:border-box;min-height:56px;max-height:200px;resize:vertical;padding:8px 12px;border-radius:10px;border:.5px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-base);color:var(--dsw-alias-label-primary);font:inherit;font-size:13px;line-height:20px;margin-bottom:8px}",
651
697
  ".cpfe-base-pending{color:var(--dsw-alias-state-warn-primary)}",
652
698
  ".cpfe-note{display:block;font-size:11px;line-height:16px;color:var(--dsw-alias-label-secondary)}",
653
699
  ".cpfe-mono{display:block;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:11px;line-height:16px;color:var(--dsw-alias-label-secondary);overflow-wrap:anywhere}",
654
700
  ".cpfe-pills{display:flex;flex-wrap:wrap;gap:6px;align-items:center}",
701
+ ".cpfe-picker{display:flex;align-items:center;gap:8px;margin:2px 0 4px}",
702
+ // 壳的菜单浮层靠一个独立的 backing 元素上色(实测在 0.1.7-rc.2 + headless 下那块是透明的,
703
+ // 文字会"压"在下面的输入框上)。用官方 token 显式补一层底色,真实浏览器与 headless 一致。
704
+ ".cpfe-menu{background:var(--dsw-menu-surface-fill,var(--dsw-alias-bg-layer-3));border-radius:var(--dsw-radius-lg);box-shadow:var(--dsw-elevation-soft)}",
705
+ ".cpfe-history{display:flex;align-items:center;gap:8px;flex-wrap:wrap;margin-top:10px}",
706
+ ".cpfe-history-label{font-size:12px;line-height:18px;color:var(--dsw-alias-label-tertiary)}",
707
+ ".cpfe-history-hint{font-size:12px;line-height:18px;color:var(--dsw-alias-label-tertiary)}",
655
708
  ".cpfe-pill-wrap{display:inline-flex;align-items:center;gap:4px}",
656
709
  ".cpfe-actions{display:flex;flex-wrap:wrap;gap:var(--g);align-items:center;margin-top:4px}",
657
710
  ".cpfe-newrow{display:flex;gap:var(--g);align-items:center;flex-wrap:wrap;margin-top:10px}",
658
711
  ".cpfe-field{display:flex;width:100%;margin-bottom:8px}",
659
- ".cpfe-row-head{font-size:13px;line-height:18px;color:var(--dsw-alias-label-primary)}",
712
+ ".cpfe-row-head{font-size:14px;line-height:22px;color:var(--dsw-alias-label-primary)}",
660
713
  ".cpfe-row-switch{flex:0 0 auto}",
661
714
  ".cpfe-row-line{display:flex;align-items:center;gap:8px;flex-wrap:wrap;min-width:0}",
662
- ".cpfe-row-note{min-height:16px;font-size:12px;line-height:16px;color:var(--dsw-alias-label-secondary);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}",
715
+ ".cpfe-row-note{min-height:18px;font-size:12px;line-height:18px;color:var(--dsw-alias-label-tertiary);overflow:hidden;text-overflow:ellipsis}",
663
716
  ".cpfe-row-toggle{flex:0 0 auto;width:22px;height:22px;padding:0;border:0;border-radius:6px;background:transparent;color:var(--dsw-alias-label-secondary);cursor:pointer;font-size:11px;line-height:1}",
664
717
  ".cpfe-row-toggle:hover{background:var(--dsw-alias-bg-layer-2)}",
665
718
  ".cpfe-row-open{border-color:var(--dsw-alias-border-l2)}",
666
- ".cpfe-row-detail{display:flex;flex-direction:column;gap:4px;margin:0 0 2px 14px;padding:8px 12px;border-left:.5px solid var(--dsw-alias-border-l1);font-size:12px;line-height:17px;color:var(--dsw-alias-label-secondary)}",
719
+ ".cpfe-row-detail{display:flex;flex-direction:column;gap:4px;margin:0 0 12px;padding:0 0 0 16px;border:0;font-size:12px;line-height:18px;color:var(--dsw-alias-label-tertiary)}",
667
720
  ".cpfe-detail-line{display:flex;gap:8px;min-width:0}",
668
721
  ".cpfe-detail-key{flex:0 0 62px;color:var(--dsw-alias-label-secondary)}",
669
722
  ".cpfe-detail-value{min-width:0;overflow-wrap:anywhere}",
670
723
  ".cpfe-sec-head{display:flex;align-items:center;justify-content:space-between;gap:var(--g);flex-wrap:wrap}",
671
724
  ".cpfe-newinput{flex:1;min-width:200px}",
672
725
  // 单列、行高统一(对齐官方插件页的形态);之前的自适应多列网格会让行高参差不齐。
673
- ".cpfe-rows{display:flex;flex-direction:column;gap:6px;margin-top:4px}",
674
- ".cpfe-line{display:flex;flex-direction:column;gap:6px;min-width:0}",
675
- ".cpfe-group{display:flex;flex-direction:column;gap:6px;margin-top:8px;min-width:0}",
676
- ".cpfe-kids{display:flex;flex-direction:column;gap:6px;margin-left:14px;padding-left:14px;border-left:.5px solid var(--dsw-alias-border-l1)}",
677
- ".cpfe-row{display:flex;gap:10px;align-items:center;box-sizing:border-box;min-height:52px;padding:8px 12px;border-radius:10px;border:.5px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-layer-1)}",
678
- ".cpfe-row-meta{display:flex;flex-direction:column;gap:3px;min-width:0;flex:1}",
726
+ // ★ 对齐官方设置行(实测 0.1.7-rc.2 的行:padding 16px 0、无背景、无圆角,行间只有一条 1px
727
+ // 分隔线)。原来是"圆角卡片 + 描边 + 灰底" —— 那正是廉价感最重的部分。
728
+ ".cpfe-rows{display:flex;flex-direction:column;gap:0;margin-top:0}",
729
+ ".cpfe-line{display:flex;flex-direction:column;min-width:0;border-bottom:1px solid var(--dsw-alias-border-l1)}",
730
+ ".cpfe-line:last-child{border-bottom:0}",
731
+ ".cpfe-group{display:flex;flex-direction:column;min-width:0}",
732
+ ".cpfe-kids{display:flex;flex-direction:column;margin-left:0;padding-left:16px}",
733
+ ".cpfe-row{display:flex;gap:12px;align-items:center;box-sizing:border-box;min-height:0;padding:16px 0;border:0;border-radius:0;background:none}",
734
+ ".cpfe-row-meta{display:flex;flex-direction:column;gap:4px;min-width:0;flex:1}",
679
735
  ".cpfe-row-badges{display:flex;align-items:center;gap:6px;flex-wrap:wrap}",
680
736
  ".cpfe-editor{box-sizing:border-box;width:100%;min-height:240px;resize:vertical;padding:12px;border-radius:10px;border:.5px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-base);color:var(--dsw-alias-label-primary);font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:13px;line-height:20px}",
681
737
  ".cpfe-bar{display:flex;align-items:center;gap:var(--g);flex-wrap:wrap}",
@@ -825,30 +881,14 @@ try {
825
881
  * the page scannable without dropping a word of the explanation.
826
882
  */
827
883
  function SectionHint(props) {
828
- const { id, hint, detail, expanded, onToggleExpand, t } = props
829
- const open = expanded[id] === true
830
- const collapsible = typeof detail === "string" && detail !== "" && detail !== hint
884
+ const { hint, detail } = props
885
+ // 官方设置页的区块引言就是标题下的一段 12px 灰字:没有圆点、没有折叠按钮,也**不重复**。
886
+ // 有完整说明就用完整说明(它是短句的超集),否则用短句 —— 两个都印会在屏幕上出现两遍。
887
+ const text = typeof detail === "string" && detail !== "" ? detail : hint
831
888
  return react.createElement(
832
889
  "div",
833
890
  { className: "cpfe-hint" },
834
- react.createElement("span", { className: "cpfe-hint-line", title: hint }, hint),
835
- collapsible === false
836
- ? null
837
- : react.createElement(
838
- "button",
839
- {
840
- type: "button",
841
- className: "cpfe-hint-toggle",
842
- "aria-expanded": open,
843
- "aria-label": t(open ? "aria.collapse" : "aria.expandHint"),
844
- title: t(open ? "aria.collapse" : "aria.expandHint"),
845
- onClick: () => onToggleExpand(id),
846
- },
847
- open ? "▾" : "▸",
848
- ),
849
- open === true && collapsible === true
850
- ? react.createElement("p", { className: "cpfe-hint-detail" }, detail)
851
- : null,
891
+ react.createElement("span", { className: "cpfe-hint-line" }, text),
852
892
  )
853
893
  }
854
894
 
@@ -1053,6 +1093,9 @@ try {
1053
1093
  * differs per user, so nothing is expanded by default and nothing is persisted.
1054
1094
  */
1055
1095
  const [expandedRows, setExpandedRows] = react.useState({})
1096
+ // 两个官方风格的下拉(壳的 `Menu`):助手切换与改动历史。壳不提供 Menu 时退回原生控件。
1097
+ const [assistantOpen, setAssistantOpen] = react.useState(false)
1098
+ const [historyOpen, setHistoryOpen] = react.useState(false)
1056
1099
  /** 本机这条线上有无法解析的行时,一键把它们关掉(服务端复用保存同一条排版手术)。 */
1057
1100
  const repairRowsNow = async () => {
1058
1101
  if (draft === null) return
@@ -1535,30 +1578,70 @@ try {
1535
1578
  : assistants.length === 0
1536
1579
  ? react.createElement("p", { className: "cpfe-sub" }, t("assistant.empty"))
1537
1580
  : react.createElement(
1581
+ // 官方的"选择"就是壳的 `Menu`:一个灰底按钮 + chevron,浮层里是选项
1582
+ // (通用设置页的「权限 / 语言 / 工作步骤展示」都是这个形态)。原来是一排 pill
1583
+ // 药丸 + 描边,是页面上最"不像官方"的一块。
1538
1584
  "div",
1539
- { className: "cpfe-pills cpfe-assistants" },
1540
- assistants.map((item) =>
1541
- react.createElement(
1542
- "span",
1543
- { key: item.id, className: "cpfe-pill-wrap" },
1544
- react.createElement(
1545
- A.Pill,
1546
- {
1547
- active: item.id === selected,
1548
- disabled: busy,
1549
- title: item.id,
1550
- onClick: () => pick(item.id),
1585
+ { className: "cpfe-picker" },
1586
+ renderable(A.Menu)
1587
+ ? react.createElement(A.Menu, {
1588
+ open: assistantOpen,
1589
+ // ★ portal:不把浮层渲染在我们的容器里。设置面板有裁剪与叠层,非 portal 的
1590
+ // 菜单会被裁掉表面,只剩文字"压"在下面的输入框上(实测截图如此)。
1591
+ portal: true,
1592
+ listClassName: "cpfe-menu",
1593
+ align: "start",
1594
+ side: "bottom",
1595
+ selectedId: selected,
1596
+ anchor: react.createElement(
1597
+ A.Button,
1598
+ {
1599
+ variant: "ghost",
1600
+ size: "sm",
1601
+ disabled: busy,
1602
+ icon: renderable(A.IconChevronDownOutline14)
1603
+ ? react.createElement(A.IconChevronDownOutline14, { size: 14 })
1604
+ : null,
1605
+ onClick: () => setAssistantOpen((open) => open !== true),
1606
+ },
1607
+ (assistants.find((item) => item.id === selected) || {}).name || selected || t("assistant.heading"),
1608
+ ),
1609
+ items: assistants.map((item) => ({
1610
+ id: item.id,
1611
+ label:
1612
+ (item.name || item.id) +
1613
+ (dirtyIds.has(item.id) ? " · " + t("msg.unsaved") : "") +
1614
+ (typeof item.broken === "string" && item.broken !== "" ? " · " + t("assistant.broken") : ""),
1615
+ })),
1616
+ onSelect: (id) => {
1617
+ setAssistantOpen(false)
1618
+ pick(id)
1551
1619
  },
1552
- item.name || item.id,
1620
+ onClose: () => setAssistantOpen(false),
1621
+ })
1622
+ : react.createElement(
1623
+ "div",
1624
+ { className: "cpfe-pills cpfe-assistants" },
1625
+ assistants.map((item) =>
1626
+ react.createElement(
1627
+ "span",
1628
+ { key: item.id, className: "cpfe-pill-wrap" },
1629
+ react.createElement(
1630
+ A.Pill,
1631
+ { active: item.id === selected, disabled: busy, title: item.id, onClick: () => pick(item.id) },
1632
+ item.name || item.id,
1633
+ ),
1634
+ dirtyIds.has(item.id) ? react.createElement(A.Tag, { tone: "warning" }, t("msg.unsaved")) : null,
1635
+ typeof item.broken === "string" && item.broken !== ""
1636
+ ? react.createElement(A.Tag, { tone: "danger" }, t("assistant.broken"))
1637
+ : null,
1638
+ ),
1639
+ ),
1553
1640
  ),
1554
- dirtyIds.has(item.id)
1555
- ? react.createElement(A.Tag, { tone: "warning" }, t("msg.unsaved"))
1556
- : null,
1557
- typeof item.broken === "string" && item.broken !== ""
1558
- ? react.createElement(A.Tag, { tone: "danger" }, t("assistant.broken"))
1559
- : null,
1560
- ),
1561
- ),
1641
+ dirtyIds.has(selected) ? react.createElement(A.Tag, { tone: "warning" }, t("msg.unsaved")) : null,
1642
+ typeof (assistants.find((item) => item.id === selected) || {}).broken === "string"
1643
+ ? react.createElement(A.Tag, { tone: "danger" }, t("assistant.broken"))
1644
+ : null,
1562
1645
  ),
1563
1646
  react.createElement(
1564
1647
  "div",
@@ -1579,7 +1662,7 @@ try {
1579
1662
  react.createElement(
1580
1663
  A.Button,
1581
1664
  {
1582
- variant: "primary",
1665
+ variant: "outline",
1583
1666
  icon: A.IconPlusOutline16 === undefined ? null : react.createElement(A.IconPlusOutline16),
1584
1667
  disabled: busy,
1585
1668
  onClick: () => create(),
@@ -1834,22 +1917,62 @@ try {
1834
1917
  "div",
1835
1918
  { className: "cpfe-history" },
1836
1919
  react.createElement("span", { className: "cpfe-history-label" }, t("history.label")),
1837
- react.createElement(
1838
- "select",
1839
- {
1840
- value: draft.historyPick,
1841
- "aria-label": t("history.label"),
1842
- onChange: (event) => update({ historyPick: event.target.value }),
1843
- },
1844
- react.createElement("option", { value: "" }, t("history.pick")),
1845
- ...draft.history.map((entry) =>
1846
- react.createElement(
1847
- "option",
1848
- { key: String(entry.n), value: String(entry.n) },
1849
- formatWhen(entry.at) + " · " + t("history.by." + entry.by) + " · " + String(entry.bytes) + " B",
1920
+ // 与助手切换同一个形态:官方的下拉是壳的 Menu(灰底按钮 + chevron + 浮层),
1921
+ // 而不是浏览器原生 `<select>`(后者的外观由浏览器决定,与官方界面完全不同)。
1922
+ renderable(A.Menu)
1923
+ ? react.createElement(A.Menu, {
1924
+ open: historyOpen,
1925
+ portal: true,
1926
+ listClassName: "cpfe-menu",
1927
+ align: "start",
1928
+ side: "bottom",
1929
+ selectedId: draft.historyPick,
1930
+ anchor: react.createElement(
1931
+ A.Button,
1932
+ {
1933
+ variant: "ghost",
1934
+ size: "sm",
1935
+ disabled: busy,
1936
+ icon: renderable(A.IconChevronDownOutline14)
1937
+ ? react.createElement(A.IconChevronDownOutline14, { size: 14 })
1938
+ : null,
1939
+ onClick: () => setHistoryOpen((open) => open !== true),
1940
+ },
1941
+ draft.historyPick === ""
1942
+ ? t("history.pick")
1943
+ : (() => {
1944
+ const entry = draft.history.find((item) => String(item.n) === draft.historyPick)
1945
+ return entry === undefined
1946
+ ? t("history.pick")
1947
+ : formatWhen(entry.at) + " · " + t("history.by." + entry.by)
1948
+ })(),
1949
+ ),
1950
+ items: draft.history.map((entry) => ({
1951
+ id: String(entry.n),
1952
+ label: formatWhen(entry.at) + " · " + t("history.by." + entry.by) + " · " + String(entry.bytes) + " B",
1953
+ })),
1954
+ onSelect: (id) => {
1955
+ setHistoryOpen(false)
1956
+ update({ historyPick: id })
1957
+ },
1958
+ onClose: () => setHistoryOpen(false),
1959
+ })
1960
+ : react.createElement(
1961
+ "select",
1962
+ {
1963
+ value: draft.historyPick,
1964
+ "aria-label": t("history.label"),
1965
+ onChange: (event) => update({ historyPick: event.target.value }),
1966
+ },
1967
+ react.createElement("option", { value: "" }, t("history.pick")),
1968
+ ...draft.history.map((entry) =>
1969
+ react.createElement(
1970
+ "option",
1971
+ { key: String(entry.n), value: String(entry.n) },
1972
+ formatWhen(entry.at) + " · " + t("history.by." + entry.by) + " · " + String(entry.bytes) + " B",
1973
+ ),
1974
+ ),
1850
1975
  ),
1851
- ),
1852
- ),
1853
1976
  react.createElement(
1854
1977
  A.Button,
1855
1978
  {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-custom-mode",
3
- "version": "1.9.15",
3
+ "version": "1.9.17",
4
4
  "description": "Custom modes and custom prompts for DeepSeek Harness (dsh): edit a mode's system prompt on the settings page (it takes effect on the next model step), pick its base mode, switch plugin rows one by one, and keep several assistants side by side — coding, chat or roleplay (RP) personas (multi-mode / multi-agent).",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -36,7 +36,14 @@
36
36
  "persona",
37
37
  "multi-agent",
38
38
  "roleplay",
39
- "chat"
39
+ "chat",
40
+ "deepseek",
41
+ "settings-page",
42
+ "自定义模式",
43
+ "系统提示词",
44
+ "提示词编辑",
45
+ "多助手",
46
+ "角色扮演"
40
47
  ],
41
48
  "dsh": {
42
49
  "bundle": {