@dsh-tom/dsh-css 0.2.0 → 0.2.8

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/README.md CHANGED
@@ -22,7 +22,7 @@ npm publish --access public --registry=https://registry.npmjs.org/
22
22
  打开 **插件 → 添加插件 → 安装第三方插件**,在“包名或地址”输入:
23
23
 
24
24
  ```text
25
- D:\product\dsh-css\dsh-tom-dsh-css-0.2.0.tgz
25
+ D:\product\dsh-css\dsh-tom-dsh-css-0.2.8.tgz
26
26
  ```
27
27
 
28
28
  安装并启用,然后完全退出并重新打开 dsh。
@@ -32,7 +32,7 @@ D:\product\dsh-css\dsh-tom-dsh-css-0.2.0.tgz
32
32
  包发布到 npmjs 后,先卸载已安装的旧版,再打开 **插件 → 添加插件 → 安装第三方插件**,输入:
33
33
 
34
34
  ```text
35
- @dsh-tom/dsh-css@0.2.0
35
+ @dsh-tom/dsh-css@0.2.8
36
36
  ```
37
37
 
38
38
  选择 **安装源 → npm 官方源**,安装并启用,然后完全退出并重新打开 dsh。
package/dist/client.js CHANGED
@@ -29,10 +29,30 @@ __export(client_exports, {
29
29
  inject: () => inject2
30
30
  });
31
31
  module.exports = __toCommonJS(client_exports);
32
- var import_react5 = require("react");
32
+ var import_react8 = require("react");
33
33
 
34
34
  // theme.css
