dsh-ws-recency 0.10.3 → 0.10.5

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 +3 -3
  2. package/client.js +98 -30
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -68,8 +68,8 @@ DSH 原生的置顶只把会话顶到**它所属工作区分组的第一行**,
68
68
  会话下方统计条上的「用量分析」,或会话「…」菜单 →「Token 消耗统计」:
69
69
 
70
70
  - **真实账单**(每步记录的 usage):步数、输入(未缓存)、缓存命中、输出、合计;
71
- - **结构占比**(按内容估算):主 agent 自己(思考 / 回复 / 用户消息 / 系统提示)vs 每个工具,一条占比条 + 图例;
72
- - **工具表**:调用次数、参数估算、结果估算、合计、占比,按合计降序。
71
+ - **消耗分布图**(按内容估算):主 agent 自己(思考 / 回复 / 用户消息 / 系统提示)vs 每个工具,每个来源一条带颜色的横条,按占用长度直观对比;
72
+ - **明细表**:调用次数、参数估算、结果估算、合计、占比,按合计降序,默认折叠,点「显示明细」展开。
73
73
 
74
74
  口径说明:prompt 的 token 是「整段上下文每一步都计费」,没法精确归属到某一条消息,所以「主 agent / 工具」的拆分是按内容长度估算(中文≈1 token/字,英文≈4 字符/token),用来比较占比;**总量是真的**。数据从会话日志算,跨重启也能看。大会话第一次打开约 1–2 秒(29MB 日志实测 1.2 秒),10 秒内重复打开走缓存。
75
75
 
@@ -110,7 +110,7 @@ dsh plugin --profile <profile> add github:chyxhtc/dsh-ws-recency
110
110
  或固定版本的预构建包:
111
111
 
112
112
  ```powershell
113
- dsh plugin --profile <profile> add https://github.com/chyxhtc/dsh-ws-recency/releases/download/v0.10.3/dsh-ws-recency-0.10.3.tgz
113
+ dsh plugin --profile <profile> add https://github.com/chyxhtc/dsh-ws-recency/releases/download/v0.10.5/dsh-ws-recency-0.10.5.tgz
114
114
  ```
115
115
 
116
116
  装完把 `dsh-ws-recency` 加进 `~/.hypers-work/profiles/<profile>/package.json` 的 `dsh.profile.bundles`,重启应用。
package/client.js CHANGED
@@ -123,6 +123,17 @@
123
123
  ".wsr-stats-legend{display:flex;flex-wrap:wrap;gap:10px}",
124
124
  ".wsr-stats-legend-item{display:flex;align-items:center;gap:5px;font-size:11px;color:var(--dsw-alias-label-tertiary)}",
125
125
  ".wsr-stats-swatch{width:8px;height:8px;border-radius:2px}",
126
+ ".wsr-chart{display:flex;flex-direction:column;gap:6px}",
127
+ ".wsr-chart-title{font-size:11px;color:var(--dsw-alias-label-tertiary)}",
128
+ ".wsr-chart-row{display:grid;grid-template-columns:120px 1fr 64px 56px;align-items:center;gap:8px}",
129
+ ".wsr-chart-label{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-weight:500}",
130
+ ".wsr-chart-track{height:8px;border-radius:4px;background:rgba(255,255,255,.06);overflow:hidden}",
131
+ ".wsr-chart-fill{height:100%;border-radius:4px;min-width:2px}",
132
+ ".wsr-chart-value{text-align:right;font-variant-numeric:tabular-nums}",
133
+ ".wsr-chart-share{text-align:right;color:var(--dsw-alias-label-tertiary);font-variant-numeric:tabular-nums}",
134
+ ".wsr-details-toggle{align-self:flex-start;border:none;background:0 0;color:var(--dsw-alias-label-tertiary);font:inherit;font-size:11px;cursor:pointer;padding:2px 0}",
135
+ ".wsr-details-toggle:hover{color:var(--dsw-alias-label-primary)}",
136
+ ".wsr-stats-details{display:flex;flex-direction:column;gap:8px}",
126
137
  ".wsr-stats-table{display:flex;flex-direction:column}",
