oh-my-opencode-cohub 1.17.1 → 1.18.0

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 (53) hide show
  1. package/README.md +2 -2
  2. package/dist/cli/config-io.d.ts +2 -2
  3. package/dist/cli/config-io.d.ts.map +1 -1
  4. package/dist/cli/index.js +209 -67
  5. package/dist/config/loader.d.ts +4 -0
  6. package/dist/config/loader.d.ts.map +1 -1
  7. package/dist/context/diag-log.d.ts +62 -0
  8. package/dist/context/diag-log.d.ts.map +1 -0
  9. package/dist/context/engine.d.ts +33 -0
  10. package/dist/context/engine.d.ts.map +1 -1
  11. package/dist/context/extractor.d.ts +1 -20
  12. package/dist/context/extractor.d.ts.map +1 -1
  13. package/dist/index.d.ts.map +1 -1
  14. package/dist/index.js +602 -386
  15. package/dist/instructions/chinese.d.ts +1 -1
  16. package/dist/instructions/chinese.d.ts.map +1 -1
  17. package/dist/prompts/council.d.ts +1 -1
  18. package/dist/prompts/council.d.ts.map +1 -1
  19. package/dist/prompts/oracle.d.ts +1 -1
  20. package/dist/prompts/oracle.d.ts.map +1 -1
  21. package/dist/prompts/orchestrator.d.ts +1 -1
  22. package/dist/prompts/orchestrator.d.ts.map +1 -1
  23. package/dist/prompts/rule-app.d.ts +1 -1
  24. package/dist/prompts/rule-app.d.ts.map +1 -1
  25. package/dist/prompts/rule-project.d.ts +1 -1
  26. package/dist/prompts/rule-project.d.ts.map +1 -1
  27. package/dist/prompts/rule-user.d.ts +1 -1
  28. package/dist/prompts/rule-user.d.ts.map +1 -1
  29. package/dist/shared/chinese.d.ts +2 -0
  30. package/dist/shared/chinese.d.ts.map +1 -0
  31. package/dist/shared/consensus.d.ts +132 -0
  32. package/dist/shared/consensus.d.ts.map +1 -0
  33. package/dist/shared/constants.d.ts +26 -0
  34. package/dist/shared/constants.d.ts.map +1 -0
  35. package/dist/shared/fs-atomic.d.ts +11 -0
  36. package/dist/shared/fs-atomic.d.ts.map +1 -0
  37. package/dist/shared/log.d.ts +38 -0
  38. package/dist/shared/log.d.ts.map +1 -0
  39. package/dist/shared/model.d.ts +7 -0
  40. package/dist/shared/model.d.ts.map +1 -0
  41. package/dist/shared/token-estimator.d.ts +25 -0
  42. package/dist/shared/token-estimator.d.ts.map +1 -0
  43. package/dist/task-manager/tracker.d.ts +17 -5
  44. package/dist/task-manager/tracker.d.ts.map +1 -1
  45. package/dist/task-manager/types.d.ts +2 -1
  46. package/dist/task-manager/types.d.ts.map +1 -1
  47. package/dist/tools/council.d.ts +2 -66
  48. package/dist/tools/council.d.ts.map +1 -1
  49. package/dist/tui.d.ts.map +1 -1
  50. package/dist/tui.js +96 -48
  51. package/dist/utils/log.d.ts +8 -12
  52. package/dist/utils/log.d.ts.map +1 -1
  53. package/package.json +4 -4
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  OpenCode 中文智能体编排插件 CoHub——纯调度模式、全中文提示词、编排引擎、上下文共享、规范分析代理、方案制定代理。
4
4
 
