@andyqiu/codeforge 0.5.8 → 0.5.10

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
 
@@ -13421,8 +13563,8 @@ async function pruneOrphanWorktrees(mainRoot) {
13421
13563
  var DEFAULT_MERGE_LOOP_CONFIG = {
13422
13564
  maxReviewLoops: 3,
13423
13565
  autoCoder: true,
13424
- reviewTimeoutMs: 600000,
13425
- coderTimeoutMs: 1800000,
13566
+ reviewTimeoutMs: 180000,
13567
+ coderTimeoutMs: 600000,
13426
13568
  abortDirtyStrategy: "checkpoint"
13427
13569
  };
13428
13570
  async function runMergeLoop(opts) {
@@ -13472,7 +13614,11 @@ async function runMergeLoop(opts) {
13472
13614
  maxRounds: config.maxReviewLoops,
13473
13615
  ...lastReviewSummary ? { prevSummary: lastReviewSummary } : {},
13474
13616
  ...opts.signal ? { signal: opts.signal } : {}
13475
- }), config.reviewTimeoutMs, `reviewer 第 ${loops} 轮`, opts.signal);
13617
+ }), config.reviewTimeoutMs, `reviewer 第 ${loops} 轮`, opts.signal, {
13618
+ onHeartbeat: (elapsedMs) => {
13619
+ progress("dispatch_review", `reviewer 第 ${loops}/${config.maxReviewLoops} 轮仍在运行,已等待 ${Math.round(elapsedMs / 1000)}s`);
13620
+ }
13621
+ });
13476
13622
  } catch (err) {
13477
13623
  const e = err;
13478
13624
  if (isAbortError2(e)) {
@@ -13541,7 +13687,11 @@ async function runMergeLoop(opts) {
13541
13687
  ...opts.planId ? { planId: opts.planId } : {},
13542
13688
  reviewerSummary: reviewResult.summary,
13543
13689
  ...opts.signal ? { signal: opts.signal } : {}
13544
- }), config.coderTimeoutMs, `coder round ${loops}`, opts.signal);
13690
+ }), config.coderTimeoutMs, `coder round ${loops}`, opts.signal, {
13691
+ onHeartbeat: (elapsedMs) => {
13692
+ progress("dispatch_coder", `coder round ${loops} 仍在运行,已等待 ${Math.round(elapsedMs / 1000)}s`);
13693
+ }
13694
+ });
13545
13695
  progress("wait_coder", `coder 完成: ${coderResult.ok ? "ok" : "fail"} - ${coderResult.summary}`);
13546
13696
  if (!coderResult.ok) {
13547
13697
  return {
@@ -13624,34 +13774,50 @@ async function handleAbortDirty(opts, config, entry) {
13624
13774
  function isAbortError2(err) {
13625
13775
  return err instanceof Error && err.name === "AbortError";
13626
13776
  }
13627
- function withTimeout2(p, ms, label, signal) {
13777
+ function withTimeout2(p, ms, label, signal, hbOpts) {
13628
13778
  return new Promise((resolve11, reject) => {
13629
- const timer = setTimeout(() => {
13779
+ const startedAt = Date.now();
13780
+ let hbTimer = null;
13781
+ let timer;
13782
+ const cleanup = () => {
13783
+ clearTimeout(timer);
13784
+ if (hbTimer)
13785
+ clearInterval(hbTimer);
13786
+ if (signal)
13787
+ signal.removeEventListener("abort", onAbort);
13788
+ };
13789
+ timer = setTimeout(() => {
13790
+ cleanup();
13630
13791
  reject(new Error(`${label} 超时 (${ms}ms)`));
13631
13792
  }, ms);
13793
+ const hbInterval = hbOpts?.heartbeatIntervalMs ?? 30000;
13794
+ const hbCb = hbOpts?.onHeartbeat;
13795
+ if (hbCb) {
13796
+ hbTimer = setInterval(() => {
13797
+ try {
13798
+ hbCb(Date.now() - startedAt);
13799
+ } catch {}
13800
+ }, hbInterval);
13801
+ }
13632
13802
  const onAbort = () => {
13633
- clearTimeout(timer);
13803
+ cleanup();
13634
13804
  const err = new Error(`${label} aborted by signal`);
13635
13805
  err.name = "AbortError";
13636
13806
  reject(err);
13637
13807
  };
13638
13808
  if (signal) {
13639
13809
  if (signal.aborted) {
13640
- clearTimeout(timer);
13810
+ cleanup();
13641
13811
  onAbort();
13642
13812
  return;
13643
13813
  }
13644
13814
  signal.addEventListener("abort", onAbort, { once: true });
13645
13815
  }
13646
13816
  p.then((v) => {
13647
- clearTimeout(timer);
13648
- if (signal)
13649
- signal.removeEventListener("abort", onAbort);
13817
+ cleanup();
13650
13818
  resolve11(v);
13651
13819
  }, (e) => {
13652
- clearTimeout(timer);
13653
- if (signal)
13654
- signal.removeEventListener("abort", onAbort);
13820
+ cleanup();
13655
13821
  reject(e);
13656
13822
  });
13657
13823
  });
@@ -14759,7 +14925,7 @@ class ProductionSpawner {
14759
14925
  prompt,
14760
14926
  title: `[merge-review] sess=${args.sessionId.slice(0, 8)} r=${args.round}/${args.maxRounds}`,
14761
14927
  ...args.signal ? { signal: args.signal } : {},
14762
- timeoutMs: this.opts.reviewerTimeoutMs ?? 600000
14928
+ timeoutMs: this.opts.reviewerTimeoutMs ?? 180000
14763
14929
  }, args.sessionId);
14764
14930
  } catch (err) {
14765
14931
  throw err;
@@ -14791,7 +14957,7 @@ ${r.text.slice(0, 800)}`
14791
14957
  prompt,
14792
14958
  title: `[merge-fix] sess=${args.sessionId.slice(0, 8)}`,
14793
14959
  ...args.signal ? { signal: args.signal } : {},
14794
- timeoutMs: this.opts.coderTimeoutMs ?? 1800000
14960
+ timeoutMs: this.opts.coderTimeoutMs ?? 600000
14795
14961
  }, args.sessionId);
14796
14962
  } catch (err) {
14797
14963
  throw err;
@@ -14856,6 +15022,15 @@ ${r.text.slice(0, 800)}`
14856
15022
  err: describe5(err)
14857
15023
  });
14858
15024
  }
15025
+ const mainRoot = this.opts.mainRoot ?? this.opts.directory;
15026
+ if (mainRoot) {
15027
+ const cid = childId;
15028
+ discardSession({ sessionId: cid, mainRoot }).catch((err) => {
15029
+ this.opts.log?.("warn", `[spawner] auto-discard 失败 ${cid}`, {
15030
+ err: describe5(err)
15031
+ });
15032
+ });
15033
+ }
14859
15034
  }
14860
15035
  }
14861
15036
  }
@@ -14955,7 +15130,6 @@ import { promises as fs14 } from "node:fs";
14955
15130
  import * as path17 from "node:path";
