@andyqiu/codeforge 0.5.8 → 0.5.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -10119,6 +10119,148 @@ var channelsServer = async (ctx) => {
10119
10119
  };
10120
10120
  var handler5 = channelsServer;
10121
10121
 
10122
+ // lib/agent-resolver.ts
10123
+ var chatAgentCacheReader = null;
10124
+ function setChatAgentCacheReader(reader) {
10125
+ chatAgentCacheReader = reader;
10126
+ }
10127
+ async function resolveCurrentAgent(client, sessionID, log4) {
10128
+ try {
10129
+ const sessionApi = client?.session;
10130
+ if (!sessionApi || typeof sessionApi.get !== "function") {
10131
+ log4.warn(`client.session.get unavailable`, { sessionID });
10132
+ return;
10133
+ }
10134
+ const res = await sessionApi.get({ path: { id: sessionID } });
10135
+ const data = res?.data ?? res;
10136
+ const rawAgent = data?.agent;
10137
+ if (typeof rawAgent !== "string" || rawAgent === "") {
10138
+ log4.warn(`client.session.get returned no string agent (返回 undefined)`, {
10139
+ sessionID,
10140
+ dataKeys: data && typeof data === "object" ? Object.keys(data) : null
10141
+ });
10142
+ return;
10143
+ }
10144
+ return rawAgent;
10145
+ } catch (err) {
10146
+ log4.warn(`client.session.get failed (返回 undefined)`, {
10147
+ sessionID,
10148
+ error: err instanceof Error ? err.message : String(err)
10149
+ });
10150
+ return;
10151
+ }
10152
+ }
10153
+ async function resolveAgentForGuard(input, client, log4, opts = {}) {
10154
+ if (typeof input.agent === "string" && input.agent.length > 0) {
10155
+ return input.agent;
10156
+ }
10157
+ if (!opts.skipCache && chatAgentCacheReader) {
10158
+ try {
10159
+ const cached = chatAgentCacheReader(input.sessionID);
10160
+ if (typeof cached === "string" && cached.length > 0) {
10161
+ return cached;
10162
+ }
10163
+ } catch (err) {
10164
+ log4.warn(`chat-agent-cache reader failed (降级到 L2 IPC)`, {
10165
+ sessionID: input.sessionID,
10166
+ error: err instanceof Error ? err.message : String(err)
10167
+ });
10168
+ }
10169
+ }
10170
+ if (!opts.skipIpcLookup) {
10171
+ const viaSession = await resolveCurrentAgent(client, input.sessionID, log4);
10172
+ if (typeof viaSession === "string" && viaSession.length > 0) {
10173
+ return viaSession;
10174
+ }
10175
+ }
10176
+ return null;
10177
+ }
10178
+
10179
+ // plugins/chat-agent-cache.ts
10180
+ var PLUGIN_NAME6 = "chat-agent-cache";
10181
+ logLifecycle(PLUGIN_NAME6, "import", {});
10182
+ var SESSION_CAP = 500;
10183
+ var SESSION_TTL_MS = 24 * 60 * 60 * 1000;
10184
+ var sessionAgentMap = new Map;
10185
+ function pruneIfOversize() {
10186
+ while (sessionAgentMap.size > SESSION_CAP) {
10187
+ const oldestKey = sessionAgentMap.keys().next().value;
10188
+ if (oldestKey === undefined)
10189
+ break;
10190
+ sessionAgentMap.delete(oldestKey);
10191
+ }
10192
+ }
10193
+ function isExpired(entry, now = Date.now()) {
10194
+ return now - entry.ts > SESSION_TTL_MS;
10195
+ }
10196
+ function rememberSessionAgent(sessionID, agent) {
10197
+ if (typeof sessionID !== "string" || sessionID.length === 0)
10198
+ return;
10199
+ if (typeof agent !== "string" || agent.length === 0)
10200
+ return;
10201
+ sessionAgentMap.set(sessionID, { agent, ts: Date.now() });
10202
+ pruneIfOversize();
10203
+ }
10204
+ function readSessionAgent(sessionID) {
10205
+ if (typeof sessionID !== "string" || sessionID.length === 0)
10206
+ return null;
10207
+ const entry = sessionAgentMap.get(sessionID);
10208
+ if (!entry)
10209
+ return null;
10210
+ if (isExpired(entry)) {
10211
+ sessionAgentMap.delete(sessionID);
10212
+ return null;
10213
+ }
10214
+ return entry.agent;
10215
+ }
10216
+ var chatAgentCachePlugin = async (ctx) => {
10217
+ setChatAgentCacheReader(readSessionAgent);
10218
+ logLifecycle(PLUGIN_NAME6, "activate", {
10219
+ directory: ctx.directory,
10220
+ session_cap: SESSION_CAP,
10221
+ session_ttl_ms: SESSION_TTL_MS
10222
+ });
10223
+ return {
10224
+ "chat.params": async (input, _output) => {
10225
+ await safeAsync(PLUGIN_NAME6, "chat.params", async () => {
10226
+ const sid = input?.sessionID;
10227
+ const agent = input?.agent;
10228
+ if (typeof sid !== "string" || sid.length === 0)
10229
+ return;
10230
+ if (typeof agent !== "string" || agent.length === 0)
10231
+ return;
10232
+ rememberSessionAgent(sid, agent);
10233
+ safeWriteLog(PLUGIN_NAME6, {
10234
+ hook: "chat.params",
10235
+ sessionID: sid,
10236
+ agent,
10237
+ action: "cache",
10238
+ map_size: sessionAgentMap.size
10239
+ });
10240
+ });
10241
+ },
10242
+ "chat.message": async (input, _output) => {
10243
+ await safeAsync(PLUGIN_NAME6, "chat.message", async () => {
10244
+ const sid = input?.sessionID;
10245
+ const agent = input?.agent;
10246
+ if (typeof sid !== "string" || sid.length === 0)
10247
+ return;
10248
+ if (typeof agent !== "string" || agent.length === 0)
10249
+ return;
10250
+ rememberSessionAgent(sid, agent);
10251
+ safeWriteLog(PLUGIN_NAME6, {
10252
+ hook: "chat.message",
10253
+ sessionID: sid,
10254
+ agent,
10255
+ action: "cache",
10256
+ map_size: sessionAgentMap.size
10257
+ });
10258
+ });
10259
+ }
10260
+ };
10261
+ };
10262
+ var handler6 = chatAgentCachePlugin;
10263
+
10122
10264
  // plugins/codeforge-tools.ts
10123
10265
  import { tool } from "@opencode-ai/plugin";
10124
10266
 
@@ -14856,6 +14998,15 @@ ${r.text.slice(0, 800)}`
14856
14998
  err: describe5(err)
14857
14999
  });
14858
15000
  }
15001
+ const mainRoot = this.opts.mainRoot ?? this.opts.directory;
15002
+ if (mainRoot) {
15003
+ const cid = childId;
15004
+ discardSession({ sessionId: cid, mainRoot }).catch((err) => {
15005
+ this.opts.log?.("warn", `[spawner] auto-discard 失败 ${cid}`, {
15006
+ err: describe5(err)
15007
+ });
15008
+ });
15009
+ }
14859
15010
  }
14860
15011
  }
14861
15012
  }
@@ -14955,7 +15106,6 @@ import { promises as fs14 } from "node:fs";
14955
15106
  import * as path17 from "node:path";
14956
15107
  var DEFAULT_RUNTIME = {
14957
15108
  autonomy: {
14958
- default_mode: "semi",
14959
15109
  downgrade_on_risky: true
14960
15110
  },
14961
15111
  runtime: {
@@ -15039,13 +15189,6 @@ function parseRuntime(raw, abs) {
15039
15189
  const cfg = cloneDefaults();
15040
15190
  if (root.autonomy && typeof root.autonomy === "object" && !Array.isArray(root.autonomy)) {
15041
15191
  const a = root.autonomy;
15042
- if (a.default_mode !== undefined) {
15043
- if (a.default_mode === "step" || a.default_mode === "semi" || a.default_mode === "full") {
15044
- cfg.autonomy.default_mode = a.default_mode;
15045
- } else {
15046
- warnings.push(`autonomy.default_mode invalid: ${String(a.default_mode)} (keep ${cfg.autonomy.default_mode})`);
15047
- }
15048
- }
15049
15192
  if (a.downgrade_on_risky !== undefined) {
15050
15193
  if (typeof a.downgrade_on_risky === "boolean") {
15051
15194
  cfg.autonomy.downgrade_on_risky = a.downgrade_on_risky;
@@ -15165,8 +15308,8 @@ function parseRuntime(raw, abs) {
15165
15308
  }
15166
15309
 
15167
15310
  // plugins/tool-heartbeat.ts
15168
- var PLUGIN_NAME6 = "tool-heartbeat";
15169
- logLifecycle(PLUGIN_NAME6, "import", {});
15311
+ var PLUGIN_NAME7 = "tool-heartbeat";
15312
+ logLifecycle(PLUGIN_NAME7, "import", {});
15170
15313
  var HEARTBEAT_INTERVAL_MS = 15000;
15171
15314
  var ALERT_30S_MS = 30000;
15172
15315
  var ALERT_60S_MS = 60000;
@@ -15248,15 +15391,15 @@ async function showToast(client, payload, log5) {
15248
15391
  return false;
15249
15392
  }
15250
15393
  }
15251
- var log5 = makePluginLogger(PLUGIN_NAME6);
15394
+ var log5 = makePluginLogger(PLUGIN_NAME7);
15252
15395
  var toolHeartbeatServer = async (ctx) => {
15253
- logLifecycle(PLUGIN_NAME6, "activate", {
15396
+ logLifecycle(PLUGIN_NAME7, "activate", {
15254
15397
  directory: ctx.directory,
15255
15398
  intervalMs: HEARTBEAT_INTERVAL_MS
15256
15399
  });
15257
15400
  const client = ctx.client;
15258
15401
  const interval = setInterval(() => {
15259
- safeAsync(PLUGIN_NAME6, "interval", async () => {
15402
+ safeAsync(PLUGIN_NAME7, "interval", async () => {
15260
15403
  if (inflight.size === 0)
15261
15404
  return;
15262
15405
  const records = [...inflight.values()];
@@ -15266,7 +15409,7 @@ var toolHeartbeatServer = async (ctx) => {
15266
15409
  continue;
15267
15410
  const sent = await showToast(client, { message: alert.message, variant: alert.variant }, log5);
15268
15411
  r.alertsSent.add(alert.threshold);
15269
- safeWriteLog(PLUGIN_NAME6, {
15412
+ safeWriteLog(PLUGIN_NAME7, {
15270
15413
  hook: "interval",
15271
15414
  tool: r.toolName,
15272
15415
  callID: r.callID,
@@ -15284,12 +15427,12 @@ var toolHeartbeatServer = async (ctx) => {
15284
15427
  event: async () => {}
15285
15428
  };
15286
15429
  };
15287
- var handler6 = toolHeartbeatServer;
15430
+ var handler7 = toolHeartbeatServer;
15288
15431
 
15289
15432
  // plugins/codeforge-tools.ts
15290
15433
  var z30 = tool.schema;
15291
- var PLUGIN_NAME7 = "codeforge-tools";
15292
- logLifecycle(PLUGIN_NAME7, "import");
15434
+ var PLUGIN_NAME8 = "codeforge-tools";
15435
+ logLifecycle(PLUGIN_NAME8, "import");
15293
15436
  function wrap(output, metadata) {
15294
15437
  const text = typeof output === "string" ? output : JSON.stringify(output, null, 2);
15295
15438
  return metadata && Object.keys(metadata).length > 0 ? { output: text, metadata } : { output: text };
@@ -15299,7 +15442,7 @@ async function runSafe(toolName, fn) {
15299
15442
  return await fn();
15300
15443
  } catch (err) {
15301
15444
  const msg = err instanceof Error ? err.message : String(err);
15302
- safeWriteLog(PLUGIN_NAME7, {
15445
+ safeWriteLog(PLUGIN_NAME8, {
15303
15446
  level: "error",
15304
15447
  tool: toolName,
15305
15448
  error: msg
@@ -15696,7 +15839,7 @@ var codeforgeToolsServer = async (ctx) => {
15696
15839
  const rt = loadRuntimeSync({ root: ctx.directory });
15697
15840
  const browserEnabled = rt.runtime.tools.browser.enabled;
15698
15841
  const activeTools = browserEnabled ? [...CORE_TOOL_NAMES, ...BROWSER_TOOL_NAMES] : [...CORE_TOOL_NAMES];
15699
- logLifecycle(PLUGIN_NAME7, "activate", {
15842
+ logLifecycle(PLUGIN_NAME8, "activate", {
15700
15843
  directory: ctx.directory,
15701
15844
  tools: activeTools,
15702
15845
  count: activeTools.length,
@@ -15711,7 +15854,8 @@ var codeforgeToolsServer = async (ctx) => {
15711
15854
  const spawner = new ProductionSpawner({
15712
15855
  client: ctx.client,
15713
15856
  directory: ctx.directory ?? process.cwd(),
15714
- log: (level, msg, data) => safeWriteLog(PLUGIN_NAME7, { level, msg, data })
15857
+ mainRoot: ctx.directory ?? process.cwd(),
15858
+ log: (level, msg, data) => safeWriteLog(PLUGIN_NAME8, { level, msg, data })
15715
15859
  });
15716
15860
  __setContext({
15717
15861
  mainRoot: ctx.directory ?? process.cwd(),
@@ -16003,7 +16147,7 @@ var codeforgeToolsServer = async (ctx) => {
16003
16147
  }
16004
16148
  };
16005
16149
  };
16006
- var handler7 = codeforgeToolsServer;
16150
+ var handler8 = codeforgeToolsServer;
16007
16151
 
16008
16152
  // plugins/discover-spec-suggest.ts
16009
16153
  import { readFileSync as readFileSync3, readdirSync, statSync as statSync3 } from "node:fs";
@@ -16185,26 +16329,26 @@ function isValidSlug(slug) {
16185
16329
  }
16186
16330
 
16187
16331
  // plugins/discover-spec-suggest.ts
16188
- var PLUGIN_NAME8 = "discover-spec-suggest";
16189
- logLifecycle(PLUGIN_NAME8, "import", {});
16332
+ var PLUGIN_NAME9 = "discover-spec-suggest";
16333
+ logLifecycle(PLUGIN_NAME9, "import", {});
16190
16334
  var TARGET_AGENT = "codeforge";
16191
- var SESSION_CAP = 200;
16192
- var SESSION_TTL_MS = 24 * 60 * 60 * 1000;
16335
+ var SESSION_CAP2 = 200;
16336
+ var SESSION_TTL_MS2 = 24 * 60 * 60 * 1000;
16193
16337
  var MATCH_THRESHOLD = 0.15;
16194
16338
  var MAX_CANDIDATES = 3;
16195
16339
  var NUDGE_MAX_LEN = 1500;
16196
16340
  var SPECS_REL_DIR = join14(".codeforge", "specs");
16197
16341
  var sessionMap = new Map;
16198
- function pruneIfOversize() {
16199
- while (sessionMap.size > SESSION_CAP) {
16342
+ function pruneIfOversize2() {
16343
+ while (sessionMap.size > SESSION_CAP2) {
16200
16344
  const oldestKey = sessionMap.keys().next().value;
16201
16345
  if (oldestKey === undefined)
16202
16346
  break;
16203
16347
  sessionMap.delete(oldestKey);
16204
16348
  }
16205
16349
  }
16206
- function isExpired(entry, now = Date.now()) {
16207
- return now - entry.ts > SESSION_TTL_MS;
16350
+ function isExpired2(entry, now = Date.now()) {
16351
+ return now - entry.ts > SESSION_TTL_MS2;
16208
16352
  }
16209
16353
  var specIndex = [];
16210
16354
  function defaultReader(p) {
@@ -16299,7 +16443,7 @@ function loadSpecs(rootDir, opts = {}) {
16299
16443
  const dirReader = opts.dirReader ?? defaultDirReader;
16300
16444
  const dirExists = opts.dirExists ?? defaultDirExists;
16301
16445
  const statReader = opts.statReader ?? defaultStatReader;
16302
- const log6 = makePluginLogger(PLUGIN_NAME8);
16446
+ const log6 = makePluginLogger(PLUGIN_NAME9);
16303
16447
  const specsRoot = join14(rootDir, SPECS_REL_DIR);
16304
16448
  const records = [];
16305
16449
  if (!dirExists(specsRoot)) {
@@ -16427,7 +16571,7 @@ function renderCandidatesNudge(matched) {
16427
16571
  return body.slice(0, NUDGE_MAX_LEN - 4) + `