127
138
  ".wsr-stats-row{display:grid;grid-template-columns:1.4fr .6fr .8fr .8fr .8fr .6fr;gap:6px;padding:6px 4px;border-bottom:.5px solid rgba(255,255,255,.06)}",
128
139
  ".wsr-stats-head{color:var(--dsw-alias-label-tertiary);font-size:11px}",
@@ -184,6 +195,9 @@
184
195
  "stats.agentDetail": "主 agent 构成:思考 {reasoning} · 回复 {text} · 用户消息 {user} · 系统提示 {system}",
185
196
  "stats.diagnose.unmounted": "\n\n(宿主半已加载,但统计路由没挂上——重启应用再试)",
186
197
  "stats.diagnose.noHost": "\n\n(宿主半未加载:需要重启应用加载新版本)",
198
+ "stats.chart": "消耗分布",
199
+ "stats.details.show": "显示明细 ▾",
200
+ "stats.details.hide": "收起明细 ▴",
187
201
  "composer.analysis": "用量分析",
188
202
  "trash.confirm": "\u5220\u9664\u8fd9\u4e2a\u4f1a\u8bdd\uff1f\n\n\u4f1a\u5148\u505c\u6b62\u5b83\u6b63\u5728\u8fdb\u884c\u7684\u5de5\u4f5c\uff0c\u518d\u628a\u65e5\u5fd7\u76ee\u5f55\u79fb\u5165\u56de\u6536\u7ad9\uff08\u4e0d\u662f\u7269\u7406\u5220\u9664\uff0c\u53ef\u4ee5\u624b\u52a8\u6062\u590d\uff09\u3002",
189
203
  "trash.failed": "\u5220\u9664\u5931\u8d25\uff1a",
@@ -225,6 +239,9 @@
225
239
  "stats.agentDetail": "Agent composition: reasoning {reasoning} \u00b7 reply {text} \u00b7 user messages {user} \u00b7 system prompt {system}",
226
240
  "stats.diagnose.unmounted": "\n\n(the host half is loaded but the statistics route did not mount - restart the app and retry)",
227
241
  "stats.diagnose.noHost": "\n\n(the host half is not loaded: restart the app to pick up the new version)",
242
+ "stats.chart": "Where the tokens went",
243
+ "stats.details.show": "Show details",
244
+ "stats.details.hide": "Hide details",
228
245
  "composer.analysis": "Usage analysis",
229
246
  "trash.confirm": "Delete this session?\n\nIt stops whatever the session is running, then moves the log directory into the trash folder (a move, not an erase: you can restore it by hand).",
230
247
  "trash.failed": "Delete failed: ",
@@ -847,6 +864,31 @@
847
864
  try { return icon.cloneNode(true); } catch (error) { return null; }
848
865
  }
849
866
 
