dsh-auto-compaction-engine 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/index.js CHANGED
@@ -6,9 +6,11 @@
6
6
  * At a configurable share of the model window (default 90%) this backend waits
7
7
  * for the agent to go idle, then asks the routed model — replaying the current
8
8
  * conversation as the prompt prefix so the provider's cache is reused — for a
9
- * handoff checkpoint, and replaces the whole compactable surface with that one
10
- * message. The next user turn therefore starts from system prompt + tools +
11
- * handoff text, and nothing else.
9
+ * handoff checkpoint, and replaces the oldest compactable span with that one
10
+ * message. Selection comes from the stock backend, so a recent tail is kept
11
+ * verbatim (retainRatio 0.16 of the window by default) and the cut never splits
12
+ * a tool-call from its result; only the head of the history becomes the
13
+ * checkpoint.
12
14
  *
13
15
  * It subclasses the stock basic backend so selection, tool-pair balance,
14
16
  * durable compaction markers, flushes and the idle admission reservation are
@@ -85,41 +87,40 @@ function readTemplate(filePath, autoCreate) {
85
87
 
86
88
 
87
89
  const BUILTIN_INSTRUCTION = [
88
- "你现在要为本会话写一份交接文档:我会用它替换掉上面的全部对话记录,然后由你在一个空白的上下文里继续同一件事。写漏的东西将永久丢失。",
90
+ "你现在充当这个 AI 编程助手的压缩引擎。请把上面的对话压缩成一份结构化检查点,让另一个模型能在不丢失关键上下文的前提下接手继续这项工作。",
89
91
  "",
90
- "只输出 Markdown,严格按下面小节顺序,每节用短横线要点,不许省略任何小节;没有内容写「(无)」。",
92
+ "严格按下面给出的 Markdown 结构输出:每个小节都要保留,顺序不要变。用简短的要点,不要写成成段的散文。空的小节写「(无)」——绝不允许删掉某个小节。",
91
93
  "",
92
- "## 用户目标与原始要求",
93
- "- 用户最初想做什么、之后怎么改过口;关键原话逐字引用",
94
+ "## 用户主要请求与意图",
95
+ "- [用户的原始目标以及后来的演变;措辞要紧之处逐字引用]",
94
96
  "",
95
- "## 当前任务状态",
96
- "- 在做什么、进行到哪一步、哪些已完成、哪些没完成",
97
+ "## 关键技术概念",
98
+ "- [当前涉及的技术、框架、模式与约定]",
97
99
  "",
98
- "## 涉及的文件与代码",
99
- "- 精确路径:为什么重要、改了什么、还有哪里没改",
100
+ "## 文件与代码",
101
+ "- [精确路径:为什么重要,关键改动或代码片段]",
100
102
  "",
101
- "## 已验证的事实与命令",
102
- "- 跑过的命令、测试结果、报错原文、环境约束(版本、端口、路径)",
103
+ "## 报错与修复",
104
+ "- [错误:是怎么解决的,以及与之相关的用户反馈]",
103
105
  "",
104
- "## 关键决策与被放弃的方案",
105
- "- 选了什么、为什么;试过但放弃的方案及放弃原因",
106
+ "## 未完成的任务",
107
+ "- [明确要求过但尚未完成的工作]",
106
108
  "",
107
- "## 待办与下一步",
108
- "- 用户明确要求但尚未完成的项;紧接着要执行的第一步",
109
+ "## 当前工作",
110
+ "- [在这个检查点上具体正在做什么]",
109
111
  "",
110
- "## 风险与注意事项",
111
- "- 容易踩的坑、用户的偏好与禁忌",
112
+ "## 下一步",
113
+ "- [紧接的单个动作,须与最近一次请求直接对齐;没有则写「(无)」]",
112
114
  "",
113
- "规则:",
114
- "- 保留精确的路径、命令、错误串、标识符、数值、函数签名;不要概括成\"修改了相关文件\"这类废话。",
115
- "- 如果对话里有用户粘贴的长文档或上一轮交接文档(例如包在 <compacted-summary> 标记里的那份),不要原样照抄:保留仍然生效的硬约束(路径、命令、版本、验收标准)并逐字引用其关键句,其余概括;过时的内容删掉,最终只输出一份同样结构的文档。",
116
- "- 如果用户的早期消息给出了一份磁盘上的工程交接文档的绝对路径,必须逐字保留该路径并注明\"需要时重新读取该文件\";不要把它的内容当成必须在本文档里复述的东西。",
117
- "- 不要提到这份交接文档本身,也不要说上下文被压缩或摘要。",
118
- "- 只输出文档正文,不要调用任何工具。",
115
+ "## 关键上下文",
116
+ "- [决策及其理由、约束条件、用户偏好、尚未解决的问题、继续所需的资料]",
119
117
  "",
120
- "写入时还要记住下面两条,它们会被交给后续的你执行:",
121
- "- 更早的对话消息不会留在你的上下文里,也没有任何工具或磁盘检索能把它们找回来。不要去搜索会话文件、日志或历史记录还原旧对话;文档里没写的,就当作不知道,直接说不知道。",
122
- "- 用户问起\"之前说过什么\"时,以本文档为准;文档没有逐字保留的内容(例如旧回答的原文),直接说明已经被压缩掉,不要编造,也不要为了找它去翻文件。",
118
+ "规则:",
119
+ "- 用简洁的中文工程表述书写。精确保留文件路径、命令、报错串、标识符、数值、函数签名与语法片段。",
120
+ "- 忠实保留用户的反馈与明确指令,尤其是纠正。",
121
+ "- 不要提到本摘要请求,也不要说上下文被压缩过。",
122
+ "- 只输出检查点正文:不要调用任何工具,也不要执行任何其他动作。",
123
+ "- 如果对话里已经含有 <compacted-summary> 块,那是一份 PRIOR 检查点。不要把它逐字抄到新的摘要里:保留仍然成立的事实,删掉过时的,并把更新的信息按同样的结构合并进这一份摘要。",
123
124
  ].join("\n");
124
125
 
125
126
  function finishError(finish) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-auto-compaction-engine",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "type": "module",
5
5
  "main": "lib/index.js",
6
6
  "exports": {
@@ -21,6 +21,7 @@
21
21
  *
22
22
  * Usage:
23
23
  * node scripts/preset-scope.mjs status [--preset <cordis.patch.yml>] [--profile-patch <cordis.patch.yml>] [--all-local] [--skip-profile]
24
+ * node scripts/preset-scope.mjs state [--preset <cordis.patch.yml>] [--profile-patch <cordis.patch.yml>] # one JSON line
24
25
  * node scripts/preset-scope.mjs enable [--preset <cordis.patch.yml>] [--profile-patch <cordis.patch.yml>] [--all-local] [--skip-profile]
25
26
  * node scripts/preset-scope.mjs disable [--preset <cordis.patch.yml>] [--profile-patch <cordis.patch.yml>] [--all-local] [--skip-profile]
26
27
  * Restart DSH afterwards.
@@ -334,8 +335,72 @@ function profileStatus() {
334
335
  console.log(`${profilePatch}\n 原生守卫: ${guardState(lines, bounds)}\n YAML: ${validateProfile(original)}`);
335
336
  }
336
337
 
338
+ const PLUGIN_ROWS = new Set(["dsh-auto-compaction-engine", "dsh-auto-compaction", "dsh-handoff-compaction"]);
339
+
340
+ /** Which compaction implementation a preset's slot currently names. */
341
+ function ownerOf(row) {
342
+ if (PLUGIN_ROWS.has(row)) return "plugin";
343
+ if (row === "@deepseek-ai/dsh-compaction-basic") return "native";
344
+ return "unknown";
345
+ }
346
+
347
+ /** Profile-level native fallback: "off" means the plugin guard is holding it. */
348
+ function rootGuardState() {
349
+ if (!fs.existsSync(profilePatch)) return "missing";
350
+ const lines = fs.readFileSync(profilePatch, "utf8").split(/\r?\n/);
351
+ const bounds = nativeBounds(lines);
352
+ if (bounds === undefined) return "missing";
353
+ const state = guardState(lines, bounds);
354
+ if (state.startsWith("auto:false")) return "off";
355
+ if (state.startsWith("absent") || state.startsWith("auto:true")) return "on";
356
+ return "other";
357
+ }
358
+
359
+ /**
360
+ * Machine-readable takeover report, one entry per editable preset. The settings
361
+ * page renders this verbatim instead of guessing from its own config: the YAML
362
+ * on disk is what decides who compacts.
363
+ */
364
+ function stateReport() {
365
+ const settings = readSettings() ?? {};
366
+ const selected = new Set(Array.isArray(settings.presets) ? settings.presets : []);
367
+ const files = presetArg >= 0 ? [argv[presetArg + 1]] : discoverPresetFiles();
368
+ const presets = [];
369
+ for (const file of files) {
370
+ let text;
371
+ try {
372
+ text = fs.readFileSync(file, "utf8");
373
+ } catch {
374
+ continue;
375
+ }
376
+ const lines = text.split(/\r?\n/);
377
+ const id = presetIdOf(text) ?? path.basename(path.dirname(file));
378
+ const row = activeName(lines);
379
+ presets.push({
380
+ id,
381
+ file,
382
+ row,
383
+ owner: slotBounds(lines) === undefined ? "no-slot" : ownerOf(row),
384
+ selected: settings.scope === "global" ? true : selected.has(id),
385
+ });
386
+ }
387
+ return {
388
+ enabled: settings.enabled !== false,
389
+ scope: settings.scope === "preset" ? "preset" : "global",
390
+ presets,
391
+ taken: presets.filter((entry) => entry.owner === "plugin").length,
392
+ total: presets.length,
393
+ rootGuard: rootGuardState(),
394
+ };
395
+ }
396
+
397
+ if (mode === "state") {
398
+ console.log(JSON.stringify(stateReport()));
399
+ process.exit(0);
400
+ }
401
+
337
402
  if (mode !== "status" && mode !== "enable" && mode !== "disable") {
338
- console.error("usage: preset-scope.mjs <status|enable|disable> [--preset <file>] [--profile-patch <file>] [--all-local] [--skip-profile]");
403
+ console.error("usage: preset-scope.mjs <status|state|enable|disable> [--preset <file>] [--profile-patch <file>] [--all-local] [--skip-profile]");
339
404
  process.exit(2);
340
405
  }
341
406