16428
16572
  …`;
16429
16573
  }
16430
- var log6 = makePluginLogger(PLUGIN_NAME8);
16574
+ var log6 = makePluginLogger(PLUGIN_NAME9);
16431
16575
  var discoverSpecSuggestServer = async (ctx) => {
16432
16576
  try {
16433
16577
  const loaded = loadSpecs(ctx.directory ?? process.cwd());
@@ -16438,19 +16582,19 @@ var discoverSpecSuggestServer = async (ctx) => {
16438
16582
  error: err instanceof Error ? err.message : String(err)
16439
16583
  });
16440
16584
  }
16441
- logLifecycle(PLUGIN_NAME8, "activate", {
16585
+ logLifecycle(PLUGIN_NAME9, "activate", {
16442
16586
  directory: ctx.directory,
16443
16587
  specs_loaded: specIndex.length,
16444
16588
  target_agent: TARGET_AGENT,
16445
16589
  match_threshold: MATCH_THRESHOLD,
16446
16590
  max_candidates: MAX_CANDIDATES,
16447
- session_cap: SESSION_CAP,
16448
- session_ttl_ms: SESSION_TTL_MS,
16591
+ session_cap: SESSION_CAP2,
16592
+ session_ttl_ms: SESSION_TTL_MS2,
16449
16593
  no_op: specIndex.length === 0
16450
16594
  });
16451
16595
  return {
16452
16596
  "chat.message": async (input, output) => {
16453
- await safeAsync(PLUGIN_NAME8, "chat.message", async () => {
16597
+ await safeAsync(PLUGIN_NAME9, "chat.message", async () => {
16454
16598
  if (specIndex.length === 0)
16455
16599
  return;
16456
16600
  const text = extractUserText(output);
@@ -16461,11 +16605,11 @@ var discoverSpecSuggestServer = async (ctx) => {
16461
16605
  if (!sid)
16462
16606
  return;
16463
16607
  sessionMap.set(sid, { text, agent, ts: Date.now() });
16464
- pruneIfOversize();
16608
+ pruneIfOversize2();
16465
16609
  });
16466
16610
  },
16467
16611
  "experimental.chat.system.transform": async (input, output) => {
16468
- await safeAsync(PLUGIN_NAME8, "experimental.chat.system.transform", async () => {
16612
+ await safeAsync(PLUGIN_NAME9, "experimental.chat.system.transform", async () => {
16469
16613
  if (specIndex.length === 0)
16470
16614
  return;
16471
16615
  if (!output || !Array.isArray(output.system))
@@ -16476,7 +16620,7 @@ var discoverSpecSuggestServer = async (ctx) => {
16476
16620
  const entry = sessionMap.get(sid);
16477
16621
  if (!entry)
16478
16622
  return;
16479
- if (isExpired(entry)) {
16623
+ if (isExpired2(entry)) {
16480
16624
  sessionMap.delete(sid);
16481
16625
  return;
16482
16626
  }
@@ -16491,7 +16635,7 @@ var discoverSpecSuggestServer = async (ctx) => {
16491
16635
  if (!nudge)
16492
16636
  return;
16493
16637
  output.system.push(nudge);
16494
- safeWriteLog(PLUGIN_NAME8, {
16638
+ safeWriteLog(PLUGIN_NAME9, {
16495
16639
  hook: "experimental.chat.system.transform",
16496
16640
  sessionID: sid,
16497
16641
  agent: entry.agent,
@@ -16503,7 +16647,7 @@ var discoverSpecSuggestServer = async (ctx) => {
16503
16647
  }
16504
16648
  };
16505
16649
  };
16506
- var handler8 = discoverSpecSuggestServer;
16650
+ var handler9 = discoverSpecSuggestServer;
16507
16651
 
16508
16652
  // plugins/kh-auto-context.ts
16509
16653
  init_kh_client();
@@ -16643,7 +16787,7 @@ class SessionScopedCache {
16643
16787
  var sharedKhCache = new SessionScopedCache;
16644
16788
 
16645
16789
  // plugins/kh-auto-context.ts
16646
- var PLUGIN_NAME9 = "kh-auto-context";
16790
+ var PLUGIN_NAME10 = "kh-auto-context";
16647
16791
  var INJECTION_MODE = "observe-only";
16648
16792
  function resolveInjectionMode(client) {
16649
16793
  return client.hasTransport() ? "system-injected" : "observe-only";
@@ -16742,7 +16886,7 @@ var CODEFORGE_CONSTRAINTS = [
16742
16886
  ];
16743
16887
  async function seedConstraints(client, log7) {
16744
16888
  if (!client.hasTransport()) {
16745
- log7?.debug?.(`[${PLUGIN_NAME9}] seedConstraints: transport 不可用,跳过`, {});
16889
+ log7?.debug?.(`[${PLUGIN_NAME10}] seedConstraints: transport 不可用,跳过`, {});
16746
16890
  return { ok: false, reason: "transport_unavailable" };
16747
16891
  }
16748
16892
  try {
@@ -16756,7 +16900,7 @@ async function seedConstraints(client, log7) {
16756
16900
  });
16757
16901
  if (result && typeof result === "object" && "ok" in result && result.ok === false) {
16758
16902
  const r = result;
16759
- log7?.warn(`[${PLUGIN_NAME9}] seedConstraints 降级`, {
16903
+ log7?.warn(`[${PLUGIN_NAME10}] seedConstraints 降级`, {
16760
16904
  reason: r.reason,
16761
16905
  message: r.message
16762
16906
  });
@@ -16766,11 +16910,11 @@ async function seedConstraints(client, log7) {
16766
16910
  message: r.message
16767
16911
  };
16768
16912
  }
16769
- log7?.info(`[${PLUGIN_NAME9}] seedConstraints: 已写入 ${CODEFORGE_CONSTRAINTS.length} 条 constraints`, { count: CODEFORGE_CONSTRAINTS.length });
16913
+ log7?.info(`[${PLUGIN_NAME10}] seedConstraints: 已写入 ${CODEFORGE_CONSTRAINTS.length} 条 constraints`, { count: CODEFORGE_CONSTRAINTS.length });
16770
16914
  return { ok: true, itemsWritten: CODEFORGE_CONSTRAINTS.length };
16771
16915
  } catch (err) {
16772
16916
  const message = err instanceof Error ? err.message : String(err);
16773
- log7?.warn(`[${PLUGIN_NAME9}] seedConstraints 失败(已静默)`, { error: message });
16917
+ log7?.warn(`[${PLUGIN_NAME10}] seedConstraints 失败(已静默)`, { error: message });
16774
16918
  return { ok: false, reason: "exception", message };
16775
16919
  }
16776
16920
  }
@@ -16785,7 +16929,7 @@ function createSystemInjectedHook(client, sessionId, log7) {
16785
16929
  });
16786
16930
  if (result && typeof result === "object" && "ok" in result && result.ok === false) {
16787
16931
  const r = result;
16788
- log7?.warn(`[${PLUGIN_NAME9}] system-injected 降级到 observe-only:${r.reason ?? "unknown"}`, {
16932
+ log7?.warn(`[${PLUGIN_NAME10}] system-injected 降级到 observe-only:${r.reason ?? "unknown"}`, {
16789
16933
  sessionId,
16790
16934
  section,
16791
16935
  preview: markdown.slice(0, 200),
@@ -16794,12 +16938,12 @@ function createSystemInjectedHook(client, sessionId, log7) {
16794
16938
  });
16795
16939
  return;
16796
16940
  }
16797
- log7?.info(`[${PLUGIN_NAME9}] system-injected: 写入 KH working_memory 成功 (${markdown.length} chars)`, {
16941
+ log7?.info(`[${PLUGIN_NAME10}] system-injected: 写入 KH working_memory 成功 (${markdown.length} chars)`, {
16798
16942
  sessionId,
16799
16943
  section
16800
16944
  });
16801
16945
  } catch (err) {
16802
- log7?.warn(`[${PLUGIN_NAME9}] system-injected 抛异常,降级到 observe-only`, {
16946
+ log7?.warn(`[${PLUGIN_NAME10}] system-injected 抛异常,降级到 observe-only`, {
16803
16947
  sessionId,
16804
16948
  section,
16805
16949
  preview: markdown.slice(0, 200),
@@ -16842,7 +16986,7 @@ async function runKhSearchAndInject(args) {
16842
16986
  });
16843
16987
  result = await racer(searchPromise, cfg.timeoutMs);
16844
16988
  } catch (err) {
16845
- log7?.warn(`[${PLUGIN_NAME9}] client.search threw (sync or async), return null`, {
16989
+ log7?.warn(`[${PLUGIN_NAME10}] client.search threw (sync or async), return null`, {
16846
16990
  query,
16847
16991
  elapsedMs: Date.now() - startedAt,
16848
16992
  sessionId: ctx.sessionId,
@@ -16852,7 +16996,7 @@ async function runKhSearchAndInject(args) {
16852
16996
  return null;
16853
16997
  }
16854
16998
  if (result === "__timeout__") {
16855
- log7?.warn(`[${PLUGIN_NAME9}] timeout`, {
16999
+ log7?.warn(`[${PLUGIN_NAME10}] timeout`, {
16856
17000
  query,
16857
17001
  ms: cfg.timeoutMs,
16858
17002
  elapsedMs: Date.now() - startedAt,
@@ -16862,7 +17006,7 @@ async function runKhSearchAndInject(args) {
16862
17006
  return null;
16863
17007
  }
16864
17008
  if (!result.ok) {
16865
- log7?.warn(`[${PLUGIN_NAME9}] kh degraded`, {
17009
+ log7?.warn(`[${PLUGIN_NAME10}] kh degraded`, {
16866
17010
  reason: result.reason,
16867
17011
  query,
16868
17012
  elapsedMs: Date.now() - startedAt,
@@ -16874,7 +17018,7 @@ async function runKhSearchAndInject(args) {
16874
17018
  const filtered = result.insights.filter((i) => (i.confidence ?? 0) >= cfg.minConfidence);
16875
17019
  if (filtered.length === 0) {
16876
17020
  opts.cache.record(query, []);
16877
- log7?.debug?.(`[${PLUGIN_NAME9}] no candidate above threshold`, {
17021
+ log7?.debug?.(`[${PLUGIN_NAME10}] no candidate above threshold`, {
16878
17022
  query,
16879
17023
  rawCount: result.insights.length,
16880
17024
  elapsedMs: Date.now() - startedAt,
@@ -16887,7 +17031,7 @@ async function runKhSearchAndInject(args) {
16887
17031
  try {
16888
17032
  await ctx.injectContext(payload.markdown);
16889
17033
  } catch (err) {
16890
- log7?.warn(`[${PLUGIN_NAME9}] injectContext threw`, {
17034
+ log7?.warn(`[${PLUGIN_NAME10}] injectContext threw`, {
16891
17035
  error: err instanceof Error ? err.message : String(err),
16892
17036
  query,
16893
17037
  sessionId: ctx.sessionId
@@ -16895,7 +17039,7 @@ async function runKhSearchAndInject(args) {
16895
17039
  }
16896
17040
  }
16897
17041
  opts.cache.record(query, filtered);
16898
- log7?.info(`[${PLUGIN_NAME9}] inject complete (${mode})`, {
17042
+ log7?.info(`[${PLUGIN_NAME10}] inject complete (${mode})`, {
16899
17043
  query,
16900
17044
  mode,
16901
17045
  candidateCount: filtered.length,
@@ -16911,18 +17055,18 @@ async function handleMessage2(raw, opts) {
16911
17055
  const log7 = ctx.log;
16912
17056
  const text = (ctx.content ?? "").trim();
16913
17057
  if (!shouldInject(text, cfg)) {
16914
- log7?.debug?.(`[${PLUGIN_NAME9}] skip (filter)`, { textLen: text.length });
17058
+ log7?.debug?.(`[${PLUGIN_NAME10}] skip (filter)`, { textLen: text.length });
16915
17059
  return;
16916
17060
  }
16917
17061
  const query = extractQuery(text);
16918
17062
  if (!query)
16919
17063
  return;
16920
17064
  if (opts.cache.shouldSkip(query)) {
16921
- log7?.debug?.(`[${PLUGIN_NAME9}] cache hit, skip`, { query });
17065
+ log7?.debug?.(`[${PLUGIN_NAME10}] cache hit, skip`, { query });
16922
17066
  return;
16923
17067
  }
16924
17068
  if (inflight2.size >= INFLIGHT_CAP) {
16925
- log7?.warn(`[${PLUGIN_NAME9}] inflight cap reached, skip`, {
17069
+ log7?.warn(`[${PLUGIN_NAME10}] inflight cap reached, skip`, {
16926
17070
  query,
16927
17071
  inflightSize: inflight2.size,
16928
17072
  cap: INFLIGHT_CAP,
@@ -16933,7 +17077,7 @@ async function handleMessage2(raw, opts) {
16933
17077
  const key = inflightKey(ctx.sessionId, query);
16934
17078
  inflight2.add(key);
16935
17079
  runKhSearchAndInject({ query, ctx, opts, mode }).catch((err) => {
16936
- log7?.warn(`[${PLUGIN_NAME9}] runKhSearchAndInject 顶层兜底捕获`, {
17080
+ log7?.warn(`[${PLUGIN_NAME10}] runKhSearchAndInject 顶层兜底捕获`, {
16937
17081
  error: err instanceof Error ? err.message : String(err),
16938
17082
  query,
16939
17083
  sessionId: ctx.sessionId
@@ -16942,13 +17086,13 @@ async function handleMessage2(raw, opts) {
16942
17086
  inflight2.delete(key);
16943
17087
  });
16944
17088
  }
16945
- logLifecycle(PLUGIN_NAME9, "import");
17089
+ logLifecycle(PLUGIN_NAME10, "import");
16946
17090
  var sharedClient2 = new KhClient;
16947
17091
  var sharedCache = new QueryCache(DEFAULT_CONFIG4.cacheTtlMs);
16948
17092
  var khAutoContextServer = async (ctx) => {
16949
- const log7 = makePluginLogger(PLUGIN_NAME9);
17093
+ const log7 = makePluginLogger(PLUGIN_NAME10);
16950
17094
  const runtimeMode = resolveInjectionMode(sharedClient2);
16951
- logLifecycle(PLUGIN_NAME9, "activate", {
17095
+ logLifecycle(PLUGIN_NAME10, "activate", {
16952
17096
  directory: ctx.directory,
16953
17097
  minConfidence: DEFAULT_CONFIG4.minConfidence,
16954
17098
  timeoutMs: DEFAULT_CONFIG4.timeoutMs,
@@ -16962,7 +17106,7 @@ var khAutoContextServer = async (ctx) => {
16962
17106
  });
16963
17107
  return {
16964
17108
  "chat.message": async (input, output) => {
16965
- await safeAsync(PLUGIN_NAME9, "chat.message", async () => {
17109
+ await safeAsync(PLUGIN_NAME10, "chat.message", async () => {
16966
17110
  const text = extractUserText(output);
16967
17111
  if (!text)
16968
17112
  return;
@@ -16982,7 +17126,7 @@ var khAutoContextServer = async (ctx) => {
16982
17126
  });
16983
17127
  },
16984
17128
  event: async ({ event }) => {
16985
- await safeAsync(PLUGIN_NAME9, "event", async () => {
17129
+ await safeAsync(PLUGIN_NAME10, "event", async () => {
16986
17130
  const e = event;
16987
17131
  if (e.type !== "session.idle")
16988
17132
  return;
@@ -16991,14 +17135,14 @@ var khAutoContextServer = async (ctx) => {
16991
17135
  if (typeof sid !== "string" || !sid)
16992
17136
  return;
16993
17137
  sharedKhCache.onSessionEnd(sid);
16994
- log7.debug?.(`[${PLUGIN_NAME9}] session.idle: cleared shared cache`, {
17138
+ log7.debug?.(`[${PLUGIN_NAME10}] session.idle: cleared shared cache`, {
16995
17139
  sessionID: sid
16996
17140
  });
16997
17141
  });
16998
17142
  }
16999
17143
  };
17000
17144
  };
17001
- var handler9 = khAutoContextServer;
17145
+ var handler10 = khAutoContextServer;
17002
17146
 
17003
17147
  // lib/condenser.ts
17004
17148
  var DEFAULT_CONDENSE = {
@@ -17116,8 +17260,8 @@ async function condense(input, opts = {}) {
17116
17260
  }
17117
17261
 
17118
17262
  // plugins/kh-reminder.ts
17119
- var PLUGIN_NAME10 = "kh-reminder";
17120
- logLifecycle(PLUGIN_NAME10, "import", {});
17263
+ var PLUGIN_NAME11 = "kh-reminder";
17264
+ logLifecycle(PLUGIN_NAME11, "import", {});
17121
17265
  var TRIGGER_WORDS_ZH = [
17122
17266
  "怎么",
17123
17267
  "怎样",
@@ -17271,7 +17415,7 @@ function handleObserve(raw, log7) {
17271
17415
  const result = evaluate(ctx);
17272
17416
  if (result.triggered && result.reason) {
17273
17417
  const reasonStr = formatReason(result.reason);
17274
- log7?.info(`[${PLUGIN_NAME10}] ⚡ KH 提醒(observe-only) · session=${result.sessionId} · ${reasonStr}`, {
17418
+ log7?.info(`[${PLUGIN_NAME11}] ⚡ KH 提醒(observe-only) · session=${result.sessionId} · ${reasonStr}`, {
17275
17419
  sessionId: result.sessionId,
17276
17420
  reason: result.reason,
17277
17421
  suggestion: "调用 smart_search 查项目历史;完成后用 save_chat_insight 沉淀"
@@ -17279,7 +17423,7 @@ function handleObserve(raw, log7) {
17279
17423
  }
17280
17424
  return result;
17281
17425
  } catch (err) {
17282
- log7?.warn(`[${PLUGIN_NAME10}] evaluate 异常(已隔离)`, {
17426
+ log7?.warn(`[${PLUGIN_NAME11}] evaluate 异常(已隔离)`, {
17283
17427
  error: err instanceof Error ? err.message : String(err)
17284
17428
  });
17285
17429
  return null;
@@ -17295,9 +17439,9 @@ function formatReason(r) {
17295
17439
  return `no-search-in-recent-rounds (last ${r.rounds})`;
17296
17440
  }
17297
17441
  }
17298
- var log7 = makePluginLogger(PLUGIN_NAME10);
17442
+ var log7 = makePluginLogger(PLUGIN_NAME11);
17299
17443
  var khReminderServer = async (ctx) => {
17300
- logLifecycle(PLUGIN_NAME10, "activate", {
17444
+ logLifecycle(PLUGIN_NAME11, "activate", {
17301
17445
  directory: ctx.directory,
17302
17446
  threshold: DEFAULT_THRESHOLD,
17303
17447
  cooldown_ms: COOLDOWN_MS,
@@ -17305,7 +17449,7 @@ var khReminderServer = async (ctx) => {
17305
17449
  });
17306
17450
  return {
17307
17451
  "experimental.chat.messages.transform": async (_input, output) => {
17308
- await safeAsync(PLUGIN_NAME10, "experimental.chat.messages.transform", async () => {
17452
+ await safeAsync(PLUGIN_NAME11, "experimental.chat.messages.transform", async () => {
17309
17453
  const list = output.messages;
17310
17454
  if (!Array.isArray(list) || list.length === 0)
17311
17455
  return;
@@ -17324,7 +17468,7 @@ var khReminderServer = async (ctx) => {
17324
17468
  const result = handleObserve({ messages: flat, sessionId }, log7);
17325
17469
  if (!result)
17326
17470
  return;
17327
- safeWriteLog(PLUGIN_NAME10, {
17471
+ safeWriteLog(PLUGIN_NAME11, {
17328
17472
  hook: "experimental.chat.messages.transform",
17329
17473
  mode: "observe-only",
17330
17474
  sessionId: result.sessionId,
@@ -17337,7 +17481,7 @@ var khReminderServer = async (ctx) => {
17337
17481
  }
17338
17482
  };
17339
17483
  };
17340
- var handler10 = khReminderServer;
17484
+ var handler11 = khReminderServer;
17341
17485
 
17342
17486
  // lib/memories.ts
17343
17487
  import { promises as fs15 } from "node:fs";
@@ -17538,7 +17682,7 @@ function bagOfWordsScore(query, doc) {
17538
17682
  }
17539
17683
 
17540
17684
  // plugins/memories-context.ts
17541
- var PLUGIN_NAME11 = "memories-context";
17685
+ var PLUGIN_NAME12 = "memories-context";
17542
17686
  var INJECTION_MODE2 = "observe-only";
17543
17687
  var DEFAULT_CONFIG5 = {
17544
17688
  minTextLength: 8,
@@ -17636,14 +17780,14 @@ async function handleMessage3(raw, opts) {
17636
17780
  return await handleDirective(dir, ctx, opts.memCfg, log8);
17637
17781
  }
17638
17782
  if (!shouldRecall(text, cfg)) {
17639
- log8?.debug?.(`[${PLUGIN_NAME11}] skip (filter)`, { textLen: text.length });
17783
+ log8?.debug?.(`[${PLUGIN_NAME12}] skip (filter)`, { textLen: text.length });
17640
17784
  return { kind: "noop", reason: "filtered", mode: INJECTION_MODE2 };
17641
17785
  }
17642
17786
  const query = extractQuery2(text);
17643
17787
  if (!query)
17644
17788
  return { kind: "noop", reason: "empty_query", mode: INJECTION_MODE2 };
17645
17789
  if (cache2.shouldSkip(query)) {
17646
- log8?.debug?.(`[${PLUGIN_NAME11}] cache hit`, { query });
17790
+ log8?.debug?.(`[${PLUGIN_NAME12}] cache hit`, { query });
17647
17791
  return { kind: "noop", reason: "cache", mode: INJECTION_MODE2 };
17648
17792
  }
17649
17793
  const racer = opts.scheduler?.raceTimeout ?? (async (p, ms) => {
@@ -17668,7 +17812,7 @@ async function handleMessage3(raw, opts) {
17668
17812
  }, opts.memCfg);
17669
17813
  const result = await racer(injectPromise, cfg.timeoutMs);
17670
17814
  if (result === "__timeout__") {
17671
- log8?.warn(`[${PLUGIN_NAME11}] timeout`, { query, ms: cfg.timeoutMs });
17815
+ log8?.warn(`[${PLUGIN_NAME12}] timeout`, { query, ms: cfg.timeoutMs });
17672
17816
  cache2.record(query, 0);
17673
17817
  return { kind: "noop", reason: "timeout", mode: INJECTION_MODE2 };
17674
17818
  }
@@ -17680,13 +17824,13 @@ async function handleMessage3(raw, opts) {
17680
17824
  try {
17681
17825
  await ctx.injectContext(result.text);
17682
17826
  } catch (err) {
17683
- log8?.warn(`[${PLUGIN_NAME11}] injectContext threw`, {
17827
+ log8?.warn(`[${PLUGIN_NAME12}] injectContext threw`, {
17684
17828
  error: err instanceof Error ? err.message : String(err)
17685
17829
  });
17686
17830
  }
17687
17831
  }
17688
17832
  cache2.record(query, result.recalled);
17689
- log8?.info(`[${PLUGIN_NAME11}] observe-only: ${result.used}/${result.recalled} memories ready`, { query, mode: INJECTION_MODE2 });
17833
+ log8?.info(`[${PLUGIN_NAME12}] observe-only: ${result.used}/${result.recalled} memories ready`, { query, mode: INJECTION_MODE2 });
17690
17834
  return { kind: "injected", payload: result, mode: INJECTION_MODE2 };
17691
17835
  }
17692
17836
  async function handleDirective(dir, ctx, memCfg, log8) {
@@ -17695,14 +17839,14 @@ async function handleDirective(dir, ctx, memCfg, log8) {
17695
17839
  if (r.ok) {
17696
17840
  const target = r.written_to === "kh" ? "KH" : "本地";
17697
17841
  await safeReply(ctx, `\uD83E\uDDE0 已记住(${dir.scope} / ${target},id=${r.id})`);
17698
- log8?.info(`[${PLUGIN_NAME11}] /remember ok`, {
17842
+ log8?.info(`[${PLUGIN_NAME12}] /remember ok`, {
17699
17843
  scope: dir.scope,
17700
17844
  id: r.id,
17701
17845
  mode: INJECTION_MODE2
17702
17846
  });
17703
17847
  } else {
17704
17848
  await safeReply(ctx, `❌ 记忆失败:${r.error ?? "unknown"}`);
17705
- log8?.warn(`[${PLUGIN_NAME11}] /remember failed`, { error: r.error });
17849
+ log8?.warn(`[${PLUGIN_NAME12}] /remember failed`, { error: r.error });
17706
17850
  }
17707
17851
  return { kind: "remembered", result: r, mode: INJECTION_MODE2 };
17708
17852
  }
@@ -17733,22 +17877,22 @@ async function safeReply(ctx, text) {
17733
17877
  await ctx.reply(text);
17734
17878
  } catch {}
17735
17879
  }
17736
- logLifecycle(PLUGIN_NAME11, "import");
17880
+ logLifecycle(PLUGIN_NAME12, "import");
17737
17881
  var sharedCache2 = new QueryCache2(DEFAULT_CONFIG5.cacheTtlMs);
17738
17882
  function buildMemCfg(directory) {
17739
17883
  return { projectRoot: directory };
17740
17884
  }
17741
17885
  var memoriesContextServer = async (ctx) => {
17742
- const log8 = makePluginLogger(PLUGIN_NAME11);
17886
+ const log8 = makePluginLogger(PLUGIN_NAME12);
17743
17887
  const memCfg = buildMemCfg(ctx.directory);
17744
- logLifecycle(PLUGIN_NAME11, "activate", {
17888
+ logLifecycle(PLUGIN_NAME12, "activate", {
17745
17889
  directory: ctx.directory,
17746
17890
  projectRoot: memCfg.projectRoot,
17747
17891
  mode: INJECTION_MODE2
17748
17892
  });
17749
17893
  return {
17750
17894
  "chat.message": async (input, output) => {
17751
- await safeAsync(PLUGIN_NAME11, "chat.message", async () => {
17895
+ await safeAsync(PLUGIN_NAME12, "chat.message", async () => {
17752
17896
  const text = extractUserText(output);
17753
17897
  if (!text)
17754
17898
  return;
@@ -17774,17 +17918,17 @@ var memoriesContextServer = async (ctx) => {
17774
17918
  }
17775
17919
  };
17776
17920
  };
17777
- var handler11 = memoriesContextServer;
17921
+ var handler12 = memoriesContextServer;
17778
17922
 
17779
17923
  // plugins/model-fallback.ts
17780
- var PLUGIN_NAME12 = "model-fallback";
17924
+ var PLUGIN_NAME13 = "model-fallback";
17781
17925
  var state = {
17782
17926
  config: null,
17783
17927
  configPath: null,
17784
17928
  warnings: [],
17785
17929
  error: null
17786
17930
  };
17787
- logLifecycle(PLUGIN_NAME12, "import");
17931
+ logLifecycle(PLUGIN_NAME13, "import");
17788
17932
  function loadOnce(root) {
17789
17933
  if (state.config !== null || state.error !== null)
17790
17934
  return;
@@ -17794,11 +17938,11 @@ function loadOnce(root) {
17794
17938
  state.configPath = r.path ?? null;
17795
17939
  state.warnings = r.warnings;
17796
17940
  if (r.warnings.length > 0) {
17797
- safeWriteLog(PLUGIN_NAME12, { phase: "load.warnings", warnings: r.warnings });
17941
+ safeWriteLog(PLUGIN_NAME13, { phase: "load.warnings", warnings: r.warnings });
17798
17942
  }
17799
17943
  } else {
17800
17944
  state.error = r.error ?? "unknown_load_error";
17801
- safeWriteLog(PLUGIN_NAME12, { phase: "load.failed", error: state.error, path: r.path });
17945
+ safeWriteLog(PLUGIN_NAME13, { phase: "load.failed", error: state.error, path: r.path });
17802
17946
  }
17803
17947
  }
17804
17948
  var MODEL_ERR_PATTERNS = [
@@ -17843,9 +17987,9 @@ fallback 链已用尽:${meta.chain.join(" → ")}`;
17843
17987
  完整链:${meta.chain.join(" → ")}`;
17844
17988
  }
17845
17989
  var modelFallbackServer = async (ctx) => {
17846
- const log8 = makePluginLogger(PLUGIN_NAME12);
17990
+ const log8 = makePluginLogger(PLUGIN_NAME13);
17847
17991
  loadOnce(ctx.directory ?? process.cwd());
17848
- logLifecycle(PLUGIN_NAME12, "activate", {
17992
+ logLifecycle(PLUGIN_NAME13, "activate", {
17849
17993
  directory: ctx.directory,
17850
17994
  config_path: state.configPath,
17851
17995
  config_loaded: state.config !== null,
@@ -17855,7 +17999,7 @@ var modelFallbackServer = async (ctx) => {
17855
17999
  });
17856
18000
  return {
17857
18001
  "chat.params": async (input, output) => {
17858
- await safeAsync(PLUGIN_NAME12, "chat.params", async () => {
18002
+ await safeAsync(PLUGIN_NAME13, "chat.params", async () => {
17859
18003
  if (!state.config)
17860
18004
  return;
17861
18005
  const agent = input.agent;
@@ -17876,7 +18020,7 @@ var modelFallbackServer = async (ctx) => {
17876
18020
  next: meta.next_fallback,
17877
18021
  source: meta.source
17878
18022
  };
17879
- safeWriteLog(PLUGIN_NAME12, {
18023
+ safeWriteLog(PLUGIN_NAME13, {
17880
18024
  hook: "chat.params",
17881
18025
  agent,
17882
18026
  model: currentModel,
@@ -17886,7 +18030,7 @@ var modelFallbackServer = async (ctx) => {
17886
18030
  });
17887
18031
  },
17888
18032
  event: async ({ event }) => {
17889
- await safeAsync(PLUGIN_NAME12, "event", async () => {
18033
+ await safeAsync(PLUGIN_NAME13, "event", async () => {
17890
18034
  if (!state.config)
17891
18035
  return;
17892
18036
  const e = event;
@@ -17902,8 +18046,8 @@ var modelFallbackServer = async (ctx) => {
17902
18046
  const model = props.model ?? "unknown/unknown";
17903
18047
  const meta = buildFallbackMeta(state.config, agent, model);
17904
18048
  const suggestion = meta ? buildSuggestion(meta, String(message)) : `⚠️ ${agent}/${model} 失败:${message}`;
17905
- log8.warn(`[${PLUGIN_NAME12}] ${suggestion}`);
17906
- safeWriteLog(PLUGIN_NAME12, {
18049
+ log8.warn(`[${PLUGIN_NAME13}] ${suggestion}`);
18050
+ safeWriteLog(PLUGIN_NAME13, {
17907
18051
  hook: "event.error",
17908
18052
  eventType: e.type,
17909
18053
  agent,
@@ -17915,7 +18059,7 @@ var modelFallbackServer = async (ctx) => {
17915
18059
  }
17916
18060
  };
17917
18061
  };
17918
- var handler12 = modelFallbackServer;
18062
+ var handler13 = modelFallbackServer;
17919
18063
 
17920
18064
  // plugins/subtask-heartbeat.ts
17921
18065
  import { promises as fsPromises } from "node:fs";
@@ -17929,8 +18073,8 @@ var sweepExpiredSessionParents2 = sweepExpiredSessionParents;
17929
18073
  var _bulkInjectSessionParentMap2 = _bulkInjectSessionParentMap;
17930
18074
  var _capSessionParentMap2 = _capSessionParentMap;
17931
18075
  var _setPersistRootForTests2 = _setPersistRootForTests;
17932
- var PLUGIN_NAME13 = "subtask-heartbeat";
17933
- logLifecycle(PLUGIN_NAME13, "import", {});
18076
+ var PLUGIN_NAME14 = "subtask-heartbeat";
18077
+ logLifecycle(PLUGIN_NAME14, "import", {});
17934
18078
  var HEARTBEAT_INTERVAL_MS2 = 30000;
17935
18079
  var HEARTBEAT_DEBOUNCE_MS = 25000;
17936
18080
  var TOAST_DURATION_MS2 = 5000;
@@ -18272,9 +18416,9 @@ async function showToast2(client, payload, log8) {
18272
18416
  return false;
18273
18417
  }
18274
18418
  }
18275
- var log8 = makePluginLogger(PLUGIN_NAME13);
18419
+ var log8 = makePluginLogger(PLUGIN_NAME14);
18276
18420
  var subtaskHeartbeatServer = async (ctx) => {
18277
- logLifecycle(PLUGIN_NAME13, "activate", {
18421
+ logLifecycle(PLUGIN_NAME14, "activate", {
18278
18422
  directory: ctx.directory,
18279
18423
  intervalMs: HEARTBEAT_INTERVAL_MS2
18280
18424
  });
@@ -18291,7 +18435,7 @@ var subtaskHeartbeatServer = async (ctx) => {
18291
18435
  }));
18292
18436
  _bulkInjectSessionParentMap2(entries);
18293
18437
  const cappedOut = _capSessionParentMap2();
18294
- safeWriteLog(PLUGIN_NAME13, {
18438
+ safeWriteLog(PLUGIN_NAME14, {
18295
18439
  hook: "activate",
18296
18440
  type: "parent-map.restore",
18297
18441
  restored: restored.size,
@@ -18304,14 +18448,14 @@ var subtaskHeartbeatServer = async (ctx) => {
18304
18448
  });
18305
18449
  }
18306
18450
  const interval = setInterval(() => {
18307
- safeAsync(PLUGIN_NAME13, "interval", async () => {
18451
+ safeAsync(PLUGIN_NAME14, "interval", async () => {
18308
18452
  const swept = sweepExpiredPendingTasks();
18309
18453
  if (swept > 0) {
18310
- safeWriteLog(PLUGIN_NAME13, { hook: "interval", pending_task_swept: swept });
18454
+ safeWriteLog(PLUGIN_NAME14, { hook: "interval", pending_task_swept: swept });
18311
18455
  }
18312
18456
  const sweptParents = sweepExpiredSessionParents2();
18313
18457
  if (sweptParents > 0) {
18314
- safeWriteLog(PLUGIN_NAME13, { hook: "interval", session_parent_swept: sweptParents });
18458
+ safeWriteLog(PLUGIN_NAME14, { hook: "interval", session_parent_swept: sweptParents });
18315
18459
  }
18316
18460
  const beats = pickHeartbeats();
18317
18461
  if (beats.length === 0)
@@ -18319,7 +18463,7 @@ var subtaskHeartbeatServer = async (ctx) => {
18319
18463
  for (const r of beats) {
18320
18464
  const t = buildHeartbeatToast(r);
18321
18465
  const sent = await showToast2(client, t, log8);
18322
- safeWriteLog(PLUGIN_NAME13, {
18466
+ safeWriteLog(PLUGIN_NAME14, {
18323
18467
  hook: "interval",
18324
18468
  child: r.childID,
18325
18469
  parent: r.parentID,
@@ -18336,7 +18480,7 @@ var subtaskHeartbeatServer = async (ctx) => {
18336
18480
  }
18337
18481
  return {
18338
18482
  event: async ({ event }) => {
18339
- await safeAsync(PLUGIN_NAME13, "event", async () => {
18483
+ await safeAsync(PLUGIN_NAME14, "event", async () => {
18340
18484
  try {
18341
18485
  if (detectUnparsedParentID(event) && _parentParseFailLogged < PARENT_PARSE_FAIL_MAX_LOG) {
18342
18486
  _parentParseFailLogged++;
@@ -18344,7 +18488,7 @@ var subtaskHeartbeatServer = async (ctx) => {
18344
18488
  sample_count: _parentParseFailLogged,
18345
18489
  hint: "如频繁出现请检查 plugins/subtask-heartbeat.ts::extractCreatedChild 是否适配新 SDK"
18346
18490
  });
18347
- safeWriteLog(PLUGIN_NAME13, {
18491
+ safeWriteLog(PLUGIN_NAME14, {
18348
18492
  hook: "event",
18349
18493
  type: "session.created.unparsed-parent-id",
18350
18494
  sample_count: _parentParseFailLogged
@@ -18367,7 +18511,7 @@ var subtaskHeartbeatServer = async (ctx) => {
18367
18511
  agent: pending?.agent ?? created.agent,
18368
18512
  description: pending?.description ?? null
18369
18513
  });
18370
- safeWriteLog(PLUGIN_NAME13, {
18514
+ safeWriteLog(PLUGIN_NAME14, {
18371
18515
  hook: "event",
18372
18516
  type: "session.created",
18373
18517
  child: created.childID,
@@ -18378,7 +18522,7 @@ var subtaskHeartbeatServer = async (ctx) => {
18378
18522
  });
18379
18523
  const startToast = buildStartToast(record);
18380
18524
  const sent = await showToast2(client, { ...startToast, duration: START_TOAST_DURATION_MS }, log8);
18381
- safeWriteLog(PLUGIN_NAME13, {
18525
+ safeWriteLog(PLUGIN_NAME14, {
18382
18526
  hook: "event",
18383
18527
  type: "session.created.toast",
18384
18528
  child: created.childID,
@@ -18398,7 +18542,7 @@ var subtaskHeartbeatServer = async (ctx) => {
18398
18542
  if (r) {
18399
18543
  const t = buildEndToast(r, ended.type);
18400
18544
  const sent = await showToast2(client, t, log8);
18401
- safeWriteLog(PLUGIN_NAME13, {
18545
+ safeWriteLog(PLUGIN_NAME14, {
18402
18546
  hook: "event",
18403
18547
  type: ended.type,
18404
18548
  child: r.childID,
@@ -18418,12 +18562,12 @@ var subtaskHeartbeatServer = async (ctx) => {
18418
18562
  const isTaskTool = input?.tool === "task";
18419
18563
  if (inflight3.size === 0 && !isTaskTool)
18420
18564
  return;
18421
- await safeAsync(PLUGIN_NAME13, "tool.execute.before", async () => {
18565
+ await safeAsync(PLUGIN_NAME14, "tool.execute.before", async () => {
18422
18566
  if (!input || typeof input.sessionID !== "string" || typeof input.tool !== "string")
18423
18567
  return;
18424
18568
  if (isTaskTool) {
18425
18569
  const args = output?.args ?? null;
18426
- safeWriteLog(PLUGIN_NAME13, {
18570
+ safeWriteLog(PLUGIN_NAME14, {
18427
18571
  hook: "tool.execute.before.task",
18428
18572
  sessionID: input.sessionID,
18429
18573
  args_keys: args && typeof args === "object" ? Object.keys(args) : null,
@@ -18435,7 +18579,7 @@ var subtaskHeartbeatServer = async (ctx) => {
18435
18579
  agent: extracted.subagentType,
18436
18580
  description: extracted.description
18437
18581
  });
18438
- safeWriteLog(PLUGIN_NAME13, {
18582
+ safeWriteLog(PLUGIN_NAME14, {
18439
18583
  hook: "tool.execute.before.task.enqueued",
18440
18584
  parent: input.sessionID,
18441
18585
  agent: extracted.subagentType,
@@ -18463,7 +18607,7 @@ var subtaskHeartbeatServer = async (ctx) => {
18463
18607
  "tool.execute.after": async (input, _output) => {
18464
18608
  if (inflight3.size === 0)
18465
18609
  return;
18466
- await safeAsync(PLUGIN_NAME13, "tool.execute.after", async () => {
18610
+ await safeAsync(PLUGIN_NAME14, "tool.execute.after", async () => {
18467
18611
  if (!input || typeof input.sessionID !== "string" || typeof input.tool !== "string")
18468
18612
  return;
18469
18613
  const rec = inflight3.get(input.sessionID);
@@ -18486,11 +18630,11 @@ var subtaskHeartbeatServer = async (ctx) => {
18486
18630
  }
18487
18631
  };
18488
18632
  };
18489
- var handler13 = subtaskHeartbeatServer;
18633
+ var handler14 = subtaskHeartbeatServer;
18490
18634
 
18491
18635
  // plugins/parallel-status.ts
18492
- var PLUGIN_NAME14 = "parallel-status";
18493
- logLifecycle(PLUGIN_NAME14, "import");
18636
+ var PLUGIN_NAME15 = "parallel-status";
18637
+ logLifecycle(PLUGIN_NAME15, "import");
18494
18638
  var ID_MAX_LEN = 16;
18495
18639
  var ID_KEEP_LEN = 13;
18496
18640
  function shortId(s) {
@@ -18522,8 +18666,8 @@ function formatInflightMarkdown(snapshot, now = Date.now()) {
18522
18666
  `);
