dsh-auto-compaction 0.2.0 → 0.2.2
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/lib/client.js +240 -12
- package/lib/index.js +43 -13
- package/package.json +2 -2
package/lib/client.js
CHANGED
|
@@ -41,10 +41,15 @@ window.__ModuleLoader__.load({
|
|
|
41
41
|
{ id: "strip", label: "状态条" },
|
|
42
42
|
{ id: "ring", label: "进度环" },
|
|
43
43
|
{ id: "topbar", label: "顶部条" },
|
|
44
|
+
{ id: "corner", label: "右上角" },
|
|
44
45
|
];
|
|
46
|
+
function presetLabel(id) {
|
|
47
|
+
for (var i = 0; i < PRESETS.length; i += 1) if (PRESETS[i].id === id) return PRESETS[i].label;
|
|
48
|
+
return String(id || "未命名预设");
|
|
49
|
+
}
|
|
45
50
|
var EXAMPLE_WINDOW = 250000, EXAMPLE_RESERVED = 20000, EXAMPLE_HEADROOM = 10000;
|
|
46
51
|
|
|
47
|
-
var DISPLAY_STYLES = ["orbit", "strip", "ring", "topbar"];
|
|
52
|
+
var DISPLAY_STYLES = ["orbit", "strip", "ring", "topbar", "corner"];
|
|
48
53
|
var displayStyle = "orbit";
|
|
49
54
|
var displayStyleListeners = [];
|
|
50
55
|
|
|
@@ -103,7 +108,11 @@ window.__ModuleLoader__.load({
|
|
|
103
108
|
var STATUS_MUTED = "var(--dsh-text-secondary, var(--dsw-alias-label-secondary, #9aa0a6))";
|
|
104
109
|
var STATUS_FAINT = "var(--dsh-text-tertiary, var(--dsw-alias-label-tertiary, #7f858c))";
|
|
105
110
|
var STATUS_BORDER = "var(--dsh-border, var(--dsw-alias-border-l2, #2c2e33))";
|
|
106
|
-
|
|
111
|
+
// Opaque elevated surface. The menu token is intentionally translucent
|
|
112
|
+
// (menu layers add their own backdrop blur); the status surfaces must stay
|
|
113
|
+
// readable over live conversation text without a blur.
|
|
114
|
+
var STATUS_SURFACE = "var(--dsw-alias-bg-layer-3, #353638)";
|
|
115
|
+
var STATUS_PANEL = STATUS_SURFACE;
|
|
107
116
|
var STATUS_ACCENT = "var(--dsw-alias-state-business-primary, #4c8dff)";
|
|
108
117
|
var STATUS_SUCCESS = "var(--dsw-alias-state-success-primary, #3fb950)";
|
|
109
118
|
var STATUS_WARN = "var(--dsw-alias-state-warn-primary, #d29922)";
|
|
@@ -166,6 +175,36 @@ window.__ModuleLoader__.load({
|
|
|
166
175
|
return call(connection, "status", { sessionId: sessionId }, 3500);
|
|
167
176
|
}
|
|
168
177
|
|
|
178
|
+
/**
|
|
179
|
+
* Anchor the corner card to the conversation area instead of the window:
|
|
180
|
+
* its top lines up with the first message row (below the 对话/轨迹 tabs) and
|
|
181
|
+
* its right edge keeps the header's inset, so it never covers the tab row.
|
|
182
|
+
* The width shrinks to the gutter between the message column and the window
|
|
183
|
+
* edge when the window is narrow.
|
|
184
|
+
*/
|
|
185
|
+
function cornerPlacement(desiredWidth) {
|
|
186
|
+
var right = (typeof window === "undefined" ? 0 : window.innerWidth) - 20;
|
|
187
|
+
var top = 96;
|
|
188
|
+
var width = desiredWidth;
|
|
189
|
+
try {
|
|
190
|
+
var body = document.querySelector("[data-conversation-content]");
|
|
191
|
+
var br = body && body.getBoundingClientRect();
|
|
192
|
+
if (br && br.height > 80) {
|
|
193
|
+
top = Math.round(br.top) + 8;
|
|
194
|
+
right = Math.round(br.right) - 20;
|
|
195
|
+
}
|
|
196
|
+
var card = document.querySelector("[data-composer-card]");
|
|
197
|
+
var cr = card && card.getBoundingClientRect();
|
|
198
|
+
if (cr && cr.width > 120) {
|
|
199
|
+
var clearance = parseFloat(getComputedStyle(card).getPropertyValue("--dsh-composer-side-clearance")) || 16;
|
|
200
|
+
var columnRight = Math.round(cr.right) - clearance;
|
|
201
|
+
width = Math.max(184, Math.min(desiredWidth, right - columnRight - 10));
|
|
202
|
+
}
|
|
203
|
+
} catch (error) { /* keep the window-corner fallback */ }
|
|
204
|
+
width = Math.max(160, Math.min(width, right - 12));
|
|
205
|
+
return { left: Math.max(8, Math.round(right - width)), top: Math.round(top), width: Math.round(width) };
|
|
206
|
+
}
|
|
207
|
+
|
|
169
208
|
function StatusDock(props) {
|
|
170
209
|
ensureStatusStyles();
|
|
171
210
|
var connection = props.connection;
|
|
@@ -344,7 +383,7 @@ window.__ModuleLoader__.load({
|
|
|
344
383
|
h("span", { style: {
|
|
345
384
|
display: "inline-flex", alignItems: "center", gap: 7, height: 26, padding: "0 11px",
|
|
346
385
|
borderRadius: 999, border: "1px solid " + STATUS_ACCENT,
|
|
347
|
-
background:
|
|
386
|
+
background: STATUS_SURFACE, color: STATUS_TEXT, fontSize: 11, whiteSpace: "nowrap",
|
|
348
387
|
} },
|
|
349
388
|
h("span", { className: "dsh-handoff-pulse", style: { width: 7, height: 7, borderRadius: "50%", background: STATUS_ACCENT } }),
|
|
350
389
|
"压缩中 · " + statusStep(status) + " · " + clockText(startedAt)
|
|
@@ -352,6 +391,43 @@ window.__ModuleLoader__.load({
|
|
|
352
391
|
);
|
|
353
392
|
return ReactDOM.createPortal(h(React.Fragment, null, topLine, pill), document.body);
|
|
354
393
|
}
|
|
394
|
+
if (themeStyle === "corner") {
|
|
395
|
+
var place = cornerPlacement(268);
|
|
396
|
+
return ReactDOM.createPortal(
|
|
397
|
+
h("div", {
|
|
398
|
+
role: "status",
|
|
399
|
+
"aria-live": "polite",
|
|
400
|
+
"data-dsh-handoff-status": "running",
|
|
401
|
+
title: "压缩期间发送的消息会排队",
|
|
402
|
+
style: {
|
|
403
|
+
position: "fixed",
|
|
404
|
+
left: place.left,
|
|
405
|
+
top: place.top,
|
|
406
|
+
zIndex: 70,
|
|
407
|
+
pointerEvents: "none",
|
|
408
|
+
boxSizing: "border-box",
|
|
409
|
+
width: place.width,
|
|
410
|
+
display: "flex",
|
|
411
|
+
alignItems: "flex-start",
|
|
412
|
+
gap: 8,
|
|
413
|
+
padding: "9px 11px",
|
|
414
|
+
borderRadius: 10,
|
|
415
|
+
background: STATUS_SURFACE,
|
|
416
|
+
border: "1px solid " + STATUS_BORDER,
|
|
417
|
+
borderLeft: "2px solid " + STATUS_ACCENT,
|
|
418
|
+
boxShadow: "0 14px 34px rgba(0,0,0,.38)",
|
|
419
|
+
},
|
|
420
|
+
},
|
|
421
|
+
h("span", { className: "dsh-handoff-pulse", style: { width: 8, height: 8, borderRadius: "50%", background: STATUS_ACCENT, flex: "0 0 auto", marginTop: 5 } }),
|
|
422
|
+
h("div", { style: { minWidth: 0, flex: 1 } },
|
|
423
|
+
h("div", { style: { fontSize: 12, fontWeight: 600, color: STATUS_TEXT, lineHeight: "17px", whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" } }, title),
|
|
424
|
+
h("div", { style: { marginTop: 2, fontSize: 10.5, color: STATUS_MUTED, whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" } },
|
|
425
|
+
clockText(startedAt) + " · " + statusStep(status) + (place.width >= 216 ? " · 消息会排队" : ""))
|
|
426
|
+
)
|
|
427
|
+
),
|
|
428
|
+
document.body
|
|
429
|
+
);
|
|
430
|
+
}
|
|
355
431
|
var indicator = themeStyle === "ring"
|
|
356
432
|
? h("div", { style: { position: "relative", width: 64, height: 64 } },
|
|
357
433
|
h("div", { style: {
|
|
@@ -388,13 +464,13 @@ window.__ModuleLoader__.load({
|
|
|
388
464
|
// scrim here would falsely suggest the conversation is frozen.
|
|
389
465
|
};
|
|
390
466
|
var cardStyle = {
|
|
391
|
-
width:
|
|
467
|
+
width: 264,
|
|
392
468
|
maxWidth: "82%",
|
|
393
|
-
background:
|
|
469
|
+
background: STATUS_SURFACE,
|
|
394
470
|
border: "1px solid " + STATUS_BORDER,
|
|
395
471
|
borderRadius: 14,
|
|
396
472
|
padding: "16px 16px 14px",
|
|
397
|
-
boxShadow: "0
|
|
473
|
+
boxShadow: "0 20px 46px rgba(0,0,0,.45), 0 2px 8px rgba(0,0,0,.3)",
|
|
398
474
|
display: "flex",
|
|
399
475
|
flexDirection: "column",
|
|
400
476
|
alignItems: "center",
|
|
@@ -406,9 +482,9 @@ window.__ModuleLoader__.load({
|
|
|
406
482
|
h("div", { role: "status", "aria-live": "polite", "data-dsh-handoff-status": "running", style: overlayStyle },
|
|
407
483
|
h("div", { style: cardStyle },
|
|
408
484
|
indicator,
|
|
409
|
-
h("div", { style: { fontSize: 12.5, fontWeight:
|
|
485
|
+
h("div", { style: { fontSize: 12.5, fontWeight: 600, color: STATUS_TEXT } }, title),
|
|
410
486
|
h("div", { style: { fontSize: 11, color: STATUS_MUTED } }, cardSub),
|
|
411
|
-
h("div", { style: { fontSize: 10.5, color:
|
|
487
|
+
h("div", { style: { fontSize: 10.5, color: STATUS_MUTED, opacity: 0.85 } }, "完成后把最旧的这段历史收成一条交接文档")
|
|
412
488
|
)
|
|
413
489
|
),
|
|
414
490
|
document.body
|
|
@@ -429,6 +505,7 @@ window.__ModuleLoader__.load({
|
|
|
429
505
|
borderLeft: "3px solid " + color,
|
|
430
506
|
borderRadius: 8,
|
|
431
507
|
background: STATUS_PANEL,
|
|
508
|
+
boxShadow: "0 8px 22px rgba(0,0,0,.28)",
|
|
432
509
|
color: STATUS_TEXT,
|
|
433
510
|
fontSize: 12,
|
|
434
511
|
lineHeight: "18px",
|
|
@@ -486,7 +563,7 @@ window.__ModuleLoader__.load({
|
|
|
486
563
|
function ThemePreview(props) {
|
|
487
564
|
var style = normalizeDisplayStyle(props.theme);
|
|
488
565
|
var chatChildren = [
|
|
489
|
-
h("div", { key: "user", style: { marginLeft: "auto", maxWidth: "
|
|
566
|
+
h("div", { key: "user", style: { marginLeft: "auto", maxWidth: "62%", border: "1px solid rgba(76,141,255,.24)", background: "rgba(76,141,255,.14)", borderRadius: "7px 7px 2px 7px", padding: "4px 6px", fontSize: 9, color: "#d9dbde" } }, "请完整读取这个文件并总结…"),
|
|
490
567
|
h("div", { key: "lines", style: { display: "flex", flexDirection: "column", gap: 4, marginTop: 6 } },
|
|
491
568
|
h("i", { key: "l1", style: { height: 4, borderRadius: 99, background: C.line } }),
|
|
492
569
|
h("i", { key: "l2", style: { height: 4, borderRadius: 99, background: C.line, width: "88%" } }),
|
|
@@ -525,6 +602,14 @@ window.__ModuleLoader__.load({
|
|
|
525
602
|
)
|
|
526
603
|
)
|
|
527
604
|
);
|
|
605
|
+
} else if (style === "corner") {
|
|
606
|
+
overlay = h("div", { style: { position: "absolute", right: 8, top: 9, boxSizing: "border-box", width: 104, display: "flex", alignItems: "flex-start", gap: 4, padding: "4px 5px", borderRadius: 5, background: "#23252a", border: "1px solid " + C.line, borderLeft: "2px solid " + C.accent } },
|
|
607
|
+
h("span", { className: "dsh-handoff-pulse", style: { width: 4, height: 4, borderRadius: "50%", background: C.accent, marginTop: 4, flex: "0 0 auto" } }),
|
|
608
|
+
h("div", { style: { minWidth: 0 } },
|
|
609
|
+
h("div", { style: { fontSize: 7, fontWeight: 600, whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" } }, "正在压缩历史会话"),
|
|
610
|
+
h("div", { style: { fontSize: 6.5, color: C.muted, whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" } }, "00:42 · 2/3 写文档")
|
|
611
|
+
)
|
|
612
|
+
);
|
|
528
613
|
}
|
|
529
614
|
var strip = style === "strip"
|
|
530
615
|
? h("div", { style: { margin: "0 8px 4px", border: "1px solid " + C.line, borderLeft: "2px solid " + C.accent, borderRadius: 5, background: "#212226", padding: "4px 6px" } },
|
|
@@ -547,7 +632,7 @@ window.__ModuleLoader__.load({
|
|
|
547
632
|
h("span", { style: { marginLeft: "auto", fontSize: 8.5, color: C.muted, border: "1px solid " + C.line, borderRadius: 999, padding: "0 6px" } }, "轻量本地")
|
|
548
633
|
),
|
|
549
634
|
h("div", { style: { flex: 1, minHeight: 0, padding: (style === "topbar" ? 22 : 7) + "px 8px 7px", position: "relative", overflow: "hidden" } },
|
|
550
|
-
style === "topbar" ? overlay : null,
|
|
635
|
+
(style === "topbar" || style === "corner") ? overlay : null,
|
|
551
636
|
chatChildren,
|
|
552
637
|
(style === "ring" || style === "orbit") ? overlay : null
|
|
553
638
|
),
|
|
@@ -606,8 +691,29 @@ window.__ModuleLoader__.load({
|
|
|
606
691
|
var draft = draftState[0], setDraft = draftState[1];
|
|
607
692
|
var statusState = React.useState({ loading: true, error: "", saving: false, saved: false });
|
|
608
693
|
var status = statusState[0], setStatus = statusState[1];
|
|
694
|
+
var scopeState = React.useState({ loading: true, error: "", value: null });
|
|
695
|
+
var scope = scopeState[0], setScope = scopeState[1];
|
|
696
|
+
var tokenState = React.useState(0);
|
|
697
|
+
var token = tokenState[0], setToken = tokenState[1];
|
|
609
698
|
ensureStatusStyles();
|
|
610
699
|
|
|
700
|
+
// The compaction slot lives in the preset YAML, not in our settings file,
|
|
701
|
+
// so the truth is read from disk each time the section opens.
|
|
702
|
+
React.useEffect(function () {
|
|
703
|
+
if (!connection) {
|
|
704
|
+
setScope({ loading: false, error: "连接服务不可用", value: null });
|
|
705
|
+
return undefined;
|
|
706
|
+
}
|
|
707
|
+
var alive = true;
|
|
708
|
+
setScope(function (previous) { return { loading: true, error: "", value: previous.value }; });
|
|
709
|
+
call(connection, "scope").then(function (value) {
|
|
710
|
+
if (alive) setScope({ loading: false, error: "", value: value });
|
|
711
|
+
}).catch(function (error) {
|
|
712
|
+
if (alive) setScope({ loading: false, error: String(error && error.message || error), value: null });
|
|
713
|
+
});
|
|
714
|
+
return function () { alive = false; };
|
|
715
|
+
}, [connection, token]);
|
|
716
|
+
|
|
611
717
|
React.useEffect(function () {
|
|
612
718
|
var alive = true;
|
|
613
719
|
if (!connection) { setStatus({ loading: false, error: "连接服务不可用", saving: false, saved: false }); return; }
|
|
@@ -634,6 +740,80 @@ window.__ModuleLoader__.load({
|
|
|
634
740
|
var defaultTemplate = base.defaultTemplate || DEFAULT_TEMPLATE;
|
|
635
741
|
var themeStyle = normalizeDisplayStyle(base.displayStyle);
|
|
636
742
|
|
|
743
|
+
// Detection only: who compacts is decided by the preset slot (read from
|
|
744
|
+
// disk) times the 启用插件 switch. This row never changes either of them.
|
|
745
|
+
var SCOPE_TONE = {
|
|
746
|
+
native: { dot: "#6b7178", text: C.muted },
|
|
747
|
+
plugin: { dot: C.accent, text: "#cfe0ff" },
|
|
748
|
+
part: { dot: "#d29922", text: "#e6c98a" },
|
|
749
|
+
err: { dot: "#e5534b", text: "#f0b8b4" },
|
|
750
|
+
};
|
|
751
|
+
var scopeView = (function () {
|
|
752
|
+
var st = scope.value;
|
|
753
|
+
if (!st) {
|
|
754
|
+
return {
|
|
755
|
+
tone: "err",
|
|
756
|
+
text: scope.loading ? "读取中…" : "读不到生效情况",
|
|
757
|
+
sub: scope.loading
|
|
758
|
+
? "正在读取各 Agent 预设里的 compaction 槽位。"
|
|
759
|
+
: "读不到预设里的 compaction 槽位:" + (scope.error || "未知原因") + "。",
|
|
760
|
+
rows: [], retry: true,
|
|
761
|
+
};
|
|
762
|
+
}
|
|
763
|
+
var list = Array.isArray(st.presets) ? st.presets : [];
|
|
764
|
+
var rows = list.map(function (p) {
|
|
765
|
+
return {
|
|
766
|
+
key: String(p.id), label: presetLabel(p.id), taken: p.owner === "plugin",
|
|
767
|
+
owner: p.owner === "plugin" ? "插件" : p.owner === "native" ? "原生" : "未知",
|
|
768
|
+
};
|
|
769
|
+
});
|
|
770
|
+
var names = rows.map(function (r) { return r.label; });
|
|
771
|
+
var takenNames = rows.filter(function (r) { return r.taken; }).map(function (r) { return r.label; });
|
|
772
|
+
var restNames = rows.filter(function (r) { return !r.taken; }).map(function (r) { return r.label; });
|
|
773
|
+
var taken = Number(st.taken) || 0;
|
|
774
|
+
var total = Number(st.total) || list.length;
|
|
775
|
+
var globalSaved = st.scope !== "preset";
|
|
776
|
+
var scopeLead = globalSaved
|
|
777
|
+
? "全局生效:"
|
|
778
|
+
: "按预设生效:";
|
|
779
|
+
if (total === 0) {
|
|
780
|
+
return {
|
|
781
|
+
tone: "err", text: "读不到预设", rows: [], retry: true,
|
|
782
|
+
sub: "没找到带 handoff 槽位的可编辑预设文件,所以无法判断压缩由谁负责。确认 DSH 已生成预设后重新读取。",
|
|
783
|
+
};
|
|
784
|
+
}
|
|
785
|
+
if (taken === 0) {
|
|
786
|
+
return {
|
|
787
|
+
tone: "native", text: "DSH 原生压缩", rows: rows, retry: false,
|
|
788
|
+
sub: "各预设的 compaction 槽位都还在原生实现手里,所以压缩目前由 DSH 自己完成。保存本页设置会自动接管 " + names.join("、") + "(写入预设文件并留备份,重启 DSH 后生效)。",
|
|
789
|
+
};
|
|
790
|
+
}
|
|
791
|
+
if (!enabled) {
|
|
792
|
+
return {
|
|
793
|
+
tone: "native", text: "DSH 原生压缩(插件已停用)", rows: rows, retry: false,
|
|
794
|
+
sub: taken < total
|
|
795
|
+
? "「启用插件」已关闭,压缩由 DSH 原生实现负责;" + takenNames.join("、") + " 的槽位仍指向插件," + restNames.join("、") + " 本来就走原生。"
|
|
796
|
+
: "「启用插件」已关闭:槽位虽然指向插件,但插件把压缩交回 DSH 原生实现。打开开关即恢复,不需要重启。",
|
|
797
|
+
};
|
|
798
|
+
}
|
|
799
|
+
if (taken < total) {
|
|
800
|
+
return {
|
|
801
|
+
tone: "part", text: taken + "/" + total + " 预设已接管", rows: rows, retry: false,
|
|
802
|
+
sub: scopeLead + "已交给插件:" + takenNames.join("、") + ";" + restNames.join("、") + " 仍走各自的原生压缩。",
|
|
803
|
+
};
|
|
804
|
+
}
|
|
805
|
+
return {
|
|
806
|
+
tone: "plugin",
|
|
807
|
+
text: globalSaved ? "已由插件接管(全局)" : "已由插件接管",
|
|
808
|
+
rows: rows, retry: false,
|
|
809
|
+
sub: scopeLead + (globalSaved
|
|
810
|
+
? "这 " + total + " 个带 handoff 槽位的可编辑预设(" + names.join("、") + ")"
|
|
811
|
+
: "勾选的 " + taken + " 个预设(" + takenNames.join("、") + ")")
|
|
812
|
+
+ "的 compaction 槽位指向插件,且「启用插件」是开的,压缩由本插件的交接文档负责。",
|
|
813
|
+
};
|
|
814
|
+
})();
|
|
815
|
+
var scopeTone = SCOPE_TONE[scopeView.tone] || SCOPE_TONE.native;
|
|
816
|
+
|
|
637
817
|
var budget = EXAMPLE_WINDOW - EXAMPLE_RESERVED;
|
|
638
818
|
var ceiling = budget - EXAMPLE_HEADROOM;
|
|
639
819
|
var eff = Math.min(Math.round(budget * ratio / 100), ceiling);
|
|
@@ -645,6 +825,20 @@ window.__ModuleLoader__.load({
|
|
|
645
825
|
(settings.templateFile || "") !== template ||
|
|
646
826
|
normalizeDisplayStyle(settings.displayStyle) !== themeStyle;
|
|
647
827
|
|
|
828
|
+
// The detection row reports what is on disk, so every unsaved choice that
|
|
829
|
+
// would change that report has to be named — otherwise it looks ignored.
|
|
830
|
+
var enabledIsPending = settings !== null && (settings.enabled !== false) !== enabled;
|
|
831
|
+
var scopeIsPending = settings !== null && (settings.scope === "preset") !== scopePreset;
|
|
832
|
+
var presetsIsPending = settings !== null && scopePreset &&
|
|
833
|
+
JSON.stringify(presets.slice().sort()) !== JSON.stringify((settings.presets || []).slice().sort());
|
|
834
|
+
var pendingParts = [];
|
|
835
|
+
if (enabledIsPending) pendingParts.push("「启用插件」改成" + (enabled ? "开" : "关"));
|
|
836
|
+
if (scopeIsPending) pendingParts.push("生效范围改成「" + (scopePreset ? "按预设生效" : "全局生效") + "」");
|
|
837
|
+
if (presetsIsPending) pendingParts.push("勾选的预设改成" + (presets.length ? presets.map(presetLabel).join("、") : "(都不勾)"));
|
|
838
|
+
var pendingNote = pendingParts.length
|
|
839
|
+
? "还没保存:" + pendingParts.join(";") + "。保存后开关立刻生效,槽位改动要重启 DSH 才加载。"
|
|
840
|
+
: "";
|
|
841
|
+
|
|
648
842
|
function patch(next) { setDraft(Object.assign({}, base, next)); setStatus({ loading: false, error: "", saving: false, saved: false }); }
|
|
649
843
|
function setPreset(id) {
|
|
650
844
|
var next = presets.indexOf(id) >= 0 ? presets.filter(function (v) { return v !== id; }) : presets.concat([id]);
|
|
@@ -663,10 +857,12 @@ window.__ModuleLoader__.load({
|
|
|
663
857
|
setSettings(value); setDraft(value);
|
|
664
858
|
publishDisplayStyle(value && value.displayStyle);
|
|
665
859
|
setStatus({ loading: false, error: "", saving: false, saved: true });
|
|
860
|
+
setToken(token + 1);
|
|
666
861
|
}).catch(function (error) {
|
|
667
862
|
setStatus({ loading: false, error: String(error && error.message || error), saving: false, saved: false });
|
|
668
863
|
});
|
|
669
864
|
}
|
|
865
|
+
|
|
670
866
|
function discard() { setDraft(settings); setStatus({ loading: false, error: "", saving: false, saved: false }); }
|
|
671
867
|
function open(kind) {
|
|
672
868
|
call(connection, "open", { kind: kind, path: template }).then(function () {
|
|
@@ -727,6 +923,38 @@ window.__ModuleLoader__.load({
|
|
|
727
923
|
})
|
|
728
924
|
) : null,
|
|
729
925
|
|
|
926
|
+
h("div", { style: rowStyle },
|
|
927
|
+
h("div", { style: { minWidth: 0 } },
|
|
928
|
+
h("div", { style: labelStyle }, "当前生效情况"),
|
|
929
|
+
h("div", { style: subStyle }, scopeView.sub),
|
|
930
|
+
scopeView.rows.length
|
|
931
|
+
? h("div", { style: { display: "flex", gap: "6px 16px", flexWrap: "wrap", marginTop: 7 } },
|
|
932
|
+
scopeView.rows.map(function (r) {
|
|
933
|
+
return h("span", { key: r.key, style: { display: "inline-flex", alignItems: "center", gap: 6, fontSize: 12, color: C.muted } },
|
|
934
|
+
h("i", { "aria-hidden": true, style: { width: 6, height: 6, borderRadius: "50%", background: r.taken ? C.accent : "#6b7178" } }),
|
|
935
|
+
r.label,
|
|
936
|
+
h("em", { style: { fontStyle: "normal", color: C.faint } }, r.owner));
|
|
937
|
+
})
|
|
938
|
+
)
|
|
939
|
+
: null,
|
|
940
|
+
pendingNote ? h("div", { style: { fontSize: 11.5, color: "#d29922", marginTop: 6 } }, pendingNote) : null,
|
|
941
|
+
scope.error && scope.value ? h("div", { style: { fontSize: 11.5, color: "#e5534b", marginTop: 6 } }, "读取失败:" + scope.error) : null
|
|
942
|
+
),
|
|
943
|
+
h("div", { style: { display: "inline-flex", alignItems: "center", gap: 12, flex: "0 0 auto" } },
|
|
944
|
+
h("span", { "aria-live": "polite", style: { display: "inline-flex", alignItems: "center", gap: 7, fontSize: 12.5, color: scopeTone.text } },
|
|
945
|
+
h("i", { "aria-hidden": true, style: { width: 7, height: 7, borderRadius: "50%", background: scopeTone.dot } }),
|
|
946
|
+
scopeView.text
|
|
947
|
+
),
|
|
948
|
+
scopeView.retry && !scope.loading
|
|
949
|
+
? h("button", {
|
|
950
|
+
type: "button",
|
|
951
|
+
onClick: function () { setToken(token + 1); },
|
|
952
|
+
style: Object.assign({}, btnBase, { fontSize: 11.5, padding: "4px 9px" }),
|
|
953
|
+
}, "重新读取")
|
|
954
|
+
: null
|
|
955
|
+
)
|
|
956
|
+
),
|
|
957
|
+
|
|
730
958
|
h("div", { style: { padding: "12px 0", borderBottom: "1px solid " + C.line } },
|
|
731
959
|
h("div", null,
|
|
732
960
|
h("div", { style: labelStyle }, "压缩显示方式", h("span", { style: newBadgeStyle }, "新")),
|
|
@@ -800,8 +1028,8 @@ window.__ModuleLoader__.load({
|
|
|
800
1028
|
h("ol", { style: { margin: 0, paddingLeft: 18, color: C.muted, fontSize: 12.5, lineHeight: 1.85 } },
|
|
801
1029
|
h("li", null, "到点后不立即动手,等 agent 结束当前回合。"),
|
|
802
1030
|
h("li", null, "用会话自己的前缀(命中缓存)让模型按模板写交接文档,不额外全量 prefill。"),
|
|
803
|
-
h("li", null, "
|
|
804
|
-
h("li", null, "
|
|
1031
|
+
h("li", null, "只替换最旧的这一段历史,换成一条交接文档;最近约 16% 窗口的原文逐字留着,切点也不会把工具调用和它的结果拆开。"),
|
|
1032
|
+
h("li", null, "下一轮 = 系统提示词 + 工具定义 + 这条交接文档 + 那段逐字保留的最近消息;旧消息仍在会话日志里,rewind 可回滚。")
|
|
805
1033
|
),
|
|
806
1034
|
h("div", { style: { marginTop: 8, fontSize: 11.5, color: C.faint } }, "手动压缩:会话里发送 /compact 可随时折叠一次,不受这个比例限制。")
|
|
807
1035
|
),
|
package/lib/index.js
CHANGED
|
@@ -60,7 +60,7 @@ function trace(message) {
|
|
|
60
60
|
|
|
61
61
|
let lastOpenRequest = "";
|
|
62
62
|
|
|
63
|
-
const DISPLAY_STYLES = new Set(["orbit", "strip", "ring", "topbar"]);
|
|
63
|
+
const DISPLAY_STYLES = new Set(["orbit", "strip", "ring", "topbar", "corner"]);
|
|
64
64
|
|
|
65
65
|
function normalizeDisplayStyle(value) {
|
|
66
66
|
return typeof value === "string" && DISPLAY_STYLES.has(value) ? value : "orbit";
|
|
@@ -143,9 +143,9 @@ function scopeScriptPath() {
|
|
|
143
143
|
}
|
|
144
144
|
}
|
|
145
145
|
|
|
146
|
-
function execSwitchOnce(command, env, script) {
|
|
146
|
+
function execSwitchOnce(command, env, script, action) {
|
|
147
147
|
return new Promise((resolve, reject) => {
|
|
148
|
-
execFile(command, [script,
|
|
148
|
+
execFile(command, [script, action], { timeout: 6000, windowsHide: true, env }, (error, stdout, stderr) => {
|
|
149
149
|
if (error) {
|
|
150
150
|
error.stdout = stdout;
|
|
151
151
|
error.stderr = stderr;
|
|
@@ -158,15 +158,14 @@ function execSwitchOnce(command, env, script) {
|
|
|
158
158
|
}
|
|
159
159
|
|
|
160
160
|
/**
|
|
161
|
-
*
|
|
162
|
-
*
|
|
163
|
-
* when the host has no node on PATH.
|
|
161
|
+
* Run one preset-scope action. `node` is the normal path; the Electron binary
|
|
162
|
+
* with ELECTRON_RUN_AS_NODE is the fallback when the host has no node on PATH.
|
|
164
163
|
*/
|
|
165
|
-
async function
|
|
164
|
+
async function runScope(action) {
|
|
166
165
|
const script = scopeScriptPath();
|
|
167
166
|
if (!existsSync(script)) {
|
|
168
167
|
const message = "preset-scope.mjs not found: " + script;
|
|
169
|
-
trace("scope
|
|
168
|
+
trace("scope " + action + " failed: " + message);
|
|
170
169
|
return { ok: false, message };
|
|
171
170
|
}
|
|
172
171
|
const explicit = process.env.DSH_NODE;
|
|
@@ -179,10 +178,11 @@ async function applyScopeSwitch() {
|
|
|
179
178
|
let lastError;
|
|
180
179
|
for (const attempt of attempts) {
|
|
181
180
|
try {
|
|
182
|
-
const result = await execSwitchOnce(attempt.command, attempt.env, script);
|
|
183
|
-
const output = `${result.stdout ?? ""}
|
|
184
|
-
|
|
185
|
-
|
|
181
|
+
const result = await execSwitchOnce(attempt.command, attempt.env, script, action);
|
|
182
|
+
const output = `${result.stdout ?? ""}`.trim();
|
|
183
|
+
const noise = `${result.stderr ?? ""}`.trim();
|
|
184
|
+
trace("scope " + action + " ok" + (output ? " | " + output : "") + (noise ? " | " + noise : ""));
|
|
185
|
+
return { ok: true, output, noise };
|
|
186
186
|
} catch (error) {
|
|
187
187
|
lastError = error;
|
|
188
188
|
if (error?.code !== "ENOENT") break;
|
|
@@ -190,10 +190,34 @@ async function applyScopeSwitch() {
|
|
|
190
190
|
}
|
|
191
191
|
const output = `${lastError?.stdout ?? ""}${lastError?.stderr ?? ""}`.trim();
|
|
192
192
|
const message = (lastError?.message ?? String(lastError)) + (output ? "\n" + output : "");
|
|
193
|
-
trace("scope
|
|
193
|
+
trace("scope " + action + " failed: " + message);
|
|
194
194
|
return { ok: false, message };
|
|
195
195
|
}
|
|
196
196
|
|
|
197
|
+
/** Who actually compacts right now, straight from the preset YAML on disk. */
|
|
198
|
+
async function readScopeState() {
|
|
199
|
+
const result = await runScope("state");
|
|
200
|
+
if (!result.ok) return { ok: false, message: result.message };
|
|
201
|
+
const line = String(result.output ?? "").split(/\r?\n/).filter(Boolean).pop() ?? "";
|
|
202
|
+
try {
|
|
203
|
+
return { ok: true, value: JSON.parse(line) };
|
|
204
|
+
} catch (error) {
|
|
205
|
+
const message = "unreadable scope state: " + (error instanceof Error ? error.message : String(error));
|
|
206
|
+
trace("scope state failed: " + message + " | " + line.slice(0, 200));
|
|
207
|
+
return { ok: false, message };
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Apply the current settings sidecar to the preset/profile YAML.
|
|
213
|
+
*/
|
|
214
|
+
async function applyScopeSwitch() {
|
|
215
|
+
const result = await runScope("enable");
|
|
216
|
+
return result.ok
|
|
217
|
+
? { ok: true, output: result.output }
|
|
218
|
+
: { ok: false, message: result.message };
|
|
219
|
+
}
|
|
220
|
+
|
|
197
221
|
const CHANNEL = "/dsh-auto-compaction";
|
|
198
222
|
|
|
199
223
|
function readSettings() {
|
|
@@ -265,6 +289,12 @@ export function apply(ctx, config = {}) {
|
|
|
265
289
|
}
|
|
266
290
|
return { ok: true, value: { ...next, switchApplied: true, defaultTemplate: TEMPLATE_FILE } };
|
|
267
291
|
}
|
|
292
|
+
if (endpoint === "scope") {
|
|
293
|
+
const state = await readScopeState();
|
|
294
|
+
return state.ok
|
|
295
|
+
? { ok: true, value: state.value }
|
|
296
|
+
: { ok: false, error: { code: "auto-compaction/scope-unknown", message: state.message } };
|
|
297
|
+
}
|
|
268
298
|
if (endpoint === "open") {
|
|
269
299
|
trace("open " + JSON.stringify(payload));
|
|
270
300
|
return await handleOpen(ctx, String(payload?.kind ?? "file") + ":" + Date.now(), String(payload?.path ?? "") || TEMPLATE_FILE);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-auto-compaction",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"exports": {
|
|
@@ -51,6 +51,6 @@
|
|
|
51
51
|
"access": "public"
|
|
52
52
|
},
|
|
53
53
|
"dependencies": {
|
|
54
|
-
"dsh-auto-compaction-engine": "^0.2.
|
|
54
|
+
"dsh-auto-compaction-engine": "^0.2.2"
|
|
55
55
|
}
|
|
56
56
|
}
|