14956
15131
  var DEFAULT_RUNTIME = {
14957
15132
  autonomy: {
14958
- default_mode: "semi",
14959
15133
  downgrade_on_risky: true
14960
15134
  },
14961
15135
  runtime: {
@@ -15039,13 +15213,6 @@ function parseRuntime(raw, abs) {
15039
15213
  const cfg = cloneDefaults();
15040
15214
  if (root.autonomy && typeof root.autonomy === "object" && !Array.isArray(root.autonomy)) {
15041
15215
  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
15216
  if (a.downgrade_on_risky !== undefined) {
15050
15217
  if (typeof a.downgrade_on_risky === "boolean") {
15051
15218
  cfg.autonomy.downgrade_on_risky = a.downgrade_on_risky;
@@ -15165,8 +15332,8 @@ function parseRuntime(raw, abs) {
15165
15332
  }
15166
15333
 
15167
15334
  // plugins/tool-heartbeat.ts
15168
- var PLUGIN_NAME6 = "tool-heartbeat";
15169
- logLifecycle(PLUGIN_NAME6, "import", {});
15335
+ var PLUGIN_NAME7 = "tool-heartbeat";
15336
+ logLifecycle(PLUGIN_NAME7, "import", {});
15170
15337
  var HEARTBEAT_INTERVAL_MS = 15000;
15171
15338
  var ALERT_30S_MS = 30000;
15172
15339
  var ALERT_60S_MS = 60000;
@@ -15248,15 +15415,15 @@ async function showToast(client, payload, log5) {
15248
15415
  return false;
15249
15416
  }
15250
15417
  }
15251
- var log5 = makePluginLogger(PLUGIN_NAME6);
15418
+ var log5 = makePluginLogger(PLUGIN_NAME7);
15252
15419
  var toolHeartbeatServer = async (ctx) => {
15253
- logLifecycle(PLUGIN_NAME6, "activate", {
15420
+ logLifecycle(PLUGIN_NAME7, "activate", {
15254
15421
  directory: ctx.directory,
15255
15422
  intervalMs: HEARTBEAT_INTERVAL_MS
15256
15423
  });
15257
15424
  const client = ctx.client;
15258
15425
  const interval = setInterval(() => {
15259
- safeAsync(PLUGIN_NAME6, "interval", async () => {
15426
+ safeAsync(PLUGIN_NAME7, "interval", async () => {
15260
15427
  if (inflight.size === 0)
15261
15428
  return;
15262
15429
  const records = [...inflight.values()];
@@ -15266,7 +15433,7 @@ var toolHeartbeatServer = async (ctx) => {
15266
15433
  continue;
15267
15434
  const sent = await showToast(client, { message: alert.message, variant: alert.variant }, log5);
15268
15435
  r.alertsSent.add(alert.threshold);
15269
- safeWriteLog(PLUGIN_NAME6, {
15436
+ safeWriteLog(PLUGIN_NAME7, {
15270
15437
  hook: "interval",
15271
15438
  tool: r.toolName,
15272
15439
  callID: r.callID,
@@ -15284,12 +15451,12 @@ var toolHeartbeatServer = async (ctx) => {
15284
15451
  event: async () => {}
15285
15452
  };
15286
15453
  };
15287
- var handler6 = toolHeartbeatServer;
15454
+ var handler7 = toolHeartbeatServer;
15288
15455
 
15289
15456
  // plugins/codeforge-tools.ts
15290
15457
  var z30 = tool.schema;
15291
- var PLUGIN_NAME7 = "codeforge-tools";
15292
- logLifecycle(PLUGIN_NAME7, "import");
15458
+ var PLUGIN_NAME8 = "codeforge-tools";
15459
+ logLifecycle(PLUGIN_NAME8, "import");
15293
15460
  function wrap(output, metadata) {
15294
15461
  const text = typeof output === "string" ? output : JSON.stringify(output, null, 2);
15295
15462
  return metadata && Object.keys(metadata).length > 0 ? { output: text, metadata } : { output: text };
@@ -15299,7 +15466,7 @@ async function runSafe(toolName, fn) {
15299
15466
  return await fn();
15300
15467
  } catch (err) {
15301
15468
  const msg = err instanceof Error ? err.message : String(err);
15302
- safeWriteLog(PLUGIN_NAME7, {
15469
+ safeWriteLog(PLUGIN_NAME8, {
15303
15470
  level: "error",
15304
15471
  tool: toolName,
15305
15472
  error: msg
@@ -15696,7 +15863,7 @@ var codeforgeToolsServer = async (ctx) => {
15696
15863
  const rt = loadRuntimeSync({ root: ctx.directory });
15697
15864
  const browserEnabled = rt.runtime.tools.browser.enabled;
15698
15865
  const activeTools = browserEnabled ? [...CORE_TOOL_NAMES, ...BROWSER_TOOL_NAMES] : [...CORE_TOOL_NAMES];
15699
- logLifecycle(PLUGIN_NAME7, "activate", {
15866
+ logLifecycle(PLUGIN_NAME8, "activate", {
15700
15867
  directory: ctx.directory,
15701
15868
  tools: activeTools,
15702
15869
  count: activeTools.length,
@@ -15711,7 +15878,8 @@ var codeforgeToolsServer = async (ctx) => {
15711
15878
  const spawner = new ProductionSpawner({
15712
15879
  client: ctx.client,
15713
15880
  directory: ctx.directory ?? process.cwd(),
15714
- log: (level, msg, data) => safeWriteLog(PLUGIN_NAME7, { level, msg, data })
15881
+ mainRoot: ctx.directory ?? process.cwd(),
15882
+ log: (level, msg, data) => safeWriteLog(PLUGIN_NAME8, { level, msg, data })
15715
15883
  });
15716
15884
  __setContext({
15717
15885
  mainRoot: ctx.directory ?? process.cwd(),
@@ -16003,7 +16171,7 @@ var codeforgeToolsServer = async (ctx) => {
16003
16171
  }
16004
16172
  };
16005
16173
  };
16006
- var handler7 = codeforgeToolsServer;
16174
+ var handler8 = codeforgeToolsServer;
16007
16175
 
16008
16176
  // plugins/discover-spec-suggest.ts
16009
16177
  import { readFileSync as readFileSync3, readdirSync, statSync as statSync3 } from "node:fs";
@@ -16185,26 +16353,26 @@ function isValidSlug(slug) {
16185
16353
  }
16186
16354
 
16187
16355
  // plugins/discover-spec-suggest.ts
16188
- var PLUGIN_NAME8 = "discover-spec-suggest";
16189
- logLifecycle(PLUGIN_NAME8, "import", {});
16356
+ var PLUGIN_NAME9 = "discover-spec-suggest";
16357
+ logLifecycle(PLUGIN_NAME9, "import", {});
16190
16358
  var TARGET_AGENT = "codeforge";
16191
- var SESSION_CAP = 200;
16192
- var SESSION_TTL_MS = 24 * 60 * 60 * 1000;
16359
+ var SESSION_CAP2 = 200;
16360
+ var SESSION_TTL_MS2 = 24 * 60 * 60 * 1000;
16193
16361
  var MATCH_THRESHOLD = 0.15;
16194
16362
  var MAX_CANDIDATES = 3;
16195
16363
  var NUDGE_MAX_LEN = 1500;
16196
16364
  var SPECS_REL_DIR = join14(".codeforge", "specs");
16197
16365
  var sessionMap = new Map;
16198
- function pruneIfOversize() {
16199
- while (sessionMap.size > SESSION_CAP) {
16366
+ function pruneIfOversize2() {
16367
+ while (sessionMap.size > SESSION_CAP2) {
16200
16368
  const oldestKey = sessionMap.keys().next().value;
16201
16369
  if (oldestKey === undefined)
16202
16370
  break;
16203
16371
  sessionMap.delete(oldestKey);
16204
16372
  }
16205
16373
  }
16206
- function isExpired(entry, now = Date.now()) {
16207
- return now - entry.ts > SESSION_TTL_MS;
16374
+ function isExpired2(entry, now = Date.now()) {
16375
+ return now - entry.ts > SESSION_TTL_MS2;
16208
16376
  }
16209
16377
  var specIndex = [];
16210
16378
  function defaultReader(p) {
@@ -16299,7 +16467,7 @@ function loadSpecs(rootDir, opts = {}) {
16299
16467
  const dirReader = opts.dirReader ?? defaultDirReader;
16300
16468
  const dirExists = opts.dirExists ?? defaultDirExists;
16301
16469
  const statReader = opts.statReader ?? defaultStatReader;
16302
- const log6 = makePluginLogger(PLUGIN_NAME8);
16470
+ const log6 = makePluginLogger(PLUGIN_NAME9);
16303
16471
  const specsRoot = join14(rootDir, SPECS_REL_DIR);
16304
16472
  const records = [];
16305
16473
  if (!dirExists(specsRoot)) {
@@ -16427,7 +16595,7 @@ function renderCandidatesNudge(matched) {
16427
16595
  return body.slice(0, NUDGE_MAX_LEN - 4) + `
16428
16596
  …`;
16429
16597
  }
16430
- var log6 = makePluginLogger(PLUGIN_NAME8);
16598
+ var log6 = makePluginLogger(PLUGIN_NAME9);
16431
16599
  var discoverSpecSuggestServer = async (ctx) => {
16432
16600
  try {
16433
16601
  const loaded = loadSpecs(ctx.directory ?? process.cwd());
@@ -16438,19 +16606,19 @@ var discoverSpecSuggestServer = async (ctx) => {
16438
16606
  error: err instanceof Error ? err.message : String(err)
16439
16607
  });
16440
16608
  }
16441
- logLifecycle(PLUGIN_NAME8, "activate", {
16609
+ logLifecycle(PLUGIN_NAME9, "activate", {
16442
16610
  directory: ctx.directory,
16443
16611
  specs_loaded: specIndex.length,
16444
16612
  target_agent: TARGET_AGENT,
16445
16613
  match_threshold: MATCH_THRESHOLD,
16446
16614
  max_candidates: MAX_CANDIDATES,
16447
- session_cap: SESSION_CAP,
16448
- session_ttl_ms: SESSION_TTL_MS,
16615
+ session_cap: SESSION_CAP2,
16616
+ session_ttl_ms: SESSION_TTL_MS2,
16449
16617
  no_op: specIndex.length === 0
16450
16618
  });
16451
16619
  return {
16452
16620
  "chat.message": async (input, output) => {
16453
- await safeAsync(PLUGIN_NAME8, "chat.message", async () => {
16621
+ await safeAsync(PLUGIN_NAME9, "chat.message", async () => {
16454
16622
  if (specIndex.length === 0)
16455
16623
  return;
16456
16624
  const text = extractUserText(output);
@@ -16461,11 +16629,11 @@ var discoverSpecSuggestServer = async (ctx) => {
16461
16629
  if (!sid)
16462
16630
  return;
16463
16631
  sessionMap.set(sid, { text, agent, ts: Date.now() });
16464
- pruneIfOversize();
16632
+ pruneIfOversize2();
16465
16633
  });
16466
16634
  },
16467
16635
  "experimental.chat.system.transform": async (input, output) => {
16468
- await safeAsync(PLUGIN_NAME8, "experimental.chat.system.transform", async () => {
16636
+ await safeAsync(PLUGIN_NAME9, "experimental.chat.system.transform", async () => {
16469
16637
  if (specIndex.length === 0)
16470
16638
  return;
16471
16639
  if (!output || !Array.isArray(output.system))
@@ -16476,7 +16644,7 @@ var discoverSpecSuggestServer = async (ctx) => {
16476
16644
  const entry = sessionMap.get(sid);
16477
16645
  if (!entry)
16478
16646
  return;
16479
- if (isExpired(entry)) {
16647
+ if (isExpired2(entry)) {
16480
16648
  sessionMap.delete(sid);
16481
16649
  return;
16482
16650
  }
@@ -16491,7 +16659,7 @@ var discoverSpecSuggestServer = async (ctx) => {
16491
16659
  if (!nudge)
16492
16660
  return;
16493
16661
  output.system.push(nudge);
16494
- safeWriteLog(PLUGIN_NAME8, {
16662
+ safeWriteLog(PLUGIN_NAME9, {
16495
16663
  hook: "experimental.chat.system.transform",
16496
16664
  sessionID: sid,
16497
16665
  agent: entry.agent,
@@ -16503,7 +16671,7 @@ var discoverSpecSuggestServer = async (ctx) => {
16503
16671
  }
16504
16672
  };
16505
16673
  };
16506
- var handler8 = discoverSpecSuggestServer;
16674
+ var handler9 = discoverSpecSuggestServer;
16507
16675
 
16508
16676
  // plugins/kh-auto-context.ts
16509
16677
  init_kh_client();
@@ -16643,7 +16811,7 @@ class SessionScopedCache {
16643
16811
  var sharedKhCache = new SessionScopedCache;
16644
16812
 
16645
16813
  // plugins/kh-auto-context.ts
16646
- var PLUGIN_NAME9 = "kh-auto-context";
16814
+ var PLUGIN_NAME10 = "kh-auto-context";
16647
16815
  var INJECTION_MODE = "observe-only";
16648
16816
  function resolveInjectionMode(client) {
16649
16817
  return client.hasTransport() ? "system-injected" : "observe-only";
@@ -16742,7 +16910,7 @@ var CODEFORGE_CONSTRAINTS = [
16742
16910
  ];
16743
16911
  async function seedConstraints(client, log7) {
16744
16912
  if (!client.hasTransport()) {
16745
- log7?.debug?.(`[${PLUGIN_NAME9}] seedConstraints: transport 不可用,跳过`, {});
16913
+ log7?.debug?.(`[${PLUGIN_NAME10}] seedConstraints: transport 不可用,跳过`, {});
16746
16914
  return { ok: false, reason: "transport_unavailable" };
16747
16915
  }
16748
16916
  try {
@@ -16756,7 +16924,7 @@ async function seedConstraints(client, log7) {
16756
16924
  });
16757
16925
  if (result && typeof result === "object" && "ok" in result && result.ok === false) {
16758
16926
  const r = result;
16759
- log7?.warn(`[${PLUGIN_NAME9}] seedConstraints 降级`, {
16927
+ log7?.warn(`[${PLUGIN_NAME10}] seedConstraints 降级`, {
16760
16928
  reason: r.reason,
16761
16929
  message: r.message
16762
16930
  });
@@ -16766,11 +16934,11 @@ async function seedConstraints(client, log7) {
16766
16934
  message: r.message
16767
16935
  };
16768
16936
  }
16769
- log7?.info(`[${PLUGIN_NAME9}] seedConstraints: 已写入 ${CODEFORGE_CONSTRAINTS.length} 条 constraints`, { count: CODEFORGE_CONSTRAINTS.length });
16937
+ log7?.info(`[${PLUGIN_NAME10}] seedConstraints: 已写入 ${CODEFORGE_CONSTRAINTS.length} 条 constraints`, { count: CODEFORGE_CONSTRAINTS.length });
16770
16938
  return { ok: true, itemsWritten: CODEFORGE_CONSTRAINTS.length };
16771
16939
  } catch (err) {
16772
16940
  const message = err instanceof Error ? err.message : String(err);
16773
- log7?.warn(`[${PLUGIN_NAME9}] seedConstraints 失败(已静默)`, { error: message });
16941
+ log7?.warn(`[${PLUGIN_NAME10}] seedConstraints 失败(已静默)`, { error: message });
16774
16942
  return { ok: false, reason: "exception", message };
16775
16943
  }
16776
16944
  }
@@ -16785,7 +16953,7 @@ function createSystemInjectedHook(client, sessionId, log7) {
16785
16953
  });
16786
16954
  if (result && typeof result === "object" && "ok" in result && result.ok === false) {
16787
16955
  const r = result;
16788
- log7?.warn(`[${PLUGIN_NAME9}] system-injected 降级到 observe-only:${r.reason ?? "unknown"}`, {
16956
+ log7?.warn(`[${PLUGIN_NAME10}] system-injected 降级到 observe-only:${r.reason ?? "unknown"}`, {
16789
16957
  sessionId,
16790
16958
  section,
16791
16959
  preview: markdown.slice(0, 200),
@@ -16794,12 +16962,12 @@ function createSystemInjectedHook(client, sessionId, log7) {
16794
16962
  });
16795
16963
  return;
16796
16964
  }
16797
- log7?.info(`[${PLUGIN_NAME9}] system-injected: 写入 KH working_memory 成功 (${markdown.length} chars)`, {
16965
+ log7?.info(`[${PLUGIN_NAME10}] system-injected: 写入 KH working_memory 成功 (${markdown.length} chars)`, {
16798
16966
  sessionId,
16799
16967
  section
16800
16968
  });
16801
16969
  } catch (err) {
16802
- log7?.warn(`[${PLUGIN_NAME9}] system-injected 抛异常,降级到 observe-only`, {
16970
+ log7?.warn(`[${PLUGIN_NAME10}] system-injected 抛异常,降级到 observe-only`, {
16803
16971
  sessionId,
16804
16972
  section,
16805
16973
  preview: markdown.slice(0, 200),
@@ -16842,7 +17010,7 @@ async function runKhSearchAndInject(args) {
16842
17010
  });
16843
17011
  result = await racer(searchPromise, cfg.timeoutMs);
16844
17012
  } catch (err) {
16845
- log7?.warn(`[${PLUGIN_NAME9}] client.search threw (sync or async), return null`, {
17013
+ log7?.warn(`[${PLUGIN_NAME10}] client.search threw (sync or async), return null`, {
16846
17014
  query,
16847
17015
  elapsedMs: Date.now() - startedAt,
16848
17016
  sessionId: ctx.sessionId,
@@ -16852,7 +17020,7 @@ async function runKhSearchAndInject(args) {
16852
17020
  return null;
16853
17021
  }
16854
17022
  if (result === "__timeout__") {
16855
- log7?.warn(`[${PLUGIN_NAME9}] timeout`, {
17023
+ log7?.warn(`[${PLUGIN_NAME10}] timeout`, {
16856
17024
  query,
16857
17025
  ms: cfg.timeoutMs,
16858
17026
  elapsedMs: Date.now() - startedAt,
@@ -16862,7 +17030,7 @@ async function runKhSearchAndInject(args) {
16862
17030
  return null;
16863
17031
  }
16864
17032
  if (!result.ok) {
16865
- log7?.warn(`[${PLUGIN_NAME9}] kh degraded`, {
17033
+ log7?.warn(`[${PLUGIN_NAME10}] kh degraded`, {
16866
17034
  reason: result.reason,
16867
17035
  query,
16868
17036
  elapsedMs: Date.now() - startedAt,
@@ -16874,7 +17042,7 @@ async function runKhSearchAndInject(args) {
16874
17042
  const filtered = result.insights.filter((i) => (i.confidence ?? 0) >= cfg.minConfidence);
16875
17043
  if (filtered.length === 0) {
16876
17044
  opts.cache.record(query, []);
16877
- log7?.debug?.(`[${PLUGIN_NAME9}] no candidate above threshold`, {
17045
+ log7?.debug?.(`[${PLUGIN_NAME10}] no candidate above threshold`, {
16878
17046
  query,
16879
17047
  rawCount: result.insights.length,
16880
17048
  elapsedMs: Date.now() - startedAt,
@@ -16887,7 +17055,7 @@ async function runKhSearchAndInject(args) {
16887
17055
  try {
16888
17056
  await ctx.injectContext(payload.markdown);
16889
17057
  } catch (err) {
16890
- log7?.warn(`[${PLUGIN_NAME9}] injectContext threw`, {
17058
+ log7?.warn(`[${PLUGIN_NAME10}] injectContext threw`, {
16891
17059
  error: err instanceof Error ? err.message : String(err),
16892
17060
  query,
16893
17061
  sessionId: ctx.sessionId
@@ -16895,7 +17063,7 @@ async function runKhSearchAndInject(args) {
16895
17063
  }
16896
17064
  }
16897
17065
  opts.cache.record(query, filtered);
16898
- log7?.info(`[${PLUGIN_NAME9}] inject complete (${mode})`, {
17066
+ log7?.info(`[${PLUGIN_NAME10}] inject complete (${mode})`, {
16899
17067
  query,
16900
17068
  mode,
16901
17069
  candidateCount: filtered.length,
@@ -16911,18 +17079,18 @@ async function handleMessage2(raw, opts) {
16911
17079
  const log7 = ctx.log;
16912
17080
  const text = (ctx.content ?? "").trim();
16913
17081
  if (!shouldInject(text, cfg)) {
16914
- log7?.debug?.(`[${PLUGIN_NAME9}] skip (filter)`, { textLen: text.length });
17082
+ log7?.debug?.(`[${PLUGIN_NAME10}] skip (filter)`, { textLen: text.length });
16915
17083
  return;
16916
17084
  }
16917
17085
  const query = extractQuery(text);
16918
17086
  if (!query)
16919
17087
  return;
16920
17088
  if (opts.cache.shouldSkip(query)) {
16921
- log7?.debug?.(`[${PLUGIN_NAME9}] cache hit, skip`, { query });
17089
+ log7?.debug?.(`[${PLUGIN_NAME10}] cache hit, skip`, { query });
16922
17090
  return;
16923
17091
  }
16924
17092
  if (inflight2.size >= INFLIGHT_CAP) {
16925
- log7?.warn(`[${PLUGIN_NAME9}] inflight cap reached, skip`, {
17093
+ log7?.warn(`[${PLUGIN_NAME10}] inflight cap reached, skip`, {
16926
17094
  query,
16927
17095
  inflightSize: inflight2.size,
16928
17096
  cap: INFLIGHT_CAP,
@@ -16933,7 +17101,7 @@ async function handleMessage2(raw, opts) {
16933
17101
  const key = inflightKey(ctx.sessionId, query);
16934
17102
  inflight2.add(key);
16935
17103
  runKhSearchAndInject({ query, ctx, opts, mode }).catch((err) => {
16936
- log7?.warn(`[${PLUGIN_NAME9}] runKhSearchAndInject 顶层兜底捕获`, {
17104
+ log7?.warn(`[${PLUGIN_NAME10}] runKhSearchAndInject 顶层兜底捕获`, {
16937
17105
  error: err instanceof Error ? err.message : String(err),
16938
17106
  query,
16939
17107
  sessionId: ctx.sessionId
@@ -16942,13 +17110,13 @@ async function handleMessage2(raw, opts) {
16942
17110
  inflight2.delete(key);
16943
17111
  });
16944
17112
  }
16945
- logLifecycle(PLUGIN_NAME9, "import");
17113
+ logLifecycle(PLUGIN_NAME10, "import");
16946
17114
  var sharedClient2 = new KhClient;
16947
17115
  var sharedCache = new QueryCache(DEFAULT_CONFIG4.cacheTtlMs);
16948
17116
  var khAutoContextServer = async (ctx) => {
16949
- const log7 = makePluginLogger(PLUGIN_NAME9);
17117
+ const log7 = makePluginLogger(PLUGIN_NAME10);
16950
17118
  const runtimeMode = resolveInjectionMode(sharedClient2);
16951
- logLifecycle(PLUGIN_NAME9, "activate", {
17119
+ logLifecycle(PLUGIN_NAME10, "activate", {
16952
17120
  directory: ctx.directory,
16953
17121
  minConfidence: DEFAULT_CONFIG4.minConfidence,
16954
17122
  timeoutMs: DEFAULT_CONFIG4.timeoutMs,
@@ -16962,7 +17130,7 @@ var khAutoContextServer = async (ctx) => {
16962
17130
  });
16963
17131
  return {
16964
17132
  "chat.message": async (input, output) => {
16965
- await safeAsync(PLUGIN_NAME9, "chat.message", async () => {
17133
+ await safeAsync(PLUGIN_NAME10, "chat.message", async () => {
16966
17134
  const text = extractUserText(output);
16967
17135
  if (!text)
16968
17136
  return;
@@ -16982,7 +17150,7 @@ var khAutoContextServer = async (ctx) => {
16982
17150
  });
16983
17151
  },
16984
17152
  event: async ({ event }) => {
16985
- await safeAsync(PLUGIN_NAME9, "event", async () => {
17153
+ await safeAsync(PLUGIN_NAME10, "event", async () => {
16986
17154
  const e = event;
16987
17155
  if (e.type !== "session.idle")
16988
17156
  return;
@@ -16991,14 +17159,14 @@ var khAutoContextServer = async (ctx) => {
16991
17159
  if (typeof sid !== "string" || !sid)
16992
17160
  return;
16993
17161
  sharedKhCache.onSessionEnd(sid);
16994
- log7.debug?.(`[${PLUGIN_NAME9}] session.idle: cleared shared cache`, {
17162
+ log7.debug?.(`[${PLUGIN_NAME10}] session.idle: cleared shared cache`, {
16995
17163
  sessionID: sid
16996
17164
  });
16997
17165
  });
16998
17166
  }
16999
17167
  };
17000
17168
  };
17001
- var handler9 = khAutoContextServer;
17169
+ var handler10 = khAutoContextServer;
17002
17170
 
17003
17171
  // lib/condenser.ts
17004
17172
  var DEFAULT_CONDENSE = {
@@ -17116,8 +17284,8 @@ async function condense(input, opts = {}) {
17116
17284
  }
17117
17285
 
17118
17286
  // plugins/kh-reminder.ts
17119
- var PLUGIN_NAME10 = "kh-reminder";
17120
- logLifecycle(PLUGIN_NAME10, "import", {});
17287
+ var PLUGIN_NAME11 = "kh-reminder";
17288
+ logLifecycle(PLUGIN_NAME11, "import", {});
17121
17289
  var TRIGGER_WORDS_ZH = [
17122
17290
  "怎么",
17123
17291
  "怎样",
@@ -17271,7 +17439,7 @@ function handleObserve(raw, log7) {
17271
17439
  const result = evaluate(ctx);
17272
17440
  if (result.triggered && result.reason) {
17273
17441
  const reasonStr = formatReason(result.reason);
17274
- log7?.info(`[${PLUGIN_NAME10}] ⚡ KH 提醒(observe-only) · session=${result.sessionId} · ${reasonStr}`, {
17442
+ log7?.info(`[${PLUGIN_NAME11}] ⚡ KH 提醒(observe-only) · session=${result.sessionId} · ${reasonStr}`, {
17275
17443
  sessionId: result.sessionId,
17276
17444
  reason: result.reason,
17277
17445
  suggestion: "调用 smart_search 查项目历史;完成后用 save_chat_insight 沉淀"
@@ -17279,7 +17447,7 @@ function handleObserve(raw, log7) {
17279
17447
  }
17280
17448
  return result;
17281
17449
  } catch (err) {
17282
- log7?.warn(`[${PLUGIN_NAME10}] evaluate 异常(已隔离)`, {
17450
+ log7?.warn(`[${PLUGIN_NAME11}] evaluate 异常(已隔离)`, {
17283
17451
  error: err instanceof Error ? err.message : String(err)
17284
17452
  });
17285
17453
  return null;
@@ -17295,9 +17463,9 @@ function formatReason(r) {
17295
17463
  return `no-search-in-recent-rounds (last ${r.rounds})`;
17296
17464
  }
17297
17465
  }
17298
- var log7 = makePluginLogger(PLUGIN_NAME10);
17466
+ var log7 = makePluginLogger(PLUGIN_NAME11);
17299
17467
  var khReminderServer = async (ctx) => {
17300
- logLifecycle(PLUGIN_NAME10, "activate", {
17468
+ logLifecycle(PLUGIN_NAME11, "activate", {
17301
17469
  directory: ctx.directory,
17302
17470
  threshold: DEFAULT_THRESHOLD,
17303
17471
  cooldown_ms: COOLDOWN_MS,
@@ -17305,7 +17473,7 @@ var khReminderServer = async (ctx) => {
17305
17473
  });
17306
17474
  return {
17307
17475
  "experimental.chat.messages.transform": async (_input, output) => {
17308
- await safeAsync(PLUGIN_NAME10, "experimental.chat.messages.transform", async () => {
17476
+ await safeAsync(PLUGIN_NAME11, "experimental.chat.messages.transform", async () => {
17309
17477
  const list = output.messages;
17310
17478
  if (!Array.isArray(list) || list.length === 0)
17311
17479
  return;
@@ -17324,7 +17492,7 @@ var khReminderServer = async (ctx) => {
17324
17492
  const result = handleObserve({ messages: flat, sessionId }, log7);
17325
17493
  if (!result)
17326
17494
  return;
17327
- safeWriteLog(PLUGIN_NAME10, {
17495
+ safeWriteLog(PLUGIN_NAME11, {
17328
17496
  hook: "experimental.chat.messages.transform",
17329
17497
  mode: "observe-only",
17330
17498
  sessionId: result.sessionId,
@@ -17337,7 +17505,7 @@ var khReminderServer = async (ctx) => {
17337
17505
  }
17338
17506
  };
17339
17507
  };
17340
- var handler10 = khReminderServer;
17508
+ var handler11 = khReminderServer;
17341
17509
 
17342
17510
  // lib/memories.ts
17343
17511
  import { promises as fs15 } from "node:fs";
@@ -17538,7 +17706,7 @@ function bagOfWordsScore(query, doc) {
17538
17706
  }
17539
17707
 
17540
17708
  // plugins/memories-context.ts
17541
- var PLUGIN_NAME11 = "memories-context";
17709
+ var PLUGIN_NAME12 = "memories-context";
17542
17710
  var INJECTION_MODE2 = "observe-only";
17543
17711
  var DEFAULT_CONFIG5 = {
17544
17712
  minTextLength: 8,
@@ -17636,14 +17804,14 @@ async function handleMessage3(raw, opts) {
17636
17804
  return await handleDirective(dir, ctx, opts.memCfg, log8);
17637
17805
  }
17638
17806
  if (!shouldRecall(text, cfg)) {
17639
- log8?.debug?.(`[${PLUGIN_NAME11}] skip (filter)`, { textLen: text.length });
17807
+ log8?.debug?.(`[${PLUGIN_NAME12}] skip (filter)`, { textLen: text.length });
17640
17808
  return { kind: "noop", reason: "filtered", mode: INJECTION_MODE2 };
17641
17809
  }
17642
17810
  const query = extractQuery2(text);
17643
17811
  if (!query)
17644
17812
  return { kind: "noop", reason: "empty_query", mode: INJECTION_MODE2 };
17645
17813
  if (cache2.shouldSkip(query)) {
17646
- log8?.debug?.(`[${PLUGIN_NAME11}] cache hit`, { query });
17814
+ log8?.debug?.(`[${PLUGIN_NAME12}] cache hit`, { query });
17647
17815
  return { kind: "noop", reason: "cache", mode: INJECTION_MODE2 };
17648
17816
  }
17649
17817
  const racer = opts.scheduler?.raceTimeout ?? (async (p, ms) => {
@@ -17668,7 +17836,7 @@ async function handleMessage3(raw, opts) {
17668
17836
  }, opts.memCfg);
17669
17837
  const result = await racer(injectPromise, cfg.timeoutMs);
17670
17838
  if (result === "__timeout__") {
17671
- log8?.warn(`[${PLUGIN_NAME11}] timeout`, { query, ms: cfg.timeoutMs });
17839
+ log8?.warn(`[${PLUGIN_NAME12}] timeout`, { query, ms: cfg.timeoutMs });
17672
17840
  cache2.record(query, 0);
17673
17841
  return { kind: "noop", reason: "timeout", mode: INJECTION_MODE2 };
17674
17842
  }
@@ -17680,13 +17848,13 @@ async function handleMessage3(raw, opts) {
17680
17848
  try {
17681
17849
  await ctx.injectContext(result.text);
17682
17850
  } catch (err) {
17683
- log8?.warn(`[${PLUGIN_NAME11}] injectContext threw`, {
17851
+ log8?.warn(`[${PLUGIN_NAME12}] injectContext threw`, {
17684
17852
  error: err instanceof Error ? err.message : String(err)
17685
17853
  });
17686
17854
  }
17687
17855
  }
17688
17856
  cache2.record(query, result.recalled);
17689
- log8?.info(`[${PLUGIN_NAME11}] observe-only: ${result.used}/${result.recalled} memories ready`, { query, mode: INJECTION_MODE2 });
17857
+ log8?.info(`[${PLUGIN_NAME12}] observe-only: ${result.used}/${result.recalled} memories ready`, { query, mode: INJECTION_MODE2 });
17690
17858
  return { kind: "injected", payload: result, mode: INJECTION_MODE2 };
17691
17859
  }
17692
17860
  async function handleDirective(dir, ctx, memCfg, log8) {
@@ -17695,14 +17863,14 @@ async function handleDirective(dir, ctx, memCfg, log8) {
17695
17863
  if (r.ok) {
17696
17864
  const target = r.written_to === "kh" ? "KH" : "本地";
17697
17865
  await safeReply(ctx, `\uD83E\uDDE0 已记住(${dir.scope} / ${target},id=${r.id})`);
17698
- log8?.info(`[${PLUGIN_NAME11}] /remember ok`, {
17866
+ log8?.info(`[${PLUGIN_NAME12}] /remember ok`, {
17699
17867
  scope: dir.scope,
17700
17868
  id: r.id,
17701
17869
  mode: INJECTION_MODE2
17702
17870
  });
17703
17871
  } else {
17704
17872
  await safeReply(ctx, `❌ 记忆失败:${r.error ?? "unknown"}`);
17705
- log8?.warn(`[${PLUGIN_NAME11}] /remember failed`, { error: r.error });
17873
+ log8?.warn(`[${PLUGIN_NAME12}] /remember failed`, { error: r.error });
17706
17874
  }
17707
17875
  return { kind: "remembered", result: r, mode: INJECTION_MODE2 };
17708
17876
  }
@@ -17733,22 +17901,22 @@ async function safeReply(ctx, text) {
17733
17901
  await ctx.reply(text);
17734
17902
  } catch {}
17735
17903
  }
17736
- logLifecycle(PLUGIN_NAME11, "import");
17904
+ logLifecycle(PLUGIN_NAME12, "import");
17737
17905
  var sharedCache2 = new QueryCache2(DEFAULT_CONFIG5.cacheTtlMs);
17738
17906
  function buildMemCfg(directory) {
17739
17907
  return { projectRoot: directory };
17740
17908
  }
17741
17909
  var memoriesContextServer = async (ctx) => {
17742
- const log8 = makePluginLogger(PLUGIN_NAME11);
17910
+ const log8 = makePluginLogger(PLUGIN_NAME12);
17743
17911
  const memCfg = buildMemCfg(ctx.directory);
17744
- logLifecycle(PLUGIN_NAME11, "activate", {
17912
+ logLifecycle(PLUGIN_NAME12, "activate", {
17745
17913
  directory: ctx.directory,
17746
17914
  projectRoot: memCfg.projectRoot,
17747
17915
  mode: INJECTION_MODE2
17748
17916
  });
17749
17917
  return {
17750
17918
  "chat.message": async (input, output) => {
17751
- await safeAsync(PLUGIN_NAME11, "chat.message", async () => {
17919
+ await safeAsync(PLUGIN_NAME12, "chat.message", async () => {
17752
17920
  const text = extractUserText(output);
17753
17921
  if (!text)
17754
17922
  return;
@@ -17774,17 +17942,17 @@ var memoriesContextServer = async (ctx) => {
17774
17942
  }
17775
17943
  };
17776
17944
  };
17777
- var handler11 = memoriesContextServer;
17945
+ var handler12 = memoriesContextServer;
17778
17946
 
17779
17947
  // plugins/model-fallback.ts
17780
- var PLUGIN_NAME12 = "model-fallback";
17948
+ var PLUGIN_NAME13 = "model-fallback";
17781
17949
  var state = {
17782
17950
  config: null,
17783
17951
  configPath: null,
17784
17952
  warnings: [],
17785
17953
  error: null
17786
17954
  };
17787
- logLifecycle(PLUGIN_NAME12, "import");
17955
+ logLifecycle(PLUGIN_NAME13, "import");
17788
17956
  function loadOnce(root) {
17789
17957
  if (state.config !== null || state.error !== null)
17790
17958
  return;
@@ -17794,11 +17962,11 @@ function loadOnce(root) {
17794
17962
  state.configPath = r.path ?? null;
17795
17963
  state.warnings = r.warnings;
17796
17964
  if (r.warnings.length > 0) {
17797
- safeWriteLog(PLUGIN_NAME12, { phase: "load.warnings", warnings: r.warnings });
17965
+ safeWriteLog(PLUGIN_NAME13, { phase: "load.warnings", warnings: r.warnings });
17798
17966
  }
17799
17967
  } else {
17800
17968
  state.error = r.error ?? "unknown_load_error";
17801
- safeWriteLog(PLUGIN_NAME12, { phase: "load.failed", error: state.error, path: r.path });
17969
+ safeWriteLog(PLUGIN_NAME13, { phase: "load.failed", error: state.error, path: r.path });
17802
17970
  }
17803
17971
  }
17804
17972
  var MODEL_ERR_PATTERNS = [
@@ -17843,9 +18011,9 @@ fallback 链已用尽:${meta.chain.join(" → ")}`;
17843
18011
  完整链:${meta.chain.join(" → ")}`;
17844
18012
  }
17845
18013
  var modelFallbackServer = async (ctx) => {
17846
- const log8 = makePluginLogger(PLUGIN_NAME12);
18014
+ const log8 = makePluginLogger(PLUGIN_NAME13);
17847
18015
  loadOnce(ctx.directory ?? process.cwd());
17848
- logLifecycle(PLUGIN_NAME12, "activate", {
18016
+ logLifecycle(PLUGIN_NAME13, "activate", {
17849
18017
  directory: ctx.directory,
17850
18018
  config_path: state.configPath,
17851
18019
  config_loaded: state.config !== null,
@@ -17855,7 +18023,7 @@ var modelFallbackServer = async (ctx) => {
17855
18023
  });
17856
18024
  return {
17857
18025
  "chat.params": async (input, output) => {
17858
- await safeAsync(PLUGIN_NAME12, "chat.params", async () => {
18026
+ await safeAsync(PLUGIN_NAME13, "chat.params", async () => {
17859
18027
  if (!state.config)
17860
18028
  return;
17861
18029
  const agent = input.agent;
@@ -17876,7 +18044,7 @@ var modelFallbackServer = async (ctx) => {
17876
18044
  next: meta.next_fallback,
17877
18045
  source: meta.source
17878
18046
  };
17879
- safeWriteLog(PLUGIN_NAME12, {
18047
+ safeWriteLog(PLUGIN_NAME13, {
17880
18048
  hook: "chat.params",
17881
18049
  agent,
17882
18050
  model: currentModel,
@@ -17886,7 +18054,7 @@ var modelFallbackServer = async (ctx) => {
17886
18054
  });
17887
18055
  },
17888
18056
  event: async ({ event }) => {
17889
- await safeAsync(PLUGIN_NAME12, "event", async () => {
18057
+ await safeAsync(PLUGIN_NAME13, "event", async () => {
17890
18058
  if (!state.config)
17891
18059
  return;
17892
18060
  const e = event;
@@ -17902,8 +18070,8 @@ var modelFallbackServer = async (ctx) => {
17902
18070
  const model = props.model ?? "unknown/unknown";
17903
18071
  const meta = buildFallbackMeta(state.config, agent, model);
17904
18072
  const suggestion = meta ? buildSuggestion(meta, String(message)) : `⚠️ ${agent}/${model} 失败:${message}`;
17905
- log8.warn(`[${PLUGIN_NAME12}] ${suggestion}`);
17906
- safeWriteLog(PLUGIN_NAME12, {
18073
+ log8.warn(`[${PLUGIN_NAME13}] ${suggestion}`);
18074
+ safeWriteLog(PLUGIN_NAME13, {
17907
18075
  hook: "event.error",
17908
18076
  eventType: e.type,
17909
18077
  agent,
@@ -17915,7 +18083,7 @@ var modelFallbackServer = async (ctx) => {
17915
18083
  }
17916
18084
  };
17917
18085
  };
17918
- var handler12 = modelFallbackServer;
18086
+ var handler13 = modelFallbackServer;
17919
18087
 
17920
18088
  // plugins/subtask-heartbeat.ts
17921
18089
  import { promises as fsPromises } from "node:fs";
@@ -17929,8 +18097,8 @@ var sweepExpiredSessionParents2 = sweepExpiredSessionParents;
17929
18097
  var _bulkInjectSessionParentMap2 = _bulkInjectSessionParentMap;
17930
18098
  var _capSessionParentMap2 = _capSessionParentMap;
17931
18099
  var _setPersistRootForTests2 = _setPersistRootForTests;
17932
- var PLUGIN_NAME13 = "subtask-heartbeat";
17933
- logLifecycle(PLUGIN_NAME13, "import", {});
18100
+ var PLUGIN_NAME14 = "subtask-heartbeat";
18101
+ logLifecycle(PLUGIN_NAME14, "import", {});
17934
18102
  var HEARTBEAT_INTERVAL_MS2 = 30000;
17935
18103
  var HEARTBEAT_DEBOUNCE_MS = 25000;
17936
18104
  var TOAST_DURATION_MS2 = 5000;
@@ -18272,9 +18440,9 @@ async function showToast2(client, payload, log8) {
18272
18440
  return false;
18273
18441
  }
18274
18442
  }
18275
- var log8 = makePluginLogger(PLUGIN_NAME13);
18443
+ var log8 = makePluginLogger(PLUGIN_NAME14);
18276
18444
  var subtaskHeartbeatServer = async (ctx) => {
18277
- logLifecycle(PLUGIN_NAME13, "activate", {
18445
+ logLifecycle(PLUGIN_NAME14, "activate", {
18278
18446
  directory: ctx.directory,
18279
18447
  intervalMs: HEARTBEAT_INTERVAL_MS2
18280
18448
  });
@@ -18291,7 +18459,7 @@ var subtaskHeartbeatServer = async (ctx) => {
18291
18459
  }));
18292
18460
  _bulkInjectSessionParentMap2(entries);
18293
18461
  const cappedOut = _capSessionParentMap2();
18294
- safeWriteLog(PLUGIN_NAME13, {
18462
+ safeWriteLog(PLUGIN_NAME14, {
18295
18463
  hook: "activate",
18296
18464
  type: "parent-map.restore",
18297
18465
  restored: restored.size,
@@ -18304,14 +18472,14 @@ var subtaskHeartbeatServer = async (ctx) => {
18304
18472
  });
18305
18473
  }
18306
18474
  const interval = setInterval(() => {
18307
- safeAsync(PLUGIN_NAME13, "interval", async () => {
18475
+ safeAsync(PLUGIN_NAME14, "interval", async () => {
18308
18476
  const swept = sweepExpiredPendingTasks();
18309
18477
  if (swept > 0) {
18310
- safeWriteLog(PLUGIN_NAME13, { hook: "interval", pending_task_swept: swept });
18478
+ safeWriteLog(PLUGIN_NAME14, { hook: "interval", pending_task_swept: swept });
18311
18479
  }
18312
18480
  const sweptParents = sweepExpiredSessionParents2();
18313
18481
  if (sweptParents > 0) {
18314
- safeWriteLog(PLUGIN_NAME13, { hook: "interval", session_parent_swept: sweptParents });
18482
+ safeWriteLog(PLUGIN_NAME14, { hook: "interval", session_parent_swept: sweptParents });
18315
18483
  }
18316
18484
  const beats = pickHeartbeats();
18317
18485
  if (beats.length === 0)
@@ -18319,7 +18487,7 @@ var subtaskHeartbeatServer = async (ctx) => {
18319
18487
  for (const r of beats) {
18320
18488
  const t = buildHeartbeatToast(r);
18321
18489
  const sent = await showToast2(client, t, log8);
18322
- safeWriteLog(PLUGIN_NAME13, {
18490
+ safeWriteLog(PLUGIN_NAME14, {
18323
18491
  hook: "interval",
18324
18492
  child: r.childID,
18325
18493
  parent: r.parentID,
@@ -18336,7 +18504,7 @@ var subtaskHeartbeatServer = async (ctx) => {
18336
18504
  }
18337
18505
  return {
18338
18506
  event: async ({ event }) => {
18339
- await safeAsync(PLUGIN_NAME13, "event", async () => {
18507
+ await safeAsync(PLUGIN_NAME14, "event", async () => {
18340
18508
  try {
18341
18509
  if (detectUnparsedParentID(event) && _parentParseFailLogged < PARENT_PARSE_FAIL_MAX_LOG) {
18342
18510
  _parentParseFailLogged++;
@@ -18344,7 +18512,7 @@ var subtaskHeartbeatServer = async (ctx) => {
18344
18512
  sample_count: _parentParseFailLogged,
18345
18513
  hint: "如频繁出现请检查 plugins/subtask-heartbeat.ts::extractCreatedChild 是否适配新 SDK"
18346
18514
  });
18347
- safeWriteLog(PLUGIN_NAME13, {
18515
+ safeWriteLog(PLUGIN_NAME14, {
18348
18516
  hook: "event",
18349
18517
  type: "session.created.unparsed-parent-id",
18350
18518
  sample_count: _parentParseFailLogged
@@ -18367,7 +18535,7 @@ var subtaskHeartbeatServer = async (ctx) => {
18367
18535
  agent: pending?.agent ?? created.agent,
18368
18536
  description: pending?.description ?? null
18369
18537
  });
18370
- safeWriteLog(PLUGIN_NAME13, {
18538
+ safeWriteLog(PLUGIN_NAME14, {
18371
18539
  hook: "event",
18372
18540
  type: "session.created",
18373
18541
  child: created.childID,
@@ -18378,7 +18546,7 @@ var subtaskHeartbeatServer = async (ctx) => {
18378
18546
  });
18379
18547
  const startToast = buildStartToast(record);
18380
18548
  const sent = await showToast2(client, { ...startToast, duration: START_TOAST_DURATION_MS }, log8);
18381
- safeWriteLog(PLUGIN_NAME13, {
18549
+ safeWriteLog(PLUGIN_NAME14, {
18382
18550
  hook: "event",
18383
18551
  type: "session.created.toast",
18384
18552
  child: created.childID,
@@ -18398,7 +18566,7 @@ var subtaskHeartbeatServer = async (ctx) => {
18398
18566
  if (r) {
18399
18567
  const t = buildEndToast(r, ended.type);
18400
18568
  const sent = await showToast2(client, t, log8);
18401
- safeWriteLog(PLUGIN_NAME13, {
18569
+ safeWriteLog(PLUGIN_NAME14, {
18402
18570
  hook: "event",
18403
18571
  type: ended.type,
18404
18572
  child: r.childID,
@@ -18418,12 +18586,12 @@ var subtaskHeartbeatServer = async (ctx) => {
18418
18586
  const isTaskTool = input?.tool === "task";
18419
18587
  if (inflight3.size === 0 && !isTaskTool)
18420
18588
  return;
18421
- await safeAsync(PLUGIN_NAME13, "tool.execute.before", async () => {
18589
+ await safeAsync(PLUGIN_NAME14, "tool.execute.before", async () => {
18422
18590
  if (!input || typeof input.sessionID !== "string" || typeof input.tool !== "string")
18423
18591
  return;
18424
18592
  if (isTaskTool) {
18425
18593
  const args = output?.args ?? null;
18426
- safeWriteLog(PLUGIN_NAME13, {
18594
+ safeWriteLog(PLUGIN_NAME14, {
18427
18595
  hook: "tool.execute.before.task",
18428
18596
  sessionID: input.sessionID,
18429
18597
  args_keys: args && typeof args === "object" ? Object.keys(args) : null,
@@ -18435,7 +18603,7 @@ var subtaskHeartbeatServer = async (ctx) => {
18435
18603
  agent: extracted.subagentType,
18436
18604
  description: extracted.description
18437
18605
  });
18438
- safeWriteLog(PLUGIN_NAME13, {
18606
+ safeWriteLog(PLUGIN_NAME14, {
18439
18607
  hook: "tool.execute.before.task.enqueued",
18440
18608
  parent: input.sessionID,
18441
18609
  agent: extracted.subagentType,
@@ -18463,7 +18631,7 @@ var subtaskHeartbeatServer = async (ctx) => {
18463
18631
  "tool.execute.after": async (input, _output) => {
18464
18632
  if (inflight3.size === 0)
18465
18633
  return;
18466
- await safeAsync(PLUGIN_NAME13, "tool.execute.after", async () => {
18634
+ await safeAsync(PLUGIN_NAME14, "tool.execute.after", async () => {
18467
18635
  if (!input || typeof input.sessionID !== "string" || typeof input.tool !== "string")
18468
18636
  return;
18469
18637
  const rec = inflight3.get(input.sessionID);
@@ -18486,11 +18654,11 @@ var subtaskHeartbeatServer = async (ctx) => {
18486
18654
  }
18487
18655
  };
18488
18656
  };
18489
- var handler13 = subtaskHeartbeatServer;
18657
+ var handler14 = subtaskHeartbeatServer;
18490
18658
 
18491
18659
  // plugins/parallel-status.ts
18492
- var PLUGIN_NAME14 = "parallel-status";
18493
- logLifecycle(PLUGIN_NAME14, "import");
18660
+ var PLUGIN_NAME15 = "parallel-status";
18661
+ logLifecycle(PLUGIN_NAME15, "import");
18494
18662
  var ID_MAX_LEN = 16;
18495
18663
  var ID_KEEP_LEN = 13;
18496
18664
  function shortId(s) {
@@ -18522,8 +18690,8 @@ function formatInflightMarkdown(snapshot, now = Date.now()) {
18522
18690
  `);
18523
18691
  }
18524
18692
  var parallelStatusServer = async (ctx) => {
18525
- const log9 = makePluginLogger(PLUGIN_NAME14);
18526
- logLifecycle(PLUGIN_NAME14, "activate", { directory: ctx.directory });
18693
+ const log9 = makePluginLogger(PLUGIN_NAME15);
18694
+ logLifecycle(PLUGIN_NAME15, "activate", { directory: ctx.directory });
18527
18695
  return {
18528
18696
  "command.execute.before": async (input, output) => {
18529
18697
  try {
@@ -18542,23 +18710,23 @@ var parallelStatusServer = async (ctx) => {
18542
18710
  synthetic: false
18543
18711
  });
18544
18712
  }
18545
- log9.info(`[${PLUGIN_NAME14}] 已回写 ${snapshot.length} 条 inflight`);
18713
+ log9.info(`[${PLUGIN_NAME15}] 已回写 ${snapshot.length} 条 inflight`);
18546
18714
  } catch (err) {
18547
- log9.error(`[${PLUGIN_NAME14}] command.execute.before 异常(已隔离)`, {
18715
+ log9.error(`[${PLUGIN_NAME15}] command.execute.before 异常(已隔离)`, {
18548
18716
  error: err instanceof Error ? err.message : String(err)
18549
18717
  });
18550
18718
  }
18551
18719
  }
18552
18720
  };
18553
18721
  };
18554
- var handler14 = parallelStatusServer;
18722
+ var handler15 = parallelStatusServer;
18555
18723
 
18556
18724
  // plugins/parallel-tool-nudge.ts
18557
18725
  import { readFileSync as readFileSync4, readdirSync as readdirSync2, statSync as statSync4 } from "node:fs";
18558
18726
  import { join as join16 } from "node:path";
18559
18727
  import { homedir as homedir6 } from "node:os";
18560
- var PLUGIN_NAME15 = "parallel-tool-nudge";
18561
- logLifecycle(PLUGIN_NAME15, "import", {});
18728
+ var PLUGIN_NAME16 = "parallel-tool-nudge";
18729
+ logLifecycle(PLUGIN_NAME16, "import", {});
18562
18730
  var PARALLEL_SAFE_TOOLS = [
18563
18731
  "read",
18564
18732
  "smart_search",
@@ -18620,7 +18788,7 @@ function loadAgentToolsMap(rootDir, opts = {}) {
18620
18788
  const result = new Map;
18621
18789
  const safeSet = new Set(PARALLEL_SAFE_TOOLS);
18622
18790
  const unionTools = new Set;
18623
- const log9 = makePluginLogger(PLUGIN_NAME15);
18791
+ const log9 = makePluginLogger(PLUGIN_NAME16);
18624
18792
  for (const dir of candidateDirs) {
18625
18793
  if (!dirExists(dir))
18626
18794
  continue;
@@ -18670,33 +18838,33 @@ function loadAgentToolsMap(rootDir, opts = {}) {
18670
18838
  result.set(DEFAULT_AGENT_KEY, Array.from(unionTools));
18671
18839
  return result;
18672
18840
  }
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;
18841
+ var sessionAgentMap2 = new Map;
18842
+ var SESSION_CAP3 = 200;
18843
+ var SESSION_TTL_MS3 = 24 * 60 * 60 * 1000;
18844
+ function pruneIfOversize3() {
18845
+ while (sessionAgentMap2.size > SESSION_CAP3) {
18846
+ const oldestKey = sessionAgentMap2.keys().next().value;
18679
18847
  if (oldestKey === undefined)
18680
18848
  break;
18681
- sessionAgentMap.delete(oldestKey);
18849
+ sessionAgentMap2.delete(oldestKey);
18682
18850
  }
18683
18851
  }
18684
- function isExpired2(entry, now) {
18685
- return now - entry.ts > SESSION_TTL_MS2;
18852
+ function isExpired3(entry, now) {
18853
+ return now - entry.ts > SESSION_TTL_MS3;
18686
18854
  }
18687
18855
  function resolveAgent(sessionID, nowFn = Date.now) {
18688
18856
  if (!sessionID)
18689
18857
  return "unknown";
18690
- const entry = sessionAgentMap.get(sessionID);
18858
+ const entry = sessionAgentMap2.get(sessionID);
18691
18859
  if (!entry)
18692
18860
  return "unknown";
18693
18861
  const now = nowFn();
18694
- if (isExpired2(entry, now)) {
18695
- sessionAgentMap.delete(sessionID);
18862
+ if (isExpired3(entry, now)) {
18863
+ sessionAgentMap2.delete(sessionID);
18696
18864
  return "unknown";
18697
18865
  }
18698
- sessionAgentMap.delete(sessionID);
18699
- sessionAgentMap.set(sessionID, entry);
18866
+ sessionAgentMap2.delete(sessionID);
18867
+ sessionAgentMap2.set(sessionID, entry);
18700
18868
  return entry.agent;
18701
18869
  }
18702
18870
  function renderNudge(agent, tools) {
@@ -18728,7 +18896,7 @@ This directive is re-injected every turn — it is not optional advice.
18728
18896
  return body.slice(0, NUDGE_MAX_LEN2 - 4) + `
18729
18897
  …`;
18730
18898
  }
18731
- var log9 = makePluginLogger(PLUGIN_NAME15);
18899
+ var log9 = makePluginLogger(PLUGIN_NAME16);
18732
18900
  var parallelToolNudgeServer = async (ctx) => {
18733
18901
  try {
18734
18902
  const loaded = loadAgentToolsMap(ctx.directory ?? process.cwd());
@@ -18748,30 +18916,30 @@ var parallelToolNudgeServer = async (ctx) => {
18748
18916
  });
18749
18917
  }
18750
18918
  const defaultTools = agentToolsMap.get(DEFAULT_AGENT_KEY) ?? [];
18751
- logLifecycle(PLUGIN_NAME15, "activate", {
18919
+ logLifecycle(PLUGIN_NAME16, "activate", {
18752
18920
  directory: ctx.directory,
18753
18921
  real_agents_loaded: realAgentCount,
18754
18922
  default_tools_union: defaultTools,
18755
18923
  parallel_safe_whitelist: PARALLEL_SAFE_TOOLS,
18756
- session_cap: SESSION_CAP2,
18757
- session_ttl_ms: SESSION_TTL_MS2,
18924
+ session_cap: SESSION_CAP3,
18925
+ session_ttl_ms: SESSION_TTL_MS3,
18758
18926
  no_op: agentToolsMap.size === 0
18759
18927
  });
18760
18928
  return {
18761
18929
  "chat.params": async (input, _output) => {
18762
- await safeAsync(PLUGIN_NAME15, "chat.params", async () => {
18930
+ await safeAsync(PLUGIN_NAME16, "chat.params", async () => {
18763
18931
  if (agentToolsMap.size === 0)
18764
18932
  return;
18765
18933
  const sid = input?.sessionID;
18766
18934
  const agent = input?.agent;
18767
18935
  if (!sid || !agent)
18768
18936
  return;
18769
- sessionAgentMap.set(sid, { agent, ts: Date.now() });
18770
- pruneIfOversize2();
18937
+ sessionAgentMap2.set(sid, { agent, ts: Date.now() });
18938
+ pruneIfOversize3();
18771
18939
  });
18772
18940
  },
18773
18941
  "experimental.chat.system.transform": async (input, output) => {
18774
- await safeAsync(PLUGIN_NAME15, "experimental.chat.system.transform", async () => {
18942
+ await safeAsync(PLUGIN_NAME16, "experimental.chat.system.transform", async () => {
18775
18943
  if (agentToolsMap.size === 0)
18776
18944
  return;
18777
18945
  if (!output || !Array.isArray(output.system))
@@ -18781,7 +18949,7 @@ var parallelToolNudgeServer = async (ctx) => {
18781
18949
  const tools = agent !== "unknown" ? agentToolsMap.get(agent) ?? agentToolsMap.get(DEFAULT_AGENT_KEY) ?? [] : agentToolsMap.get(DEFAULT_AGENT_KEY) ?? [];
18782
18950
  const nudge = renderNudge(agent, tools);
18783
18951
  output.system.push(nudge);
18784
- safeWriteLog(PLUGIN_NAME15, {
18952
+ safeWriteLog(PLUGIN_NAME16, {
18785
18953
  hook: "experimental.chat.system.transform",
18786
18954
  sessionID: sid,
18787
18955
  agent,
@@ -18793,11 +18961,11 @@ var parallelToolNudgeServer = async (ctx) => {
18793
18961
  }
18794
18962
  };
18795
18963
  };
18796
- var handler15 = parallelToolNudgeServer;
18964
+ var handler16 = parallelToolNudgeServer;
18797
18965
 
18798
18966
  // plugins/pwsh-utf8.ts
18799
- var PLUGIN_NAME16 = "pwsh-utf8";
18800
- logLifecycle(PLUGIN_NAME16, "import", {});
18967
+ var PLUGIN_NAME17 = "pwsh-utf8";
18968
+ logLifecycle(PLUGIN_NAME17, "import", {});
18801
18969
  var PRELUDE = "chcp 65001 *> $null; " + "[Console]::OutputEncoding = [System.Text.UTF8Encoding]::new(); " + "$OutputEncoding = [System.Text.UTF8Encoding]::new(); ";
18802
18970
  function prependUtf8Prelude(command) {
18803
18971
  if (typeof command !== "string")
@@ -18810,15 +18978,15 @@ function prependUtf8Prelude(command) {
18810
18978
  return command;
18811
18979
  return PRELUDE + command;
18812
18980
  }
18813
- var handler16 = async (_ctx3) => {
18981
+ var handler17 = async (_ctx3) => {
18814
18982
  const enabled = process.platform === "win32" && process.env.CODEFORGE_DISABLE_PWSH_UTF8 !== "1";
18815
18983
  const reason = enabled ? "win32" : process.platform !== "win32" ? "non-win32" : "disabled-by-env";
18816
- logLifecycle(PLUGIN_NAME16, "activate", { enabled, platform: process.platform, reason });
18984
+ logLifecycle(PLUGIN_NAME17, "activate", { enabled, platform: process.platform, reason });
18817
18985
  if (!enabled)
18818
18986
  return {};
18819
18987
  return {
18820
18988
  "tool.execute.before": async (input, output) => {
18821
- await safeAsync(PLUGIN_NAME16, "tool.execute.before", async () => {
18989
+ await safeAsync(PLUGIN_NAME17, "tool.execute.before", async () => {
18822
18990
  if (input.tool !== "bash")
18823
18991
  return;
18824
18992
  const args = output.args ?? {};
@@ -18827,7 +18995,7 @@ var handler16 = async (_ctx3) => {
18827
18995
  if (next !== undefined && next !== original) {
18828
18996
  args["command"] = next;
18829
18997
  output.args = args;
18830
- safeWriteLog(PLUGIN_NAME16, {
18998
+ safeWriteLog(PLUGIN_NAME17, {
18831
18999
  hook: "tool.execute.before",
18832
19000
  tool: input.tool,
18833
19001
  callID: input.callID,
@@ -19243,8 +19411,8 @@ async function markBlocksConsumed(absRoot, entries) {
19243
19411
  }
19244
19412
 
19245
19413
  // plugins/session-recovery.ts
19246
- var PLUGIN_NAME17 = "session-recovery";
19247
- logLifecycle(PLUGIN_NAME17, "import", {});
19414
+ var PLUGIN_NAME18 = "session-recovery";
19415
+ logLifecycle(PLUGIN_NAME18, "import", {});
19248
19416
  async function processSessionStart(currentSessionId, opts = {}) {
19249
19417
  if (opts.disabled) {
19250
19418
  return { ok: true, injected: false, reason: "disabled" };
@@ -19254,7 +19422,7 @@ async function processSessionStart(currentSessionId, opts = {}) {
19254
19422
  excludeIds.add(currentSessionId);
19255
19423
  const r = await scanLastSession({ ...opts, excludeIds: [...excludeIds] });
19256
19424
  if (!r.ok) {
19257
- opts.log?.warn?.(`[${PLUGIN_NAME17}] 扫描失败:${r.error}`);
19425
+ opts.log?.warn?.(`[${PLUGIN_NAME18}] 扫描失败:${r.error}`);
19258
19426
  return { ok: false, injected: false, reason: "scan_error", error: r.error };
19259
19427
  }
19260
19428
  const plan = r.plan;
@@ -19263,7 +19431,7 @@ async function processSessionStart(currentSessionId, opts = {}) {
19263
19431
  try {
19264
19432
  pendingBlocks = await scanBlockPending(opts.root);
19265
19433
  } catch (err) {
19266
- opts.log?.warn?.(`[${PLUGIN_NAME17}] scanBlockPending 异常:${err instanceof Error ? err.message : String(err)}`);
19434
+ opts.log?.warn?.(`[${PLUGIN_NAME18}] scanBlockPending 异常:${err instanceof Error ? err.message : String(err)}`);
19267
19435
  }
19268
19436
  }
19269
19437
  const hasPendingBlocks = pendingBlocks.length > 0;
@@ -19281,7 +19449,7 @@ async function processSessionStart(currentSessionId, opts = {}) {
19281
19449
  await opts.injectRecovery(injection);
19282
19450
  } catch (err) {
19283
19451
  const msg = err instanceof Error ? err.message : String(err);
19284
- opts.log?.warn?.(`[${PLUGIN_NAME17}] injectRecovery 异常:${msg}`);
19452
+ opts.log?.warn?.(`[${PLUGIN_NAME18}] injectRecovery 异常:${msg}`);
19285
19453
  return { ok: false, injected: false, plan, reason: "inject_error", error: msg, pendingBlocks };
19286
19454
  }
19287
19455
  }
@@ -19289,7 +19457,7 @@ async function processSessionStart(currentSessionId, opts = {}) {
19289
19457
  try {
19290
19458
  await markBlocksConsumed(opts.root, pendingBlocks);
19291
19459
  } catch (err) {
19292
- opts.log?.warn?.(`[${PLUGIN_NAME17}] markBlocksConsumed 异常:${err instanceof Error ? err.message : String(err)}`);
19460
+ opts.log?.warn?.(`[${PLUGIN_NAME18}] markBlocksConsumed 异常:${err instanceof Error ? err.message : String(err)}`);
19293
19461
  }
19294
19462
  }
19295
19463
  return { ok: true, injected: true, plan, reason: "ok", pendingBlocks };
@@ -19336,13 +19504,13 @@ function renderPrompt(plan) {
19336
19504
  return lines.join(`
19337
19505
  `);
19338
19506
  }
19339
- var log10 = makePluginLogger(PLUGIN_NAME17);
19507
+ var log10 = makePluginLogger(PLUGIN_NAME18);
19340
19508
  var _lastInjection = null;
19341
19509
  var sessionRecoveryServer = async (ctx) => {
19342
- logLifecycle(PLUGIN_NAME17, "activate", { directory: ctx.directory });
19510
+ logLifecycle(PLUGIN_NAME18, "activate", { directory: ctx.directory });
19343
19511
  return {
19344
19512
  event: async ({ event }) => {
19345
- await safeAsync(PLUGIN_NAME17, "event", async () => {
19513
+ await safeAsync(PLUGIN_NAME18, "event", async () => {
19346
19514
  const e = event;
19347
19515
  if (!e || typeof e.type !== "string")
19348
19516
  return;
@@ -19357,7 +19525,7 @@ var sessionRecoveryServer = async (ctx) => {
19357
19525
  _lastInjection = inj;
19358
19526
  }
19359
19527
  });
19360
- safeWriteLog(PLUGIN_NAME17, {
19528
+ safeWriteLog(PLUGIN_NAME18, {
19361
19529
  hook: "event",
19362
19530
  type: "session.start",
19363
19531
  ok: r.ok,
@@ -19367,13 +19535,13 @@ var sessionRecoveryServer = async (ctx) => {
19367
19535
  pending_blocks_count: r.pendingBlocks?.length ?? 0
19368
19536
  });
19369
19537
  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})`);
19538
+ log10.info(`[${PLUGIN_NAME18}] 注入恢复提示(last=${r.plan.last_session_id?.slice(0, 8) ?? "?"}, reason=${r.plan.reason}, pending_blocks=${r.pendingBlocks?.length ?? 0})`);
19371
19539
  }
19372
19540
  });
19373
19541
  }
19374
19542
  };
19375
19543
  };
19376
- var handler17 = sessionRecoveryServer;
19544
+ var handler18 = sessionRecoveryServer;
19377
19545
 
19378
19546
  // plugins/subtasks.ts
19379
19547
  import { promises as fs18 } from "node:fs";
@@ -20200,7 +20368,7 @@ function sleep2(ms) {
20200
20368
 
20201
20369
  // plugins/subtasks.ts
20202
20370
  init_runtime_paths();
20203
- var PLUGIN_NAME18 = "subtasks";
20371
+ var PLUGIN_NAME19 = "subtasks";
20204
20372
  function getLogFile(root = process.cwd()) {
20205
20373
  return path22.join(runtimeDir(root), "logs", "subtasks.log");
20206
20374
  }
@@ -20280,7 +20448,7 @@ async function handleParallelCommand(raw) {
20280
20448
  specs = splitDescriptions(args.description, { parentId });
20281
20449
  }
20282
20450
  if (specs.length === 0) {
20283
- log11?.warn(`[${PLUGIN_NAME18}] /parallel 缺有效子任务`);
20451
+ log11?.warn(`[${PLUGIN_NAME19}] /parallel 缺有效子任务`);
20284
20452
  await safeReply2(ctx, "⚠ /parallel 需要至少 1 个子任务(用 ; 或换行分隔)");
20285
20453
  return { ok: false, reason: "no_subtasks" };
20286
20454
  }
@@ -20315,7 +20483,7 @@ async function handleParallelCommand(raw) {
20315
20483
  }
20316
20484
  const canNotice = Boolean(ctx.client && ctx.parentSessionID);
20317
20485
  if (specs.length === 1 && rawDescription.length >= 30 && ctx.client) {
20318
- log11?.info(`[${PLUGIN_NAME18}] 触发 AI 拆分(描述长度=${rawDescription.length})`);
20486
+ log11?.info(`[${PLUGIN_NAME19}] 触发 AI 拆分(描述长度=${rawDescription.length})`);
20319
20487
  const dec = await decomposeTask(rawDescription, {
20320
20488
  client: ctx.client,
20321
20489
  directory: ctx.directory,
@@ -20336,7 +20504,7 @@ async function handleParallelCommand(raw) {
20336
20504
  timeout_ms: undefined,
20337
20505
  args: d.hintFiles && d.hintFiles.length > 0 ? { hintFiles: d.hintFiles } : undefined
20338
20506
  }));
20339
- log11?.info(`[${PLUGIN_NAME18}] AI 拆分成功 → ${specs.length} 个子任务`);
20507
+ log11?.info(`[${PLUGIN_NAME19}] AI 拆分成功 → ${specs.length} 个子任务`);
20340
20508
  if (canNotice) {
20341
20509
  const lines = [
20342
20510
  `\uD83E\uDD16 AI 已自动拆为 ${specs.length} 个并行子任务:`,
@@ -20348,7 +20516,7 @@ async function handleParallelCommand(raw) {
20348
20516
  });
20349
20517
  }
20350
20518
  } else if (dec.reason === "llm_unavailable" || dec.reason === "parse_failed") {
20351
- log11?.warn(`[${PLUGIN_NAME18}] AI 拆分失败(${dec.reason}),按单任务执行`);
20519
+ log11?.warn(`[${PLUGIN_NAME19}] AI 拆分失败(${dec.reason}),按单任务执行`);
20352
20520
  }
20353
20521
  }
20354
20522
  if (canNotice) {
@@ -20427,7 +20595,7 @@ async function handleParallelCommand(raw) {
20427
20595
  });
20428
20596
  } catch (err) {
20429
20597
  const msg = err instanceof Error ? err.message : String(err);
20430
- log11?.error(`[${PLUGIN_NAME18}] schedule 抛错`, { error: msg });
20598
+ log11?.error(`[${PLUGIN_NAME19}] schedule 抛错`, { error: msg });
20431
20599
  await safeReply2(ctx, `❌ 并发调度失败:${msg}`);
20432
20600
  return { ok: false, reason: msg };
20433
20601
  }
@@ -20445,7 +20613,7 @@ async function handleParallelCommand(raw) {
20445
20613
  }
20446
20614
  await safeReply2(ctx, summaryLines.join(`
20447
20615
  `));
20448
- log11?.info(`[${PLUGIN_NAME18}] schedule 完成`, {
20616
+ log11?.info(`[${PLUGIN_NAME19}] schedule 完成`, {
20449
20617
  parentId,
20450
20618
  success: result.digest.success,
20451
20619
  failed: result.digest.failed,
@@ -20458,7 +20626,7 @@ async function handleParallelCommand(raw) {
20458
20626
  try {
20459
20627
  await ctx.onCompleted(result);
20460
20628
  } catch (err) {
20461
- log11?.warn(`[${PLUGIN_NAME18}] onCompleted hook 抛错(已隔离)`, {
20629
+ log11?.warn(`[${PLUGIN_NAME19}] onCompleted hook 抛错(已隔离)`, {
20462
20630
  error: err instanceof Error ? err.message : String(err)
20463
20631
  });
20464
20632
  }
@@ -20500,7 +20668,7 @@ async function writeLog(level, msg, data) {
20500
20668
  const line = JSON.stringify({
20501
20669
  ts: new Date().toISOString(),
20502
20670
  level,
20503
- plugin: PLUGIN_NAME18,
20671
+ plugin: PLUGIN_NAME19,
20504
20672
  msg,
20505
20673
  data
20506
20674
  }) + `
@@ -20511,11 +20679,11 @@ async function writeLog(level, msg, data) {
20511
20679
  await fs18.appendFile(logFile, line, "utf8");
20512
20680
  } catch {}
20513
20681
  }
20514
- logLifecycle(PLUGIN_NAME18, "import");
20682
+ logLifecycle(PLUGIN_NAME19, "import");
20515
20683
  var subtasksServer = async (ctx) => {
20516
- const log11 = makePluginLogger(PLUGIN_NAME18);
20684
+ const log11 = makePluginLogger(PLUGIN_NAME19);
20517
20685
  const client = ctx?.client ?? undefined;
20518
- logLifecycle(PLUGIN_NAME18, "activate", {
20686
+ logLifecycle(PLUGIN_NAME19, "activate", {
20519
20687
  directory: ctx.directory,
20520
20688
  hasClient: Boolean(client)
20521
20689
  });
@@ -20612,19 +20780,19 @@ var subtasksServer = async (ctx) => {
20612
20780
  });
20613
20781
  }
20614
20782
  } catch (err) {
20615
- log11.error(`[${PLUGIN_NAME18}] command.execute.before 异常(已隔离)`, {
20783
+ log11.error(`[${PLUGIN_NAME19}] command.execute.before 异常(已隔离)`, {
20616
20784
  error: err instanceof Error ? err.message : String(err)
20617
20785
  });
20618
20786
  }
20619
20787
  }
20620
20788
  };
20621
20789
  };
20622
- var handler18 = subtasksServer;
20790
+ var handler19 = subtasksServer;
20623
20791
 
20624
20792
  // plugins/terminal-monitor.ts
20625
20793
  import * as crypto5 from "node:crypto";
20626
- var PLUGIN_NAME19 = "terminal-monitor";
20627
- logLifecycle(PLUGIN_NAME19, "import", {});
20794
+ var PLUGIN_NAME20 = "terminal-monitor";
20795
+ logLifecycle(PLUGIN_NAME20, "import", {});
20628
20796
  var DEFAULT_CONFIG6 = {
20629
20797
  minScore: 0.6,
20630
20798
  cooldownMs: 30000,
@@ -20906,17 +21074,17 @@ function describeError(err) {
20906
21074
  return String(err);
20907
21075
  }
20908
21076
  }
20909
- var log11 = makePluginLogger(PLUGIN_NAME19);
21077
+ var log11 = makePluginLogger(PLUGIN_NAME20);
20910
21078
  var lru = new FingerprintLRU2;
20911
21079
  var _lastNotification = null;
20912
21080
  var terminalMonitorServer = async (ctx) => {
20913
- logLifecycle(PLUGIN_NAME19, "activate", {
21081
+ logLifecycle(PLUGIN_NAME20, "activate", {
20914
21082
  directory: ctx.directory,
20915
21083
  triggerEventTypes: ["terminal.output", "terminal.exit"]
20916
21084
  });
20917
21085
  return {
20918
21086
  event: async ({ event }) => {
20919
- await safeAsync(PLUGIN_NAME19, "event", async () => {
21087
+ await safeAsync(PLUGIN_NAME20, "event", async () => {
20920
21088
  const e = event;
20921
21089
  if (!e || typeof e.type !== "string")
20922
21090
  return;
@@ -20930,7 +21098,7 @@ var terminalMonitorServer = async (ctx) => {
20930
21098
  _lastNotification = msg;
20931
21099
  }
20932
21100
  });
20933
- safeWriteLog(PLUGIN_NAME19, {
21101
+ safeWriteLog(PLUGIN_NAME20, {
20934
21102
  hook: "event",
20935
21103
  type: e.type,
20936
21104
  notified: r.notified,
@@ -20938,17 +21106,17 @@ var terminalMonitorServer = async (ctx) => {
20938
21106
  findings: r.notification?.findings.length ?? 0
20939
21107
  });
20940
21108
  if (r.notified && r.notification) {
20941
- log11.info(`[${PLUGIN_NAME19}] 反馈 ${r.notification.findings.length} 条 → ${r.notification.summary}`);
21109
+ log11.info(`[${PLUGIN_NAME20}] 反馈 ${r.notification.findings.length} 条 → ${r.notification.summary}`);
20942
21110
  }
20943
21111
  });
20944
21112
  }
20945
21113
  };
20946
21114
  };
20947
- var handler19 = terminalMonitorServer;
21115
+ var handler20 = terminalMonitorServer;
20948
21116
 
20949
21117
  // plugins/token-manager.ts
20950
- var PLUGIN_NAME20 = "token-manager";
20951
- logLifecycle(PLUGIN_NAME20, "import", {});
21118
+ var PLUGIN_NAME21 = "token-manager";
21119
+ logLifecycle(PLUGIN_NAME21, "import", {});
20952
21120
  async function handleMessageBefore(raw, log12, defaults) {
20953
21121
  const ctx = raw ?? {};
20954
21122
  if (!Array.isArray(ctx.messages) || ctx.messages.length === 0)
@@ -20968,21 +21136,21 @@ async function handleMessageBefore(raw, log12, defaults) {
20968
21136
  };
20969
21137
  if (r.compressed) {
20970
21138
  ctx.messages = r.messages;
20971
- log12?.info(`[${PLUGIN_NAME20}] 压缩 ${r.before.count}→${r.after.count} 条 / ${r.before.tokens}→${r.after.tokens} tokens`);
21139
+ log12?.info(`[${PLUGIN_NAME21}] 压缩 ${r.before.count}→${r.after.count} 条 / ${r.before.tokens}→${r.after.tokens} tokens`);
20972
21140
  }
20973
21141
  return r;
20974
21142
  } catch (err) {
20975
- log12?.warn(`[${PLUGIN_NAME20}] 压缩异常(已隔离)`, {
21143
+ log12?.warn(`[${PLUGIN_NAME21}] 压缩异常(已隔离)`, {
20976
21144
  error: err instanceof Error ? err.message : String(err)
20977
21145
  });
20978
21146
  return null;
20979
21147
  }
20980
21148
  }
20981
- var log12 = makePluginLogger(PLUGIN_NAME20);
21149
+ var log12 = makePluginLogger(PLUGIN_NAME21);
20982
21150
  var tokenManagerServer = async (ctx) => {
20983
21151
  const rt = loadRuntimeSync();
20984
21152
  const threshold = rt.runtime.context.condenser_threshold_ratio;
20985
- logLifecycle(PLUGIN_NAME20, "activate", {
21153
+ logLifecycle(PLUGIN_NAME21, "activate", {
20986
21154
  directory: ctx.directory,
20987
21155
  threshold,
20988
21156
  target: DEFAULT_CONDENSE.target,
@@ -20991,7 +21159,7 @@ var tokenManagerServer = async (ctx) => {
20991
21159
  });
20992
21160
  return {
20993
21161
  "experimental.chat.messages.transform": async (_input, output) => {
20994
- await safeAsync(PLUGIN_NAME20, "experimental.chat.messages.transform", async () => {
21162
+ await safeAsync(PLUGIN_NAME21, "experimental.chat.messages.transform", async () => {
20995
21163
  const list = output.messages;
20996
21164
  if (!Array.isArray(list) || list.length === 0)
20997
21165
  return;
@@ -21004,7 +21172,7 @@ var tokenManagerServer = async (ctx) => {
21004
21172
  const r = await handleMessageBefore({ messages: flat }, log12, { threshold });
21005
21173
  if (!r)
21006
21174
  return;
21007
- safeWriteLog(PLUGIN_NAME20, {
21175
+ safeWriteLog(PLUGIN_NAME21, {
21008
21176
  hook: "experimental.chat.messages.transform",
21009
21177
  mode: "observe-only",
21010
21178
  before_msgs: r.before.count,
@@ -21015,13 +21183,13 @@ var tokenManagerServer = async (ctx) => {
21015
21183
  reason: r.reason
21016
21184
  });
21017
21185
  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)`);
21186
+ 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
21187
  }
21020
21188
  });
21021
21189
  }
21022
21190
  };
21023
21191
  };
21024
- var handler20 = tokenManagerServer;
21192
+ var handler21 = tokenManagerServer;
21025
21193
 
21026
21194
  // plugins/tool-policy.ts
21027
21195
  import { promises as fs19 } from "node:fs";
@@ -21284,9 +21452,14 @@ function checkFileAccess(acl, file, op) {
21284
21452
  }
21285
21453
 
21286
21454
  // plugins/tool-policy.ts
21287
- var PLUGIN_NAME21 = "tool-policy";
21288
- logLifecycle(PLUGIN_NAME21, "import", {});
21455
+ var PLUGIN_NAME22 = "tool-policy";
21456
+ logLifecycle(PLUGIN_NAME22, "import", {});
21289
21457
  var EMPTY_ACL = { whitelistMode: false };
21458
+ function getAgentAcl(cfg, agent) {
21459
+ if (!agent || !cfg.per_agent)
21460
+ return;
21461
+ return cfg.per_agent[agent];
21462
+ }
21290
21463
  var SUBAGENT_APPLY_DENY_LIST = new Set([
21291
21464
  "coder",
21292
21465
  "planner",
@@ -21350,7 +21523,7 @@ async function loadPolicy(root = process.cwd()) {
21350
21523
  function classifyToolKind(toolName) {
21351
21524
  return classifyTool(toolName);
21352
21525
  }
21353
- async function resolveCurrentAgent(client, sessionID, log13) {
21526
+ async function resolveCurrentAgent2(client, sessionID, log13) {
21354
21527
  try {
21355
21528
  const sessionApi = client?.session;
21356
21529
  if (!sessionApi || typeof sessionApi.get !== "function") {
@@ -21376,24 +21549,24 @@ async function resolveCurrentAgent(client, sessionID, log13) {
21376
21549
  return;
21377
21550
  }
21378
21551
  }
21379
- var log13 = makePluginLogger(PLUGIN_NAME21);
21552
+ var log13 = makePluginLogger(PLUGIN_NAME22);
21380
21553
  var toolPolicyServer = async (ctx) => {
21381
21554
  const directory = ctx.directory ?? process.cwd();
21382
21555
  const cfg = await loadPolicy(directory);
21383
- logLifecycle(PLUGIN_NAME21, "activate", {
21556
+ logLifecycle(PLUGIN_NAME22, "activate", {
21384
21557
  directory,
21385
21558
  acl_loaded: !!cfg.acl
21386
21559
  });
21387
21560
  return {
21388
21561
  "tool.execute.before": async (input, output) => {
21389
21562
  let denied;
21390
- await safeAsync(PLUGIN_NAME21, "tool.execute.before", async () => {
21563
+ await safeAsync(PLUGIN_NAME22, "tool.execute.before", async () => {
21391
21564
  const toolName = input.tool;
21392
21565
  const argsObj = output.args ?? {};
21393
21566
  const needsAgentDetection = toolName === "pending_changes" && (argsObj.action === "apply" || argsObj.action === "apply_all");
21394
21567
  let currentAgent = undefined;
21395
21568
  if (needsAgentDetection) {
21396
- currentAgent = await resolveCurrentAgent(ctx.client, input.sessionID, log13);
21569
+ currentAgent = await resolveCurrentAgent2(ctx.client, input.sessionID, log13);
21397
21570
  }
21398
21571
  const files = [];
21399
21572
  const filePath = argsObj["path"] ?? argsObj["filePath"] ?? argsObj["file"];
@@ -21407,7 +21580,7 @@ var toolPolicyServer = async (ctx) => {
21407
21580
  args: argsObj,
21408
21581
  files: files.length ? files : undefined
21409
21582
  }, cfg, currentAgent);
21410
- safeWriteLog(PLUGIN_NAME21, {
21583
+ safeWriteLog(PLUGIN_NAME22, {
21411
21584
  hook: "tool.execute.before",
21412
21585
  tool: toolName,
21413
21586
  callID: input.callID,
@@ -21418,7 +21591,7 @@ var toolPolicyServer = async (ctx) => {
21418
21591
  currentAgent
21419
21592
  });
21420
21593
  if (decision.action === "deny") {
21421
- log13.warn(`[${PLUGIN_NAME21}] DENY ${toolName}`, {
21594
+ log13.warn(`[${PLUGIN_NAME22}] DENY ${toolName}`, {
21422
21595
  action: argsObj.action,
21423
21596
  currentAgent,
21424
21597
  reasons: decision.reasons,
@@ -21433,7 +21606,7 @@ var toolPolicyServer = async (ctx) => {
21433
21606
  }
21434
21607
  };
21435
21608
  };
21436
- var handler21 = toolPolicyServer;
21609
+ var handler22 = toolPolicyServer;
21437
21610
 
21438
21611
  // plugins/update-checker.ts
21439
21612
  import { existsSync as existsSync5 } from "node:fs";
@@ -21463,7 +21636,7 @@ import * as zlib from "node:zlib";
21463
21636
  // lib/version-injected.ts
21464
21637
  function getInjectedVersion() {
21465
21638
  try {
21466
- const v = "0.5.8";
21639
+ const v = "0.5.10";
21467
21640
  if (typeof v === "string" && /^\d+\.\d+\.\d+/.test(v)) {
21468
21641
  return v;
21469
21642
  }
@@ -21966,20 +22139,20 @@ function compareOpencodeVersion(opts) {
21966
22139
  }
21967
22140
 
21968
22141
  // plugins/update-checker.ts
21969
- var PLUGIN_NAME22 = "update-checker";
22142
+ var PLUGIN_NAME23 = "update-checker";
21970
22143
  var PLUGIN_VERSION = "2.0.0";
21971
- logLifecycle(PLUGIN_NAME22, "import", { version: PLUGIN_VERSION });
22144
+ logLifecycle(PLUGIN_NAME23, "import", { version: PLUGIN_VERSION });
21972
22145
  var updateCheckerServer = async (ctx) => {
21973
22146
  const yieldResult = shouldYieldToLocalPlugin({ directory: ctx.directory });
21974
22147
  if (yieldResult.yield) {
21975
- safeWriteLog(PLUGIN_NAME22, {
22148
+ safeWriteLog(PLUGIN_NAME23, {
21976
22149
  level: "info",
21977
22150
  msg: "dev_mode_yield_skip",
21978
22151
  reason: yieldResult.reason,
21979
22152
  markerPath: yieldResult.markerPath,
21980
22153
  detail: formatYieldLog(yieldResult)
21981
22154
  });
21982
- logLifecycle(PLUGIN_NAME22, "activate", {
22155
+ logLifecycle(PLUGIN_NAME23, "activate", {
21983
22156
  yield_to_local: true,
21984
22157
  yield_reason: yieldResult.reason,
21985
22158
  skipped: "auto_install + background_check"
@@ -21988,7 +22161,7 @@ var updateCheckerServer = async (ctx) => {
21988
22161
  }
21989
22162
  const rt = loadRuntimeSync();
21990
22163
  const u = rt.runtime.update;
21991
- logLifecycle(PLUGIN_NAME22, "activate", {
22164
+ logLifecycle(PLUGIN_NAME23, "activate", {
21992
22165
  version: PLUGIN_VERSION,
21993
22166
  auto_check_enabled: u.auto_check_enabled,
21994
22167
  interval_hours: u.interval_hours,
@@ -22000,14 +22173,14 @@ var updateCheckerServer = async (ctx) => {
22000
22173
  repo_fallback: u.repo,
22001
22174
  config_source: "codeforge.json"
22002
22175
  });
22003
- await safeAsync(PLUGIN_NAME22, "opencode_version_check", async () => {
22176
+ await safeAsync(PLUGIN_NAME23, "opencode_version_check", async () => {
22004
22177
  const compat = loadCompatibility();
22005
22178
  const opencodeVer = detectOpencodeVersion();
22006
22179
  const verdict = compareOpencodeVersion({
22007
22180
  currentOpencodeVer: opencodeVer,
22008
22181
  compat
22009
22182
  });
22010
- safeWriteLog(PLUGIN_NAME22, {
22183
+ safeWriteLog(PLUGIN_NAME23, {
22011
22184
  level: "info",
22012
22185
  msg: "opencode_version_check",
22013
22186
  opencodeVer,
@@ -22024,14 +22197,14 @@ var updateCheckerServer = async (ctx) => {
22024
22197
  }
22025
22198
  });
22026
22199
  if (!u.auto_check_enabled) {
22027
- safeWriteLog(PLUGIN_NAME22, {
22200
+ safeWriteLog(PLUGIN_NAME23, {
22028
22201
  level: "info",
22029
22202
  msg: "auto-check disabled (codeforge.json update.auto_check_enabled=false)"
22030
22203
  });
22031
22204
  return {};
22032
22205
  }
22033
22206
  setImmediate(() => {
22034
- safeAsync(PLUGIN_NAME22, "checkAndMaybeUpdate", async () => {
22207
+ safeAsync(PLUGIN_NAME23, "checkAndMaybeUpdate", async () => {
22035
22208
  const local = readLocalVersion();
22036
22209
  let npmResult = null;
22037
22210
  try {
@@ -22042,7 +22215,7 @@ var updateCheckerServer = async (ctx) => {
22042
22215
  timeoutMs: 5000
22043
22216
  });
22044
22217
  } catch (e) {
22045
- safeWriteLog(PLUGIN_NAME22, {
22218
+ safeWriteLog(PLUGIN_NAME23, {
22046
22219
  level: "warn",
22047
22220
  msg: "npm_fetch_failed",
22048
22221
  error: e.message
@@ -22051,7 +22224,7 @@ var updateCheckerServer = async (ctx) => {
22051
22224
  return;
22052
22225
  }
22053
22226
  if (!npmResult) {
22054
- safeWriteLog(PLUGIN_NAME22, {
22227
+ safeWriteLog(PLUGIN_NAME23, {
22055
22228
  level: "info",
22056
22229
  msg: "npm_no_release",
22057
22230
  package: u.package,
@@ -22060,7 +22233,7 @@ var updateCheckerServer = async (ctx) => {
22060
22233
  return;
22061
22234
  }
22062
22235
  const hasUpdate = cmpVersion(local, npmResult.version) < 0;
22063
- safeWriteLog(PLUGIN_NAME22, {
22236
+ safeWriteLog(PLUGIN_NAME23, {
22064
22237
  level: "info",
22065
22238
  msg: "npm_check_result",
22066
22239
  local,
@@ -22075,10 +22248,10 @@ var updateCheckerServer = async (ctx) => {
22075
22248
  更新命令:npx ${u.package} install --global`);
22076
22249
  return;
22077
22250
  }
22078
- await safeAsync(PLUGIN_NAME22, "auto_install_bundle", async () => {
22251
+ await safeAsync(PLUGIN_NAME23, "auto_install_bundle", async () => {
22079
22252
  const target = getOpencodeBundlePath();
22080
22253
  if (!target) {
22081
- safeWriteLog(PLUGIN_NAME22, {
22254
+ safeWriteLog(PLUGIN_NAME23, {
22082
22255
  level: "warn",
22083
22256
  msg: "auto_install_skip",
22084
22257
  reason: "无法定位 opencode bundle 路径"
@@ -22097,7 +22270,7 @@ var updateCheckerServer = async (ctx) => {
22097
22270
  oldVersion: local,
22098
22271
  keepBackups: u.backup_keep
22099
22272
  });
22100
- safeWriteLog(PLUGIN_NAME22, {
22273
+ safeWriteLog(PLUGIN_NAME23, {
22101
22274
  level: "info",
22102
22275
  msg: "auto_install_success",
22103
22276
  local,
@@ -22137,7 +22310,7 @@ function getOpencodeBundlePath() {
22137
22310
  return candidates[0] ?? null;
22138
22311
  }
22139
22312
  async function fallbackToGitHubReleases(ctx, u) {
22140
- await safeAsync(PLUGIN_NAME22, "github_fallback", async () => {
22313
+ await safeAsync(PLUGIN_NAME23, "github_fallback", async () => {
22141
22314
  const result = await checkUpdateOnce({
22142
22315
  repo: u.repo,
22143
22316
  intervalMs: u.interval_hours * 3600 * 1000
@@ -22147,7 +22320,7 @@ async function fallbackToGitHubReleases(ctx, u) {
22147
22320
  }
22148
22321
  async function reportLegacyResult(ctx, result, repo) {
22149
22322
  if (result.error && !result.fromCache) {
22150
- safeWriteLog(PLUGIN_NAME22, {
22323
+ safeWriteLog(PLUGIN_NAME23, {
22151
22324
  level: "warn",
22152
22325
  msg: `update check failed: ${result.error}`,
22153
22326
  ...result
@@ -22155,7 +22328,7 @@ async function reportLegacyResult(ctx, result, repo) {
22155
22328
  return;
22156
22329
  }
22157
22330
  if (!result.hasUpdate) {
22158
- safeWriteLog(PLUGIN_NAME22, {
22331
+ safeWriteLog(PLUGIN_NAME23, {
22159
22332
  level: "info",
22160
22333
  msg: `up-to-date (local=${result.local}, remote=${result.remote}${result.fromCache ? ", from_cache" : ""})`,
22161
22334
  ...result
@@ -22165,7 +22338,7 @@ async function reportLegacyResult(ctx, result, repo) {
22165
22338
  const updateCmd = `bunx --bun github:${repo} install`;
22166
22339
  const toast = `[CodeForge] 有新版本:${result.local} → ${result.remote}
22167
22340
  更新命令:${updateCmd}`;
22168
- safeWriteLog(PLUGIN_NAME22, {
22341
+ safeWriteLog(PLUGIN_NAME23, {
22169
22342
  level: "info",
22170
22343
  msg: "new_version_available_github_fallback",
22171
22344
  local: result.local,
@@ -22176,17 +22349,17 @@ async function reportLegacyResult(ctx, result, repo) {
22176
22349
  await postToast(ctx, toast);
22177
22350
  }
22178
22351
  async function postToast(ctx, message) {
22179
- await safeAsync(PLUGIN_NAME22, "client.app.log", async () => {
22352
+ await safeAsync(PLUGIN_NAME23, "client.app.log", async () => {
22180
22353
  await ctx.client.app.log({
22181
22354
  body: {
22182
- service: PLUGIN_NAME22,
22355
+ service: PLUGIN_NAME23,
22183
22356
  level: "info",
22184
22357
  message
22185
22358
  }
22186
22359
  });
22187
22360
  });
22188
22361
  }
22189
- var handler22 = updateCheckerServer;
22362
+ var handler23 = updateCheckerServer;
22190
22363
 
22191
22364
  // plugins/workflow-engine.ts
22192
22365
  import * as path26 from "node:path";
@@ -22641,9 +22814,9 @@ async function runStepAutoFeedback(step, adapter) {
22641
22814
  }
22642
22815
 
22643
22816
  // plugins/workflow-engine.ts
22644
- var PLUGIN_NAME23 = "workflow-engine";
22645
- logLifecycle(PLUGIN_NAME23, "import", {});
22646
- var fallbackLog2 = makePluginLogger(PLUGIN_NAME23);
22817
+ var PLUGIN_NAME24 = "workflow-engine";
22818
+ logLifecycle(PLUGIN_NAME24, "import", {});
22819
+ var fallbackLog2 = makePluginLogger(PLUGIN_NAME24);
22647
22820
  var _registry = null;
22648
22821
  async function loadRegistry(workflowsDir) {
22649
22822
  const { loaded, failed } = await loadWorkflowsFromDir(workflowsDir);
@@ -22663,32 +22836,32 @@ async function handleCommandInvoked(raw, workflowsDir = "workflows") {
22663
22836
  const log14 = ctx.log ?? fallbackLog2;
22664
22837
  const command = typeof ctx.command === "string" ? ctx.command : null;
22665
22838
  if (!command) {
22666
- log14.warn(`[${PLUGIN_NAME23}] command.invoked 缺 command 字段`, ctx);
22839
+ log14.warn(`[${PLUGIN_NAME24}] command.invoked 缺 command 字段`, ctx);
22667
22840
  return null;
22668
22841
  }
22669
22842
  const reg = await ensureRegistry(workflowsDir);
22670
22843
  if (reg.errors.length) {
22671
- log14.warn(`[${PLUGIN_NAME23}] 有 ${reg.errors.length} 个 workflow 加载失败`, reg.errors);
22844
+ log14.warn(`[${PLUGIN_NAME24}] 有 ${reg.errors.length} 个 workflow 加载失败`, reg.errors);
22672
22845
  }
22673
22846
  const wf = reg.workflows.find((w) => matchesTrigger(w, command));
22674
22847
  if (!wf) {
22675
- log14.info(`[${PLUGIN_NAME23}] no workflow matches "${command}"`);
22848
+ log14.info(`[${PLUGIN_NAME24}] no workflow matches "${command}"`);
22676
22849
  return null;
22677
22850
  }
22678
- log14.info(`[${PLUGIN_NAME23}] dispatch "${command}" → workflow "${wf.name}"`);
22851
+ log14.info(`[${PLUGIN_NAME24}] dispatch "${command}" → workflow "${wf.name}"`);
22679
22852
  try {
22680
22853
  const result = await run(wf, {
22681
22854
  mode: ctx.adapter ? "real" : "dry_run",
22682
22855
  autonomy: ctx.autonomy ?? "semi",
22683
22856
  adapter: ctx.adapter
22684
22857
  });
22685
- log14.info(`[${PLUGIN_NAME23}] workflow "${wf.name}" 完成 (${result.plan.mode})`, {
22858
+ log14.info(`[${PLUGIN_NAME24}] workflow "${wf.name}" 完成 (${result.plan.mode})`, {
22686
22859
  steps: result.plan.steps.length,
22687
22860
  results: result.results.length
22688
22861
  });
22689
22862
  return result;
22690
22863
  } catch (err) {
22691
- log14.error(`[${PLUGIN_NAME23}] workflow "${wf.name}" 执行失败`, {
22864
+ log14.error(`[${PLUGIN_NAME24}] workflow "${wf.name}" 执行失败`, {
22692
22865
  error: err instanceof Error ? err.message : String(err)
22693
22866
  });
22694
22867
  throw err;
@@ -22697,15 +22870,15 @@ async function handleCommandInvoked(raw, workflowsDir = "workflows") {
22697
22870
  var workflowEngineServer = async (ctx) => {
22698
22871
  const directory = ctx.directory ?? process.cwd();
22699
22872
  const workflowsDir = path26.join(directory, "workflows");
22700
- ensureRegistry(workflowsDir).catch((err) => fallbackLog2.warn(`[${PLUGIN_NAME23}] preload workflows failed`, {
22873
+ ensureRegistry(workflowsDir).catch((err) => fallbackLog2.warn(`[${PLUGIN_NAME24}] preload workflows failed`, {
22701
22874
  error: err instanceof Error ? err.message : String(err)
22702
22875
  }));
22703
- logLifecycle(PLUGIN_NAME23, "activate", { directory, workflowsDir });
22876
+ logLifecycle(PLUGIN_NAME24, "activate", { directory, workflowsDir });
22704
22877
  return {
22705
22878
  "command.execute.before": async (input, output) => {
22706
- await safeAsync(PLUGIN_NAME23, "command.execute.before", async () => {
22879
+ await safeAsync(PLUGIN_NAME24, "command.execute.before", async () => {
22707
22880
  const cmd = input.command.startsWith("/") ? input.command : `/${input.command}`;
22708
- safeWriteLog(PLUGIN_NAME23, {
22881
+ safeWriteLog(PLUGIN_NAME24, {
22709
22882
  hook: "command.execute.before",
22710
22883
  command: cmd,
22711
22884
  sessionID: input.sessionID
@@ -22720,14 +22893,14 @@ var workflowEngineServer = async (ctx) => {
22720
22893
  });
22721
22894
  },
22722
22895
  "chat.message": async (input, output) => {
22723
- await safeAsync(PLUGIN_NAME23, "chat.message", async () => {
22896
+ await safeAsync(PLUGIN_NAME24, "chat.message", async () => {
22724
22897
  const text = extractUserText(output).trim();
22725
22898
  if (!text.startsWith("/"))
22726
22899
  return;
22727
22900
  const cmd = text.split(/\s+/)[0];
22728
22901
  if (!cmd)
22729
22902
  return;
22730
- safeWriteLog(PLUGIN_NAME23, {
22903
+ safeWriteLog(PLUGIN_NAME24, {
22731
22904
  hook: "chat.message",
22732
22905
  command: cmd,
22733
22906
  sessionID: input.sessionID
@@ -22737,12 +22910,12 @@ var workflowEngineServer = async (ctx) => {
22737
22910
  }
22738
22911
  };
22739
22912
  };
22740
- var handler23 = workflowEngineServer;
22913
+ var handler24 = workflowEngineServer;
22741
22914
 
22742
22915
  // plugins/session-worktree-guard.ts
22743
22916
  import path27 from "node:path";
22744
- var PLUGIN_NAME24 = "session-worktree-guard";
22745
- logLifecycle(PLUGIN_NAME24, "import", {});
22917
+ var PLUGIN_NAME25 = "session-worktree-guard";
22918
+ logLifecycle(PLUGIN_NAME25, "import", {});
22746
22919
  var WRITE_INTENT_RE = />(?!=)|\btee\b|\brm\b|\bmv\b|\bcp\b|\bmkdir\b|\btouch\b|\bchmod\b|\bchown\b|\bln\b/;
22747
22920
  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
22921
  var SIDE_EFFECT_TOKEN_RE = />(?!=)|\|\s*tee\b|\btee\b/;
@@ -22766,10 +22939,19 @@ function buildGitVcsWriteRegex(mainRoot) {
22766
22939
  return new RegExp(`git\\b[^\\n]*(?:-C\\s+|--work-tree[=\\s])${esc}`);
22767
22940
  }
22768
22941
  var WRITE_TOOLS = new Set(["write", "edit", "ast_edit"]);
22942
+ var CLASS_B_CALLER_WHITELIST = new Set([
22943
+ "codeforge",
22944
+ "reviewer",
22945
+ "general"
22946
+ ]);
22769
22947
  function rewritePath(value, mainRoot, worktreeRoot) {
22770
22948
  if (!value)
22771
22949
  return null;
22772
22950
  const resolved = path27.isAbsolute(value) ? value : path27.resolve(mainRoot, value);
22951
+ const wtPrefix2 = worktreeRoot.endsWith("/") ? worktreeRoot : worktreeRoot + "/";
22952
+ if (resolved === worktreeRoot || resolved.startsWith(wtPrefix2)) {
22953
+ return null;
22954
+ }
22773
22955
  if (resolved === mainRoot)
22774
22956
  return worktreeRoot;
22775
22957
  const prefix = mainRoot.endsWith("/") ? mainRoot : mainRoot + "/";
@@ -22821,7 +23003,17 @@ function isWriteOperation(toolName, argsObj, mainRoot) {
22821
23003
  return true;
22822
23004
  return false;
22823
23005
  }
22824
- var log14 = makePluginLogger(PLUGIN_NAME24);
23006
+ function collectWritePaths(toolName, argsObj, worktreeRoot) {
23007
+ const out = [];
23008
+ const candidate = toolName === "write" || toolName === "edit" ? argsObj["filePath"] : toolName === "ast_edit" ? argsObj["target"] : undefined;
23009
+ if (typeof candidate !== "string" || candidate.length === 0)
23010
+ return out;
23011
+ const abs = path27.isAbsolute(candidate) ? candidate : path27.resolve(worktreeRoot, candidate);
23012
+ const rel = path27.relative(worktreeRoot, abs).split(path27.sep).join("/");
23013
+ out.push(rel);
23014
+ return out;
23015
+ }
23016
+ var log14 = makePluginLogger(PLUGIN_NAME25);
22825
23017
  function resolveMainRoot2(rawDir) {
22826
23018
  const worktreeMarker = "/.git/codeforge-worktrees/";
22827
23019
  const idx = rawDir.indexOf(worktreeMarker);
@@ -22832,7 +23024,17 @@ function resolveMainRoot2(rawDir) {
22832
23024
  }
22833
23025
  var sessionWorktreeGuardPlugin = async (ctx) => {
22834
23026
  const mainRoot = resolveMainRoot2(ctx.directory ?? process.cwd());
22835
- logLifecycle(PLUGIN_NAME24, "activate", {
23027
+ let policyCfg = {};
23028
+ try {
23029
+ policyCfg = await loadPolicy(mainRoot);
23030
+ } catch (err) {
23031
+ log14.warn("loadPolicy failed (class E skipped)", {
23032
+ mainRoot,
23033
+ error: err instanceof Error ? err.message : String(err)
23034
+ });
23035
+ }
23036
+ const perAgentEnabled = !!policyCfg.per_agent && Object.keys(policyCfg.per_agent).length > 0;
23037
+ logLifecycle(PLUGIN_NAME25, "activate", {
22836
23038
  mainRoot,
22837
23039
  CODEFORGE_SESSION_ID: process.env["CODEFORGE_SESSION_ID"] ?? "(not set)"
22838
23040
  });
@@ -22842,7 +23044,7 @@ var sessionWorktreeGuardPlugin = async (ctx) => {
22842
23044
  if (!sessionId)
22843
23045
  return;
22844
23046
  let denied;
22845
- await safeAsync(PLUGIN_NAME24, "tool.execute.before", async () => {
23047
+ await safeAsync(PLUGIN_NAME25, "tool.execute.before", async () => {
22846
23048
  const toolName = input.tool;
22847
23049
  const argsObj = output.args ?? {};
22848
23050
  let entry = null;
@@ -22867,7 +23069,7 @@ var sessionWorktreeGuardPlugin = async (ctx) => {
22867
23069
  if (parentEntry && parentEntry.status === "active") {
22868
23070
  entry = parentEntry;
22869
23071
  log14.debug?.(`[child-inherit] session ${sessionId} 继承父 ${parentId} 的 worktree`, { parentSessionId: parentId, worktreePath: parentEntry.worktreePath });
22870
- safeWriteLog(PLUGIN_NAME24, {
23072
+ safeWriteLog(PLUGIN_NAME25, {
22871
23073
  hook: "tool.execute.before",
22872
23074
  tool: toolName,
22873
23075
  sessionID: input.sessionID,
@@ -22886,7 +23088,7 @@ var sessionWorktreeGuardPlugin = async (ctx) => {
22886
23088
  try {
22887
23089
  entry = await bindSessionWorktree({ sessionId, mainRoot });
22888
23090
  log14.info(`[lazy-bind] auto-created worktree for session ${sessionId}`, { branch: entry.branch, path: entry.worktreePath });
22889
- safeWriteLog(PLUGIN_NAME24, {
23091
+ safeWriteLog(PLUGIN_NAME25, {
22890
23092
  hook: "tool.execute.before",
22891
23093
  tool: toolName,
22892
23094
  sessionID: input.sessionID,
@@ -22896,7 +23098,7 @@ var sessionWorktreeGuardPlugin = async (ctx) => {
22896
23098
  });
22897
23099
  } catch (err) {
22898
23100
  log14.warn(`[lazy-bind] failed (落到主仓写)`, { sessionId, error: err instanceof Error ? err.message : String(err) });
22899
- safeWriteLog(PLUGIN_NAME24, {
23101
+ safeWriteLog(PLUGIN_NAME25, {
22900
23102
  hook: "tool.execute.before",
22901
23103
  tool: toolName,
22902
23104
  sessionID: input.sessionID,
@@ -22919,7 +23121,7 @@ var sessionWorktreeGuardPlugin = async (ctx) => {
22919
23121
  action,
22920
23122
  caller
22921
23123
  });
22922
- safeWriteLog(PLUGIN_NAME24, {
23124
+ safeWriteLog(PLUGIN_NAME25, {
22923
23125
  hook: "tool.execute.before",
22924
23126
  tool: toolName,
22925
23127
  sessionID: input.sessionID,
@@ -22933,6 +23135,56 @@ var sessionWorktreeGuardPlugin = async (ctx) => {
22933
23135
  }
22934
23136
  }
22935
23137
  }
23138
+ if (perAgentEnabled && isWriteOperation(toolName, argsObj, mainRoot)) {
23139
+ const caller = await resolveAgentForGuard({ sessionID: input.sessionID, agent: input.agent }, ctx.client, log14);
23140
+ if (caller === null) {
23141
+ 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)`;
23142
+ log14.warn(reason, {
23143
+ sessionId,
23144
+ tool: toolName,
23145
+ source: "per-agent-acl-fail-closed"
23146
+ });
23147
+ safeWriteLog(PLUGIN_NAME25, {
23148
+ hook: "tool.execute.before",
23149
+ tool: toolName,
23150
+ sessionID: input.sessionID,
23151
+ action: "deny",
23152
+ source: "per-agent-acl-fail-closed",
23153
+ caller: null
23154
+ });
23155
+ denied = new DeniedError(reason);
23156
+ return;
23157
+ }
23158
+ const agentAcl = getAgentAcl(policyCfg, caller);
23159
+ if (agentAcl) {
23160
+ const writePaths = collectWritePaths(toolName, argsObj, worktreePath);
23161
+ for (const relPath of writePaths) {
23162
+ const dec = checkFileAccess(agentAcl, relPath, "write");
23163
+ if (dec.action === "deny") {
23164
+ const reason = `[session-worktree-guard] DENIED: agent='${caller}' 写路径不在 ACL 白名单 — ` + `${relPath} (${dec.reason})`;
23165
+ log14.warn(reason, {
23166
+ sessionId,
23167
+ tool: toolName,
23168
+ caller,
23169
+ path: relPath,
23170
+ acl_decision: dec
23171
+ });
23172
+ safeWriteLog(PLUGIN_NAME25, {
23173
+ hook: "tool.execute.before",
23174
+ tool: toolName,
23175
+ sessionID: input.sessionID,
23176
+ action: "deny",
23177
+ source: "per-agent-acl",
23178
+ caller,
23179
+ path: relPath,
23180
+ acl_decision: dec
23181
+ });
23182
+ denied = new DeniedError(reason);
23183
+ return;
23184
+ }
23185
+ }
23186
+ }
23187
+ }
22936
23188
  if (toolName !== "plan_read" && entry.requiredPlanId && entry.planReadOk !== true) {
22937
23189
  let isWriteOp = WRITE_TOOLS.has(toolName);
22938
23190
  if (!isWriteOp && toolName === "bash") {
@@ -22961,7 +23213,7 @@ var sessionWorktreeGuardPlugin = async (ctx) => {
22961
23213
  requiredPlanId: entry.requiredPlanId,
22962
23214
  inheritedFromParent: inherited ? entry.sessionId : undefined
22963
23215
  });
22964
- safeWriteLog(PLUGIN_NAME24, {
23216
+ safeWriteLog(PLUGIN_NAME25, {
22965
23217
  hook: "tool.execute.before",
22966
23218
  tool: toolName,
22967
23219
  sessionID: input.sessionID,
@@ -22976,19 +23228,35 @@ var sessionWorktreeGuardPlugin = async (ctx) => {
22976
23228
  if (toolName === "bash") {
22977
23229
  const command = argsObj["command"];
22978
23230
  if (typeof command === "string" && commandContainsMainRoot(command, mainRoot) && detectBashWriteIntent(command, mainRoot)) {
22979
- const snippet = command.length > 60 ? command.slice(0, 60) + "…" : command;
22980
- const reason = `[session-worktree-guard] DENIED: bash.command 含主仓绝对路径写操作 (${snippet}),请在当前 session worktree (${worktreePath}) 内操作`;
22981
- log14.warn(reason, { sessionId, command: command.slice(0, 200) });
22982
- safeWriteLog(PLUGIN_NAME24, {
22983
- hook: "tool.execute.before",
22984
- tool: toolName,
22985
- sessionID: input.sessionID,
22986
- action: "deny",
22987
- source: "bash-write-intent",
22988
- command: command.slice(0, 200)
22989
- });
22990
- denied = new DeniedError(reason);
22991
- return;
23231
+ const caller = await resolveAgentForGuard({ sessionID: input.sessionID, agent: input.agent }, ctx.client, log14);
23232
+ if (caller !== null && CLASS_B_CALLER_WHITELIST.has(caller)) {
23233
+ log14.debug?.(`[class-b-whitelist] allow caller=${caller}`, { sessionId, tool: toolName, command: command.slice(0, 200) });
23234
+ safeWriteLog(PLUGIN_NAME25, {
23235
+ hook: "tool.execute.before",
23236
+ tool: toolName,
23237
+ sessionID: input.sessionID,
23238
+ action: "allow-whitelist",
23239
+ source: "class-b-caller-whitelist",
23240
+ caller,
23241
+ command: command.slice(0, 200)
23242
+ });
23243
+ } else {
23244
+ const callerTag = caller === null ? "unresolved" : caller;
23245
+ const snippet = command.length > 60 ? command.slice(0, 60) + "…" : command;
23246
+ const reason = `[session-worktree-guard] DENIED: bash.command 含主仓绝对路径写操作 (${snippet}) [caller=${callerTag}],请在当前 session worktree (${worktreePath}) 内操作`;
23247
+ log14.warn(reason, { sessionId, caller: callerTag, command: command.slice(0, 200) });
23248
+ safeWriteLog(PLUGIN_NAME25, {
23249
+ hook: "tool.execute.before",
23250
+ tool: toolName,
23251
+ sessionID: input.sessionID,
23252
+ action: "deny",
23253
+ source: "bash-write-intent",
23254
+ caller: callerTag,
23255
+ command: command.slice(0, 200)
23256
+ });
23257
+ denied = new DeniedError(reason);
23258
+ return;
23259
+ }
22992
23260
  }
22993
23261
  }
22994
23262
  if (toolName === "write" || toolName === "edit") {
@@ -22997,7 +23265,7 @@ var sessionWorktreeGuardPlugin = async (ctx) => {
22997
23265
  const newPath = rewritePath(filePath, mainRoot, worktreePath);
22998
23266
  if (newPath !== null) {
22999
23267
  log14.info(`rewrote ${toolName}.filePath: ${filePath} → ${newPath}`);
23000
- safeWriteLog(PLUGIN_NAME24, {
23268
+ safeWriteLog(PLUGIN_NAME25, {
23001
23269
  hook: "tool.execute.before",
23002
23270
  tool: toolName,
23003
23271
  field: "filePath",
@@ -23015,7 +23283,7 @@ var sessionWorktreeGuardPlugin = async (ctx) => {
23015
23283
  const newTarget = rewritePath(target, mainRoot, worktreePath);
23016
23284
  if (newTarget !== null) {
23017
23285
  log14.info(`rewrote ast_edit.target: ${target} → ${newTarget}`);
23018
- safeWriteLog(PLUGIN_NAME24, {
23286
+ safeWriteLog(PLUGIN_NAME25, {
23019
23287
  hook: "tool.execute.before",
23020
23288
  tool: toolName,
23021
23289
  field: "target",
@@ -23031,7 +23299,7 @@ var sessionWorktreeGuardPlugin = async (ctx) => {
23031
23299
  const newRoot = rewritePath(root, mainRoot, worktreePath);
23032
23300
  if (newRoot !== null) {
23033
23301
  log14.info(`rewrote ast_edit.root: ${root} → ${newRoot}`);
23034
- safeWriteLog(PLUGIN_NAME24, {
23302
+ safeWriteLog(PLUGIN_NAME25, {
23035
23303
  hook: "tool.execute.before",
23036
23304
  tool: toolName,
23037
23305
  field: "root",
@@ -23049,7 +23317,7 @@ var sessionWorktreeGuardPlugin = async (ctx) => {
23049
23317
  const newWorkdir = rewritePath(workdir, mainRoot, worktreePath);
23050
23318
  if (newWorkdir !== null) {
23051
23319
  log14.info(`rewrote bash.workdir: ${workdir} → ${newWorkdir}`);
23052
- safeWriteLog(PLUGIN_NAME24, {
23320
+ safeWriteLog(PLUGIN_NAME25, {
23053
23321
  hook: "tool.execute.before",
23054
23322
  tool: toolName,
23055
23323
  field: "workdir",
@@ -23067,19 +23335,22 @@ var sessionWorktreeGuardPlugin = async (ctx) => {
23067
23335
  }
23068
23336
  };
23069
23337
  };
23070
- var handler24 = sessionWorktreeGuardPlugin;
23338
+ var handler25 = sessionWorktreeGuardPlugin;
23071
23339
 
23072
23340
  // plugins/worktree-lifecycle.ts
23073
- var PLUGIN_NAME25 = "worktree-lifecycle";
23074
- logLifecycle(PLUGIN_NAME25, "import", {});
23341
+ var PLUGIN_NAME26 = "worktree-lifecycle";
23342
+ logLifecycle(PLUGIN_NAME26, "import", {});
23075
23343
  var IDLE_TOAST_THROTTLE_MS = 60 * 60000;
23076
23344
  var IDLE_TOAST_DURATION_MS = 8000;
23077
23345
  var IDLE_TOAST_REMINDER_INTERVAL_MS = 30 * 60000;
23346
+ var PRUNE_INTERVAL_MS = 30 * 60000;
23078
23347
  var lastIdleToastAt = new Map;
23079
- var log15 = makePluginLogger(PLUGIN_NAME25);
23348
+ var pruneRunning = false;
23349
+ var _pruneTimer;
23350
+ var log15 = makePluginLogger(PLUGIN_NAME26);
23080
23351
  var worktreeLifecyclePlugin = async (ctx) => {
23081
23352
  const mainRoot = ctx.directory;
23082
- logLifecycle(PLUGIN_NAME25, "activate", {
23353
+ logLifecycle(PLUGIN_NAME26, "activate", {
23083
23354
  mainRoot: mainRoot ?? "(not set)",
23084
23355
  idle_threshold_ms: IDLE_TOAST_THROTTLE_MS
23085
23356
  });
@@ -23088,11 +23359,11 @@ var worktreeLifecyclePlugin = async (ctx) => {
23088
23359
  }
23089
23360
  const client = ctx.client;
23090
23361
  setImmediate(() => {
23091
- safeAsync(PLUGIN_NAME25, "activate.pruneOrphan", async () => {
23362
+ safeAsync(PLUGIN_NAME26, "activate.pruneOrphan", async () => {
23092
23363
  const result = await pruneOrphanWorktrees(mainRoot);
23093
23364
  if (result.cleaned.length > 0 || result.failed.length > 0) {
23094
23365
  log15.info(`[pruneOrphan] cleaned=${result.cleaned.length} failed=${result.failed.length} skipped=${result.skipped}`);
23095
- safeWriteLog(PLUGIN_NAME25, {
23366
+ safeWriteLog(PLUGIN_NAME26, {
23096
23367
  hook: "activate.pruneOrphan",
23097
23368
  cleaned: result.cleaned,
23098
23369
  failed: result.failed,
@@ -23101,16 +23372,46 @@ var worktreeLifecyclePlugin = async (ctx) => {
23101
23372
  }
23102
23373
  });
23103
23374
  });
23375
+ if (_pruneTimer)
23376
+ clearInterval(_pruneTimer);
23377
+ _pruneTimer = setInterval(() => {
23378
+ if (pruneRunning) {
23379
+ safeWriteLog(PLUGIN_NAME26, {
23380
+ hook: "interval.pruneOrphan",
23381
+ action: "skip",
23382
+ reason: "previous prune still running"
23383
+ });
23384
+ return;
23385
+ }
23386
+ pruneRunning = true;
23387
+ safeAsync(PLUGIN_NAME26, "interval.pruneOrphan", async () => {
23388
+ try {
23389
+ const result = await pruneOrphanWorktrees(mainRoot);
23390
+ if (result.cleaned.length > 0 || result.failed.length > 0) {
23391
+ log15.info(`[pruneOrphan interval] cleaned=${result.cleaned.length} failed=${result.failed.length} skipped=${result.skipped}`);
23392
+ safeWriteLog(PLUGIN_NAME26, {
23393
+ hook: "interval.pruneOrphan",
23394
+ cleaned: result.cleaned,
23395
+ failed: result.failed,
23396
+ skipped: result.skipped
23397
+ });
23398
+ }
23399
+ } finally {
23400
+ pruneRunning = false;
23401
+ }
23402
+ });
23403
+ }, PRUNE_INTERVAL_MS);
23404
+ _pruneTimer.unref();
23104
23405
  return {
23105
23406
  event: async ({ event }) => {
23106
- await safeAsync(PLUGIN_NAME25, "event", async () => {
23407
+ await safeAsync(PLUGIN_NAME26, "event", async () => {
23107
23408
  const ended = extractEndedSessionID(event);
23108
23409
  if (!ended)
23109
23410
  return;
23110
23411
  if (ended.type === "session.deleted") {
23111
23412
  const entry = await getSessionWorktree(ended.sessionID, mainRoot);
23112
23413
  if (!entry || entry.status !== "active") {
23113
- safeWriteLog(PLUGIN_NAME25, {
23414
+ safeWriteLog(PLUGIN_NAME26, {
23114
23415
  hook: "event",
23115
23416
  type: ended.type,
23116
23417
  sessionID: ended.sessionID,
@@ -23125,7 +23426,7 @@ var worktreeLifecyclePlugin = async (ctx) => {
23125
23426
  const fastDirty = await isWorktreeDirty(entry.worktreePath);
23126
23427
  if (!fastDirty) {
23127
23428
  await discardSession({ sessionId: ended.sessionID, mainRoot });
23128
- safeWriteLog(PLUGIN_NAME25, {
23429
+ safeWriteLog(PLUGIN_NAME26, {
23129
23430
  hook: "event",
23130
23431
  type: ended.type,
23131
23432
  sessionID: ended.sessionID,
@@ -23160,7 +23461,7 @@ var worktreeLifecyclePlugin = async (ctx) => {
23160
23461
  }
23161
23462
  try {
23162
23463
  await discardSession({ sessionId: ended.sessionID, mainRoot });
23163
- safeWriteLog(PLUGIN_NAME25, {
23464
+ safeWriteLog(PLUGIN_NAME26, {
23164
23465
  hook: "event",
23165
23466
  type: ended.type,
23166
23467
  sessionID: ended.sessionID,
@@ -23194,7 +23495,7 @@ var worktreeLifecyclePlugin = async (ctx) => {
23194
23495
  const idleMin = Math.round(idleMs / 60000);
23195
23496
  const msg = `\uD83D\uDCA4 Session ${ended.sessionID.slice(0, 8)} worktree 已空闲 ${idleMin}min,` + `用 /merge 收尾或 /discard-session 放弃`;
23196
23497
  const sent = await showToast2(client, { message: msg, variant: "default", duration: IDLE_TOAST_DURATION_MS, title: "CodeForge" }, log15);
23197
- safeWriteLog(PLUGIN_NAME25, {
23498
+ safeWriteLog(PLUGIN_NAME26, {
23198
23499
  hook: "event",
23199
23500
  type: ended.type,
23200
23501
  sessionID: ended.sessionID,
@@ -23207,7 +23508,7 @@ var worktreeLifecyclePlugin = async (ctx) => {
23207
23508
  }
23208
23509
  };
23209
23510
  };
23210
- var handler25 = worktreeLifecyclePlugin;
23511
+ var handler26 = worktreeLifecyclePlugin;
23211
23512
 
23212
23513
  // src/index.ts
23213
23514
  var PLUGIN_ID = "codeforge";
@@ -23219,26 +23520,27 @@ var HANDLERS = [
23219
23520
  { name: "auto-commit", init: handler3 },
23220
23521
  { name: "auto-learning", init: handler4 },
23221
23522
  { 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 }
23523
+ { name: "chat-agent-cache", init: handler6 },
23524
+ { name: "codeforge-tools", init: handler8 },
23525
+ { name: "discover-spec-suggest", init: handler9 },
23526
+ { name: "kh-auto-context", init: handler10 },
23527
+ { name: "kh-reminder", init: handler11 },
23528
+ { name: "memories-context", init: handler12 },
23529
+ { name: "model-fallback", init: handler13 },
23530
+ { name: "parallel-tool-nudge", init: handler16 },
23531
+ { name: "pwsh-utf8", init: handler17 },
23532
+ { name: "session-recovery", init: handler18 },
23533
+ { name: "subtask-heartbeat", init: handler14 },
23534
+ { name: "subtasks", init: handler19 },
23535
+ { name: "parallel-status", init: handler15 },
23536
+ { name: "terminal-monitor", init: handler20 },
23537
+ { name: "token-manager", init: handler21 },
23538
+ { name: "tool-heartbeat", init: handler7 },
23539
+ { name: "tool-policy", init: handler22 },
23540
+ { name: "update-checker", init: handler23 },
23541
+ { name: "workflow-engine", init: handler24 },
23542
+ { name: "session-worktree-guard", init: handler25 },
23543
+ { name: "worktree-lifecycle", init: handler26 }
23242
23544
  ];
23243
23545
  function makeSerialHook(hookName, fns) {
23244
23546
  return async (input, output) => {