18523
18667
  }
18524
18668
  var parallelStatusServer = async (ctx) => {
18525
- const log9 = makePluginLogger(PLUGIN_NAME14);
18526
- logLifecycle(PLUGIN_NAME14, "activate", { directory: ctx.directory });
18669
+ const log9 = makePluginLogger(PLUGIN_NAME15);
18670
+ logLifecycle(PLUGIN_NAME15, "activate", { directory: ctx.directory });
18527
18671
  return {
18528
18672
  "command.execute.before": async (input, output) => {
18529
18673
  try {
@@ -18542,23 +18686,23 @@ var parallelStatusServer = async (ctx) => {
18542
18686
  synthetic: false
18543
18687
  });
18544
18688
  }
18545
- log9.info(`[${PLUGIN_NAME14}] 已回写 ${snapshot.length} 条 inflight`);
18689
+ log9.info(`[${PLUGIN_NAME15}] 已回写 ${snapshot.length} 条 inflight`);
18546
18690
  } catch (err) {
18547
- log9.error(`[${PLUGIN_NAME14}] command.execute.before 异常(已隔离)`, {
18691
+ log9.error(`[${PLUGIN_NAME15}] command.execute.before 异常(已隔离)`, {
18548
18692
  error: err instanceof Error ? err.message : String(err)
18549
18693
  });
18550
18694
  }
18551
18695
  }
18552
18696
  };
18553
18697
  };
