@lexwdex-org/opencode-dcp 4.0.0 → 5.0.1

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/dist/index.js CHANGED
@@ -1028,6 +1028,7 @@ function estimateSlice(messages, start, end) {
1028
1028
  }
1029
1029
  if (typeof state.output === "string") tokens += estimateTokens(state.output);
1030
1030
  if (state.input) tokens += estimateTokens(JSON.stringify(state.input));
1031
+ if (typeof state.error === "string") tokens += estimateTokens(state.error);
1031
1032
  }
1032
1033
  }
1033
1034
  return tokens;
@@ -1044,6 +1045,10 @@ var DTC_DEFAULTS = {
1044
1045
  var C_ZONE_MAX_TURNS = 8;
1045
1046
  var M_ZONE_MAX_TURNS = 12;
1046
1047
  var M_TEXT_KEEP_CHARS = 200;
1048
+ var M_ERROR_KEEP_CHARS = 200;
1049
+ var D_ERROR_KEEP_CHARS = 100;
1050
+ var INPUT_TARGET_KEYS = ["filePath", "path", "file", "filename", "pattern", "directory"];
1051
+ var COMMAND_KEEP_CHARS = 80;
1047
1052
  var FOLDED_TEXT = " ";
1048
1053
  var NO_STATS = {
1049
1054
  messages: 0,
@@ -1054,6 +1059,8 @@ var NO_STATS = {
1054
1059
  contextTokens: 0,
1055
1060
  foldedTools: 0,
1056
1061
  foldedTexts: 0,
1062
+ reducedInputs: 0,
1063
+ foldedErrors: 0,
1057
1064
  digestedTurns: 0,
1058
1065
  skipped: void 0
1059
1066
  };
@@ -1105,11 +1112,18 @@ function transformMessages(messages, deps) {
1105
1112
  const zones = computeZones(messages, headTurns, sessionID, state, config);
1106
1113
  const now = (deps.now ?? Date.now)();
1107
1114
  let level = Math.max(1, minLevel);
1108
- applyLevel(messages, headTurns, zones, level, deps, stats, now);
1115
+ let appliedBand = 0;
1116
+ const applyUpTo = (lvl) => {
1117
+ while (appliedBand < lvl) {
1118
+ appliedBand++;
1119
+ applyBand(messages, headTurns, zones, appliedBand, deps, stats, now);
1120
+ }
1121
+ };
1122
+ applyUpTo(level);
1109
1123
  let estimatedAfter = estimateSlice(messages, 0, messages.length);
1110
1124
  while (estimatedAfter > target && level < 3) {
1111
1125
  level = level + 1;
1112
- applyLevel(messages, headTurns, zones, level, deps, stats, now);
1126
+ applyUpTo(level);
1113
1127
  estimatedAfter = estimateSlice(messages, 0, messages.length);
1114
1128
  }
1115
1129
  stats.level = level;
@@ -1123,6 +1137,8 @@ function transformMessages(messages, deps) {
1123
1137
  estimatedAfter,
1124
1138
  contextTokens,
1125
1139
  foldedTools: stats.foldedTools,
1140
+ reducedInputs: stats.reducedInputs,
1141
+ foldedErrors: stats.foldedErrors,
1126
1142
  digestedTurns: stats.digestedTurns
1127
1143
  });
1128
1144
  return stats;
@@ -1146,19 +1162,16 @@ function computeZones(messages, headTurns, sessionID, state, config) {
1146
1162
  const mStart = Math.min(cStart, Math.max(secondLast, cStart - M_ZONE_MAX_TURNS));
1147
1163
  return { mStart, cStart };
1148
1164
  }
1149
- function applyLevel(messages, headTurns, zones, level, deps, stats, now) {
1150
- const prev = level === 1 ? -1 : level - 1;
1151
- if (level >= 1 && prev < 1) {
1165
+ function applyBand(messages, headTurns, zones, band, deps, stats, now) {
1166
+ if (band === 1) {
1152
1167
  for (let t = 0; t < zones.mStart; t++) {
1153
1168
  foldDistant(messages, headTurns[t], t + 1, deps, stats, now);
1154
1169
  }
1155
- }
1156
- if (level >= 2 && prev < 2) {
1170
+ } else if (band === 2) {
1157
1171
  for (let t = zones.mStart; t < zones.cStart; t++) {
1158
1172
  foldMiddle(messages, headTurns[t], stats, now);
1159
1173
  }
1160
- }
1161
- if (level >= 3 && prev < 3) {
1174
+ } else if (band === 3) {
1162
1175
  for (let t = zones.cStart; t < headTurns.length; t++) {
1163
1176
  foldCurrent(messages, headTurns[t], deps.config.toolOutputKeepChars, stats);
1164
1177
  }
@@ -1177,7 +1190,11 @@ function foldDistant(messages, turn, ordinal, deps, stats, now) {
1177
1190
  const message = messages[i];
1178
1191
  for (const part of message?.parts ?? []) {
1179
1192
  if (!part || typeof part !== "object") continue;
1180
- if (foldToolPart(part, now, true)) {
1193
+ if (part.type === "tool" && part.state?.status === "error") {
1194
+ foldErrorPart(part, D_ERROR_KEEP_CHARS, true, stats);
1195
+ continue;
1196
+ }
1197
+ if (foldToolPart(part, now, "distant", stats)) {
1181
1198
  stats.foldedTools++;
1182
1199
  continue;
1183
1200
  }
@@ -1201,7 +1218,11 @@ function foldMiddle(messages, turn, stats, now) {
1201
1218
  for (let i = turn.start; i < turn.end; i++) {
1202
1219
  for (const part of messages[i]?.parts ?? []) {
1203
1220
  if (!part || typeof part !== "object") continue;
1204
- if (foldToolPart(part, now, false)) {
1221
+ if (part.type === "tool" && part.state?.status === "error") {
1222
+ foldErrorPart(part, M_ERROR_KEEP_CHARS, false, stats);
1223
+ continue;
1224
+ }
1225
+ if (foldToolPart(part, now, "middle", stats)) {
1205
1226
  stats.foldedTools++;
1206
1227
  continue;
1207
1228
  }
@@ -1233,7 +1254,20 @@ function foldCurrent(messages, turn, keepChars, stats) {
1233
1254
  }
1234
1255
  }
1235
1256
  }
1236
- function foldToolPart(part, now, clearInput) {
1257
+ function reduceInput(input) {
1258
+ if (!input || typeof input !== "object") return {};
1259
+ const reduced = {};
1260
+ for (const key of INPUT_TARGET_KEYS) {
1261
+ const value = input[key];
1262
+ if (typeof value === "string" && value) reduced[key] = value;
1263
+ }
1264
+ const command = input.command;
1265
+ if (typeof command === "string" && command) {
1266
+ reduced.command = firstLine(command, COMMAND_KEEP_CHARS);
1267
+ }
1268
+ return reduced;
1269
+ }
1270
+ function foldToolPart(part, now, zone, stats) {
1237
1271
  if (part.type !== "tool") return false;
1238
1272
  const state = part.state;
1239
1273
  if (!state || state.status !== "completed") return false;
@@ -1242,11 +1276,24 @@ function foldToolPart(part, now, clearInput) {
1242
1276
  } else {
1243
1277
  state.time = { compacted: now };
1244
1278
  }
1245
- if (clearInput && state.input && typeof state.input === "object") {
1246
- state.input = {};
1279
+ if (state.input && typeof state.input === "object") {
1280
+ state.input = zone === "distant" ? {} : reduceInput(state.input);
1281
+ stats.reducedInputs++;
1247
1282
  }
1248
1283
  return true;
1249
1284
  }
1285
+ function foldErrorPart(part, keepChars, clearInput, stats) {
1286
+ const state = part.state;
1287
+ if (!state) return;
1288
+ if (typeof state.error === "string" && state.error.length > keepChars) {
1289
+ state.error = firstLine(state.error, keepChars) || FOLDED_TEXT;
1290
+ stats.foldedErrors++;
1291
+ }
1292
+ if (state.input && typeof state.input === "object") {
1293
+ state.input = clearInput ? {} : reduceInput(state.input);
1294
+ stats.reducedInputs++;
1295
+ }
1296
+ }
1250
1297
  function findSessionID(messages) {
1251
1298
  for (const message of messages) {
1252
1299
  const id = message?.info?.sessionID;
@@ -1265,11 +1312,8 @@ var VALID_CONFIG_KEYS = /* @__PURE__ */ new Set([
1265
1312
  "enabled",
1266
1313
  "autoUpdate",
1267
1314
  "debug",
1268
- "language",
1269
1315
  "commands",
1270
1316
  "commands.enabled",
1271
- "experimental",
1272
- "experimental.customPrompts",
1273
1317
  "dtc",
1274
1318
  "dtc.enabled",
1275
1319
  "dtc.tailTurns",
@@ -1322,6 +1366,9 @@ var DEPRECATED_CONFIG_KEYS = /* @__PURE__ */ new Set([
1322
1366
  "protectedFilePatterns",
1323
1367
  "commands.protectedTools",
1324
1368
  "experimental.allowSubAgents",
1369
+ "language",
1370
+ "experimental",
1371
+ "experimental.customPrompts",
1325
1372
  "summarize",
1326
1373
  "summarize.failureCooldownMs",
1327
1374
  "autoPrune",
@@ -1367,13 +1414,6 @@ function validateConfigTypes(config) {
1367
1414
  if (config.debug !== void 0 && typeof config.debug !== "boolean") {
1368
1415
  errors.push({ key: "debug", expected: "boolean", actual: typeof config.debug });
1369
1416
  }
1370
- if (config.language !== void 0 && config.language !== "zh" && config.language !== "en") {
1371
- errors.push({
1372
- key: "language",
1373
- expected: '"zh" or "en"',
1374
- actual: JSON.stringify(config.language)
1375
- });
1376
- }
1377
1417
  const commands = config.commands;
1378
1418
  if (commands !== void 0) {
1379
1419
  if (typeof commands !== "object" || commands === null || Array.isArray(commands)) {
@@ -1386,18 +1426,6 @@ function validateConfigTypes(config) {
1386
1426
  });
1387
1427
  }
1388
1428
  }
1389
- const experimental = config.experimental;
1390
- if (experimental !== void 0) {
1391
- if (typeof experimental !== "object" || experimental === null || Array.isArray(experimental)) {
1392
- errors.push({ key: "experimental", expected: "object", actual: typeof experimental });
1393
- } else if (experimental.customPrompts !== void 0 && typeof experimental.customPrompts !== "boolean") {
1394
- errors.push({
1395
- key: "experimental.customPrompts",
1396
- expected: "boolean",
1397
- actual: typeof experimental.customPrompts
1398
- });
1399
- }
1400
- }
1401
1429
  const dtc = config.dtc;
1402
1430
  if (dtc !== void 0) {
1403
1431
  if (typeof dtc !== "object" || dtc === null || Array.isArray(dtc)) {
@@ -1491,13 +1519,9 @@ var defaultConfig = {
1491
1519
  enabled: true,
1492
1520
  autoUpdate: true,
1493
1521
  debug: false,
1494
- language: "zh",
1495
1522
  commands: {
1496
1523
  enabled: true
1497
1524
  },
1498
- experimental: {
1499
- customPrompts: false
1500
- },
1501
1525
  dtc: { ...DEFAULT_DTC },
1502
1526
  tool: {
1503
1527
  enabled: true
@@ -1576,14 +1600,6 @@ function mergeCommands(base, override) {
1576
1600
  enabled: typeof override.enabled === "boolean" ? override.enabled : base.enabled
1577
1601
  };
1578
1602
  }
1579
- function mergeExperimental(base, override) {
1580
- if (!override) {
1581
- return base;
1582
- }
1583
- return {
1584
- customPrompts: typeof override.customPrompts === "boolean" ? override.customPrompts : base.customPrompts
1585
- };
1586
- }
1587
1603
  function mergeDtc(base, override, legacyDrift) {
1588
1604
  const driftFallback = legacyDrift ?? base.driftThreshold;
1589
1605
  if (!override || typeof override !== "object" || Array.isArray(override)) {
@@ -1611,7 +1627,6 @@ function deepCloneConfig(config) {
1611
1627
  return {
1612
1628
  ...config,
1613
1629
  commands: { ...config.commands },
1614
- experimental: { ...config.experimental },
1615
1630
  dtc: { ...config.dtc },
1616
1631
  tool: { ...config.tool }
1617
1632
  };
@@ -1621,9 +1636,7 @@ function mergeLayer(config, data) {
1621
1636
  enabled: typeof data.enabled === "boolean" ? data.enabled : config.enabled,
1622
1637
  autoUpdate: typeof data.autoUpdate === "boolean" ? data.autoUpdate : config.autoUpdate,
1623
1638
  debug: typeof data.debug === "boolean" ? data.debug : config.debug,
1624
- language: data.language === "zh" || data.language === "en" ? data.language : config.language,
1625
1639
  commands: mergeCommands(config.commands, data.commands),
1626
- experimental: mergeExperimental(config.experimental, data.experimental),
1627
1640
  dtc: mergeDtc(config.dtc, data.dtc, legacyDriftThreshold(data)),
1628
1641
  tool: mergeTool(config.tool, data.tool)
1629
1642
  };
@@ -1704,7 +1717,10 @@ var DtcState = class {
1704
1717
  contextTokens(sessionID) {
1705
1718
  return this.sessions.get(sessionID)?.contextTokens;
1706
1719
  }
1707
- /** Called by the compacting hook; consumed by the very next transform. */
1720
+ /** Called by the compacting hook; consumed by the very next transform.
1721
+ * Accepted leak: if the host aborts between the hook and the summarizer
1722
+ * transform, the flag skips folding once for the next unrelated request —
1723
+ * fail-open (extra tokens, never lost information) and self-healing. */
1708
1724
  armCompactionSkip(sessionID) {
1709
1725
  const state = this.session(sessionID);
1710
1726
  state.skipNextTransform = true;
@@ -1759,56 +1775,18 @@ function eventSessionID(properties) {
1759
1775
  // lib/hooks.ts
1760
1776
  var DEFAULT_TAIL_TURNS = 4;
1761
1777
  var DEFAULT_PRESERVE_RECENT_TOKENS = 32e3;
1762
- function extractPreviousCheckpoint(messages) {
1763
- if (!Array.isArray(messages)) return void 0;
1764
- for (let index = messages.length - 1; index >= 0; index--) {
1765
- const message = messages[index];
1766
- const info = message?.info;
1767
- if (info?.role !== "assistant" || info.summary !== true) continue;
1768
- const parts = Array.isArray(message?.parts) ? message.parts : [];
1769
- const text = parts.filter((part) => part?.type === "text" && typeof part.text === "string").map((part) => part.text.trim()).filter(Boolean).join("\n\n").trim();
1770
- if (text) return text;
1771
- }
1772
- return void 0;
1773
- }
1774
- async function fetchPreviousCheckpoint(client, sessionID) {
1775
- if (!sessionID) return void 0;
1776
- try {
1777
- const response = await client.session.messages({ path: { id: sessionID } });
1778
- return extractPreviousCheckpoint(response.data ?? response);
1779
- } catch {
1780
- return void 0;
1781
- }
1782
- }
1783
1778
  function createSessionCompactingHandler(deps) {
1784
- return async (input, output) => {
1779
+ return async (input, _output) => {
1785
1780
  try {
1786
1781
  deps.state.armCompactionSkip(input.sessionID);
1787
- deps.prompts.reload();
1788
- const prompt = deps.prompts.getRuntimePrompts().compaction;
1789
- const previous = await fetchPreviousCheckpoint(deps.client, input.sessionID);
1790
- const withCheckpoint = previous ? `${prompt}
1791
-
1792
- <previous-checkpoint>
1793
- ${previous}
1794
- </previous-checkpoint>` : prompt;
1795
- if (!output.prompt) {
1796
- output.prompt = withCheckpoint;
1797
- } else if (!output.context.includes(prompt)) {
1798
- output.context.push(withCheckpoint);
1799
- }
1800
- deps.logger.debug("Applied semantic pruning prompt", {
1801
- sessionId: input.sessionID,
1802
- carriedCheckpoint: Boolean(previous)
1782
+ deps.logger.debug("Armed DTC skip for the native compaction input", {
1783
+ sessionId: input.sessionID
1803
1784
  });
1804
1785
  } catch (error) {
1805
- deps.logger.warn(
1806
- "Failed to apply semantic pruning prompt; native compaction continues",
1807
- {
1808
- sessionId: input.sessionID,
1809
- error: error instanceof Error ? error.message : String(error)
1810
- }
1811
- );
1786
+ deps.logger.warn("Failed to arm the compaction skip; native compaction continues", {
1787
+ sessionId: input.sessionID,
1788
+ error: error instanceof Error ? error.message : String(error)
1789
+ });
1812
1790
  }
1813
1791
  };
1814
1792
  }
@@ -2124,153 +2102,6 @@ var Logger = class {
2124
2102
  }
2125
2103
  };
2126
2104
 
2127
- // lib/prompts/store.ts
2128
- import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync2, statSync as statSync2, writeFileSync as writeFileSync2 } from "fs";
2129
- import { homedir as homedir3 } from "os";
2130
- import { dirname as dirname2, join as join3 } from "path";
2131
-
2132
- // lib/prompts/compaction.ts
2133
- var COMPACTION = `\u4F60\u6B63\u5728\u751F\u6210\u5F53\u524D\u4F1A\u8BDD\u552F\u4E00\u7684\u6EDA\u52A8\u68C0\u67E5\u70B9\u3002\u8F93\u51FA\u5C06\u66FF\u4EE3\u65E7\u5BF9\u8BDD\u524D\u7F00\uFF0C\u6210\u4E3A\u540E\u7EED\u6A21\u578B\u770B\u5230\u7684\u7B2C\u4E00\u6BB5\u4E0A\u4E0B\u6587\uFF1BOpenCode \u4F1A\u5728\u68C0\u67E5\u70B9\u4E4B\u540E\u76F4\u63A5\u4FDD\u7559\u6700\u8FD1\u82E5\u5E72\u8F6E\u5B8C\u6574\u5BF9\u8BDD\uFF08\u4E0D\u7ECF\u538B\u7F29\uFF0C\u8303\u56F4\u7531\u5BBF\u4E3B\u914D\u7F6E\u51B3\u5B9A\uFF09\uFF0C\u8FD9\u90E8\u5206\u5C3E\u90E8\u4E0D\u5C5E\u4E8E\u538B\u7F29\u5BF9\u8C61\uFF0C\u4E0D\u8981\u590D\u8FF0\u3001\u603B\u7ED3\u6216\u6539\u5199\u5B83\u4EEC\u3002\u7CFB\u7EDF\u7EA7\u5185\u5BB9\uFF08AGENTS.md\u3001\u9879\u76EE\u89C4\u5219\u7B49\uFF09\u7531 OpenCode \u5728\u6BCF\u6B21\u8BF7\u6C42\u65F6\u72EC\u7ACB\u6CE8\u5165\uFF0C\u4E0D\u5C5E\u4E8E\u538B\u7F29\u8303\u56F4\uFF0C\u4E0D\u8981\u590D\u8FF0\u5B83\u4EEC\u3002
2134
-
2135
- \u8FD9\u4E0D\u662F\u804A\u5929\u8BB0\u5F55\u6458\u8981\uFF0C\u800C\u662F\u53EF\u76F4\u63A5\u7EE7\u7EED\u5DE5\u4F5C\u7684\u8BED\u4E49\u526A\u679D\u7ED3\u679C\u3002\u6309\u4EE5\u4E0B\u5206\u5C42\u89C4\u5219\u538B\u7F29\uFF1A
2136
-
2137
- 1. \u6EDA\u52A8\u5408\u5E76\uFF1A\u5982\u679C\u8F93\u5165\u672B\u5C3E\u542B\u6709 <previous-checkpoint> \u6807\u7B7E\u5305\u88F9\u7684\u4E0A\u4E00\u4EFD\u68C0\u67E5\u70B9\uFF0C\u628A\u4ECD\u6709\u6548\u7684\u4FE1\u606F\u5408\u5E76\u8FDB\u65B0\u68C0\u67E5\u70B9\uFF1B\u4E0D\u8981\u5D4C\u5957\u3001\u5F15\u7528\u6216\u9010\u5B57\u91CD\u590D\u65E7\u68C0\u67E5\u70B9\uFF0C\u5931\u6548\u5185\u5BB9\u76F4\u63A5\u4E22\u5F03\u3002\u6CA1\u6709\u8BE5\u6807\u7B7E\u65F6\u5FFD\u7565\u672C\u6761\u3002
2138
- 2. \u8FDC\u8DDD\u79BB\u5185\u5BB9\uFF08\u65E9\u671F\u4E0E\u4E2D\u90E8\u5386\u53F2\uFF09\u91CD\u5EA6\u538B\u7F29\uFF1A\u6BCF\u4E2A\u4E3B\u9898\u53EA\u7559\u4E00\u53E5\u8BDD\u7ED3\u8BBA\uFF0C\u4E0D\u7559\u8FC7\u7A0B\u3001\u4E0D\u590D\u8FF0\u5BF9\u8BDD\u5F80\u6765\u3002
2139
- 3. \u5220\u9664\u65E0\u5173\u95F2\u804A\u3001\u5176\u4ED6\u9879\u76EE\u6216\u5176\u4ED6\u4ED3\u5E93\u7684\u5BF9\u8BDD\u3001\u91CD\u590D\u89E3\u91CA\u3001\u5DF2\u7ECF\u63A8\u7FFB\u4E14\u4E0D\u518D\u6709\u8BCA\u65AD\u4EF7\u503C\u7684\u65B9\u6848\u3002
2140
- 4. \u591A\u6B21\u5DE5\u5177\u8C03\u7528\u8BD5\u9519\u6216\u5931\u8D25\u540E\u6210\u529F\u65F6\uFF0C\u53EA\u4FDD\u7559\u6700\u7EC8\u6210\u529F\u7ED3\u679C\uFF1B\u4EC5\u5F53\u6839\u56E0\u4F1A\u5F71\u54CD\u540E\u7EED\u5DE5\u4F5C\u65F6\u4FDD\u7559\u4E00\u6B21\u7B80\u77ED\u5931\u8D25\u539F\u56E0\u3002
2141
- 5. \u540C\u4E00\u5185\u5BB9\u6216\u6587\u4EF6\u88AB\u91CD\u590D\u7F16\u8F91\u65F6\uFF0C\u53EA\u4FDD\u7559\u6700\u7EC8\u72B6\u6001\u3001\u4ECD\u6709\u6548\u7684\u5173\u952E\u51B3\u7B56\u548C\u5FC5\u8981\u7406\u7531\uFF0C\u4E0D\u590D\u8FF0\u6BCF\u8F6E\u4FEE\u6539\u3002
2142
- 6. \u8FD1\u8DDD\u79BB\u5185\u5BB9\uFF08\u81EA\u4E0A\u4E00\u4EFD\u68C0\u67E5\u70B9\u4EE5\u6765\u7684\u65B0\u5185\u5BB9\uFF0C\u4E14\u4E0D\u5C5E\u4E8E\u5BBF\u4E3B\u4FDD\u7559\u7684\u5C3E\u90E8\uFF09\u8F7B\u5EA6\u538B\u7F29\uFF1A\u5F53\u524D\u4EFB\u52A1\u3001\u76EE\u6807\u3001\u72B6\u6001\u4E0E\u8FDB\u884C\u4E2D\u7684\u60C5\u51B5\u505A\u6C47\u603B\u4F46\u4E0D\u4E22\u7EC6\u8282\u2014\u2014\u4FDD\u7559\u7EE7\u7EED\u5DE5\u4F5C\u6240\u9700\u7684\u6587\u4EF6\u8DEF\u5F84\u3001\u63A5\u53E3\u3001\u547D\u4EE4\u3001\u6D4B\u8BD5\u7ED3\u679C\u3001\u9519\u8BEF\u4E8B\u5B9E\u3001\u4ECD\u6709\u6548\u7684\u51B3\u7B56\u53CA\u7406\u7531\uFF0C\u53EA\u6298\u53E0\u91CD\u590D\u4E0E\u5DF2\u5931\u6548\u7684\u5185\u5BB9\u3002
2143
-
2144
- \u4F7F\u7528\u4EE5\u4E0B\u56FA\u5B9A\u7ED3\u6784\uFF0C\u7701\u7565\u786E\u5B9E\u4E3A\u7A7A\u7684\u6761\u76EE\uFF1A
2145
-
2146
- ## \u5386\u53F2\u6982\u8981
2147
- \u65E9\u671F\u4E0E\u4E2D\u90E8\u5386\u53F2\u7684\u4E3B\u9898\u548C\u80CC\u666F\uFF0C\u6BCF\u9879\u4E00\u53E5\u8BDD\u7ED3\u8BBA\uFF1B\u8FDC\u671F\u5DF2\u5B8C\u6210\u4EFB\u52A1\u5F52\u5165\u6B64\u5904\uFF0C\u4E0D\u542B\u6267\u884C\u8FC7\u7A0B\u3002
2148
-
2149
- ## \u5DF2\u5B8C\u6210\u4EFB\u52A1\u7684\u6982\u62EC
2150
- \u6700\u8FD1\u7684\u5DF2\u5B8C\u6210\u4EFB\u52A1\uFF0C\u6BCF\u4E2A\u4EFB\u52A1\u4E00\u53E5\u8BDD\u6982\u62EC\u5176\u7ED3\u679C\u4E0E\u5173\u952E\u4EA7\u51FA\u3002
2151
-
2152
- ## \u8FDB\u884C\u4E2D\u4EFB\u52A1\u8BE6\u60C5
2153
- \u5F53\u524D\u6B63\u5728\u8FDB\u884C\u7684\u4EFB\u52A1\u9010\u9879\u5199\u6E05\uFF1A\u76EE\u6807\u3001\u5DF2\u5B8C\u6210\u6B65\u9AA4\u3001\u6D89\u53CA\u6587\u4EF6\u8DEF\u5F84\u4E0E\u63A5\u53E3\u3001\u5173\u952E\u51B3\u7B56\u3001\u9047\u5230\u7684\u963B\u585E\u3001\u4E0B\u4E00\u6B65\u5177\u4F53\u52A8\u4F5C\u3002\u672C\u8282\u5C5E\u4E8E\u8F7B\u5EA6\u538B\u7F29\u533A\uFF0C\u5B81\u53EF\u591A\u4FDD\u7559\u7EC6\u8282\uFF0C\u4E0D\u505A\u4E8C\u6B21\u63A8\u65AD\u3002
2154
-
2155
- ## \u672A\u89E3\u51B3\u95EE\u9898
2156
- \u8DE8\u4EFB\u52A1\u7684\u9057\u7559\u98CE\u9669\u548C\u5F85\u786E\u8BA4\u4E8B\u9879\u3002\u53EA\u5199\u672A\u5728\u300C\u8FDB\u884C\u4E2D\u4EFB\u52A1\u8BE6\u60C5\u300D\u4E2D\u51FA\u73B0\u7684\u5185\u5BB9\uFF0C\u907F\u514D\u4E0E\u8BE5\u8282\u91CD\u590D\u3002
2157
-
2158
- \u4FDD\u6301\u5177\u4F53\u3001\u53EF\u9A8C\u8BC1\u548C\u9879\u76EE\u5185\u805A\u3002\u8FDB\u884C\u4E2D\u7684\u4EFB\u52A1\u5FC5\u987B\u80FD\u51ED\u68C0\u67E5\u70B9\u76F4\u63A5\u7EE7\u7EED\uFF0C\u4E0D\u8981\u4F9D\u8D56\u5DF2\u88AB\u538B\u7F29\u6389\u7684\u4E2D\u95F4\u8FC7\u7A0B\u3002\u4FDD\u7559\u6587\u4EF6\u8DEF\u5F84\u3001\u63A5\u53E3\u3001\u547D\u4EE4\u3001\u6D4B\u8BD5\u7ED3\u679C\u548C\u9519\u8BEF\u4E8B\u5B9E\u7B49\u786C\u4E8B\u5B9E\uFF0C\u4F46\u4E0D\u8981\u4FDD\u7559\u6D88\u606F ID\u3001\u5757 ID\u3001\u951A\u70B9\u3001\u5360\u4F4D\u7B26\u3001\u63A7\u5236\u6807\u7B7E\u6216\u8FC7\u7A0B\u6027\u804A\u5929\u3002`;
2159
- var COMPACTION_EN = `You are generating the single rolling checkpoint for this session. Your output will replace the old conversation prefix as the first context the model sees afterwards; OpenCode keeps the most recent conversation turns directly after the checkpoint, uncompacted (their range is a host setting). That tail is outside the compaction scope: do not restate, summarize, or rewrite it. System-level content (AGENTS.md, project rules, etc.) is injected independently by OpenCode on every request and is not part of compaction; do not restate it.
2160
-
2161
- This is not a chat-log summary but a semantic pruning result one can resume working from directly. Compress by these tiered rules:
2162
-
2163
- 1. Rolling merge: if the input ends with a <previous-checkpoint> block containing the previous checkpoint, merge its still-valid information into the new checkpoint; do not nest, quote, or repeat it verbatim, and drop stale content outright. Ignore this rule when the tag is absent.
2164
- 2. Distant content (early and middle history) is compressed heavily: one concluding sentence per topic, no process detail, no dialogue restatement.
2165
- 3. Remove irrelevant chitchat, conversations about other projects or repositories, repeated explanations, and approaches that were overturned and no longer carry diagnostic value.
2166
- 4. When repeated tool trial-and-error ends in success, keep only the final successful outcome; retain one brief failure reason only if the root cause affects future work.
2167
- 5. When the same content or file was edited repeatedly, keep only the final state, the still-valid key decisions, and the necessary rationale; do not restate each round of edits.
2168
- 6. Recent content (everything generated since the previous checkpoint, excluding the host-retained tail) is compressed lightly: summarize the current task, goals, status, and in-progress work without losing detail\u2014keep the file paths, interfaces, commands, test results, error facts, and still-valid decisions with rationale needed to continue, folding only duplicates and invalidated content.
2169
-
2170
- Use the following fixed structure, omitting sections that are truly empty:
2171
-
2172
- ## History Overview
2173
- Topics and background from early and middle history, one concluding sentence each; long-completed tasks belong here, without execution process.
2174
-
2175
- ## Completed Task Summaries
2176
- Recently completed tasks, one sentence per task covering its outcome and key outputs.
2177
-
2178
- ## In-Progress Task Details
2179
- For the current task, itemize: goal, completed steps, file paths and interfaces involved, key decisions, blockers encountered, concrete next actions. This is the lightly-compressed zone; prefer keeping more detail and avoid second-order inference.
2180
-
2181
- ## Unresolved Issues
2182
- Cross-task lingering risks and items awaiting confirmation. Only list items not already covered under In-Progress Task Details, to avoid duplication.
2183
-
2184
- Stay specific, verifiable, and project-cohesive. An in-progress task must be resumable straight from this checkpoint, without relying on intermediate process that was pruned away. Keep hard facts such as file paths, interfaces, commands, test results, and error facts, but never message IDs, block IDs, anchors, placeholders, control tags, or procedural chatter.`;
2185
- function getCompactionPrompt(language) {
2186
- return language === "en" ? COMPACTION_EN : COMPACTION;
2187
- }
2188
-
2189
- // lib/prompts/store.ts
2190
- function findOpencodeDir2(startDir) {
2191
- let current = startDir;
2192
- while (current !== "/") {
2193
- const candidate = join3(current, ".opencode");
2194
- if (existsSync3(candidate) && statSync2(candidate).isDirectory()) return candidate;
2195
- const parent = dirname2(current);
2196
- if (parent === current) break;
2197
- current = parent;
2198
- }
2199
- return null;
2200
- }
2201
- function resolvePaths(workingDirectory) {
2202
- const configHome = process.env.XDG_CONFIG_HOME || join3(homedir3(), ".config");
2203
- const globalRoot = join3(configHome, "opencode", "dcp-prompts");
2204
- const opencodeDir = findOpencodeDir2(workingDirectory);
2205
- return {
2206
- defaultsDir: join3(globalRoot, "defaults"),
2207
- overrides: [
2208
- opencodeDir ? join3(opencodeDir, "dcp-prompts", "overrides", "compaction.md") : "",
2209
- process.env.OPENCODE_CONFIG_DIR ? join3(process.env.OPENCODE_CONFIG_DIR, "dcp-prompts", "overrides", "compaction.md") : "",
2210
- join3(globalRoot, "overrides", "compaction.md")
2211
- ].filter(Boolean)
2212
- };
2213
- }
2214
- function normalize(content) {
2215
- return content.replace(/^\uFEFF/, "").replace(/\r\n?/g, "\n").replace(/<!--[\s\S]*?-->/g, "").trim();
2216
- }
2217
- var PromptStore = class {
2218
- constructor(logger, workingDirectory, customPromptsEnabled = false, language) {
2219
- this.logger = logger;
2220
- this.customPromptsEnabled = customPromptsEnabled;
2221
- this.paths = resolvePaths(workingDirectory);
2222
- this.defaultPrompt = getCompactionPrompt(language);
2223
- this.runtime = { compaction: this.defaultPrompt };
2224
- if (customPromptsEnabled) this.ensureDefaults();
2225
- this.reload();
2226
- }
2227
- paths;
2228
- defaultPrompt;
2229
- runtime;
2230
- lastReloadAt = 0;
2231
- getRuntimePrompts() {
2232
- return { ...this.runtime };
2233
- }
2234
- reload() {
2235
- const now = Date.now();
2236
- if (now - this.lastReloadAt < 1e3) return;
2237
- this.lastReloadAt = now;
2238
- this.runtime = { compaction: this.defaultPrompt };
2239
- if (!this.customPromptsEnabled) return;
2240
- for (const path of this.paths.overrides) {
2241
- if (!existsSync3(path)) continue;
2242
- try {
2243
- const prompt = normalize(readFileSync2(path, "utf-8"));
2244
- if (prompt) this.runtime = { compaction: prompt };
2245
- return;
2246
- } catch (error) {
2247
- this.logger.warn("Failed to load compaction prompt override", {
2248
- path,
2249
- error: error instanceof Error ? error.message : String(error)
2250
- });
2251
- }
2252
- }
2253
- }
2254
- ensureDefaults() {
2255
- try {
2256
- mkdirSync2(this.paths.defaultsDir, { recursive: true });
2257
- writeFileSync2(
2258
- join3(this.paths.defaultsDir, "compaction.md"),
2259
- `${this.defaultPrompt.trim()}
2260
- `
2261
- );
2262
- writeFileSync2(
2263
- join3(this.paths.defaultsDir, "README.md"),
2264
- "# DCP compaction prompt\n\nCopy `compaction.md` to an `overrides` directory and restart OpenCode.\n"
2265
- );
2266
- } catch (error) {
2267
- this.logger.warn("Failed to write bundled compaction prompt", {
2268
- error: error instanceof Error ? error.message : String(error)
2269
- });
2270
- }
2271
- }
2272
- };
2273
-
2274
2105
  // lib/prune-tool.ts
2275
2106
  import { tool } from "@opencode-ai/plugin";
2276
2107
  var PRUNE_TOOL_NAME = "dcp_prune";
@@ -2299,7 +2130,7 @@ function createPruneTool(deps) {
2299
2130
 
2300
2131
  // lib/update.ts
2301
2132
  import { readFile } from "fs/promises";
2302
- import { basename, dirname as dirname3, join as join4 } from "path";
2133
+ import { basename, dirname as dirname2, join as join3 } from "path";
2303
2134
  import { fileURLToPath } from "url";
2304
2135
  var PACKAGE_NAME = "@lexwdex-org/opencode-dcp";
2305
2136
  function startAutoUpdate(ctx, enabled) {
@@ -2325,7 +2156,7 @@ function startAutoUpdate(ctx, enabled) {
2325
2156
  async function checkAutoUpdate(signal) {
2326
2157
  const packageDir = await findPackageDir(PACKAGE_NAME);
2327
2158
  if (!packageDir) return { available: false };
2328
- const pkg = await readPackageJson(join4(packageDir, "package.json"));
2159
+ const pkg = await readPackageJson(join3(packageDir, "package.json"));
2329
2160
  if (!pkg?.name || !pkg.version) return { available: false };
2330
2161
  const latest = await fetchLatestVersion(pkg.name, signal);
2331
2162
  if (!latest || !isVersionNewer(latest, pkg.version)) return { available: false };
@@ -2334,21 +2165,21 @@ async function checkAutoUpdate(signal) {
2334
2165
  return { available: true, name: pkg.name, current: pkg.version, latest };
2335
2166
  }
2336
2167
  async function findPackageDir(name) {
2337
- let dir = dirname3(fileURLToPath(import.meta.url));
2168
+ let dir = dirname2(fileURLToPath(import.meta.url));
2338
2169
  for (; ; ) {
2339
- const pkg = await readPackageJson(join4(dir, "package.json"));
2170
+ const pkg = await readPackageJson(join3(dir, "package.json"));
2340
2171
  if (pkg?.name === name) return dir;
2341
- const parent = dirname3(dir);
2172
+ const parent = dirname2(dir);
2342
2173
  if (parent === dir) return void 0;
2343
2174
  dir = parent;
2344
2175
  }
2345
2176
  }
2346
2177
  async function findAutoUpdateWrapperDir(packageDir, name) {
2347
- const packageParent = dirname3(packageDir);
2348
- const nodeModulesDir = basename(packageParent).startsWith("@") ? dirname3(packageParent) : packageParent;
2178
+ const packageParent = dirname2(packageDir);
2179
+ const nodeModulesDir = basename(packageParent).startsWith("@") ? dirname2(packageParent) : packageParent;
2349
2180
  if (basename(nodeModulesDir) !== "node_modules") return void 0;
2350
- const wrapperDir = dirname3(nodeModulesDir);
2351
- const wrapperPkg = await readPackageJson(join4(wrapperDir, "package.json"));
2181
+ const wrapperDir = dirname2(nodeModulesDir);
2182
+ const wrapperPkg = await readPackageJson(join3(wrapperDir, "package.json"));
2352
2183
  const spec = wrapperSpec(wrapperDir, name) ?? wrapperPkg?.dependencies?.[name];
2353
2184
  if (!spec || !isAutoUpdatableSpec(spec)) return void 0;
2354
2185
  return wrapperDir;
@@ -2356,7 +2187,7 @@ async function findAutoUpdateWrapperDir(packageDir, name) {
2356
2187
  function wrapperSpec(wrapperDir, name) {
2357
2188
  if (name.startsWith("@")) {
2358
2189
  const [scope, pkg] = name.split("/");
2359
- if (!scope || !pkg || basename(dirname3(wrapperDir)) !== scope) return void 0;
2190
+ if (!scope || !pkg || basename(dirname2(wrapperDir)) !== scope) return void 0;
2360
2191
  const prefix2 = `${pkg}@`;
2361
2192
  const base2 = basename(wrapperDir);
2362
2193
  return base2.startsWith(prefix2) ? base2.slice(prefix2.length) : void 0;
@@ -2437,37 +2268,28 @@ var server = (async (ctx) => {
2437
2268
  const config = getConfig(ctx);
2438
2269
  if (!config.enabled) return {};
2439
2270
  const logger = new Logger(config.debug);
2440
- const prompts = new PromptStore(
2441
- logger,
2442
- ctx.directory,
2443
- config.experimental.customPrompts,
2444
- config.language
2445
- );
2446
2271
  const state = new DtcState();
2447
2272
  logger.info("DCP initialized", {
2448
2273
  commands: config.commands.enabled,
2449
2274
  dtc: config.dtc.enabled,
2450
- tool: config.tool.enabled,
2451
- customPrompts: config.experimental.customPrompts
2275
+ tool: config.tool.enabled
2452
2276
  });
2453
2277
  startAutoUpdate(ctx, config.autoUpdate);
2454
2278
  return {
2455
- "experimental.session.compacting": createSessionCompactingHandler({
2456
- prompts,
2457
- logger,
2458
- client: ctx.client,
2459
- state
2460
- }),
2461
2279
  // THE compression surface: dynamic tiered folding on every model
2462
2280
  // request, plus the chat.params feed that teaches the engine each
2463
- // session's context-window size.
2281
+ // session's context-window size. The compacting hook rides along
2282
+ // solely to keep the host's native compaction input unfolded — the
2283
+ // native prompt is never touched. With DTC off, DCP registers no
2284
+ // compaction-adjacent hook at all.
2464
2285
  ...config.dtc.enabled && {
2465
2286
  "experimental.chat.messages.transform": createTransformHandler({
2466
2287
  state,
2467
2288
  config: config.dtc,
2468
2289
  logger
2469
2290
  }),
2470
- "chat.params": createChatParamsHandler({ state, logger })
2291
+ "chat.params": createChatParamsHandler({ state, logger }),
2292
+ "experimental.session.compacting": createSessionCompactingHandler({ state, logger })
2471
2293
  },
2472
2294
  // Lifecycle cleanup for DTC session state (LRU-bounded regardless).
2473
2295
  event: createEventHandler({ state, logger }),