dsh-auto-compaction 0.2.1 → 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 +150 -3
- package/lib/index.js +42 -12
- package/package.json +2 -2
package/lib/client.js
CHANGED
|
@@ -43,6 +43,10 @@ window.__ModuleLoader__.load({
|
|
|
43
43
|
{ id: "topbar", label: "顶部条" },
|
|
44
44
|
{ id: "corner", label: "右上角" },
|
|
45
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
|
+
}
|
|
46
50
|
var EXAMPLE_WINDOW = 250000, EXAMPLE_RESERVED = 20000, EXAMPLE_HEADROOM = 10000;
|
|
47
51
|
|
|
48
52
|
var DISPLAY_STYLES = ["orbit", "strip", "ring", "topbar", "corner"];
|
|
@@ -480,7 +484,7 @@ window.__ModuleLoader__.load({
|
|
|
480
484
|
indicator,
|
|
481
485
|
h("div", { style: { fontSize: 12.5, fontWeight: 600, color: STATUS_TEXT } }, title),
|
|
482
486
|
h("div", { style: { fontSize: 11, color: STATUS_MUTED } }, cardSub),
|
|
483
|
-
h("div", { style: { fontSize: 10.5, color: STATUS_MUTED, opacity: 0.85 } }, "
|
|
487
|
+
h("div", { style: { fontSize: 10.5, color: STATUS_MUTED, opacity: 0.85 } }, "完成后把最旧的这段历史收成一条交接文档")
|
|
484
488
|
)
|
|
485
489
|
),
|
|
486
490
|
document.body
|
|
@@ -687,8 +691,29 @@ window.__ModuleLoader__.load({
|
|
|
687
691
|
var draft = draftState[0], setDraft = draftState[1];
|
|
688
692
|
var statusState = React.useState({ loading: true, error: "", saving: false, saved: false });
|
|
689
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];
|
|
690
698
|
ensureStatusStyles();
|
|
691
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
|
+
|
|
692
717
|
React.useEffect(function () {
|
|
693
718
|
var alive = true;
|
|
694
719
|
if (!connection) { setStatus({ loading: false, error: "连接服务不可用", saving: false, saved: false }); return; }
|
|
@@ -715,6 +740,80 @@ window.__ModuleLoader__.load({
|
|
|
715
740
|
var defaultTemplate = base.defaultTemplate || DEFAULT_TEMPLATE;
|
|
716
741
|
var themeStyle = normalizeDisplayStyle(base.displayStyle);
|
|
717
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
|
+
|
|
718
817
|
var budget = EXAMPLE_WINDOW - EXAMPLE_RESERVED;
|
|
719
818
|
var ceiling = budget - EXAMPLE_HEADROOM;
|
|
720
819
|
var eff = Math.min(Math.round(budget * ratio / 100), ceiling);
|
|
@@ -726,6 +825,20 @@ window.__ModuleLoader__.load({
|
|
|
726
825
|
(settings.templateFile || "") !== template ||
|
|
727
826
|
normalizeDisplayStyle(settings.displayStyle) !== themeStyle;
|
|
728
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
|
+
|
|
729
842
|
function patch(next) { setDraft(Object.assign({}, base, next)); setStatus({ loading: false, error: "", saving: false, saved: false }); }
|
|
730
843
|
function setPreset(id) {
|
|
731
844
|
var next = presets.indexOf(id) >= 0 ? presets.filter(function (v) { return v !== id; }) : presets.concat([id]);
|
|
@@ -744,10 +857,12 @@ window.__ModuleLoader__.load({
|
|
|
744
857
|
setSettings(value); setDraft(value);
|
|
745
858
|
publishDisplayStyle(value && value.displayStyle);
|
|
746
859
|
setStatus({ loading: false, error: "", saving: false, saved: true });
|
|
860
|
+
setToken(token + 1);
|
|
747
861
|
}).catch(function (error) {
|
|
748
862
|
setStatus({ loading: false, error: String(error && error.message || error), saving: false, saved: false });
|
|
749
863
|
});
|
|
750
864
|
}
|
|
865
|
+
|
|
751
866
|
function discard() { setDraft(settings); setStatus({ loading: false, error: "", saving: false, saved: false }); }
|
|
752
867
|
function open(kind) {
|
|
753
868
|
call(connection, "open", { kind: kind, path: template }).then(function () {
|
|
@@ -808,6 +923,38 @@ window.__ModuleLoader__.load({
|
|
|
808
923
|
})
|
|
809
924
|
) : null,
|
|
810
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
|
+
|
|
811
958
|
h("div", { style: { padding: "12px 0", borderBottom: "1px solid " + C.line } },
|
|
812
959
|
h("div", null,
|
|
813
960
|
h("div", { style: labelStyle }, "压缩显示方式", h("span", { style: newBadgeStyle }, "新")),
|
|
@@ -881,8 +1028,8 @@ window.__ModuleLoader__.load({
|
|
|
881
1028
|
h("ol", { style: { margin: 0, paddingLeft: 18, color: C.muted, fontSize: 12.5, lineHeight: 1.85 } },
|
|
882
1029
|
h("li", null, "到点后不立即动手,等 agent 结束当前回合。"),
|
|
883
1030
|
h("li", null, "用会话自己的前缀(命中缓存)让模型按模板写交接文档,不额外全量 prefill。"),
|
|
884
|
-
h("li", null, "
|
|
885
|
-
h("li", null, "
|
|
1031
|
+
h("li", null, "只替换最旧的这一段历史,换成一条交接文档;最近约 16% 窗口的原文逐字留着,切点也不会把工具调用和它的结果拆开。"),
|
|
1032
|
+
h("li", null, "下一轮 = 系统提示词 + 工具定义 + 这条交接文档 + 那段逐字保留的最近消息;旧消息仍在会话日志里,rewind 可回滚。")
|
|
886
1033
|
),
|
|
887
1034
|
h("div", { style: { marginTop: 8, fontSize: 11.5, color: C.faint } }, "手动压缩:会话里发送 /compact 可随时折叠一次,不受这个比例限制。")
|
|
888
1035
|
),
|
package/lib/index.js
CHANGED
|
@@ -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
|
}
|