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.
Files changed (3) hide show
  1. package/lib/client.js +240 -12
  2. package/lib/index.js +43 -13
  3. 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
- var STATUS_PANEL = "var(--dsw-alias-interactive-bg-hover, rgba(127,133,140,.10))";
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: "var(--dsw-specific-menu, #23252a)", color: STATUS_TEXT, fontSize: 11, whiteSpace: "nowrap",
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: 260,
467
+ width: 264,
392
468
  maxWidth: "82%",
393
- background: "var(--dsw-specific-menu, #23252a)",
469
+ background: STATUS_SURFACE,
394
470
  border: "1px solid " + STATUS_BORDER,
395
471
  borderRadius: 14,
396
472
  padding: "16px 16px 14px",
397
- boxShadow: "0 22px 50px rgba(0,0,0,.34)",
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: 500, color: STATUS_TEXT } }, title),
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: STATUS_FAINT } }, "完成后整段历史收成一条交接文档")
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: "78%", 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" } }, "请完整读取这个文件并总结…"),
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, "把整段历史替换成这一条消息;旧消息仍在会话日志里,rewind 可回滚。"),
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, "enable"], { timeout: 6000, windowsHide: true, env }, (error, stdout, stderr) => {
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
- * Apply the current settings sidecar to the preset/profile YAML. `node` is the
162
- * normal path; the Electron binary with ELECTRON_RUN_AS_NODE is the fallback
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 applyScopeSwitch() {
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 switch failed: " + message);
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 ?? ""}${result.stderr ?? ""}`.trim();
184
- trace("scope switch ok" + (output ? " | " + output : ""));
185
- return { ok: true, output };
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 switch failed: " + message);
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.0",
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.0"
54
+ "dsh-auto-compaction-engine": "^0.2.2"
55
55
  }
56
56
  }