867
+ /** Copy the host pill's own computed metrics onto the clone. */
868
+ function copyPillMetrics(template, wrapper, button) {
869
+ if (template === null || template === undefined || typeof window === "undefined" || typeof window.getComputedStyle !== "function") return;
870
+ var read = function (name) {
871
+ try {
872
+ return window.getComputedStyle(template).getPropertyValue(name) || "";
873
+ } catch (error) {
874
+ return "";
875
+ }
876
+ };
877
+ var inherited = ["font-size", "font-family", "font-weight", "line-height", "letter-spacing"];
878
+ for (var i = 0; i < inherited.length; i += 1) {
879
+ var value = read(inherited[i]);
880
+ if (value !== "") {
881
+ wrapper.style[inherited[i]] = value;
882
+ button.style[inherited[i]] = value;
883
+ }
884
+ }
885
+ var own = ["padding", "gap", "border-radius", "color"];
886
+ for (var j = 0; j < own.length; j += 1) {
887
+ var metric = read(own[j]);
888
+ if (metric !== "") button.style[own[j]] = metric;
889
+ }
890
+ }
891
+
850
892
  /**
851
893
  * Add one pill at the END of the composer footer that opens the analysis
852
894
  * panel for the conversation on screen.
@@ -871,9 +913,17 @@
871
913
 
872
914
  if (existing === null) {
873
915
  var template = pillTemplateOf(row);
916
+ /* Wrapped exactly like a host pill (its anchor span) and carrying the
917
+ host's own computed metrics: appended to the footer dock rather than
918
+ into the stats row, this pill would otherwise inherit the dock's font
919
+ size and read larger than its neighbours. */
920
+ var wrapper = document.createElement("span");
921
+ wrapper.setAttribute("data-wsr-composer-analysis", "true");
922
+ var anchorTemplate = template === null ? null : template.parentElement;
923
+ var anchorClass = anchorTemplate !== null && typeof anchorTemplate.className === "string" ? anchorTemplate.className : "";
924
+ if (anchorClass !== "") wrapper.className = anchorClass;
874
925
  var button = document.createElement("button");
875
926
  button.type = "button";
876
- button.setAttribute("data-wsr-composer-analysis", "true");
877
927
  var base = template !== null && typeof template.className === "string" ? template.className : "";
878
928
  button.className = base === "" ? "wsr-pill" : base + " wsr-pill";
879
929
  var icon = cloneIconOf(template);
@@ -889,7 +939,9 @@
889
939
  var id = currentConversationId();
890
940
  if (id !== null) openStatsPanel(id);
891
941
  });
892
- composerPill = button;
942
+ copyPillMetrics(template, wrapper, button);
943
+ wrapper.appendChild(button);
944
+ composerPill = wrapper;
893
945
  } else {
894
946
  composerPill = existing;
895
947
  }
@@ -1112,31 +1164,39 @@
1112
1164
 
1113
1165
  var tools = estimate.tools || [];
1114
1166
  var palette = ["#d97706", "#059669", "#7c3aed", "#db2777", "#0891b2", "#65a30d", "#9333ea", "#dc2626"];