35
- var theme_default = `/* \u6DF1\u8272\u6A21\u5F0F\u914D\u8272\uFF0C\u4FDD\u7559\u5185\u7F6E\u6587\u5B57\u3001\u8FB9\u6846\u548C\u8BED\u6CD5\u9AD8\u4EAE\u989C\u8272\u3002 */
35
+ var theme_default = `.dsh-css-unread-dot {
36
+ display: inline-block;
37
+ flex: none;
38
+ width: 8px;
39
+ height: 8px;
40
+ border-radius: 50%;
41
+ background: var(--dsw-alias-label-secondary, #aaa);
42
+ }
43
+
44
+ /* \u4F1A\u8BDD\u64CD\u4F5C\u51FA\u73B0\u65F6\u9690\u85CF\u672A\u8BFB\u63D0\u793A\uFF1B\u9879\u76EE\u6811\u4FDD\u7559\u5DE6\u4FA7\u56FE\u6807\u4F4D\u7F6E\uFF0C\u907F\u514D\u6807\u9898\u8DF3\u52A8\u3002 */
45
+ .hIlkoa_sessionRow:is(:hover, .hIlkoa_menuOpen) > .hIlkoa_slot .dsh-css-unread-dot {
46
+ visibility: hidden;
47
+ }
48
+
49
+ /* \u83DC\u5355\u5173\u95ED\u53EF\u80FD\u6B8B\u7559\u5BBF\u4E3B\u7684\u6EDA\u52A8\u548C\u6E10\u9690\u72B6\u6001\uFF1B\u975E\u60AC\u505C\u65F6\u4ECE\u6807\u9898\u5F00\u5934\u6B63\u5E38\u663E\u793A\u3002 */
50
+ .hIlkoa_sessionRow:not(:hover):not(.hIlkoa_menuOpen) > .hIlkoa_title {
51
+ overflow: clip;
52
+ mask-image: none;
53
+ }
54
+
55
+ /* \u6DF1\u8272\u6A21\u5F0F\u914D\u8272\uFF0C\u4FDD\u7559\u5185\u7F6E\u6587\u5B57\u3001\u8FB9\u6846\u548C\u8BED\u6CD5\u9AD8\u4EAE\u989C\u8272\u3002 */
36
56
  html body[data-ds-dark-theme] {
37
57
  /* \u533A\u57DF 1\uFF1A\u5DE6\u4FA7\u4F1A\u8BDD\u5217\u8868\uFF1BWindows \u6807\u9898\u680F\u5728\u4E0B\u65B9\u5355\u72EC\u8BBE\u7F6E\u3002\u539F RGB(27, 27, 28)\u3002 */
38
58
  --dsw-specific-sidebar-fill: rgb(36, 37, 33) !important;
@@ -200,9 +220,10 @@ html[data-windows-titlebar] body[data-ds-dark-theme] ._2H3hWW_root:not(._2H3hWW_
200
220
  padding: 44px 0 0 12px;
201
221
  }
202
222
 
203
- /* \u5E73\u94FA\u4F1A\u8BDD\u6807\u9898\u4E0E\u201C\u4F1A\u8BDD\u201D\u5DE6\u5BF9\u9F50\uFF0C\u72B6\u6001\u56FE\u6807\u653E\u5230\u53F3\u4FA7\u3002 */
223
+ /* \u884C\u80CC\u666F\u5411\u5DE6\u5EF6\u4F38 4px\uFF0C\u7559\u8DB3\u6807\u9898\u5185\u8FB9\u8DDD\u5E76\u4E0E\u201C\u4F1A\u8BDD\u201D\u4FDD\u6301\u5DE6\u5BF9\u9F50\u3002 */
204
224
  & ._9lTDKa_flatList .hIlkoa_sessionRow {
205
- padding-inline-start: 4px;
225
+ margin-inline-start: -4px;
226
+ padding-inline-start: 8px;
206
227
 
207
228
  & > .hIlkoa_title {
208
229
  margin-left: 0;
@@ -212,7 +233,8 @@ html[data-windows-titlebar] body[data-ds-dark-theme] ._2H3hWW_root:not(._2H3hWW_
212
233
  order: 1;
213
234
  }
214
235
 
215
- & > .hIlkoa_slot:empty {
236
+ & > .hIlkoa_slot:empty,
237
+ &:is(:hover, .hIlkoa_menuOpen) > .hIlkoa_slot:has(.dsh-css-unread-dot) {
216
238
  display: none;
217
239
  }
218
240
  }
@@ -289,6 +311,15 @@ html[data-windows-titlebar] body[data-ds-dark-theme] ._2H3hWW_root:not(._2H3hWW_
289
311
  }
290
312
  }
291
313
 
314
+ .dsh-css-date-heading {
315
+ padding: 16px 4px 6px;
316
+ color: var(--dsw-alias-label-tertiary);
317
+ font-size: 12px;
318
+ line-height: 18px;
319
+
320
+ &:first-child { padding-top: 6px; }
321
+ }
322
+
292
323
  /* \u60AC\u6D6E\u5361\u7247\u7531\u5BBF\u4E3B\u901A\u8FC7 portal \u6302\u5728\u4FA7\u680F\u5916\u3002 */
293
324
  .dsh-css-project {
294
325
  display: none;
@@ -1014,8 +1045,246 @@ function registerProjects(ctx) {
1014
1045
  }, ProjectsNav));
1015
1046
  }
1016
1047
 
1017
- // src/cost/client.js
1048
+ // src/cache-hit.js
1049
+ var import_react2 = require("react");
1050
+ function replacePercent(value, percent) {
1051
+ if (typeof value === "string") return value.replace(/\d+(?:\.\d+)?%/g, `${percent}%`);
1052
+ if (Array.isArray(value)) return import_react2.Children.map(value, (child) => replacePercent(child, percent));
1053
+ if (!(0, import_react2.isValidElement)(value)) return value;
1054
+ const props = {};
1055
+ for (const key of ["label", "ariaLabel", "children"]) {
1056
+ if (key in value.props) props[key] = replacePercent(value.props[key], percent);
1057
+ }
1058
+ return (0, import_react2.cloneElement)(value, props);
1059
+ }
1060
+ function registerCacheHitPrecision(ctx) {
1061
+ const slot = "conversation.composer.dock";
1062
+ ctx.slots.inject(slot, () => {
1063
+ const original = ctx.slots.entries(slot).find((entry) => entry.options.id === "usage");
1064
+ if (!original) return;
1065
+ const renderUsage = original.component.type;
1066
+ function UsagePill(props) {
1067
+ const element = renderUsage(props);
1068
+ const usage = props.useProjection("tokenUsage");
1069
+ if (!usage) return element;
1070
+ const input2 = usage.uncachedInputTokens + usage.cacheReadTokens + usage.cacheWriteTokens;
1071
+ if (input2 === 0) return element;
1072
+ return replacePercent(element, (usage.cacheReadTokens / input2 * 100).toFixed(2));
1073
+ }
1074
+ return ctx.slots.register({
1075
+ ...original.options,
1076
+ name: slot,
1077
+ priority: (original.options.priority ?? 0) - 1,
1078
+ locale: original.locale,
1079
+ inject: original.inject
1080
+ }, UsagePill);
1081
+ });
1082
+ }
1083
+
1084
+ // src/date-groups.js
1085
+ var import_react3 = require("react");
1086
+ var DAY = 864e5;
1087
+ var calendarDay = (date5) => Date.UTC(date5.getFullYear(), date5.getMonth(), date5.getDate()) / DAY;
1088
+ function dateHeading(timestamp, now) {
1089
+ const date5 = new Date(timestamp);
1090
+ const day = calendarDay(date5);
1091
+ const age = calendarDay(new Date(now)) - day;
1092
+ const label = ["\u4ECA\u5929", "\u6628\u5929", "\u524D\u5929"][age] ?? `${String(date5.getMonth() + 1).padStart(2, "0")}-${String(date5.getDate()).padStart(2, "0")}`;
1093
+ return { day, label };
1094
+ }
1095
+ function mapElements(value, visit2) {
1096
+ if (Array.isArray(value)) return import_react3.Children.map(value, (child) => mapElements(child, visit2));
1097
+ if (!(0, import_react3.isValidElement)(value)) return value;
1098
+ const replacement = visit2(value);
1099
+ if (replacement !== value) return replacement;
1100
+ return "children" in value.props ? (0, import_react3.cloneElement)(value, {}, mapElements(value.props.children, visit2)) : value;
1101
+ }
1102
+ function DateViewOptions({ renderMenu, dateMode, ...props }) {
1103
+ const menu = renderMenu(props);
1104
+ const items = menu.props.items.flatMap((item) => item.id === "flat" ? [item, {
1105
+ id: "date",
1106
+ label: "\u6309\u65E5\u671F",
1107
+ icon: (0, import_react3.createElement)("svg", {
1108
+ width: 16,
1109
+ height: 16,
1110
+ viewBox: "0 0 24 24",
1111
+ fill: "none",
1112
+ stroke: "currentColor",
1113
+ strokeWidth: 1.7,
1114
+ strokeLinecap: "round",
1115
+ strokeLinejoin: "round",
1116
+ "aria-hidden": true
1117
+ }, (0, import_react3.createElement)("path", { d: "M8 3v4M16 3v4M3 10h18M5 5h14a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V7a2 2 0 0 1 2-2Z" }))
1118
+ }] : [item]);
1119
+ return (0, import_react3.cloneElement)(menu, {
1120
+ items,
1121
+ selectedIds: menu.props.selectedIds.map((id) => dateMode && id === "flat" ? "date" : id),
1122
+ onSelect: (id) => {
1123
+ if (id !== "date") return menu.props.onSelect(id);
1124
+ props.onGroupPick("date");
1125
+ menu.props.onClose();
1126
+ }
1127
+ });
1128
+ }
1129
+ function DateFlatList({ renderList, enabled, ...props }) {
1130
+ const [clock, setClock] = (0, import_react3.useState)(Date.now);
1131
+ (0, import_react3.useEffect)(() => {
1132
+ if (!enabled) return;
1133
+ const now2 = /* @__PURE__ */ new Date();
1134
+ const midnight = new Date(now2.getFullYear(), now2.getMonth(), now2.getDate() + 1);
1135
+ const timer = setTimeout(() => setClock(Date.now()), midnight - now2);
1136
+ return () => clearTimeout(timer);
1137
+ }, [enabled, clock]);
1138
+ const tree = renderList(props);
1139
+ if (!enabled) return tree;
1140
+ const now = Date.now();
1141
+ return mapElements(tree, (element) => {
1142
+ if (!element.props.className?.split(" ").includes("_9lTDKa_flatList")) return element;
1143
+ let previousDay;
1144
+ const children = mapElements(element.props.children, (row) => {
1145
+ const node2 = row.props.node;
1146
+ if (!node2 || node2.pinned || node2.blank) return row;
1147
+ const { day, label } = dateHeading(node2.updatedAt, now);
1148
+ if (day === previousDay) return row;
1149
+ previousDay = day;
1150
+ return [(0, import_react3.createElement)("div", {
1151
+ key: `date:${node2.id}`,
1152
+ className: "dsh-css-date-heading",
1153
+ role: "presentation"
1154
+ }, label), row];
1155
+ });
1156
+ return (0, import_react3.cloneElement)(element, {}, children);
1157
+ });
1158
+ }
1159
+ function registerDateGroups(ctx) {
1160
+ const slot = "sidebar.workspaces";
1161
+ ctx.slots.inject(slot, () => {
1162
+ const original = ctx.slots.entries(slot)[0];
1163
+ if (!original) return;
1164
+ const renderBrowser = original.component;
1165
+ function DateWorkspaceBrowser(props) {
1166
+ const dateMode = props.useStore((state) => state.groupBy === "date");
1167
+ const tree = renderBrowser({
1168
+ ...props,
1169
+ useStore: (selector) => props.useStore((state) => selector(state.groupBy === "date" ? { ...state, groupBy: "flat" } : state))
1170
+ });
1171
+ return mapElements(tree, (element) => {
1172
+ if (element.props.onGroupPick && element.props.onOrderPick) {
1173
+ return (0, import_react3.createElement)(DateViewOptions, {
1174
+ ...element.props,
1175
+ key: element.key ?? void 0,
1176
+ renderMenu: element.type,
1177
+ dateMode
1178
+ });
1179
+ }
1180
+ if (element.props.sessionIds && element.props.setSessionOrder) {
1181
+ return (0, import_react3.createElement)(DateFlatList, {
1182
+ ...element.props,
1183
+ key: element.key ?? void 0,
1184
+ renderList: element.type,
1185
+ enabled: dateMode
1186
+ });
1187
+ }
1188
+ return element;
1189
+ });
1190
+ }
1191
+ const priority = Math.max(...ctx.slots.entries(slot).map((entry) => entry.options.priority ?? 0)) + 1;
1192
+ const dispose = ctx.slots.register({ name: slot, priority }, renderBrowser);
1193
+ original.component = DateWorkspaceBrowser;
1194
+ return () => {
1195
+ original.component = renderBrowser;
1196
+ dispose();
1197
+ };
1198
+ });
1199
+ }
1200
+
1201
+ // src/unread.js
1018
1202
  var import_react4 = require("react");
1203
+ var import_dsh_client_ui_primitives2 = require("@deepseek-ai/dsh-client-ui-primitives");
1204
+
1205
+ // src/unread-store.js
1206
+ var STORAGE_KEY = "dsh-css.unread.v1";
1207
+ function createUnreadStore(sessions, storage) {
1208
+ let snapshot = /* @__PURE__ */ new Set();
1209
+ const listeners = /* @__PURE__ */ new Set();
1210
+ try {
1211
+ const saved = JSON.parse((storage ?? window.localStorage).getItem(STORAGE_KEY) ?? "[]");
1212
+ if (Array.isArray(saved)) snapshot = new Set(saved.filter((id) => typeof id === "string"));
1213
+ } catch {
1214
+ }
1215
+ const publish = (next) => {
1216
+ snapshot = next;
1217
+ try {
1218
+ (storage ?? window.localStorage).setItem(STORAGE_KEY, JSON.stringify([...next]));
1219
+ } catch {
1220
+ console.warn("[dsh-css] \u672A\u8BFB\u6807\u8BB0\u65E0\u6CD5\u4FDD\u5B58\uFF0C\u91CD\u542F\u540E\u53EF\u80FD\u4E22\u5931\u3002");
1221
+ }
1222
+ for (const listener of listeners) listener();
1223
+ };
1224
+ const mainCount = (row) => row?.retainedBy?.mainView ?? 0;
1225
+ return {
1226
+ getSnapshot: () => snapshot,
1227
+ subscribe(listener) {
1228
+ listeners.add(listener);
1229
+ return () => listeners.delete(listener);
1230
+ },
1231
+ setUnread(id, unread) {
1232
+ if (snapshot.has(id) === unread) return;
1233
+ const next = new Set(snapshot);
1234
+ if (unread) next.add(id);
1235
+ else next.delete(id);
1236
+ publish(next);
1237
+ },
1238
+ watch() {
1239
+ let previous = sessions.getSnapshot().byId;
1240
+ return sessions.subscribe(() => {
1241
+ const current = sessions.getSnapshot().byId;
1242
+ const next = new Set(snapshot);
1243
+ for (const id of snapshot) {
1244
+ if (mainCount(current[id]) > mainCount(previous[id])) next.delete(id);
1245
+ }
1246
+ previous = current;
1247
+ if (next.size !== snapshot.size) publish(next);
1248
+ });
1249
+ }
1250
+ };
1251
+ }
1252
+
1253
+ // src/unread.js
1254
+ function UnreadMenuItem({ sessionId, useUnread, useWorkspaces, useMenuOpenState, setUnread }) {
1255
+ const unread = useUnread((state) => state.has(sessionId));
1256
+ const archived = useWorkspaces((state) => state.archivedSessionIds.includes(sessionId));
1257
+ const [, setMenuOpen] = useMenuOpenState();
1258
+ if (archived) return null;
1259
+ return (0, import_react4.createElement)(import_dsh_client_ui_primitives2.MenuItemButton, {
1260
+ icon: unread ? (0, import_react4.createElement)(import_dsh_client_ui_primitives2.IconCheckOutlineRegular, { size: 14 }) : (0, import_react4.createElement)("span", { className: "dsh-css-unread-dot", "aria-hidden": true }),
1261
+ onSelect: () => {
1262
+ setUnread(sessionId, !unread);
1263
+ setMenuOpen(false);
1264
+ }
1265
+ }, unread ? "\u6807\u8BB0\u4E3A\u5DF2\u8BFB" : "\u6807\u8BB0\u4E3A\u672A\u8BFB");
1266
+ }
1267
+ function UnreadIndicator({ sessionId, useUnread }) {
1268
+ return useUnread((state) => state.has(sessionId)) ? (0, import_react4.createElement)("span", { className: "dsh-css-unread-dot", role: "img", "aria-label": "\u672A\u8BFB", title: "\u672A\u8BFB" }) : null;
1269
+ }
1270
+ function registerUnread(ctx) {
1271
+ const unread = createUnreadStore(ctx.sessions.list);
1272
+ ctx.effect(() => unread.watch());
1273
+ ctx.slots.inject("sidebar.workspaces.session.menu.item", () => ctx.slots.register({
1274
+ name: "sidebar.workspaces.session.menu.item",
1275
+ id: "dsh-css-unread",
1276
+ order: 250,
1277
+ inject: () => ({ setUnread: unread.setUnread, hooks: { unread, workspaces: ctx.workspaces.list } })
1278
+ }, UnreadMenuItem));
1279
+ ctx.slots.inject("sidebar.session.row.leading", () => ctx.slots.register({
1280
+ name: "sidebar.session.row.leading",
1281
+ id: "dsh-css-unread",
1282
+ inject: () => ({ hooks: { unread } })
1283
+ }, UnreadIndicator));
1284
+ }
1285
+
1286
+ // src/cost/client.js
1287
+ var import_react7 = require("react");
1019
1288
 
1020
1289
  // src/cost/pricing.js
1021
1290
  var PROJECTION_KEY = "dshTomCost";
@@ -1053,7 +1322,7 @@ function formatMoney(units) {
1053
1322
  }
1054
1323
 
1055
1324
  // src/cost/panel.js
1056
- var import_react2 = require("react");
1325
+ var import_react5 = require("react");
1057
1326
  var import_react_dom = require("react-dom");
1058
1327
 
1059
1328
  // src/cost/panel-styles.js
@@ -1129,8 +1398,8 @@ function costRows(totals, daily, day = beijingDay()) {
1129
1398
  ];
1130
1399
  }
