dsh-ws-recency 0.10.1 → 0.10.3
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 +2 -2
- package/client.js +141 -18
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -65,7 +65,7 @@ DSH 原生的置顶只把会话顶到**它所属工作区分组的第一行**,
|
|
|
65
65
|
|
|
66
66
|
## 六、Token 消耗统计
|
|
67
67
|
|
|
68
|
-
|
|
68
|
+
会话下方统计条上的「用量分析」,或会话「…」菜单 →「Token 消耗统计」:
|
|
69
69
|
|
|
70
70
|
- **真实账单**(每步记录的 usage):步数、输入(未缓存)、缓存命中、输出、合计;
|
|
71
71
|
- **结构占比**(按内容估算):主 agent 自己(思考 / 回复 / 用户消息 / 系统提示)vs 每个工具,一条占比条 + 图例;
|
|
@@ -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.
|
|
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
|
|
114
114
|
```
|
|
115
115
|
|
|
116
116
|
装完把 `dsh-ws-recency` 加进 `~/.hypers-work/profiles/<profile>/package.json` 的 `dsh.profile.bundles`,重启应用。
|
package/client.js
CHANGED
|
@@ -85,6 +85,7 @@
|
|
|
85
85
|
var pinHidden = new Set();
|
|
86
86
|
var pinHost = null;
|
|
87
87
|
var expandClicks = new Map();
|
|
88
|
+
var composerPill = null;
|
|
88
89
|
var expandedSections = new Set();
|
|
89
90
|
var headerMarked = new WeakSet();
|
|
90
91
|
var disposed = false;
|
|
@@ -104,6 +105,8 @@
|
|
|
104
105
|
".wsr-pin-unpin{flex:none;border:none;background:transparent;color:var(--dsw-alias-label-tertiary);cursor:pointer;font-size:11px;padding:2px 4px;border-radius:4px;visibility:hidden}",
|
|
105
106
|
".wsr-pin-row:hover .wsr-pin-unpin{visibility:visible}",
|
|
106
107
|
".wsr-pin-unpin:hover{color:var(--dsw-alias-label-primary)}",
|
|
108
|
+
".wsr-pill{box-sizing:border-box;border:none;background:0 0;color:var(--dsw-alias-label-tertiary);font:inherit;font-variant-numeric:tabular-nums;line-height:inherit;white-space:nowrap;border-radius:var(--dsw-radius-sm,6px);display:inline-flex;align-items:center;gap:6px;padding:1px 8px;cursor:pointer}",
|
|
109
|
+
".wsr-pill:hover{background:var(--dsw-alias-interactive-bg-hover,rgba(255,255,255,.08));color:var(--dsw-alias-label-secondary,#cfd3d8)}",
|
|
107
110
|
".wsr-modal-backdrop{position:fixed;inset:0;z-index:2000;display:flex;align-items:center;justify-content:center;background:rgba(0,0,0,.45)}",
|
|
108
111
|
".wsr-modal{width:min(720px,92vw);max-height:84vh;display:flex;flex-direction:column;background:var(--dsw-alias-bg-layer-1,#1f2226);color:var(--dsw-alias-label-primary,#e8e8e8);border:.5px solid var(--dsw-alias-border-l2,rgba(255,255,255,.14));border-radius:10px;box-shadow:0 12px 40px rgba(0,0,0,.45);overflow:hidden}",
|
|
109
112
|
".wsr-modal-head{display:flex;align-items:center;justify-content:space-between;padding:12px 14px;border-bottom:.5px solid var(--dsw-alias-border-l2,rgba(255,255,255,.1))}",
|
|
@@ -181,6 +184,7 @@
|
|
|
181
184
|
"stats.agentDetail": "主 agent 构成:思考 {reasoning} · 回复 {text} · 用户消息 {user} · 系统提示 {system}",
|
|
182
185
|
"stats.diagnose.unmounted": "\n\n(宿主半已加载,但统计路由没挂上——重启应用再试)",
|
|
183
186
|
"stats.diagnose.noHost": "\n\n(宿主半未加载:需要重启应用加载新版本)",
|
|
187
|
+
"composer.analysis": "用量分析",
|
|
184
188
|
"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",
|
|
185
189
|
"trash.failed": "\u5220\u9664\u5931\u8d25\uff1a",
|
|
186
190
|
};
|
|
@@ -221,17 +225,32 @@
|
|
|
221
225
|
"stats.agentDetail": "Agent composition: reasoning {reasoning} \u00b7 reply {text} \u00b7 user messages {user} \u00b7 system prompt {system}",
|
|
222
226
|
"stats.diagnose.unmounted": "\n\n(the host half is loaded but the statistics route did not mount - restart the app and retry)",
|
|
223
227
|
"stats.diagnose.noHost": "\n\n(the host half is not loaded: restart the app to pick up the new version)",
|
|
228
|
+
"composer.analysis": "Usage analysis",
|
|
224
229
|
"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).",
|
|
225
230
|
"trash.failed": "Delete failed: ",
|
|
226
231
|
};
|
|
227
232
|
|
|
228
|
-
|
|
233
|
+
/* Resolved lazily: the dictionaries are declared further down this closure. */
|
|
234
|
+
var resolvedDictionary = null;
|
|
235
|
+
|
|
236
|
+
function fallbackDictionary() {
|
|
237
|
+
if (resolvedDictionary !== null) return resolvedDictionary;
|
|
238
|
+
var language = "";
|
|
239
|
+
/* The page's own lang wins: it is what the UI is actually rendered in. */
|
|
240
|
+
try { if (typeof document !== "undefined" && document.documentElement !== null && document.documentElement !== undefined) language = document.documentElement.lang || ""; } catch (error) { language = ""; }
|
|
241
|
+
try { if (language === "" && typeof navigator !== "undefined" && navigator.language) language = navigator.language; } catch (error) { /* keep empty */ }
|
|
242
|
+
resolvedDictionary = String(language).toLowerCase().indexOf("zh") === 0 ? ZH : EN;
|
|
243
|
+
return resolvedDictionary;
|
|
244
|
+
}
|
|
229
245
|
|
|
230
246
|
function t(key, params) {
|
|
231
247
|
if (typeof data.t === "function") {
|
|
232
|
-
|
|
248
|
+
var translated = null;
|
|
249
|
+
try { translated = data.t(key, params); } catch (error) { translated = null; }
|
|
250
|
+
if (typeof translated === "string" && translated !== key) return translated;
|
|
233
251
|
}
|
|
234
|
-
var
|
|
252
|
+
var dictionary = fallbackDictionary();
|
|
253
|
+
var text = (dictionary && dictionary[key]) || key;
|
|
235
254
|
if (params !== undefined && params.n !== undefined) text = text.replace("{n}", String(params.n));
|
|
236
255
|
return text;
|
|
237
256
|
}
|
|
@@ -785,20 +804,122 @@
|
|
|
785
804
|
}
|
|
786
805
|
}
|
|
787
806
|
|
|
807
|
+
/** The session the conversation view is showing, from its own DOM hook. */
|
|
808
|
+
function currentConversationId() {
|
|
809
|
+
if (typeof document === "undefined") return null;
|
|
810
|
+
var element = document.querySelector("[data-conversation-session]");
|
|
811
|
+
if (element === null || typeof element.getAttribute !== "function") return null;
|
|
812
|
+
var id = element.getAttribute("data-conversation-session");
|
|
813
|
+
return typeof id === "string" && id !== "" ? id : null;
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
/** The nearest horizontal flex row above the stats: the whole footer dock. */
|
|
817
|
+
function composerDockOf(row) {
|
|
818
|
+
var node = row;
|
|
819
|
+
for (var depth = 0; depth < 4; depth += 1) {
|
|
820
|
+
var parent = node.parentElement;
|
|
821
|
+
if (parent === null) break;
|
|
822
|
+
var style = null;
|
|
823
|
+
try {
|
|
824
|
+
style = typeof window !== "undefined" && typeof window.getComputedStyle === "function"
|
|
825
|
+
? window.getComputedStyle(parent)
|
|
826
|
+
: null;
|
|
827
|
+
} catch (error) {
|
|
828
|
+
style = null;
|
|
829
|
+
}
|
|
830
|
+
if (style !== null && style.display === "flex" && (style.flexDirection === "row" || style.flexDirection === "")) return parent;
|
|
831
|
+
node = parent;
|
|
832
|
+
}
|
|
833
|
+
return row.parentElement === null ? row : row.parentElement;
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
/** The host's own pill in that row, used as the visual template. */
|
|
837
|
+
function pillTemplateOf(row) {
|
|
838
|
+
if (typeof row.querySelector !== "function") return null;
|
|
839
|
+
var button = row.querySelector('button[class*="_pill"]');
|
|
840
|
+
return button !== null ? button : row.querySelector('[class*="_pill"]');
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
function cloneIconOf(template) {
|
|
844
|
+
if (template === null || typeof template.querySelector !== "function") return null;
|
|
845
|
+
var icon = template.querySelector("svg");
|
|
846
|
+
if (icon === null || typeof icon.cloneNode !== "function") return null;
|
|
847
|
+
try { return icon.cloneNode(true); } catch (error) { return null; }
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
/**
|
|
851
|
+
* Add one pill at the END of the composer footer that opens the analysis
|
|
852
|
+
* panel for the conversation on screen.
|
|
853
|
+
*
|
|
854
|
+
* It reuses the host pill's own class names and icon so it reads as part of
|
|
855
|
+
* the same row, sits after the context ring rather than between the stats,
|
|
856
|
+
* is re-attached whenever the host re-renders, and disappears with the row.
|
|
857
|
+
*/
|
|
858
|
+
function decorateComposerStats() {
|
|
859
|
+
if (typeof document === "undefined") return;
|
|
860
|
+
var existing = document.querySelector("[data-wsr-composer-analysis]");
|
|
861
|
+
var seat = document.querySelector("[data-composer-seat]");
|
|
862
|
+
var anchors = seat === null || typeof seat.querySelectorAll !== "function" ? [] : seat.querySelectorAll("[data-composer-stat]");
|
|
863
|
+
if (anchors.length === 0 || currentConversationId() === null) {
|
|
864
|
+
if (existing !== null) existing.remove();
|
|
865
|
+
composerPill = null;
|
|
866
|
+
return;
|
|
867
|
+
}
|
|
868
|
+
var row = anchors[0].parentElement;
|
|
869
|
+
if (row === null) return;
|
|
870
|
+
var dock = composerDockOf(row);
|
|
871
|
+
|
|
872
|
+
if (existing === null) {
|
|
873
|
+
var template = pillTemplateOf(row);
|
|
874
|
+
var button = document.createElement("button");
|
|
875
|
+
button.type = "button";
|
|
876
|
+
button.setAttribute("data-wsr-composer-analysis", "true");
|
|
877
|
+
var base = template !== null && typeof template.className === "string" ? template.className : "";
|
|
878
|
+
button.className = base === "" ? "wsr-pill" : base + " wsr-pill";
|
|
879
|
+
var icon = cloneIconOf(template);
|
|
880
|
+
if (icon !== null) button.appendChild(icon);
|
|
881
|
+
var label = document.createElement("span");
|
|
882
|
+
var labelTemplate = template === null || typeof template.querySelector !== "function" ? null : template.querySelector('[class*="_label"]');
|
|
883
|
+
label.className = labelTemplate !== null && typeof labelTemplate.className === "string" && labelTemplate.className !== ""
|
|
884
|
+
? labelTemplate.className
|
|
885
|
+
: "wsr-pill-label";
|
|
886
|
+
label.textContent = t("composer.analysis");
|
|
887
|
+
button.appendChild(label);
|
|
888
|
+
button.addEventListener("click", function () {
|
|
889
|
+
var id = currentConversationId();
|
|
890
|
+
if (id !== null) openStatsPanel(id);
|
|
891
|
+
});
|
|
892
|
+
composerPill = button;
|
|
893
|
+
} else {
|
|
894
|
+
composerPill = existing;
|
|
895
|
+
}
|
|
896
|
+
var children = dock.children || [];
|
|
897
|
+
if (composerPill.parentElement !== dock || children[children.length - 1] !== composerPill) {
|
|
898
|
+
dock.appendChild(composerPill);
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
|
|
788
902
|
function apply() {
|
|
789
903
|
if (disposed) return;
|
|
790
904
|
var sessions = data.sessions;
|
|
791
905
|
var workspaces = data.workspaces;
|
|
792
|
-
if (!ready(sessions) || !ready(workspaces)) return;
|
|
793
|
-
|
|
794
906
|
var settings = settingsOf();
|
|
907
|
+
if (!ready(sessions) || !ready(workspaces)) {
|
|
908
|
+
decorateComposerStats();
|
|
909
|
+
return;
|
|
910
|
+
}
|
|
911
|
+
|
|
795
912
|
var entries = collect();
|
|
796
|
-
if (entries.length === 0)
|
|
913
|
+
if (entries.length === 0) {
|
|
914
|
+
decorateComposerStats();
|
|
915
|
+
return;
|
|
916
|
+
}
|
|
797
917
|
|
|
798
918
|
orderGroups(entries, latestByWorkspace(sessions, workspaces));
|
|
799
919
|
foldRows(entries, settings, sessions, Date.now());
|
|
800
920
|
renderPinned(entries, sessions, workspaces, settings);
|
|
801
921
|
if (settings.colors) decorateRows(entries, sessions, data.statuses, settings);
|
|
922
|
+
decorateComposerStats();
|
|
802
923
|
}
|
|
803
924
|
|
|
804
925
|
function schedule() {
|
|
@@ -846,7 +967,7 @@
|
|
|
846
967
|
return h(Item, {
|
|
847
968
|
icon: typeof Icon === "function" ? h(Icon, {}) : undefined,
|
|
848
969
|
onSelect: function () { props.actions.toggleFollowUp(props.sessionId); },
|
|
849
|
-
},
|
|
970
|
+
}, t(flagged ? "menu.unflag" : "menu.flag"));
|
|
850
971
|
}
|
|
851
972
|
|
|
852
973
|
/**
|
|
@@ -910,7 +1031,7 @@
|
|
|
910
1031
|
if (typeof window.alert === "function") window.alert(t("trash.failed") + (error && error.message ? error.message : error));
|
|
911
1032
|
});
|
|
912
1033
|
},
|
|
913
|
-
},
|
|
1034
|
+
}, t("menu.trash"));
|
|
914
1035
|
}
|
|
915
1036
|
|
|
916
1037
|
/* ------------------------- token statistics ------------------------- */
|
|
@@ -1118,7 +1239,7 @@
|
|
|
1118
1239
|
return h(Item, {
|
|
1119
1240
|
icon: typeof Icon === "function" ? h(Icon, {}) : undefined,
|
|
1120
1241
|
onSelect: function () { openStatsPanel(props.sessionId); },
|
|
1121
|
-
},
|
|
1242
|
+
}, t("menu.stats"));
|
|
1122
1243
|
}
|
|
1123
1244
|
|
|
1124
1245
|
/** Settings -> General row: how old a session must be before it is folded. */
|
|
@@ -1127,11 +1248,11 @@
|
|
|
1127
1248
|
var days = Number(settings.foldDays);
|
|
1128
1249
|
if (!Number.isFinite(days)) days = DEFAULT_DAYS;
|
|
1129
1250
|
var keep = settings.keepActive !== false;
|
|
1130
|
-
var text =
|
|
1251
|
+
var text = t("settings.title");
|
|
1131
1252
|
return h("div", { className: "wsr-setRow" },
|
|
1132
1253
|
h("div", { className: "wsr-setText" },
|
|
1133
1254
|
h("div", { className: "wsr-setTitle" }, text),
|
|
1134
|
-
h("div", { className: "wsr-setDesc" },
|
|
1255
|
+
h("div", { className: "wsr-setDesc" }, t("settings.description")),
|
|
1135
1256
|
),
|
|
1136
1257
|
h("div", { className: "wsr-controls" },
|
|
1137
1258
|
h("button", {
|
|
@@ -1161,7 +1282,7 @@
|
|
|
1161
1282
|
checked: keep,
|
|
1162
1283
|
onChange: function (event) { props.actions.setKeepActive(event.target.checked); },
|
|
1163
1284
|
}),
|
|
1164
|
-
|
|
1285
|
+
t("settings.exempt")),
|
|
1165
1286
|
),
|
|
1166
1287
|
);
|
|
1167
1288
|
}
|
|
@@ -1170,11 +1291,11 @@
|
|
|
1170
1291
|
function ColorsRow(props) {
|
|
1171
1292
|
var settings = props.useStore(function (state) { return state; });
|
|
1172
1293
|
var enabled = settings.colors !== false;
|
|
1173
|
-
var text =
|
|
1294
|
+
var text = t("color.title");
|
|
1174
1295
|
return h("div", { className: "wsr-setRow" },
|
|
1175
1296
|
h("div", { className: "wsr-setText" },
|
|
1176
1297
|
h("div", { className: "wsr-setTitle" }, text),
|
|
1177
|
-
h("div", { className: "wsr-setDesc" },
|
|
1298
|
+
h("div", { className: "wsr-setDesc" }, t("color.description")),
|
|
1178
1299
|
),
|
|
1179
1300
|
h("div", { className: "wsr-controls" },
|
|
1180
1301
|
h("label", { className: "wsr-exempt" },
|
|
@@ -1183,7 +1304,7 @@
|
|
|
1183
1304
|
checked: enabled,
|
|
1184
1305
|
onChange: function (event) { props.actions.setColors(event.target.checked); },
|
|
1185
1306
|
}),
|
|
1186
|
-
|
|
1307
|
+
t("color.enabled")),
|
|
1187
1308
|
),
|
|
1188
1309
|
);
|
|
1189
1310
|
}
|
|
@@ -1192,11 +1313,11 @@
|
|
|
1192
1313
|
function PinSectionRow(props) {
|
|
1193
1314
|
var settings = props.useStore(function (state) { return state; });
|
|
1194
1315
|
var enabled = settings.pinnedSection !== false;
|
|
1195
|
-
var text =
|
|
1316
|
+
var text = t("pin.title");
|
|
1196
1317
|
return h("div", { className: "wsr-setRow" },
|
|
1197
1318
|
h("div", { className: "wsr-setText" },
|
|
1198
1319
|
h("div", { className: "wsr-setTitle" }, text),
|
|
1199
|
-
h("div", { className: "wsr-setDesc" },
|
|
1320
|
+
h("div", { className: "wsr-setDesc" }, t("pin.description")),
|
|
1200
1321
|
),
|
|
1201
1322
|
h("div", { className: "wsr-controls" },
|
|
1202
1323
|
h("label", { className: "wsr-exempt" },
|
|
@@ -1205,7 +1326,7 @@
|
|
|
1205
1326
|
checked: enabled,
|
|
1206
1327
|
onChange: function (event) { props.actions.setPinnedSection(event.target.checked); },
|
|
1207
1328
|
}),
|
|
1208
|
-
|
|
1329
|
+
t("pin.enabled")),
|
|
1209
1330
|
),
|
|
1210
1331
|
);
|
|
1211
1332
|
}
|
|
@@ -1236,6 +1357,8 @@
|
|
|
1236
1357
|
});
|
|
1237
1358
|
pinHidden.clear();
|
|
1238
1359
|
if (pinHost !== null && pinHost.parentElement !== null) pinHost.remove();
|
|
1360
|
+
if (composerPill !== null && typeof composerPill.remove === "function") composerPill.remove();
|
|
1361
|
+
composerPill = null;
|
|
1239
1362
|
pinHost = null;
|
|
1240
1363
|
expandClicks.clear();
|
|
1241
1364
|
roots.forEach(function (root) { root.removeAttribute(ROOT_ATTR); });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-ws-recency",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.3",
|
|
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",
|