1115
- var segments = [{ name: t("stats.agent"), share: agent.share || 0, color: "#4b8bf5" }];
1116
- for (var j = 0; j < tools.length; j += 1) segments.push({ name: tools[j].name, share: tools[j].share || 0, color: palette[j % palette.length] });
1117
-
1118
- var bar = statsDiv("wsr-stats-bar");
1119
- for (var k = 0; k < segments.length; k += 1) {
1120
- if (segments[k].share <= 0) continue;
1121
- var piece = statsDiv("wsr-stats-seg");
1122
- piece.style.width = (segments[k].share * 100).toFixed(2) + "%";
1123
- piece.style.background = segments[k].color;
1124
- piece.setAttribute("title", segments[k].name + " " + formatShare(segments[k].share));
1125
- bar.appendChild(piece);
1167
+ var entries = [{ name: t("stats.agent"), value: agent.total || 0, share: agent.share || 0, color: "#4b8bf5" }];
1168
+ for (var j = 0; j < tools.length; j += 1) {
1169
+ entries.push({ name: tools[j].name, value: tools[j].total || 0, share: tools[j].share || 0, color: palette[j % palette.length] });
1126
1170
  }
1127
- body.appendChild(bar);
1128
-
1129
- var legend = statsDiv("wsr-stats-legend");
1130
- for (var m = 0; m < segments.length; m += 1) {
1131
- if (segments[m].share <= 0) continue;
1132
- var item = statsDiv("wsr-stats-legend-item");
1133
- var swatch = statsDiv("wsr-stats-swatch");
1134
- swatch.style.background = segments[m].color;
1135
- item.appendChild(swatch);
1136
- item.appendChild(statsDiv("wsr-stats-legend-text", segments[m].name + " " + formatShare(segments[m].share)));
1137
- legend.appendChild(item);
1171
+
1172
+ /* The picture first: one bar per source, sized against the largest. */
1173
+ var chart = statsDiv("wsr-chart");
1174
+ chart.appendChild(statsDiv("wsr-chart-title", t("stats.chart")));
1175
+ var largest = 1;
1176
+ for (var m = 0; m < entries.length; m += 1) if (entries[m].value > largest) largest = entries[m].value;
1177
+ for (var k = 0; k < entries.length; k += 1) {
1178
+ var line = statsDiv("wsr-chart-row");
1179
+ line.appendChild(statsDiv("wsr-chart-label", entries[k].name));
1180
+ var track = statsDiv("wsr-chart-track");
1181
+ var fill = statsDiv("wsr-chart-fill");
1182
+ fill.style.width = ((entries[k].value / largest) * 100).toFixed(2) + "%";
1183
+ fill.style.background = entries[k].color;
1184
+ track.appendChild(fill);
1185
+ line.appendChild(track);
1186
+ line.appendChild(statsDiv("wsr-chart-value", formatTokens(entries[k].value)));
1187
+ line.appendChild(statsDiv("wsr-chart-share", formatShare(entries[k].share)));
1188
+ chart.appendChild(line);
1138
1189
  }
1139
- body.appendChild(legend);
1190
+ body.appendChild(chart);
1191
+
1192
+ /* The table is the detail view: folded away until asked for. */
1193
+ var toggle = document.createElement("button");
1194
+ toggle.type = "button";
1195
+ toggle.className = "wsr-details-toggle";
1196
+ toggle.textContent = t("stats.details.show");
1197
+
1198
+ var details = statsDiv("wsr-stats-details");
1199
+ details.style.display = "none";
1140
1200
 
1141
1201
  var table = statsDiv("wsr-stats-table");
1142
1202
  var head = document.createElement("div");
@@ -1145,18 +1205,26 @@
1145
1205
  for (var n = 0; n < columns.length; n += 1) head.appendChild(statsDiv("wsr-td", t(columns[n])));
1146
1206
  table.appendChild(head);
1147
1207
  table.appendChild(statsRow(t("stats.agent"), stats.steps, null, null, agent.total, agent.share));
1148
- for (var p = 0; p < tools.length; p += 1) {
1149
- var tool = tools[p];
1208
+ for (var q = 0; q < tools.length; q += 1) {
1209
+ var tool = tools[q];
1150
1210
  table.appendChild(statsRow(tool.name, tool.calls, tool.args, tool.results, tool.total, tool.share));
1151
1211
  }
1152
- body.appendChild(table);
1153
-
1154
- body.appendChild(statsDiv("wsr-stats-detail", t("stats.agentDetail", {
1212
+ details.appendChild(table);
1213
+ details.appendChild(statsDiv("wsr-stats-detail", t("stats.agentDetail", {
1155
1214
  reasoning: formatTokens(agent.reasoning),
1156
1215
  text: formatTokens(agent.text),
1157
1216
  user: formatTokens(agent.user),
1158
1217
  system: formatTokens(agent.system),
1159
1218
  })));
1219
+
1220
+ toggle.addEventListener("click", function () {
1221
+ var open = details.style.display !== "none";
1222
+ details.style.display = open ? "none" : "";
1223
+ toggle.textContent = open ? t("stats.details.show") : t("stats.details.hide");
1224
+ });
1225
+
1226
+ body.appendChild(toggle);
1227
+ body.appendChild(details);
1160
1228
  body.appendChild(statsDiv("wsr-stats-note", t("stats.note")));
1161
1229
  }
1162
1230
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-ws-recency",
3
- "version": "0.10.3",
3
+ "version": "0.10.5",
4
4
  "description": "Sidebar session management for DeepSeek Harness: workspace groups sorted by latest activity, stale history folded, a global pinned section, follow-up marks, session delete into a recoverable trash, and per-session token statistics split by tool.",
5
5
  "license": "MIT",
6
6
  "author": "chyxhtc",