1131
1400
  function useBeijingDay() {
1132
- const [day, setDay] = (0, import_react2.useState)(beijingDay);
1133
- (0, import_react2.useEffect)(() => {
1401
+ const [day, setDay] = (0, import_react5.useState)(beijingDay);
1402
+ (0, import_react5.useEffect)(() => {
1134
1403
  let timer;
1135
1404
  const refresh = () => {
1136
1405
  const current = beijingDay();
@@ -1144,7 +1413,7 @@ function useBeijingDay() {
1144
1413
  return day;
1145
1414
  }
1146
1415
  function CostIcon() {
1147
- return (0, import_react2.createElement)(
1416
+ return (0, import_react5.createElement)(
1148
1417
  "svg",
1149
1418
  {
1150
1419
  viewBox: "0 0 24 24",
@@ -1157,18 +1426,18 @@ function CostIcon() {
1157
1426
  strokeLinejoin: "round",
1158
1427
  "aria-hidden": true
1159
1428
  },
1160
- (0, import_react2.createElement)("circle", { cx: 12, cy: 12, r: 9 }),
1161
- (0, import_react2.createElement)("path", { d: "m8.5 7.5 3.5 4 3.5-4M8.5 12h7M8.5 15h7M12 11.5V18" })
1429
+ (0, import_react5.createElement)("circle", { cx: 12, cy: 12, r: 9 }),
1430
+ (0, import_react5.createElement)("path", { d: "m8.5 7.5 3.5 4 3.5-4M8.5 12h7M8.5 15h7M12 11.5V18" })
1162
1431
  );
1163
1432
  }
1164
1433
  function CostPanel({ totals, daily, balance }) {
1165
1434
  const day = useBeijingDay();
1166
1435
  const rows = costRows(totals, daily, day);
1167
- const [open2, setOpen] = (0, import_react2.useState)(false);
1168
- const anchor2 = (0, import_react2.useRef)(null);
1169
- const panel = (0, import_react2.useRef)(null);
1170
- const dialogId = (0, import_react2.useId)();
1171
- (0, import_react2.useLayoutEffect)(() => {
1436
+ const [open2, setOpen] = (0, import_react5.useState)(false);
1437
+ const anchor2 = (0, import_react5.useRef)(null);
1438
+ const panel = (0, import_react5.useRef)(null);
1439
+ const dialogId = (0, import_react5.useId)();
1440
+ (0, import_react5.useLayoutEffect)(() => {
1172
1441
  if (!open2) return;
1173
1442
  const element = panel.current;
1174
1443
  const document2 = element.ownerDocument;
@@ -1207,11 +1476,11 @@ function CostPanel({ totals, daily, balance }) {
1207
1476
  document2.removeEventListener("keydown", dismissEscape);
1208
1477
  };
1209
1478
  }, [open2]);
1210
- return (0, import_react2.createElement)(
1479
+ return (0, import_react5.createElement)(
1211
1480
  "span",
1212
1481
  { className: "dsh-tom-cost-anchor", "data-composer-stat": "cost" },
1213
- (0, import_react2.createElement)("style", { "data-dsh-tom-cost-styles": "" }, panelStyles),
1214
- (0, import_react2.createElement)("button", {
1482
+ (0, import_react5.createElement)("style", { "data-dsh-tom-cost-styles": "" }, panelStyles),
1483
+ (0, import_react5.createElement)("button", {
1215
1484
  ref: anchor2,
1216
1485
  type: "button",
1217
1486
  className: "dsh-tom-cost-pill",
@@ -1221,8 +1490,8 @@ function CostPanel({ totals, daily, balance }) {
1221
1490
  "aria-controls": open2 ? dialogId : void 0,
1222
1491
  "aria-label": `\u4F1A\u8BDD\u7D2F\u8BA1\u8D39\u7528 ${rows[1][1]}`,
1223
1492
  onClick: () => setOpen(!open2)
1224
- }, (0, import_react2.createElement)(CostIcon), (0, import_react2.createElement)("span", { className: "dsh-tom-cost-label" }, rows[1][1])),
1225
- open2 && (0, import_react_dom.createPortal)((0, import_react2.createElement)(
1493
+ }, (0, import_react5.createElement)(CostIcon), (0, import_react5.createElement)("span", { className: "dsh-tom-cost-label" }, rows[1][1])),
1494
+ open2 && (0, import_react_dom.createPortal)((0, import_react5.createElement)(
1226
1495
  "div",
1227
1496
  {
1228
1497
  ref: panel,
@@ -1232,11 +1501,11 @@ function CostPanel({ totals, daily, balance }) {
1232
1501
  className: "dsh-tom-cost-panel",
1233
1502
  style: { visibility: "hidden", left: 0, top: 0 }
1234
1503
  },
1235
- (0, import_react2.createElement)("div", { className: "dsh-tom-cost-title" }, (0, import_react2.createElement)(CostIcon), "\u8D39\u7528\u660E\u7EC6"),
1236
- (0, import_react2.createElement)("div", { className: "dsh-tom-cost-rule", "aria-hidden": true }),
1237
- (0, import_react2.createElement)("dl", { className: "dsh-tom-cost-details" }, rows.flatMap(([label, value]) => [
1238
- (0, import_react2.createElement)("dt", { key: label }, label),
1239
- (0, import_react2.createElement)("dd", { key: `${label}-value` }, value)
1504
+ (0, import_react5.createElement)("div", { className: "dsh-tom-cost-title" }, (0, import_react5.createElement)(CostIcon), "\u8D39\u7528\u660E\u7EC6"),
1505
+ (0, import_react5.createElement)("div", { className: "dsh-tom-cost-rule", "aria-hidden": true }),
1506
+ (0, import_react5.createElement)("dl", { className: "dsh-tom-cost-details" }, rows.flatMap(([label, value]) => [
1507
+ (0, import_react5.createElement)("dt", { key: label }, label),
1508
+ (0, import_react5.createElement)("dd", { key: `${label}-value` }, value)
1240
1509
  ]))
1241
1510
  ), anchor2.current.ownerDocument.body)
1242
1511
  );
@@ -21183,7 +21452,7 @@ function createBalanceStore(readBalance) {
21183
21452
  }
21184
21453
 
21185
21454
  // src/cost/settings.js
21186
- var import_react3 = require("react");
21455
+ var import_react6 = require("react");
21187
21456
 
21188
21457
  // src/cost/settings-styles.js
21189
21458
  var settingsStyles = `
@@ -21239,62 +21508,62 @@ function usageSummary(row) {
21239
21508
  function CostSettings({ useCostReport }) {
21240
21509
  const report = useCostReport((value2) => value2);
21241
21510
  const day = useBeijingDay();
21242
- const [hideZero, setHideZero] = (0, import_react3.useState)(true);
21511
+ const [hideZero, setHideZero] = (0, import_react6.useState)(true);
21243
21512
  const value = report.status === "ready" && report.value.day === day ? report.value : void 0;
21244
21513
  const status = report.status === "error" ? "\u8BFB\u53D6\u5931\u8D25" : "\u52A0\u8F7D\u4E2D\u2026";
21245
21514
  const rows = value ? visibleHistory(value.history, hideZero) : [];
21246
21515
  const summaries = [["today", "\u4ECA\u65E5\u8D39\u7528"], ["week", "\u672C\u5468\u8D39\u7528"], ["month", "\u672C\u6708\u8D39\u7528"]];
21247
- return (0, import_react3.createElement)(
21516
+ return (0, import_react6.createElement)(
21248
21517
  "section",
21249
21518
  { className: "dsh-tom-cost-settings", "aria-label": "\u8D39\u7528" },
21250
- (0, import_react3.createElement)("style", null, settingsStyles),
21251
- (0, import_react3.createElement)("div", { className: "dsh-tom-cost-cards" }, summaries.map(([key, label]) => {
21519
+ (0, import_react6.createElement)("style", null, settingsStyles),
21520
+ (0, import_react6.createElement)("div", { className: "dsh-tom-cost-cards" }, summaries.map(([key, label]) => {
21252
21521
  const row = value?.[key];
21253
- return (0, import_react3.createElement)(
21522
+ return (0, import_react6.createElement)(
21254
21523
  "div",
21255
21524
  { key, className: "dsh-tom-cost-card" },
21256
- (0, import_react3.createElement)("div", { className: "dsh-tom-cost-card-label" }, label),
21257
- (0, import_react3.createElement)("div", { className: "dsh-tom-cost-amount" }, row ? costValue(row.totals, row.failedSessions) : status),
21258
- (0, import_react3.createElement)("div", { className: "dsh-tom-cost-usage" }, usageSummary(row))
21525
+ (0, import_react6.createElement)("div", { className: "dsh-tom-cost-card-label" }, label),
21526
+ (0, import_react6.createElement)("div", { className: "dsh-tom-cost-amount" }, row ? costValue(row.totals, row.failedSessions) : status),
21527
+ (0, import_react6.createElement)("div", { className: "dsh-tom-cost-usage" }, usageSummary(row))
21259
21528
  );
21260
21529
  })),
21261
- (0, import_react3.createElement)(
21530
+ (0, import_react6.createElement)(
21262
21531
  "div",
21263
21532
  { className: "dsh-tom-cost-history-heading" },
21264
- (0, import_react3.createElement)("h3", null, "\u5386\u53F2\u8BB0\u5F55", (0, import_react3.createElement)("span", null, "\u6700\u8FD1 30 \u5929")),
21265
- (0, import_react3.createElement)(
21533
+ (0, import_react6.createElement)("h3", null, "\u5386\u53F2\u8BB0\u5F55", (0, import_react6.createElement)("span", null, "\u6700\u8FD1 30 \u5929")),
21534
+ (0, import_react6.createElement)(
21266
21535
  "label",
21267
21536
  { className: "dsh-tom-cost-filter" },
21268
- (0, import_react3.createElement)("input", { type: "checkbox", checked: hideZero, onChange: (event) => setHideZero(event.target.checked) }),
21537
+ (0, import_react6.createElement)("input", { type: "checkbox", checked: hideZero, onChange: (event) => setHideZero(event.target.checked) }),
21269
21538
  "\u8FC7\u6EE4\u96F6\u8D39\u7528\u65E5\u671F"
21270
21539
  )
21271
21540
  ),
21272
- (0, import_react3.createElement)(
21541
+ (0, import_react6.createElement)(
21273
21542
  "div",
21274
21543
  { className: "dsh-tom-cost-table-wrap", tabIndex: 0, role: "region", "aria-label": "\u6BCF\u65E5\u8D39\u7528\u5386\u53F2\u8BB0\u5F55" },
21275
- (0, import_react3.createElement)(
21544
+ (0, import_react6.createElement)(
21276
21545
  "table",
21277
21546
  { className: "dsh-tom-cost-table" },
21278
- (0, import_react3.createElement)("thead", null, (0, import_react3.createElement)("tr", null, ["\u65E5\u671F", "\u8C03\u7528", "\u8F93\u5165 tok", "\u7F13\u5B58 tok", "\u8F93\u51FA tok", "\u8D39\u7528"].map((label) => (0, import_react3.createElement)("th", { key: label, scope: "col" }, label)))),
21279
- (0, import_react3.createElement)("tbody", null, value && rows.length ? rows.map((row) => {
21547
+ (0, import_react6.createElement)("thead", null, (0, import_react6.createElement)("tr", null, ["\u65E5\u671F", "\u8C03\u7528", "\u8F93\u5165 tok", "\u7F13\u5B58 tok", "\u8F93\u51FA tok", "\u8D39\u7528"].map((label) => (0, import_react6.createElement)("th", { key: label, scope: "col" }, label)))),
21548
+ (0, import_react6.createElement)("tbody", null, value && rows.length ? rows.map((row) => {
21280
21549
  const totals = row.totals;
21281
21550
  const unavailable = row.failedSessions > 0 && calls(totals) === 0;
21282
- return (0, import_react3.createElement)(
21551
+ return (0, import_react6.createElement)(
21283
21552
  "tr",
21284
21553
  { key: row.day },
21285
- (0, import_react3.createElement)("td", null, row.day),
21286
- (0, import_react3.createElement)("td", null, unavailable ? "\u2014" : calls(totals).toLocaleString("zh-CN")),
21287
- ...["inputTokens", "cacheTokens", "outputTokens"].map((key) => (0, import_react3.createElement)("td", {
21554
+ (0, import_react6.createElement)("td", null, row.day),
21555
+ (0, import_react6.createElement)("td", null, unavailable ? "\u2014" : calls(totals).toLocaleString("zh-CN")),
21556
+ ...["inputTokens", "cacheTokens", "outputTokens"].map((key) => (0, import_react6.createElement)("td", {
21288
21557
  key,
21289
21558
  title: unavailable ? void 0 : totals[key].toLocaleString("zh-CN")
21290
21559
  }, unavailable ? "\u2014" : formatTokens(totals[key]))),
21291
- (0, import_react3.createElement)("td", null, costValue(totals, row.failedSessions))
21560
+ (0, import_react6.createElement)("td", null, costValue(totals, row.failedSessions))
21292
21561
  );
21293
- }) : (0, import_react3.createElement)("tr", null, (0, import_react3.createElement)("td", { colSpan: 6, className: "dsh-tom-cost-empty", role: "status" }, value ? "\u6700\u8FD1 30 \u5929\u6682\u65E0\u975E\u96F6\u8D39\u7528\u8BB0\u5F55" : status)))
21562
+ }) : (0, import_react6.createElement)("tr", null, (0, import_react6.createElement)("td", { colSpan: 6, className: "dsh-tom-cost-empty", role: "status" }, value ? "\u6700\u8FD1 30 \u5929\u6682\u65E0\u975E\u96F6\u8D39\u7528\u8BB0\u5F55" : status)))
21294
21563
  )
21295
21564
  ),
21296
- (0, import_react3.createElement)("p", { className: "dsh-tom-cost-note" }, "\u5317\u4EAC\u65F6\u95F4 \xB7 \u672C\u5468\u4ECE\u5468\u4E00\u5F00\u59CB \xB7 \u8D39\u7528\u6309\u672C\u673A\u4F1A\u8BDD\u8BB0\u5F55\u4F30\u7B97"),
21297
- value && value.history.some(incomplete) && (0, import_react3.createElement)(
21565
+ (0, import_react6.createElement)("p", { className: "dsh-tom-cost-note" }, "\u5317\u4EAC\u65F6\u95F4 \xB7 \u672C\u5468\u4ECE\u5468\u4E00\u5F00\u59CB \xB7 \u8D39\u7528\u6309\u672C\u673A\u4F1A\u8BDD\u8BB0\u5F55\u4F30\u7B97"),
21566
+ value && value.history.some(incomplete) && (0, import_react6.createElement)(
21298
21567
  "p",
21299
21568
  { className: "dsh-tom-cost-note", role: "status" },
21300
21569
  "\u90E8\u5206\u8BB0\u5F55\u65E0\u6CD5\u8BFB\u53D6\u6216\u8BA1\u4EF7\uFF0C\u76F8\u5173\u91D1\u989D\u53EF\u80FD\u4E0D\u5B8C\u6574\u3002"
@@ -21308,7 +21577,7 @@ function CostMount({ useProjection, useDailyCost, useAccountBalance, sessionId }
21308
21577
  const totals = useProjection(PROJECTION_KEY);
21309
21578
  const daily = useDailyCost((value) => value);
21310
21579
  const balance = useAccountBalance((value) => value);
21311
- return (0, import_react4.createElement)(CostPanel, { key: sessionId, totals, daily, balance });
21580
+ return (0, import_react7.createElement)(CostPanel, { key: sessionId, totals, daily, balance });
21312
21581
  }
21313
21582
  async function apply(ctx) {
21314
21583
  await ctx.remote.$mount(dailyRemote);
@@ -21344,10 +21613,10 @@ function ProjectHover({ sessionId, useWorkspaces, useSessions }) {
21344
21613
  const cwd = useSessions((state) => state.byId[sessionId]?.cwd);
21345
21614
  const path = workspace?.path || cwd;
21346
21615
  const name = workspace?.title || path?.split(/[\\/]/).filter(Boolean).at(-1) || path || "\u672A\u5F52\u5C5E\u9879\u76EE";
21347
- return (0, import_react5.createElement)(
21616
+ return (0, import_react8.createElement)(
21348
21617
  "div",
21349
21618
  { className: "dsh-css-project", title: path },
21350
- (0, import_react5.createElement)("svg", {
21619
+ (0, import_react8.createElement)("svg", {
21351
21620
  width: 14,
21352
21621
  height: 14,
21353
21622
  viewBox: "0 0 24 24",
@@ -21356,8 +21625,8 @@ function ProjectHover({ sessionId, useWorkspaces, useSessions }) {
21356
21625
  strokeWidth: 1.7,
21357
21626
  strokeLinejoin: "round",
21358
21627
  "aria-hidden": true
21359
- }, (0, import_react5.createElement)("path", { d: "M3 7V5a2 2 0 0 1 2-2h5l2 3h7a2 2 0 0 1 2 2v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V7Zm0 2h18" })),
21360
- (0, import_react5.createElement)("span", null, name)
21628
+ }, (0, import_react8.createElement)("path", { d: "M3 7V5a2 2 0 0 1 2-2h5l2 3h7a2 2 0 0 1 2 2v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V7Zm0 2h18" })),
21629
+ (0, import_react8.createElement)("span", null, name)
21361
21630
  );
21362
21631
  }
21363
21632
  function applySidebarWidth(layout) {
@@ -21409,7 +21678,10 @@ async function apply2(ctx) {
21409
21678
  return () => style.remove();
21410
21679
  });
21411
21680
  ctx.effect(() => applySidebarWidth(ctx.layout));
21681
+ registerCacheHitPrecision(ctx);
21682
+ registerDateGroups(ctx);
21412
21683
  registerProjects(ctx);
21684
+ registerUnread(ctx);
21413
21685
  ctx.slots.inject("sidebar.session.row.hover", () => ctx.slots.register({
21414
21686
  name: "sidebar.session.row.hover",
21415
21687
  id: "dsh-css-project",
package/dist/index.js CHANGED
@@ -20064,10 +20064,33 @@ function apply(ctx) {
20064
20064
  });
20065
20065
  }
20066
20066
 
20067
+ // src/progress.js
20068
+ var PROGRESS_SECTION = "dsh-tom:progress-updates";
20069
+ var instructions = `# \u4EFB\u52A1\u8FDB\u5C55\u64AD\u62A5
20070
+ \u6267\u884C\u9700\u8981\u8C03\u67E5\u3001\u4FEE\u6539\u3001\u9A8C\u8BC1\u6216\u591A\u6B65\u5DE5\u5177\u64CD\u4F5C\u7684\u4EFB\u52A1\u65F6\uFF0C\u4E3B\u52A8\u5411\u7528\u6237\u7B80\u77ED\u6C47\u62A5\u8FDB\u5C55\uFF1A
20071
+ - \u7B2C\u4E00\u6B21\u8C03\u7528\u5DE5\u5177\u524D\uFF0C\u7528\u4E00\u4E24\u53E5\u8BDD\u8BF4\u660E\u63A5\u4E0B\u6765\u51C6\u5907\u505A\u4EC0\u4E48\u3002
20072
+ - \u83B7\u5F97\u5173\u952E\u53D1\u73B0\u3001\u8C03\u6574\u65B9\u5411\u6216\u8FDB\u5165\u9A8C\u8BC1\u9636\u6BB5\u65F6\uFF0C\u7B80\u77ED\u8BF4\u660E\u5DF2\u786E\u8BA4\u7684\u7ED3\u679C\u4EE5\u53CA\u4E0B\u4E00\u6B65\u3002\u8FDE\u7EED\u591A\u8F6E\u5DE5\u5177\u64CD\u4F5C\u65F6\u9002\u65F6\u66F4\u65B0\uFF0C\u4E0D\u8981\u59CB\u7EC8\u6C89\u9ED8\u76F4\u5230\u7ED3\u675F\uFF1B\u65E0\u9700\u6BCF\u6B21\u8C03\u7528\u5DE5\u5177\u90FD\u6C47\u62A5\uFF0C\u4E5F\u4E0D\u8981\u91CD\u590D\u7A7A\u6CDB\u7684\u201C\u6B63\u5728\u5904\u7406\u201D\u3002
20073
+ - \u8FDB\u5C55\u5FC5\u987B\u4F5C\u4E3A\u7528\u6237\u53EF\u89C1\u7684\u666E\u901A\u52A9\u624B\u6B63\u6587\u8F93\u51FA\uFF0C\u4E0D\u8981\u53EA\u653E\u5728\u601D\u8003\u5185\u5BB9\u3001\u5DE5\u5177\u53C2\u6570\u6216\u6700\u7EC8\u56DE\u7B54\u91CC\u3002\u9700\u8981\u7EE7\u7EED\u6267\u884C\u65F6\uFF0C\u5728\u540C\u4E00\u54CD\u5E94\u4E2D\u5148\u8F93\u51FA\u8FDB\u5C55\u6B63\u6587\uFF0C\u518D\u53D1\u8D77\u5B9E\u9645\u5DE5\u5177\u8C03\u7528\uFF1B\u4E0D\u8981\u4EC5\u8BF4\u201C\u63A5\u4E0B\u6765\u4F1A\u505A\u201D\u5C31\u7ED3\u675F\u4EFB\u52A1\u6216\u7B49\u5F85\u786E\u8BA4\u3002
20074
+ - \u53EA\u62A5\u544A\u771F\u5B9E\u89C2\u5BDF\u3001\u5DF2\u7ECF\u5B8C\u6210\u7684\u64CD\u4F5C\u548C\u660E\u786E\u7684\u4E0B\u4E00\u6B65\uFF0C\u4E0D\u8981\u63D0\u524D\u58F0\u79F0\u6210\u529F\uFF0C\u4E0D\u8981\u5C55\u793A\u5185\u90E8\u63A8\u7406\u8FC7\u7A0B\u3002\u9ED8\u8BA4\u4F7F\u7528\u7528\u6237\u7684\u8BED\u8A00\uFF0C\u6BCF\u6B21\u4E00\u4E24\u53E5\u8BDD\u3002
20075
+ - \u7B80\u5355\u95EE\u7B54\u76F4\u63A5\u56DE\u7B54\u3002\u7528\u6237\u660E\u786E\u8981\u6C42\u4E0D\u64AD\u62A5\u3001\u4EC5\u7ED9\u7ED3\u8BBA\u6216\u7279\u5B9A\u8F93\u51FA\u683C\u5F0F\u65F6\uFF0C\u9075\u5FAA\u7528\u6237\u8981\u6C42\u3002
20076
+ - \u4EFB\u52A1\u5B8C\u6210\u540E\u6309\u539F\u6709\u65B9\u5F0F\u7ED9\u51FA\u6700\u7EC8\u56DE\u7B54\uFF0C\u4E0D\u8981\u628A\u4E2D\u9014\u64AD\u62A5\u91CD\u65B0\u62FC\u6210\u4E00\u4EFD\u91CD\u590D\u7684\u603B\u7ED3\u3002`;
20077
+ function registerProgressUpdates(ctx) {
20078
+ ctx.systemPrompt.section({
20079
+ name: PROGRESS_SECTION,
20080
+ order: 10300,
20081
+ interpolate: false,
20082
+ text: ({ agent }) => agent && agent.session.header.origin !== "subagent" ? instructions : ""
20083
+ });
20084
+ }
20085
+
20067
20086
  // index.js
20068
20087
  var name = "dsh-css";
20088
+ function apply2(ctx) {
20089
+ apply(ctx);
20090
+ ctx.inject(["systemPrompt"], registerProgressUpdates);
20091
+ }
20069
20092
  export {
20070
- apply,
20093
+ apply2 as apply,
20071
20094
  inject,
20072
20095
  name
20073
20096
  };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@dsh-tom/dsh-css",
3
- "version": "0.2.0",
4
- "description": "为 DeepSeek Harness 调整深色界面,增加项目管理、会话费用和费用历史统计。",
3
+ "version": "0.2.8",
4
+ "description": "为 DeepSeek Harness 调整深色界面,增加项目管理、费用统计和任务进展播报。",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
7
7
  "exports": {
@@ -43,7 +43,7 @@
43
43
  "scripts": {
44
44
  "build": "node scripts/build.mjs",
45
45
  "test": "node --test test/*.test.mjs",
46
- "check": "node --check index.js && node --check src/client.js && node --check src/projects.js && node --check scripts/build.mjs && npm run build && node --check dist/index.js && node --check dist/client.js && npm test",
46
+ "check": "node --check index.js && node --check src/client.js && node --check src/projects.js && node --check src/progress.js && node --check scripts/build.mjs && npm run build && node --check dist/index.js && node --check dist/client.js && npm test",
47
47
  "prepack": "npm run check"
48
48
  },
49
49
  "license": "MIT",
@@ -65,6 +65,7 @@
65
65
  "@deepseek-ai/dsh-typert-registry": "0.2.1-rc.2",
66
66
  "@deepseek-ai/dsh-typert-protocol": "0.2.1-rc.2",
67
67
  "@deepseek-ai/dsh-session-projection": "0.2.1-rc.2",
68
+ "@deepseek-ai/dsh-system-prompt": "0.2.1-rc.2",
68
69
  "esbuild": "0.28.2",
69
70
  "jsdom": "^27.0.0",
70
71
  "react": "18.3.1",
package/theme.css CHANGED
@@ -1,3 +1,23 @@
1
+ .dsh-css-unread-dot {
2
+ display: inline-block;
3
+ flex: none;
4
+ width: 8px;
5
+ height: 8px;
6
+ border-radius: 50%;
7
+ background: var(--dsw-alias-label-secondary, #aaa);
8
+ }
9
+
10
+ /* 会话操作出现时隐藏未读提示;项目树保留左侧图标位置,避免标题跳动。 */
11
+ .hIlkoa_sessionRow:is(:hover, .hIlkoa_menuOpen) > .hIlkoa_slot .dsh-css-unread-dot {
12
+ visibility: hidden;
13
+ }
14
+
15
+ /* 菜单关闭可能残留宿主的滚动和渐隐状态;非悬停时从标题开头正常显示。 */
16
+ .hIlkoa_sessionRow:not(:hover):not(.hIlkoa_menuOpen) > .hIlkoa_title {
17
+ overflow: clip;
18
+ mask-image: none;
19
+ }
20
+
1
21
  /* 深色模式配色,保留内置文字、边框和语法高亮颜色。 */
2
22
  html body[data-ds-dark-theme] {
3
23
  /* 区域 1:左侧会话列表;Windows 标题栏在下方单独设置。原 RGB(27, 27, 28)。 */
@@ -166,9 +186,10 @@ html[data-windows-titlebar] body[data-ds-dark-theme] ._2H3hWW_root:not(._2H3hWW_
166
186
  padding: 44px 0 0 12px;
167
187
  }
168
188
 
169
- /* 平铺会话标题与“会话”左对齐,状态图标放到右侧。 */
189
+ /* 行背景向左延伸 4px,留足标题内边距并与“会话”保持左对齐。 */
170
190
  & ._9lTDKa_flatList .hIlkoa_sessionRow {
171
- padding-inline-start: 4px;
191
+ margin-inline-start: -4px;
192
+ padding-inline-start: 8px;
172
193
 
173
194
  & > .hIlkoa_title {
174
195
  margin-left: 0;
@@ -178,7 +199,8 @@ html[data-windows-titlebar] body[data-ds-dark-theme] ._2H3hWW_root:not(._2H3hWW_
178
199
  order: 1;
179
200
  }
180
201
 
181
- & > .hIlkoa_slot:empty {
202
+ & > .hIlkoa_slot:empty,
203
+ &:is(:hover, .hIlkoa_menuOpen) > .hIlkoa_slot:has(.dsh-css-unread-dot) {
182
204
  display: none;
183
205
  }
184
206
  }
@@ -255,6 +277,15 @@ html[data-windows-titlebar] body[data-ds-dark-theme] ._2H3hWW_root:not(._2H3hWW_
255
277
  }
256
278
  }
257
279
 
280
+ .dsh-css-date-heading {
281
+ padding: 16px 4px 6px;
282
+ color: var(--dsw-alias-label-tertiary);
283
+ font-size: 12px;
284
+ line-height: 18px;
285
+
286
+ &:first-child { padding-top: 6px; }
287
+ }
288
+
258
289
  /* 悬浮卡片由宿主通过 portal 挂在侧栏外。 */
259
290
  .dsh-css-project {
260
291
  display: none;