@andyqiu/codeforge 0.5.20 → 0.5.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/dist/index.js +655 -1616
  2. package/install.sh +15 -0
  3. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -8047,7 +8047,7 @@ async function runAutoFeedback(cfg, ctx) {
8047
8047
  if (cmds.length === 0) {
8048
8048
  throw new Error("auto-feedback: 至少需要 test_cmd 或 lint_cmd 之一");
8049
8049
  }
8050
- const log14 = ctx.log ?? (() => {});
8050
+ const log13 = ctx.log ?? (() => {});
8051
8051
  const attempt = async () => {
8052
8052
  let last = {
8053
8053
  cmd: "",
@@ -8084,7 +8084,7 @@ ${excerpt}
8084
8084
 
8085
8085
  请基于错误信息修复 pending-changes(用 ast-edit / pending-changes.stage),完成后我会重新跑测试验证。`;
8086
8086
  const result2 = await ctx.dispatchToAgent("coder", prompt);
8087
- log14("info", `auto-feedback round ${round} dispatch summary: ${result2.summary.slice(0, 200)}`);
8087
+ log13("info", `auto-feedback round ${round} dispatch summary: ${result2.summary.slice(0, 200)}`);
8088
8088
  };
8089
8089
  const debugResult = await runWithAutoDebug({
8090
8090
  attempt,
@@ -8105,7 +8105,7 @@ ${excerpt}
8105
8105
  history: debugResult.history
8106
8106
  };
8107
8107
  if (!debugResult.ok && debugResult.stopReason === "max-rounds") {
8108
- log14("warn", `auto-feedback 达 max_retries=${cfg.max_retries},escalate to ${cfg.escalate_to}`);
8108
+ log13("warn", `auto-feedback 达 max_retries=${cfg.max_retries},escalate to ${cfg.escalate_to}`);
8109
8109
  const lastFail = debugResult.history[debugResult.history.length - 1];
8110
8110
  const excerpt = lastFail?.result ? extractErrorExcerpt(lastFail.result, cfg.error_excerpt_lines) : "(无失败结果)";
8111
8111
  const escalatePrompt = `coder 连续 ${cfg.max_retries} 轮自纠失败,错误片段:
@@ -8825,248 +8825,6 @@ var autoCommitServer = async (ctx) => {
8825
8825
  };
8826
8826
  var handler3 = autoCommitServer;
8827
8827
 
8828
- // plugins/auto-learning.ts
8829
- init_kh_client();
8830
- import * as crypto2 from "node:crypto";
8831
- var PLUGIN_NAME4 = "auto-learning";
8832
- var DEFAULT_CONFIG2 = {
8833
- minScore: 0.5,
8834
- cooldownMs: 24 * 60 * 60 * 1000,
8835
- fingerprintLimit: 256,
8836
- events: ["bug.fixed", "workflow.completed", "session.ended"]
8837
- };
8838
- var NOISE_TITLE_RE = /^\s*(test|wip|tmp|fix typo|update|chore|debug|todo|hello)\b/i;
8839
- var TRIVIAL_KEYWORDS = ["typo", "格式化", "format", "rename", "重命名"];
8840
- function extractInsights(ctx) {
8841
- if (!ctx || typeof ctx !== "object")
8842
- return [];
8843
- const out = [];
8844
- if (ctx.bug && ctx.bug.symptom && ctx.bug.fix) {
8845
- const rootCause = ctx.bug.rootCause?.trim() ?? "";
8846
- const baseScore = rootCause ? 0.85 : 0.55;
8847
- const score = adjustScore(baseScore, ctx);
8848
- const title = clip2(`Bug 修复:${ctx.bug.symptom}`, 80);
8849
- if (!isTrivial(title)) {
8850
- out.push({
8851
- title,
8852
- content: clip2([
8853
- `**症状**:${ctx.bug.symptom}`,
8854
- rootCause ? `**根因**:${rootCause}` : "",
8855
- `**修复**:${ctx.bug.fix}`,
8856
- ctx.bug.files?.length ? `**涉及**:${ctx.bug.files.slice(0, 5).join(", ")}` : ""
8857
- ].filter(Boolean).join(`
8858
-
8859
- `), 500),
8860
- kind: "bug-fix",
8861
- category: "踩坑记录",
8862
- tags: ctx.bug.files?.slice(0, 5),
8863
- score
8864
- });
8865
- }
8866
- }
8867
- if (ctx.workflow?.learnings?.length) {
8868
- for (const lesson of ctx.workflow.learnings) {
8869
- const text = lesson.trim();
8870
- if (!text || isTrivial(text))
8871
- continue;
8872
- const score = adjustScore(0.7, ctx);
8873
- out.push({
8874
- title: clip2(`${ctx.workflow.name ?? "workflow"}:${text}`, 80),
8875
- content: clip2(text, 500),
8876
- kind: "decision",
8877
- category: "操作指南",
8878
- tags: ctx.workflow.name ? [ctx.workflow.name] : undefined,
8879
- score
8880
- });
8881
- }
8882
- }
8883
- if (ctx.summary && ctx.positiveFeedback && !ctx.rolledBack) {
8884
- const text = ctx.summary.trim();
8885
- if (text && !isTrivial(text)) {
8886
- out.push({
8887
- title: clip2(text.split(/[。.\n]/)[0] || "会话洞察", 80),
8888
- content: clip2(text, 500),
8889
- kind: "decision",
8890
- category: "其它",
8891
- score: adjustScore(0.55, ctx)
8892
- });
8893
- }
8894
- }
8895
- for (const i of out)
8896
- i.fingerprint = fingerprint(i);
8897
- return out.filter((i) => i.score > 0);
8898
- }
8899
- function adjustScore(base, ctx) {
8900
- let s = base;
8901
- if (ctx.positiveFeedback)
8902
- s += 0.1;
8903
- if (ctx.rolledBack)
8904
- s -= 0.5;
8905
- return Math.max(-1, Math.min(1, s));
8906
- }
8907
- function isTrivial(text) {
8908
- if (!text)
8909
- return true;
8910
- if (NOISE_TITLE_RE.test(text))
8911
- return true;
8912
- const lower = text.toLowerCase();
8913
- return TRIVIAL_KEYWORDS.some((k) => lower.includes(k));
8914
- }
8915
- function clip2(s, max) {
8916
- if (!s)
8917
- return "";
8918
- if (s.length <= max)
8919
- return s;
8920
- return s.slice(0, max - 1) + "…";
8921
- }
8922
- function fingerprint(i) {
8923
- const seed = `${i.kind}|${i.title}|${i.content}`;
8924
- return crypto2.createHash("sha1").update(seed).digest("hex").slice(0, 16);
8925
- }
8926
-
8927
- class FingerprintLRU {
8928
- limit;
8929
- list = [];
8930
- constructor(limit = 256) {
8931
- this.limit = limit;
8932
- }
8933
- has(fp, withinMs, now = Date.now()) {
8934
- const e = this.list.find((x) => x.fp === fp);
8935
- if (!e)
8936
- return false;
8937
- return now - e.ts < withinMs;
8938
- }
8939
- add(fp, now = Date.now()) {
8940
- this.list = this.list.filter((x) => x.fp !== fp);
8941
- this.list.unshift({ fp, ts: now });
8942
- if (this.list.length > this.limit)
8943
- this.list.length = this.limit;
8944
- }
8945
- size() {
8946
- return this.list.length;
8947
- }
8948
- }
8949
- function shouldSave(insight, lru, cfg, now = Date.now()) {
8950
- if (insight.score < cfg.minScore) {
8951
- return { decision: "skip", reason: `score ${insight.score.toFixed(2)} < ${cfg.minScore}`, insight };
8952
- }
8953
- if (insight.fingerprint && lru.has(insight.fingerprint, cfg.cooldownMs, now)) {
8954
- return { decision: "skip", reason: "duplicate within cooldown", insight };
8955
- }
8956
- return { decision: "save", reason: "ok", insight };
8957
- }
8958
- var moduleLRU = new FingerprintLRU(DEFAULT_CONFIG2.fingerprintLimit);
8959
- async function processEvent(raw, opts = {}) {
8960
- const cfg = opts.config ?? DEFAULT_CONFIG2;
8961
- const lru = opts.lru ?? moduleLRU;
8962
- const now = opts.now ?? Date.now();
8963
- const ctx = raw ?? {};
8964
- const insights = extractInsights(ctx);
8965
- const decisions = insights.map((i) => shouldSave(i, lru, cfg, now));
8966
- let saved = 0;
8967
- let skipped = 0;
8968
- for (const d of decisions) {
8969
- if (d.decision === "skip") {
8970
- skipped++;
8971
- continue;
8972
- }
8973
- if (typeof opts.saveInsight !== "function") {
8974
- if (d.insight.fingerprint)
8975
- lru.add(d.insight.fingerprint, now);
8976
- skipped++;
8977
- continue;
8978
- }
8979
- try {
8980
- const res = await opts.saveInsight({
8981
- title: d.insight.title,
8982
- content: d.insight.content,
8983
- category: d.insight.category,
8984
- tags: d.insight.tags
8985
- });
8986
- if (res?.ok) {
8987
- if (d.insight.fingerprint)
8988
- lru.add(d.insight.fingerprint, now);
8989
- saved++;
8990
- opts.log?.info(`[${PLUGIN_NAME4}] saved insight: ${d.insight.title}`, { id: res.id });
8991
- if (typeof opts.onSaved === "function") {
8992
- try {
8993
- await opts.onSaved(d.insight, { id: res.id });
8994
- } catch (err) {
8995
- opts.log?.warn(`[${PLUGIN_NAME4}] onSaved hook 抛错(已隔离)`, {
8996
- error: err instanceof Error ? err.message : String(err)
8997
- });
8998
- }
8999
- }
9000
- } else {
9001
- skipped++;
9002
- opts.log?.warn(`[${PLUGIN_NAME4}] save failed: ${res?.error ?? "unknown"}`);
9003
- }
9004
- } catch (err) {
9005
- skipped++;
9006
- opts.log?.warn(`[${PLUGIN_NAME4}] save threw(已隔离)`, {
9007
- error: err instanceof Error ? err.message : String(err)
9008
- });
9009
- }
9010
- }
9011
- return { saved, skipped, decisions };
9012
- }
9013
- function createKhSaveInsightHook(client, log3) {
9014
- if (!client.hasTransport()) {
9015
- log3?.debug?.(`[${PLUGIN_NAME4}] createKhSaveInsightHook: transport 不可用,返 undefined(走 noop)`, {});
9016
- return;
9017
- }
9018
- return async ({ title, content, category, tags }) => {
9019
- const insight = `**${title}**
9020
-
9021
- ${content}`;
9022
- try {
9023
- const r = await client.save({ insight, category, tags });
9024
- if (r.ok) {
9025
- return { ok: true, id: r.id };
9026
- }
9027
- return { ok: false, error: `${r.reason}: ${r.message}` };
9028
- } catch (err) {
9029
- const message = err instanceof Error ? err.message : String(err);
9030
- log3?.warn(`[${PLUGIN_NAME4}] saveInsight hook 抛异常(已隔离)`, { error: message });
9031
- return { ok: false, error: message };
9032
- }
9033
- };
9034
- }
9035
- logLifecycle(PLUGIN_NAME4, "import");
9036
- var TRIGGER_EVENT_TYPES2 = new Set([
9037
- "session.idle",
9038
- "session.error"
9039
- ]);
9040
- var sharedClient = new KhClient;
9041
- var autoLearningServer = async (ctx) => {
9042
- const log3 = makePluginLogger(PLUGIN_NAME4);
9043
- const saveInsight = createKhSaveInsightHook(sharedClient, log3);
9044
- logLifecycle(PLUGIN_NAME4, "activate", {
9045
- directory: ctx.directory,
9046
- triggerEventTypes: [...TRIGGER_EVENT_TYPES2],
9047
- minScore: DEFAULT_CONFIG2.minScore,
9048
- transport_available: sharedClient.hasTransport(),
9049
- save_insight_wired: typeof saveInsight === "function"
9050
- });
9051
- return {
9052
- event: async ({ event }) => {
9053
- await safeAsync(PLUGIN_NAME4, "event", async () => {
9054
- const e = event;
9055
- if (!e.type || !TRIGGER_EVENT_TYPES2.has(e.type))
9056
- return;
9057
- const r = await processEvent({
9058
- type: e.type,
9059
- ...e.properties && typeof e.properties === "object" ? e.properties : {}
9060
- }, { log: log3, saveInsight });
9061
- if (r.saved > 0 || r.skipped > 0) {
9062
- log3.info(`event ${e.type}: saved=${r.saved} skipped=${r.skipped}`);
9063
- }
9064
- });
9065
- }
9066
- };
9067
- };
9068
- var handler4 = autoLearningServer;
9069
-
9070
8828
  // plugins/channels.ts
9071
8829
  import { promises as fs2, statSync as statSync2 } from "node:fs";
9072
8830
  import * as path4 from "node:path";
@@ -9755,9 +9513,9 @@ function makeChannelEvent(event, data = {}) {
9755
9513
 
9756
9514
  // plugins/channels.ts
9757
9515
  init_global_config();
9758
- var PLUGIN_NAME5 = "channels";
9759
- logLifecycle(PLUGIN_NAME5, "import", {});
9760
- var fallbackLog = makePluginLogger(PLUGIN_NAME5);
9516
+ var PLUGIN_NAME4 = "channels";
9517
+ logLifecycle(PLUGIN_NAME4, "import", {});
9518
+ var fallbackLog = makePluginLogger(PLUGIN_NAME4);
9761
9519
  var _channelsCache = null;
9762
9520
  var _activatedDirectory;
9763
9521
  var KNOWN_TYPES = new Set([
@@ -10074,8 +9832,8 @@ function pickSeverity(eventName) {
10074
9832
  return 10;
10075
9833
  return 10;
10076
9834
  }
10077
- var log3 = makePluginLogger(PLUGIN_NAME5);
10078
- var TRIGGER_EVENT_TYPES3 = new Set([
9835
+ var log3 = makePluginLogger(PLUGIN_NAME4);
9836
+ var TRIGGER_EVENT_TYPES2 = new Set([
10079
9837
  "workflow.completed",
10080
9838
  "workflow.failed",
10081
9839
  "approval.required",
@@ -10087,26 +9845,26 @@ var TRIGGER_EVENT_TYPES3 = new Set([
10087
9845
  ]);
10088
9846
  var channelsServer = async (ctx) => {
10089
9847
  _activatedDirectory = ctx.directory;
10090
- logLifecycle(PLUGIN_NAME5, "activate", {
9848
+ logLifecycle(PLUGIN_NAME4, "activate", {
10091
9849
  directory: ctx.directory,
10092
- triggerEventTypes: [...TRIGGER_EVENT_TYPES3],
9850
+ triggerEventTypes: [...TRIGGER_EVENT_TYPES2],
10093
9851
  channelsConfigured: ensureChannels().length
10094
9852
  });
10095
9853
  return {
10096
9854
  event: async ({ event }) => {
10097
- await safeAsync(PLUGIN_NAME5, "event", async () => {
9855
+ await safeAsync(PLUGIN_NAME4, "event", async () => {
10098
9856
  const e = event;
10099
9857
  if (!e || typeof e.type !== "string")
10100
9858
  return;
10101
- if (!TRIGGER_EVENT_TYPES3.has(e.type))
9859
+ if (!TRIGGER_EVENT_TYPES2.has(e.type))
10102
9860
  return;
10103
9861
  const summary = await bridgeEvent(e.type, e.properties ?? {});
10104
9862
  if (summary.results.length > 0) {
10105
- log3.info(`[${PLUGIN_NAME5}] ${e.type} → ${summary.results.length} channels`, {
9863
+ log3.info(`[${PLUGIN_NAME4}] ${e.type} → ${summary.results.length} channels`, {
10106
9864
  any_sent: summary.any_sent,
10107
9865
  rate_limited: summary.rate_limited
10108
9866
  });
10109
- safeWriteLog(PLUGIN_NAME5, {
9867
+ safeWriteLog(PLUGIN_NAME4, {
10110
9868
  hook: "event",
10111
9869
  type: e.type,
10112
9870
  results: summary.results.length,
@@ -10118,7 +9876,7 @@ var channelsServer = async (ctx) => {
10118
9876
  }
10119
9877
  };
10120
9878
  };
10121
- var handler5 = channelsServer;
9879
+ var handler4 = channelsServer;
10122
9880
 
10123
9881
  // lib/agent-resolver.ts
10124
9882
  var chatAgentCacheReader = null;
@@ -10178,8 +9936,8 @@ async function resolveAgentForGuard(input, client, log4, opts = {}) {
10178
9936
  }
10179
9937
 
10180
9938
  // plugins/chat-agent-cache.ts
10181
- var PLUGIN_NAME6 = "chat-agent-cache";
10182
- logLifecycle(PLUGIN_NAME6, "import", {});
9939
+ var PLUGIN_NAME5 = "chat-agent-cache";
9940
+ logLifecycle(PLUGIN_NAME5, "import", {});
10183
9941
  var SESSION_CAP = 500;
10184
9942
  var SESSION_TTL_MS = 24 * 60 * 60 * 1000;
10185
9943
  var sessionAgentMap = new Map;
@@ -10216,14 +9974,14 @@ function readSessionAgent(sessionID) {
10216
9974
  }
10217
9975
  var chatAgentCachePlugin = async (ctx) => {
10218
9976
  setChatAgentCacheReader(readSessionAgent);
10219
- logLifecycle(PLUGIN_NAME6, "activate", {
9977
+ logLifecycle(PLUGIN_NAME5, "activate", {
10220
9978
  directory: ctx.directory,
10221
9979
  session_cap: SESSION_CAP,
10222
9980
  session_ttl_ms: SESSION_TTL_MS
10223
9981
  });
10224
9982
  return {
10225
9983
  "chat.params": async (input, _output) => {
10226
- await safeAsync(PLUGIN_NAME6, "chat.params", async () => {
9984
+ await safeAsync(PLUGIN_NAME5, "chat.params", async () => {
10227
9985
  const sid = input?.sessionID;
10228
9986
  const agent = input?.agent;
10229
9987
  if (typeof sid !== "string" || sid.length === 0)
@@ -10231,7 +9989,7 @@ var chatAgentCachePlugin = async (ctx) => {
10231
9989
  if (typeof agent !== "string" || agent.length === 0)
10232
9990
  return;
10233
9991
  rememberSessionAgent(sid, agent);
10234
- safeWriteLog(PLUGIN_NAME6, {
9992
+ safeWriteLog(PLUGIN_NAME5, {
10235
9993
  hook: "chat.params",
10236
9994
  sessionID: sid,
10237
9995
  agent,
@@ -10241,7 +9999,7 @@ var chatAgentCachePlugin = async (ctx) => {
10241
9999
  });
10242
10000
  },
10243
10001
  "chat.message": async (input, _output) => {
10244
- await safeAsync(PLUGIN_NAME6, "chat.message", async () => {
10002
+ await safeAsync(PLUGIN_NAME5, "chat.message", async () => {
10245
10003
  const sid = input?.sessionID;
10246
10004
  const agent = input?.agent;
10247
10005
  if (typeof sid !== "string" || sid.length === 0)
@@ -10249,7 +10007,7 @@ var chatAgentCachePlugin = async (ctx) => {
10249
10007
  if (typeof agent !== "string" || agent.length === 0)
10250
10008
  return;
10251
10009
  rememberSessionAgent(sid, agent);
10252
- safeWriteLog(PLUGIN_NAME6, {
10010
+ safeWriteLog(PLUGIN_NAME5, {
10253
10011
  hook: "chat.message",
10254
10012
  sessionID: sid,
10255
10013
  agent,
@@ -10260,7 +10018,7 @@ var chatAgentCachePlugin = async (ctx) => {
10260
10018
  }
10261
10019
  };
10262
10020
  };
10263
- var handler6 = chatAgentCachePlugin;
10021
+ var handler5 = chatAgentCachePlugin;
10264
10022
 
10265
10023
  // plugins/codeforge-tools.ts
10266
10024
  import { tool } from "@opencode-ai/plugin";
@@ -10766,10 +10524,10 @@ import * as path5 from "node:path";
10766
10524
  import { z as z16 } from "zod";
10767
10525
 
10768
10526
  // lib/ast-edit-engine.ts
10769
- import * as crypto3 from "node:crypto";
10527
+ import * as crypto2 from "node:crypto";
10770
10528
  var IDENT_RE = /^[A-Za-z_$][\w$]*$/;
10771
10529
  function sha256(s) {
10772
- return crypto3.createHash("sha256").update(s).digest("hex");
10530
+ return crypto2.createHash("sha256").update(s).digest("hex");
10773
10531
  }
10774
10532
  function detectEol(content) {
10775
10533
  let crlf = 0;
@@ -12047,7 +11805,7 @@ import { z as z20 } from "zod";
12047
11805
  // lib/browser-control.ts
12048
11806
  init_runtime_paths();
12049
11807
  import * as path9 from "node:path";
12050
- var DEFAULT_CONFIG3 = {
11808
+ var DEFAULT_CONFIG2 = {
12051
11809
  enabled: false,
12052
11810
  headless: true,
12053
11811
  allow: ["^https?://"],
@@ -12060,7 +11818,7 @@ var DEFAULT_CONFIG3 = {
12060
11818
  function defaultScreenshotDir(root = process.cwd()) {
12061
11819
  return path9.join(runtimeDir(root), "browser", "screenshots");
12062
11820
  }
12063
- function checkUrl(url, cfg = DEFAULT_CONFIG3) {
11821
+ function checkUrl(url, cfg = DEFAULT_CONFIG2) {
12064
11822
  if (typeof url !== "string" || url.trim() === "") {
12065
11823
  return { ok: false, reason: "empty_url" };
12066
11824
  }
@@ -12107,7 +11865,7 @@ class NoopBrowserController {
12107
11865
  }
12108
11866
  async close() {}
12109
11867
  }
12110
- async function tryCreatePlaywrightController(cfg = DEFAULT_CONFIG3, resolver = defaultPlaywrightResolver) {
11868
+ async function tryCreatePlaywrightController(cfg = DEFAULT_CONFIG2, resolver = defaultPlaywrightResolver) {
12111
11869
  if (!cfg.enabled)
12112
11870
  return null;
12113
11871
  let mod;
@@ -12253,7 +12011,7 @@ function describe3(err) {
12253
12011
  }
12254
12012
  }
12255
12013
  async function createBrowserController(opts = {}) {
12256
- const cfg = { ...DEFAULT_CONFIG3, ...opts.cfg };
12014
+ const cfg = { ...DEFAULT_CONFIG2, ...opts.cfg };
12257
12015
  if (!cfg.enabled) {
12258
12016
  return new NoopBrowserController("browser disabled in config");
12259
12017
  }
@@ -13018,7 +12776,7 @@ import * as path13 from "node:path";
13018
12776
 
13019
12777
  // lib/file-lock.ts
13020
12778
  import { promises as fs9 } from "node:fs";
13021
- import * as crypto4 from "node:crypto";
12779
+ import * as crypto3 from "node:crypto";
13022
12780
  import * as os4 from "node:os";
13023
12781
  import * as path12 from "node:path";
13024
12782
  async function withFileLock(lockPath, fn, opts = {}) {
@@ -13028,7 +12786,7 @@ async function withFileLock(lockPath, fn, opts = {}) {
13028
12786
  await fs9.mkdir(path12.dirname(lockPath), { recursive: true });
13029
12787
  const deadline = Date.now() + timeoutMs;
13030
12788
  const myHost = os4.hostname();
13031
- const myToken = crypto4.randomBytes(16).toString("hex");
12789
+ const myToken = crypto3.randomBytes(16).toString("hex");
13032
12790
  let acquired = false;
13033
12791
  while (!acquired) {
13034
12792
  try {
@@ -14786,8 +14544,10 @@ var description29 = [
14786
14544
  "**何时调用**:",
14787
14545
  "- 用户说「给这个项目加 ADR / 装一下 ADR 检查 / 同步一下 ADR 模板」",
14788
14546
  "- 新项目 init 流程中希望把决策记录体系一起带上",
14789
- "**特点**:零 npm 依赖(用 git config core.hooksPath 而非 husky);跟仓库走(提交 .githooks/ 进 git);幂等(默认 skip 已存在文件,--force 自动 .bak.<ts> 备份)",
14790
- "**何时不需要**:当前项目已有完整的 ADR + hooks(默认 skip 行为会保护,但调用本身浪费一次 IO)"
14547
+ "- 已有 ADR 但文件内容不正确时,传 force=true 覆盖修正",
14548
+ "**特点**:零 npm 依赖(用 git config core.hooksPath 而非 husky);跟仓库走(提交 .githooks/ 进 git);幂等(默认 skip 已存在文件,force=true 自动 .bak.<ts> 备份)",
14549
+ "**何时不需要**:当前项目已有完整的 ADR + hooks(默认 skip 行为会保护)",
14550
+ "**\uD83D\uDEAB 失败时严禁绕过**:如果返回 ok=false reason=assets_not_found,必须停止并告知用户重装 codeforge(bash install.sh --global),**绝对不允许手动创建文件替代**——手动创建会产生格式错误的文件(如错误的数字编号前缀),破坏整个项目的 ADR 体系"
14791
14551
  ].join(`
14792
14552
  `);
14793
14553
  var ArgsSchema29 = z30.object({
@@ -14871,7 +14631,7 @@ function makeOpencodeRunner(opts) {
14871
14631
  const created = await opts.client.session.create({
14872
14632
  body: {
14873
14633
  parentID: opts.parentSessionID,
14874
- title: clip3(`subtask:${spec.id}`, 80)
14634
+ title: clip2(`subtask:${spec.id}`, 80)
14875
14635
  },
14876
14636
  query: opts.directory ? { directory: opts.directory } : undefined
14877
14637
  });
@@ -15071,7 +14831,7 @@ function safeStringify(v) {
15071
14831
  return String(v);
15072
14832
  }
15073
14833
  }
15074
- function clip3(s, max) {
14834
+ function clip2(s, max) {
15075
14835
  if (!s)
15076
14836
  return "";
15077
14837
  return s.length <= max ? s : s.slice(0, max - 1) + "…";
@@ -15391,7 +15151,7 @@ ${r.text.slice(0, 800)}`
15391
15151
  let childId;
15392
15152
  try {
15393
15153
  const created = await this.opts.client.session.create({
15394
- body: { title: clip4(opts.title, 80) },
15154
+ body: { title: clip3(opts.title, 80) },
15395
15155
  query: this.opts.directory ? { directory: this.opts.directory } : undefined
15396
15156
  });
15397
15157
  if (created.error || !created.data?.id) {
@@ -15534,7 +15294,7 @@ function describe5(err) {
15534
15294
  return String(err);
15535
15295
  }
15536
15296
  }
15537
- function clip4(s, max) {
15297
+ function clip3(s, max) {
15538
15298
  if (!s)
15539
15299
  return "";
15540
15300
  return s.length <= max ? s : s.slice(0, max - 1) + "…";
@@ -15747,8 +15507,8 @@ function parseRuntime(raw, abs) {
15747
15507
  }
15748
15508
 
15749
15509
  // plugins/tool-heartbeat.ts
15750
- var PLUGIN_NAME7 = "tool-heartbeat";
15751
- logLifecycle(PLUGIN_NAME7, "import", {});
15510
+ var PLUGIN_NAME6 = "tool-heartbeat";
15511
+ logLifecycle(PLUGIN_NAME6, "import", {});
15752
15512
  var HEARTBEAT_INTERVAL_MS = 15000;
15753
15513
  var ALERT_30S_MS = 30000;
15754
15514
  var ALERT_60S_MS = 60000;
@@ -15830,15 +15590,15 @@ async function showToast(client, payload, log5) {
15830
15590
  return false;
15831
15591
  }
15832
15592
  }
15833
- var log5 = makePluginLogger(PLUGIN_NAME7);
15593
+ var log5 = makePluginLogger(PLUGIN_NAME6);
15834
15594
  var toolHeartbeatServer = async (ctx) => {
15835
- logLifecycle(PLUGIN_NAME7, "activate", {
15595
+ logLifecycle(PLUGIN_NAME6, "activate", {
15836
15596
  directory: ctx.directory,
15837
15597
  intervalMs: HEARTBEAT_INTERVAL_MS
15838
15598
  });
15839
15599
  const client = ctx.client;
15840
15600
  const interval = setInterval(() => {
15841
- safeAsync(PLUGIN_NAME7, "interval", async () => {
15601
+ safeAsync(PLUGIN_NAME6, "interval", async () => {
15842
15602
  if (inflight.size === 0)
15843
15603
  return;
15844
15604
  const records = [...inflight.values()];
@@ -15848,7 +15608,7 @@ var toolHeartbeatServer = async (ctx) => {
15848
15608
  continue;
15849
15609
  const sent = await showToast(client, { message: alert.message, variant: alert.variant }, log5);
15850
15610
  r.alertsSent.add(alert.threshold);
15851
- safeWriteLog(PLUGIN_NAME7, {
15611
+ safeWriteLog(PLUGIN_NAME6, {
15852
15612
  hook: "interval",
15853
15613
  tool: r.toolName,
15854
15614
  callID: r.callID,
@@ -15866,12 +15626,12 @@ var toolHeartbeatServer = async (ctx) => {
15866
15626
  event: async () => {}
15867
15627
  };
15868
15628
  };
15869
- var handler7 = toolHeartbeatServer;
15629
+ var handler6 = toolHeartbeatServer;
15870
15630
 
15871
15631
  // plugins/codeforge-tools.ts
15872
15632
  var z31 = tool.schema;
15873
- var PLUGIN_NAME8 = "codeforge-tools";
15874
- logLifecycle(PLUGIN_NAME8, "import");
15633
+ var PLUGIN_NAME7 = "codeforge-tools";
15634
+ logLifecycle(PLUGIN_NAME7, "import");
15875
15635
  function wrap(output, metadata) {
15876
15636
  const text = typeof output === "string" ? output : JSON.stringify(output, null, 2);
15877
15637
  return metadata && Object.keys(metadata).length > 0 ? { output: text, metadata } : { output: text };
@@ -15881,7 +15641,7 @@ async function runSafe(toolName, fn) {
15881
15641
  return await fn();
15882
15642
  } catch (err) {
15883
15643
  const msg = err instanceof Error ? err.message : String(err);
15884
- safeWriteLog(PLUGIN_NAME8, {
15644
+ safeWriteLog(PLUGIN_NAME7, {
15885
15645
  level: "error",
15886
15646
  tool: toolName,
15887
15647
  error: msg
@@ -16279,7 +16039,7 @@ var codeforgeToolsServer = async (ctx) => {
16279
16039
  const rt = loadRuntimeSync({ root: ctx.directory });
16280
16040
  const browserEnabled = rt.runtime.tools.browser.enabled;
16281
16041
  const activeTools = browserEnabled ? [...CORE_TOOL_NAMES, ...BROWSER_TOOL_NAMES] : [...CORE_TOOL_NAMES];
16282
- logLifecycle(PLUGIN_NAME8, "activate", {
16042
+ logLifecycle(PLUGIN_NAME7, "activate", {
16283
16043
  directory: ctx.directory,
16284
16044
  tools: activeTools,
16285
16045
  count: activeTools.length,
@@ -16295,7 +16055,7 @@ var codeforgeToolsServer = async (ctx) => {
16295
16055
  client: ctx.client,
16296
16056
  directory: ctx.directory ?? process.cwd(),
16297
16057
  mainRoot: ctx.directory ?? process.cwd(),
16298
- log: (level, msg, data) => safeWriteLog(PLUGIN_NAME8, { level, msg, data })
16058
+ log: (level, msg, data) => safeWriteLog(PLUGIN_NAME7, { level, msg, data })
16299
16059
  });
16300
16060
  __setContext({
16301
16061
  mainRoot: ctx.directory ?? process.cwd(),
@@ -16599,7 +16359,8 @@ var codeforgeToolsServer = async (ctx) => {
16599
16359
  const v = projectValidate("adr_init", ArgsSchema29, args);
16600
16360
  if (!v.ok)
16601
16361
  return wrap(JSON.parse(v.output));
16602
- const result = await execute29(v.data);
16362
+ const dataWithCwd = { ...v.data, cwd: v.data.cwd ?? ctx.directory ?? process.cwd() };
16363
+ const result = await execute29(dataWithCwd);
16603
16364
  return wrap(result, { title: "adr_init" });
16604
16365
  });
16605
16366
  }
@@ -16607,7 +16368,7 @@ var codeforgeToolsServer = async (ctx) => {
16607
16368
  }
16608
16369
  };
16609
16370
  };
16610
- var handler8 = codeforgeToolsServer;
16371
+ var handler7 = codeforgeToolsServer;
16611
16372
 
16612
16373
  // plugins/discover-spec-suggest.ts
16613
16374
  import { readFileSync as readFileSync3, readdirSync, statSync as statSync3 } from "node:fs";
@@ -16789,8 +16550,8 @@ function isValidSlug(slug) {
16789
16550
  }
16790
16551
 
16791
16552
  // plugins/discover-spec-suggest.ts
16792
- var PLUGIN_NAME9 = "discover-spec-suggest";
16793
- logLifecycle(PLUGIN_NAME9, "import", {});
16553
+ var PLUGIN_NAME8 = "discover-spec-suggest";
16554
+ logLifecycle(PLUGIN_NAME8, "import", {});
16794
16555
  var TARGET_AGENT = "codeforge";
16795
16556
  var SESSION_CAP2 = 200;
16796
16557
  var SESSION_TTL_MS2 = 24 * 60 * 60 * 1000;
@@ -16903,7 +16664,7 @@ function loadSpecs(rootDir, opts = {}) {
16903
16664
  const dirReader = opts.dirReader ?? defaultDirReader;
16904
16665
  const dirExists = opts.dirExists ?? defaultDirExists;
16905
16666
  const statReader = opts.statReader ?? defaultStatReader;
16906
- const log6 = makePluginLogger(PLUGIN_NAME9);
16667
+ const log6 = makePluginLogger(PLUGIN_NAME8);
16907
16668
  const specsRoot = join15(rootDir, SPECS_REL_DIR);
16908
16669
  const records = [];
16909
16670
  if (!dirExists(specsRoot)) {
@@ -17031,7 +16792,7 @@ function renderCandidatesNudge(matched) {
17031
16792
  return body.slice(0, NUDGE_MAX_LEN - 4) + `
17032
16793
  …`;
17033
16794
  }
17034
- var log6 = makePluginLogger(PLUGIN_NAME9);
16795
+ var log6 = makePluginLogger(PLUGIN_NAME8);
17035
16796
  var discoverSpecSuggestServer = async (ctx) => {
17036
16797
  try {
17037
16798
  const loaded = loadSpecs(ctx.directory ?? process.cwd());
@@ -17042,7 +16803,7 @@ var discoverSpecSuggestServer = async (ctx) => {
17042
16803
  error: err instanceof Error ? err.message : String(err)
17043
16804
  });
17044
16805
  }
17045
- logLifecycle(PLUGIN_NAME9, "activate", {
16806
+ logLifecycle(PLUGIN_NAME8, "activate", {
17046
16807
  directory: ctx.directory,
17047
16808
  specs_loaded: specIndex.length,
17048
16809
  target_agent: TARGET_AGENT,
@@ -17054,7 +16815,7 @@ var discoverSpecSuggestServer = async (ctx) => {
17054
16815
  });
17055
16816
  return {
17056
16817
  "chat.message": async (input, output) => {
17057
- await safeAsync(PLUGIN_NAME9, "chat.message", async () => {
16818
+ await safeAsync(PLUGIN_NAME8, "chat.message", async () => {
17058
16819
  if (specIndex.length === 0)
17059
16820
  return;
17060
16821
  const text = extractUserText(output);
@@ -17069,7 +16830,7 @@ var discoverSpecSuggestServer = async (ctx) => {
17069
16830
  });
17070
16831
  },
17071
16832
  "experimental.chat.system.transform": async (input, output) => {
17072
- await safeAsync(PLUGIN_NAME9, "experimental.chat.system.transform", async () => {
16833
+ await safeAsync(PLUGIN_NAME8, "experimental.chat.system.transform", async () => {
17073
16834
  if (specIndex.length === 0)
17074
16835
  return;
17075
16836
  if (!output || !Array.isArray(output.system))
@@ -17095,7 +16856,7 @@ var discoverSpecSuggestServer = async (ctx) => {
17095
16856
  if (!nudge)
17096
16857
  return;
17097
16858
  output.system.push(nudge);
17098
- safeWriteLog(PLUGIN_NAME9, {
16859
+ safeWriteLog(PLUGIN_NAME8, {
17099
16860
  hook: "experimental.chat.system.transform",
17100
16861
  sessionID: sid,
17101
16862
  agent: entry.agent,
@@ -17107,927 +16868,93 @@ var discoverSpecSuggestServer = async (ctx) => {
17107
16868
  }
17108
16869
  };
17109
16870
  };
17110
- var handler9 = discoverSpecSuggestServer;
16871
+ var handler8 = discoverSpecSuggestServer;
17111
16872
 
17112
- // plugins/kh-auto-context.ts
17113
- init_kh_client();
17114
-
17115
- // lib/kh-shared-context.ts
17116
- var DEFAULT_MAX_PER_SESSION = 20;
17117
- var DEFAULT_TTL_MS = 5 * 60 * 1000;
17118
-
17119
- class SessionScopedCache {
17120
- cache = new Map;
17121
- inflight = new Map;
17122
- generation = new Map;
17123
- ttlMs;
17124
- maxPerSession;
17125
- now;
17126
- constructor(opts = {}) {
17127
- this.ttlMs = opts.ttlMs ?? DEFAULT_TTL_MS;
17128
- this.maxPerSession = opts.maxPerSession ?? DEFAULT_MAX_PER_SESSION;
17129
- this.now = opts.now ?? (() => Date.now());
17130
- }
17131
- async searchOrGet(args) {
17132
- const { client, sessionId, query, limit, timeoutMs } = args;
17133
- const queryHash = this.hashQuery(sessionId, query);
17134
- const inflightKey = `${sessionId}::${queryHash}`;
17135
- const sessionMap2 = this.cache.get(sessionId);
17136
- if (sessionMap2) {
17137
- const entry = sessionMap2.get(queryHash);
17138
- if (entry && this.now() - entry.cachedAt < this.ttlMs) {
17139
- sessionMap2.delete(queryHash);
17140
- sessionMap2.set(queryHash, entry);
17141
- return entry.result;
17142
- }
17143
- if (entry)
17144
- sessionMap2.delete(queryHash);
17145
- }
17146
- const pending = this.inflight.get(inflightKey);
17147
- if (pending)
17148
- return pending;
17149
- const startGen = this.generation.get(sessionId) ?? 0;
17150
- const promise = this.doSearchAndMaybeCache(client, sessionId, queryHash, query, limit, timeoutMs, startGen).finally(() => {
17151
- this.inflight.delete(inflightKey);
17152
- });
17153
- this.inflight.set(inflightKey, promise);
17154
- return promise;
17155
- }
17156
- onSessionEnd(sessionId) {
17157
- this.generation.set(sessionId, (this.generation.get(sessionId) ?? 0) + 1);
17158
- this.cache.delete(sessionId);
17159
- const prefix = `${sessionId}::`;
17160
- for (const key of this.inflight.keys()) {
17161
- if (key.startsWith(prefix)) {
17162
- this.inflight.delete(key);
17163
- }
17164
- }
17165
- }
17166
- _snapshot() {
17167
- const perSession = {};
17168
- let total = 0;
17169
- for (const [sid, map] of this.cache.entries()) {
17170
- perSession[sid] = map.size;
17171
- total += map.size;
17172
- }
17173
- const generations = {};
17174
- for (const [sid, g] of this.generation.entries()) {
17175
- generations[sid] = g;
17176
- }
17177
- return {
17178
- cacheSize: total,
17179
- inflightSize: this.inflight.size,
17180
- sessions: Array.from(this.cache.keys()),
17181
- perSession,
17182
- generations
17183
- };
17184
- }
17185
- _reset() {
17186
- this.cache.clear();
17187
- this.inflight.clear();
17188
- this.generation.clear();
16873
+ // lib/memories.ts
16874
+ import { promises as fs15 } from "node:fs";
16875
+ import * as path19 from "node:path";
16876
+ import * as os5 from "node:os";
16877
+ function resolveConfig(c) {
16878
+ return {
16879
+ projectRoot: c.projectRoot,
16880
+ homeDir: c.homeDir ?? os5.homedir(),
16881
+ projectName: c.projectName ?? path19.basename(c.projectRoot),
16882
+ kh: c.kh,
16883
+ now: c.now ?? Date.now,
16884
+ log: c.log ?? (() => {}),
16885
+ maxPerScope: c.maxPerScope ?? 1000
16886
+ };
16887
+ }
16888
+ function fileFor(scope, cfg) {
16889
+ if (scope === "project") {
16890
+ return path19.join(cfg.projectRoot, ".codeforge", "memories.json");
17189
16891
  }
17190
- hashQuery(sessionId, query) {
17191
- return `${sessionId}::${query}`;
16892
+ return path19.join(cfg.homeDir, ".codeforge", "memories.json");
16893
+ }
16894
+ async function readBank(p) {
16895
+ try {
16896
+ const raw = await fs15.readFile(p, "utf8");
16897
+ const arr = JSON.parse(raw);
16898
+ if (!Array.isArray(arr))
16899
+ return [];
16900
+ return arr.filter((x) => isMemory(x));
16901
+ } catch {
16902
+ return [];
17192
16903
  }
17193
- async doSearchAndMaybeCache(client, sessionId, queryHash, query, limit, timeoutMs, startGen) {
17194
- const result = await this.doSearch(client, query, limit, timeoutMs);
17195
- const currentGen = this.generation.get(sessionId) ?? 0;
17196
- if (currentGen === startGen && result.ok) {
17197
- this.writeCache(sessionId, queryHash, query, result);
17198
- }
17199
- return result;
16904
+ }
16905
+ async function writeBank(p, items) {
16906
+ await fs15.mkdir(path19.dirname(p), { recursive: true });
16907
+ const tmp = `${p}.tmp`;
16908
+ await fs15.writeFile(tmp, JSON.stringify(items, null, 2), "utf8");
16909
+ await fs15.rename(tmp, p);
16910
+ }
16911
+ function isMemory(x) {
16912
+ if (!x || typeof x !== "object")
16913
+ return false;
16914
+ const m = x;
16915
+ return typeof m.id === "string" && (m.scope === "project" || m.scope === "user") && typeof m.content === "string" && typeof m.created_at === "number" && typeof m.updated_at === "number";
16916
+ }
16917
+ var _seq = 0;
16918
+ function localId(now) {
16919
+ _seq = (_seq + 1) % 1e5;
16920
+ return `mem-${now}-${_seq.toString(36)}`;
16921
+ }
16922
+ async function addMemory(params, cfgRaw) {
16923
+ const cfg = resolveConfig(cfgRaw);
16924
+ if (!params.content || params.content.trim().length === 0) {
16925
+ return { ok: false, id: "", written_to: "local", error: "content 不能为空" };
17200
16926
  }
17201
- async doSearch(client, query, limit, timeoutMs) {
17202
- const callPromise = (async () => {
17203
- try {
17204
- return await client.search({ query, limit });
17205
- } catch (err) {
17206
- return {
17207
- ok: false,
17208
- reason: "kh_returned_error",
17209
- message: err instanceof Error ? err.message : String(err)
17210
- };
17211
- }
17212
- })();
17213
- if (timeoutMs === undefined || timeoutMs <= 0) {
17214
- return callPromise;
17215
- }
17216
- let timer = null;
16927
+ const now = cfg.now();
16928
+ const base = {
16929
+ scope: params.scope,
16930
+ content: params.content.trim(),
16931
+ tags: params.tags?.filter((t) => typeof t === "string") ?? [],
16932
+ project: params.scope === "project" ? cfg.projectName : undefined,
16933
+ created_at: now,
16934
+ updated_at: now,
16935
+ source: params.source ?? "manual"
16936
+ };
16937
+ if (cfg.kh) {
17217
16938
  try {
17218
- const racer = new Promise((res) => {
17219
- timer = setTimeout(() => res({
17220
- ok: false,
17221
- reason: "kh_returned_error",
17222
- message: `kh search timeout after ${timeoutMs}ms`
17223
- }), timeoutMs);
16939
+ const r = await cfg.kh.add(base);
16940
+ cfg.log("info", `[memories] add KH ${r.id}`);
16941
+ return { ok: true, id: r.id, written_to: "kh" };
16942
+ } catch (err) {
16943
+ cfg.log("warn", `[memories] KH 写入失败,降级本地`, {
16944
+ error: err instanceof Error ? err.message : String(err)
17224
16945
  });
17225
- return await Promise.race([callPromise, racer]);
17226
- } finally {
17227
- if (timer)
17228
- clearTimeout(timer);
17229
16946
  }
17230
16947
  }
17231
- writeCache(sessionId, queryHash, query, result) {
17232
- let sessionMap2 = this.cache.get(sessionId);
17233
- if (!sessionMap2) {
17234
- sessionMap2 = new Map;
17235
- this.cache.set(sessionId, sessionMap2);
17236
- }
17237
- sessionMap2.delete(queryHash);
17238
- sessionMap2.set(queryHash, { query, result, cachedAt: this.now() });
17239
- while (sessionMap2.size > this.maxPerSession) {
17240
- const oldest = sessionMap2.keys().next().value;
17241
- if (oldest === undefined)
17242
- break;
17243
- sessionMap2.delete(oldest);
17244
- }
16948
+ const file = fileFor(params.scope, cfg);
16949
+ const id = localId(now);
16950
+ const m = { id, ...base };
16951
+ const items = await readBank(file);
16952
+ items.push(m);
16953
+ if (items.length > cfg.maxPerScope) {
16954
+ items.splice(0, items.length - cfg.maxPerScope);
17245
16955
  }
17246
- }
17247
- var sharedKhCache = new SessionScopedCache;
17248
-
17249
- // plugins/kh-auto-context.ts
17250
- var PLUGIN_NAME10 = "kh-auto-context";
17251
- var INJECTION_MODE = "observe-only";
17252
- function resolveInjectionMode(client) {
17253
- return client.hasTransport() ? "system-injected" : "observe-only";
17254
- }
17255
- var DEFAULT_CONFIG4 = {
17256
- minConfidence: 0.55,
17257
- limit: 3,
17258
- minTextLength: 8,
17259
- cacheTtlMs: 5 * 60 * 1000,
17260
- timeoutMs: 1000,
17261
- skipPrefixes: ["/", "!", "@"],
17262
- skipKeywords: ["你好", "hello", "hi", "thanks", "谢谢"]
17263
- };
17264
-
17265
- class QueryCache {
17266
- ttlMs;
17267
- now;
17268
- map = new Map;
17269
- constructor(ttlMs, now = () => Date.now()) {
17270
- this.ttlMs = ttlMs;
17271
- this.now = now;
17272
- }
17273
- shouldSkip(query) {
17274
- const entry = this.map.get(query);
17275
- if (!entry)
17276
- return false;
17277
- return this.now() - entry.injectedAt < this.ttlMs;
17278
- }
17279
- record(query, insights) {
17280
- this.map.set(query, { query, injectedAt: this.now(), insights });
17281
- }
17282
- size() {
17283
- return this.map.size;
17284
- }
17285
- clear() {
17286
- this.map.clear();
17287
- }
17288
- }
17289
- function extractQuery(text) {
17290
- if (!text)
17291
- return "";
17292
- const norm = text.trim().replace(/\s+/g, " ");
17293
- const m = /^[^.。?!?!;;\n]+/.exec(norm);
17294
- let head = (m ? m[0] : norm).trim();
17295
- const fillerRe = /^(请|帮我|麻烦|我想|你能|你可以|看一?下|查一?下|how (do|can) (i|you)|can you|could you|please|help me)\s*/i;
17296
- let prev = "";
17297
- while (prev !== head) {
17298
- prev = head;
17299
- head = head.replace(fillerRe, "").trim();
17300
- }
17301
- return head.slice(0, 80);
17302
- }
17303
- function shouldInject(text, cfg = DEFAULT_CONFIG4) {
17304
- if (!text)
17305
- return false;
17306
- const t = text.trim();
17307
- if (t.length < cfg.minTextLength)
17308
- return false;
17309
- for (const p of cfg.skipPrefixes) {
17310
- if (t.startsWith(p))
17311
- return false;
17312
- }
17313
- const lower = t.toLowerCase();
17314
- for (const kw of cfg.skipKeywords) {
17315
- if (lower.includes(kw.toLowerCase()) && t.length <= kw.length + 5)
17316
- return false;
17317
- }
17318
- return true;
17319
- }
17320
- function formatInjection(query, insights, mode = INJECTION_MODE) {
17321
- const lines = [];
17322
- lines.push(`> \uD83E\uDDE0 [${mode}] 候选 ${insights.length} 条团队经验(query: "${query}")`);
17323
- lines.push("");
17324
- for (const ins of insights) {
17325
- lines.push(`**${ins.title}** \`${ins.category}\` _(${(ins.confidence * 100).toFixed(0)}%)_`);
17326
- const content = ins.content.length > 240 ? ins.content.slice(0, 240) + "…" : ins.content;
17327
- lines.push(content.replace(/\n+/g, " "));
17328
- lines.push("");
17329
- }
17330
- return { query, insights, markdown: lines.join(`
17331
- `), mode };
17332
- }
17333
- var CODEFORGE_CONSTRAINTS = [
17334
- {
17335
- content: "本会话使用 CodeForge:触发词命中(部署/怎么/为什么/之前/历史等)必须先 smart_search;写代码直接在 session worktree 内 edit/write/ast_edit(worktree 由 session-worktree-guard 隔离主仓),完成后由用户 /merge 拍板",
17336
- priority: 9
17337
- },
17338
- {
17339
- content: "API key 仅读环境变量 KNOWLEDGE_API_KEY,禁止写入任何配置文件",
17340
- priority: 9
17341
- },
17342
- {
17343
- content: "沉淀经验优先用 save_chat_insight(自动分类、去重、合并),不要直接 add_knowledge(结构化录入)",
17344
- priority: 8
17345
- }
17346
- ];
17347
- async function seedConstraints(client, log7) {
17348
- if (!client.hasTransport()) {
17349
- log7?.debug?.(`[${PLUGIN_NAME10}] seedConstraints: transport 不可用,跳过`, {});
17350
- return { ok: false, reason: "transport_unavailable" };
17351
- }
17352
- try {
17353
- const result = await client.updateWorkingMemory({
17354
- section: "constraints",
17355
- replace: true,
17356
- items: CODEFORGE_CONSTRAINTS.map((c) => ({
17357
- content: c.content,
17358
- priority: c.priority
17359
- }))
17360
- });
17361
- if (result && typeof result === "object" && "ok" in result && result.ok === false) {
17362
- const r = result;
17363
- log7?.warn(`[${PLUGIN_NAME10}] seedConstraints 降级`, {
17364
- reason: r.reason,
17365
- message: r.message
17366
- });
17367
- return {
17368
- ok: false,
17369
- reason: r.reason === "transport_unavailable" ? "transport_unavailable" : "kh_call_failed",
17370
- message: r.message
17371
- };
17372
- }
17373
- log7?.info(`[${PLUGIN_NAME10}] seedConstraints: 已写入 ${CODEFORGE_CONSTRAINTS.length} 条 constraints`, { count: CODEFORGE_CONSTRAINTS.length });
17374
- return { ok: true, itemsWritten: CODEFORGE_CONSTRAINTS.length };
17375
- } catch (err) {
17376
- const message = err instanceof Error ? err.message : String(err);
17377
- log7?.warn(`[${PLUGIN_NAME10}] seedConstraints 失败(已静默)`, { error: message });
17378
- return { ok: false, reason: "exception", message };
17379
- }
17380
- }
17381
- function createSystemInjectedHook(client, sessionId, log7) {
17382
- const section = `topic:auto-context-${sessionId ?? "global"}`;
17383
- return async (markdown) => {
17384
- try {
17385
- const result = await client.updateWorkingMemory({
17386
- section,
17387
- replace: true,
17388
- items: [{ content: markdown, priority: 7 }]
17389
- });
17390
- if (result && typeof result === "object" && "ok" in result && result.ok === false) {
17391
- const r = result;
17392
- log7?.warn(`[${PLUGIN_NAME10}] system-injected 降级到 observe-only:${r.reason ?? "unknown"}`, {
17393
- sessionId,
17394
- section,
17395
- preview: markdown.slice(0, 200),
17396
- reason: r.reason,
17397
- message: r.message
17398
- });
17399
- return;
17400
- }
17401
- log7?.info(`[${PLUGIN_NAME10}] system-injected: 写入 KH working_memory 成功 (${markdown.length} chars)`, {
17402
- sessionId,
17403
- section
17404
- });
17405
- } catch (err) {
17406
- log7?.warn(`[${PLUGIN_NAME10}] system-injected 抛异常,降级到 observe-only`, {
17407
- sessionId,
17408
- section,
17409
- preview: markdown.slice(0, 200),
17410
- error: err instanceof Error ? err.message : String(err)
17411
- });
17412
- }
17413
- };
17414
- }
17415
- var inflight2 = new Set;
17416
- var INFLIGHT_CAP = 5;
17417
- function inflightKey(sessionId, query) {
17418
- return `${sessionId ?? "global"}:${Date.now()}:${Math.random().toString(36).slice(2, 10)}`;
17419
- }
17420
- async function runKhSearchAndInject(args) {
17421
- const { query, ctx, opts, mode } = args;
17422
- const cfg = opts.config ?? DEFAULT_CONFIG4;
17423
- const log7 = ctx.log;
17424
- const startedAt = Date.now();
17425
- const racer = opts.scheduler?.raceTimeout ?? (async (p, ms) => {
17426
- let timer = null;
17427
- try {
17428
- return await Promise.race([
17429
- p,
17430
- new Promise((res) => {
17431
- timer = setTimeout(() => res("__timeout__"), ms);
17432
- })
17433
- ]);
17434
- } finally {
17435
- if (timer)
17436
- clearTimeout(timer);
17437
- }
17438
- });
17439
- let result;
17440
- try {
17441
- const searchPromise = sharedKhCache.searchOrGet({
17442
- client: opts.client,
17443
- sessionId: ctx.sessionId ?? "global",
17444
- query,
17445
- limit: cfg.limit
17446
- });
17447
- result = await racer(searchPromise, cfg.timeoutMs);
17448
- } catch (err) {
17449
- log7?.warn(`[${PLUGIN_NAME10}] client.search threw (sync or async), return null`, {
17450
- query,
17451
- elapsedMs: Date.now() - startedAt,
17452
- sessionId: ctx.sessionId,
17453
- error: err instanceof Error ? err.message : String(err)
17454
- });
17455
- opts.cache.record(query, []);
17456
- return null;
17457
- }
17458
- if (result === "__timeout__") {
17459
- log7?.warn(`[${PLUGIN_NAME10}] timeout`, {
17460
- query,
17461
- ms: cfg.timeoutMs,
17462
- elapsedMs: Date.now() - startedAt,
17463
- sessionId: ctx.sessionId
17464
- });
17465
- opts.cache.record(query, []);
17466
- return null;
17467
- }
17468
- if (!result.ok) {
17469
- log7?.warn(`[${PLUGIN_NAME10}] kh degraded`, {
17470
- reason: result.reason,
17471
- query,
17472
- elapsedMs: Date.now() - startedAt,
17473
- sessionId: ctx.sessionId
17474
- });
17475
- opts.cache.record(query, []);
17476
- return null;
17477
- }
17478
- const filtered = result.insights.filter((i) => (i.confidence ?? 0) >= cfg.minConfidence);
17479
- if (filtered.length === 0) {
17480
- opts.cache.record(query, []);
17481
- log7?.debug?.(`[${PLUGIN_NAME10}] no candidate above threshold`, {
17482
- query,
17483
- rawCount: result.insights.length,
17484
- elapsedMs: Date.now() - startedAt,
17485
- sessionId: ctx.sessionId
17486
- });
17487
- return null;
17488
- }
17489
- const payload = formatInjection(query, filtered, mode);
17490
- if (typeof ctx.injectContext === "function") {
17491
- try {
17492
- await ctx.injectContext(payload.markdown);
17493
- } catch (err) {
17494
- log7?.warn(`[${PLUGIN_NAME10}] injectContext threw`, {
17495
- error: err instanceof Error ? err.message : String(err),
17496
- query,
17497
- sessionId: ctx.sessionId
17498
- });
17499
- }
17500
- }
17501
- opts.cache.record(query, filtered);
17502
- log7?.info(`[${PLUGIN_NAME10}] inject complete (${mode})`, {
17503
- query,
17504
- mode,
17505
- candidateCount: filtered.length,
17506
- elapsedMs: Date.now() - startedAt,
17507
- sessionId: ctx.sessionId
17508
- });
17509
- return payload;
17510
- }
17511
- async function handleMessage2(raw, opts) {
17512
- const cfg = opts.config ?? DEFAULT_CONFIG4;
17513
- const mode = opts.mode ?? INJECTION_MODE;
17514
- const ctx = raw ?? {};
17515
- const log7 = ctx.log;
17516
- const text = (ctx.content ?? "").trim();
17517
- if (!shouldInject(text, cfg)) {
17518
- log7?.debug?.(`[${PLUGIN_NAME10}] skip (filter)`, { textLen: text.length });
17519
- return;
17520
- }
17521
- const query = extractQuery(text);
17522
- if (!query)
17523
- return;
17524
- if (opts.cache.shouldSkip(query)) {
17525
- log7?.debug?.(`[${PLUGIN_NAME10}] cache hit, skip`, { query });
17526
- return;
17527
- }
17528
- if (inflight2.size >= INFLIGHT_CAP) {
17529
- log7?.warn(`[${PLUGIN_NAME10}] inflight cap reached, skip`, {
17530
- query,
17531
- inflightSize: inflight2.size,
17532
- cap: INFLIGHT_CAP,
17533
- sessionId: ctx.sessionId
17534
- });
17535
- return;
17536
- }
17537
- const key = inflightKey(ctx.sessionId, query);
17538
- inflight2.add(key);
17539
- runKhSearchAndInject({ query, ctx, opts, mode }).catch((err) => {
17540
- log7?.warn(`[${PLUGIN_NAME10}] runKhSearchAndInject 顶层兜底捕获`, {
17541
- error: err instanceof Error ? err.message : String(err),
17542
- query,
17543
- sessionId: ctx.sessionId
17544
- });
17545
- }).finally(() => {
17546
- inflight2.delete(key);
17547
- });
17548
- }
17549
- logLifecycle(PLUGIN_NAME10, "import");
17550
- var sharedClient2 = new KhClient;
17551
- var sharedCache = new QueryCache(DEFAULT_CONFIG4.cacheTtlMs);
17552
- var khAutoContextServer = async (ctx) => {
17553
- const log7 = makePluginLogger(PLUGIN_NAME10);
17554
- const runtimeMode = resolveInjectionMode(sharedClient2);
17555
- logLifecycle(PLUGIN_NAME10, "activate", {
17556
- directory: ctx.directory,
17557
- minConfidence: DEFAULT_CONFIG4.minConfidence,
17558
- timeoutMs: DEFAULT_CONFIG4.timeoutMs,
17559
- mode: runtimeMode,
17560
- transport_available: sharedClient2.hasTransport()
17561
- });
17562
- seedConstraints(sharedClient2, log7).catch((err) => {
17563
- log7.warn("seedConstraints 顶层兜底捕获", {
17564
- error: err instanceof Error ? err.message : String(err)
17565
- });
17566
- });
17567
- return {
17568
- "chat.message": async (input, output) => {
17569
- await safeAsync(PLUGIN_NAME10, "chat.message", async () => {
17570
- const text = extractUserText(output);
17571
- if (!text)
17572
- return;
17573
- const injectContext = runtimeMode === "system-injected" ? createSystemInjectedHook(sharedClient2, input.sessionID, log7) : async (markdown) => {
17574
- log7.info(`KH context candidate (${markdown.length} chars)`, {
17575
- sessionID: input.sessionID,
17576
- mode: runtimeMode,
17577
- preview: markdown.slice(0, 200)
17578
- });
17579
- };
17580
- await handleMessage2({
17581
- content: text,
17582
- sessionId: input.sessionID,
17583
- injectContext,
17584
- log: log7
17585
- }, { client: sharedClient2, cache: sharedCache, mode: runtimeMode });
17586
- });
17587
- },
17588
- event: async ({ event }) => {
17589
- await safeAsync(PLUGIN_NAME10, "event", async () => {
17590
- const e = event;
17591
- if (e.type !== "session.idle")
17592
- return;
17593
- const props = e.properties;
17594
- const sid = props?.sessionID;
17595
- if (typeof sid !== "string" || !sid)
17596
- return;
17597
- sharedKhCache.onSessionEnd(sid);
17598
- log7.debug?.(`[${PLUGIN_NAME10}] session.idle: cleared shared cache`, {
17599
- sessionID: sid
17600
- });
17601
- });
17602
- }
17603
- };
17604
- };
17605
- var handler10 = khAutoContextServer;
17606
-
17607
- // lib/condenser.ts
17608
- var DEFAULT_CONDENSE = {
17609
- budget: 128000,
17610
- threshold: 0.7,
17611
- target: 0.4,
17612
- keepRecent: 6,
17613
- keepSystem: true,
17614
- charsPerToken: 4
17615
- };
17616
- function estimateTokens(text, charsPerToken = 4) {
17617
- if (!text)
17618
- return 0;
17619
- return Math.max(1, Math.ceil(text.length / charsPerToken));
17620
- }
17621
- function tokenizeMessages(msgs, charsPerToken = 4) {
17622
- let total = 0;
17623
- for (const m of msgs) {
17624
- total += m.tokens ?? estimateTokens(m.content, charsPerToken);
17625
- }
17626
- return total;
17627
- }
17628
- function fallbackSummarize(msgs) {
17629
- if (msgs.length === 0)
17630
- return "";
17631
- const head = msgs.slice(0, 2).map((m) => `- [${m.role}] ${truncate(m.content, 120)}`);
17632
- const tail = msgs.slice(-2).map((m) => `- [${m.role}] ${truncate(m.content, 120)}`);
17633
- const tagCounts = new Map;
17634
- let userCount = 0;
17635
- let assistantCount = 0;
17636
- let toolCount = 0;
17637
- for (const m of msgs) {
17638
- if (m.role === "user")
17639
- userCount++;
17640
- else if (m.role === "assistant")
17641
- assistantCount++;
17642
- else if (m.role === "tool")
17643
- toolCount++;
17644
- if (m.tag)
17645
- tagCounts.set(m.tag, (tagCounts.get(m.tag) ?? 0) + 1);
17646
- }
17647
- const tagsLine = [...tagCounts.entries()].sort((a, b) => b[1] - a[1]).slice(0, 5).map(([t, c]) => `${t}×${c}`).join(", ");
17648
- return [
17649
- `### 历史摘要(共 ${msgs.length} 条 · user=${userCount} assistant=${assistantCount} tool=${toolCount})`,
17650
- tagsLine ? `- 关键事件:${tagsLine}` : "",
17651
- "",
17652
- "**开头:**",
17653
- ...head,
17654
- "",
17655
- "**结尾:**",
17656
- ...tail
17657
- ].filter(Boolean).join(`
17658
- `);
17659
- }
17660
- function truncate(s, max) {
17661
- if (!s)
17662
- return "";
17663
- return s.length <= max ? s : s.slice(0, max - 1) + "…";
17664
- }
17665
- async function condense(input, opts = {}) {
17666
- const cfg = { ...DEFAULT_CONDENSE, ...opts.config ?? {} };
17667
- const msgs = [...input];
17668
- const before = {
17669
- count: msgs.length,
17670
- tokens: tokenizeMessages(msgs, cfg.charsPerToken)
17671
- };
17672
- if (cfg.budget <= 0 || before.tokens / cfg.budget < cfg.threshold) {
17673
- return {
17674
- messages: msgs,
17675
- before,
17676
- after: { ...before },
17677
- compressed: false,
17678
- reason: `usage ${(before.tokens / Math.max(cfg.budget, 1)).toFixed(2)} < threshold ${cfg.threshold}`
17679
- };
17680
- }
17681
- const systems = cfg.keepSystem ? msgs.filter((m) => m.role === "system") : [];
17682
- const nonSystem = cfg.keepSystem ? msgs.filter((m) => m.role !== "system") : msgs;
17683
- const recents = nonSystem.slice(-cfg.keepRecent);
17684
- const middles = nonSystem.slice(0, Math.max(0, nonSystem.length - cfg.keepRecent));
17685
- if (middles.length === 0) {
17686
- return {
17687
- messages: msgs,
17688
- before,
17689
- after: { ...before },
17690
- compressed: false,
17691
- reason: "nothing in middle to compress"
17692
- };
17693
- }
17694
- let summary = "";
17695
- try {
17696
- summary = await (opts.summarize ?? fallbackSummarize)(middles) ?? "";
17697
- } catch {
17698
- summary = fallbackSummarize(middles);
17699
- }
17700
- if (!summary)
17701
- summary = fallbackSummarize(middles);
17702
- const summaryMsg = {
17703
- role: "assistant",
17704
- content: summary,
17705
- tag: "condensed-summary"
17706
- };
17707
- const out = [...systems, summaryMsg, ...recents];
17708
- const after = {
17709
- count: out.length,
17710
- tokens: tokenizeMessages(out, cfg.charsPerToken)
17711
- };
17712
- return {
17713
- messages: out,
17714
- before,
17715
- after,
17716
- compressed: true,
17717
- summary,
17718
- reason: `compressed ${middles.length} → 1 summary (target ${cfg.target})`
17719
- };
17720
- }
17721
-
17722
- // plugins/kh-reminder.ts
17723
- var PLUGIN_NAME11 = "kh-reminder";
17724
- logLifecycle(PLUGIN_NAME11, "import", {});
17725
- var TRIGGER_WORDS_ZH = [
17726
- "怎么",
17727
- "怎样",
17728
- "如何",
17729
- "为什么",
17730
- "之前",
17731
- "历史",
17732
- "以前",
17733
- "部署",
17734
- "上线",
17735
- "发布",
17736
- "配置",
17737
- "地址",
17738
- "端口",
17739
- "凭证",
17740
- "接入",
17741
- "集成",
17742
- "谁负责",
17743
- "谁写的",
17744
- "报错",
17745
- "报什么错",
17746
- "跑不起来",
17747
- "起不来",
17748
- "挂了",
17749
- "崩了",
17750
- "规范",
17751
- "约定",
17752
- "风格"
17753
- ];
17754
- var TRIGGER_WORDS_EN = [
17755
- "how to",
17756
- "why",
17757
- "previously",
17758
- "deploy",
17759
- "release",
17760
- "config",
17761
- "URL",
17762
- "credential",
17763
- "token",
17764
- "API key",
17765
- "api[_-]?key",
17766
- "integrate",
17767
- "who owns",
17768
- "error",
17769
- "failed",
17770
- "convention"
17771
- ];
17772
- function buildTriggerRegex() {
17773
- const escapedZh = TRIGGER_WORDS_ZH.map(escapeRegex).join("|");
17774
- const enParts = [];
17775
- for (const w of TRIGGER_WORDS_EN) {
17776
- if (w === "api[_-]?key") {
17777
- enParts.push(w);
17778
- } else {
17779
- enParts.push(escapeRegex(w));
17780
- }
17781
- }
17782
- const escapedEn = enParts.join("|");
17783
- const pattern = `(${escapedZh})|\\b(${escapedEn})\\b`;
17784
- return new RegExp(pattern, "i");
17785
- }
17786
- function escapeRegex(s) {
17787
- return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
17788
- }
17789
- var TRIGGER_REGEX = buildTriggerRegex();
17790
- var COOLDOWN_MS = 5 * 60 * 1000;
17791
- var lastTriggerAt = new Map;
17792
- var DEFAULT_THRESHOLD = 0.5;
17793
- var DEFAULT_RECENT_ROUNDS = 5;
17794
- function computeUsageRatio(messages) {
17795
- const cpt = DEFAULT_CONDENSE.charsPerToken;
17796
- const budget = DEFAULT_CONDENSE.budget;
17797
- if (budget <= 0)
17798
- return 0;
17799
- let total = 0;
17800
- for (const m of messages) {
17801
- total += m.tokens ?? estimateTokens(m.content, cpt);
17802
- }
17803
- return total / budget;
17804
- }
17805
- function findTriggerWord(messages) {
17806
- for (let i = messages.length - 1;i >= 0; i--) {
17807
- const m = messages[i];
17808
- if (!m || m.role !== "user")
17809
- continue;
17810
- const text = m.content ?? "";
17811
- const match = TRIGGER_REGEX.exec(text);
17812
- if (match) {
17813
- return match[0];
17814
- }
17815
- return null;
17816
- }
17817
- return null;
17818
- }
17819
- function hasRecentSmartSearch(messages, recentRounds) {
17820
- const slice = messages.slice(-recentRounds);
17821
- for (const m of slice) {
17822
- const c = m.content ?? "";
17823
- if (/smart_search/i.test(c))
17824
- return true;
17825
- if (m.tag && /smart_search/i.test(m.tag))
17826
- return true;
17827
- }
17828
- return false;
17829
- }
17830
- function evaluate(input) {
17831
- const messages = Array.isArray(input.messages) ? input.messages : [];
17832
- const sessionId = input.sessionId ?? "unknown";
17833
- const threshold = input.threshold ?? DEFAULT_THRESHOLD;
17834
- const recentRounds = input.recentRounds ?? DEFAULT_RECENT_ROUNDS;
17835
- const now = (input.nowFn ?? Date.now)();
17836
- if (messages.length === 0) {
17837
- return { triggered: false, sessionId };
17838
- }
17839
- let reason = null;
17840
- const ratio = computeUsageRatio(messages);
17841
- if (ratio >= threshold) {
17842
- reason = { kind: "token-usage", ratio, threshold };
17843
- }
17844
- if (!reason) {
17845
- const word = findTriggerWord(messages);
17846
- if (word) {
17847
- reason = { kind: "trigger-word", word };
17848
- }
17849
- }
17850
- if (!reason) {
17851
- if (messages.length >= 6 && !hasRecentSmartSearch(messages, recentRounds)) {
17852
- reason = { kind: "no-search-in-recent-rounds", rounds: recentRounds };
17853
- }
17854
- }
17855
- if (!reason) {
17856
- return { triggered: false, sessionId };
17857
- }
17858
- const last = lastTriggerAt.get(sessionId);
17859
- if (last !== undefined && now - last < COOLDOWN_MS) {
17860
- return {
17861
- triggered: false,
17862
- cooldown_skipped: true,
17863
- reason,
17864
- sessionId
17865
- };
17866
- }
17867
- lastTriggerAt.set(sessionId, now);
17868
- return { triggered: true, reason, sessionId };
17869
- }
17870
- function handleObserve(raw, log7) {
17871
- const ctx = raw ?? {};
17872
- if (!Array.isArray(ctx.messages))
17873
- return null;
17874
- try {
17875
- const result = evaluate(ctx);
17876
- if (result.triggered && result.reason) {
17877
- const reasonStr = formatReason(result.reason);
17878
- log7?.info(`[${PLUGIN_NAME11}] ⚡ KH 提醒(observe-only) · session=${result.sessionId} · ${reasonStr}`, {
17879
- sessionId: result.sessionId,
17880
- reason: result.reason,
17881
- suggestion: "调用 smart_search 查项目历史;完成后用 save_chat_insight 沉淀"
17882
- });
17883
- }
17884
- return result;
17885
- } catch (err) {
17886
- log7?.warn(`[${PLUGIN_NAME11}] evaluate 异常(已隔离)`, {
17887
- error: err instanceof Error ? err.message : String(err)
17888
- });
17889
- return null;
17890
- }
17891
- }
17892
- function formatReason(r) {
17893
- switch (r.kind) {
17894
- case "token-usage":
17895
- return `token-usage ${(r.ratio * 100).toFixed(1)}% ≥ ${(r.threshold * 100).toFixed(0)}%`;
17896
- case "trigger-word":
17897
- return `trigger-word: "${r.word}"`;
17898
- case "no-search-in-recent-rounds":
17899
- return `no-search-in-recent-rounds (last ${r.rounds})`;
17900
- }
17901
- }
17902
- var log7 = makePluginLogger(PLUGIN_NAME11);
17903
- var khReminderServer = async (ctx) => {
17904
- logLifecycle(PLUGIN_NAME11, "activate", {
17905
- directory: ctx.directory,
17906
- threshold: DEFAULT_THRESHOLD,
17907
- cooldown_ms: COOLDOWN_MS,
17908
- trigger_words_total: TRIGGER_WORDS_ZH.length + TRIGGER_WORDS_EN.length
17909
- });
17910
- return {
17911
- "experimental.chat.messages.transform": async (_input, output) => {
17912
- await safeAsync(PLUGIN_NAME11, "experimental.chat.messages.transform", async () => {
17913
- const list = output.messages;
17914
- if (!Array.isArray(list) || list.length === 0)
17915
- return;
17916
- let sessionId = "unknown";
17917
- const flat = list.map((m) => {
17918
- const info = m.info;
17919
- if (info && typeof info.sessionID === "string" && sessionId === "unknown") {
17920
- sessionId = info.sessionID;
17921
- }
17922
- const role = info?.role ?? "user";
17923
- const parts = m.parts ?? [];
17924
- const content = parts.filter((p) => p.type === "text").map((p) => p.text ?? "").join(`
17925
- `);
17926
- return { role, content };
17927
- });
17928
- const result = handleObserve({ messages: flat, sessionId }, log7);
17929
- if (!result)
17930
- return;
17931
- safeWriteLog(PLUGIN_NAME11, {
17932
- hook: "experimental.chat.messages.transform",
17933
- mode: "observe-only",
17934
- sessionId: result.sessionId,
17935
- triggered: result.triggered,
17936
- cooldown_skipped: result.cooldown_skipped ?? false,
17937
- reason: result.reason ?? null,
17938
- msgs: flat.length
17939
- });
17940
- });
17941
- }
17942
- };
17943
- };
17944
- var handler11 = khReminderServer;
17945
-
17946
- // lib/memories.ts
17947
- import { promises as fs15 } from "node:fs";
17948
- import * as path19 from "node:path";
17949
- import * as os5 from "node:os";
17950
- function resolveConfig(c) {
17951
- return {
17952
- projectRoot: c.projectRoot,
17953
- homeDir: c.homeDir ?? os5.homedir(),
17954
- projectName: c.projectName ?? path19.basename(c.projectRoot),
17955
- kh: c.kh,
17956
- now: c.now ?? Date.now,
17957
- log: c.log ?? (() => {}),
17958
- maxPerScope: c.maxPerScope ?? 1000
17959
- };
17960
- }
17961
- function fileFor(scope, cfg) {
17962
- if (scope === "project") {
17963
- return path19.join(cfg.projectRoot, ".codeforge", "memories.json");
17964
- }
17965
- return path19.join(cfg.homeDir, ".codeforge", "memories.json");
17966
- }
17967
- async function readBank(p) {
17968
- try {
17969
- const raw = await fs15.readFile(p, "utf8");
17970
- const arr = JSON.parse(raw);
17971
- if (!Array.isArray(arr))
17972
- return [];
17973
- return arr.filter((x) => isMemory(x));
17974
- } catch {
17975
- return [];
17976
- }
17977
- }
17978
- async function writeBank(p, items) {
17979
- await fs15.mkdir(path19.dirname(p), { recursive: true });
17980
- const tmp = `${p}.tmp`;
17981
- await fs15.writeFile(tmp, JSON.stringify(items, null, 2), "utf8");
17982
- await fs15.rename(tmp, p);
17983
- }
17984
- function isMemory(x) {
17985
- if (!x || typeof x !== "object")
17986
- return false;
17987
- const m = x;
17988
- return typeof m.id === "string" && (m.scope === "project" || m.scope === "user") && typeof m.content === "string" && typeof m.created_at === "number" && typeof m.updated_at === "number";
17989
- }
17990
- var _seq = 0;
17991
- function localId(now) {
17992
- _seq = (_seq + 1) % 1e5;
17993
- return `mem-${now}-${_seq.toString(36)}`;
17994
- }
17995
- async function addMemory(params, cfgRaw) {
17996
- const cfg = resolveConfig(cfgRaw);
17997
- if (!params.content || params.content.trim().length === 0) {
17998
- return { ok: false, id: "", written_to: "local", error: "content 不能为空" };
17999
- }
18000
- const now = cfg.now();
18001
- const base = {
18002
- scope: params.scope,
18003
- content: params.content.trim(),
18004
- tags: params.tags?.filter((t) => typeof t === "string") ?? [],
18005
- project: params.scope === "project" ? cfg.projectName : undefined,
18006
- created_at: now,
18007
- updated_at: now,
18008
- source: params.source ?? "manual"
18009
- };
18010
- if (cfg.kh) {
18011
- try {
18012
- const r = await cfg.kh.add(base);
18013
- cfg.log("info", `[memories] add → KH ${r.id}`);
18014
- return { ok: true, id: r.id, written_to: "kh" };
18015
- } catch (err) {
18016
- cfg.log("warn", `[memories] KH 写入失败,降级本地`, {
18017
- error: err instanceof Error ? err.message : String(err)
18018
- });
18019
- }
18020
- }
18021
- const file = fileFor(params.scope, cfg);
18022
- const id = localId(now);
18023
- const m = { id, ...base };
18024
- const items = await readBank(file);
18025
- items.push(m);
18026
- if (items.length > cfg.maxPerScope) {
18027
- items.splice(0, items.length - cfg.maxPerScope);
18028
- }
18029
- await writeBank(file, items);
18030
- return { ok: true, id, written_to: "local" };
16956
+ await writeBank(file, items);
16957
+ return { ok: true, id, written_to: "local" };
18031
16958
  }
18032
16959
  async function listMemories(opts, cfgRaw) {
18033
16960
  const cfg = resolveConfig(cfgRaw);
@@ -18142,9 +17069,9 @@ function bagOfWordsScore(query, doc) {
18142
17069
  }
18143
17070
 
18144
17071
  // plugins/memories-context.ts
18145
- var PLUGIN_NAME12 = "memories-context";
18146
- var INJECTION_MODE2 = "observe-only";
18147
- var DEFAULT_CONFIG5 = {
17072
+ var PLUGIN_NAME9 = "memories-context";
17073
+ var INJECTION_MODE = "observe-only";
17074
+ var DEFAULT_CONFIG3 = {
18148
17075
  minTextLength: 8,
18149
17076
  limit: 5,
18150
17077
  perItemLimit: 240,
@@ -18155,7 +17082,7 @@ var DEFAULT_CONFIG5 = {
18155
17082
  skipKeywords: ["你好", "hello", "hi", "thanks", "谢谢"]
18156
17083
  };
18157
17084
 
18158
- class QueryCache2 {
17085
+ class QueryCache {
18159
17086
  ttlMs;
18160
17087
  now;
18161
17088
  map = new Map;
@@ -18180,7 +17107,7 @@ class QueryCache2 {
18180
17107
  }
18181
17108
  }
18182
17109
  var FILLER_RE = /^(请|帮我|麻烦|我想|你能|你可以|看一?下|查一?下|how (do|can) (i|you)|can you|could you|please|help me)\s*/i;
18183
- function extractQuery2(text) {
17110
+ function extractQuery(text) {
18184
17111
  if (!text)
18185
17112
  return "";
18186
17113
  const norm = text.trim().replace(/\s+/g, " ");
@@ -18193,7 +17120,7 @@ function extractQuery2(text) {
18193
17120
  }
18194
17121
  return head.slice(0, 100);
18195
17122
  }
18196
- function shouldRecall(text, cfg = DEFAULT_CONFIG5) {
17123
+ function shouldRecall(text, cfg = DEFAULT_CONFIG3) {
18197
17124
  if (!text)
18198
17125
  return false;
18199
17126
  const t = text.trim();
@@ -18227,28 +17154,28 @@ function parseDirective(text) {
18227
17154
  return { kind: "list" };
18228
17155
  return { kind: "none" };
18229
17156
  }
18230
- async function handleMessage3(raw, opts) {
18231
- const cfg = opts.cfg ?? DEFAULT_CONFIG5;
18232
- const cache2 = opts.cache ?? new QueryCache2(cfg.cacheTtlMs);
17157
+ async function handleMessage2(raw, opts) {
17158
+ const cfg = opts.cfg ?? DEFAULT_CONFIG3;
17159
+ const cache2 = opts.cache ?? new QueryCache(cfg.cacheTtlMs);
18233
17160
  const ctx = raw ?? {};
18234
- const log8 = ctx.log;
17161
+ const log7 = ctx.log;
18235
17162
  const text = (ctx.content ?? "").trim();
18236
17163
  if (!text)
18237
- return { kind: "noop", reason: "empty", mode: INJECTION_MODE2 };
17164
+ return { kind: "noop", reason: "empty", mode: INJECTION_MODE };
18238
17165
  const dir = parseDirective(text);
18239
17166
  if (dir.kind !== "none") {
18240
- return await handleDirective(dir, ctx, opts.memCfg, log8);
17167
+ return await handleDirective(dir, ctx, opts.memCfg, log7);
18241
17168
  }
18242
17169
  if (!shouldRecall(text, cfg)) {
18243
- log8?.debug?.(`[${PLUGIN_NAME12}] skip (filter)`, { textLen: text.length });
18244
- return { kind: "noop", reason: "filtered", mode: INJECTION_MODE2 };
17170
+ log7?.debug?.(`[${PLUGIN_NAME9}] skip (filter)`, { textLen: text.length });
17171
+ return { kind: "noop", reason: "filtered", mode: INJECTION_MODE };
18245
17172
  }
18246
- const query = extractQuery2(text);
17173
+ const query = extractQuery(text);
18247
17174
  if (!query)
18248
- return { kind: "noop", reason: "empty_query", mode: INJECTION_MODE2 };
17175
+ return { kind: "noop", reason: "empty_query", mode: INJECTION_MODE };
18249
17176
  if (cache2.shouldSkip(query)) {
18250
- log8?.debug?.(`[${PLUGIN_NAME12}] cache hit`, { query });
18251
- return { kind: "noop", reason: "cache", mode: INJECTION_MODE2 };
17177
+ log7?.debug?.(`[${PLUGIN_NAME9}] cache hit`, { query });
17178
+ return { kind: "noop", reason: "cache", mode: INJECTION_MODE };
18252
17179
  }
18253
17180
  const racer = opts.scheduler?.raceTimeout ?? (async (p, ms) => {
18254
17181
  let timer = null;
@@ -18272,43 +17199,43 @@ async function handleMessage3(raw, opts) {
18272
17199
  }, opts.memCfg);
18273
17200
  const result = await racer(injectPromise, cfg.timeoutMs);
18274
17201
  if (result === "__timeout__") {
18275
- log8?.warn(`[${PLUGIN_NAME12}] timeout`, { query, ms: cfg.timeoutMs });
17202
+ log7?.warn(`[${PLUGIN_NAME9}] timeout`, { query, ms: cfg.timeoutMs });
18276
17203
  cache2.record(query, 0);
18277
- return { kind: "noop", reason: "timeout", mode: INJECTION_MODE2 };
17204
+ return { kind: "noop", reason: "timeout", mode: INJECTION_MODE };
18278
17205
  }
18279
17206
  if (result.recalled === 0 || result.used === 0) {
18280
17207
  cache2.record(query, 0);
18281
- return { kind: "noop", reason: "no_match", mode: INJECTION_MODE2 };
17208
+ return { kind: "noop", reason: "no_match", mode: INJECTION_MODE };
18282
17209
  }
18283
17210
  if (typeof ctx.injectContext === "function") {
18284
17211
  try {
18285
17212
  await ctx.injectContext(result.text);
18286
17213
  } catch (err) {
18287
- log8?.warn(`[${PLUGIN_NAME12}] injectContext threw`, {
17214
+ log7?.warn(`[${PLUGIN_NAME9}] injectContext threw`, {
18288
17215
  error: err instanceof Error ? err.message : String(err)
18289
17216
  });
18290
17217
  }
18291
17218
  }
18292
17219
  cache2.record(query, result.recalled);
18293
- log8?.info(`[${PLUGIN_NAME12}] observe-only: ${result.used}/${result.recalled} memories ready`, { query, mode: INJECTION_MODE2 });
18294
- return { kind: "injected", payload: result, mode: INJECTION_MODE2 };
17220
+ log7?.info(`[${PLUGIN_NAME9}] observe-only: ${result.used}/${result.recalled} memories ready`, { query, mode: INJECTION_MODE });
17221
+ return { kind: "injected", payload: result, mode: INJECTION_MODE };
18295
17222
  }
18296
- async function handleDirective(dir, ctx, memCfg, log8) {
17223
+ async function handleDirective(dir, ctx, memCfg, log7) {
18297
17224
  if (dir.kind === "remember") {
18298
17225
  const r = await addMemory({ scope: dir.scope, content: dir.content, source: "directive" }, memCfg);
18299
17226
  if (r.ok) {
18300
17227
  const target = r.written_to === "kh" ? "KH" : "本地";
18301
17228
  await safeReply(ctx, `\uD83E\uDDE0 已记住(${dir.scope} / ${target},id=${r.id})`);
18302
- log8?.info(`[${PLUGIN_NAME12}] /remember ok`, {
17229
+ log7?.info(`[${PLUGIN_NAME9}] /remember ok`, {
18303
17230
  scope: dir.scope,
18304
17231
  id: r.id,
18305
- mode: INJECTION_MODE2
17232
+ mode: INJECTION_MODE
18306
17233
  });
18307
17234
  } else {
18308
17235
  await safeReply(ctx, `❌ 记忆失败:${r.error ?? "unknown"}`);
18309
- log8?.warn(`[${PLUGIN_NAME12}] /remember failed`, { error: r.error });
17236
+ log7?.warn(`[${PLUGIN_NAME9}] /remember failed`, { error: r.error });
18310
17237
  }
18311
- return { kind: "remembered", result: r, mode: INJECTION_MODE2 };
17238
+ return { kind: "remembered", result: r, mode: INJECTION_MODE };
18312
17239
  }
18313
17240
  if (dir.kind === "forget") {
18314
17241
  const r = await removeMemory({ scope: "project", id: dir.id }, memCfg);
@@ -18318,17 +17245,17 @@ async function handleDirective(dir, ctx, memCfg, log8) {
18318
17245
  const r2 = await removeMemory({ scope: "user", id: dir.id }, memCfg);
18319
17246
  if (r2.removed) {
18320
17247
  await safeReply(ctx, `\uD83D\uDDD1 已遗忘记忆 ${dir.id}(user scope)`);
18321
- return { kind: "forgotten", result: r2, mode: INJECTION_MODE2 };
17248
+ return { kind: "forgotten", result: r2, mode: INJECTION_MODE };
18322
17249
  }
18323
17250
  await safeReply(ctx, `⚠ 找不到记忆 ${dir.id}`);
18324
17251
  }
18325
- return { kind: "forgotten", result: r, mode: INJECTION_MODE2 };
17252
+ return { kind: "forgotten", result: r, mode: INJECTION_MODE };
18326
17253
  }
18327
17254
  const proj = await listMemories({ scope: "project" }, memCfg);
18328
17255
  const user = await listMemories({ scope: "user" }, memCfg);
18329
17256
  const total = proj.length + user.length;
18330
17257
  await safeReply(ctx, `\uD83D\uDCDA 共 ${total} 条记忆(project=${proj.length}, user=${user.length})`);
18331
- return { kind: "listed", total, mode: INJECTION_MODE2 };
17258
+ return { kind: "listed", total, mode: INJECTION_MODE };
18332
17259
  }
18333
17260
  async function safeReply(ctx, text) {
18334
17261
  if (typeof ctx.reply !== "function")
@@ -18337,58 +17264,58 @@ async function safeReply(ctx, text) {
18337
17264
  await ctx.reply(text);
18338
17265
  } catch {}
18339
17266
  }
18340
- logLifecycle(PLUGIN_NAME12, "import");
18341
- var sharedCache2 = new QueryCache2(DEFAULT_CONFIG5.cacheTtlMs);
17267
+ logLifecycle(PLUGIN_NAME9, "import");
17268
+ var sharedCache = new QueryCache(DEFAULT_CONFIG3.cacheTtlMs);
18342
17269
  function buildMemCfg(directory) {
18343
17270
  return { projectRoot: directory };
18344
17271
  }
18345
17272
  var memoriesContextServer = async (ctx) => {
18346
- const log8 = makePluginLogger(PLUGIN_NAME12);
17273
+ const log7 = makePluginLogger(PLUGIN_NAME9);
18347
17274
  const memCfg = buildMemCfg(ctx.directory);
18348
- logLifecycle(PLUGIN_NAME12, "activate", {
17275
+ logLifecycle(PLUGIN_NAME9, "activate", {
18349
17276
  directory: ctx.directory,
18350
17277
  projectRoot: memCfg.projectRoot,
18351
- mode: INJECTION_MODE2
17278
+ mode: INJECTION_MODE
18352
17279
  });
18353
17280
  return {
18354
17281
  "chat.message": async (input, output) => {
18355
- await safeAsync(PLUGIN_NAME12, "chat.message", async () => {
17282
+ await safeAsync(PLUGIN_NAME9, "chat.message", async () => {
18356
17283
  const text = extractUserText(output);
18357
17284
  if (!text)
18358
17285
  return;
18359
- await handleMessage3({
17286
+ await handleMessage2({
18360
17287
  content: text,
18361
17288
  sessionId: input.sessionID,
18362
17289
  injectContext: async (markdown) => {
18363
- log8.info(`memories context candidate (${markdown.length} chars)`, {
17290
+ log7.info(`memories context candidate (${markdown.length} chars)`, {
18364
17291
  sessionID: input.sessionID,
18365
- mode: INJECTION_MODE2,
17292
+ mode: INJECTION_MODE,
18366
17293
  preview: markdown.slice(0, 200)
18367
17294
  });
18368
17295
  },
18369
17296
  reply: async (msg) => {
18370
- log8.info(`directive reply (observe-only): ${msg}`, {
17297
+ log7.info(`directive reply (observe-only): ${msg}`, {
18371
17298
  sessionID: input.sessionID,
18372
- mode: INJECTION_MODE2
17299
+ mode: INJECTION_MODE
18373
17300
  });
18374
17301
  },
18375
- log: log8
18376
- }, { cache: sharedCache2, memCfg });
17302
+ log: log7
17303
+ }, { cache: sharedCache, memCfg });
18377
17304
  });
18378
17305
  }
18379
17306
  };
18380
17307
  };
18381
- var handler12 = memoriesContextServer;
17308
+ var handler9 = memoriesContextServer;
18382
17309
 
18383
17310
  // plugins/model-fallback.ts
18384
- var PLUGIN_NAME13 = "model-fallback";
17311
+ var PLUGIN_NAME10 = "model-fallback";
18385
17312
  var state = {
18386
17313
  config: null,
18387
17314
  configPath: null,
18388
17315
  warnings: [],
18389
17316
  error: null
18390
17317
  };
18391
- logLifecycle(PLUGIN_NAME13, "import");
17318
+ logLifecycle(PLUGIN_NAME10, "import");
18392
17319
  function loadOnce(root) {
18393
17320
  if (state.config !== null || state.error !== null)
18394
17321
  return;
@@ -18398,11 +17325,11 @@ function loadOnce(root) {
18398
17325
  state.configPath = r.path ?? null;
18399
17326
  state.warnings = r.warnings;
18400
17327
  if (r.warnings.length > 0) {
18401
- safeWriteLog(PLUGIN_NAME13, { phase: "load.warnings", warnings: r.warnings });
17328
+ safeWriteLog(PLUGIN_NAME10, { phase: "load.warnings", warnings: r.warnings });
18402
17329
  }
18403
17330
  } else {
18404
17331
  state.error = r.error ?? "unknown_load_error";
18405
- safeWriteLog(PLUGIN_NAME13, { phase: "load.failed", error: state.error, path: r.path });
17332
+ safeWriteLog(PLUGIN_NAME10, { phase: "load.failed", error: state.error, path: r.path });
18406
17333
  }
18407
17334
  }
18408
17335
  var MODEL_ERR_PATTERNS = [
@@ -18447,9 +17374,9 @@ fallback 链已用尽:${meta.chain.join(" → ")}`;
18447
17374
  完整链:${meta.chain.join(" → ")}`;
18448
17375
  }
18449
17376
  var modelFallbackServer = async (ctx) => {
18450
- const log8 = makePluginLogger(PLUGIN_NAME13);
17377
+ const log7 = makePluginLogger(PLUGIN_NAME10);
18451
17378
  loadOnce(ctx.directory ?? process.cwd());
18452
- logLifecycle(PLUGIN_NAME13, "activate", {
17379
+ logLifecycle(PLUGIN_NAME10, "activate", {
18453
17380
  directory: ctx.directory,
18454
17381
  config_path: state.configPath,
18455
17382
  config_loaded: state.config !== null,
@@ -18459,7 +17386,7 @@ var modelFallbackServer = async (ctx) => {
18459
17386
  });
18460
17387
  return {
18461
17388
  "chat.params": async (input, output) => {
18462
- await safeAsync(PLUGIN_NAME13, "chat.params", async () => {
17389
+ await safeAsync(PLUGIN_NAME10, "chat.params", async () => {
18463
17390
  if (!state.config)
18464
17391
  return;
18465
17392
  const agent = input.agent;
@@ -18480,7 +17407,7 @@ var modelFallbackServer = async (ctx) => {
18480
17407
  next: meta.next_fallback,
18481
17408
  source: meta.source
18482
17409
  };
18483
- safeWriteLog(PLUGIN_NAME13, {
17410
+ safeWriteLog(PLUGIN_NAME10, {
18484
17411
  hook: "chat.params",
18485
17412
  agent,
18486
17413
  model: currentModel,
@@ -18490,7 +17417,7 @@ var modelFallbackServer = async (ctx) => {
18490
17417
  });
18491
17418
  },
18492
17419
  event: async ({ event }) => {
18493
- await safeAsync(PLUGIN_NAME13, "event", async () => {
17420
+ await safeAsync(PLUGIN_NAME10, "event", async () => {
18494
17421
  if (!state.config)
18495
17422
  return;
18496
17423
  const e = event;
@@ -18506,8 +17433,8 @@ var modelFallbackServer = async (ctx) => {
18506
17433
  const model = props.model ?? "unknown/unknown";
18507
17434
  const meta = buildFallbackMeta(state.config, agent, model);
18508
17435
  const suggestion = meta ? buildSuggestion(meta, String(message)) : `⚠️ ${agent}/${model} 失败:${message}`;
18509
- log8.warn(`[${PLUGIN_NAME13}] ${suggestion}`);
18510
- safeWriteLog(PLUGIN_NAME13, {
17436
+ log7.warn(`[${PLUGIN_NAME10}] ${suggestion}`);
17437
+ safeWriteLog(PLUGIN_NAME10, {
18511
17438
  hook: "event.error",
18512
17439
  eventType: e.type,
18513
17440
  agent,
@@ -18519,7 +17446,7 @@ var modelFallbackServer = async (ctx) => {
18519
17446
  }
18520
17447
  };
18521
17448
  };
18522
- var handler13 = modelFallbackServer;
17449
+ var handler10 = modelFallbackServer;
18523
17450
 
18524
17451
  // plugins/subtask-heartbeat.ts
18525
17452
  import { promises as fsPromises } from "node:fs";
@@ -18533,8 +17460,8 @@ var sweepExpiredSessionParents2 = sweepExpiredSessionParents;
18533
17460
  var _bulkInjectSessionParentMap2 = _bulkInjectSessionParentMap;
18534
17461
  var _capSessionParentMap2 = _capSessionParentMap;
18535
17462
  var _setPersistRootForTests2 = _setPersistRootForTests;
18536
- var PLUGIN_NAME14 = "subtask-heartbeat";
18537
- logLifecycle(PLUGIN_NAME14, "import", {});
17463
+ var PLUGIN_NAME11 = "subtask-heartbeat";
17464
+ logLifecycle(PLUGIN_NAME11, "import", {});
18538
17465
  var HEARTBEAT_INTERVAL_MS2 = 30000;
18539
17466
  var HEARTBEAT_DEBOUNCE_MS = 25000;
18540
17467
  var TOAST_DURATION_MS2 = 5000;
@@ -18544,11 +17471,11 @@ var PENDING_TASK_MAX_PARENTS = 64;
18544
17471
  var PENDING_TASK_MAX_PER_PARENT = 16;
18545
17472
  var DESCRIPTION_MAX_LEN = 60;
18546
17473
  var PARENT_PARSE_FAIL_MAX_LOG = 10;
18547
- var inflight3 = new Map;
17474
+ var inflight2 = new Map;
18548
17475
  var pendingTask = new Map;
18549
17476
  var _parentParseFailLogged = 0;
18550
17477
  function _snapshotInflight() {
18551
- return [...inflight3.values()].map((r) => ({ ...r }));
17478
+ return [...inflight2.values()].map((r) => ({ ...r }));
18552
17479
  }
18553
17480
  function detectUnparsedParentID(event) {
18554
17481
  if (!event || typeof event !== "object")
@@ -18684,11 +17611,11 @@ function registerInflight(payload, now = Date.now()) {
18684
17611
  lastTool: null,
18685
17612
  pendingCalls: new Map
18686
17613
  };
18687
- inflight3.set(payload.childID, r);
17614
+ inflight2.set(payload.childID, r);
18688
17615
  return r;
18689
17616
  }
18690
17617
  function recordToolBeat(sessionID, tool2, now = Date.now()) {
18691
- const r = inflight3.get(sessionID);
17618
+ const r = inflight2.get(sessionID);
18692
17619
  if (!r)
18693
17620
  return null;
18694
17621
  r.lastBeatAt = now;
@@ -18696,15 +17623,15 @@ function recordToolBeat(sessionID, tool2, now = Date.now()) {
18696
17623
  return r;
18697
17624
  }
18698
17625
  function clearInflight2(sessionID) {
18699
- const r = inflight3.get(sessionID);
17626
+ const r = inflight2.get(sessionID);
18700
17627
  if (!r)
18701
17628
  return null;
18702
- inflight3.delete(sessionID);
17629
+ inflight2.delete(sessionID);
18703
17630
  return r;
18704
17631
  }
18705
17632
  function pickHeartbeats(now = Date.now()) {
18706
17633
  const out = [];
18707
- for (const r of inflight3.values()) {
17634
+ for (const r of inflight2.values()) {
18708
17635
  if (now - r.lastBeatAt >= HEARTBEAT_DEBOUNCE_MS)
18709
17636
  out.push(r);
18710
17637
  }
@@ -18823,13 +17750,13 @@ function buildAfterLogLine(toolName, ok, durationMs, now = Date.now()) {
18823
17750
  duration_ms: durationMs
18824
17751
  });
18825
17752
  }
18826
- async function appendSubagentLog(filePath, line, log8) {
17753
+ async function appendSubagentLog(filePath, line, log7) {
18827
17754
  try {
18828
17755
  await fsPromises.mkdir(path20.dirname(filePath), { recursive: true });
18829
17756
  await fsPromises.appendFile(filePath, line + `
18830
17757
  `, "utf8");
18831
17758
  } catch (err) {
18832
- log8?.debug?.("appendSubagentLog 失败(已隔离)", {
17759
+ log7?.debug?.("appendSubagentLog 失败(已隔离)", {
18833
17760
  error: err instanceof Error ? err.message : String(err),
18834
17761
  file: filePath
18835
17762
  });
@@ -18854,9 +17781,9 @@ function normalizeVariant2(raw) {
18854
17781
  return "default";
18855
17782
  return raw;
18856
17783
  }
18857
- async function showToast2(client, payload, log8) {
17784
+ async function showToast2(client, payload, log7) {
18858
17785
  if (typeof client?.tui?.showToast !== "function") {
18859
- log8?.debug?.("tui.showToast 不可用,noop");
17786
+ log7?.debug?.("tui.showToast 不可用,noop");
18860
17787
  return false;
18861
17788
  }
18862
17789
  try {
@@ -18870,15 +17797,15 @@ async function showToast2(client, payload, log8) {
18870
17797
  });
18871
17798
  return true;
18872
17799
  } catch (err) {
18873
- log8?.warn("tui.showToast 抛错(已隔离)", {
17800
+ log7?.warn("tui.showToast 抛错(已隔离)", {
18874
17801
  error: err instanceof Error ? err.message : String(err)
18875
17802
  });
18876
17803
  return false;
18877
17804
  }
18878
17805
  }
18879
- var log8 = makePluginLogger(PLUGIN_NAME14);
17806
+ var log7 = makePluginLogger(PLUGIN_NAME11);
18880
17807
  var subtaskHeartbeatServer = async (ctx) => {
18881
- logLifecycle(PLUGIN_NAME14, "activate", {
17808
+ logLifecycle(PLUGIN_NAME11, "activate", {
18882
17809
  directory: ctx.directory,
18883
17810
  intervalMs: HEARTBEAT_INTERVAL_MS2
18884
17811
  });
@@ -18895,7 +17822,7 @@ var subtaskHeartbeatServer = async (ctx) => {
18895
17822
  }));
18896
17823
  _bulkInjectSessionParentMap2(entries);
18897
17824
  const cappedOut = _capSessionParentMap2();
18898
- safeWriteLog(PLUGIN_NAME14, {
17825
+ safeWriteLog(PLUGIN_NAME11, {
18899
17826
  hook: "activate",
18900
17827
  type: "parent-map.restore",
18901
17828
  restored: restored.size,
@@ -18903,27 +17830,27 @@ var subtaskHeartbeatServer = async (ctx) => {
18903
17830
  });
18904
17831
  }
18905
17832
  } catch (err) {
18906
- log8.warn("loadParentMap 失败(已隔离),降级为空表", {
17833
+ log7.warn("loadParentMap 失败(已隔离),降级为空表", {
18907
17834
  error: err instanceof Error ? err.message : String(err)
18908
17835
  });
18909
17836
  }
18910
17837
  const interval = setInterval(() => {
18911
- safeAsync(PLUGIN_NAME14, "interval", async () => {
17838
+ safeAsync(PLUGIN_NAME11, "interval", async () => {
18912
17839
  const swept = sweepExpiredPendingTasks();
18913
17840
  if (swept > 0) {
18914
- safeWriteLog(PLUGIN_NAME14, { hook: "interval", pending_task_swept: swept });
17841
+ safeWriteLog(PLUGIN_NAME11, { hook: "interval", pending_task_swept: swept });
18915
17842
  }
18916
17843
  const sweptParents = sweepExpiredSessionParents2();
18917
17844
  if (sweptParents > 0) {
18918
- safeWriteLog(PLUGIN_NAME14, { hook: "interval", session_parent_swept: sweptParents });
17845
+ safeWriteLog(PLUGIN_NAME11, { hook: "interval", session_parent_swept: sweptParents });
18919
17846
  }
18920
17847
  const beats = pickHeartbeats();
18921
17848
  if (beats.length === 0)
18922
17849
  return;
18923
17850
  for (const r of beats) {
18924
17851
  const t = buildHeartbeatToast(r);
18925
- const sent = await showToast2(client, t, log8);
18926
- safeWriteLog(PLUGIN_NAME14, {
17852
+ const sent = await showToast2(client, t, log7);
17853
+ safeWriteLog(PLUGIN_NAME11, {
18927
17854
  hook: "interval",
18928
17855
  child: r.childID,
18929
17856
  parent: r.parentID,
@@ -18940,15 +17867,15 @@ var subtaskHeartbeatServer = async (ctx) => {
18940
17867
  }
18941
17868
  return {
18942
17869
  event: async ({ event }) => {
18943
- await safeAsync(PLUGIN_NAME14, "event", async () => {
17870
+ await safeAsync(PLUGIN_NAME11, "event", async () => {
18944
17871
  try {
18945
17872
  if (detectUnparsedParentID(event) && _parentParseFailLogged < PARENT_PARSE_FAIL_MAX_LOG) {
18946
17873
  _parentParseFailLogged++;
18947
- log8.warn("session.created 含 parentID 关键字但 extractCreatedChild 解析失败,可能 SDK schema 变更", {
17874
+ log7.warn("session.created 含 parentID 关键字但 extractCreatedChild 解析失败,可能 SDK schema 变更", {
18948
17875
  sample_count: _parentParseFailLogged,
18949
17876
  hint: "如频繁出现请检查 plugins/subtask-heartbeat.ts::extractCreatedChild 是否适配新 SDK"
18950
17877
  });
18951
- safeWriteLog(PLUGIN_NAME14, {
17878
+ safeWriteLog(PLUGIN_NAME11, {
18952
17879
  hook: "event",
18953
17880
  type: "session.created.unparsed-parent-id",
18954
17881
  sample_count: _parentParseFailLogged
@@ -18960,7 +17887,7 @@ var subtaskHeartbeatServer = async (ctx) => {
18960
17887
  try {
18961
17888
  recordSessionParent2(created.childID, created.parentID);
18962
17889
  } catch (err) {
18963
- log8.warn("recordSessionParent 抛错(已隔离)", {
17890
+ log7.warn("recordSessionParent 抛错(已隔离)", {
18964
17891
  error: err instanceof Error ? err.message : String(err)
18965
17892
  });
18966
17893
  }
@@ -18971,7 +17898,7 @@ var subtaskHeartbeatServer = async (ctx) => {
18971
17898
  agent: pending?.agent ?? created.agent,
18972
17899
  description: pending?.description ?? null
18973
17900
  });
18974
- safeWriteLog(PLUGIN_NAME14, {
17901
+ safeWriteLog(PLUGIN_NAME11, {
18975
17902
  hook: "event",
18976
17903
  type: "session.created",
18977
17904
  child: created.childID,
@@ -18981,8 +17908,8 @@ var subtaskHeartbeatServer = async (ctx) => {
18981
17908
  description_len: record.description?.length ?? 0
18982
17909
  });
18983
17910
  const startToast = buildStartToast(record);
18984
- const sent = await showToast2(client, { ...startToast, duration: START_TOAST_DURATION_MS }, log8);
18985
- safeWriteLog(PLUGIN_NAME14, {
17911
+ const sent = await showToast2(client, { ...startToast, duration: START_TOAST_DURATION_MS }, log7);
17912
+ safeWriteLog(PLUGIN_NAME11, {
18986
17913
  hook: "event",
18987
17914
  type: "session.created.toast",
18988
17915
  child: created.childID,
@@ -19001,8 +17928,8 @@ var subtaskHeartbeatServer = async (ctx) => {
19001
17928
  const r = clearInflight2(ended.sessionID);
19002
17929
  if (r) {
19003
17930
  const t = buildEndToast(r, ended.type);
19004
- const sent = await showToast2(client, t, log8);
19005
- safeWriteLog(PLUGIN_NAME14, {
17931
+ const sent = await showToast2(client, t, log7);
17932
+ safeWriteLog(PLUGIN_NAME11, {
19006
17933
  hook: "event",
19007
17934
  type: ended.type,
19008
17935
  child: r.childID,
@@ -19020,14 +17947,14 @@ var subtaskHeartbeatServer = async (ctx) => {
19020
17947
  },
19021
17948
  "tool.execute.before": async (input, output) => {
19022
17949
  const isTaskTool = input?.tool === "task";
19023
- if (inflight3.size === 0 && !isTaskTool)
17950
+ if (inflight2.size === 0 && !isTaskTool)
19024
17951
  return;
19025
- await safeAsync(PLUGIN_NAME14, "tool.execute.before", async () => {
17952
+ await safeAsync(PLUGIN_NAME11, "tool.execute.before", async () => {
19026
17953
  if (!input || typeof input.sessionID !== "string" || typeof input.tool !== "string")
19027
17954
  return;
19028
17955
  if (isTaskTool) {
19029
17956
  const args = output?.args ?? null;
19030
- safeWriteLog(PLUGIN_NAME14, {
17957
+ safeWriteLog(PLUGIN_NAME11, {
19031
17958
  hook: "tool.execute.before.task",
19032
17959
  sessionID: input.sessionID,
19033
17960
  args_keys: args && typeof args === "object" ? Object.keys(args) : null,
@@ -19039,7 +17966,7 @@ var subtaskHeartbeatServer = async (ctx) => {
19039
17966
  agent: extracted.subagentType,
19040
17967
  description: extracted.description
19041
17968
  });
19042
- safeWriteLog(PLUGIN_NAME14, {
17969
+ safeWriteLog(PLUGIN_NAME11, {
19043
17970
  hook: "tool.execute.before.task.enqueued",
19044
17971
  parent: input.sessionID,
19045
17972
  agent: extracted.subagentType,
@@ -19047,7 +17974,7 @@ var subtaskHeartbeatServer = async (ctx) => {
19047
17974
  });
19048
17975
  }
19049
17976
  }
19050
- const rec = inflight3.get(input.sessionID);
17977
+ const rec = inflight2.get(input.sessionID);
19051
17978
  if (rec) {
19052
17979
  recordToolBeat(input.sessionID, input.tool);
19053
17980
  if (!rec.pendingCalls)
@@ -19059,18 +17986,18 @@ var subtaskHeartbeatServer = async (ctx) => {
19059
17986
  const args = output?.args ?? null;
19060
17987
  const line = buildBeforeLogLine(input.tool, args);
19061
17988
  const file = subagentLogPath(cwd, rec.parentID, rec.childID);
19062
- appendSubagentLog(file, line, log8);
17989
+ appendSubagentLog(file, line, log7);
19063
17990
  }
19064
17991
  }
19065
17992
  });
19066
17993
  },
19067
17994
  "tool.execute.after": async (input, _output) => {
19068
- if (inflight3.size === 0)
17995
+ if (inflight2.size === 0)
19069
17996
  return;
19070
- await safeAsync(PLUGIN_NAME14, "tool.execute.after", async () => {
17997
+ await safeAsync(PLUGIN_NAME11, "tool.execute.after", async () => {
19071
17998
  if (!input || typeof input.sessionID !== "string" || typeof input.tool !== "string")
19072
17999
  return;
19073
- const rec = inflight3.get(input.sessionID);
18000
+ const rec = inflight2.get(input.sessionID);
19074
18001
  if (!rec)
19075
18002
  return;
19076
18003
  let durationMs = 0;
@@ -19084,17 +18011,17 @@ var subtaskHeartbeatServer = async (ctx) => {
19084
18011
  if (isLogPersistenceEnabled(cwd)) {
19085
18012
  const line = buildAfterLogLine(input.tool, true, durationMs);
19086
18013
  const file = subagentLogPath(cwd, rec.parentID, rec.childID);
19087
- appendSubagentLog(file, line, log8);
18014
+ appendSubagentLog(file, line, log7);
19088
18015
  }
19089
18016
  });
19090
18017
  }
19091
18018
  };
19092
18019
  };
19093
- var handler14 = subtaskHeartbeatServer;
18020
+ var handler11 = subtaskHeartbeatServer;
19094
18021
 
19095
18022
  // plugins/parallel-status.ts
19096
- var PLUGIN_NAME15 = "parallel-status";
19097
- logLifecycle(PLUGIN_NAME15, "import");
18023
+ var PLUGIN_NAME12 = "parallel-status";
18024
+ logLifecycle(PLUGIN_NAME12, "import");
19098
18025
  var ID_MAX_LEN = 16;
19099
18026
  var ID_KEEP_LEN = 13;
19100
18027
  function shortId(s) {
@@ -19126,8 +18053,8 @@ function formatInflightMarkdown(snapshot, now = Date.now()) {
19126
18053
  `);
19127
18054
  }
19128
18055
  var parallelStatusServer = async (ctx) => {
19129
- const log9 = makePluginLogger(PLUGIN_NAME15);
19130
- logLifecycle(PLUGIN_NAME15, "activate", { directory: ctx.directory });
18056
+ const log8 = makePluginLogger(PLUGIN_NAME12);
18057
+ logLifecycle(PLUGIN_NAME12, "activate", { directory: ctx.directory });
19131
18058
  return {
19132
18059
  "command.execute.before": async (input, output) => {
19133
18060
  try {
@@ -19146,23 +18073,23 @@ var parallelStatusServer = async (ctx) => {
19146
18073
  synthetic: false
19147
18074
  });
19148
18075
  }
19149
- log9.info(`[${PLUGIN_NAME15}] 已回写 ${snapshot.length} 条 inflight`);
18076
+ log8.info(`[${PLUGIN_NAME12}] 已回写 ${snapshot.length} 条 inflight`);
19150
18077
  } catch (err) {
19151
- log9.error(`[${PLUGIN_NAME15}] command.execute.before 异常(已隔离)`, {
18078
+ log8.error(`[${PLUGIN_NAME12}] command.execute.before 异常(已隔离)`, {
19152
18079
  error: err instanceof Error ? err.message : String(err)
19153
18080
  });
19154
18081
  }
19155
18082
  }
19156
18083
  };
19157
18084
  };
19158
- var handler15 = parallelStatusServer;
18085
+ var handler12 = parallelStatusServer;
19159
18086
 
19160
18087
  // plugins/parallel-tool-nudge.ts
19161
18088
  import { readFileSync as readFileSync4, readdirSync as readdirSync2, statSync as statSync4 } from "node:fs";
19162
18089
  import { join as join17 } from "node:path";
19163
18090
  import { homedir as homedir6 } from "node:os";
19164
- var PLUGIN_NAME16 = "parallel-tool-nudge";
19165
- logLifecycle(PLUGIN_NAME16, "import", {});
18091
+ var PLUGIN_NAME13 = "parallel-tool-nudge";
18092
+ logLifecycle(PLUGIN_NAME13, "import", {});
19166
18093
  var PARALLEL_SAFE_TOOLS = [
19167
18094
  "read",
19168
18095
  "smart_search",
@@ -19224,7 +18151,7 @@ function loadAgentToolsMap(rootDir, opts = {}) {
19224
18151
  const result = new Map;
19225
18152
  const safeSet = new Set(PARALLEL_SAFE_TOOLS);
19226
18153
  const unionTools = new Set;
19227
- const log9 = makePluginLogger(PLUGIN_NAME16);
18154
+ const log8 = makePluginLogger(PLUGIN_NAME13);
19228
18155
  for (const dir of candidateDirs) {
19229
18156
  if (!dirExists(dir))
19230
18157
  continue;
@@ -19232,7 +18159,7 @@ function loadAgentToolsMap(rootDir, opts = {}) {
19232
18159
  try {
19233
18160
  entries = dirReader(dir);
19234
18161
  } catch (err) {
19235
- log9.warn(`agents 目录读取失败(已跳过)`, {
18162
+ log8.warn(`agents 目录读取失败(已跳过)`, {
19236
18163
  dir,
19237
18164
  error: err instanceof Error ? err.message : String(err)
19238
18165
  });
@@ -19246,7 +18173,7 @@ function loadAgentToolsMap(rootDir, opts = {}) {
19246
18173
  try {
19247
18174
  content = reader(path21);
19248
18175
  } catch (err) {
19249
- log9.warn(`agent.md 读取失败(已跳过)`, {
18176
+ log8.warn(`agent.md 读取失败(已跳过)`, {
19250
18177
  path: path21,
19251
18178
  error: err instanceof Error ? err.message : String(err)
19252
18179
  });
@@ -19254,7 +18181,7 @@ function loadAgentToolsMap(rootDir, opts = {}) {
19254
18181
  }
19255
18182
  const parsed = parseAgentFrontmatter(content);
19256
18183
  if (!parsed) {
19257
- log9.warn(`agent frontmatter 解析失败(已跳过)`, { path: path21 });
18184
+ log8.warn(`agent frontmatter 解析失败(已跳过)`, { path: path21 });
19258
18185
  continue;
19259
18186
  }
19260
18187
  if (result.has(parsed.name))
@@ -19332,7 +18259,7 @@ This directive is re-injected every turn — it is not optional advice.
19332
18259
  return body.slice(0, NUDGE_MAX_LEN2 - 4) + `
19333
18260
  …`;
19334
18261
  }
19335
- var log9 = makePluginLogger(PLUGIN_NAME16);
18262
+ var log8 = makePluginLogger(PLUGIN_NAME13);
19336
18263
  var parallelToolNudgeServer = async (ctx) => {
19337
18264
  try {
19338
18265
  const loaded = loadAgentToolsMap(ctx.directory ?? process.cwd());
@@ -19340,19 +18267,19 @@ var parallelToolNudgeServer = async (ctx) => {
19340
18267
  for (const [k, v] of loaded.entries())
19341
18268
  agentToolsMap.set(k, v);
19342
18269
  } catch (err) {
19343
- log9.warn(`loadAgentToolsMap 失败(plugin 将 no-op)`, {
18270
+ log8.warn(`loadAgentToolsMap 失败(plugin 将 no-op)`, {
19344
18271
  error: err instanceof Error ? err.message : String(err)
19345
18272
  });
19346
18273
  }
19347
18274
  const realAgentCount = Math.max(0, agentToolsMap.size - (agentToolsMap.has(DEFAULT_AGENT_KEY) ? 1 : 0));
19348
18275
  if (realAgentCount === 0) {
19349
18276
  agentToolsMap.clear();
19350
- log9.warn(`0 real agents loaded; plugin will be no-op for this session`, {
18277
+ log8.warn(`0 real agents loaded; plugin will be no-op for this session`, {
19351
18278
  directory: ctx.directory
19352
18279
  });
19353
18280
  }
19354
18281
  const defaultTools = agentToolsMap.get(DEFAULT_AGENT_KEY) ?? [];
19355
- logLifecycle(PLUGIN_NAME16, "activate", {
18282
+ logLifecycle(PLUGIN_NAME13, "activate", {
19356
18283
  directory: ctx.directory,
19357
18284
  real_agents_loaded: realAgentCount,
19358
18285
  default_tools_union: defaultTools,
@@ -19363,7 +18290,7 @@ var parallelToolNudgeServer = async (ctx) => {
19363
18290
  });
19364
18291
  return {
19365
18292
  "chat.params": async (input, _output) => {
19366
- await safeAsync(PLUGIN_NAME16, "chat.params", async () => {
18293
+ await safeAsync(PLUGIN_NAME13, "chat.params", async () => {
19367
18294
  if (agentToolsMap.size === 0)
19368
18295
  return;
19369
18296
  const sid = input?.sessionID;
@@ -19375,7 +18302,7 @@ var parallelToolNudgeServer = async (ctx) => {
19375
18302
  });
19376
18303
  },
19377
18304
  "experimental.chat.system.transform": async (input, output) => {
19378
- await safeAsync(PLUGIN_NAME16, "experimental.chat.system.transform", async () => {
18305
+ await safeAsync(PLUGIN_NAME13, "experimental.chat.system.transform", async () => {
19379
18306
  if (agentToolsMap.size === 0)
19380
18307
  return;
19381
18308
  if (!output || !Array.isArray(output.system))
@@ -19385,7 +18312,7 @@ var parallelToolNudgeServer = async (ctx) => {
19385
18312
  const tools = agent !== "unknown" ? agentToolsMap.get(agent) ?? agentToolsMap.get(DEFAULT_AGENT_KEY) ?? [] : agentToolsMap.get(DEFAULT_AGENT_KEY) ?? [];
19386
18313
  const nudge = renderNudge(agent, tools);
19387
18314
  output.system.push(nudge);
19388
- safeWriteLog(PLUGIN_NAME16, {
18315
+ safeWriteLog(PLUGIN_NAME13, {
19389
18316
  hook: "experimental.chat.system.transform",
19390
18317
  sessionID: sid,
19391
18318
  agent,
@@ -19397,11 +18324,11 @@ var parallelToolNudgeServer = async (ctx) => {
19397
18324
  }
19398
18325
  };
19399
18326
  };
19400
- var handler16 = parallelToolNudgeServer;
18327
+ var handler13 = parallelToolNudgeServer;
19401
18328
 
19402
18329
  // plugins/pwsh-utf8.ts
19403
- var PLUGIN_NAME17 = "pwsh-utf8";
19404
- logLifecycle(PLUGIN_NAME17, "import", {});
18330
+ var PLUGIN_NAME14 = "pwsh-utf8";
18331
+ logLifecycle(PLUGIN_NAME14, "import", {});
19405
18332
  var PRELUDE = "chcp 65001 *> $null; " + "[Console]::OutputEncoding = [System.Text.UTF8Encoding]::new(); " + "$OutputEncoding = [System.Text.UTF8Encoding]::new(); ";
19406
18333
  function prependUtf8Prelude(command) {
19407
18334
  if (typeof command !== "string")
@@ -19414,15 +18341,15 @@ function prependUtf8Prelude(command) {
19414
18341
  return command;
19415
18342
  return PRELUDE + command;
19416
18343
  }
19417
- var handler17 = async (_ctx3) => {
18344
+ var handler14 = async (_ctx3) => {
19418
18345
  const enabled = process.platform === "win32" && process.env.CODEFORGE_DISABLE_PWSH_UTF8 !== "1";
19419
18346
  const reason = enabled ? "win32" : process.platform !== "win32" ? "non-win32" : "disabled-by-env";
19420
- logLifecycle(PLUGIN_NAME17, "activate", { enabled, platform: process.platform, reason });
18347
+ logLifecycle(PLUGIN_NAME14, "activate", { enabled, platform: process.platform, reason });
19421
18348
  if (!enabled)
19422
18349
  return {};
19423
18350
  return {
19424
18351
  "tool.execute.before": async (input, output) => {
19425
- await safeAsync(PLUGIN_NAME17, "tool.execute.before", async () => {
18352
+ await safeAsync(PLUGIN_NAME14, "tool.execute.before", async () => {
19426
18353
  if (input.tool !== "bash")
19427
18354
  return;
19428
18355
  const args = output.args ?? {};
@@ -19431,7 +18358,7 @@ var handler17 = async (_ctx3) => {
19431
18358
  if (next !== undefined && next !== original) {
19432
18359
  args["command"] = next;
19433
18360
  output.args = args;
19434
- safeWriteLog(PLUGIN_NAME17, {
18361
+ safeWriteLog(PLUGIN_NAME14, {
19435
18362
  hook: "tool.execute.before",
19436
18363
  tool: input.tool,
19437
18364
  callID: input.callID,
@@ -19587,7 +18514,7 @@ function buildRecoveryPlan(events, meta = { id: null }, opts = {}) {
19587
18514
  for (let i = events.length - 1;i >= 0; i--) {
19588
18515
  const e = events[i];
19589
18516
  if (e.type === "message" && e.role === "user") {
19590
- lastUser = clip5(e.content, o.excerptLimit);
18517
+ lastUser = clip4(e.content, o.excerptLimit);
19591
18518
  break;
19592
18519
  }
19593
18520
  }
@@ -19605,7 +18532,7 @@ function buildRecoveryPlan(events, meta = { id: null }, opts = {}) {
19605
18532
  const toolMap = new Map;
19606
18533
  for (const t of window) {
19607
18534
  const cur = toolMap.get(t.tool);
19608
- const argsExcerpt = clip5(safeJson(t.args), o.excerptLimit);
18535
+ const argsExcerpt = clip4(safeJson(t.args), o.excerptLimit);
19609
18536
  if (cur) {
19610
18537
  cur.count++;
19611
18538
  cur.last_ok = t.ok;
@@ -19621,7 +18548,7 @@ function buildRecoveryPlan(events, meta = { id: null }, opts = {}) {
19621
18548
  });
19622
18549
  }
19623
18550
  }
19624
- const inflight4 = [...toolMap.values()].sort((a, b) => b.last_ts - a.last_ts);
18551
+ const inflight3 = [...toolMap.values()].sort((a, b) => b.last_ts - a.last_ts);
19625
18552
  const proposed = window.some((t) => PENDING_CHANGES_TOOLS.has(t.tool));
19626
18553
  const applied = window.some((t) => APPLY_TOOLS.has(t.tool) && t.ok);
19627
18554
  const pending_changes_likely = proposed && !applied;
@@ -19645,7 +18572,7 @@ function buildRecoveryPlan(events, meta = { id: null }, opts = {}) {
19645
18572
  idleMs,
19646
18573
  lastUser,
19647
18574
  lastAgent,
19648
- inflight: inflight4,
18575
+ inflight: inflight3,
19649
18576
  pending_changes_likely,
19650
18577
  open_subtasks_likely,
19651
18578
  reason
@@ -19656,7 +18583,7 @@ function buildRecoveryPlan(events, meta = { id: null }, opts = {}) {
19656
18583
  idle_ms: idleMs,
19657
18584
  last_user_intent: lastUser,
19658
18585
  last_agent: lastAgent,
19659
- inflight_tools: inflight4,
18586
+ inflight_tools: inflight3,
19660
18587
  pending_changes_likely,
19661
18588
  open_subtasks_likely,
19662
18589
  summary
@@ -19694,7 +18621,7 @@ function formatIdle(ms) {
19694
18621
  return `${Math.round(ms / 3600000)}h`;
19695
18622
  return `${Math.round(ms / 86400000)}d`;
19696
18623
  }
19697
- function clip5(s, max) {
18624
+ function clip4(s, max) {
19698
18625
  if (!s)
19699
18626
  return "";
19700
18627
  if (s.length <= max)
@@ -19847,8 +18774,8 @@ async function markBlocksConsumed(absRoot, entries) {
19847
18774
  }
19848
18775
 
19849
18776
  // plugins/session-recovery.ts
19850
- var PLUGIN_NAME18 = "session-recovery";
19851
- logLifecycle(PLUGIN_NAME18, "import", {});
18777
+ var PLUGIN_NAME15 = "session-recovery";
18778
+ logLifecycle(PLUGIN_NAME15, "import", {});
19852
18779
  async function processSessionStart(currentSessionId, opts = {}) {
19853
18780
  if (opts.disabled) {
19854
18781
  return { ok: true, injected: false, reason: "disabled" };
@@ -19858,7 +18785,7 @@ async function processSessionStart(currentSessionId, opts = {}) {
19858
18785
  excludeIds.add(currentSessionId);
19859
18786
  const r = await scanLastSession({ ...opts, excludeIds: [...excludeIds] });
19860
18787
  if (!r.ok) {
19861
- opts.log?.warn?.(`[${PLUGIN_NAME18}] 扫描失败:${r.error}`);
18788
+ opts.log?.warn?.(`[${PLUGIN_NAME15}] 扫描失败:${r.error}`);
19862
18789
  return { ok: false, injected: false, reason: "scan_error", error: r.error };
19863
18790
  }
19864
18791
  const plan = r.plan;
@@ -19867,7 +18794,7 @@ async function processSessionStart(currentSessionId, opts = {}) {
19867
18794
  try {
19868
18795
  pendingBlocks = await scanBlockPending(opts.root);
19869
18796
  } catch (err) {
19870
- opts.log?.warn?.(`[${PLUGIN_NAME18}] scanBlockPending 异常:${err instanceof Error ? err.message : String(err)}`);
18797
+ opts.log?.warn?.(`[${PLUGIN_NAME15}] scanBlockPending 异常:${err instanceof Error ? err.message : String(err)}`);
19871
18798
  }
19872
18799
  }
19873
18800
  const hasPendingBlocks = pendingBlocks.length > 0;
@@ -19885,7 +18812,7 @@ async function processSessionStart(currentSessionId, opts = {}) {
19885
18812
  await opts.injectRecovery(injection);
19886
18813
  } catch (err) {
19887
18814
  const msg = err instanceof Error ? err.message : String(err);
19888
- opts.log?.warn?.(`[${PLUGIN_NAME18}] injectRecovery 异常:${msg}`);
18815
+ opts.log?.warn?.(`[${PLUGIN_NAME15}] injectRecovery 异常:${msg}`);
19889
18816
  return { ok: false, injected: false, plan, reason: "inject_error", error: msg, pendingBlocks };
19890
18817
  }
19891
18818
  }
@@ -19893,7 +18820,7 @@ async function processSessionStart(currentSessionId, opts = {}) {
19893
18820
  try {
19894
18821
  await markBlocksConsumed(opts.root, pendingBlocks);
19895
18822
  } catch (err) {
19896
- opts.log?.warn?.(`[${PLUGIN_NAME18}] markBlocksConsumed 异常:${err instanceof Error ? err.message : String(err)}`);
18823
+ opts.log?.warn?.(`[${PLUGIN_NAME15}] markBlocksConsumed 异常:${err instanceof Error ? err.message : String(err)}`);
19897
18824
  }
19898
18825
  }
19899
18826
  return { ok: true, injected: true, plan, reason: "ok", pendingBlocks };
@@ -19940,13 +18867,13 @@ function renderPrompt(plan) {
19940
18867
  return lines.join(`
19941
18868
  `);
19942
18869
  }
19943
- var log10 = makePluginLogger(PLUGIN_NAME18);
18870
+ var log9 = makePluginLogger(PLUGIN_NAME15);
19944
18871
  var _lastInjection = null;
19945
18872
  var sessionRecoveryServer = async (ctx) => {
19946
- logLifecycle(PLUGIN_NAME18, "activate", { directory: ctx.directory });
18873
+ logLifecycle(PLUGIN_NAME15, "activate", { directory: ctx.directory });
19947
18874
  return {
19948
18875
  event: async ({ event }) => {
19949
- await safeAsync(PLUGIN_NAME18, "event", async () => {
18876
+ await safeAsync(PLUGIN_NAME15, "event", async () => {
19950
18877
  const e = event;
19951
18878
  if (!e || typeof e.type !== "string")
19952
18879
  return;
@@ -19956,12 +18883,12 @@ var sessionRecoveryServer = async (ctx) => {
19956
18883
  const root = typeof e.properties?.["root"] === "string" ? e.properties["root"] : ctx.directory ?? process.cwd();
19957
18884
  const r = await processSessionStart(sid, {
19958
18885
  root,
19959
- log: log10,
18886
+ log: log9,
19960
18887
  injectRecovery: (inj) => {
19961
18888
  _lastInjection = inj;
19962
18889
  }
19963
18890
  });
19964
- safeWriteLog(PLUGIN_NAME18, {
18891
+ safeWriteLog(PLUGIN_NAME15, {
19965
18892
  hook: "event",
19966
18893
  type: "session.start",
19967
18894
  ok: r.ok,
@@ -19971,13 +18898,13 @@ var sessionRecoveryServer = async (ctx) => {
19971
18898
  pending_blocks_count: r.pendingBlocks?.length ?? 0
19972
18899
  });
19973
18900
  if (r.injected && r.plan) {
19974
- log10.info(`[${PLUGIN_NAME18}] 注入恢复提示(last=${r.plan.last_session_id?.slice(0, 8) ?? "?"}, reason=${r.plan.reason}, pending_blocks=${r.pendingBlocks?.length ?? 0})`);
18901
+ log9.info(`[${PLUGIN_NAME15}] 注入恢复提示(last=${r.plan.last_session_id?.slice(0, 8) ?? "?"}, reason=${r.plan.reason}, pending_blocks=${r.pendingBlocks?.length ?? 0})`);
19975
18902
  }
19976
18903
  });
19977
18904
  }
19978
18905
  };
19979
18906
  };
19980
- var handler18 = sessionRecoveryServer;
18907
+ var handler15 = sessionRecoveryServer;
19981
18908
 
19982
18909
  // plugins/subtasks.ts
19983
18910
  import { promises as fs18 } from "node:fs";
@@ -19995,7 +18922,7 @@ function resolveMergeFns(deps) {
19995
18922
  worktreeHasChangesFn: deps.worktreeHasChanges ?? worktreeHasChanges
19996
18923
  };
19997
18924
  }
19998
- async function mergeOneAttempt(r, opts, mergeFns, log11) {
18925
+ async function mergeOneAttempt(r, opts, mergeFns, log10) {
19999
18926
  const t0 = Date.now();
20000
18927
  const root = opts.mergeRoot;
20001
18928
  const {
@@ -20077,7 +19004,7 @@ async function mergeOneAttempt(r, opts, mergeFns, log11) {
20077
19004
  await mergeAbortFn({ root });
20078
19005
  } catch (abortErr) {
20079
19006
  abortFailed = true;
20080
- log11("error", `[parallel-merge] mergeAbort 失败(仓库可能锁死)`, {
19007
+ log10("error", `[parallel-merge] mergeAbort 失败(仓库可能锁死)`, {
20081
19008
  id: r.id,
20082
19009
  error: describe6(abortErr)
20083
19010
  });
@@ -20093,13 +19020,13 @@ async function mergeOneAttempt(r, opts, mergeFns, log11) {
20093
19020
  }
20094
19021
  attempt.ok = true;
20095
19022
  attempt.durationMs = Date.now() - t0;
20096
- await safeRemoveWorktree(removeWorktreeFn, root, wt, log11, r.id);
19023
+ await safeRemoveWorktree(removeWorktreeFn, root, wt, log10, r.id);
20097
19024
  return { attempt };
20098
19025
  } else {
20099
19026
  attempt.ok = true;
20100
19027
  attempt.skippedReason = "no_changes";
20101
19028
  attempt.durationMs = Date.now() - t0;
20102
- await safeRemoveWorktree(removeWorktreeFn, root, wt, log11, r.id);
19029
+ await safeRemoveWorktree(removeWorktreeFn, root, wt, log10, r.id);
20103
19030
  return { attempt };
20104
19031
  }
20105
19032
  } catch (err) {
@@ -20113,7 +19040,7 @@ async function mergeOneAttempt(r, opts, mergeFns, log11) {
20113
19040
  }
20114
19041
  }
20115
19042
  async function mergeWorktrees(results, opts, deps) {
20116
- const log11 = deps.log ?? (() => {});
19043
+ const log10 = deps.log ?? (() => {});
20117
19044
  const root = opts.mergeRoot;
20118
19045
  const mergeFns = resolveMergeFns(deps);
20119
19046
  const report = {
@@ -20155,10 +19082,10 @@ async function mergeWorktrees(results, opts, deps) {
20155
19082
  };
20156
19083
  report.attempts.push(attempt);
20157
19084
  report.skipped++;
20158
- await fireMergeAttempt(opts.onMergeAttempt, attempt, idx++, log11);
19085
+ await fireMergeAttempt(opts.onMergeAttempt, attempt, idx++, log10);
20159
19086
  }
20160
19087
  for (const r of mergeable) {
20161
- const { attempt, pendingItem } = await mergeOneAttempt(r, { mergeRoot: root }, mergeFns, log11);
19088
+ const { attempt, pendingItem } = await mergeOneAttempt(r, { mergeRoot: root }, mergeFns, log10);
20162
19089
  report.attempts.push(attempt);
20163
19090
  if (attempt.ok && !attempt.skippedReason)
20164
19091
  report.merged++;
@@ -20168,12 +19095,12 @@ async function mergeWorktrees(results, opts, deps) {
20168
19095
  report.conflicted++;
20169
19096
  if (pendingItem)
20170
19097
  report.pendingWorktrees.push(pendingItem);
20171
- await fireMergeAttempt(opts.onMergeAttempt, attempt, idx++, log11);
19098
+ await fireMergeAttempt(opts.onMergeAttempt, attempt, idx++, log10);
20172
19099
  }
20173
19100
  return report;
20174
19101
  }
20175
19102
  function createMergeQueue(opts, deps = {}) {
20176
- const log11 = deps.log ?? (() => {});
19103
+ const log10 = deps.log ?? (() => {});
20177
19104
  const mergeFns = {
20178
19105
  commitWorktreeIfDirtyFn: deps.commitWorktreeIfDirtyFn ?? commitWorktreeIfDirty,
20179
19106
  tryMergeFn: deps.tryMergeFn ?? tryMerge,
@@ -20214,10 +19141,10 @@ function createMergeQueue(opts, deps = {}) {
20214
19141
  conflicts: []
20215
19142
  });
20216
19143
  }
20217
- await fireMergeAttempt(opts.onMergeAttempt, attempt2, idx++, log11);
19144
+ await fireMergeAttempt(opts.onMergeAttempt, attempt2, idx++, log10);
20218
19145
  return;
20219
19146
  }
20220
- const { attempt, abortFailed, pendingItem } = await mergeOneAttempt(result, { mergeRoot: opts.mergeRoot, summaryCharLimit: opts.summaryCharLimit }, mergeFns, log11);
19147
+ const { attempt, abortFailed, pendingItem } = await mergeOneAttempt(result, { mergeRoot: opts.mergeRoot, summaryCharLimit: opts.summaryCharLimit }, mergeFns, log10);
20221
19148
  report.attempts.push(attempt);
20222
19149
  if (attempt.ok && !attempt.skippedReason)
20223
19150
  report.merged++;
@@ -20229,11 +19156,11 @@ function createMergeQueue(opts, deps = {}) {
20229
19156
  report.pendingWorktrees.push(pendingItem);
20230
19157
  if (abortFailed) {
20231
19158
  queueAborted = true;
20232
- log11("error", `[parallel-merge] queue aborted (mergeAbort failed)`, {
19159
+ log10("error", `[parallel-merge] queue aborted (mergeAbort failed)`, {
20233
19160
  id: result.id
20234
19161
  });
20235
19162
  }
20236
- await fireMergeAttempt(opts.onMergeAttempt, attempt, idx++, log11);
19163
+ await fireMergeAttempt(opts.onMergeAttempt, attempt, idx++, log10);
20237
19164
  });
20238
19165
  },
20239
19166
  async flushAndReport() {
@@ -20242,23 +19169,23 @@ function createMergeQueue(opts, deps = {}) {
20242
19169
  }
20243
19170
  };
20244
19171
  }
20245
- async function safeRemoveWorktree(fn, root, wt, log11, subtaskId) {
19172
+ async function safeRemoveWorktree(fn, root, wt, log10, subtaskId) {
20246
19173
  try {
20247
19174
  await fn({ root, worktree_path: wt, force: true });
20248
19175
  } catch (err) {
20249
- log11("warn", `[parallel] removeWorktree 失败 ${subtaskId}`, {
19176
+ log10("warn", `[parallel] removeWorktree 失败 ${subtaskId}`, {
20250
19177
  error: describe6(err),
20251
19178
  worktree: wt
20252
19179
  });
20253
19180
  }
20254
19181
  }
20255
- async function fireMergeAttempt(cb, attempt, idx, log11) {
19182
+ async function fireMergeAttempt(cb, attempt, idx, log10) {
20256
19183
  if (!cb)
20257
19184
  return;
20258
19185
  try {
20259
19186
  await cb(attempt, idx);
20260
19187
  } catch (err) {
20261
- log11("warn", `[parallel] onMergeAttempt 抛错(已隔离)`, {
19188
+ log10("warn", `[parallel] onMergeAttempt 抛错(已隔离)`, {
20262
19189
  id: attempt.subtaskId,
20263
19190
  error: describe6(err)
20264
19191
  });
@@ -20284,7 +19211,7 @@ async function schedule(opts) {
20284
19211
  }
20285
19212
  const now = opts.deps.now ?? Date.now;
20286
19213
  const start = now();
20287
- const log11 = opts.deps.log ?? (() => {});
19214
+ const log10 = opts.deps.log ?? (() => {});
20288
19215
  const concurrency = Math.max(1, opts.maxConcurrency ?? 4);
20289
19216
  const totalTimeout = opts.totalTimeout_ms ?? 30 * 60000;
20290
19217
  const limit = Math.max(1, opts.summaryCharLimit ?? 500);
@@ -20317,7 +19244,7 @@ async function schedule(opts) {
20317
19244
  mergeAbortFn: opts.deps.mergeAbort,
20318
19245
  removeWorktreeFn: opts.deps.removeWorktree,
20319
19246
  worktreeHasChangesFn: opts.deps.worktreeHasChanges,
20320
- log: log11
19247
+ log: log10
20321
19248
  });
20322
19249
  }
20323
19250
  const fireFinish = async (i, res) => {
@@ -20325,7 +19252,7 @@ async function schedule(opts) {
20325
19252
  try {
20326
19253
  queue.enqueue(res);
20327
19254
  } catch (err) {
20328
- log11("warn", `[parallel] queue.enqueue 抛错(已隔离)`, {
19255
+ log10("warn", `[parallel] queue.enqueue 抛错(已隔离)`, {
20329
19256
  id: res.id,
20330
19257
  error: describe7(err)
20331
19258
  });
@@ -20336,7 +19263,7 @@ async function schedule(opts) {
20336
19263
  try {
20337
19264
  await opts.onSubtaskFinish(res, i);
20338
19265
  } catch (err) {
20339
- log11("warn", `[parallel] onSubtaskFinish 抛错(已隔离)`, {
19266
+ log10("warn", `[parallel] onSubtaskFinish 抛错(已隔离)`, {
20340
19267
  id: res.id,
20341
19268
  error: describe7(err)
20342
19269
  });
@@ -20366,7 +19293,7 @@ async function schedule(opts) {
20366
19293
  try {
20367
19294
  await opts.onSubtaskStart(spec, i);
20368
19295
  } catch (err) {
20369
- log11("warn", `[parallel] onSubtaskStart 抛错(已隔离)`, {
19296
+ log10("warn", `[parallel] onSubtaskStart 抛错(已隔离)`, {
20370
19297
  id: spec.id,
20371
19298
  error: describe7(err)
20372
19299
  });
@@ -20417,7 +19344,7 @@ async function schedule(opts) {
20417
19344
  try {
20418
19345
  await alloc.cleanup();
20419
19346
  } catch (err) {
20420
- log11("warn", `[parallel] worktree 清理失败 ${spec.id}`, {
19347
+ log10("warn", `[parallel] worktree 清理失败 ${spec.id}`, {
20421
19348
  error: describe7(err)
20422
19349
  });
20423
19350
  }
@@ -20597,20 +19524,20 @@ function buildSystemPrompt(maxSubtasks) {
20597
19524
  `);
20598
19525
  }
20599
19526
  async function decomposeTask(description30, opts) {
20600
- const log11 = opts.log ?? (() => {});
19527
+ const log10 = opts.log ?? (() => {});
20601
19528
  if (opts.mockResponse) {
20602
- return validateAndFinalize(opts.mockResponse, undefined, log11, opts.maxSubtasks ?? DEFAULT_MAX_SUBTASKS);
19529
+ return validateAndFinalize(opts.mockResponse, undefined, log10, opts.maxSubtasks ?? DEFAULT_MAX_SUBTASKS);
20603
19530
  }
20604
19531
  const maxSubtasks = opts.maxSubtasks ?? DEFAULT_MAX_SUBTASKS;
20605
19532
  const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS2;
20606
19533
  let childSessionId;
20607
19534
  try {
20608
19535
  const created = await opts.client.session.create({
20609
- body: { title: `decompose:${clip6(description30, 60)}` },
19536
+ body: { title: `decompose:${clip5(description30, 60)}` },
20610
19537
  query: opts.directory ? { directory: opts.directory } : undefined
20611
19538
  });
20612
19539
  if (created.error || !created.data?.id) {
20613
- log11("warn", "[decompose] session.create 失败", { error: describe8(created.error) });
19540
+ log10("warn", "[decompose] session.create 失败", { error: describe8(created.error) });
20614
19541
  return {
20615
19542
  ok: false,
20616
19543
  subtasks: [],
@@ -20632,22 +19559,22 @@ async function decomposeTask(description30, opts) {
20632
19559
  sleep2(timeoutMs).then(() => ({ kind: "timeout" }))
20633
19560
  ]);
20634
19561
  if (raced.kind === "timeout") {
20635
- log11("warn", "[decompose] LLM 调用超时", { timeoutMs });
19562
+ log10("warn", "[decompose] LLM 调用超时", { timeoutMs });
20636
19563
  return { ok: false, subtasks: [], reason: "llm_unavailable" };
20637
19564
  }
20638
19565
  const r = raced.value;
20639
19566
  if (r.error || !r.data) {
20640
- log11("warn", "[decompose] session.prompt 返回错误", { error: describe8(r.error) });
19567
+ log10("warn", "[decompose] session.prompt 返回错误", { error: describe8(r.error) });
20641
19568
  return { ok: false, subtasks: [], reason: "llm_unavailable" };
20642
19569
  }
20643
19570
  const rawText = pickLastText2(r.data.parts ?? []);
20644
19571
  if (!rawText) {
20645
- log11("warn", "[decompose] LLM 输出为空");
19572
+ log10("warn", "[decompose] LLM 输出为空");
20646
19573
  return { ok: false, subtasks: [], reason: "parse_failed", raw: "" };
20647
19574
  }
20648
19575
  const parsed = extractJson(rawText);
20649
19576
  if (!parsed) {
20650
- log11("warn", "[decompose] JSON 解析失败", { raw: clip6(rawText, 200) });
19577
+ log10("warn", "[decompose] JSON 解析失败", { raw: clip5(rawText, 200) });
20651
19578
  return { ok: false, subtasks: [], reason: "parse_failed", raw: rawText };
20652
19579
  }
20653
19580
  if (parsed && typeof parsed === "object" && parsed.single_task === true) {
@@ -20655,7 +19582,7 @@ async function decomposeTask(description30, opts) {
20655
19582
  }
20656
19583
  const subtasksRaw = parsed.subtasks;
20657
19584
  if (!Array.isArray(subtasksRaw)) {
20658
- log11("warn", "[decompose] JSON 缺 subtasks 数组");
19585
+ log10("warn", "[decompose] JSON 缺 subtasks 数组");
20659
19586
  return { ok: false, subtasks: [], reason: "parse_failed", raw: rawText };
20660
19587
  }
20661
19588
  const normalized = [];
@@ -20671,12 +19598,12 @@ async function decomposeTask(description30, opts) {
20671
19598
  normalized.push({ description: desc, hintFiles });
20672
19599
  }
20673
19600
  if (normalized.length > maxSubtasks) {
20674
- log11("warn", "[decompose] LLM 返回子任务超过上限", { count: normalized.length, maxSubtasks });
19601
+ log10("warn", "[decompose] LLM 返回子任务超过上限", { count: normalized.length, maxSubtasks });
20675
19602
  return { ok: false, subtasks: [], reason: "parse_failed", raw: rawText };
20676
19603
  }
20677
- return validateAndFinalize(normalized, rawText, log11, maxSubtasks);
19604
+ return validateAndFinalize(normalized, rawText, log10, maxSubtasks);
20678
19605
  } catch (err) {
20679
- log11("warn", "[decompose] 抛错", { error: describe8(err) });
19606
+ log10("warn", "[decompose] 抛错", { error: describe8(err) });
20680
19607
  return { ok: false, subtasks: [], reason: "llm_unavailable" };
20681
19608
  } finally {
20682
19609
  if (childSessionId) {
@@ -20686,12 +19613,12 @@ async function decomposeTask(description30, opts) {
20686
19613
  query: opts.directory ? { directory: opts.directory } : undefined
20687
19614
  });
20688
19615
  } catch (err) {
20689
- log11("warn", "[decompose] session.delete 失败", { error: describe8(err) });
19616
+ log10("warn", "[decompose] session.delete 失败", { error: describe8(err) });
20690
19617
  }
20691
19618
  }
20692
19619
  }
20693
19620
  }
20694
- function validateAndFinalize(subtasks, raw, log11, maxSubtasks) {
19621
+ function validateAndFinalize(subtasks, raw, log10, maxSubtasks) {
20695
19622
  if (subtasks.length < 2) {
20696
19623
  return { ok: false, subtasks: [], reason: "single_task", raw };
20697
19624
  }
@@ -20701,7 +19628,7 @@ function validateAndFinalize(subtasks, raw, log11, maxSubtasks) {
20701
19628
  }
20702
19629
  }
20703
19630
  if (subtasks.length > maxSubtasks) {
20704
- log11("warn", "[decompose] LLM 返回子任务超过上限", { count: subtasks.length, maxSubtasks });
19631
+ log10("warn", "[decompose] LLM 返回子任务超过上限", { count: subtasks.length, maxSubtasks });
20705
19632
  return { ok: false, subtasks: [], reason: "parse_failed", raw };
20706
19633
  }
20707
19634
  for (let i = 0;i < subtasks.length; i++) {
@@ -20715,7 +19642,7 @@ function validateAndFinalize(subtasks, raw, log11, maxSubtasks) {
20715
19642
  continue;
20716
19643
  for (const f of b) {
20717
19644
  if (setA.has(f.trim())) {
20718
- log11("info", "[decompose] hintFiles 有交集,降级 single_task", {
19645
+ log10("info", "[decompose] hintFiles 有交集,降级 single_task", {
20719
19646
  a: subtasks[i].description,
20720
19647
  b: subtasks[j].description,
20721
19648
  file: f
@@ -20780,7 +19707,7 @@ function tryParse(s) {
20780
19707
  return null;
20781
19708
  }
20782
19709
  }
20783
- function clip6(s, n) {
19710
+ function clip5(s, n) {
20784
19711
  if (!s)
20785
19712
  return "";
20786
19713
  return s.length <= n ? s : s.slice(0, n - 1) + "…";
@@ -20804,7 +19731,7 @@ function sleep2(ms) {
20804
19731
 
20805
19732
  // plugins/subtasks.ts
20806
19733
  init_runtime_paths();
20807
- var PLUGIN_NAME19 = "subtasks";
19734
+ var PLUGIN_NAME16 = "subtasks";
20808
19735
  function getLogFile(root = process.cwd()) {
20809
19736
  return path23.join(runtimeDir(root), "logs", "subtasks.log");
20810
19737
  }
@@ -20870,7 +19797,7 @@ var mockRunner = async (spec) => {
20870
19797
  };
20871
19798
  async function handleParallelCommand(raw) {
20872
19799
  const ctx = raw ?? {};
20873
- const log11 = ctx.log;
19800
+ const log10 = ctx.log;
20874
19801
  const args = ctx.args ?? {};
20875
19802
  const parentId = (args.parentId ?? `task-${Date.now().toString(36)}`).replace(/[^a-zA-Z0-9._-]/g, "-");
20876
19803
  const rawDescription = typeof args.description === "string" ? args.description.trim() : "";
@@ -20884,7 +19811,7 @@ async function handleParallelCommand(raw) {
20884
19811
  specs = splitDescriptions(args.description, { parentId });
20885
19812
  }
20886
19813
  if (specs.length === 0) {
20887
- log11?.warn(`[${PLUGIN_NAME19}] /parallel 缺有效子任务`);
19814
+ log10?.warn(`[${PLUGIN_NAME16}] /parallel 缺有效子任务`);
20888
19815
  await safeReply2(ctx, "⚠ /parallel 需要至少 1 个子任务(用 ; 或换行分隔)");
20889
19816
  return { ok: false, reason: "no_subtasks" };
20890
19817
  }
@@ -20910,7 +19837,7 @@ async function handleParallelCommand(raw) {
20910
19837
  if (!gitOk) {
20911
19838
  autoMerge = false;
20912
19839
  downgradedAutoMerge = true;
20913
- log11?.warn("[subtasks] mergeRoot 非 git 仓库,降级 autoMerge=false");
19840
+ log10?.warn("[subtasks] mergeRoot 非 git 仓库,降级 autoMerge=false");
20914
19841
  const canNoticeEarly = Boolean(ctx.client && ctx.parentSessionID);
20915
19842
  if (canNoticeEarly) {
20916
19843
  await sendParentNotice(ctx.client, ctx.parentSessionID, "ℹ 当前目录不是 git 仓库,worktree 隔离已自动关闭,以单目录模式运行", { directory: ctx.directory });
@@ -20919,17 +19846,17 @@ async function handleParallelCommand(raw) {
20919
19846
  }
20920
19847
  const canNotice = Boolean(ctx.client && ctx.parentSessionID);
20921
19848
  if (specs.length === 1 && rawDescription.length >= 30 && ctx.client) {
20922
- log11?.info(`[${PLUGIN_NAME19}] 触发 AI 拆分(描述长度=${rawDescription.length})`);
19849
+ log10?.info(`[${PLUGIN_NAME16}] 触发 AI 拆分(描述长度=${rawDescription.length})`);
20923
19850
  const dec = await decomposeTask(rawDescription, {
20924
19851
  client: ctx.client,
20925
19852
  directory: ctx.directory,
20926
19853
  log: (lvl, msg, data) => {
20927
19854
  if (lvl === "error")
20928
- log11?.error(msg, data);
19855
+ log10?.error(msg, data);
20929
19856
  else if (lvl === "warn")
20930
- log11?.warn(msg, data);
19857
+ log10?.warn(msg, data);
20931
19858
  else
20932
- log11?.info(msg, data);
19859
+ log10?.info(msg, data);
20933
19860
  },
20934
19861
  mockResponse: ctx.decomposeMockResponse
20935
19862
  });
@@ -20940,7 +19867,7 @@ async function handleParallelCommand(raw) {
20940
19867
  timeout_ms: undefined,
20941
19868
  args: d.hintFiles && d.hintFiles.length > 0 ? { hintFiles: d.hintFiles } : undefined
20942
19869
  }));
20943
- log11?.info(`[${PLUGIN_NAME19}] AI 拆分成功 → ${specs.length} 个子任务`);
19870
+ log10?.info(`[${PLUGIN_NAME16}] AI 拆分成功 → ${specs.length} 个子任务`);
20944
19871
  if (canNotice) {
20945
19872
  const lines = [
20946
19873
  `\uD83E\uDD16 AI 已自动拆为 ${specs.length} 个并行子任务:`,
@@ -20952,7 +19879,7 @@ async function handleParallelCommand(raw) {
20952
19879
  });
20953
19880
  }
20954
19881
  } else if (dec.reason === "llm_unavailable" || dec.reason === "parse_failed") {
20955
- log11?.warn(`[${PLUGIN_NAME19}] AI 拆分失败(${dec.reason}),按单任务执行`);
19882
+ log10?.warn(`[${PLUGIN_NAME16}] AI 拆分失败(${dec.reason}),按单任务执行`);
20956
19883
  }
20957
19884
  }
20958
19885
  if (canNotice) {
@@ -20967,11 +19894,11 @@ async function handleParallelCommand(raw) {
20967
19894
  directory: ctx.directory,
20968
19895
  log: (lvl, msg, data) => {
20969
19896
  if (lvl === "error")
20970
- log11?.error(msg, data);
19897
+ log10?.error(msg, data);
20971
19898
  else if (lvl === "warn")
20972
- log11?.warn(msg, data);
19899
+ log10?.warn(msg, data);
20973
19900
  else
20974
- log11?.info(msg, data);
19901
+ log10?.info(msg, data);
20975
19902
  }
20976
19903
  });
20977
19904
  }
@@ -21015,11 +19942,11 @@ async function handleParallelCommand(raw) {
21015
19942
  allocateWorktree: downgradedAutoMerge ? undefined : ctx.allocateWorktree,
21016
19943
  log: (lvl, msg, data) => {
21017
19944
  if (lvl === "error")
21018
- log11?.error(msg, data);
19945
+ log10?.error(msg, data);
21019
19946
  else if (lvl === "warn")
21020
- log11?.warn(msg, data);
19947
+ log10?.warn(msg, data);
21021
19948
  else
21022
- log11?.info(msg, data);
19949
+ log10?.info(msg, data);
21023
19950
  },
21024
19951
  tryMerge: ctx.tryMerge,
21025
19952
  mergeCommit: ctx.mergeCommit,
@@ -21031,7 +19958,7 @@ async function handleParallelCommand(raw) {
21031
19958
  });
21032
19959
  } catch (err) {
21033
19960
  const msg = err instanceof Error ? err.message : String(err);
21034
- log11?.error(`[${PLUGIN_NAME19}] schedule 抛错`, { error: msg });
19961
+ log10?.error(`[${PLUGIN_NAME16}] schedule 抛错`, { error: msg });
21035
19962
  await safeReply2(ctx, `❌ 并发调度失败:${msg}`);
21036
19963
  return { ok: false, reason: msg };
21037
19964
  }
@@ -21049,7 +19976,7 @@ async function handleParallelCommand(raw) {
21049
19976
  }
21050
19977
  await safeReply2(ctx, summaryLines.join(`
21051
19978
  `));
21052
- log11?.info(`[${PLUGIN_NAME19}] schedule 完成`, {
19979
+ log10?.info(`[${PLUGIN_NAME16}] schedule 完成`, {
21053
19980
  parentId,
21054
19981
  success: result.digest.success,
21055
19982
  failed: result.digest.failed,
@@ -21062,7 +19989,7 @@ async function handleParallelCommand(raw) {
21062
19989
  try {
21063
19990
  await ctx.onCompleted(result);
21064
19991
  } catch (err) {
21065
- log11?.warn(`[${PLUGIN_NAME19}] onCompleted hook 抛错(已隔离)`, {
19992
+ log10?.warn(`[${PLUGIN_NAME16}] onCompleted hook 抛错(已隔离)`, {
21066
19993
  error: err instanceof Error ? err.message : String(err)
21067
19994
  });
21068
19995
  }
@@ -21104,7 +20031,7 @@ async function writeLog(level, msg, data) {
21104
20031
  const line = JSON.stringify({
21105
20032
  ts: new Date().toISOString(),
21106
20033
  level,
21107
- plugin: PLUGIN_NAME19,
20034
+ plugin: PLUGIN_NAME16,
21108
20035
  msg,
21109
20036
  data
21110
20037
  }) + `
@@ -21115,11 +20042,11 @@ async function writeLog(level, msg, data) {
21115
20042
  await fs18.appendFile(logFile, line, "utf8");
21116
20043
  } catch {}
21117
20044
  }
21118
- logLifecycle(PLUGIN_NAME19, "import");
20045
+ logLifecycle(PLUGIN_NAME16, "import");
21119
20046
  var subtasksServer = async (ctx) => {
21120
- const log11 = makePluginLogger(PLUGIN_NAME19);
20047
+ const log10 = makePluginLogger(PLUGIN_NAME16);
21121
20048
  const client = ctx?.client ?? undefined;
21122
- logLifecycle(PLUGIN_NAME19, "activate", {
20049
+ logLifecycle(PLUGIN_NAME16, "activate", {
21123
20050
  directory: ctx.directory,
21124
20051
  hasClient: Boolean(client)
21125
20052
  });
@@ -21144,7 +20071,7 @@ var subtasksServer = async (ctx) => {
21144
20071
  const gitOk = await isGitRepo({ root: repoRoot }).catch(() => false);
21145
20072
  if (!gitOk) {
21146
20073
  autoMerge = false;
21147
- log11?.warn("[subtasks] worktree_isolation=true 但非 git 仓库,自动降级 autoMerge=false");
20074
+ log10?.warn("[subtasks] worktree_isolation=true 但非 git 仓库,自动降级 autoMerge=false");
21148
20075
  const canNotice = Boolean(client);
21149
20076
  if (canNotice) {
21150
20077
  await sendParentNotice(client, input.sessionID, "ℹ 当前目录不是 git 仓库,worktree 隔离已自动关闭,以单目录模式运行", { directory: ctx.directory });
@@ -21173,7 +20100,7 @@ var subtasksServer = async (ctx) => {
21173
20100
  replyLines.push(s);
21174
20101
  return Promise.resolve();
21175
20102
  },
21176
- log: log11,
20103
+ log: log10,
21177
20104
  client,
21178
20105
  parentSessionID: input.sessionID,
21179
20106
  directory: ctx.directory,
@@ -21186,11 +20113,11 @@ var subtasksServer = async (ctx) => {
21186
20113
  perTaskTimeoutMs: 5 * 60000,
21187
20114
  log: (lvl, msg, data) => {
21188
20115
  if (lvl === "error")
21189
- log11.error(msg, data);
20116
+ log10.error(msg, data);
21190
20117
  else if (lvl === "warn")
21191
- log11.warn(msg, data);
20118
+ log10.warn(msg, data);
21192
20119
  else
21193
- log11.info(msg, data);
20120
+ log10.info(msg, data);
21194
20121
  }
21195
20122
  }) : undefined
21196
20123
  };
@@ -21216,20 +20143,20 @@ var subtasksServer = async (ctx) => {
21216
20143
  });
21217
20144
  }
21218
20145
  } catch (err) {
21219
- log11.error(`[${PLUGIN_NAME19}] command.execute.before 异常(已隔离)`, {
20146
+ log10.error(`[${PLUGIN_NAME16}] command.execute.before 异常(已隔离)`, {
21220
20147
  error: err instanceof Error ? err.message : String(err)
21221
20148
  });
21222
20149
  }
21223
20150
  }
21224
20151
  };
21225
20152
  };
21226
- var handler19 = subtasksServer;
20153
+ var handler16 = subtasksServer;
21227
20154
 
21228
20155
  // plugins/terminal-monitor.ts
21229
- import * as crypto5 from "node:crypto";
21230
- var PLUGIN_NAME20 = "terminal-monitor";
21231
- logLifecycle(PLUGIN_NAME20, "import", {});
21232
- var DEFAULT_CONFIG6 = {
20156
+ import * as crypto4 from "node:crypto";
20157
+ var PLUGIN_NAME17 = "terminal-monitor";
20158
+ logLifecycle(PLUGIN_NAME17, "import", {});
20159
+ var DEFAULT_CONFIG4 = {
21233
20160
  minScore: 0.6,
21234
20161
  cooldownMs: 30000,
21235
20162
  lruLimit: 256,
@@ -21344,7 +20271,7 @@ var DEFAULT_RULES = [
21344
20271
  score: 0.3
21345
20272
  }
21346
20273
  ];
21347
- function normalizeLines(raw, maxLines = DEFAULT_CONFIG6.maxLines) {
20274
+ function normalizeLines(raw, maxLines = DEFAULT_CONFIG4.maxLines) {
21348
20275
  if (!raw)
21349
20276
  return [];
21350
20277
  const stripped = raw.replace(/\u001b\[[0-9;?]*[A-Za-z]/g, "");
@@ -21355,7 +20282,7 @@ function normalizeLines(raw, maxLines = DEFAULT_CONFIG6.maxLines) {
21355
20282
  return [...lines.slice(0, half), `... [省略 ${lines.length - maxLines} 行] ...`, ...lines.slice(-half)];
21356
20283
  }
21357
20284
  function parseTerminalOutput(ev, cfg = {}, rules = DEFAULT_RULES) {
21358
- const c = { ...DEFAULT_CONFIG6, ...cfg };
20285
+ const c = { ...DEFAULT_CONFIG4, ...cfg };
21359
20286
  const stdoutLines = normalizeLines(ev.stdout, c.maxLines);
21360
20287
  const stderrLines = normalizeLines(ev.stderr, c.maxLines);
21361
20288
  const text = [...stdoutLines, ...stderrLines].join(`
@@ -21371,7 +20298,7 @@ function parseTerminalOutput(ev, cfg = {}, rules = DEFAULT_RULES) {
21371
20298
  `).length;
21372
20299
  const lineFromStart = text.split(`
21373
20300
  `)[lineIdx - 1] ?? m[0];
21374
- const excerpt = clip7(lineFromStart.trim(), c.maxExcerpt);
20301
+ const excerpt = clip6(lineFromStart.trim(), c.maxExcerpt);
21375
20302
  if (!out.has(rule.kind)) {
21376
20303
  out.set(rule.kind, {
21377
20304
  severity: rule.severity,
@@ -21393,16 +20320,16 @@ function parseTerminalOutput(ev, cfg = {}, rules = DEFAULT_RULES) {
21393
20320
  }
21394
20321
  return [...out.values()].sort((a, b) => b.score - a.score);
21395
20322
  }
21396
- function clip7(s, max) {
20323
+ function clip6(s, max) {
21397
20324
  if (s.length <= max)
21398
20325
  return s;
21399
20326
  return s.slice(0, max - 1) + "…";
21400
20327
  }
21401
20328
 
21402
- class FingerprintLRU2 {
20329
+ class FingerprintLRU {
21403
20330
  limit;
21404
20331
  map = new Map;
21405
- constructor(limit = DEFAULT_CONFIG6.lruLimit) {
20332
+ constructor(limit = DEFAULT_CONFIG4.lruLimit) {
21406
20333
  this.limit = limit;
21407
20334
  }
21408
20335
  add(fp, ts = Date.now()) {
@@ -21428,10 +20355,10 @@ class FingerprintLRU2 {
21428
20355
  }
21429
20356
  function fingerprintFinding(cmd, f) {
21430
20357
  const raw = `${cmd ?? ""}|${f.kind}|${f.excerpt.slice(0, 60)}`;
21431
- return crypto5.createHash("sha1").update(raw).digest("hex").slice(0, 16);
20358
+ return crypto4.createHash("sha1").update(raw).digest("hex").slice(0, 16);
21432
20359
  }
21433
20360
  function shouldNotify(findings, ev, lru, cfg = {}, now = Date.now()) {
21434
- const c = { ...DEFAULT_CONFIG6, ...cfg };
20361
+ const c = { ...DEFAULT_CONFIG4, ...cfg };
21435
20362
  const out = [];
21436
20363
  let suppressed = 0;
21437
20364
  for (const f of findings) {
@@ -21452,7 +20379,7 @@ function shouldNotify(findings, ev, lru, cfg = {}, now = Date.now()) {
21452
20379
  function buildSummary(ev, findings) {
21453
20380
  const parts = [];
21454
20381
  if (ev.cmd)
21455
- parts.push(`\`${clip7(ev.cmd, 60)}\``);
20382
+ parts.push(`\`${clip6(ev.cmd, 60)}\``);
21456
20383
  if (ev.type === "terminal.exit" && typeof ev.exit_code === "number") {
21457
20384
  parts.push(`exit=${ev.exit_code}`);
21458
20385
  }
@@ -21464,7 +20391,7 @@ function buildSummary(ev, findings) {
21464
20391
  return parts.join(" · ");
21465
20392
  }
21466
20393
  const top = findings[0];
21467
- parts.push(`${top.severity}/${top.kind}: ${clip7(top.excerpt, 80)}`);
20394
+ parts.push(`${top.severity}/${top.kind}: ${clip6(top.excerpt, 80)}`);
21468
20395
  if (findings.length > 1)
21469
20396
  parts.push(`(+${findings.length - 1} 条)`);
21470
20397
  return parts.join(" · ");
@@ -21498,7 +20425,7 @@ async function processTerminalEvent(ev, opts = {}) {
21498
20425
  return { ok: false, notified: false, suppressed: 0, error: msg };
21499
20426
  }
21500
20427
  }
21501
- var sharedLRU = new FingerprintLRU2;
20428
+ var sharedLRU = new FingerprintLRU;
21502
20429
  function describeError(err) {
21503
20430
  if (err instanceof Error)
21504
20431
  return err.message;
@@ -21510,17 +20437,17 @@ function describeError(err) {
21510
20437
  return String(err);
21511
20438
  }
21512
20439
  }
21513
- var log11 = makePluginLogger(PLUGIN_NAME20);
21514
- var lru = new FingerprintLRU2;
20440
+ var log10 = makePluginLogger(PLUGIN_NAME17);
20441
+ var lru = new FingerprintLRU;
21515
20442
  var _lastNotification = null;
21516
20443
  var terminalMonitorServer = async (ctx) => {
21517
- logLifecycle(PLUGIN_NAME20, "activate", {
20444
+ logLifecycle(PLUGIN_NAME17, "activate", {
21518
20445
  directory: ctx.directory,
21519
20446
  triggerEventTypes: ["terminal.output", "terminal.exit"]
21520
20447
  });
21521
20448
  return {
21522
20449
  event: async ({ event }) => {
21523
- await safeAsync(PLUGIN_NAME20, "event", async () => {
20450
+ await safeAsync(PLUGIN_NAME17, "event", async () => {
21524
20451
  const e = event;
21525
20452
  if (!e || typeof e.type !== "string")
21526
20453
  return;
@@ -21529,12 +20456,12 @@ var terminalMonitorServer = async (ctx) => {
21529
20456
  const ev = { type: e.type, ...e.properties ?? {} };
21530
20457
  const r = await processTerminalEvent(ev, {
21531
20458
  lru,
21532
- log: log11,
20459
+ log: log10,
21533
20460
  notifyAgent: (msg) => {
21534
20461
  _lastNotification = msg;
21535
20462
  }
21536
20463
  });
21537
- safeWriteLog(PLUGIN_NAME20, {
20464
+ safeWriteLog(PLUGIN_NAME17, {
21538
20465
  hook: "event",
21539
20466
  type: e.type,
21540
20467
  notified: r.notified,
@@ -21542,18 +20469,133 @@ var terminalMonitorServer = async (ctx) => {
21542
20469
  findings: r.notification?.findings.length ?? 0
21543
20470
  });
21544
20471
  if (r.notified && r.notification) {
21545
- log11.info(`[${PLUGIN_NAME20}] 反馈 ${r.notification.findings.length} 条 → ${r.notification.summary}`);
20472
+ log10.info(`[${PLUGIN_NAME17}] 反馈 ${r.notification.findings.length} 条 → ${r.notification.summary}`);
21546
20473
  }
21547
20474
  });
21548
20475
  }
21549
20476
  };
21550
20477
  };
21551
- var handler20 = terminalMonitorServer;
20478
+ var handler17 = terminalMonitorServer;
20479
+
20480
+ // lib/condenser.ts
20481
+ var DEFAULT_CONDENSE = {
20482
+ budget: 128000,
20483
+ threshold: 0.7,
20484
+ target: 0.4,
20485
+ keepRecent: 6,
20486
+ keepSystem: true,
20487
+ charsPerToken: 4
20488
+ };
20489
+ function estimateTokens(text, charsPerToken = 4) {
20490
+ if (!text)
20491
+ return 0;
20492
+ return Math.max(1, Math.ceil(text.length / charsPerToken));
20493
+ }
20494
+ function tokenizeMessages(msgs, charsPerToken = 4) {
20495
+ let total = 0;
20496
+ for (const m of msgs) {
20497
+ total += m.tokens ?? estimateTokens(m.content, charsPerToken);
20498
+ }
20499
+ return total;
20500
+ }
20501
+ function fallbackSummarize(msgs) {
20502
+ if (msgs.length === 0)
20503
+ return "";
20504
+ const head = msgs.slice(0, 2).map((m) => `- [${m.role}] ${truncate(m.content, 120)}`);
20505
+ const tail = msgs.slice(-2).map((m) => `- [${m.role}] ${truncate(m.content, 120)}`);
20506
+ const tagCounts = new Map;
20507
+ let userCount = 0;
20508
+ let assistantCount = 0;
20509
+ let toolCount = 0;
20510
+ for (const m of msgs) {
20511
+ if (m.role === "user")
20512
+ userCount++;
20513
+ else if (m.role === "assistant")
20514
+ assistantCount++;
20515
+ else if (m.role === "tool")
20516
+ toolCount++;
20517
+ if (m.tag)
20518
+ tagCounts.set(m.tag, (tagCounts.get(m.tag) ?? 0) + 1);
20519
+ }
20520
+ const tagsLine = [...tagCounts.entries()].sort((a, b) => b[1] - a[1]).slice(0, 5).map(([t, c]) => `${t}×${c}`).join(", ");
20521
+ return [
20522
+ `### 历史摘要(共 ${msgs.length} 条 · user=${userCount} assistant=${assistantCount} tool=${toolCount})`,
20523
+ tagsLine ? `- 关键事件:${tagsLine}` : "",
20524
+ "",
20525
+ "**开头:**",
20526
+ ...head,
20527
+ "",
20528
+ "**结尾:**",
20529
+ ...tail
20530
+ ].filter(Boolean).join(`
20531
+ `);
20532
+ }
20533
+ function truncate(s, max) {
20534
+ if (!s)
20535
+ return "";
20536
+ return s.length <= max ? s : s.slice(0, max - 1) + "…";
20537
+ }
20538
+ async function condense(input, opts = {}) {
20539
+ const cfg = { ...DEFAULT_CONDENSE, ...opts.config ?? {} };
20540
+ const msgs = [...input];
20541
+ const before = {
20542
+ count: msgs.length,
20543
+ tokens: tokenizeMessages(msgs, cfg.charsPerToken)
20544
+ };
20545
+ if (cfg.budget <= 0 || before.tokens / cfg.budget < cfg.threshold) {
20546
+ return {
20547
+ messages: msgs,
20548
+ before,
20549
+ after: { ...before },
20550
+ compressed: false,
20551
+ reason: `usage ${(before.tokens / Math.max(cfg.budget, 1)).toFixed(2)} < threshold ${cfg.threshold}`
20552
+ };
20553
+ }
20554
+ const systems = cfg.keepSystem ? msgs.filter((m) => m.role === "system") : [];
20555
+ const nonSystem = cfg.keepSystem ? msgs.filter((m) => m.role !== "system") : msgs;
20556
+ const recents = nonSystem.slice(-cfg.keepRecent);
20557
+ const middles = nonSystem.slice(0, Math.max(0, nonSystem.length - cfg.keepRecent));
20558
+ if (middles.length === 0) {
20559
+ return {
20560
+ messages: msgs,
20561
+ before,
20562
+ after: { ...before },
20563
+ compressed: false,
20564
+ reason: "nothing in middle to compress"
20565
+ };
20566
+ }
20567
+ let summary = "";
20568
+ try {
20569
+ summary = await (opts.summarize ?? fallbackSummarize)(middles) ?? "";
20570
+ } catch {
20571
+ summary = fallbackSummarize(middles);
20572
+ }
20573
+ if (!summary)
20574
+ summary = fallbackSummarize(middles);
20575
+ const summaryMsg = {
20576
+ role: "assistant",
20577
+ content: summary,
20578
+ tag: "condensed-summary"
20579
+ };
20580
+ const out = [...systems, summaryMsg, ...recents];
20581
+ const after = {
20582
+ count: out.length,
20583
+ tokens: tokenizeMessages(out, cfg.charsPerToken)
20584
+ };
20585
+ return {
20586
+ messages: out,
20587
+ before,
20588
+ after,
20589
+ compressed: true,
20590
+ summary,
20591
+ reason: `compressed ${middles.length} → 1 summary (target ${cfg.target})`
20592
+ };
20593
+ }
21552
20594
 
21553
20595
  // plugins/token-manager.ts
21554
- var PLUGIN_NAME21 = "token-manager";
21555
- logLifecycle(PLUGIN_NAME21, "import", {});
21556
- async function handleMessageBefore(raw, log12, defaults) {
20596
+ var PLUGIN_NAME18 = "token-manager";
20597
+ logLifecycle(PLUGIN_NAME18, "import", {});
20598
+ async function handleMessageBefore(raw, log11, defaults) {
21557
20599
  const ctx = raw ?? {};
21558
20600
  if (!Array.isArray(ctx.messages) || ctx.messages.length === 0)
21559
20601
  return null;
@@ -21572,21 +20614,21 @@ async function handleMessageBefore(raw, log12, defaults) {
21572
20614
  };
21573
20615
  if (r.compressed) {
21574
20616
  ctx.messages = r.messages;
21575
- log12?.info(`[${PLUGIN_NAME21}] 压缩 ${r.before.count}→${r.after.count} 条 / ${r.before.tokens}→${r.after.tokens} tokens`);
20617
+ log11?.info(`[${PLUGIN_NAME18}] 压缩 ${r.before.count}→${r.after.count} 条 / ${r.before.tokens}→${r.after.tokens} tokens`);
21576
20618
  }
21577
20619
  return r;
21578
20620
  } catch (err) {
21579
- log12?.warn(`[${PLUGIN_NAME21}] 压缩异常(已隔离)`, {
20621
+ log11?.warn(`[${PLUGIN_NAME18}] 压缩异常(已隔离)`, {
21580
20622
  error: err instanceof Error ? err.message : String(err)
21581
20623
  });
21582
20624
  return null;
21583
20625
  }
21584
20626
  }
21585
- var log12 = makePluginLogger(PLUGIN_NAME21);
20627
+ var log11 = makePluginLogger(PLUGIN_NAME18);
21586
20628
  var tokenManagerServer = async (ctx) => {
21587
20629
  const rt = loadRuntimeSync();
21588
20630
  const threshold = rt.runtime.context.condenser_threshold_ratio;
21589
- logLifecycle(PLUGIN_NAME21, "activate", {
20631
+ logLifecycle(PLUGIN_NAME18, "activate", {
21590
20632
  directory: ctx.directory,
21591
20633
  threshold,
21592
20634
  target: DEFAULT_CONDENSE.target,
@@ -21595,7 +20637,7 @@ var tokenManagerServer = async (ctx) => {
21595
20637
  });
21596
20638
  return {
21597
20639
  "experimental.chat.messages.transform": async (_input, output) => {
21598
- await safeAsync(PLUGIN_NAME21, "experimental.chat.messages.transform", async () => {
20640
+ await safeAsync(PLUGIN_NAME18, "experimental.chat.messages.transform", async () => {
21599
20641
  const list = output.messages;
21600
20642
  if (!Array.isArray(list) || list.length === 0)
21601
20643
  return;
@@ -21605,10 +20647,10 @@ var tokenManagerServer = async (ctx) => {
21605
20647
  `);
21606
20648
  return { role, content };
21607
20649
  });
21608
- const r = await handleMessageBefore({ messages: flat }, log12, { threshold });
20650
+ const r = await handleMessageBefore({ messages: flat }, log11, { threshold });
21609
20651
  if (!r)
21610
20652
  return;
21611
- safeWriteLog(PLUGIN_NAME21, {
20653
+ safeWriteLog(PLUGIN_NAME18, {
21612
20654
  hook: "experimental.chat.messages.transform",
21613
20655
  mode: "observe-only",
21614
20656
  before_msgs: r.before.count,
@@ -21619,13 +20661,13 @@ var tokenManagerServer = async (ctx) => {
21619
20661
  reason: r.reason
21620
20662
  });
21621
20663
  if (r.compressed) {
21622
- log12.warn(`[${PLUGIN_NAME21}] advise condense: ${r.before.count}→${r.after.count} msgs / ${r.before.tokens}→${r.after.tokens} tokens (observe-only, no write-back to avoid opencode message schema mismatch)`);
20664
+ log11.warn(`[${PLUGIN_NAME18}] advise condense: ${r.before.count}→${r.after.count} msgs / ${r.before.tokens}→${r.after.tokens} tokens (observe-only, no write-back to avoid opencode message schema mismatch)`);
21623
20665
  }
21624
20666
  });
21625
20667
  }
21626
20668
  };
21627
20669
  };
21628
- var handler21 = tokenManagerServer;
20670
+ var handler18 = tokenManagerServer;
21629
20671
 
21630
20672
  // plugins/tool-policy.ts
21631
20673
  import { promises as fs19 } from "node:fs";
@@ -21909,8 +20951,8 @@ function checkFileAccess(acl, file, op) {
21909
20951
  }
21910
20952
 
21911
20953
  // plugins/tool-policy.ts
21912
- var PLUGIN_NAME22 = "tool-policy";
21913
- logLifecycle(PLUGIN_NAME22, "import", {});
20954
+ var PLUGIN_NAME19 = "tool-policy";
20955
+ logLifecycle(PLUGIN_NAME19, "import", {});
21914
20956
  var EMPTY_ACL = { whitelistMode: false };
21915
20957
  function getAgentAcl(cfg, agent) {
21916
20958
  if (!agent || !cfg.per_agent)
@@ -21980,18 +21022,18 @@ async function loadPolicy(root = process.cwd()) {
21980
21022
  function classifyToolKind(toolName) {
21981
21023
  return classifyTool(toolName);
21982
21024
  }
21983
- async function resolveCurrentAgent2(client, sessionID, log13) {
21025
+ async function resolveCurrentAgent2(client, sessionID, log12) {
21984
21026
  try {
21985
21027
  const sessionApi = client?.session;
21986
21028
  if (!sessionApi || typeof sessionApi.get !== "function") {
21987
- log13.warn(`client.session.get unavailable`, { sessionID });
21029
+ log12.warn(`client.session.get unavailable`, { sessionID });
21988
21030
  return;
21989
21031
  }
21990
21032
  const res = await sessionApi.get({ path: { id: sessionID } });
21991
21033
  const data = res?.data ?? res;
21992
21034
  const rawAgent = data?.agent;
21993
21035
  if (typeof rawAgent !== "string" || rawAgent === "") {
21994
- log13.warn(`client.session.get returned no string agent (保守放行)`, {
21036
+ log12.warn(`client.session.get returned no string agent (保守放行)`, {
21995
21037
  sessionID,
21996
21038
  dataKeys: data && typeof data === "object" ? Object.keys(data) : null
21997
21039
  });
@@ -21999,31 +21041,31 @@ async function resolveCurrentAgent2(client, sessionID, log13) {
21999
21041
  }
22000
21042
  return rawAgent;
22001
21043
  } catch (err) {
22002
- log13.warn(`client.session.get failed (保守放行)`, {
21044
+ log12.warn(`client.session.get failed (保守放行)`, {
22003
21045
  sessionID,
22004
21046
  error: err instanceof Error ? err.message : String(err)
22005
21047
  });
22006
21048
  return;
22007
21049
  }
22008
21050
  }
22009
- var log13 = makePluginLogger(PLUGIN_NAME22);
21051
+ var log12 = makePluginLogger(PLUGIN_NAME19);
22010
21052
  var toolPolicyServer = async (ctx) => {
22011
21053
  const directory = ctx.directory ?? process.cwd();
22012
21054
  const cfg = await loadPolicy(directory);
22013
- logLifecycle(PLUGIN_NAME22, "activate", {
21055
+ logLifecycle(PLUGIN_NAME19, "activate", {
22014
21056
  directory,
22015
21057
  acl_loaded: !!cfg.acl
22016
21058
  });
22017
21059
  return {
22018
21060
  "tool.execute.before": async (input, output) => {
22019
21061
  let denied;
22020
- await safeAsync(PLUGIN_NAME22, "tool.execute.before", async () => {
21062
+ await safeAsync(PLUGIN_NAME19, "tool.execute.before", async () => {
22021
21063
  const toolName = input.tool;
22022
21064
  const argsObj = output.args ?? {};
22023
21065
  const needsAgentDetection = toolName === "pending_changes" && (argsObj.action === "apply" || argsObj.action === "apply_all");
22024
21066
  let currentAgent = undefined;
22025
21067
  if (needsAgentDetection) {
22026
- currentAgent = await resolveCurrentAgent2(ctx.client, input.sessionID, log13);
21068
+ currentAgent = await resolveCurrentAgent2(ctx.client, input.sessionID, log12);
22027
21069
  }
22028
21070
  const files = [];
22029
21071
  const filePath = argsObj["path"] ?? argsObj["filePath"] ?? argsObj["file"];
@@ -22037,7 +21079,7 @@ var toolPolicyServer = async (ctx) => {
22037
21079
  args: argsObj,
22038
21080
  files: files.length ? files : undefined
22039
21081
  }, cfg, currentAgent);
22040
- safeWriteLog(PLUGIN_NAME22, {
21082
+ safeWriteLog(PLUGIN_NAME19, {
22041
21083
  hook: "tool.execute.before",
22042
21084
  tool: toolName,
22043
21085
  callID: input.callID,
@@ -22048,7 +21090,7 @@ var toolPolicyServer = async (ctx) => {
22048
21090
  currentAgent
22049
21091
  });
22050
21092
  if (decision.action === "deny") {
22051
- log13.warn(`[${PLUGIN_NAME22}] DENY ${toolName}`, {
21093
+ log12.warn(`[${PLUGIN_NAME19}] DENY ${toolName}`, {
22052
21094
  action: argsObj.action,
22053
21095
  currentAgent,
22054
21096
  reasons: decision.reasons,
@@ -22063,7 +21105,7 @@ var toolPolicyServer = async (ctx) => {
22063
21105
  }
22064
21106
  };
22065
21107
  };
22066
- var handler22 = toolPolicyServer;
21108
+ var handler19 = toolPolicyServer;
22067
21109
 
22068
21110
  // plugins/update-checker.ts
22069
21111
  import { existsSync as existsSync6 } from "node:fs";
@@ -22071,7 +21113,7 @@ import { homedir as homedir8 } from "node:os";
22071
21113
  import { join as join23 } from "node:path";
22072
21114
 
22073
21115
  // lib/update-checker-impl.ts
22074
- import { createHash as createHash5 } from "node:crypto";
21116
+ import { createHash as createHash4 } from "node:crypto";
22075
21117
  import {
22076
21118
  copyFileSync,
22077
21119
  existsSync as existsSync5,
@@ -22093,7 +21135,7 @@ import * as zlib from "node:zlib";
22093
21135
  // lib/version-injected.ts
22094
21136
  function getInjectedVersion() {
22095
21137
  try {
22096
- const v = "0.5.20";
21138
+ const v = "0.5.22";
22097
21139
  if (typeof v === "string" && /^\d+\.\d+\.\d+/.test(v)) {
22098
21140
  return v;
22099
21141
  }
@@ -22372,7 +21414,7 @@ function verifyIntegrity(buf, expected) {
22372
21414
  if (algo !== "sha512" && algo !== "sha256" && algo !== "sha384") {
22373
21415
  throw new Error(`integrity_algo_unsupported: ${algo}`);
22374
21416
  }
22375
- const actualB64 = createHash5(algo).update(buf).digest("base64");
21417
+ const actualB64 = createHash4(algo).update(buf).digest("base64");
22376
21418
  if (actualB64 !== expectedB64) {
22377
21419
  throw new Error(`integrity_mismatch: expected ${algo}=${expectedB64.slice(0, 16)}... got ${actualB64.slice(0, 16)}...`);
22378
21420
  }
@@ -22596,20 +21638,20 @@ function compareOpencodeVersion(opts) {
22596
21638
  }
22597
21639
 
22598
21640
  // plugins/update-checker.ts
22599
- var PLUGIN_NAME23 = "update-checker";
21641
+ var PLUGIN_NAME20 = "update-checker";
22600
21642
  var PLUGIN_VERSION = "2.0.0";
22601
- logLifecycle(PLUGIN_NAME23, "import", { version: PLUGIN_VERSION });
21643
+ logLifecycle(PLUGIN_NAME20, "import", { version: PLUGIN_VERSION });
22602
21644
  var updateCheckerServer = async (ctx) => {
22603
21645
  const yieldResult = shouldYieldToLocalPlugin({ directory: ctx.directory });
22604
21646
  if (yieldResult.yield) {
22605
- safeWriteLog(PLUGIN_NAME23, {
21647
+ safeWriteLog(PLUGIN_NAME20, {
22606
21648
  level: "info",
22607
21649
  msg: "dev_mode_yield_skip",
22608
21650
  reason: yieldResult.reason,
22609
21651
  markerPath: yieldResult.markerPath,
22610
21652
  detail: formatYieldLog(yieldResult)
22611
21653
  });
22612
- logLifecycle(PLUGIN_NAME23, "activate", {
21654
+ logLifecycle(PLUGIN_NAME20, "activate", {
22613
21655
  yield_to_local: true,
22614
21656
  yield_reason: yieldResult.reason,
22615
21657
  skipped: "auto_install + background_check"
@@ -22618,7 +21660,7 @@ var updateCheckerServer = async (ctx) => {
22618
21660
  }
22619
21661
  const rt = loadRuntimeSync();
22620
21662
  const u = rt.runtime.update;
22621
- logLifecycle(PLUGIN_NAME23, "activate", {
21663
+ logLifecycle(PLUGIN_NAME20, "activate", {
22622
21664
  version: PLUGIN_VERSION,
22623
21665
  auto_check_enabled: u.auto_check_enabled,
22624
21666
  interval_hours: u.interval_hours,
@@ -22630,14 +21672,14 @@ var updateCheckerServer = async (ctx) => {
22630
21672
  repo_fallback: u.repo,
22631
21673
  config_source: "codeforge.json"
22632
21674
  });
22633
- await safeAsync(PLUGIN_NAME23, "opencode_version_check", async () => {
21675
+ await safeAsync(PLUGIN_NAME20, "opencode_version_check", async () => {
22634
21676
  const compat = loadCompatibility();
22635
21677
  const opencodeVer = detectOpencodeVersion();
22636
21678
  const verdict = compareOpencodeVersion({
22637
21679
  currentOpencodeVer: opencodeVer,
22638
21680
  compat
22639
21681
  });
22640
- safeWriteLog(PLUGIN_NAME23, {
21682
+ safeWriteLog(PLUGIN_NAME20, {
22641
21683
  level: "info",
22642
21684
  msg: "opencode_version_check",
22643
21685
  opencodeVer,
@@ -22654,14 +21696,14 @@ var updateCheckerServer = async (ctx) => {
22654
21696
  }
22655
21697
  });
22656
21698
  if (!u.auto_check_enabled) {
22657
- safeWriteLog(PLUGIN_NAME23, {
21699
+ safeWriteLog(PLUGIN_NAME20, {
22658
21700
  level: "info",
22659
21701
  msg: "auto-check disabled (codeforge.json update.auto_check_enabled=false)"
22660
21702
  });
22661
21703
  return {};
22662
21704
  }
22663
21705
  setImmediate(() => {
22664
- safeAsync(PLUGIN_NAME23, "checkAndMaybeUpdate", async () => {
21706
+ safeAsync(PLUGIN_NAME20, "checkAndMaybeUpdate", async () => {
22665
21707
  const local = readLocalVersion();
22666
21708
  let npmResult = null;
22667
21709
  try {
@@ -22672,7 +21714,7 @@ var updateCheckerServer = async (ctx) => {
22672
21714
  timeoutMs: 5000
22673
21715
  });
22674
21716
  } catch (e) {
22675
- safeWriteLog(PLUGIN_NAME23, {
21717
+ safeWriteLog(PLUGIN_NAME20, {
22676
21718
  level: "warn",
22677
21719
  msg: "npm_fetch_failed",
22678
21720
  error: e.message
@@ -22681,7 +21723,7 @@ var updateCheckerServer = async (ctx) => {
22681
21723
  return;
22682
21724
  }
22683
21725
  if (!npmResult) {
22684
- safeWriteLog(PLUGIN_NAME23, {
21726
+ safeWriteLog(PLUGIN_NAME20, {
22685
21727
  level: "info",
22686
21728
  msg: "npm_no_release",
22687
21729
  package: u.package,
@@ -22690,7 +21732,7 @@ var updateCheckerServer = async (ctx) => {
22690
21732
  return;
22691
21733
  }
22692
21734
  const hasUpdate = cmpVersion(local, npmResult.version) < 0;
22693
- safeWriteLog(PLUGIN_NAME23, {
21735
+ safeWriteLog(PLUGIN_NAME20, {
22694
21736
  level: "info",
22695
21737
  msg: "npm_check_result",
22696
21738
  local,
@@ -22705,10 +21747,10 @@ var updateCheckerServer = async (ctx) => {
22705
21747
  更新命令:npx ${u.package} install --global`);
22706
21748
  return;
22707
21749
  }
22708
- await safeAsync(PLUGIN_NAME23, "auto_install_bundle", async () => {
21750
+ await safeAsync(PLUGIN_NAME20, "auto_install_bundle", async () => {
22709
21751
  const target = getOpencodeBundlePath();
22710
21752
  if (!target) {
22711
- safeWriteLog(PLUGIN_NAME23, {
21753
+ safeWriteLog(PLUGIN_NAME20, {
22712
21754
  level: "warn",
22713
21755
  msg: "auto_install_skip",
22714
21756
  reason: "无法定位 opencode bundle 路径"
@@ -22727,7 +21769,7 @@ var updateCheckerServer = async (ctx) => {
22727
21769
  oldVersion: local,
22728
21770
  keepBackups: u.backup_keep
22729
21771
  });
22730
- safeWriteLog(PLUGIN_NAME23, {
21772
+ safeWriteLog(PLUGIN_NAME20, {
22731
21773
  level: "info",
22732
21774
  msg: "auto_install_success",
22733
21775
  local,
@@ -22767,7 +21809,7 @@ function getOpencodeBundlePath() {
22767
21809
  return candidates[0] ?? null;
22768
21810
  }
22769
21811
  async function fallbackToGitHubReleases(ctx, u) {
22770
- await safeAsync(PLUGIN_NAME23, "github_fallback", async () => {
21812
+ await safeAsync(PLUGIN_NAME20, "github_fallback", async () => {
22771
21813
  const result = await checkUpdateOnce({
22772
21814
  repo: u.repo,
22773
21815
  intervalMs: u.interval_hours * 3600 * 1000
@@ -22777,7 +21819,7 @@ async function fallbackToGitHubReleases(ctx, u) {
22777
21819
  }
22778
21820
  async function reportLegacyResult(ctx, result, repo) {
22779
21821
  if (result.error && !result.fromCache) {
22780
- safeWriteLog(PLUGIN_NAME23, {
21822
+ safeWriteLog(PLUGIN_NAME20, {
22781
21823
  level: "warn",
22782
21824
  msg: `update check failed: ${result.error}`,
22783
21825
  ...result
@@ -22785,7 +21827,7 @@ async function reportLegacyResult(ctx, result, repo) {
22785
21827
  return;
22786
21828
  }
22787
21829
  if (!result.hasUpdate) {
22788
- safeWriteLog(PLUGIN_NAME23, {
21830
+ safeWriteLog(PLUGIN_NAME20, {
22789
21831
  level: "info",
22790
21832
  msg: `up-to-date (local=${result.local}, remote=${result.remote}${result.fromCache ? ", from_cache" : ""})`,
22791
21833
  ...result
@@ -22795,7 +21837,7 @@ async function reportLegacyResult(ctx, result, repo) {
22795
21837
  const updateCmd = `bunx --bun github:${repo} install`;
22796
21838
  const toast = `[CodeForge] 有新版本:${result.local} → ${result.remote}
22797
21839
  更新命令:${updateCmd}`;
22798
- safeWriteLog(PLUGIN_NAME23, {
21840
+ safeWriteLog(PLUGIN_NAME20, {
22799
21841
  level: "info",
22800
21842
  msg: "new_version_available_github_fallback",
22801
21843
  local: result.local,
@@ -22806,17 +21848,17 @@ async function reportLegacyResult(ctx, result, repo) {
22806
21848
  await postToast(ctx, toast);
22807
21849
  }
22808
21850
  async function postToast(ctx, message) {
22809
- await safeAsync(PLUGIN_NAME23, "client.app.log", async () => {
21851
+ await safeAsync(PLUGIN_NAME20, "client.app.log", async () => {
22810
21852
  await ctx.client.app.log({
22811
21853
  body: {
22812
- service: PLUGIN_NAME23,
21854
+ service: PLUGIN_NAME20,
22813
21855
  level: "info",
22814
21856
  message
22815
21857
  }
22816
21858
  });
22817
21859
  });
22818
21860
  }
22819
- var handler23 = updateCheckerServer;
21861
+ var handler20 = updateCheckerServer;
22820
21862
 
22821
21863
  // plugins/workflow-engine.ts
22822
21864
  import * as path27 from "node:path";
@@ -23271,9 +22313,9 @@ async function runStepAutoFeedback(step, adapter) {
23271
22313
  }
23272
22314
 
23273
22315
  // plugins/workflow-engine.ts
23274
- var PLUGIN_NAME24 = "workflow-engine";
23275
- logLifecycle(PLUGIN_NAME24, "import", {});
23276
- var fallbackLog2 = makePluginLogger(PLUGIN_NAME24);
22316
+ var PLUGIN_NAME21 = "workflow-engine";
22317
+ logLifecycle(PLUGIN_NAME21, "import", {});
22318
+ var fallbackLog2 = makePluginLogger(PLUGIN_NAME21);
23277
22319
  var _registry = null;
23278
22320
  async function loadRegistry(workflowsDir) {
23279
22321
  const { loaded, failed } = await loadWorkflowsFromDir(workflowsDir);
@@ -23290,35 +22332,35 @@ async function ensureRegistry(workflowsDir = "workflows") {
23290
22332
  }
23291
22333
  async function handleCommandInvoked(raw, workflowsDir = "workflows") {
23292
22334
  const ctx = raw ?? {};
23293
- const log14 = ctx.log ?? fallbackLog2;
22335
+ const log13 = ctx.log ?? fallbackLog2;
23294
22336
  const command = typeof ctx.command === "string" ? ctx.command : null;
23295
22337
  if (!command) {
23296
- log14.warn(`[${PLUGIN_NAME24}] command.invoked 缺 command 字段`, ctx);
22338
+ log13.warn(`[${PLUGIN_NAME21}] command.invoked 缺 command 字段`, ctx);
23297
22339
  return null;
23298
22340
  }
23299
22341
  const reg = await ensureRegistry(workflowsDir);
23300
22342
  if (reg.errors.length) {
23301
- log14.warn(`[${PLUGIN_NAME24}] 有 ${reg.errors.length} 个 workflow 加载失败`, reg.errors);
22343
+ log13.warn(`[${PLUGIN_NAME21}] 有 ${reg.errors.length} 个 workflow 加载失败`, reg.errors);
23302
22344
  }
23303
22345
  const wf = reg.workflows.find((w) => matchesTrigger(w, command));
23304
22346
  if (!wf) {
23305
- log14.info(`[${PLUGIN_NAME24}] no workflow matches "${command}"`);
22347
+ log13.info(`[${PLUGIN_NAME21}] no workflow matches "${command}"`);
23306
22348
  return null;
23307
22349
  }
23308
- log14.info(`[${PLUGIN_NAME24}] dispatch "${command}" → workflow "${wf.name}"`);
22350
+ log13.info(`[${PLUGIN_NAME21}] dispatch "${command}" → workflow "${wf.name}"`);
23309
22351
  try {
23310
22352
  const result = await run(wf, {
23311
22353
  mode: ctx.adapter ? "real" : "dry_run",
23312
22354
  autonomy: ctx.autonomy ?? "semi",
23313
22355
  adapter: ctx.adapter
23314
22356
  });
23315
- log14.info(`[${PLUGIN_NAME24}] workflow "${wf.name}" 完成 (${result.plan.mode})`, {
22357
+ log13.info(`[${PLUGIN_NAME21}] workflow "${wf.name}" 完成 (${result.plan.mode})`, {
23316
22358
  steps: result.plan.steps.length,
23317
22359
  results: result.results.length
23318
22360
  });
23319
22361
  return result;
23320
22362
  } catch (err) {
23321
- log14.error(`[${PLUGIN_NAME24}] workflow "${wf.name}" 执行失败`, {
22363
+ log13.error(`[${PLUGIN_NAME21}] workflow "${wf.name}" 执行失败`, {
23322
22364
  error: err instanceof Error ? err.message : String(err)
23323
22365
  });
23324
22366
  throw err;
@@ -23327,15 +22369,15 @@ async function handleCommandInvoked(raw, workflowsDir = "workflows") {
23327
22369
  var workflowEngineServer = async (ctx) => {
23328
22370
  const directory = ctx.directory ?? process.cwd();
23329
22371
  const workflowsDir = path27.join(directory, "workflows");
23330
- ensureRegistry(workflowsDir).catch((err) => fallbackLog2.warn(`[${PLUGIN_NAME24}] preload workflows failed`, {
22372
+ ensureRegistry(workflowsDir).catch((err) => fallbackLog2.warn(`[${PLUGIN_NAME21}] preload workflows failed`, {
23331
22373
  error: err instanceof Error ? err.message : String(err)
23332
22374
  }));
23333
- logLifecycle(PLUGIN_NAME24, "activate", { directory, workflowsDir });
22375
+ logLifecycle(PLUGIN_NAME21, "activate", { directory, workflowsDir });
23334
22376
  return {
23335
22377
  "command.execute.before": async (input, output) => {
23336
- await safeAsync(PLUGIN_NAME24, "command.execute.before", async () => {
22378
+ await safeAsync(PLUGIN_NAME21, "command.execute.before", async () => {
23337
22379
  const cmd = input.command.startsWith("/") ? input.command : `/${input.command}`;
23338
- safeWriteLog(PLUGIN_NAME24, {
22380
+ safeWriteLog(PLUGIN_NAME21, {
23339
22381
  hook: "command.execute.before",
23340
22382
  command: cmd,
23341
22383
  sessionID: input.sessionID
@@ -23350,14 +22392,14 @@ var workflowEngineServer = async (ctx) => {
23350
22392
  });
23351
22393
  },
23352
22394
  "chat.message": async (input, output) => {
23353
- await safeAsync(PLUGIN_NAME24, "chat.message", async () => {
22395
+ await safeAsync(PLUGIN_NAME21, "chat.message", async () => {
23354
22396
  const text = extractUserText(output).trim();
23355
22397
  if (!text.startsWith("/"))
23356
22398
  return;
23357
22399
  const cmd = text.split(/\s+/)[0];
23358
22400
  if (!cmd)
23359
22401
  return;
23360
- safeWriteLog(PLUGIN_NAME24, {
22402
+ safeWriteLog(PLUGIN_NAME21, {
23361
22403
  hook: "chat.message",
23362
22404
  command: cmd,
23363
22405
  sessionID: input.sessionID
@@ -23367,12 +22409,12 @@ var workflowEngineServer = async (ctx) => {
23367
22409
  }
23368
22410
  };
23369
22411
  };
23370
- var handler24 = workflowEngineServer;
22412
+ var handler21 = workflowEngineServer;
23371
22413
 
23372
22414
  // plugins/session-worktree-guard.ts
23373
22415
  import path28 from "node:path";
23374
- var PLUGIN_NAME25 = "session-worktree-guard";
23375
- logLifecycle(PLUGIN_NAME25, "import", {});
22416
+ var PLUGIN_NAME22 = "session-worktree-guard";
22417
+ logLifecycle(PLUGIN_NAME22, "import", {});
23376
22418
  var WRITE_INTENT_RE = />(?![=&])(?!\s*\/dev\/(?:null|stdout|stderr|fd\/\d+)\b)|\btee\b|\brm\b|\bmv\b|\bcp\b|\bmkdir\b|\btouch\b|\bchmod\b|\bchown\b|\bln\b/;
23377
22419
  var READ_ONLY_COMMANDS = /^\s*(?:ls|cat|head|tail|grep|rg|find|fd|wc|stat|file|which|whereis|echo|pwd|cd|pushd|popd|env|printenv|type|less|more|sort|uniq|awk|tr|cut|jq|date|whoami|id|uname|node|npx|tsc|diff|python3?|git(?:\s+-C\s+\S+)?\s+(?:log|show|diff|status|branch|tag|remote|config\s+--get|rev-parse|rev-list|ls-files|ls-tree|cat-file|describe|reflog|blame|shortlog|name-rev|symbolic-ref|merge-base|worktree\s+list|stash\s+list|stash\s+show))\b/;
23378
22420
  var SIDE_EFFECT_TOKEN_RE = />(?![=&])(?!\s*\/dev\/(?:null|stdout|stderr|fd\/\d+)\b)|\|\s*tee\b|\btee\b/;
@@ -23395,11 +22437,11 @@ var INTERPRETER_WRITE_RES = [
23395
22437
  function stripCommitMessageArgs(command) {
23396
22438
  return command.replace(/(?:^|\s)(?:-m|--message|-F|--file)(?:=|\s+)("[^"]*"|'[^']*')/g, " ");
23397
22439
  }
23398
- function escapeRegex2(s) {
22440
+ function escapeRegex(s) {
23399
22441
  return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
23400
22442
  }
23401
22443
  function buildGitVcsWriteRegex(mainRoot) {
23402
- const esc = escapeRegex2(mainRoot);
22444
+ const esc = escapeRegex(mainRoot);
23403
22445
  return new RegExp(`git\\b[^\\n]*(?:-C\\s+|--work-tree[=\\s])${esc}`);
23404
22446
  }
23405
22447
  var WRITE_TOOLS = new Set(["write", "edit", "ast_edit"]);
@@ -23514,7 +22556,7 @@ function commandContainsMainRoot(command, mainRoot) {
23514
22556
  const prefix = mainRoot.endsWith("/") ? mainRoot : mainRoot + "/";
23515
22557
  if (command.includes(prefix))
23516
22558
  return true;
23517
- const re = new RegExp(`${escapeRegex2(mainRoot)}(?=[\\s'"\`)]|$)`);
22559
+ const re = new RegExp(`${escapeRegex(mainRoot)}(?=[\\s'"\`)]|$)`);
23518
22560
  return re.test(command);
23519
22561
  }
23520
22562
  function commandContainsMainRootExcludingWorktree(command, mainRoot, worktreePath) {
@@ -23530,7 +22572,7 @@ function commandContainsMainRootExcludingWorktree(command, mainRoot, worktreePat
23530
22572
  }
23531
22573
  const wtRoot = worktreesRoot(mainRoot);
23532
22574
  const wtRootPrefix = wtRoot + path28.sep;
23533
- const escapedWtRootPrefix = escapeRegex2(wtRootPrefix);
22575
+ const escapedWtRootPrefix = escapeRegex(wtRootPrefix);
23534
22576
  const wtPathPattern = escapedWtRootPrefix + `[^\\s'"\\x60)]*`;
23535
22577
  const allWorktreePathsReForEscape = new RegExp(wtPathPattern, "g");
23536
22578
  const allWorktreePathsReForReplace = new RegExp(wtPathPattern, "g");
@@ -23589,7 +22631,7 @@ function collectWritePaths(toolName, argsObj, worktreeRoot) {
23589
22631
  out.push(rel);
23590
22632
  return out;
23591
22633
  }
23592
- var log14 = makePluginLogger(PLUGIN_NAME25);
22634
+ var log13 = makePluginLogger(PLUGIN_NAME22);
23593
22635
  function resolveMainRoot2(rawDir) {
23594
22636
  const worktreeMarker = "/.git/codeforge-worktrees/";
23595
22637
  const idx = rawDir.indexOf(worktreeMarker);
@@ -23601,14 +22643,14 @@ function resolveMainRoot2(rawDir) {
23601
22643
  var sessionWorktreeGuardPlugin = async (ctx) => {
23602
22644
  const disableEnv = process.env["CODEFORGE_DISABLE_WORKTREE_GUARD"];
23603
22645
  if (disableEnv === "1" || disableEnv === "true" || disableEnv === "yes") {
23604
- log14.warn("[guard] CODEFORGE_DISABLE_WORKTREE_GUARD 已启用,session-worktree-guard 全部 hook 跳过;" + "本次 opencode 会话所有写操作将直接落到主工作区(失去隔离保护)", { env: disableEnv });
23605
- safeWriteLog(PLUGIN_NAME25, {
22646
+ log13.warn("[guard] CODEFORGE_DISABLE_WORKTREE_GUARD 已启用,session-worktree-guard 全部 hook 跳过;" + "本次 opencode 会话所有写操作将直接落到主工作区(失去隔离保护)", { env: disableEnv });
22647
+ safeWriteLog(PLUGIN_NAME22, {
23606
22648
  hook: "activate",
23607
22649
  action: "skip",
23608
22650
  source: "disable-env",
23609
22651
  env_value: disableEnv
23610
22652
  });
23611
- logLifecycle(PLUGIN_NAME25, "activate", { disabled_by_env: true });
22653
+ logLifecycle(PLUGIN_NAME22, "activate", { disabled_by_env: true });
23612
22654
  return {};
23613
22655
  }
23614
22656
  const mainRoot = resolveMainRoot2(ctx.directory ?? process.cwd());
@@ -23616,13 +22658,13 @@ var sessionWorktreeGuardPlugin = async (ctx) => {
23616
22658
  try {
23617
22659
  policyCfg = await loadPolicy(mainRoot);
23618
22660
  } catch (err) {
23619
- log14.warn("loadPolicy failed (class E skipped)", {
22661
+ log13.warn("loadPolicy failed (class E skipped)", {
23620
22662
  mainRoot,
23621
22663
  error: err instanceof Error ? err.message : String(err)
23622
22664
  });
23623
22665
  }
23624
22666
  const perAgentEnabled = !!policyCfg.per_agent && Object.keys(policyCfg.per_agent).length > 0;
23625
- logLifecycle(PLUGIN_NAME25, "activate", {
22667
+ logLifecycle(PLUGIN_NAME22, "activate", {
23626
22668
  mainRoot,
23627
22669
  CODEFORGE_SESSION_ID: process.env["CODEFORGE_SESSION_ID"] ?? "(not set)"
23628
22670
  });
@@ -23633,12 +22675,12 @@ var sessionWorktreeGuardPlugin = async (ctx) => {
23633
22675
  const isWrite = isWriteOperation(input.tool, output.args ?? {}, mainRoot);
23634
22676
  if (isWrite && !_sessionIdMissingWarned) {
23635
22677
  _sessionIdMissingWarned = true;
23636
- log14.warn("[guard] sessionID 缺失,无法绑定 worktree;本会话所有写操作将落到主工作区(仅本进程内 warn 一次)。" + "排查:grep no-session-id ~/.cache/codeforge/plugins.log", {
22678
+ log13.warn("[guard] sessionID 缺失,无法绑定 worktree;本会话所有写操作将落到主工作区(仅本进程内 warn 一次)。" + "排查:grep no-session-id ~/.cache/codeforge/plugins.log", {
23637
22679
  tool: input.tool,
23638
22680
  opencode_version_hint: "需 opencode >= 0.x 才会在 tool.execute.before 注入 input.sessionID"
23639
22681
  });
23640
22682
  }
23641
- safeWriteLog(PLUGIN_NAME25, {
22683
+ safeWriteLog(PLUGIN_NAME22, {
23642
22684
  hook: "tool.execute.before",
23643
22685
  tool: input.tool,
23644
22686
  action: "skip",
@@ -23648,14 +22690,14 @@ var sessionWorktreeGuardPlugin = async (ctx) => {
23648
22690
  return;
23649
22691
  }
23650
22692
  let denied;
23651
- await safeAsync(PLUGIN_NAME25, "tool.execute.before", async () => {
22693
+ await safeAsync(PLUGIN_NAME22, "tool.execute.before", async () => {
23652
22694
  const toolName = input.tool;
23653
22695
  const argsObj = output.args ?? {};
23654
22696
  let entry = null;
23655
22697
  try {
23656
22698
  entry = await getSessionWorktree(sessionId, mainRoot);
23657
22699
  } catch (err) {
23658
- log14.warn(`getSessionWorktree failed (跳过本次检查)`, {
22700
+ log13.warn(`getSessionWorktree failed (跳过本次检查)`, {
23659
22701
  sessionId,
23660
22702
  mainRoot,
23661
22703
  error: err instanceof Error ? err.message : String(err)
@@ -23672,8 +22714,8 @@ var sessionWorktreeGuardPlugin = async (ctx) => {
23672
22714
  const parentEntry = await getSessionWorktree(parentId, mainRoot);
23673
22715
  if (parentEntry && parentEntry.status === "active") {
23674
22716
  entry = parentEntry;
23675
- log14.debug?.(`[child-inherit] session ${sessionId} 继承父 ${parentId} 的 worktree`, { parentSessionId: parentId, worktreePath: parentEntry.worktreePath });
23676
- safeWriteLog(PLUGIN_NAME25, {
22717
+ log13.debug?.(`[child-inherit] session ${sessionId} 继承父 ${parentId} 的 worktree`, { parentSessionId: parentId, worktreePath: parentEntry.worktreePath });
22718
+ safeWriteLog(PLUGIN_NAME22, {
23677
22719
  hook: "tool.execute.before",
23678
22720
  tool: toolName,
23679
22721
  sessionID: input.sessionID,
@@ -23685,14 +22727,14 @@ var sessionWorktreeGuardPlugin = async (ctx) => {
23685
22727
  }
23686
22728
  }
23687
22729
  } catch (lookupErr) {
23688
- log14.debug?.("[child-inherit] lookupParentSessionId 抛错(已隔离,退回 lazy-bind)", { error: lookupErr instanceof Error ? lookupErr.message : String(lookupErr) });
22730
+ log13.debug?.("[child-inherit] lookupParentSessionId 抛错(已隔离,退回 lazy-bind)", { error: lookupErr instanceof Error ? lookupErr.message : String(lookupErr) });
23689
22731
  }
23690
22732
  }
23691
22733
  if (!entry) {
23692
22734
  try {
23693
22735
  entry = await bindSessionWorktree({ sessionId, mainRoot });
23694
- log14.info(`[lazy-bind] auto-created worktree for session ${sessionId}`, { branch: entry.branch, path: entry.worktreePath });
23695
- safeWriteLog(PLUGIN_NAME25, {
22736
+ log13.info(`[lazy-bind] auto-created worktree for session ${sessionId}`, { branch: entry.branch, path: entry.worktreePath });
22737
+ safeWriteLog(PLUGIN_NAME22, {
23696
22738
  hook: "tool.execute.before",
23697
22739
  tool: toolName,
23698
22740
  sessionID: input.sessionID,
@@ -23711,13 +22753,13 @@ var sessionWorktreeGuardPlugin = async (ctx) => {
23711
22753
  alreadyNotified
23712
22754
  });
23713
22755
  _bindFailNotified.add(sessionId);
23714
- log14.warn(`[lazy-bind] DENY (bind failed)`, {
22756
+ log13.warn(`[lazy-bind] DENY (bind failed)`, {
23715
22757
  sessionId,
23716
22758
  tool: toolName,
23717
22759
  error: errMsg,
23718
22760
  throttled: alreadyNotified
23719
22761
  });
23720
- safeWriteLog(PLUGIN_NAME25, {
22762
+ safeWriteLog(PLUGIN_NAME22, {
23721
22763
  hook: "tool.execute.before",
23722
22764
  tool: toolName,
23723
22765
  sessionID: input.sessionID,
@@ -23740,7 +22782,7 @@ var sessionWorktreeGuardPlugin = async (ctx) => {
23740
22782
  sessionId: entry.sessionId,
23741
22783
  mainRoot
23742
22784
  }).catch((err) => {
23743
- log14.warn("touchEntryUpdatedAt 失败 (已忽略)", {
22785
+ log13.warn("touchEntryUpdatedAt 失败 (已忽略)", {
23744
22786
  sessionId: entry?.sessionId,
23745
22787
  error: err instanceof Error ? err.message : String(err)
23746
22788
  });
@@ -23753,13 +22795,13 @@ var sessionWorktreeGuardPlugin = async (ctx) => {
23753
22795
  const caller = input.agent;
23754
22796
  if (caller !== undefined && caller !== "codeforge") {
23755
22797
  const reason = `[session-worktree-guard] DENIED: session_merge action=merge 仅 codeforge orchestrator 或用户可调;当前 caller=${caller}`;
23756
- log14.warn(reason, {
22798
+ log13.warn(reason, {
23757
22799
  sessionId,
23758
22800
  tool: toolName,
23759
22801
  action,
23760
22802
  caller
23761
22803
  });
23762
- safeWriteLog(PLUGIN_NAME25, {
22804
+ safeWriteLog(PLUGIN_NAME22, {
23763
22805
  hook: "tool.execute.before",
23764
22806
  tool: toolName,
23765
22807
  sessionID: input.sessionID,
@@ -23774,15 +22816,15 @@ var sessionWorktreeGuardPlugin = async (ctx) => {
23774
22816
  }
23775
22817
  }
23776
22818
  if (perAgentEnabled && isWriteOperation(toolName, argsObj, mainRoot)) {
23777
- const caller = await resolveAgentForGuard({ sessionID: input.sessionID, agent: input.agent }, ctx.client, log14);
22819
+ const caller = await resolveAgentForGuard({ sessionID: input.sessionID, agent: input.agent }, ctx.client, log13);
23778
22820
  if (caller === null) {
23779
22821
  const reason = `[session-worktree-guard] DENIED: per-agent ACL 启用但 agent 解析失败 ` + `(L1 input.agent 缺, L2a chat-agent-cache 无, L2 IPC 反查失败) — fail-closed 拒绝写 ${toolName} ` + `(ADR:discover-write-permission-acl)`;
23780
- log14.warn(reason, {
22822
+ log13.warn(reason, {
23781
22823
  sessionId,
23782
22824
  tool: toolName,
23783
22825
  source: "per-agent-acl-fail-closed"
23784
22826
  });
23785
- safeWriteLog(PLUGIN_NAME25, {
22827
+ safeWriteLog(PLUGIN_NAME22, {
23786
22828
  hook: "tool.execute.before",
23787
22829
  tool: toolName,
23788
22830
  sessionID: input.sessionID,
@@ -23800,14 +22842,14 @@ var sessionWorktreeGuardPlugin = async (ctx) => {
23800
22842
  const dec = checkFileAccess(agentAcl, relPath, "write");
23801
22843
  if (dec.action === "deny") {
23802
22844
  const reason = `[session-worktree-guard] DENIED: agent='${caller}' 写路径不在 ACL 白名单 — ` + `${relPath} (${dec.reason})`;
23803
- log14.warn(reason, {
22845
+ log13.warn(reason, {
23804
22846
  sessionId,
23805
22847
  tool: toolName,
23806
22848
  caller,
23807
22849
  path: relPath,
23808
22850
  acl_decision: dec
23809
22851
  });
23810
- safeWriteLog(PLUGIN_NAME25, {
22852
+ safeWriteLog(PLUGIN_NAME22, {
23811
22853
  hook: "tool.execute.before",
23812
22854
  tool: toolName,
23813
22855
  sessionID: input.sessionID,
@@ -23845,13 +22887,13 @@ var sessionWorktreeGuardPlugin = async (ctx) => {
23845
22887
  const reasonBase = `[session-worktree-guard] DENIED: 当前 session 要求先调用 plan_read(plan_id="${entry.requiredPlanId}") 再执行写操作`;
23846
22888
  const reason = inherited ? `${reasonBase}
23847
22889
  [gate-deny] child session=${sessionId} 继承父 entry 但父 session planReadOk=false,父 session=${entry.sessionId} 需先调 plan_read(plan_id="${entry.requiredPlanId}")` : reasonBase;
23848
- log14.warn(reason, {
22890
+ log13.warn(reason, {
23849
22891
  tool: toolName,
23850
22892
  sessionId,
23851
22893
  requiredPlanId: entry.requiredPlanId,
23852
22894
  inheritedFromParent: inherited ? entry.sessionId : undefined
23853
22895
  });
23854
- safeWriteLog(PLUGIN_NAME25, {
22896
+ safeWriteLog(PLUGIN_NAME22, {
23855
22897
  hook: "tool.execute.before",
23856
22898
  tool: toolName,
23857
22899
  sessionID: input.sessionID,
@@ -23866,10 +22908,10 @@ var sessionWorktreeGuardPlugin = async (ctx) => {
23866
22908
  if (toolName === "bash") {
23867
22909
  const command = argsObj["command"];
23868
22910
  if (typeof command === "string" && commandContainsMainRootExcludingWorktree(command, mainRoot, worktreePath) && detectBashWriteIntent(command, mainRoot)) {
23869
- const caller = await resolveAgentForGuard({ sessionID: input.sessionID, agent: input.agent }, ctx.client, log14);
22911
+ const caller = await resolveAgentForGuard({ sessionID: input.sessionID, agent: input.agent }, ctx.client, log13);
23870
22912
  if (caller !== null && CLASS_B_CALLER_WHITELIST.has(caller)) {
23871
- log14.debug?.(`[class-b-whitelist] allow caller=${caller}`, { sessionId, tool: toolName, command: command.slice(0, 200) });
23872
- safeWriteLog(PLUGIN_NAME25, {
22913
+ log13.debug?.(`[class-b-whitelist] allow caller=${caller}`, { sessionId, tool: toolName, command: command.slice(0, 200) });
22914
+ safeWriteLog(PLUGIN_NAME22, {
23873
22915
  hook: "tool.execute.before",
23874
22916
  tool: toolName,
23875
22917
  sessionID: input.sessionID,
@@ -23882,8 +22924,8 @@ var sessionWorktreeGuardPlugin = async (ctx) => {
23882
22924
  const callerTag = caller === null ? "unresolved" : caller;
23883
22925
  const snippet = command.length > 60 ? command.slice(0, 60) + "…" : command;
23884
22926
  const reason = `[session-worktree-guard] DENIED: bash.command 含主仓绝对路径写操作 (${snippet}) [caller=${callerTag}],请在当前 session worktree (${worktreePath}) 内操作`;
23885
- log14.warn(reason, { sessionId, caller: callerTag, command: command.slice(0, 200) });
23886
- safeWriteLog(PLUGIN_NAME25, {
22927
+ log13.warn(reason, { sessionId, caller: callerTag, command: command.slice(0, 200) });
22928
+ safeWriteLog(PLUGIN_NAME22, {
23887
22929
  hook: "tool.execute.before",
23888
22930
  tool: toolName,
23889
22931
  sessionID: input.sessionID,
@@ -23902,8 +22944,8 @@ var sessionWorktreeGuardPlugin = async (ctx) => {
23902
22944
  if (typeof filePath === "string") {
23903
22945
  const newPath = rewritePath(filePath, mainRoot, worktreePath);
23904
22946
  if (newPath !== null) {
23905
- log14.info(`rewrote ${toolName}.filePath: ${filePath} → ${newPath}`);
23906
- safeWriteLog(PLUGIN_NAME25, {
22947
+ log13.info(`rewrote ${toolName}.filePath: ${filePath} → ${newPath}`);
22948
+ safeWriteLog(PLUGIN_NAME22, {
23907
22949
  hook: "tool.execute.before",
23908
22950
  tool: toolName,
23909
22951
  field: "filePath",
@@ -23920,8 +22962,8 @@ var sessionWorktreeGuardPlugin = async (ctx) => {
23920
22962
  if (typeof target === "string") {
23921
22963
  const newTarget = rewritePath(target, mainRoot, worktreePath);
23922
22964
  if (newTarget !== null) {
23923
- log14.info(`rewrote ast_edit.target: ${target} → ${newTarget}`);
23924
- safeWriteLog(PLUGIN_NAME25, {
22965
+ log13.info(`rewrote ast_edit.target: ${target} → ${newTarget}`);
22966
+ safeWriteLog(PLUGIN_NAME22, {
23925
22967
  hook: "tool.execute.before",
23926
22968
  tool: toolName,
23927
22969
  field: "target",
@@ -23936,8 +22978,8 @@ var sessionWorktreeGuardPlugin = async (ctx) => {
23936
22978
  if (typeof root === "string") {
23937
22979
  const newRoot = rewritePath(root, mainRoot, worktreePath);
23938
22980
  if (newRoot !== null) {
23939
- log14.info(`rewrote ast_edit.root: ${root} → ${newRoot}`);
23940
- safeWriteLog(PLUGIN_NAME25, {
22981
+ log13.info(`rewrote ast_edit.root: ${root} → ${newRoot}`);
22982
+ safeWriteLog(PLUGIN_NAME22, {
23941
22983
  hook: "tool.execute.before",
23942
22984
  tool: toolName,
23943
22985
  field: "root",
@@ -23954,8 +22996,8 @@ var sessionWorktreeGuardPlugin = async (ctx) => {
23954
22996
  if (typeof workdir === "string") {
23955
22997
  const newWorkdir = rewritePath(workdir, mainRoot, worktreePath);
23956
22998
  if (newWorkdir !== null) {
23957
- log14.info(`rewrote bash.workdir: ${workdir} → ${newWorkdir}`);
23958
- safeWriteLog(PLUGIN_NAME25, {
22999
+ log13.info(`rewrote bash.workdir: ${workdir} → ${newWorkdir}`);
23000
+ safeWriteLog(PLUGIN_NAME22, {
23959
23001
  hook: "tool.execute.before",
23960
23002
  tool: toolName,
23961
23003
  field: "workdir",
@@ -23973,7 +23015,7 @@ var sessionWorktreeGuardPlugin = async (ctx) => {
23973
23015
  }
23974
23016
  };
23975
23017
  };
23976
- var handler25 = sessionWorktreeGuardPlugin;
23018
+ var handler22 = sessionWorktreeGuardPlugin;
23977
23019
 
23978
23020
  // lib/opencode-session-probe.ts
23979
23021
  import * as path29 from "node:path";
@@ -24073,8 +23115,8 @@ function createSessionProbe(opts = {}) {
24073
23115
  }
24074
23116
 
24075
23117
  // plugins/worktree-lifecycle.ts
24076
- var PLUGIN_NAME26 = "worktree-lifecycle";
24077
- logLifecycle(PLUGIN_NAME26, "import", {});
23118
+ var PLUGIN_NAME23 = "worktree-lifecycle";
23119
+ logLifecycle(PLUGIN_NAME23, "import", {});
24078
23120
  var IDLE_TOAST_THROTTLE_MS = 60 * 60000;
24079
23121
  var IDLE_TOAST_DURATION_MS = 8000;
24080
23122
  var IDLE_TOAST_REMINDER_INTERVAL_MS = 30 * 60000;
@@ -24083,10 +23125,10 @@ var lastIdleToastAt = new Map;
24083
23125
  var pruneRunning = false;
24084
23126
  var _pruneTimer;
24085
23127
  var _probe = null;
24086
- var log15 = makePluginLogger(PLUGIN_NAME26);
23128
+ var log14 = makePluginLogger(PLUGIN_NAME23);
24087
23129
  var worktreeLifecyclePlugin = async (ctx) => {
24088
23130
  const mainRoot = ctx.directory;
24089
- logLifecycle(PLUGIN_NAME26, "activate", {
23131
+ logLifecycle(PLUGIN_NAME23, "activate", {
24090
23132
  mainRoot: mainRoot ?? "(not set)",
24091
23133
  idle_threshold_ms: IDLE_TOAST_THROTTLE_MS
24092
23134
  });
@@ -24101,13 +23143,13 @@ var worktreeLifecyclePlugin = async (ctx) => {
24101
23143
  }
24102
23144
  _probe = createSessionProbe();
24103
23145
  setImmediate(() => {
24104
- safeAsync(PLUGIN_NAME26, "activate.pruneOrphan", async () => {
23146
+ safeAsync(PLUGIN_NAME23, "activate.pruneOrphan", async () => {
24105
23147
  const result = await pruneOrphanWorktrees(mainRoot, {
24106
23148
  isSessionAlive: _probe.isSessionAlive
24107
23149
  });
24108
23150
  if (result.cleaned.length > 0 || result.failed.length > 0) {
24109
- log15.info(`[pruneOrphan] cleaned=${result.cleaned.length} failed=${result.failed.length} skipped=${result.skipped}`);
24110
- safeWriteLog(PLUGIN_NAME26, {
23151
+ log14.info(`[pruneOrphan] cleaned=${result.cleaned.length} failed=${result.failed.length} skipped=${result.skipped}`);
23152
+ safeWriteLog(PLUGIN_NAME23, {
24111
23153
  hook: "activate.pruneOrphan",
24112
23154
  cleaned: result.cleaned,
24113
23155
  failed: result.failed,
@@ -24120,7 +23162,7 @@ var worktreeLifecyclePlugin = async (ctx) => {
24120
23162
  clearInterval(_pruneTimer);
24121
23163
  _pruneTimer = setInterval(() => {
24122
23164
  if (pruneRunning) {
24123
- safeWriteLog(PLUGIN_NAME26, {
23165
+ safeWriteLog(PLUGIN_NAME23, {
24124
23166
  hook: "interval.pruneOrphan",
24125
23167
  action: "skip",
24126
23168
  reason: "previous prune still running"
@@ -24128,14 +23170,14 @@ var worktreeLifecyclePlugin = async (ctx) => {
24128
23170
  return;
24129
23171
  }
24130
23172
  pruneRunning = true;
24131
- safeAsync(PLUGIN_NAME26, "interval.pruneOrphan", async () => {
23173
+ safeAsync(PLUGIN_NAME23, "interval.pruneOrphan", async () => {
24132
23174
  try {
24133
23175
  const result = await pruneOrphanWorktrees(mainRoot, {
24134
23176
  isSessionAlive: _probe.isSessionAlive
24135
23177
  });
24136
23178
  if (result.cleaned.length > 0 || result.failed.length > 0) {
24137
- log15.info(`[pruneOrphan interval] cleaned=${result.cleaned.length} failed=${result.failed.length} skipped=${result.skipped}`);
24138
- safeWriteLog(PLUGIN_NAME26, {
23179
+ log14.info(`[pruneOrphan interval] cleaned=${result.cleaned.length} failed=${result.failed.length} skipped=${result.skipped}`);
23180
+ safeWriteLog(PLUGIN_NAME23, {
24139
23181
  hook: "interval.pruneOrphan",
24140
23182
  cleaned: result.cleaned,
24141
23183
  failed: result.failed,
@@ -24150,14 +23192,14 @@ var worktreeLifecyclePlugin = async (ctx) => {
24150
23192
  _pruneTimer.unref();
24151
23193
  return {
24152
23194
  event: async ({ event }) => {
24153
- await safeAsync(PLUGIN_NAME26, "event", async () => {
23195
+ await safeAsync(PLUGIN_NAME23, "event", async () => {
24154
23196
  const ended = extractEndedSessionID(event);
24155
23197
  if (!ended)
24156
23198
  return;
24157
23199
  if (ended.type === "session.deleted") {
24158
23200
  const entry = await getSessionWorktree(ended.sessionID, mainRoot);
24159
23201
  if (!entry || entry.status !== "active") {
24160
- safeWriteLog(PLUGIN_NAME26, {
23202
+ safeWriteLog(PLUGIN_NAME23, {
24161
23203
  hook: "event",
24162
23204
  type: ended.type,
24163
23205
  sessionID: ended.sessionID,
@@ -24172,7 +23214,7 @@ var worktreeLifecyclePlugin = async (ctx) => {
24172
23214
  const fastDirty = await isWorktreeDirty(entry.worktreePath);
24173
23215
  if (!fastDirty) {
24174
23216
  await discardSession({ sessionId: ended.sessionID, mainRoot });
24175
- safeWriteLog(PLUGIN_NAME26, {
23217
+ safeWriteLog(PLUGIN_NAME23, {
24176
23218
  hook: "event",
24177
23219
  type: ended.type,
24178
23220
  sessionID: ended.sessionID,
@@ -24185,7 +23227,7 @@ var worktreeLifecyclePlugin = async (ctx) => {
24185
23227
  }
24186
23228
  }
24187
23229
  } catch (err) {
24188
- log15.warn(`[lifecycle] empty-worktree fast-path 检测失败 (回退到常规路径)`, {
23230
+ log14.warn(`[lifecycle] empty-worktree fast-path 检测失败 (回退到常规路径)`, {
24189
23231
  sessionId: ended.sessionID,
24190
23232
  error: err instanceof Error ? err.message : String(err)
24191
23233
  });
@@ -24200,14 +23242,14 @@ var worktreeLifecyclePlugin = async (ctx) => {
24200
23242
  });
24201
23243
  }
24202
23244
  } catch (err) {
24203
- log15.warn(`[lifecycle] checkpointCommit failed (继续 discard)`, {
23245
+ log14.warn(`[lifecycle] checkpointCommit failed (继续 discard)`, {
24204
23246
  sessionId: ended.sessionID,
24205
23247
  error: err instanceof Error ? err.message : String(err)
24206
23248
  });
24207
23249
  }
24208
23250
  try {
24209
23251
  await discardSession({ sessionId: ended.sessionID, mainRoot });
24210
- safeWriteLog(PLUGIN_NAME26, {
23252
+ safeWriteLog(PLUGIN_NAME23, {
24211
23253
  hook: "event",
24212
23254
  type: ended.type,
24213
23255
  sessionID: ended.sessionID,
@@ -24217,7 +23259,7 @@ var worktreeLifecyclePlugin = async (ctx) => {
24217
23259
  worktreePath: entry.worktreePath
24218
23260
  });
24219
23261
  } catch (err) {
24220
- log15.warn(`[lifecycle] discardSession failed`, {
23262
+ log14.warn(`[lifecycle] discardSession failed`, {
24221
23263
  sessionId: ended.sessionID,
24222
23264
  error: err instanceof Error ? err.message : String(err)
24223
23265
  });
@@ -24240,8 +23282,8 @@ var worktreeLifecyclePlugin = async (ctx) => {
24240
23282
  lastIdleToastAt.set(ended.sessionID, now);
24241
23283
  const idleMin = Math.round(idleMs / 60000);
24242
23284
  const msg = `\uD83D\uDCA4 Session ${ended.sessionID.slice(0, 8)} worktree 已空闲 ${idleMin}min,` + `用 /merge 收尾或 /discard-session 放弃`;
24243
- const sent = await showToast2(client, { message: msg, variant: "default", duration: IDLE_TOAST_DURATION_MS, title: "CodeForge" }, log15);
24244
- safeWriteLog(PLUGIN_NAME26, {
23285
+ const sent = await showToast2(client, { message: msg, variant: "default", duration: IDLE_TOAST_DURATION_MS, title: "CodeForge" }, log14);
23286
+ safeWriteLog(PLUGIN_NAME23, {
24245
23287
  hook: "event",
24246
23288
  type: ended.type,
24247
23289
  sessionID: ended.sessionID,
@@ -24254,39 +23296,36 @@ var worktreeLifecyclePlugin = async (ctx) => {
24254
23296
  }
24255
23297
  };
24256
23298
  };
24257
- var handler26 = worktreeLifecyclePlugin;
23299
+ var handler23 = worktreeLifecyclePlugin;
24258
23300
 
24259
23301
  // src/index.ts
24260
23302
  var PLUGIN_ID = "codeforge";
24261
- var log16 = makePluginLogger(PLUGIN_ID);
23303
+ var log15 = makePluginLogger(PLUGIN_ID);
24262
23304
  logLifecycle(PLUGIN_ID, "import", { entry: "src/index.ts" });
24263
23305
  var HANDLERS = [
24264
23306
  { name: "agent-router", init: handler },
24265
23307
  { name: "arena-orchestrator", init: handler2 },
24266
23308
  { name: "auto-commit", init: handler3 },
24267
- { name: "auto-learning", init: handler4 },
24268
- { name: "channels", init: handler5 },
24269
- { name: "chat-agent-cache", init: handler6 },
24270
- { name: "codeforge-tools", init: handler8 },
24271
- { name: "discover-spec-suggest", init: handler9 },
24272
- { name: "kh-auto-context", init: handler10 },
24273
- { name: "kh-reminder", init: handler11 },
24274
- { name: "memories-context", init: handler12 },
24275
- { name: "model-fallback", init: handler13 },
24276
- { name: "parallel-tool-nudge", init: handler16 },
24277
- { name: "pwsh-utf8", init: handler17 },
24278
- { name: "session-recovery", init: handler18 },
24279
- { name: "subtask-heartbeat", init: handler14 },
24280
- { name: "subtasks", init: handler19 },
24281
- { name: "parallel-status", init: handler15 },
24282
- { name: "terminal-monitor", init: handler20 },
24283
- { name: "token-manager", init: handler21 },
24284
- { name: "tool-heartbeat", init: handler7 },
24285
- { name: "tool-policy", init: handler22 },
24286
- { name: "update-checker", init: handler23 },
24287
- { name: "workflow-engine", init: handler24 },
24288
- { name: "session-worktree-guard", init: handler25 },
24289
- { name: "worktree-lifecycle", init: handler26 }
23309
+ { name: "channels", init: handler4 },
23310
+ { name: "chat-agent-cache", init: handler5 },
23311
+ { name: "codeforge-tools", init: handler7 },
23312
+ { name: "discover-spec-suggest", init: handler8 },
23313
+ { name: "memories-context", init: handler9 },
23314
+ { name: "model-fallback", init: handler10 },
23315
+ { name: "parallel-tool-nudge", init: handler13 },
23316
+ { name: "pwsh-utf8", init: handler14 },
23317
+ { name: "session-recovery", init: handler15 },
23318
+ { name: "subtask-heartbeat", init: handler11 },
23319
+ { name: "subtasks", init: handler16 },
23320
+ { name: "parallel-status", init: handler12 },
23321
+ { name: "terminal-monitor", init: handler17 },
23322
+ { name: "token-manager", init: handler18 },
23323
+ { name: "tool-heartbeat", init: handler6 },
23324
+ { name: "tool-policy", init: handler19 },
23325
+ { name: "update-checker", init: handler20 },
23326
+ { name: "workflow-engine", init: handler21 },
23327
+ { name: "session-worktree-guard", init: handler22 },
23328
+ { name: "worktree-lifecycle", init: handler23 }
24290
23329
  ];
24291
23330
  function makeSerialHook(hookName, fns) {
24292
23331
  return async (input, output) => {
@@ -24296,7 +23335,7 @@ function makeSerialHook(hookName, fns) {
24296
23335
  } catch (err) {
24297
23336
  if (isDeniedError(err))
24298
23337
  throw err;
24299
- log16.warn(`[${PLUGIN_ID}] ${hookName} handler 异常(已隔离)`, {
23338
+ log15.warn(`[${PLUGIN_ID}] ${hookName} handler 异常(已隔离)`, {
24300
23339
  error: err instanceof Error ? err.message : String(err)
24301
23340
  });
24302
23341
  }
@@ -24309,7 +23348,7 @@ function createCodeforgeServer(opts) {
24309
23348
  const yieldResult = shouldYieldToLocalPlugin({ directory: input.directory });
24310
23349
  if (yieldResult.yield) {
24311
23350
  const msg = formatYieldLog(yieldResult);
24312
- log16.info(msg, { reason: yieldResult.reason, markerPath: yieldResult.markerPath });
23351
+ log15.info(msg, { reason: yieldResult.reason, markerPath: yieldResult.markerPath });
24313
23352
  logLifecycle(PLUGIN_ID, "activate", {
24314
23353
  yield_to_local: true,
24315
23354
  yield_reason: yieldResult.reason,
@@ -24327,7 +23366,7 @@ function createCodeforgeServer(opts) {
24327
23366
  if (r.status === "fulfilled" && r.value && typeof r.value === "object") {
24328
23367
  hooksList.push(r.value);
24329
23368
  } else if (r.status === "rejected") {
24330
- log16.warn(`[${PLUGIN_ID}] handler ${HANDLERS[i].name} init failed (隔离,其他 handler 继续)`, { error: r.reason instanceof Error ? r.reason.message : String(r.reason) });
23369
+ log15.warn(`[${PLUGIN_ID}] handler ${HANDLERS[i].name} init failed (隔离,其他 handler 继续)`, { error: r.reason instanceof Error ? r.reason.message : String(r.reason) });
24331
23370
  }
24332
23371
  });
24333
23372
  logLifecycle(PLUGIN_ID, "activate", {
@@ -24382,7 +23421,7 @@ function createCodeforgeServer(opts) {
24382
23421
  } catch (err) {
24383
23422
  if (isDeniedError(err))
24384
23423
  throw err;
24385
- log16.warn(`[${PLUGIN_ID}] event handler 异常(已隔离)`, {
23424
+ log15.warn(`[${PLUGIN_ID}] event handler 异常(已隔离)`, {
24386
23425
  error: err instanceof Error ? err.message : String(err)
24387
23426
  });
24388
23427
  }