5
- [![GitHub](https://img.shields.io/badge/GitHub-Mr--cjf%2Foh--my--opencode--cohub-blue?logo=github)](https://github.com/Mr-cjf/oh-my-opencode-cohub)
5
+ [![GitHub](https://img.shields.io/badge/GitHub-Mr--cjf%2Fcohub-blue?logo=github)](https://github.com/Mr-cjf/cohub)
6
6
 
7
7
  ## 特性
8
8
 
@@ -259,7 +259,7 @@ bunx oh-my-opencode-cohub uninstall && bun pm cache rm && bunx oh-my-opencode-co
259
259
 
260
260
  ```bash
261
261
  # 1. 克隆项目
262
- git clone https://github.com/Mr-cjf/oh-my-opencode-cohub.git && cd oh-my-opencode-cohub
262
+ git clone https://github.com/Mr-cjf/cohub.git && cd cohub/packages/oh-my-opencode-cohub
263
263
 
264
264
  # 2. 安装依赖
265
265
  npm install
@@ -1,9 +1,9 @@
1
- /** 添加 CoHub 到 opencode.json 的 plugin 数组 */
1
+ /** 添加 CoHub 到 opencode.json 的 plugin 数组(宿主配置:解析失败中止 + 写入前备份) */
2
2
  export declare function addPluginToOpenCodeConfig(version: string): {
3
3
  success: boolean;
4
4
  message: string;
5
5
  };
6
- /** 添加 CoHub 到 tui.json 的 plugin 数组 */
6
+ /** 添加 CoHub 到 tui.json 的 plugin 数组(宿主配置:解析失败中止 + 写入前备份) */
7
7
  export declare function addPluginToTuiConfig(): {
8
8
  success: boolean;
9
9
  message: string;
@@ -1 +1 @@
1
- {"version":3,"file":"config-io.d.ts","sourceRoot":"","sources":["../../src/cli/config-io.ts"],"names":[],"mappings":"AAmCA,2CAA2C;AAC3C,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,MAAM,GAAG;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAwBhG;AAED,sCAAsC;AACtC,wBAAgB,oBAAoB,IAAI;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAa5E;AAOD;;wDAEwD;AACxD,wBAAgB,mBAAmB,IAAI;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAuF3E;AA+ED,4DAA4D;AAC5D,wBAAgB,kBAAkB,IAAI;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CA2G1E;AAED,8BAA8B;AAC9B,wBAAgB,cAAc,IAAI;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,QAAQ,EAAE,MAAM,EAAE,CAAA;CAAE,CAgIzE;AAED,qFAAqF;AACrF,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CA6BxF"}
1
+ {"version":3,"file":"config-io.d.ts","sourceRoot":"","sources":["../../src/cli/config-io.ts"],"names":[],"mappings":"AAiJA,gEAAgE;AAChE,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,MAAM,GAAG;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CA0BhG;AAED,2DAA2D;AAC3D,wBAAgB,oBAAoB,IAAI;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAe5E;AAOD;;wDAEwD;AACxD,wBAAgB,mBAAmB,IAAI;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAwF3E;AA+ED,4DAA4D;AAC5D,wBAAgB,kBAAkB,IAAI;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAuG1E;AAED,8BAA8B;AAC9B,wBAAgB,cAAc,IAAI;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,QAAQ,EAAE,MAAM,EAAE,CAAA;CAAE,CA2IzE;AAED,qFAAqF;AACrF,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CA6BxF"}
package/dist/cli/index.js CHANGED
@@ -1,13 +1,41 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/cli/index.ts
4
- import * as fs2 from "node:fs";
4
+ import * as fs3 from "node:fs";
5
5
 
6
6
  // src/task-manager/tracker.ts
7
7
  import * as os from "node:os";
8
+ import * as path2 from "node:path";
9
+
10
+ // src/shared/fs-atomic.ts
11
+ import * as fs from "node:fs";
8
12
  import * as path from "node:path";
13
+ function writeFileAtomic(filePath, data) {
14
+ const tmpPath = filePath + ".tmp";
15
+ const dir = path.dirname(filePath);
16
+ fs.mkdirSync(dir, { recursive: true });
17
+ try {
18
+ fs.writeFileSync(tmpPath, data, "utf-8");
19
+ fs.renameSync(tmpPath, filePath);
20
+ } catch (err) {
21
+ try {
22
+ fs.unlinkSync(tmpPath);
23
+ } catch {}
24
+ throw err;
25
+ } finally {
26
+ try {
27
+ if (fs.existsSync(tmpPath))
28
+ fs.unlinkSync(tmpPath);
29
+ } catch {}
30
+ }
31
+ }
32
+ function writeJsonFileAtomic(filePath, data) {
33
+ writeFileAtomic(filePath, JSON.stringify(data, null, 2));
34
+ }
35
+
36
+ // src/task-manager/tracker.ts
9
37
  var DEFAULT_STATS_WINDOW = 50;
10
- var STATS_FILE = path.join(os.homedir(), ".local", "share", "opencode", "storage", "oh-my-opencode-cohub", "stats.json");
38
+ var STATS_FILE = path2.join(os.homedir(), ".local", "share", "opencode", "storage", "oh-my-opencode-cohub", "stats.json");
11
39
  function computeStats(records, window = DEFAULT_STATS_WINDOW) {
12
40
  const recent = records.slice(-Math.max(1, window));
13
41
  const buckets = new Map;
@@ -49,45 +77,154 @@ function computeStats(records, window = DEFAULT_STATS_WINDOW) {
49
77
  return result;
50
78
  }
51
79
  // package.json
52
- var version = "1.17.1";
80
+ var version = "1.18.0";
53
81
 
54
82
  // src/cli/config-io.ts
55
- import * as fs from "node:fs";
56
- import * as path2 from "node:path";
83
+ import * as fs2 from "node:fs";
84
+ import * as path3 from "node:path";
57
85
  import * as os2 from "node:os";
86
+
87
+ // src/shared/constants.ts
88
+ var COUNCIL_TIMEOUT_MS = 180000;
89
+ var STALE_TASK_MS = 30 * 60 * 1000;
90
+
91
+ // src/cli/config-io.ts
58
92
  import { execSync } from "node:child_process";
59
93
  var PACKAGE_NAME = "oh-my-opencode-cohub";
60
94
  function getOpencodeConfigPath() {
61
- return path2.join(os2.homedir(), ".config", "opencode", "opencode.json");
95
+ return path3.join(os2.homedir(), ".config", "opencode", "opencode.json");
62
96
  }
63
97
  function getTuiConfigPath() {
64
- return path2.join(os2.homedir(), ".config", "opencode", "tui.json");
98
+ return path3.join(os2.homedir(), ".config", "opencode", "tui.json");
99
+ }
100
+ function stripJsonc(raw) {
101
+ const out = [];
102
+ let inString = false;
103
+ let escaped = false;
104
+ let pendingComma = -1;
105
+ let i = 0;
106
+ while (i < raw.length) {
107
+ const ch = raw[i];
108
+ if (inString) {
109
+ out.push(ch);
110
+ if (escaped)
111
+ escaped = false;
112
+ else if (ch === "\\")
113
+ escaped = true;
114
+ else if (ch === '"')
115
+ inString = false;
116
+ i++;
117
+ continue;
118
+ }
119
+ if (ch === '"') {
120
+ inString = true;
121
+ pendingComma = -1;
122
+ out.push(ch);
123
+ i++;
124
+ continue;
125
+ }
126
+ if (ch === "/" && raw[i + 1] === "/") {
127
+ i += 2;
128
+ while (i < raw.length && raw[i] !== `
129
+ `)
130
+ i++;
131
+ continue;
132
+ }
133
+ if (ch === "/" && raw[i + 1] === "*") {
134
+ i += 2;
135
+ while (i < raw.length && !(raw[i] === "*" && raw[i + 1] === "/"))
136
+ i++;
137
+ i += 2;
138
+ continue;
139
+ }
140
+ if (ch === ",") {
141
+ pendingComma = out.length;
142
+ out.push(ch);
143
+ i++;
144
+ continue;
145
+ }
146
+ if (ch === " " || ch === "\t" || ch === "\r" || ch === `
147
+ `) {
148
+ out.push(ch);
149
+ i++;
150
+ continue;
151
+ }
152
+ if ((ch === "}" || ch === "]") && pendingComma >= 0) {
153
+ out[pendingComma] = " ";
154
+ }
155
+ pendingComma = -1;
156
+ out.push(ch);
157
+ i++;
158
+ }
159
+ return out.join("");
65
160
  }
66
- function readJSON(filePath) {
161
+ function readJsonFile(filePath) {
162
+ let raw;
67
163
  try {
68
- if (!fs.existsSync(filePath))
69
- return null;
70
- const raw = fs.readFileSync(filePath, "utf-8");
71
- try {
72
- return JSON.parse(raw);
73
- } catch {
74
- return JSON.parse(raw.replace(/\/\/.*$/gm, ""));
164
+ if (!fs2.existsSync(filePath))
165
+ return { ok: true, value: null };
166
+ raw = fs2.readFileSync(filePath, "utf-8");
167
+ } catch (err) {
168
+ return { ok: false, reason: `读取失败: ${err.message}` };
169
+ }
170
+ if (raw.trim() === "")
171
+ return { ok: true, value: null };
172
+ try {
173
+ const parsed = JSON.parse(raw);
174
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
175
+ return { ok: false, reason: "顶层不是 JSON 对象" };
75
176
  }
76
- } catch {
77
- return null;
177
+ return { ok: true, value: parsed };
178
+ } catch {}
179
+ try {
180
+ const parsed = JSON.parse(stripJsonc(raw));
181
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
182
+ return { ok: false, reason: "顶层不是 JSON 对象" };
183
+ }
184
+ return { ok: true, value: parsed };
185
+ } catch (err) {
186
+ return { ok: false, reason: err.message };
187
+ }
188
+ }
189
+ function readJSONLoose(filePath) {
190
+ const r = readJsonFile(filePath);
191
+ return r.ok ? r.value : null;
192
+ }
193
+ function loadConfigForWrite(filePath, label) {
194
+ const r = readJsonFile(filePath);
195
+ if (!r.ok) {
196
+ return {
197
+ ok: false,
198
+ message: `⚠ 拒绝覆盖 ${label}(JSON 解析失败): ${filePath}
199
+ ${r.reason}
200
+ 请先修复该文件语法后重新运行;原文件未被修改。`
201
+ };
78
202
  }
203
+ return { ok: true, config: r.value ?? {} };
79
204
  }
80
- function writeJSON(filePath, data) {
81
- const dir = path2.dirname(filePath);
82
- if (!fs.existsSync(dir))
83
- fs.mkdirSync(dir, { recursive: true });
205
+ function backupFile(filePath) {
206
+ const bak = filePath + ".bak";
207
+ if (fs2.existsSync(bak))
208
+ return;
209
+ if (fs2.existsSync(filePath))
210
+ fs2.copyFileSync(filePath, bak);
211
+ }
212
+ function writeJSON(filePath, data, opts = {}) {
213
+ const dir = path3.dirname(filePath);
214
+ if (!fs2.existsSync(dir))
215
+ fs2.mkdirSync(dir, { recursive: true });
216
+ if (opts.backup !== false)
217
+ backupFile(filePath);
84
218
  const content = JSON.stringify(data, null, 2) + `
85
219
  `;
86
- fs.writeFileSync(filePath, content, "utf-8");
220
+ fs2.writeFileSync(filePath, content, "utf-8");
87
221
  }
88
222
  function addPluginToOpenCodeConfig(version) {
89
223
  const configPath = getOpencodeConfigPath();
90
- let config = readJSON(configPath) ?? {};
224
+ const loaded = loadConfigForWrite(configPath, "opencode.json");
225
+ if (!loaded.ok)
226
+ return { success: false, message: loaded.message };
227
+ const config = loaded.config;
91
228
  const plugins = Array.isArray(config.plugin) ? [...config.plugin] : [];
92
229
  const pkgSpec = `${PACKAGE_NAME}@${version}`;
93
230
  if (plugins.some((p) => p.includes(PACKAGE_NAME))) {
@@ -107,7 +244,10 @@ function addPluginToOpenCodeConfig(version) {
107
244
  }
108
245
  function addPluginToTuiConfig() {
109
246
  const configPath = getTuiConfigPath();
110
- let config = readJSON(configPath) ?? {};
247
+ const loaded = loadConfigForWrite(configPath, "tui.json");
248
+ if (!loaded.ok)
249
+ return { success: false, message: loaded.message };
250
+ const config = loaded.config;
111
251
  const plugins = Array.isArray(config.plugin) ? [...config.plugin] : [];
112
252
  if (plugins.some((p) => p.includes(PACKAGE_NAME))) {
113
253
  return { success: true, message: "CoHub 已在 tui.json 的 plugin 数组中,跳过" };
@@ -118,14 +258,14 @@ function addPluginToTuiConfig() {
118
258
  return { success: true, message: `✓ 已添加 "${PACKAGE_NAME}" 到 tui.json 的 plugin 数组` };
119
259
  }
120
260
  function getOhMyConfigPath() {
121
- return path2.join(os2.homedir(), ".config", "opencode", "oh-my-opencode-slim.json");
261
+ return path3.join(os2.homedir(), ".config", "opencode", "oh-my-opencode-slim.json");
122
262
  }
123
263
  function registerCoHubAgents() {
124
264
  const configPath = getOpencodeConfigPath();
125
- let config = readJSON(configPath);
126
- if (!config) {
127
- config = {};
128
- }
265
+ const loaded = loadConfigForWrite(configPath, "opencode.json");
266
+ if (!loaded.ok)
267
+ return { success: false, message: loaded.message };
268
+ const config = loaded.config;
129
269
  let needUpdate = false;
130
270
  const COHUB_BUILTIN_FIELDS = new Set(["description", "mode", "prompt", "permission"]);
131
271
  let cleaned = 0;
@@ -198,8 +338,8 @@ function registerCoHubAgents() {
198
338
  msgParts.push("Agent 定义已由插件运行时接管(参照 slim 架构)");
199
339
  return { success: true, message: `✓ ${msgParts.join(",")}` };
200
340
  }
201
- var COHUB_CONFIG_PATH = path2.join(os2.homedir(), ".config", "opencode", "oh-my-opencode-cohub.json");
202
- var PACKAGES_CACHE = path2.join(os2.homedir(), ".cache", "opencode", "packages", PACKAGE_NAME);
341
+ var COHUB_CONFIG_PATH = path3.join(os2.homedir(), ".config", "opencode", "oh-my-opencode-cohub.json");
342
+ var PACKAGES_CACHE = path3.join(os2.homedir(), ".cache", "opencode", "packages", PACKAGE_NAME);
203
343
  var HEAVY_KEYWORDS = ["pro", "preview", "opus", "max", "sonnet", "gpt-5.6"];
204
344
  var LIGHT_KEYWORDS = ["flash", "mini", "haiku", "gpt-5.4", "gpt-5.3"];
205
345
  var DESIGN_KEYWORDS = ["minimax", "m3", "sonnet", "claude"];
@@ -231,7 +371,7 @@ function findBestModel(flatModels, keywords, agentType) {
231
371
  return null;
232
372
  }
233
373
  function flattenModelsFromOpenCode() {
234
- const opencodeConfig = readJSON(getOpencodeConfigPath());
374
+ const opencodeConfig = readJSONLoose(getOpencodeConfigPath());
235
375
  if (!opencodeConfig)
236
376
  return [];
237
377
  const providerRecord = opencodeConfig?.config;
@@ -253,7 +393,7 @@ function flattenModelsFromOpenCode() {
253
393
  return result;
254
394
  }
255
395
  function writeDefaultConfig() {
256
- if (fs.existsSync(COHUB_CONFIG_PATH)) {
396
+ if (fs2.existsSync(COHUB_CONFIG_PATH)) {
257
397
  return { success: true, message: "oh-my-opencode-cohub.json 已存在,跳过" };
258
398
  }
259
399
  const flatModels = flattenModelsFromOpenCode();
@@ -265,10 +405,7 @@ function writeDefaultConfig() {
265
405
  _comment: "未找到可用供应商。请在 opencode.json 中配置 provider 后重新运行 install,或手动在此文件中配置 model 和 variant。"
266
406
  };
267
407
  try {
268
- const dir = path2.dirname(COHUB_CONFIG_PATH);
269
- if (!fs.existsSync(dir))
270
- fs.mkdirSync(dir, { recursive: true });
271
- fs.writeFileSync(COHUB_CONFIG_PATH, JSON.stringify(fallbackConfig, null, 2), "utf-8");
408
+ writeJsonFileAtomic(COHUB_CONFIG_PATH, fallbackConfig);
272
409
  return { success: true, message: "✓ 已创建 oh-my-opencode-cohub.json(无可用 provider,请手动配置模型)" };
273
410
  } catch {
274
411
  return { success: false, message: "⚠ 无法创建配置文件" };
@@ -334,7 +471,7 @@ function writeDefaultConfig() {
334
471
  agents,
335
472
  council: councilCount > 0 ? {
336
473
  default_preset: "default",
337
- timeout: 180000,
474
+ timeout: COUNCIL_TIMEOUT_MS,
338
475
  councillor_execution_mode: "parallel",
339
476
  councillor_retries: 3,
340
477
  presets: {
@@ -343,10 +480,7 @@ function writeDefaultConfig() {
343
480
  } : { presets: {} }
344
481
  };
345
482
  try {
346
- const dir = path2.dirname(COHUB_CONFIG_PATH);
347
- if (!fs.existsSync(dir))
348
- fs.mkdirSync(dir, { recursive: true });
349
- fs.writeFileSync(COHUB_CONFIG_PATH, JSON.stringify(defaultConfig, null, 2), "utf-8");
483
+ writeJsonFileAtomic(COHUB_CONFIG_PATH, defaultConfig);
350
484
  return { success: true, message: `✓ 已智能匹配 ${matched}/12 个代理的模型,生成 oh-my-opencode-cohub.json` };
351
485
  } catch {
352
486
  return { success: false, message: "⚠ 无法创建配置文件" };
@@ -355,8 +489,9 @@ function writeDefaultConfig() {
355
489
  function uninstallCoHub() {
356
490
  const messages = [];
357
491
  const configPath = getOpencodeConfigPath();
358
- let config = readJSON(configPath);
359
- if (config) {
492
+ let config = readJSONLoose(configPath);
493
+ const configReadable = readJsonFile(configPath).ok;
494
+ if (config && configReadable) {
360
495
  const plugins = Array.isArray(config.plugin) ? [...config.plugin] : [];
361
496
  const before = plugins.length;
362
497
  const filtered = plugins.filter((p) => {
@@ -386,13 +521,16 @@ function uninstallCoHub() {
386
521
  }
387
522
  }
388
523
  writeJSON(configPath, config);
524
+ } else if (!configReadable) {
525
+ messages.push(`⚠ opencode.json 解析失败,已跳过(原文件未被覆盖): ${configPath}`);
389
526
  } else {
390
527
  messages.push("⚠ opencode.json 不存在,跳过");
391
528
  }
392
529
  const tuiPath = getTuiConfigPath();
393
- if (fs.existsSync(tuiPath)) {
394
- const tuiConfig = readJSON(tuiPath);
395
- if (tuiConfig) {
530
+ if (fs2.existsSync(tuiPath)) {
531
+ const tuiReadable = readJsonFile(tuiPath).ok;
532
+ const tuiConfig = readJSONLoose(tuiPath);
533
+ if (tuiConfig && tuiReadable) {
396
534
  const tuiPlugins = Array.isArray(tuiConfig.plugin) ? [...tuiConfig.plugin] : [];
397
535
  const before = tuiPlugins.length;
398
536
  const filtered = tuiPlugins.filter((p) => {
@@ -406,12 +544,15 @@ function uninstallCoHub() {
406
544
  writeJSON(tuiPath, tuiConfig);
407
545
  messages.push("✓ 已从 tui.json 的 plugin 数组移除 CoHub");
408
546
  }
547
+ } else if (!tuiReadable) {
548
+ messages.push(`⚠ tui.json 解析失败,已跳过(原文件未被覆盖): ${tuiPath}`);
409
549
  }
410
550
  }
411
- const tuiStatePath = path2.join(os2.homedir(), ".local", "share", "opencode", "storage", "oh-my-opencode-slim", "tui-state.json");
412
- if (fs.existsSync(tuiStatePath)) {
413
- let tuiState = readJSON(tuiStatePath);
414
- if (tuiState) {
551
+ const tuiStatePath = path3.join(os2.homedir(), ".local", "share", "opencode", "storage", "oh-my-opencode-slim", "tui-state.json");
552
+ if (fs2.existsSync(tuiStatePath)) {
553
+ const tuiStateReadable = readJsonFile(tuiStatePath).ok;
554
+ let tuiState = readJSONLoose(tuiStatePath);
555
+ if (tuiState && tuiStateReadable) {
415
556
  let cleaned = false;
416
557
  if (tuiState.agentModels && typeof tuiState.agentModels === "object") {
417
558
  const models = tuiState.agentModels;
@@ -438,9 +579,10 @@ function uninstallCoHub() {
438
579
  }
439
580
  }
440
581
  const ohMyPath = getOhMyConfigPath();
441
- if (fs.existsSync(ohMyPath)) {
442
- let ohMyConfig = readJSON(ohMyPath);
443
- if (ohMyConfig && ohMyConfig.agents && typeof ohMyConfig.agents === "object") {
582
+ if (fs2.existsSync(ohMyPath)) {
583
+ const ohMyReadable = readJsonFile(ohMyPath).ok;
584
+ let ohMyConfig = readJSONLoose(ohMyPath);
585
+ if (ohMyConfig && ohMyReadable && ohMyConfig.agents && typeof ohMyConfig.agents === "object") {
444
586
  let cleanedOhMy = false;
445
587
  const agents = ohMyConfig.agents;
446
588
  for (const key of Object.keys(agents)) {
@@ -455,18 +597,18 @@ function uninstallCoHub() {
455
597
  }
456
598
  }
457
599
  }
458
- if (fs.existsSync(PACKAGES_CACHE)) {
600
+ if (fs2.existsSync(PACKAGES_CACHE)) {
459
601
  try {
460
- fs.rmSync(PACKAGES_CACHE, { recursive: true, force: true });
602
+ fs2.rmSync(PACKAGES_CACHE, { recursive: true, force: true });
461
603
  messages.push("✓ 已清理缓存目录");
462
604
  } catch {
463
605
  messages.push("⚠ 清理缓存目录失败,请手动删除: " + PACKAGES_CACHE);
464
606
  }
465
607
  }
466
- const legacyPluginsDir = path2.join(os2.homedir(), ".config", "opencode", "plugins", PACKAGE_NAME);
467
- if (fs.existsSync(legacyPluginsDir)) {
608
+ const legacyPluginsDir = path3.join(os2.homedir(), ".config", "opencode", "plugins", PACKAGE_NAME);
609
+ if (fs2.existsSync(legacyPluginsDir)) {
468
610
  try {
469
- fs.rmSync(legacyPluginsDir, { recursive: true, force: true });
611
+ fs2.rmSync(legacyPluginsDir, { recursive: true, force: true });
470
612
  messages.push("✓ 已清理旧 plugins 目录");
471
613
  } catch {}
472
614
  }
@@ -476,17 +618,17 @@ function uninstallCoHub() {
476
618
  function installToCacheDir(version) {
477
619
  try {
478
620
  const targetDir = PACKAGES_CACHE;
479
- if (fs.existsSync(targetDir)) {
480
- fs.rmSync(targetDir, { recursive: true, force: true });
621
+ if (fs2.existsSync(targetDir)) {
622
+ fs2.rmSync(targetDir, { recursive: true, force: true });
481
623
  }
482
- fs.mkdirSync(targetDir, { recursive: true });
624
+ fs2.mkdirSync(targetDir, { recursive: true });
483
625
  const pkgJson = { private: true };
484
- fs.writeFileSync(path2.join(targetDir, "package.json"), JSON.stringify(pkgJson, null, 2), "utf-8");
626
+ writeJsonFileAtomic(path3.join(targetDir, "package.json"), pkgJson);
485
627
  const pkgSpec = `${PACKAGE_NAME}@${version}`;
486
628
  const cmd = `npm install "${pkgSpec}" --save --legacy-peer-deps`;
487
629
  execSync(cmd, { cwd: targetDir, stdio: "pipe", timeout: 120000 });
488
- const entryPath = path2.join(targetDir, "node_modules", PACKAGE_NAME, "dist", "index.js");
489
- if (!fs.existsSync(entryPath)) {
630
+ const entryPath = path3.join(targetDir, "node_modules", PACKAGE_NAME, "dist", "index.js");
631
+ if (!fs2.existsSync(entryPath)) {
490
632
  return { success: false, message: `⚠ npm install 成功但入口文件缺失: ${entryPath}` };
491
633
  }
492
634
  return { success: true, message: `✓ 已安装 ${pkgSpec} 到 ${targetDir}` };
@@ -669,8 +811,8 @@ function printStats(window) {
669
811
  `);
670
812
  let records = [];
671
813
  try {
672
- if (fs2.existsSync(STATS_FILE)) {
673
- const raw = JSON.parse(fs2.readFileSync(STATS_FILE, "utf8"));
814
+ if (fs3.existsSync(STATS_FILE)) {
815
+ const raw = JSON.parse(fs3.readFileSync(STATS_FILE, "utf8"));
674
816
  records = computeStats(raw, window);
675
817
  }
676
818
  } catch {
@@ -3,6 +3,8 @@ import type { QualityConfig } from '../task-manager/quality';
3
3
  import { DEFAULT_CONTEXT_CONFIG } from '../context/types';
4
4
  import type { CouncilConfig, CouncilPreset, CouncillorConfig } from '../tools/council';
5
5
  export type { CouncilConfig, CouncilPreset, CouncillorConfig };
6
+ import type { CtxDiagConfig } from '../context/diag-log';
7
+ export type { CtxDiagConfig };
6
8
  /** 用户配置中单个代理的覆盖项 */
7
9
  export interface AgentOverride {
8
10
  model?: string;
@@ -15,6 +17,8 @@ export interface CoHubConfig {
15
17
  context?: Partial<ContextConfig>;
16
18
  /** 质量回送配置(P0-1 负反馈闭环,默认开启) */
17
19
  quality?: Partial<QualityConfig>;
20
+ /** 诊断日志配置(默认全部关闭:ctx-diag opt-in;新增字段,旧配置缺省即安全) */
21
+ diag?: CtxDiagConfig;
18
22
  }
19
23
  /** 加载用户配置(项目级优先覆盖用户级) */
20
24
  export declare function loadCoHubConfig(projectDir?: string): CoHubConfig;
@@ -1 +1 @@
1
- {"version":3,"file":"loader.d.ts","sourceRoot":"","sources":["../../src/config/loader.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACtD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAC7D,OAAO,EAAE,sBAAsB,EAAE,MAAM,kBAAkB,CAAC;AAI1D,OAAO,KAAK,EAAE,aAAa,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AACvF,YAAY,EAAE,aAAa,EAAE,aAAa,EAAE,gBAAgB,EAAE,CAAC;AAE/D,oBAAoB;AACpB,MAAM,WAAW,aAAa;IAC5B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,WAAW;IAC1B,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;IACvC,OAAO,CAAC,EAAE,aAAa,CAAC;IACxB,OAAO,CAAC,EAAE,OAAO,CAAC,aAAa,CAAC,CAAC;IACjC,8BAA8B;IAC9B,OAAO,CAAC,EAAE,OAAO,CAAC,aAAa,CAAC,CAAC;CAClC;AAKD,yBAAyB;AACzB,wBAAgB,eAAe,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,WAAW,CAuBhE;AAYD,OAAO,EAAE,sBAAsB,EAAE,CAAC"}
1
+ {"version":3,"file":"loader.d.ts","sourceRoot":"","sources":["../../src/config/loader.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACtD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAC7D,OAAO,EAAE,sBAAsB,EAAE,MAAM,kBAAkB,CAAC;AAI1D,OAAO,KAAK,EAAE,aAAa,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AACvF,YAAY,EAAE,aAAa,EAAE,aAAa,EAAE,gBAAgB,EAAE,CAAC;AAC/D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACzD,YAAY,EAAE,aAAa,EAAE,CAAC;AAE9B,oBAAoB;AACpB,MAAM,WAAW,aAAa;IAC5B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,WAAW;IAC1B,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;IACvC,OAAO,CAAC,EAAE,aAAa,CAAC;IACxB,OAAO,CAAC,EAAE,OAAO,CAAC,aAAa,CAAC,CAAC;IACjC,8BAA8B;IAC9B,OAAO,CAAC,EAAE,OAAO,CAAC,aAAa,CAAC,CAAC;IACjC,mDAAmD;IACnD,IAAI,CAAC,EAAE,aAAa,CAAC;CACtB;AAKD,yBAAyB;AACzB,wBAAgB,eAAe,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,WAAW,CA0BhE;AAYD,OAAO,EAAE,sBAAsB,EAAE,CAAC"}
@@ -0,0 +1,62 @@
1
+ /**
2
+ * 上下文注入诊断日志(opt-in,**默认关闭**)。
3
+ *
4
+ * 用途:排查「CoHub 注入的上下文是否真的到达了子代理」。
5
+ * 历史(见 CHANGELOG 1.4.0 / 1.5.1):曾连续出现「上下文未注入 / 详情未追加 /
6
+ * 写入 description 而非 prompt」等 bug,该日志就是当时的排查手段,因此**保留字段与
7
+ * 默认路径不变**,只把「每次委派无条件同步写盘」改成「显式开启后才写」。
8
+ *
9
+ * 开启方式(任一即可):
10
+ * 1. 环境变量 `COHUB_CTX_DIAG=1`(`=0` 可强制关闭,优先级最高)
11
+ * 2. 用户配置 `~/.config/opencode/oh-my-opencode-cohub.json` → `{ "diag": { "enabled": true } }`
12
+ * 或项目配置 `.opencode/oh-my-opencode-cohub.json` → 同上
13
+ *
14
+ * 设计要点(对比旧实现):
15
+ * - 旧:每次委派 = statSync + appendFileSync(同步阻塞事件循环),
16
+ * 且「超 50KB → 读全文 → 重写末 30 行 → 立刻又 append」形成**反复全量读+全量写**的循环。
17
+ * - 新:关闭时不产生任何 I/O;开启时用**异步串行队列** appendFile(不阻塞事件循环),
18
+ * 仅在「自上次压缩以来累计写入 > MAX_BYTES」时触发一次压缩,且压缩**只读文件尾部窗口**
19
+ * (不读全文),压缩后计数器归零 → 需要再累积 MAX_BYTES 才会再次压缩,循环被消除。
20
+ * - 文件创建时使用 mode 0o600(POSIX 收紧权限;Windows 上为 no-op)。
21
+ */
22
+ /** 诊断日志配置(全部可选,缺省即安全) */
23
+ export interface CtxDiagConfig {
24
+ /** 是否开启上下文注入诊断日志(默认 false) */
25
+ enabled?: boolean;
26
+ /** 自定义日志文件路径(默认 os.tmpdir()/opencode/ctx-diag.log,保持历史路径不变) */
27
+ path?: string;
28
+ }
29
+ /** 缺省配置:关闭。热路径零开销是缺省行为。 */
30
+ export declare const DEFAULT_CTX_DIAG_CONFIG: Required<Omit<CtxDiagConfig, 'path'>>;
31
+ /** 解析后的诊断设置(在插件初始化时求值一次,热路径只读常量) */
32
+ export interface CtxDiagSettings {
33
+ enabled: boolean;
34
+ filePath: string;
35
+ }
36
+ /**
37
+ * 是否开启诊断日志。
38
+ * 优先级:环境变量(1/true 开,0/false 关)> 配置 > 缺省(关)。
39
+ */
40
+ export declare function isCtxDiagEnabled(config?: Partial<CtxDiagConfig> | null, env?: string): boolean;
41
+ /** 诊断日志文件路径(默认保持历史 tmpdir 路径,可被配置覆盖) */
42
+ export declare function resolveCtxDiagPath(config?: Partial<CtxDiagConfig> | null): string;
43
+ /** 一次性解析设置(插件初始化时调用,避免热路径重复读 process.env) */
44
+ export declare function resolveCtxDiagSettings(config?: Partial<CtxDiagConfig> | null, env?: NodeJS.ProcessEnv): CtxDiagSettings;
45
+ /**
46
+ * 追加一条诊断记录(异步,失败静默)。
47
+ * 调用方应先判断 `settings.enabled`;本函数自身不做开关判断,便于复用。
48
+ */
49
+ export declare function appendCtxDiag(record: unknown, filePath: string): void;
50
+ /** 测试/诊断辅助:等待队列落盘 */
51
+ export declare function flushCtxDiag(): Promise<void>;
52
+ /** 测试辅助:重置进程内计数器 */
53
+ export declare function __resetCtxDiagState(): void;
54
+ /** 供测试断言用的常量导出 */
55
+ export declare const CTX_DIAG_LIMITS: {
56
+ readonly MAX_BYTES: number;
57
+ readonly KEEP_LINES: 30;
58
+ readonly TAIL_WINDOW: number;
59
+ };
60
+ /** 诊断日志目录(供排查指引使用) */
61
+ export declare function ctxDiagDir(filePath?: string): string;
62
+ //# sourceMappingURL=diag-log.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"diag-log.d.ts","sourceRoot":"","sources":["../../src/context/diag-log.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAeH,yBAAyB;AACzB,MAAM,WAAW,aAAa;IAC5B,8BAA8B;IAC9B,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,+DAA+D;IAC/D,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,2BAA2B;AAC3B,eAAO,MAAM,uBAAuB,EAAE,QAAQ,CAAC,IAAI,CAAC,aAAa,EAAE,MAAM,CAAC,CAEzE,CAAC;AAEF,oCAAoC;AACpC,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC,aAAa,CAAC,GAAG,IAAI,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,OAAO,CAQ9F;AAED,wCAAwC;AACxC,wBAAgB,kBAAkB,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC,aAAa,CAAC,GAAG,IAAI,GAAG,MAAM,CAIjF;AAED,6CAA6C;AAC7C,wBAAgB,sBAAsB,CACpC,MAAM,CAAC,EAAE,OAAO,CAAC,aAAa,CAAC,GAAG,IAAI,EACtC,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,GACtB,eAAe,CAKjB;AA8CD;;;GAGG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,CAgBrE;AAED,qBAAqB;AACrB,wBAAgB,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC,CAE5C;AAED,oBAAoB;AACpB,wBAAgB,mBAAmB,IAAI,IAAI,CAG1C;AAED,kBAAkB;AAClB,eAAO,MAAM,eAAe;;;;CAAkD,CAAC;AAE/E,sBAAsB;AACtB,wBAAgB,UAAU,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,CAEpD"}
@@ -4,6 +4,14 @@ type SdkClient = ReturnType<typeof createOpencodeClient>;
4
4
  export declare class ContextEngine {
5
5
  /** contextId → TaskContext */
6
6
  private registry;
7
+ /**
8
+ * contextId → 最后一次被读写的时间戳(ms)。
9
+ * registry 的唯二读取者是 `formatContextDetails()`(index.ts 注入详情)与
10
+ * `consumeMarkedContext()`(消息标记替换);两者都会刷新本时间戳。
11
+ * 定时清理据此判断条目是否已「过期未被合法消费」——
12
+ * 时间戳只在读写时更新,因此不会误删仍在被后续读取的条目。
13
+ */
14
+ private registryTouchedAt;
7
15
  /** alias → 前置子代理结果 */
8
16
  private dependencyCache;
9
17
  /**
@@ -70,6 +78,31 @@ export declare class ContextEngine {
70
78
  * 清理过期的依赖缓存(超过 10 分钟的条目)。
71
79
  */
72
80
  cleanupStaleDependencies(maxAgeMs?: number): void;
81
+ /** 刷新 registry 条目的「最后读写时间」(内部使用) */
82
+ private touch;
83
+ /**
84
+ * 显式释放一个已消费完的上下文(供调用方在注入完成后主动回收)。
85
+ * 未注册的 contextId 为无害 no-op。
86
+ */
87
+ releaseContext(contextId: string): void;
88
+ /**
89
+ * 清理长期未被读写的 registry 条目(默认 20 分钟)。
90
+ *
91
+ * 背景:`registry` 的唯一删除点原本在 `consumeMarkedContext()` 内,而该方法全仓库零调用
92
+ * (真实注入路径是 `formatContextDetails()`),于是每次委派都新增一条 TaskContext 且永不回收。
93
+ *
94
+ * 为何用「最后读写时间」而非 `createdAt`:TaskContext 类型上没有任何时间字段
95
+ * (见 `context/types.ts`),故另开 `registryTouchedAt` 侧表记录;且清理**不能早于消费**——
96
+ * 时间戳在 `fillContextAsync` 结束时刷新、并在每次 `formatContextDetails()` / `consumeMarkedContext()`
97
+ * 读取时刷新,因此仍会被读取的条目不会因登记时间久远而被误删。
98
+ * 阈值取 20 分钟(两倍于依赖缓存 TTL):一次委派从登记到注入详情是秒级,
99
+ * 20 分钟已远超任何合法读写窗口,仅回收真正的泄漏。
100
+ *
101
+ * @returns 被回收的条目数
102
+ */
103
+ cleanupStaleContexts(maxAgeMs?: number): number;
104
+ /** 当前 registry 条目数(诊断/测试用,只读) */
105
+ get registrySize(): number;
73
106
  }
74
107
  export {};
75
108
  //# sourceMappingURL=engine.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../../src/context/engine.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,eAAe,EAAe,aAAa,EAAkC,MAAM,SAAS,CAAC;AAK3G,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAE7D,KAAK,SAAS,GAAG,UAAU,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAEzD,qBAAa,aAAa;IACxB,8BAA8B;IAC9B,OAAO,CAAC,QAAQ,CAAkC;IAClD,sBAAsB;IACtB,OAAO,CAAC,eAAe,CAAuC;IAC9D;;;;OAIG;IACH,OAAO,CAAC,QAAQ,CAAgG;IAChH,OAAO,CAAC,MAAM,CAAY;IAC1B,OAAO,CAAC,MAAM,CAAgB;gBAElB,MAAM,EAAE,SAAS,EAAE,MAAM,CAAC,EAAE,OAAO,CAAC,aAAa,CAAC;IAU9D;;;OAGG;IACH,eAAe,CAAC,IAAI,EAAE;QAAE,WAAW,EAAE,MAAM,CAAA;KAAE,GAAG,MAAM;IAYtD;;OAEG;IACG,gBAAgB,CACpB,SAAS,EAAE,MAAM,EACjB,eAAe,EAAE,MAAM,EACvB,IAAI,EAAE;QAAE,QAAQ,EAAE,eAAe,CAAA;KAAE,GAClC,OAAO,CAAC,IAAI,CAAC;IA0DhB;;;OAGG;IACH,OAAO,CAAC,qBAAqB;IAa7B;;;;OAIG;IACH,oBAAoB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;IAiBxD;;;OAGG;IACH,oBAAoB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM;IAkE/C;;;;;OAKG;IACG,aAAa,CACjB,cAAc,EAAE,MAAM,EACtB,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE;YAAE,KAAK,EAAE,MAAM,CAAC;YAAC,MAAM,EAAE,MAAM,CAAA;SAAE,CAAA;KAAE,GAAG,IAAI,CAAC;IAqDpG;;OAEG;IACH,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM;IAIvC;;OAEG;IACH,WAAW,CAAC,SAAS,EAAE,MAAM,GAAG,eAAe;IAI/C;;OAEG;IACH,wBAAwB,CAAC,QAAQ,GAAE,MAAuB,GAAG,IAAI;CAQlE"}
1
+ {"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../../src/context/engine.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,eAAe,EAAe,aAAa,EAAkC,MAAM,SAAS,CAAC;AAK3G,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAE7D,KAAK,SAAS,GAAG,UAAU,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAEzD,qBAAa,aAAa;IACxB,8BAA8B;IAC9B,OAAO,CAAC,QAAQ,CAAkC;IAClD;;;;;;OAMG;IACH,OAAO,CAAC,iBAAiB,CAA6B;IACtD,sBAAsB;IACtB,OAAO,CAAC,eAAe,CAAuC;IAC9D;;;;OAIG;IACH,OAAO,CAAC,QAAQ,CAAgG;IAChH,OAAO,CAAC,MAAM,CAAY;IAC1B,OAAO,CAAC,MAAM,CAAgB;gBAElB,MAAM,EAAE,SAAS,EAAE,MAAM,CAAC,EAAE,OAAO,CAAC,aAAa,CAAC;IAU9D;;;OAGG;IACH,eAAe,CAAC,IAAI,EAAE;QAAE,WAAW,EAAE,MAAM,CAAA;KAAE,GAAG,MAAM;IAatD;;OAEG;IACG,gBAAgB,CACpB,SAAS,EAAE,MAAM,EACjB,eAAe,EAAE,MAAM,EACvB,IAAI,EAAE;QAAE,QAAQ,EAAE,eAAe,CAAA;KAAE,GAClC,OAAO,CAAC,IAAI,CAAC;IA8DhB;;;OAGG;IACH,OAAO,CAAC,qBAAqB;IAa7B;;;;OAIG;IACH,oBAAoB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;IAmBxD;;;OAGG;IACH,oBAAoB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM;IAmE/C;;;;;OAKG;IACG,aAAa,CACjB,cAAc,EAAE,MAAM,EACtB,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE;YAAE,KAAK,EAAE,MAAM,CAAC;YAAC,MAAM,EAAE,MAAM,CAAA;SAAE,CAAA;KAAE,GAAG,IAAI,CAAC;IAqDpG;;OAEG;IACH,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM;IAIvC;;OAEG;IACH,WAAW,CAAC,SAAS,EAAE,MAAM,GAAG,eAAe;IAI/C;;OAEG;IACH,wBAAwB,CAAC,QAAQ,GAAE,MAAuB,GAAG,IAAI;IASjE,oCAAoC;IACpC,OAAO,CAAC,KAAK;IAIb;;;OAGG;IACH,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI;IAKvC;;;;;;;;;;;;;;OAcG;IACH,oBAAoB,CAAC,QAAQ,GAAE,MAAuB,GAAG,MAAM;IAmB/D,iCAAiC;IACjC,IAAI,YAAY,IAAI,MAAM,CAEzB;CACF"}
@@ -32,24 +32,5 @@ export declare function extractDecisions(messages: SdkMessage[], maxDecisions: n
32
32
  * 从 bash 输出中提取编译/测试错误。
33
33
  */
34
34
  export declare function extractErrors(messages: SdkMessage[], maxErrors: number, windowSize: number): string[];
35
- /**
36
- * 估算文本的 token 数。
37
- * 中文字符(CJK 及东亚表意区)按 1 token/字符,其余字符按 0.25 token/字符。
38
- */
39
- export declare function estimateTokens(text: string): number;
40
- /**
41
- * 强制 prompt 预算约束。
42
- * 若 full 的估算 token 数 ≤ maxTokens → 返回 full(完整保留);
43
- * 否则 → 返回 base(丢弃全部 CoHub 注入后缀,只保留用户原始 prompt)。
44
- */
45
- export declare function enforcePromptBudget(base: string, full: string, maxTokens: number): string;
46
- /**
47
- * 按 token 预算截断文本。
48
- * P2-5: 估算规则区分中西文——中文字符(CJK 及东亚表意区)按 1 token/字符,
49
- * 其余字符按 4 字符 ≈ 1 token(原统一 4 字符/token 对中文低估 2-4 倍)。
50
- * 仅用于 summary 策略下文件正文裁剪;空文本或 maxTokens<=0 返回空串;
51
- * 未超预算时原样返回,超预算时保留头部并在末尾追加省略标记。
52
- */
53
- export declare function truncateByTokens(text: string, maxTokens: number): string;
54
- export {};
35
+ export { estimateTokens, enforcePromptBudget, truncateByTokens } from '../shared/token-estimator';
55
36
  //# sourceMappingURL=extractor.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"extractor.d.ts","sourceRoot":"","sources":["../../src/context/extractor.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAuB5C,yBAAyB;AACzB,UAAU,OAAO;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE;QACN,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QAChC,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,KAAK,CAAC,EAAE,MAAM,CAAC;KAChB,CAAC;CACH;AAED,sBAAsB;AACtB,UAAU,UAAU;IAClB,IAAI,CAAC,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACzB,KAAK,CAAC,EAAE,OAAO,EAAE,CAAC;CACnB;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAClC,QAAQ,EAAE,UAAU,EAAE,EACtB,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,GACjB,YAAY,EAAE,CAmGhB;AAUD;;;GAGG;AACH,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,UAAU,EAAE,EACtB,YAAY,EAAE,MAAM,EACpB,UAAU,EAAE,MAAM,GACjB,MAAM,EAAE,CAqBV;AAED;;GAEG;AACH,wBAAgB,aAAa,CAC3B,QAAQ,EAAE,UAAU,EAAE,EACtB,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,GACjB,MAAM,EAAE,CAwBV;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAanD;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAGzF;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAqBxE"}
1
+ {"version":3,"file":"extractor.d.ts","sourceRoot":"","sources":["../../src/context/extractor.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAuB5C,yBAAyB;AACzB,UAAU,OAAO;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE;QACN,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QAChC,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,KAAK,CAAC,EAAE,MAAM,CAAC;KAChB,CAAC;CACH;AAED,sBAAsB;AACtB,UAAU,UAAU;IAClB,IAAI,CAAC,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACzB,KAAK,CAAC,EAAE,OAAO,EAAE,CAAC;CACnB;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAClC,QAAQ,EAAE,UAAU,EAAE,EACtB,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,GACjB,YAAY,EAAE,CAmGhB;AAUD;;;GAGG;AACH,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,UAAU,EAAE,EACtB,YAAY,EAAE,MAAM,EACpB,UAAU,EAAE,MAAM,GACjB,MAAM,EAAE,CAqBV;AAED;;GAEG;AACH,wBAAgB,aAAa,CAC3B,QAAQ,EAAE,UAAU,EAAE,EACtB,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,GACjB,MAAM,EAAE,CAwBV;AAID,OAAO,EAAE,cAAc,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC"}