18554
- var handler14 = parallelStatusServer;
18698
+ var handler15 = parallelStatusServer;
18555
18699
 
18556
18700
  // plugins/parallel-tool-nudge.ts
18557
18701
  import { readFileSync as readFileSync4, readdirSync as readdirSync2, statSync as statSync4 } from "node:fs";
18558
18702
  import { join as join16 } from "node:path";
18559
18703
  import { homedir as homedir6 } from "node:os";
18560
- var PLUGIN_NAME15 = "parallel-tool-nudge";
18561
- logLifecycle(PLUGIN_NAME15, "import", {});
18704
+ var PLUGIN_NAME16 = "parallel-tool-nudge";
18705
+ logLifecycle(PLUGIN_NAME16, "import", {});
18562
18706
  var PARALLEL_SAFE_TOOLS = [
18563
18707
  "read",
18564
18708
  "smart_search",
@@ -18620,7 +18764,7 @@ function loadAgentToolsMap(rootDir, opts = {}) {
18620
18764
  const result = new Map;
18621
18765
  const safeSet = new Set(PARALLEL_SAFE_TOOLS);
18622
18766
  const unionTools = new Set;
18623
- const log9 = makePluginLogger(PLUGIN_NAME15);
18767
+ const log9 = makePluginLogger(PLUGIN_NAME16);
18624
18768
  for (const dir of candidateDirs) {
18625
18769
  if (!dirExists(dir))
18626
18770
  continue;
@@ -18670,33 +18814,33 @@ function loadAgentToolsMap(rootDir, opts = {}) {
18670
18814
  result.set(DEFAULT_AGENT_KEY, Array.from(unionTools));
18671
18815
  return result;
18672
18816
  }
18673
- var sessionAgentMap = new Map;
18674
- var SESSION_CAP2 = 200;
18675
- var SESSION_TTL_MS2 = 24 * 60 * 60 * 1000;
18676
- function pruneIfOversize2() {
18677
- while (sessionAgentMap.size > SESSION_CAP2) {
18678
- const oldestKey = sessionAgentMap.keys().next().value;
18817
+ var sessionAgentMap2 = new Map;
18818
+ var SESSION_CAP3 = 200;
18819
+ var SESSION_TTL_MS3 = 24 * 60 * 60 * 1000;
18820
+ function pruneIfOversize3() {
18821
+ while (sessionAgentMap2.size > SESSION_CAP3) {
18822
+ const oldestKey = sessionAgentMap2.keys().next().value;
18679
18823
  if (oldestKey === undefined)
18680
18824
  break;
18681
- sessionAgentMap.delete(oldestKey);
18825
+ sessionAgentMap2.delete(oldestKey);
18682
18826
  }
18683
18827
  }
18684
- function isExpired2(entry, now) {
18685
- return now - entry.ts > SESSION_TTL_MS2;
18828
+ function isExpired3(entry, now) {
18829
+ return now - entry.ts > SESSION_TTL_MS3;
18686
18830
  }
18687
18831
  function resolveAgent(sessionID, nowFn = Date.now) {
18688
18832
  if (!sessionID)
18689
18833
  return "unknown";
18690
- const entry = sessionAgentMap.get(sessionID);
18834
+ const entry = sessionAgentMap2.get(sessionID);
18691
18835
  if (!entry)
18692
18836
  return "unknown";
18693
18837
  const now = nowFn();
18694
- if (isExpired2(entry, now)) {
18695
- sessionAgentMap.delete(sessionID);
18838
+ if (isExpired3(entry, now)) {
18839
+ sessionAgentMap2.delete(sessionID);
18696
18840
  return "unknown";
18697
18841
  }
18698
- sessionAgentMap.delete(sessionID);
18699
- sessionAgentMap.set(sessionID, entry);
18842
+ sessionAgentMap2.delete(sessionID);
18843
+ sessionAgentMap2.set(sessionID, entry);
18700
18844
  return entry.agent;
18701
18845
  }
18702
18846
  function renderNudge(agent, tools) {
@@ -18728,7 +18872,7 @@ This directive is re-injected every turn — it is not optional advice.
18728
18872
  return body.slice(0, NUDGE_MAX_LEN2 - 4) + `
18729
18873
  …`;
18730
18874
  }
18731
- var log9 = makePluginLogger(PLUGIN_NAME15);
18875
+ var log9 = makePluginLogger(PLUGIN_NAME16);
18732
18876
  var parallelToolNudgeServer = async (ctx) => {
18733
18877
  try {
18734
18878
  const loaded = loadAgentToolsMap(ctx.directory ?? process.cwd());
@@ -18748,30 +18892,30 @@ var parallelToolNudgeServer = async (ctx) => {
18748
18892
  });
18749
18893
  }
18750
18894
  const defaultTools = agentToolsMap.get(DEFAULT_AGENT_KEY) ?? [];
18751
- logLifecycle(PLUGIN_NAME15, "activate", {
18895
+ logLifecycle(PLUGIN_NAME16, "activate", {
18752
18896
  directory: ctx.directory,
18753
18897
  real_agents_loaded: realAgentCount,
18754
18898
  default_tools_union: defaultTools,
18755
18899
  parallel_safe_whitelist: PARALLEL_SAFE_TOOLS,
18756
- session_cap: SESSION_CAP2,
18757
- session_ttl_ms: SESSION_TTL_MS2,
18900
+ session_cap: SESSION_CAP3,
18901
+ session_ttl_ms: SESSION_TTL_MS3,
18758
18902
  no_op: agentToolsMap.size === 0
18759
18903
  });
18760
18904
  return {
18761
18905
  "chat.params": async (input, _output) => {
18762
- await safeAsync(PLUGIN_NAME15, "chat.params", async () => {
18906
+ await safeAsync(PLUGIN_NAME16, "chat.params", async () => {
18763
18907
  if (agentToolsMap.size === 0)
18764
18908
  return;
18765
18909
  const sid = input?.sessionID;
18766
18910
  const agent = input?.agent;
18767
18911
  if (!sid || !agent)
18768
18912
  return;
18769
- sessionAgentMap.set(sid, { agent, ts: Date.now() });
18770
- pruneIfOversize2();
18913
+ sessionAgentMap2.set(sid, { agent, ts: Date.now() });
18914
+ pruneIfOversize3();
18771
18915
  });
18772
18916
  },
18773
18917
  "experimental.chat.system.transform": async (input, output) => {
18774
- await safeAsync(PLUGIN_NAME15, "experimental.chat.system.transform", async () => {
18918
+ await safeAsync(PLUGIN_NAME16, "experimental.chat.system.transform", async () => {
18775
18919
  if (agentToolsMap.size === 0)
18776
18920
  return;
18777
18921
  if (!output || !Array.isArray(output.system))
@@ -18781,7 +18925,7 @@ var parallelToolNudgeServer = async (ctx) => {
18781
18925
  const tools = agent !== "unknown" ? agentToolsMap.get(agent) ?? agentToolsMap.get(DEFAULT_AGENT_KEY) ?? [] : agentToolsMap.get(DEFAULT_AGENT_KEY) ?? [];
18782
18926
  const nudge = renderNudge(agent, tools);
18783
18927
  output.system.push(nudge);
18784
- safeWriteLog(PLUGIN_NAME15, {
18928
+ safeWriteLog(PLUGIN_NAME16, {
18785
18929
  hook: "experimental.chat.system.transform",
18786
18930
  sessionID: sid,
18787
18931
  agent,
@@ -18793,11 +18937,11 @@ var parallelToolNudgeServer = async (ctx) => {
18793
18937
  }
18794
18938
  };
18795
18939
  };
18796
- var handler15 = parallelToolNudgeServer;
18940
+ var handler16 = parallelToolNudgeServer;
18797
18941
 
18798
18942
  // plugins/pwsh-utf8.ts
18799
- var PLUGIN_NAME16 = "pwsh-utf8";
18800
- logLifecycle(PLUGIN_NAME16, "import", {});
18943
+ var PLUGIN_NAME17 = "pwsh-utf8";
18944
+ logLifecycle(PLUGIN_NAME17, "import", {});
18801
18945
  var PRELUDE = "chcp 65001 *> $null; " + "[Console]::OutputEncoding = [System.Text.UTF8Encoding]::new(); " + "$OutputEncoding = [System.Text.UTF8Encoding]::new(); ";
18802
18946
  function prependUtf8Prelude(command) {
18803
18947
  if (typeof command !== "string")
@@ -18810,15 +18954,15 @@ function prependUtf8Prelude(command) {
18810
18954
  return command;
18811
18955
  return PRELUDE + command;
18812
18956
  }
18813
- var handler16 = async (_ctx3) => {
18957
+ var handler17 = async (_ctx3) => {
18814
18958
  const enabled = process.platform === "win32" && process.env.CODEFORGE_DISABLE_PWSH_UTF8 !== "1";
18815
18959
  const reason = enabled ? "win32" : process.platform !== "win32" ? "non-win32" : "disabled-by-env";
18816
- logLifecycle(PLUGIN_NAME16, "activate", { enabled, platform: process.platform, reason });
18960
+ logLifecycle(PLUGIN_NAME17, "activate", { enabled, platform: process.platform, reason });
18817
18961
  if (!enabled)
18818
18962
  return {};
18819
18963
  return {
18820
18964
  "tool.execute.before": async (input, output) => {
18821
- await safeAsync(PLUGIN_NAME16, "tool.execute.before", async () => {
18965
+ await safeAsync(PLUGIN_NAME17, "tool.execute.before", async () => {
18822
18966
  if (input.tool !== "bash")
18823
18967
  return;
18824
18968
  const args = output.args ?? {};
@@ -18827,7 +18971,7 @@ var handler16 = async (_ctx3) => {
18827
18971
  if (next !== undefined && next !== original) {
18828
18972
  args["command"] = next;
18829
18973
  output.args = args;
18830
- safeWriteLog(PLUGIN_NAME16, {
18974
+ safeWriteLog(PLUGIN_NAME17, {
18831
18975
  hook: "tool.execute.before",
18832
18976
  tool: input.tool,
18833
18977
  callID: input.callID,
@@ -19243,8 +19387,8 @@ async function markBlocksConsumed(absRoot, entries) {
19243
19387
  }
19244
19388
 
19245
19389
  // plugins/session-recovery.ts
19246
- var PLUGIN_NAME17 = "session-recovery";
19247
- logLifecycle(PLUGIN_NAME17, "import", {});
19390
+ var PLUGIN_NAME18 = "session-recovery";
19391
+ logLifecycle(PLUGIN_NAME18, "import", {});
19248
19392
  async function processSessionStart(currentSessionId, opts = {}) {
19249
19393
  if (opts.disabled) {
19250
19394
  return { ok: true, injected: false, reason: "disabled" };
@@ -19254,7 +19398,7 @@ async function processSessionStart(currentSessionId, opts = {}) {
19254
19398
  excludeIds.add(currentSessionId);
19255
19399
  const r = await scanLastSession({ ...opts, excludeIds: [...excludeIds] });
19256
19400
  if (!r.ok) {
19257
- opts.log?.warn?.(`[${PLUGIN_NAME17}] 扫描失败:${r.error}`);
19401
+ opts.log?.warn?.(`[${PLUGIN_NAME18}] 扫描失败:${r.error}`);
19258
19402
  return { ok: false, injected: false, reason: "scan_error", error: r.error };
19259
19403
  }
19260
19404
  const plan = r.plan;
@@ -19263,7 +19407,7 @@ async function processSessionStart(currentSessionId, opts = {}) {
19263
19407
  try {
19264
19408
  pendingBlocks = await scanBlockPending(opts.root);
19265
19409
  } catch (err) {
19266
- opts.log?.warn?.(`[${PLUGIN_NAME17}] scanBlockPending 异常:${err instanceof Error ? err.message : String(err)}`);
19410
+ opts.log?.warn?.(`[${PLUGIN_NAME18}] scanBlockPending 异常:${err instanceof Error ? err.message : String(err)}`);
19267
19411
  }
19268
19412
  }
19269
19413
  const hasPendingBlocks = pendingBlocks.length > 0;
@@ -19281,7 +19425,7 @@ async function processSessionStart(currentSessionId, opts = {}) {
19281
19425
  await opts.injectRecovery(injection);
19282
19426
  } catch (err) {
19283
19427
  const msg = err instanceof Error ? err.message : String(err);
19284
- opts.log?.warn?.(`[${PLUGIN_NAME17}] injectRecovery 异常:${msg}`);
19428
+ opts.log?.warn?.(`[${PLUGIN_NAME18}] injectRecovery 异常:${msg}`);
19285
19429
  return { ok: false, injected: false, plan, reason: "inject_error", error: msg, pendingBlocks };
19286
19430
  }
19287
19431
  }
@@ -19289,7 +19433,7 @@ async function processSessionStart(currentSessionId, opts = {}) {
19289
19433
  try {
19290
19434
  await markBlocksConsumed(opts.root, pendingBlocks);
19291
19435
  } catch (err) {
19292
- opts.log?.warn?.(`[${PLUGIN_NAME17}] markBlocksConsumed 异常:${err instanceof Error ? err.message : String(err)}`);
19436
+ opts.log?.warn?.(`[${PLUGIN_NAME18}] markBlocksConsumed 异常:${err instanceof Error ? err.message : String(err)}`);
19293
19437
  }
19294
19438
  }
19295
19439
  return { ok: true, injected: true, plan, reason: "ok", pendingBlocks };
@@ -19336,13 +19480,13 @@ function renderPrompt(plan) {
19336
19480
  return lines.join(`
19337
19481
  `);
19338
19482
  }
19339
- var log10 = makePluginLogger(PLUGIN_NAME17);
19483
+ var log10 = makePluginLogger(PLUGIN_NAME18);
19340
19484
  var _lastInjection = null;
19341
19485
  var sessionRecoveryServer = async (ctx) => {
19342
- logLifecycle(PLUGIN_NAME17, "activate", { directory: ctx.directory });
19486
+ logLifecycle(PLUGIN_NAME18, "activate", { directory: ctx.directory });
19343
19487
  return {
19344
19488
  event: async ({ event }) => {
19345
- await safeAsync(PLUGIN_NAME17, "event", async () => {
19489
+ await safeAsync(PLUGIN_NAME18, "event", async () => {
19346
19490
  const e = event;
19347
19491
  if (!e || typeof e.type !== "string")
19348
19492
  return;
@@ -19357,7 +19501,7 @@ var sessionRecoveryServer = async (ctx) => {
19357
19501
  _lastInjection = inj;
19358
19502
  }
19359
19503
  });
19360
- safeWriteLog(PLUGIN_NAME17, {
19504
+ safeWriteLog(PLUGIN_NAME18, {
19361
19505
  hook: "event",
19362
19506
  type: "session.start",
19363
19507
  ok: r.ok,
@@ -19367,13 +19511,13 @@ var sessionRecoveryServer = async (ctx) => {
19367
19511
  pending_blocks_count: r.pendingBlocks?.length ?? 0
19368
19512
  });
19369
19513
  if (r.injected && r.plan) {
19370
- log10.info(`[${PLUGIN_NAME17}] 注入恢复提示(last=${r.plan.last_session_id?.slice(0, 8) ?? "?"}, reason=${r.plan.reason}, pending_blocks=${r.pendingBlocks?.length ?? 0})`);
19514
+ log10.info(`[${PLUGIN_NAME18}] 注入恢复提示(last=${r.plan.last_session_id?.slice(0, 8) ?? "?"}, reason=${r.plan.reason}, pending_blocks=${r.pendingBlocks?.length ?? 0})`);
19371
19515
  }
19372
19516
  });
19373
19517
  }
19374
19518
  };
19375
19519
  };
19376
- var handler17 = sessionRecoveryServer;
19520
+ var handler18 = sessionRecoveryServer;
19377
19521
 
19378
19522
  // plugins/subtasks.ts
19379
19523
  import { promises as fs18 } from "node:fs";
@@ -20200,7 +20344,7 @@ function sleep2(ms) {
20200
20344
 
20201
20345
  // plugins/subtasks.ts
20202
20346
  init_runtime_paths();
20203
- var PLUGIN_NAME18 = "subtasks";
20347
+ var PLUGIN_NAME19 = "subtasks";
20204
20348
  function getLogFile(root = process.cwd()) {
20205
20349
  return path22.join(runtimeDir(root), "logs", "subtasks.log");
20206
20350
  }
@@ -20280,7 +20424,7 @@ async function handleParallelCommand(raw) {
20280
20424
  specs = splitDescriptions(args.description, { parentId });
20281
20425
  }
20282
20426
  if (specs.length === 0) {
20283
- log11?.warn(`[${PLUGIN_NAME18}] /parallel 缺有效子任务`);
20427
+ log11?.warn(`[${PLUGIN_NAME19}] /parallel 缺有效子任务`);
20284
20428
  await safeReply2(ctx, "⚠ /parallel 需要至少 1 个子任务(用 ; 或换行分隔)");
20285
20429
  return { ok: false, reason: "no_subtasks" };
20286
20430
  }
@@ -20315,7 +20459,7 @@ async function handleParallelCommand(raw) {
20315
20459
  }
20316
20460
  const canNotice = Boolean(ctx.client && ctx.parentSessionID);
20317
20461
  if (specs.length === 1 && rawDescription.length >= 30 && ctx.client) {
20318
- log11?.info(`[${PLUGIN_NAME18}] 触发 AI 拆分(描述长度=${rawDescription.length})`);
20462
+ log11?.info(`[${PLUGIN_NAME19}] 触发 AI 拆分(描述长度=${rawDescription.length})`);
20319
20463
  const dec = await decomposeTask(rawDescription, {
20320
20464
  client: ctx.client,
20321
20465
  directory: ctx.directory,
@@ -20336,7 +20480,7 @@ async function handleParallelCommand(raw) {
20336
20480
  timeout_ms: undefined,
20337
20481
  args: d.hintFiles && d.hintFiles.length > 0 ? { hintFiles: d.hintFiles } : undefined
20338
20482
  }));
20339
- log11?.info(`[${PLUGIN_NAME18}] AI 拆分成功 → ${specs.length} 个子任务`);
20483
+ log11?.info(`[${PLUGIN_NAME19}] AI 拆分成功 → ${specs.length} 个子任务`);
20340
20484
  if (canNotice) {
20341
20485
  const lines = [
20342
20486
  `\uD83E\uDD16 AI 已自动拆为 ${specs.length} 个并行子任务:`,
@@ -20348,7 +20492,7 @@ async function handleParallelCommand(raw) {
20348
20492
  });
20349
20493
  }
20350
20494
  } else if (dec.reason === "llm_unavailable" || dec.reason === "parse_failed") {
20351
- log11?.warn(`[${PLUGIN_NAME18}] AI 拆分失败(${dec.reason}),按单任务执行`);
20495
+ log11?.warn(`[${PLUGIN_NAME19}] AI 拆分失败(${dec.reason}),按单任务执行`);
20352
20496
  }
20353
20497
  }
20354
20498
  if (canNotice) {
@@ -20427,7 +20571,7 @@ async function handleParallelCommand(raw) {
20427
20571
  });
20428
20572
  } catch (err) {
20429
20573
  const msg = err instanceof Error ? err.message : String(err);
20430
- log11?.error(`[${PLUGIN_NAME18}] schedule 抛错`, { error: msg });
20574
+ log11?.error(`[${PLUGIN_NAME19}] schedule 抛错`, { error: msg });
20431
20575
  await safeReply2(ctx, `❌ 并发调度失败:${msg}`);
20432
20576
  return { ok: false, reason: msg };
20433
20577
  }
@@ -20445,7 +20589,7 @@ async function handleParallelCommand(raw) {
20445
20589
  }
20446
20590
  await safeReply2(ctx, summaryLines.join(`
20447
20591
  `));
20448
- log11?.info(`[${PLUGIN_NAME18}] schedule 完成`, {
20592
+ log11?.info(`[${PLUGIN_NAME19}] schedule 完成`, {
20449
20593
  parentId,
20450
20594
  success: result.digest.success,
20451
20595
  failed: result.digest.failed,
@@ -20458,7 +20602,7 @@ async function handleParallelCommand(raw) {
20458
20602
  try {
20459
20603
  await ctx.onCompleted(result);
20460
20604
  } catch (err) {
20461
- log11?.warn(`[${PLUGIN_NAME18}] onCompleted hook 抛错(已隔离)`, {
20605
+ log11?.warn(`[${PLUGIN_NAME19}] onCompleted hook 抛错(已隔离)`, {
20462
20606
  error: err instanceof Error ? err.message : String(err)
20463
20607
  });
20464
20608
  }
@@ -20500,7 +20644,7 @@ async function writeLog(level, msg, data) {
20500
20644
  const line = JSON.stringify({
20501
20645
  ts: new Date().toISOString(),
20502
20646
  level,
20503
- plugin: PLUGIN_NAME18,
20647
+ plugin: PLUGIN_NAME19,
20504
20648
  msg,
20505
20649
  data
20506
20650
  }) + `
@@ -20511,11 +20655,11 @@ async function writeLog(level, msg, data) {
20511
20655
  await fs18.appendFile(logFile, line, "utf8");
20512
20656
  } catch {}
20513
20657
  }
20514
- logLifecycle(PLUGIN_NAME18, "import");
20658
+ logLifecycle(PLUGIN_NAME19, "import");
20515
20659
  var subtasksServer = async (ctx) => {
20516
- const log11 = makePluginLogger(PLUGIN_NAME18);
20660
+ const log11 = makePluginLogger(PLUGIN_NAME19);
20517
20661
  const client = ctx?.client ?? undefined;
20518
- logLifecycle(PLUGIN_NAME18, "activate", {
20662
+ logLifecycle(PLUGIN_NAME19, "activate", {
20519
20663
  directory: ctx.directory,
20520
20664
  hasClient: Boolean(client)
20521
20665
  });
@@ -20612,19 +20756,19 @@ var subtasksServer = async (ctx) => {
20612
20756
  });
20613
20757
  }
20614
20758
  } catch (err) {
20615
- log11.error(`[${PLUGIN_NAME18}] command.execute.before 异常(已隔离)`, {
20759
+ log11.error(`[${PLUGIN_NAME19}] command.execute.before 异常(已隔离)`, {
20616
20760
  error: err instanceof Error ? err.message : String(err)
20617
20761
  });
20618
20762
  }
20619
20763
  }
20620
20764
  };
20621
20765
  };
20622
- var handler18 = subtasksServer;
20766
+ var handler19 = subtasksServer;
20623
20767
 
20624
20768
  // plugins/terminal-monitor.ts
20625
20769
  import * as crypto5 from "node:crypto";
20626
- var PLUGIN_NAME19 = "terminal-monitor";
20627
- logLifecycle(PLUGIN_NAME19, "import", {});
20770
+ var PLUGIN_NAME20 = "terminal-monitor";
20771
+ logLifecycle(PLUGIN_NAME20, "import", {});
20628
20772
  var DEFAULT_CONFIG6 = {
20629
20773
  minScore: 0.6,
20630
20774
  cooldownMs: 30000,
@@ -20906,17 +21050,17 @@ function describeError(err) {
20906
21050
  return String(err);
20907
21051
  }
20908
21052
  }
20909
- var log11 = makePluginLogger(PLUGIN_NAME19);
21053
+ var log11 = makePluginLogger(PLUGIN_NAME20);
20910
21054
  var lru = new FingerprintLRU2;
20911
21055
  var _lastNotification = null;
20912
21056
  var terminalMonitorServer = async (ctx) => {
20913
- logLifecycle(PLUGIN_NAME19, "activate", {
21057
+ logLifecycle(PLUGIN_NAME20, "activate", {
20914
21058
  directory: ctx.directory,
20915
21059
  triggerEventTypes: ["terminal.output", "terminal.exit"]
20916
21060
  });
20917
21061
  return {
20918
21062
  event: async ({ event }) => {
20919
- await safeAsync(PLUGIN_NAME19, "event", async () => {
21063
+ await safeAsync(PLUGIN_NAME20, "event", async () => {
20920
21064
  const e = event;
20921
21065
  if (!e || typeof e.type !== "string")
20922
21066
  return;
@@ -20930,7 +21074,7 @@ var terminalMonitorServer = async (ctx) => {
20930
21074
  _lastNotification = msg;
20931
21075
  }
20932
21076
  });
20933
- safeWriteLog(PLUGIN_NAME19, {
21077
+ safeWriteLog(PLUGIN_NAME20, {
20934
21078
  hook: "event",
20935
21079
  type: e.type,
20936
21080
  notified: r.notified,
@@ -20938,17 +21082,17 @@ var terminalMonitorServer = async (ctx) => {
20938
21082
  findings: r.notification?.findings.length ?? 0
20939
21083
  });
20940
21084
  if (r.notified && r.notification) {
20941
- log11.info(`[${PLUGIN_NAME19}] 反馈 ${r.notification.findings.length} 条 → ${r.notification.summary}`);
21085
+ log11.info(`[${PLUGIN_NAME20}] 反馈 ${r.notification.findings.length} 条 → ${r.notification.summary}`);
20942
21086
  }
20943
21087
  });
20944
21088
  }
20945
21089
  };
20946
21090
  };
20947
- var handler19 = terminalMonitorServer;
21091
+ var handler20 = terminalMonitorServer;
20948
21092
 
20949
21093
  // plugins/token-manager.ts
20950
- var PLUGIN_NAME20 = "token-manager";
20951
- logLifecycle(PLUGIN_NAME20, "import", {});
21094
+ var PLUGIN_NAME21 = "token-manager";
21095
+ logLifecycle(PLUGIN_NAME21, "import", {});
20952
21096
  async function handleMessageBefore(raw, log12, defaults) {
20953
21097
  const ctx = raw ?? {};
20954
21098
  if (!Array.isArray(ctx.messages) || ctx.messages.length === 0)
@@ -20968,21 +21112,21 @@ async function handleMessageBefore(raw, log12, defaults) {
20968
21112
  };
20969
21113
  if (r.compressed) {
20970
21114
  ctx.messages = r.messages;
20971
- log12?.info(`[${PLUGIN_NAME20}] 压缩 ${r.before.count}→${r.after.count} 条 / ${r.before.tokens}→${r.after.tokens} tokens`);
21115
+ log12?.info(`[${PLUGIN_NAME21}] 压缩 ${r.before.count}→${r.after.count} 条 / ${r.before.tokens}→${r.after.tokens} tokens`);
20972
21116
  }
20973
21117
  return r;
20974
21118
  } catch (err) {
20975
- log12?.warn(`[${PLUGIN_NAME20}] 压缩异常(已隔离)`, {
21119
+ log12?.warn(`[${PLUGIN_NAME21}] 压缩异常(已隔离)`, {
20976
21120
  error: err instanceof Error ? err.message : String(err)
20977
21121
  });
20978
21122
  return null;
20979
21123
  }
20980
21124
  }
20981
- var log12 = makePluginLogger(PLUGIN_NAME20);
21125
+ var log12 = makePluginLogger(PLUGIN_NAME21);
20982
21126
  var tokenManagerServer = async (ctx) => {
20983
21127
  const rt = loadRuntimeSync();
20984
21128
  const threshold = rt.runtime.context.condenser_threshold_ratio;
20985
- logLifecycle(PLUGIN_NAME20, "activate", {
21129
+ logLifecycle(PLUGIN_NAME21, "activate", {
20986
21130
  directory: ctx.directory,
20987
21131
  threshold,
20988
21132
  target: DEFAULT_CONDENSE.target,
@@ -20991,7 +21135,7 @@ var tokenManagerServer = async (ctx) => {
20991
21135
  });
20992
21136
  return {
20993
21137
  "experimental.chat.messages.transform": async (_input, output) => {
20994
- await safeAsync(PLUGIN_NAME20, "experimental.chat.messages.transform", async () => {
21138
+ await safeAsync(PLUGIN_NAME21, "experimental.chat.messages.transform", async () => {
20995
21139
  const list = output.messages;
20996
21140
  if (!Array.isArray(list) || list.length === 0)
20997
21141
  return;
@@ -21004,7 +21148,7 @@ var tokenManagerServer = async (ctx) => {
21004
21148
  const r = await handleMessageBefore({ messages: flat }, log12, { threshold });
21005
21149
  if (!r)
21006
21150
  return;
21007
- safeWriteLog(PLUGIN_NAME20, {
21151
+ safeWriteLog(PLUGIN_NAME21, {
21008
21152
  hook: "experimental.chat.messages.transform",
21009
21153
  mode: "observe-only",
21010
21154
  before_msgs: r.before.count,
@@ -21015,13 +21159,13 @@ var tokenManagerServer = async (ctx) => {
21015
21159
  reason: r.reason
21016
21160
  });
21017
21161
  if (r.compressed) {
21018
- log12.warn(`[${PLUGIN_NAME20}] 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)`);
21162
+ 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)`);
21019
21163
  }
21020
21164
  });
21021
21165
  }
21022
21166
  };
21023
21167
  };
21024
- var handler20 = tokenManagerServer;
21168
+ var handler21 = tokenManagerServer;
21025
21169
 
21026
21170
  // plugins/tool-policy.ts
21027
21171
  import { promises as fs19 } from "node:fs";
@@ -21284,9 +21428,14 @@ function checkFileAccess(acl, file, op) {
21284
21428
  }
21285
21429
 
21286
21430
  // plugins/tool-policy.ts
21287
- var PLUGIN_NAME21 = "tool-policy";
21288
- logLifecycle(PLUGIN_NAME21, "import", {});
21431
+ var PLUGIN_NAME22 = "tool-policy";
21432
+ logLifecycle(PLUGIN_NAME22, "import", {});
21289
21433
  var EMPTY_ACL = { whitelistMode: false };
21434
+ function getAgentAcl(cfg, agent) {
21435
+ if (!agent || !cfg.per_agent)
21436
+ return;
21437
+ return cfg.per_agent[agent];
21438
+ }
21290
21439
  var SUBAGENT_APPLY_DENY_LIST = new Set([
21291
21440
  "coder",
21292
21441
  "planner",
@@ -21350,7 +21499,7 @@ async function loadPolicy(root = process.cwd()) {
21350
21499
  function classifyToolKind(toolName) {
21351
21500
  return classifyTool(toolName);
21352
21501
  }
21353
- async function resolveCurrentAgent(client, sessionID, log13) {
21502
+ async function resolveCurrentAgent2(client, sessionID, log13) {
21354
21503
  try {
21355
21504
  const sessionApi = client?.session;
21356
21505
  if (!sessionApi || typeof sessionApi.get !== "function") {
@@ -21376,24 +21525,24 @@ async function resolveCurrentAgent(client, sessionID, log13) {
21376
21525
  return;
21377
21526
  }
21378
21527
  }
21379
- var log13 = makePluginLogger(PLUGIN_NAME21);
21528
+ var log13 = makePluginLogger(PLUGIN_NAME22);
21380
21529
  var toolPolicyServer = async (ctx) => {
21381
21530
  const directory = ctx.directory ?? process.cwd();
21382
21531
  const cfg = await loadPolicy(directory);
21383
- logLifecycle(PLUGIN_NAME21, "activate", {
21532
+ logLifecycle(PLUGIN_NAME22, "activate", {
21384
21533
  directory,
21385
21534
  acl_loaded: !!cfg.acl
21386
21535
  });
21387
21536
  return {
21388
21537
  "tool.execute.before": async (input, output) => {
21389
21538
  let denied;
21390
- await safeAsync(PLUGIN_NAME21, "tool.execute.before", async () => {
21539
+ await safeAsync(PLUGIN_NAME22, "tool.execute.before", async () => {
21391
21540
  const toolName = input.tool;
21392
21541
  const argsObj = output.args ?? {};
21393
21542
  const needsAgentDetection = toolName === "pending_changes" && (argsObj.action === "apply" || argsObj.action === "apply_all");
21394
21543
  let currentAgent = undefined;
21395
21544
  if (needsAgentDetection) {
21396
- currentAgent = await resolveCurrentAgent(ctx.client, input.sessionID, log13);
21545
+ currentAgent = await resolveCurrentAgent2(ctx.client, input.sessionID, log13);
21397
21546
  }
21398
21547
  const files = [];
21399
21548
  const filePath = argsObj["path"] ?? argsObj["filePath"] ?? argsObj["file"];
@@ -21407,7 +21556,7 @@ var toolPolicyServer = async (ctx) => {
21407
21556
  args: argsObj,
21408
21557
  files: files.length ? files : undefined
21409
21558
  }, cfg, currentAgent);
21410
- safeWriteLog(PLUGIN_NAME21, {
21559
+ safeWriteLog(PLUGIN_NAME22, {
21411
21560
  hook: "tool.execute.before",
21412
21561
  tool: toolName,
21413
21562
  callID: input.callID,
@@ -21418,7 +21567,7 @@ var toolPolicyServer = async (ctx) => {
21418
21567
  currentAgent
21419
21568
  });
21420
21569
  if (decision.action === "deny") {
21421
- log13.warn(`[${PLUGIN_NAME21}] DENY ${toolName}`, {
21570
+ log13.warn(`[${PLUGIN_NAME22}] DENY ${toolName}`, {
21422
21571
  action: argsObj.action,
21423
21572
  currentAgent,
21424
21573
  reasons: decision.reasons,
@@ -21433,7 +21582,7 @@ var toolPolicyServer = async (ctx) => {
21433
21582
  }
21434
21583
  };
21435
21584
  };
21436
- var handler21 = toolPolicyServer;
21585
+ var handler22 = toolPolicyServer;
21437
21586
 
21438
21587
  // plugins/update-checker.ts
21439
21588
  import { existsSync as existsSync5 } from "node:fs";
@@ -21463,7 +21612,7 @@ import * as zlib from "node:zlib";
21463
21612
  // lib/version-injected.ts
21464
21613
  function getInjectedVersion() {
21465
21614
  try {
21466
- const v = "0.5.8";
21615
+ const v = "0.5.9";
21467
21616
  if (typeof v === "string" && /^\d+\.\d+\.\d+/.test(v)) {
21468
21617
  return v;
21469
21618
  }
@@ -21966,20 +22115,20 @@ function compareOpencodeVersion(opts) {
21966
22115
  }
21967
22116
 
21968
22117
  // plugins/update-checker.ts
21969
- var PLUGIN_NAME22 = "update-checker";
22118
+ var PLUGIN_NAME23 = "update-checker";
21970
22119
  var PLUGIN_VERSION = "2.0.0";
21971
- logLifecycle(PLUGIN_NAME22, "import", { version: PLUGIN_VERSION });
22120
+ logLifecycle(PLUGIN_NAME23, "import", { version: PLUGIN_VERSION });
21972
22121
  var updateCheckerServer = async (ctx) => {
21973
22122
  const yieldResult = shouldYieldToLocalPlugin({ directory: ctx.directory });
21974
22123
  if (yieldResult.yield) {
21975
- safeWriteLog(PLUGIN_NAME22, {
22124
+ safeWriteLog(PLUGIN_NAME23, {
21976
22125
  level: "info",
21977
22126
  msg: "dev_mode_yield_skip",
21978
22127
  reason: yieldResult.reason,
21979
22128
  markerPath: yieldResult.markerPath,
21980
22129
  detail: formatYieldLog(yieldResult)
21981
22130
  });
21982
- logLifecycle(PLUGIN_NAME22, "activate", {
22131
+ logLifecycle(PLUGIN_NAME23, "activate", {
21983
22132
  yield_to_local: true,
21984
22133
  yield_reason: yieldResult.reason,
21985
22134
  skipped: "auto_install + background_check"
@@ -21988,7 +22137,7 @@ var updateCheckerServer = async (ctx) => {
21988
22137
  }
21989
22138
  const rt = loadRuntimeSync();
21990
22139
  const u = rt.runtime.update;
21991
- logLifecycle(PLUGIN_NAME22, "activate", {
22140
+ logLifecycle(PLUGIN_NAME23, "activate", {
21992
22141
  version: PLUGIN_VERSION,
21993
22142
  auto_check_enabled: u.auto_check_enabled,
21994
22143
  interval_hours: u.interval_hours,
@@ -22000,14 +22149,14 @@ var updateCheckerServer = async (ctx) => {
22000
22149
  repo_fallback: u.repo,
22001
22150
  config_source: "codeforge.json"
22002
22151
  });
22003
- await safeAsync(PLUGIN_NAME22, "opencode_version_check", async () => {
22152
+ await safeAsync(PLUGIN_NAME23, "opencode_version_check", async () => {
22004
22153
  const compat = loadCompatibility();
22005
22154
  const opencodeVer = detectOpencodeVersion();
22006
22155
  const verdict = compareOpencodeVersion({
22007
22156
  currentOpencodeVer: opencodeVer,
22008
22157
  compat
22009
22158
  });
22010
- safeWriteLog(PLUGIN_NAME22, {
22159
+ safeWriteLog(PLUGIN_NAME23, {
22011
22160
  level: "info",
22012
22161
  msg: "opencode_version_check",
22013
22162
  opencodeVer,
@@ -22024,14 +22173,14 @@ var updateCheckerServer = async (ctx) => {
22024
22173
  }
22025
22174
  });
22026
22175
  if (!u.auto_check_enabled) {
22027
- safeWriteLog(PLUGIN_NAME22, {
22176
+ safeWriteLog(PLUGIN_NAME23, {
22028
22177
  level: "info",
22029
22178
  msg: "auto-check disabled (codeforge.json update.auto_check_enabled=false)"
22030
22179
  });
22031
22180
  return {};
22032
22181
  }
22033
22182
  setImmediate(() => {
22034
- safeAsync(PLUGIN_NAME22, "checkAndMaybeUpdate", async () => {
22183
+ safeAsync(PLUGIN_NAME23, "checkAndMaybeUpdate", async () => {
22035
22184
  const local = readLocalVersion();
22036
22185
  let npmResult = null;
22037
22186
  try {
@@ -22042,7 +22191,7 @@ var updateCheckerServer = async (ctx) => {
22042
22191
  timeoutMs: 5000
22043
22192
  });
22044
22193
  } catch (e) {
22045
- safeWriteLog(PLUGIN_NAME22, {
22194
+ safeWriteLog(PLUGIN_NAME23, {
22046
22195
  level: "warn",
22047
22196
  msg: "npm_fetch_failed",
22048
22197
  error: e.message
@@ -22051,7 +22200,7 @@ var updateCheckerServer = async (ctx) => {
22051
22200
  return;
22052
22201
  }
22053
22202
  if (!npmResult) {
22054
- safeWriteLog(PLUGIN_NAME22, {
22203
+ safeWriteLog(PLUGIN_NAME23, {
22055
22204
  level: "info",
22056
22205
  msg: "npm_no_release",
22057
22206
  package: u.package,
@@ -22060,7 +22209,7 @@ var updateCheckerServer = async (ctx) => {
22060
22209
  return;
22061
22210
  }
22062
22211
  const hasUpdate = cmpVersion(local, npmResult.version) < 0;
22063
- safeWriteLog(PLUGIN_NAME22, {
22212
+ safeWriteLog(PLUGIN_NAME23, {
22064
22213
  level: "info",
22065
22214
  msg: "npm_check_result",
22066
22215
  local,
@@ -22075,10 +22224,10 @@ var updateCheckerServer = async (ctx) => {
22075
22224
  更新命令:npx ${u.package} install --global`);
22076
22225
  return;
22077
22226
  }
22078
- await safeAsync(PLUGIN_NAME22, "auto_install_bundle", async () => {
22227
+ await safeAsync(PLUGIN_NAME23, "auto_install_bundle", async () => {
22079
22228
  const target = getOpencodeBundlePath();
22080
22229
  if (!target) {
22081
- safeWriteLog(PLUGIN_NAME22, {
22230
+ safeWriteLog(PLUGIN_NAME23, {
22082
22231
  level: "warn",
22083
22232
  msg: "auto_install_skip",
22084
22233
  reason: "无法定位 opencode bundle 路径"
@@ -22097,7 +22246,7 @@ var updateCheckerServer = async (ctx) => {
22097
22246
  oldVersion: local,
22098
22247
  keepBackups: u.backup_keep
22099
22248
  });
22100
- safeWriteLog(PLUGIN_NAME22, {
22249
+ safeWriteLog(PLUGIN_NAME23, {
22101
22250
  level: "info",
22102
22251
  msg: "auto_install_success",
22103
22252
  local,
@@ -22137,7 +22286,7 @@ function getOpencodeBundlePath() {
22137
22286
  return candidates[0] ?? null;
22138
22287
  }
22139
22288
  async function fallbackToGitHubReleases(ctx, u) {
22140
- await safeAsync(PLUGIN_NAME22, "github_fallback", async () => {
22289
+ await safeAsync(PLUGIN_NAME23, "github_fallback", async () => {
22141
22290
  const result = await checkUpdateOnce({
22142
22291
  repo: u.repo,
22143
22292
  intervalMs: u.interval_hours * 3600 * 1000
@@ -22147,7 +22296,7 @@ async function fallbackToGitHubReleases(ctx, u) {
22147
22296
  }
22148
22297
  async function reportLegacyResult(ctx, result, repo) {
22149
22298
  if (result.error && !result.fromCache) {
22150
- safeWriteLog(PLUGIN_NAME22, {
22299
+ safeWriteLog(PLUGIN_NAME23, {
22151
22300
  level: "warn",
22152
22301
  msg: `update check failed: ${result.error}`,
22153
22302
  ...result
@@ -22155,7 +22304,7 @@ async function reportLegacyResult(ctx, result, repo) {
22155
22304
  return;
22156
22305
  }
22157
22306
  if (!result.hasUpdate) {
22158
- safeWriteLog(PLUGIN_NAME22, {
22307
+ safeWriteLog(PLUGIN_NAME23, {
22159
22308
  level: "info",
22160
22309
  msg: `up-to-date (local=${result.local}, remote=${result.remote}${result.fromCache ? ", from_cache" : ""})`,
22161
22310
  ...result
@@ -22165,7 +22314,7 @@ async function reportLegacyResult(ctx, result, repo) {
22165
22314
  const updateCmd = `bunx --bun github:${repo} install`;
22166
22315
  const toast = `[CodeForge] 有新版本:${result.local} → ${result.remote}
22167
22316
  更新命令:${updateCmd}`;
22168
- safeWriteLog(PLUGIN_NAME22, {
22317
+ safeWriteLog(PLUGIN_NAME23, {
22169
22318
  level: "info",
22170
22319
  msg: "new_version_available_github_fallback",
22171
22320
  local: result.local,
@@ -22176,17 +22325,17 @@ async function reportLegacyResult(ctx, result, repo) {
22176
22325
  await postToast(ctx, toast);
22177
22326
  }
22178
22327
  async function postToast(ctx, message) {
22179
- await safeAsync(PLUGIN_NAME22, "client.app.log", async () => {
22328
+ await safeAsync(PLUGIN_NAME23, "client.app.log", async () => {
22180
22329
  await ctx.client.app.log({
22181
22330
  body: {
22182
- service: PLUGIN_NAME22,
22331
+ service: PLUGIN_NAME23,
22183
22332
  level: "info",
22184
22333
  message
22185
22334
  }
22186
22335
  });
22187
22336
  });
22188
22337
  }
22189
- var handler22 = updateCheckerServer;
22338
+ var handler23 = updateCheckerServer;
22190
22339
 
22191
22340
  // plugins/workflow-engine.ts
22192
22341
  import * as path26 from "node:path";
@@ -22641,9 +22790,9 @@ async function runStepAutoFeedback(step, adapter) {
22641
22790
  }
22642
22791
 
22643
22792
  // plugins/workflow-engine.ts
22644
- var PLUGIN_NAME23 = "workflow-engine";
22645
- logLifecycle(PLUGIN_NAME23, "import", {});
22646
- var fallbackLog2 = makePluginLogger(PLUGIN_NAME23);
22793
+ var PLUGIN_NAME24 = "workflow-engine";
22794
+ logLifecycle(PLUGIN_NAME24, "import", {});
22795
+ var fallbackLog2 = makePluginLogger(PLUGIN_NAME24);
22647
22796
  var _registry = null;
22648
22797
  async function loadRegistry(workflowsDir) {
22649
22798
  const { loaded, failed } = await loadWorkflowsFromDir(workflowsDir);
@@ -22663,32 +22812,32 @@ async function handleCommandInvoked(raw, workflowsDir = "workflows") {
22663
22812
  const log14 = ctx.log ?? fallbackLog2;
22664
22813
  const command = typeof ctx.command === "string" ? ctx.command : null;
22665
22814
  if (!command) {
22666
- log14.warn(`[${PLUGIN_NAME23}] command.invoked 缺 command 字段`, ctx);
22815
+ log14.warn(`[${PLUGIN_NAME24}] command.invoked 缺 command 字段`, ctx);
22667
22816
  return null;
22668
22817
  }
22669
22818
  const reg = await ensureRegistry(workflowsDir);
22670
22819
  if (reg.errors.length) {
22671
- log14.warn(`[${PLUGIN_NAME23}] 有 ${reg.errors.length} 个 workflow 加载失败`, reg.errors);
22820
+ log14.warn(`[${PLUGIN_NAME24}] 有 ${reg.errors.length} 个 workflow 加载失败`, reg.errors);
22672
22821
  }
22673
22822
  const wf = reg.workflows.find((w) => matchesTrigger(w, command));
22674
22823
  if (!wf) {
22675
- log14.info(`[${PLUGIN_NAME23}] no workflow matches "${command}"`);
22824
+ log14.info(`[${PLUGIN_NAME24}] no workflow matches "${command}"`);
22676
22825
  return null;
22677
22826
  }
22678
- log14.info(`[${PLUGIN_NAME23}] dispatch "${command}" → workflow "${wf.name}"`);
22827
+ log14.info(`[${PLUGIN_NAME24}] dispatch "${command}" → workflow "${wf.name}"`);
22679
22828
  try {
22680
22829
  const result = await run(wf, {
22681
22830
  mode: ctx.adapter ? "real" : "dry_run",
22682
22831
  autonomy: ctx.autonomy ?? "semi",
22683
22832
  adapter: ctx.adapter
22684
22833
  });
22685
- log14.info(`[${PLUGIN_NAME23}] workflow "${wf.name}" 完成 (${result.plan.mode})`, {
22834
+ log14.info(`[${PLUGIN_NAME24}] workflow "${wf.name}" 完成 (${result.plan.mode})`, {
22686
22835
  steps: result.plan.steps.length,
22687
22836
  results: result.results.length
22688
22837
  });
22689
22838
  return result;
22690
22839
  } catch (err) {
22691
- log14.error(`[${PLUGIN_NAME23}] workflow "${wf.name}" 执行失败`, {
22840
+ log14.error(`[${PLUGIN_NAME24}] workflow "${wf.name}" 执行失败`, {
22692
22841
  error: err instanceof Error ? err.message : String(err)
22693
22842
  });
22694
22843
  throw err;
@@ -22697,15 +22846,15 @@ async function handleCommandInvoked(raw, workflowsDir = "workflows") {
22697
22846
  var workflowEngineServer = async (ctx) => {
22698
22847
  const directory = ctx.directory ?? process.cwd();
22699
22848
  const workflowsDir = path26.join(directory, "workflows");
22700
- ensureRegistry(workflowsDir).catch((err) => fallbackLog2.warn(`[${PLUGIN_NAME23}] preload workflows failed`, {
22849
+ ensureRegistry(workflowsDir).catch((err) => fallbackLog2.warn(`[${PLUGIN_NAME24}] preload workflows failed`, {
22701
22850
  error: err instanceof Error ? err.message : String(err)
22702
22851
  }));
22703
- logLifecycle(PLUGIN_NAME23, "activate", { directory, workflowsDir });
22852
+ logLifecycle(PLUGIN_NAME24, "activate", { directory, workflowsDir });
22704
22853
  return {
22705
22854
  "command.execute.before": async (input, output) => {
22706
- await safeAsync(PLUGIN_NAME23, "command.execute.before", async () => {
22855
+ await safeAsync(PLUGIN_NAME24, "command.execute.before", async () => {
22707
22856
  const cmd = input.command.startsWith("/") ? input.command : `/${input.command}`;
22708
- safeWriteLog(PLUGIN_NAME23, {
22857
+ safeWriteLog(PLUGIN_NAME24, {
22709
22858
  hook: "command.execute.before",
22710
22859
  command: cmd,
22711
22860
  sessionID: input.sessionID
@@ -22720,14 +22869,14 @@ var workflowEngineServer = async (ctx) => {
22720
22869
  });
22721
22870
  },
22722
22871
  "chat.message": async (input, output) => {
22723
- await safeAsync(PLUGIN_NAME23, "chat.message", async () => {
22872
+ await safeAsync(PLUGIN_NAME24, "chat.message", async () => {
22724
22873
  const text = extractUserText(output).trim();
22725
22874
  if (!text.startsWith("/"))
22726
22875
  return;
22727
22876
  const cmd = text.split(/\s+/)[0];
22728
22877
  if (!cmd)
22729
22878
  return;
22730
- safeWriteLog(PLUGIN_NAME23, {
22879
+ safeWriteLog(PLUGIN_NAME24, {
22731
22880
  hook: "chat.message",
22732
22881
  command: cmd,
22733
22882
  sessionID: input.sessionID
@@ -22737,12 +22886,12 @@ var workflowEngineServer = async (ctx) => {
22737
22886
  }
22738
22887
  };
22739
22888
  };
22740
- var handler23 = workflowEngineServer;
22889
+ var handler24 = workflowEngineServer;
22741
22890
 
22742
22891
  // plugins/session-worktree-guard.ts
22743
22892
  import path27 from "node:path";
22744
- var PLUGIN_NAME24 = "session-worktree-guard";
22745
- logLifecycle(PLUGIN_NAME24, "import", {});
22893
+ var PLUGIN_NAME25 = "session-worktree-guard";
22894
+ logLifecycle(PLUGIN_NAME25, "import", {});
22746
22895
  var WRITE_INTENT_RE = />(?!=)|\btee\b|\brm\b|\bmv\b|\bcp\b|\bmkdir\b|\btouch\b|\bchmod\b|\bchown\b|\bln\b/;
22747
22896
  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|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/;
22748
22897
  var SIDE_EFFECT_TOKEN_RE = />(?!=)|\|\s*tee\b|\btee\b/;
@@ -22821,7 +22970,17 @@ function isWriteOperation(toolName, argsObj, mainRoot) {
22821
22970
  return true;
22822
22971
  return false;
22823
22972
  }
22824
- var log14 = makePluginLogger(PLUGIN_NAME24);
22973
+ function collectWritePaths(toolName, argsObj, worktreeRoot) {
22974
+ const out = [];
22975
+ const candidate = toolName === "write" || toolName === "edit" ? argsObj["filePath"] : toolName === "ast_edit" ? argsObj["target"] : undefined;
22976
+ if (typeof candidate !== "string" || candidate.length === 0)
22977
+ return out;
22978
+ const abs = path27.isAbsolute(candidate) ? candidate : path27.resolve(worktreeRoot, candidate);
22979
+ const rel = path27.relative(worktreeRoot, abs).split(path27.sep).join("/");
22980
+ out.push(rel);
22981
+ return out;
22982
+ }
22983
+ var log14 = makePluginLogger(PLUGIN_NAME25);
22825
22984
  function resolveMainRoot2(rawDir) {
22826
22985
  const worktreeMarker = "/.git/codeforge-worktrees/";
22827
22986
  const idx = rawDir.indexOf(worktreeMarker);
@@ -22832,7 +22991,17 @@ function resolveMainRoot2(rawDir) {
22832
22991
  }
22833
22992
  var sessionWorktreeGuardPlugin = async (ctx) => {
22834
22993
  const mainRoot = resolveMainRoot2(ctx.directory ?? process.cwd());
22835
- logLifecycle(PLUGIN_NAME24, "activate", {
22994
+ let policyCfg = {};
22995
+ try {
22996
+ policyCfg = await loadPolicy(mainRoot);
22997
+ } catch (err) {
22998
+ log14.warn("loadPolicy failed (class E skipped)", {
22999
+ mainRoot,
23000
+ error: err instanceof Error ? err.message : String(err)
23001
+ });
23002
+ }
23003
+ const perAgentEnabled = !!policyCfg.per_agent && Object.keys(policyCfg.per_agent).length > 0;
23004
+ logLifecycle(PLUGIN_NAME25, "activate", {
22836
23005
  mainRoot,
22837
23006
  CODEFORGE_SESSION_ID: process.env["CODEFORGE_SESSION_ID"] ?? "(not set)"
22838
23007
  });
@@ -22842,7 +23011,7 @@ var sessionWorktreeGuardPlugin = async (ctx) => {
22842
23011
  if (!sessionId)
22843
23012
  return;
22844
23013
  let denied;
22845
- await safeAsync(PLUGIN_NAME24, "tool.execute.before", async () => {
23014
+ await safeAsync(PLUGIN_NAME25, "tool.execute.before", async () => {
22846
23015
  const toolName = input.tool;
22847
23016
  const argsObj = output.args ?? {};
22848
23017
  let entry = null;
@@ -22867,7 +23036,7 @@ var sessionWorktreeGuardPlugin = async (ctx) => {
22867
23036
  if (parentEntry && parentEntry.status === "active") {
22868
23037
  entry = parentEntry;
22869
23038
  log14.debug?.(`[child-inherit] session ${sessionId} 继承父 ${parentId} 的 worktree`, { parentSessionId: parentId, worktreePath: parentEntry.worktreePath });
22870
- safeWriteLog(PLUGIN_NAME24, {
23039
+ safeWriteLog(PLUGIN_NAME25, {
22871
23040
  hook: "tool.execute.before",
22872
23041
  tool: toolName,
22873
23042
  sessionID: input.sessionID,
@@ -22886,7 +23055,7 @@ var sessionWorktreeGuardPlugin = async (ctx) => {
22886
23055
  try {
22887
23056
  entry = await bindSessionWorktree({ sessionId, mainRoot });
22888
23057
  log14.info(`[lazy-bind] auto-created worktree for session ${sessionId}`, { branch: entry.branch, path: entry.worktreePath });
22889
- safeWriteLog(PLUGIN_NAME24, {
23058
+ safeWriteLog(PLUGIN_NAME25, {
22890
23059
  hook: "tool.execute.before",
22891
23060
  tool: toolName,
22892
23061
  sessionID: input.sessionID,
@@ -22896,7 +23065,7 @@ var sessionWorktreeGuardPlugin = async (ctx) => {
22896
23065
  });
22897
23066
  } catch (err) {
22898
23067
  log14.warn(`[lazy-bind] failed (落到主仓写)`, { sessionId, error: err instanceof Error ? err.message : String(err) });
22899
- safeWriteLog(PLUGIN_NAME24, {
23068
+ safeWriteLog(PLUGIN_NAME25, {
22900
23069
  hook: "tool.execute.before",
22901
23070
  tool: toolName,
22902
23071
  sessionID: input.sessionID,
@@ -22919,7 +23088,7 @@ var sessionWorktreeGuardPlugin = async (ctx) => {
22919
23088
  action,
22920
23089
  caller
22921
23090
  });
22922
- safeWriteLog(PLUGIN_NAME24, {
23091
+ safeWriteLog(PLUGIN_NAME25, {
22923
23092
  hook: "tool.execute.before",
22924
23093
  tool: toolName,
22925
23094
  sessionID: input.sessionID,
@@ -22933,6 +23102,56 @@ var sessionWorktreeGuardPlugin = async (ctx) => {
22933
23102
  }
22934
23103
  }
22935
23104
  }
23105
+ if (perAgentEnabled && isWriteOperation(toolName, argsObj, mainRoot)) {
23106
+ const caller = await resolveAgentForGuard({ sessionID: input.sessionID, agent: input.agent }, ctx.client, log14);
23107
+ if (caller === null) {
23108
+ 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)`;
23109
+ log14.warn(reason, {
23110
+ sessionId,
23111
+ tool: toolName,
23112
+ source: "per-agent-acl-fail-closed"
23113
+ });
23114
+ safeWriteLog(PLUGIN_NAME25, {
23115
+ hook: "tool.execute.before",
23116
+ tool: toolName,
23117
+ sessionID: input.sessionID,
23118
+ action: "deny",
23119
+ source: "per-agent-acl-fail-closed",
23120
+ caller: null
23121
+ });
23122
+ denied = new DeniedError(reason);
23123
+ return;
23124
+ }
23125
+ const agentAcl = getAgentAcl(policyCfg, caller);
23126
+ if (agentAcl) {
23127
+ const writePaths = collectWritePaths(toolName, argsObj, worktreePath);
23128
+ for (const relPath of writePaths) {
23129
+ const dec = checkFileAccess(agentAcl, relPath, "write");
23130
+ if (dec.action === "deny") {
23131
+ const reason = `[session-worktree-guard] DENIED: agent='${caller}' 写路径不在 ACL 白名单 — ` + `${relPath} (${dec.reason})`;
23132
+ log14.warn(reason, {
23133
+ sessionId,
23134
+ tool: toolName,
23135
+ caller,
23136
+ path: relPath,
23137
+ acl_decision: dec
23138
+ });
23139
+ safeWriteLog(PLUGIN_NAME25, {
23140
+ hook: "tool.execute.before",
23141
+ tool: toolName,
23142
+ sessionID: input.sessionID,
23143
+ action: "deny",
23144
+ source: "per-agent-acl",
23145
+ caller,
23146
+ path: relPath,
23147
+ acl_decision: dec
23148
+ });
23149
+ denied = new DeniedError(reason);
23150
+ return;
23151
+ }
23152
+ }
23153
+ }
23154
+ }
22936
23155
  if (toolName !== "plan_read" && entry.requiredPlanId && entry.planReadOk !== true) {
22937
23156
  let isWriteOp = WRITE_TOOLS.has(toolName);
22938
23157
  if (!isWriteOp && toolName === "bash") {
@@ -22961,7 +23180,7 @@ var sessionWorktreeGuardPlugin = async (ctx) => {
22961
23180
  requiredPlanId: entry.requiredPlanId,
22962
23181
  inheritedFromParent: inherited ? entry.sessionId : undefined
22963
23182
  });
22964
- safeWriteLog(PLUGIN_NAME24, {
23183
+ safeWriteLog(PLUGIN_NAME25, {
22965
23184
  hook: "tool.execute.before",
22966
23185
  tool: toolName,
22967
23186
  sessionID: input.sessionID,
@@ -22979,7 +23198,7 @@ var sessionWorktreeGuardPlugin = async (ctx) => {
22979
23198
  const snippet = command.length > 60 ? command.slice(0, 60) + "…" : command;
22980
23199
  const reason = `[session-worktree-guard] DENIED: bash.command 含主仓绝对路径写操作 (${snippet}),请在当前 session worktree (${worktreePath}) 内操作`;
22981
23200
  log14.warn(reason, { sessionId, command: command.slice(0, 200) });
22982
- safeWriteLog(PLUGIN_NAME24, {
23201
+ safeWriteLog(PLUGIN_NAME25, {
22983
23202
  hook: "tool.execute.before",
22984
23203
  tool: toolName,
22985
23204
  sessionID: input.sessionID,
@@ -22997,7 +23216,7 @@ var sessionWorktreeGuardPlugin = async (ctx) => {
22997
23216
  const newPath = rewritePath(filePath, mainRoot, worktreePath);
22998
23217
  if (newPath !== null) {
22999
23218
  log14.info(`rewrote ${toolName}.filePath: ${filePath} → ${newPath}`);
23000
- safeWriteLog(PLUGIN_NAME24, {
23219
+ safeWriteLog(PLUGIN_NAME25, {
23001
23220
  hook: "tool.execute.before",
23002
23221
  tool: toolName,
23003
23222
  field: "filePath",
@@ -23015,7 +23234,7 @@ var sessionWorktreeGuardPlugin = async (ctx) => {
23015
23234
  const newTarget = rewritePath(target, mainRoot, worktreePath);
23016
23235
  if (newTarget !== null) {
23017
23236
  log14.info(`rewrote ast_edit.target: ${target} → ${newTarget}`);
23018
- safeWriteLog(PLUGIN_NAME24, {
23237
+ safeWriteLog(PLUGIN_NAME25, {
23019
23238
  hook: "tool.execute.before",
23020
23239
  tool: toolName,
23021
23240
  field: "target",
@@ -23031,7 +23250,7 @@ var sessionWorktreeGuardPlugin = async (ctx) => {
23031
23250
  const newRoot = rewritePath(root, mainRoot, worktreePath);
23032
23251
  if (newRoot !== null) {
23033
23252
  log14.info(`rewrote ast_edit.root: ${root} → ${newRoot}`);
23034
- safeWriteLog(PLUGIN_NAME24, {
23253
+ safeWriteLog(PLUGIN_NAME25, {
23035
23254
  hook: "tool.execute.before",
23036
23255
  tool: toolName,
23037
23256
  field: "root",
@@ -23049,7 +23268,7 @@ var sessionWorktreeGuardPlugin = async (ctx) => {
23049
23268
  const newWorkdir = rewritePath(workdir, mainRoot, worktreePath);
23050
23269
  if (newWorkdir !== null) {
23051
23270
  log14.info(`rewrote bash.workdir: ${workdir} → ${newWorkdir}`);
23052
- safeWriteLog(PLUGIN_NAME24, {
23271
+ safeWriteLog(PLUGIN_NAME25, {
23053
23272
  hook: "tool.execute.before",
23054
23273
  tool: toolName,
23055
23274
  field: "workdir",
@@ -23067,19 +23286,22 @@ var sessionWorktreeGuardPlugin = async (ctx) => {
23067
23286
  }
23068
23287
  };
23069
23288
  };
23070
- var handler24 = sessionWorktreeGuardPlugin;
23289
+ var handler25 = sessionWorktreeGuardPlugin;
23071
23290
 
23072
23291
  // plugins/worktree-lifecycle.ts
23073
- var PLUGIN_NAME25 = "worktree-lifecycle";
23074
- logLifecycle(PLUGIN_NAME25, "import", {});
23292
+ var PLUGIN_NAME26 = "worktree-lifecycle";
23293
+ logLifecycle(PLUGIN_NAME26, "import", {});
23075
23294
  var IDLE_TOAST_THROTTLE_MS = 60 * 60000;
23076
23295
  var IDLE_TOAST_DURATION_MS = 8000;
23077
23296
  var IDLE_TOAST_REMINDER_INTERVAL_MS = 30 * 60000;
23297
+ var PRUNE_INTERVAL_MS = 30 * 60000;
23078
23298
  var lastIdleToastAt = new Map;
23079
- var log15 = makePluginLogger(PLUGIN_NAME25);
23299
+ var pruneRunning = false;
23300
+ var _pruneTimer;
23301
+ var log15 = makePluginLogger(PLUGIN_NAME26);
23080
23302
  var worktreeLifecyclePlugin = async (ctx) => {
23081
23303
  const mainRoot = ctx.directory;
23082
- logLifecycle(PLUGIN_NAME25, "activate", {
23304
+ logLifecycle(PLUGIN_NAME26, "activate", {
23083
23305
  mainRoot: mainRoot ?? "(not set)",
23084
23306
  idle_threshold_ms: IDLE_TOAST_THROTTLE_MS
23085
23307
  });
@@ -23088,11 +23310,11 @@ var worktreeLifecyclePlugin = async (ctx) => {
23088
23310
  }
23089
23311
  const client = ctx.client;
23090
23312
  setImmediate(() => {
23091
- safeAsync(PLUGIN_NAME25, "activate.pruneOrphan", async () => {
23313
+ safeAsync(PLUGIN_NAME26, "activate.pruneOrphan", async () => {
23092
23314
  const result = await pruneOrphanWorktrees(mainRoot);
23093
23315
  if (result.cleaned.length > 0 || result.failed.length > 0) {
23094
23316
  log15.info(`[pruneOrphan] cleaned=${result.cleaned.length} failed=${result.failed.length} skipped=${result.skipped}`);
23095
- safeWriteLog(PLUGIN_NAME25, {
23317
+ safeWriteLog(PLUGIN_NAME26, {
23096
23318
  hook: "activate.pruneOrphan",
23097
23319
  cleaned: result.cleaned,
23098
23320
  failed: result.failed,
@@ -23101,16 +23323,46 @@ var worktreeLifecyclePlugin = async (ctx) => {
23101
23323
  }
23102
23324
  });
23103
23325
  });
23326
+ if (_pruneTimer)
23327
+ clearInterval(_pruneTimer);
23328
+ _pruneTimer = setInterval(() => {
23329
+ if (pruneRunning) {
23330
+ safeWriteLog(PLUGIN_NAME26, {
23331
+ hook: "interval.pruneOrphan",
23332
+ action: "skip",
23333
+ reason: "previous prune still running"
23334
+ });
23335
+ return;
23336
+ }
23337
+ pruneRunning = true;
23338
+ safeAsync(PLUGIN_NAME26, "interval.pruneOrphan", async () => {
23339
+ try {
23340
+ const result = await pruneOrphanWorktrees(mainRoot);
23341
+ if (result.cleaned.length > 0 || result.failed.length > 0) {
23342
+ log15.info(`[pruneOrphan interval] cleaned=${result.cleaned.length} failed=${result.failed.length} skipped=${result.skipped}`);
23343
+ safeWriteLog(PLUGIN_NAME26, {
23344
+ hook: "interval.pruneOrphan",
23345
+ cleaned: result.cleaned,
23346
+ failed: result.failed,
23347
+ skipped: result.skipped
23348
+ });
23349
+ }
23350
+ } finally {
23351
+ pruneRunning = false;
23352
+ }
23353
+ });
23354
+ }, PRUNE_INTERVAL_MS);
23355
+ _pruneTimer.unref();
23104
23356
  return {
23105
23357
  event: async ({ event }) => {
23106
- await safeAsync(PLUGIN_NAME25, "event", async () => {
23358
+ await safeAsync(PLUGIN_NAME26, "event", async () => {
23107
23359
  const ended = extractEndedSessionID(event);
23108
23360
  if (!ended)
23109
23361
  return;
23110
23362
  if (ended.type === "session.deleted") {
23111
23363
  const entry = await getSessionWorktree(ended.sessionID, mainRoot);
23112
23364
  if (!entry || entry.status !== "active") {
23113
- safeWriteLog(PLUGIN_NAME25, {
23365
+ safeWriteLog(PLUGIN_NAME26, {
23114
23366
  hook: "event",
23115
23367
  type: ended.type,
23116
23368
  sessionID: ended.sessionID,
@@ -23125,7 +23377,7 @@ var worktreeLifecyclePlugin = async (ctx) => {
23125
23377
  const fastDirty = await isWorktreeDirty(entry.worktreePath);
23126
23378
  if (!fastDirty) {
23127
23379
  await discardSession({ sessionId: ended.sessionID, mainRoot });
23128
- safeWriteLog(PLUGIN_NAME25, {
23380
+ safeWriteLog(PLUGIN_NAME26, {
23129
23381
  hook: "event",
23130
23382
  type: ended.type,
23131
23383
  sessionID: ended.sessionID,
@@ -23160,7 +23412,7 @@ var worktreeLifecyclePlugin = async (ctx) => {
23160
23412
  }
23161
23413
  try {
23162
23414
  await discardSession({ sessionId: ended.sessionID, mainRoot });
23163
- safeWriteLog(PLUGIN_NAME25, {
23415
+ safeWriteLog(PLUGIN_NAME26, {
23164
23416
  hook: "event",
23165
23417
  type: ended.type,
23166
23418
  sessionID: ended.sessionID,
@@ -23194,7 +23446,7 @@ var worktreeLifecyclePlugin = async (ctx) => {
23194
23446
  const idleMin = Math.round(idleMs / 60000);
23195
23447
  const msg = `\uD83D\uDCA4 Session ${ended.sessionID.slice(0, 8)} worktree 已空闲 ${idleMin}min,` + `用 /merge 收尾或 /discard-session 放弃`;
23196
23448
  const sent = await showToast2(client, { message: msg, variant: "default", duration: IDLE_TOAST_DURATION_MS, title: "CodeForge" }, log15);
23197
- safeWriteLog(PLUGIN_NAME25, {
23449
+ safeWriteLog(PLUGIN_NAME26, {
23198
23450
  hook: "event",
23199
23451
  type: ended.type,
23200
23452
  sessionID: ended.sessionID,
@@ -23207,7 +23459,7 @@ var worktreeLifecyclePlugin = async (ctx) => {
23207
23459
  }
23208
23460
  };
23209
23461
  };
23210
- var handler25 = worktreeLifecyclePlugin;
23462
+ var handler26 = worktreeLifecyclePlugin;
23211
23463
 
23212
23464
  // src/index.ts
23213
23465
  var PLUGIN_ID = "codeforge";
@@ -23219,26 +23471,27 @@ var HANDLERS = [
23219
23471
  { name: "auto-commit", init: handler3 },
23220
23472
  { name: "auto-learning", init: handler4 },
23221
23473
  { name: "channels", init: handler5 },
23222
- { name: "codeforge-tools", init: handler7 },
23223
- { name: "discover-spec-suggest", init: handler8 },
23224
- { name: "kh-auto-context", init: handler9 },
23225
- { name: "kh-reminder", init: handler10 },
23226
- { name: "memories-context", init: handler11 },
23227
- { name: "model-fallback", init: handler12 },
23228
- { name: "parallel-tool-nudge", init: handler15 },
23229
- { name: "pwsh-utf8", init: handler16 },
23230
- { name: "session-recovery", init: handler17 },
23231
- { name: "subtask-heartbeat", init: handler13 },
23232
- { name: "subtasks", init: handler18 },
23233
- { name: "parallel-status", init: handler14 },
23234
- { name: "terminal-monitor", init: handler19 },
23235
- { name: "token-manager", init: handler20 },
23236
- { name: "tool-heartbeat", init: handler6 },
23237
- { name: "tool-policy", init: handler21 },
23238
- { name: "update-checker", init: handler22 },
23239
- { name: "workflow-engine", init: handler23 },
23240
- { name: "session-worktree-guard", init: handler24 },
23241
- { name: "worktree-lifecycle", init: handler25 }
23474
+ { name: "chat-agent-cache", init: handler6 },
23475
+ { name: "codeforge-tools", init: handler8 },
23476
+ { name: "discover-spec-suggest", init: handler9 },
23477
+ { name: "kh-auto-context", init: handler10 },
23478
+ { name: "kh-reminder", init: handler11 },
23479
+ { name: "memories-context", init: handler12 },
23480
+ { name: "model-fallback", init: handler13 },
23481
+ { name: "parallel-tool-nudge", init: handler16 },
23482
+ { name: "pwsh-utf8", init: handler17 },
23483
+ { name: "session-recovery", init: handler18 },
23484
+ { name: "subtask-heartbeat", init: handler14 },
23485
+ { name: "subtasks", init: handler19 },
23486
+ { name: "parallel-status", init: handler15 },
23487
+ { name: "terminal-monitor", init: handler20 },
23488
+ { name: "token-manager", init: handler21 },
23489
+ { name: "tool-heartbeat", init: handler7 },
23490
+ { name: "tool-policy", init: handler22 },
23491
+ { name: "update-checker", init: handler23 },
23492
+ { name: "workflow-engine", init: handler24 },
23493
+ { name: "session-worktree-guard", init: handler25 },
23494
+ { name: "worktree-lifecycle", init: handler26 }
23242
23495
  ];
23243
23496
  function makeSerialHook(hookName, fns) {
23244
23497
  return async (input, output) => {