@andyqiu/codeforge 0.3.8 → 0.3.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
@@ -83,6 +83,11 @@ function logLifecycle(plugin, phase, extra) {
83
83
  ...extra
84
84
  });
85
85
  }
86
+ function makePartId(prefix = "prt_") {
87
+ const rand = Math.random().toString(36).slice(2, 9);
88
+ const ts = Date.now().toString(36).slice(-7);
89
+ return `${prefix}${rand}${ts}`;
90
+ }
86
91
  var LOG_DIR, LOG_FILE;
87
92
  var init_opencode_plugin_helpers = __esm(() => {
88
93
  LOG_DIR = process.env["CODEFORGE_LOG_DIR"] ?? join(homedir(), ".cache", "codeforge");
@@ -10764,6 +10769,14 @@ function applyAnchor(content, edit, eol, beforeHash) {
10764
10769
  if (!edit.anchor) {
10765
10770
  return { ok: false, reason: "invalid_input", message: "anchor 不能为空" };
10766
10771
  }
10772
+ if (edit.anchor.includes(`
10773
+ `)) {
10774
+ return {
10775
+ ok: false,
10776
+ reason: "invalid_input",
10777
+ message: "anchor 不能含换行符(仅支持单行匹配);多行改动请改用 pending_changes.stage 整文件暂存"
10778
+ };
10779
+ }
10767
10780
  const lines = splitLines(content);
10768
10781
  const hits = findAnchorLines(lines, edit.anchor, edit.regex === true);
10769
10782
  if (hits.length === 0) {
@@ -10966,6 +10979,7 @@ function finish(before, after, beforeHash, affected) {
10966
10979
  // tools/ast-edit.ts
10967
10980
  var description16 = [
10968
10981
  "AST 风格的精确文件编辑:anchor + 哈希校验,避免 LLM 「整文件重写」误改无关代码。",
10982
+ "**anchor 仅支持单行**:含 `\\n` 的多行 anchor 会被直接拒绝(reason=invalid_input);多行改动请改用 `pending_changes.stage` 整文件暂存。",
10969
10983
  "**何时调用**:",
10970
10984
  "- 需要在指定 anchor(行特征)后插入代码(hook 注入、import 添加)",
10971
10985
  "- 重命名一个标识符(rename_symbol,全文件词边界匹配)",
@@ -10973,6 +10987,7 @@ var description16 = [
10973
10987
  "**何时不需要**:",
10974
10988
  "- 整文件重写更合理(直接 pending-changes.stage 整文件)",
10975
10989
  "- 只是 1-2 个字符的 typo(用 edit 工具更直接)",
10990
+ "- 多行 anchor / 跨行匹配(请用 pending_changes.stage 整文件)",
10976
10991
  "**安全约束**:",
10977
10992
  "- 必须传 before_hash(如果文件已存在),不一致会被拒绝",
10978
10993
  "- 默认 auto_stage=true:结果直接进 pending-changes 等待人审,不立刻落盘"
@@ -10990,7 +11005,7 @@ var AnchorAction = Common.extend({
10990
11005
  "insert_after_anchor",
10991
11006
  "insert_before_anchor"
10992
11007
  ]),
10993
- anchor: z17.string().min(1).describe("anchor 文本子串或正则源"),
11008
+ anchor: z17.string().min(1).describe("anchor 文本子串或正则源(必须单行;含 \\n 会被拒绝,多行改动请用 pending_changes.stage)"),
10994
11009
  regex: z17.boolean().optional().describe("anchor 是否按 RegExp 解释,默认 false"),
10995
11010
  occurrence: z17.number().int().min(1).optional().describe("第几次匹配(1-based),默认 1;多次命中时必须显式指定"),
10996
11011
  payload: z17.string().describe("要写入的内容;引擎会按文件原 EOL 处理")
@@ -13512,76 +13527,146 @@ var codeforgeToolsServer = async (ctx) => {
13512
13527
  };
13513
13528
  var handler8 = codeforgeToolsServer;
13514
13529
 
13515
- // plugins/hello-world.ts
13516
- init_opencode_plugin_helpers();
13517
- var PLUGIN_NAME9 = "hello-world";
13518
- var PLUGIN_VERSION = "1.0.0";
13519
- try {
13520
- logLifecycle(PLUGIN_NAME9, "import", { version: PLUGIN_VERSION });
13521
- safeWriteLog(PLUGIN_NAME9, { phase: "import", version: PLUGIN_VERSION });
13522
- } catch (importErr) {
13523
- console.error(`[${PLUGIN_NAME9}] import lifecycle log failed:`, importErr);
13524
- }
13525
- var helloWorldServer = async (ctx) => {
13526
- logLifecycle(PLUGIN_NAME9, "activate", {
13527
- version: PLUGIN_VERSION,
13528
- directory: ctx.directory,
13529
- worktree: ctx.worktree,
13530
- projectId: ctx.project?.id
13531
- });
13532
- await safeAsync(PLUGIN_NAME9, "client.app.log", async () => {
13533
- await ctx.client.app.log({
13534
- body: {
13535
- service: PLUGIN_NAME9,
13536
- level: "info",
13537
- message: `${PLUGIN_NAME9} v${PLUGIN_VERSION} activated in ${ctx.directory}`
13538
- }
13530
+ // plugins/kh-auto-context.ts
13531
+ init_kh_client();
13532
+
13533
+ // lib/kh-shared-context.ts
13534
+ var DEFAULT_MAX_PER_SESSION = 20;
13535
+ var DEFAULT_TTL_MS = 5 * 60 * 1000;
13536
+
13537
+ class SessionScopedCache {
13538
+ cache = new Map;
13539
+ inflight = new Map;
13540
+ generation = new Map;
13541
+ ttlMs;
13542
+ maxPerSession;
13543
+ now;
13544
+ constructor(opts = {}) {
13545
+ this.ttlMs = opts.ttlMs ?? DEFAULT_TTL_MS;
13546
+ this.maxPerSession = opts.maxPerSession ?? DEFAULT_MAX_PER_SESSION;
13547
+ this.now = opts.now ?? (() => Date.now());
13548
+ }
13549
+ async searchOrGet(args) {
13550
+ const { client, sessionId, query, limit, timeoutMs } = args;
13551
+ const queryHash = this.hashQuery(sessionId, query);
13552
+ const inflightKey = `${sessionId}::${queryHash}`;
13553
+ const sessionMap = this.cache.get(sessionId);
13554
+ if (sessionMap) {
13555
+ const entry = sessionMap.get(queryHash);
13556
+ if (entry && this.now() - entry.cachedAt < this.ttlMs) {
13557
+ sessionMap.delete(queryHash);
13558
+ sessionMap.set(queryHash, entry);
13559
+ return entry.result;
13560
+ }
13561
+ if (entry)
13562
+ sessionMap.delete(queryHash);
13563
+ }
13564
+ const pending = this.inflight.get(inflightKey);
13565
+ if (pending)
13566
+ return pending;
13567
+ const startGen = this.generation.get(sessionId) ?? 0;
13568
+ const promise = this.doSearchAndMaybeCache(client, sessionId, queryHash, query, limit, timeoutMs, startGen).finally(() => {
13569
+ this.inflight.delete(inflightKey);
13539
13570
  });
13540
- });
13541
- return {
13542
- event: async ({ event }) => {
13543
- await safeAsync(PLUGIN_NAME9, "event", async () => {
13544
- const e = event;
13545
- const propKeys = e.properties && typeof e.properties === "object" ? Object.keys(e.properties).slice(0, 10) : [];
13546
- safeWriteLog(PLUGIN_NAME9, {
13547
- hook: "event",
13548
- type: e.type ?? "unknown",
13549
- propKeys
13550
- });
13551
- });
13552
- },
13553
- "chat.message": async (input, output) => {
13554
- await safeAsync(PLUGIN_NAME9, "chat.message", async () => {
13555
- const text = extractUserText(output);
13556
- safeWriteLog(PLUGIN_NAME9, {
13557
- hook: "chat.message",
13558
- sessionID: input.sessionID,
13559
- agent: input.agent,
13560
- messageID: input.messageID,
13561
- textPreview: text.slice(0, 80),
13562
- textLen: text.length,
13563
- partsCount: output.parts?.length ?? 0
13564
- });
13565
- });
13566
- },
13567
- "tool.execute.before": async (input, _output) => {
13568
- await safeAsync(PLUGIN_NAME9, "tool.execute.before", async () => {
13569
- safeWriteLog(PLUGIN_NAME9, {
13570
- hook: "tool.execute.before",
13571
- tool: input.tool,
13572
- sessionID: input.sessionID,
13573
- callID: input.callID
13574
- });
13571
+ this.inflight.set(inflightKey, promise);
13572
+ return promise;
13573
+ }
13574
+ onSessionEnd(sessionId) {
13575
+ this.generation.set(sessionId, (this.generation.get(sessionId) ?? 0) + 1);
13576
+ this.cache.delete(sessionId);
13577
+ const prefix = `${sessionId}::`;
13578
+ for (const key of this.inflight.keys()) {
13579
+ if (key.startsWith(prefix)) {
13580
+ this.inflight.delete(key);
13581
+ }
13582
+ }
13583
+ }
13584
+ _snapshot() {
13585
+ const perSession = {};
13586
+ let total = 0;
13587
+ for (const [sid, map] of this.cache.entries()) {
13588
+ perSession[sid] = map.size;
13589
+ total += map.size;
13590
+ }
13591
+ const generations = {};
13592
+ for (const [sid, g] of this.generation.entries()) {
13593
+ generations[sid] = g;
13594
+ }
13595
+ return {
13596
+ cacheSize: total,
13597
+ inflightSize: this.inflight.size,
13598
+ sessions: Array.from(this.cache.keys()),
13599
+ perSession,
13600
+ generations
13601
+ };
13602
+ }
13603
+ _reset() {
13604
+ this.cache.clear();
13605
+ this.inflight.clear();
13606
+ this.generation.clear();
13607
+ }
13608
+ hashQuery(sessionId, query) {
13609
+ return `${sessionId}::${query}`;
13610
+ }
13611
+ async doSearchAndMaybeCache(client, sessionId, queryHash, query, limit, timeoutMs, startGen) {
13612
+ const result = await this.doSearch(client, query, limit, timeoutMs);
13613
+ const currentGen = this.generation.get(sessionId) ?? 0;
13614
+ if (currentGen === startGen && result.ok) {
13615
+ this.writeCache(sessionId, queryHash, query, result);
13616
+ }
13617
+ return result;
13618
+ }
13619
+ async doSearch(client, query, limit, timeoutMs) {
13620
+ const callPromise = (async () => {
13621
+ try {
13622
+ return await client.search({ query, limit });
13623
+ } catch (err) {
13624
+ return {
13625
+ ok: false,
13626
+ reason: "kh_returned_error",
13627
+ message: err instanceof Error ? err.message : String(err)
13628
+ };
13629
+ }
13630
+ })();
13631
+ if (timeoutMs === undefined || timeoutMs <= 0) {
13632
+ return callPromise;
13633
+ }
13634
+ let timer = null;
13635
+ try {
13636
+ const racer = new Promise((res) => {
13637
+ timer = setTimeout(() => res({
13638
+ ok: false,
13639
+ reason: "kh_returned_error",
13640
+ message: `kh search timeout after ${timeoutMs}ms`
13641
+ }), timeoutMs);
13575
13642
  });
13643
+ return await Promise.race([callPromise, racer]);
13644
+ } finally {
13645
+ if (timer)
13646
+ clearTimeout(timer);
13576
13647
  }
13577
- };
13578
- };
13579
- var handler9 = helloWorldServer;
13648
+ }
13649
+ writeCache(sessionId, queryHash, query, result) {
13650
+ let sessionMap = this.cache.get(sessionId);
13651
+ if (!sessionMap) {
13652
+ sessionMap = new Map;
13653
+ this.cache.set(sessionId, sessionMap);
13654
+ }
13655
+ sessionMap.delete(queryHash);
13656
+ sessionMap.set(queryHash, { query, result, cachedAt: this.now() });
13657
+ while (sessionMap.size > this.maxPerSession) {
13658
+ const oldest = sessionMap.keys().next().value;
13659
+ if (oldest === undefined)
13660
+ break;
13661
+ sessionMap.delete(oldest);
13662
+ }
13663
+ }
13664
+ }
13665
+ var sharedKhCache = new SessionScopedCache;
13580
13666
 
13581
13667
  // plugins/kh-auto-context.ts
13582
- init_kh_client();
13583
13668
  init_opencode_plugin_helpers();
13584
- var PLUGIN_NAME10 = "kh-auto-context";
13669
+ var PLUGIN_NAME9 = "kh-auto-context";
13585
13670
  var INJECTION_MODE = "observe-only";
13586
13671
  function resolveInjectionMode(client) {
13587
13672
  return client.hasTransport() ? "system-injected" : "observe-only";
@@ -13680,7 +13765,7 @@ var CODEFORGE_CONSTRAINTS = [
13680
13765
  ];
13681
13766
  async function seedConstraints(client, log6) {
13682
13767
  if (!client.hasTransport()) {
13683
- log6?.debug?.(`[${PLUGIN_NAME10}] seedConstraints: transport 不可用,跳过`, {});
13768
+ log6?.debug?.(`[${PLUGIN_NAME9}] seedConstraints: transport 不可用,跳过`, {});
13684
13769
  return { ok: false, reason: "transport_unavailable" };
13685
13770
  }
13686
13771
  try {
@@ -13694,7 +13779,7 @@ async function seedConstraints(client, log6) {
13694
13779
  });
13695
13780
  if (result && typeof result === "object" && "ok" in result && result.ok === false) {
13696
13781
  const r = result;
13697
- log6?.warn(`[${PLUGIN_NAME10}] seedConstraints 降级`, {
13782
+ log6?.warn(`[${PLUGIN_NAME9}] seedConstraints 降级`, {
13698
13783
  reason: r.reason,
13699
13784
  message: r.message
13700
13785
  });
@@ -13704,11 +13789,11 @@ async function seedConstraints(client, log6) {
13704
13789
  message: r.message
13705
13790
  };
13706
13791
  }
13707
- log6?.info(`[${PLUGIN_NAME10}] seedConstraints: 已写入 ${CODEFORGE_CONSTRAINTS.length} 条 constraints`, { count: CODEFORGE_CONSTRAINTS.length });
13792
+ log6?.info(`[${PLUGIN_NAME9}] seedConstraints: 已写入 ${CODEFORGE_CONSTRAINTS.length} 条 constraints`, { count: CODEFORGE_CONSTRAINTS.length });
13708
13793
  return { ok: true, itemsWritten: CODEFORGE_CONSTRAINTS.length };
13709
13794
  } catch (err) {
13710
13795
  const message = err instanceof Error ? err.message : String(err);
13711
- log6?.warn(`[${PLUGIN_NAME10}] seedConstraints 失败(已静默)`, { error: message });
13796
+ log6?.warn(`[${PLUGIN_NAME9}] seedConstraints 失败(已静默)`, { error: message });
13712
13797
  return { ok: false, reason: "exception", message };
13713
13798
  }
13714
13799
  }
@@ -13723,7 +13808,7 @@ function createSystemInjectedHook(client, sessionId, log6) {
13723
13808
  });
13724
13809
  if (result && typeof result === "object" && "ok" in result && result.ok === false) {
13725
13810
  const r = result;
13726
- log6?.warn(`[${PLUGIN_NAME10}] system-injected 降级到 observe-only:${r.reason ?? "unknown"}`, {
13811
+ log6?.warn(`[${PLUGIN_NAME9}] system-injected 降级到 observe-only:${r.reason ?? "unknown"}`, {
13727
13812
  sessionId,
13728
13813
  section,
13729
13814
  preview: markdown.slice(0, 200),
@@ -13732,12 +13817,12 @@ function createSystemInjectedHook(client, sessionId, log6) {
13732
13817
  });
13733
13818
  return;
13734
13819
  }
13735
- log6?.info(`[${PLUGIN_NAME10}] system-injected: 写入 KH working_memory 成功 (${markdown.length} chars)`, {
13820
+ log6?.info(`[${PLUGIN_NAME9}] system-injected: 写入 KH working_memory 成功 (${markdown.length} chars)`, {
13736
13821
  sessionId,
13737
13822
  section
13738
13823
  });
13739
13824
  } catch (err) {
13740
- log6?.warn(`[${PLUGIN_NAME10}] system-injected 抛异常,降级到 observe-only`, {
13825
+ log6?.warn(`[${PLUGIN_NAME9}] system-injected 抛异常,降级到 observe-only`, {
13741
13826
  sessionId,
13742
13827
  section,
13743
13828
  preview: markdown.slice(0, 200),
@@ -13746,23 +13831,16 @@ function createSystemInjectedHook(client, sessionId, log6) {
13746
13831
  }
13747
13832
  };
13748
13833
  }
13749
- async function handleMessage2(raw, opts) {
13834
+ var inflight2 = new Set;
13835
+ var INFLIGHT_CAP = 5;
13836
+ function inflightKey(sessionId, query) {
13837
+ return `${sessionId ?? "global"}:${Date.now()}:${Math.random().toString(36).slice(2, 10)}`;
13838
+ }
13839
+ async function runKhSearchAndInject(args) {
13840
+ const { query, ctx, opts, mode } = args;
13750
13841
  const cfg = opts.config ?? DEFAULT_CONFIG5;
13751
- const mode = opts.mode ?? INJECTION_MODE;
13752
- const ctx = raw ?? {};
13753
13842
  const log6 = ctx.log;
13754
- const text = (ctx.content ?? "").trim();
13755
- if (!shouldInject(text, cfg)) {
13756
- log6?.debug?.(`[${PLUGIN_NAME10}] skip (filter)`, { textLen: text.length });
13757
- return null;
13758
- }
13759
- const query = extractQuery(text);
13760
- if (!query)
13761
- return null;
13762
- if (opts.cache.shouldSkip(query)) {
13763
- log6?.debug?.(`[${PLUGIN_NAME10}] cache hit, skip`, { query });
13764
- return null;
13765
- }
13843
+ const startedAt = Date.now();
13766
13844
  const racer = opts.scheduler?.raceTimeout ?? (async (p, ms) => {
13767
13845
  let timer = null;
13768
13846
  try {
@@ -13777,20 +13855,54 @@ async function handleMessage2(raw, opts) {
13777
13855
  clearTimeout(timer);
13778
13856
  }
13779
13857
  });
13780
- const result = await racer(opts.client.search({ query, limit: cfg.limit }), cfg.timeoutMs);
13858
+ let result;
13859
+ try {
13860
+ const searchPromise = sharedKhCache.searchOrGet({
13861
+ client: opts.client,
13862
+ sessionId: ctx.sessionId ?? "global",
13863
+ query,
13864
+ limit: cfg.limit
13865
+ });
13866
+ result = await racer(searchPromise, cfg.timeoutMs);
13867
+ } catch (err) {
13868
+ log6?.warn(`[${PLUGIN_NAME9}] client.search threw (sync or async), return null`, {
13869
+ query,
13870
+ elapsedMs: Date.now() - startedAt,
13871
+ sessionId: ctx.sessionId,
13872
+ error: err instanceof Error ? err.message : String(err)
13873
+ });
13874
+ opts.cache.record(query, []);
13875
+ return null;
13876
+ }
13781
13877
  if (result === "__timeout__") {
13782
- log6?.warn(`[${PLUGIN_NAME10}] timeout`, { query, ms: cfg.timeoutMs });
13878
+ log6?.warn(`[${PLUGIN_NAME9}] timeout`, {
13879
+ query,
13880
+ ms: cfg.timeoutMs,
13881
+ elapsedMs: Date.now() - startedAt,
13882
+ sessionId: ctx.sessionId
13883
+ });
13783
13884
  opts.cache.record(query, []);
13784
13885
  return null;
13785
13886
  }
13786
13887
  if (!result.ok) {
13787
- log6?.debug?.(`[${PLUGIN_NAME10}] kh degraded`, { reason: result.reason });
13888
+ log6?.warn(`[${PLUGIN_NAME9}] kh degraded`, {
13889
+ reason: result.reason,
13890
+ query,
13891
+ elapsedMs: Date.now() - startedAt,
13892
+ sessionId: ctx.sessionId
13893
+ });
13788
13894
  opts.cache.record(query, []);
13789
13895
  return null;
13790
13896
  }
13791
13897
  const filtered = result.insights.filter((i) => (i.confidence ?? 0) >= cfg.minConfidence);
13792
13898
  if (filtered.length === 0) {
13793
13899
  opts.cache.record(query, []);
13900
+ log6?.debug?.(`[${PLUGIN_NAME9}] no candidate above threshold`, {
13901
+ query,
13902
+ rawCount: result.insights.length,
13903
+ elapsedMs: Date.now() - startedAt,
13904
+ sessionId: ctx.sessionId
13905
+ });
13794
13906
  return null;
13795
13907
  }
13796
13908
  const payload = formatInjection(query, filtered, mode);
@@ -13798,22 +13910,68 @@ async function handleMessage2(raw, opts) {
13798
13910
  try {
13799
13911
  await ctx.injectContext(payload.markdown);
13800
13912
  } catch (err) {
13801
- log6?.warn(`[${PLUGIN_NAME10}] injectContext threw`, {
13802
- error: err instanceof Error ? err.message : String(err)
13913
+ log6?.warn(`[${PLUGIN_NAME9}] injectContext threw`, {
13914
+ error: err instanceof Error ? err.message : String(err),
13915
+ query,
13916
+ sessionId: ctx.sessionId
13803
13917
  });
13804
13918
  }
13805
13919
  }
13806
13920
  opts.cache.record(query, filtered);
13807
- log6?.info(`[${PLUGIN_NAME10}] ${mode}: ${filtered.length} candidate insights ready`, { query, mode });
13921
+ log6?.info(`[${PLUGIN_NAME9}] inject complete (${mode})`, {
13922
+ query,
13923
+ mode,
13924
+ candidateCount: filtered.length,
13925
+ elapsedMs: Date.now() - startedAt,
13926
+ sessionId: ctx.sessionId
13927
+ });
13808
13928
  return payload;
13809
13929
  }
13810
- logLifecycle(PLUGIN_NAME10, "import");
13930
+ async function handleMessage2(raw, opts) {
13931
+ const cfg = opts.config ?? DEFAULT_CONFIG5;
13932
+ const mode = opts.mode ?? INJECTION_MODE;
13933
+ const ctx = raw ?? {};
13934
+ const log6 = ctx.log;
13935
+ const text = (ctx.content ?? "").trim();
13936
+ if (!shouldInject(text, cfg)) {
13937
+ log6?.debug?.(`[${PLUGIN_NAME9}] skip (filter)`, { textLen: text.length });
13938
+ return;
13939
+ }
13940
+ const query = extractQuery(text);
13941
+ if (!query)
13942
+ return;
13943
+ if (opts.cache.shouldSkip(query)) {
13944
+ log6?.debug?.(`[${PLUGIN_NAME9}] cache hit, skip`, { query });
13945
+ return;
13946
+ }
13947
+ if (inflight2.size >= INFLIGHT_CAP) {
13948
+ log6?.warn(`[${PLUGIN_NAME9}] inflight cap reached, skip`, {
13949
+ query,
13950
+ inflightSize: inflight2.size,
13951
+ cap: INFLIGHT_CAP,
13952
+ sessionId: ctx.sessionId
13953
+ });
13954
+ return;
13955
+ }
13956
+ const key = inflightKey(ctx.sessionId, query);
13957
+ inflight2.add(key);
13958
+ runKhSearchAndInject({ query, ctx, opts, mode }).catch((err) => {
13959
+ log6?.warn(`[${PLUGIN_NAME9}] runKhSearchAndInject 顶层兜底捕获`, {
13960
+ error: err instanceof Error ? err.message : String(err),
13961
+ query,
13962
+ sessionId: ctx.sessionId
13963
+ });
13964
+ }).finally(() => {
13965
+ inflight2.delete(key);
13966
+ });
13967
+ }
13968
+ logLifecycle(PLUGIN_NAME9, "import");
13811
13969
  var sharedClient2 = new KhClient;
13812
13970
  var sharedCache = new QueryCache(DEFAULT_CONFIG5.cacheTtlMs);
13813
13971
  var khAutoContextServer = async (ctx) => {
13814
- const log6 = makePluginLogger(PLUGIN_NAME10);
13972
+ const log6 = makePluginLogger(PLUGIN_NAME9);
13815
13973
  const runtimeMode = resolveInjectionMode(sharedClient2);
13816
- logLifecycle(PLUGIN_NAME10, "activate", {
13974
+ logLifecycle(PLUGIN_NAME9, "activate", {
13817
13975
  directory: ctx.directory,
13818
13976
  minConfidence: DEFAULT_CONFIG5.minConfidence,
13819
13977
  timeoutMs: DEFAULT_CONFIG5.timeoutMs,
@@ -13827,7 +13985,7 @@ var khAutoContextServer = async (ctx) => {
13827
13985
  });
13828
13986
  return {
13829
13987
  "chat.message": async (input, output) => {
13830
- await safeAsync(PLUGIN_NAME10, "chat.message", async () => {
13988
+ await safeAsync(PLUGIN_NAME9, "chat.message", async () => {
13831
13989
  const text = extractUserText(output);
13832
13990
  if (!text)
13833
13991
  return;
@@ -13845,10 +14003,25 @@ var khAutoContextServer = async (ctx) => {
13845
14003
  log: log6
13846
14004
  }, { client: sharedClient2, cache: sharedCache, mode: runtimeMode });
13847
14005
  });
14006
+ },
14007
+ event: async ({ event }) => {
14008
+ await safeAsync(PLUGIN_NAME9, "event", async () => {
14009
+ const e = event;
14010
+ if (e.type !== "session.idle")
14011
+ return;
14012
+ const props = e.properties;
14013
+ const sid = props?.sessionID;
14014
+ if (typeof sid !== "string" || !sid)
14015
+ return;
14016
+ sharedKhCache.onSessionEnd(sid);
14017
+ log6.debug?.(`[${PLUGIN_NAME9}] session.idle: cleared shared cache`, {
14018
+ sessionID: sid
14019
+ });
14020
+ });
13848
14021
  }
13849
14022
  };
13850
14023
  };
13851
- var handler10 = khAutoContextServer;
14024
+ var handler9 = khAutoContextServer;
13852
14025
 
13853
14026
  // plugins/kh-reminder.ts
13854
14027
  init_opencode_plugin_helpers();
@@ -13969,8 +14142,8 @@ async function condense(input, opts = {}) {
13969
14142
  }
13970
14143
 
13971
14144
  // plugins/kh-reminder.ts
13972
- var PLUGIN_NAME11 = "kh-reminder";
13973
- logLifecycle(PLUGIN_NAME11, "import", {});
14145
+ var PLUGIN_NAME10 = "kh-reminder";
14146
+ logLifecycle(PLUGIN_NAME10, "import", {});
13974
14147
  var TRIGGER_WORDS_ZH = [
13975
14148
  "怎么",
13976
14149
  "怎样",
@@ -14124,7 +14297,7 @@ function handleObserve(raw, log6) {
14124
14297
  const result = evaluate(ctx);
14125
14298
  if (result.triggered && result.reason) {
14126
14299
  const reasonStr = formatReason(result.reason);
14127
- log6?.info(`[${PLUGIN_NAME11}] ⚡ KH 提醒(observe-only) · session=${result.sessionId} · ${reasonStr}`, {
14300
+ log6?.info(`[${PLUGIN_NAME10}] ⚡ KH 提醒(observe-only) · session=${result.sessionId} · ${reasonStr}`, {
14128
14301
  sessionId: result.sessionId,
14129
14302
  reason: result.reason,
14130
14303
  suggestion: "调用 smart_search 查项目历史;完成后用 save_chat_insight 沉淀"
@@ -14132,7 +14305,7 @@ function handleObserve(raw, log6) {
14132
14305
  }
14133
14306
  return result;
14134
14307
  } catch (err) {
14135
- log6?.warn(`[${PLUGIN_NAME11}] evaluate 异常(已隔离)`, {
14308
+ log6?.warn(`[${PLUGIN_NAME10}] evaluate 异常(已隔离)`, {
14136
14309
  error: err instanceof Error ? err.message : String(err)
14137
14310
  });
14138
14311
  return null;
@@ -14148,9 +14321,9 @@ function formatReason(r) {
14148
14321
  return `no-search-in-recent-rounds (last ${r.rounds})`;
14149
14322
  }
14150
14323
  }
14151
- var log6 = makePluginLogger(PLUGIN_NAME11);
14324
+ var log6 = makePluginLogger(PLUGIN_NAME10);
14152
14325
  var khReminderServer = async (ctx) => {
14153
- logLifecycle(PLUGIN_NAME11, "activate", {
14326
+ logLifecycle(PLUGIN_NAME10, "activate", {
14154
14327
  directory: ctx.directory,
14155
14328
  threshold: DEFAULT_THRESHOLD,
14156
14329
  cooldown_ms: COOLDOWN_MS,
@@ -14158,7 +14331,7 @@ var khReminderServer = async (ctx) => {
14158
14331
  });
14159
14332
  return {
14160
14333
  "experimental.chat.messages.transform": async (_input, output) => {
14161
- await safeAsync(PLUGIN_NAME11, "experimental.chat.messages.transform", async () => {
14334
+ await safeAsync(PLUGIN_NAME10, "experimental.chat.messages.transform", async () => {
14162
14335
  const list = output.messages;
14163
14336
  if (!Array.isArray(list) || list.length === 0)
14164
14337
  return;
@@ -14177,7 +14350,7 @@ var khReminderServer = async (ctx) => {
14177
14350
  const result = handleObserve({ messages: flat, sessionId }, log6);
14178
14351
  if (!result)
14179
14352
  return;
14180
- safeWriteLog(PLUGIN_NAME11, {
14353
+ safeWriteLog(PLUGIN_NAME10, {
14181
14354
  hook: "experimental.chat.messages.transform",
14182
14355
  mode: "observe-only",
14183
14356
  sessionId: result.sessionId,
@@ -14190,7 +14363,7 @@ var khReminderServer = async (ctx) => {
14190
14363
  }
14191
14364
  };
14192
14365
  };
14193
- var handler11 = khReminderServer;
14366
+ var handler10 = khReminderServer;
14194
14367
 
14195
14368
  // lib/memories.ts
14196
14369
  import { promises as fs8 } from "node:fs";
@@ -14392,7 +14565,7 @@ function bagOfWordsScore(query, doc) {
14392
14565
 
14393
14566
  // plugins/memories-context.ts
14394
14567
  init_opencode_plugin_helpers();
14395
- var PLUGIN_NAME12 = "memories-context";
14568
+ var PLUGIN_NAME11 = "memories-context";
14396
14569
  var INJECTION_MODE2 = "observe-only";
14397
14570
  var DEFAULT_CONFIG6 = {
14398
14571
  minTextLength: 8,
@@ -14490,14 +14663,14 @@ async function handleMessage3(raw, opts) {
14490
14663
  return await handleDirective(dir, ctx, opts.memCfg, log7);
14491
14664
  }
14492
14665
  if (!shouldRecall(text, cfg)) {
14493
- log7?.debug?.(`[${PLUGIN_NAME12}] skip (filter)`, { textLen: text.length });
14666
+ log7?.debug?.(`[${PLUGIN_NAME11}] skip (filter)`, { textLen: text.length });
14494
14667
  return { kind: "noop", reason: "filtered", mode: INJECTION_MODE2 };
14495
14668
  }
14496
14669
  const query = extractQuery2(text);
14497
14670
  if (!query)
14498
14671
  return { kind: "noop", reason: "empty_query", mode: INJECTION_MODE2 };
14499
14672
  if (cache2.shouldSkip(query)) {
14500
- log7?.debug?.(`[${PLUGIN_NAME12}] cache hit`, { query });
14673
+ log7?.debug?.(`[${PLUGIN_NAME11}] cache hit`, { query });
14501
14674
  return { kind: "noop", reason: "cache", mode: INJECTION_MODE2 };
14502
14675
  }
14503
14676
  const racer = opts.scheduler?.raceTimeout ?? (async (p, ms) => {
@@ -14522,7 +14695,7 @@ async function handleMessage3(raw, opts) {
14522
14695
  }, opts.memCfg);
14523
14696
  const result = await racer(injectPromise, cfg.timeoutMs);
14524
14697
  if (result === "__timeout__") {
14525
- log7?.warn(`[${PLUGIN_NAME12}] timeout`, { query, ms: cfg.timeoutMs });
14698
+ log7?.warn(`[${PLUGIN_NAME11}] timeout`, { query, ms: cfg.timeoutMs });
14526
14699
  cache2.record(query, 0);
14527
14700
  return { kind: "noop", reason: "timeout", mode: INJECTION_MODE2 };
14528
14701
  }
@@ -14534,13 +14707,13 @@ async function handleMessage3(raw, opts) {
14534
14707
  try {
14535
14708
  await ctx.injectContext(result.text);
14536
14709
  } catch (err) {
14537
- log7?.warn(`[${PLUGIN_NAME12}] injectContext threw`, {
14710
+ log7?.warn(`[${PLUGIN_NAME11}] injectContext threw`, {
14538
14711
  error: err instanceof Error ? err.message : String(err)
14539
14712
  });
14540
14713
  }
14541
14714
  }
14542
14715
  cache2.record(query, result.recalled);
14543
- log7?.info(`[${PLUGIN_NAME12}] observe-only: ${result.used}/${result.recalled} memories ready`, { query, mode: INJECTION_MODE2 });
14716
+ log7?.info(`[${PLUGIN_NAME11}] observe-only: ${result.used}/${result.recalled} memories ready`, { query, mode: INJECTION_MODE2 });
14544
14717
  return { kind: "injected", payload: result, mode: INJECTION_MODE2 };
14545
14718
  }
14546
14719
  async function handleDirective(dir, ctx, memCfg, log7) {
@@ -14549,14 +14722,14 @@ async function handleDirective(dir, ctx, memCfg, log7) {
14549
14722
  if (r.ok) {
14550
14723
  const target = r.written_to === "kh" ? "KH" : "本地";
14551
14724
  await safeReply(ctx, `\uD83E\uDDE0 已记住(${dir.scope} / ${target},id=${r.id})`);
14552
- log7?.info(`[${PLUGIN_NAME12}] /remember ok`, {
14725
+ log7?.info(`[${PLUGIN_NAME11}] /remember ok`, {
14553
14726
  scope: dir.scope,
14554
14727
  id: r.id,
14555
14728
  mode: INJECTION_MODE2
14556
14729
  });
14557
14730
  } else {
14558
14731
  await safeReply(ctx, `❌ 记忆失败:${r.error ?? "unknown"}`);
14559
- log7?.warn(`[${PLUGIN_NAME12}] /remember failed`, { error: r.error });
14732
+ log7?.warn(`[${PLUGIN_NAME11}] /remember failed`, { error: r.error });
14560
14733
  }
14561
14734
  return { kind: "remembered", result: r, mode: INJECTION_MODE2 };
14562
14735
  }
@@ -14587,22 +14760,22 @@ async function safeReply(ctx, text) {
14587
14760
  await ctx.reply(text);
14588
14761
  } catch {}
14589
14762
  }
14590
- logLifecycle(PLUGIN_NAME12, "import");
14763
+ logLifecycle(PLUGIN_NAME11, "import");
14591
14764
  var sharedCache2 = new QueryCache2(DEFAULT_CONFIG6.cacheTtlMs);
14592
14765
  function buildMemCfg(directory) {
14593
14766
  return { projectRoot: directory };
14594
14767
  }
14595
14768
  var memoriesContextServer = async (ctx) => {
14596
- const log7 = makePluginLogger(PLUGIN_NAME12);
14769
+ const log7 = makePluginLogger(PLUGIN_NAME11);
14597
14770
  const memCfg = buildMemCfg(ctx.directory);
14598
- logLifecycle(PLUGIN_NAME12, "activate", {
14771
+ logLifecycle(PLUGIN_NAME11, "activate", {
14599
14772
  directory: ctx.directory,
14600
14773
  projectRoot: memCfg.projectRoot,
14601
14774
  mode: INJECTION_MODE2
14602
14775
  });
14603
14776
  return {
14604
14777
  "chat.message": async (input, output) => {
14605
- await safeAsync(PLUGIN_NAME12, "chat.message", async () => {
14778
+ await safeAsync(PLUGIN_NAME11, "chat.message", async () => {
14606
14779
  const text = extractUserText(output);
14607
14780
  if (!text)
14608
14781
  return;
@@ -14628,18 +14801,18 @@ var memoriesContextServer = async (ctx) => {
14628
14801
  }
14629
14802
  };
14630
14803
  };
14631
- var handler12 = memoriesContextServer;
14804
+ var handler11 = memoriesContextServer;
14632
14805
 
14633
14806
  // plugins/model-fallback.ts
14634
14807
  init_opencode_plugin_helpers();
14635
- var PLUGIN_NAME13 = "model-fallback";
14808
+ var PLUGIN_NAME12 = "model-fallback";
14636
14809
  var state2 = {
14637
14810
  config: null,
14638
14811
  configPath: null,
14639
14812
  warnings: [],
14640
14813
  error: null
14641
14814
  };
14642
- logLifecycle(PLUGIN_NAME13, "import");
14815
+ logLifecycle(PLUGIN_NAME12, "import");
14643
14816
  function loadOnce(root) {
14644
14817
  if (state2.config !== null || state2.error !== null)
14645
14818
  return;
@@ -14649,11 +14822,11 @@ function loadOnce(root) {
14649
14822
  state2.configPath = r.path ?? null;
14650
14823
  state2.warnings = r.warnings;
14651
14824
  if (r.warnings.length > 0) {
14652
- safeWriteLog(PLUGIN_NAME13, { phase: "load.warnings", warnings: r.warnings });
14825
+ safeWriteLog(PLUGIN_NAME12, { phase: "load.warnings", warnings: r.warnings });
14653
14826
  }
14654
14827
  } else {
14655
14828
  state2.error = r.error ?? "unknown_load_error";
14656
- safeWriteLog(PLUGIN_NAME13, { phase: "load.failed", error: state2.error, path: r.path });
14829
+ safeWriteLog(PLUGIN_NAME12, { phase: "load.failed", error: state2.error, path: r.path });
14657
14830
  }
14658
14831
  }
14659
14832
  var MODEL_ERR_PATTERNS = [
@@ -14698,9 +14871,9 @@ fallback 链已用尽:${meta.chain.join(" → ")}`;
14698
14871
  完整链:${meta.chain.join(" → ")}`;
14699
14872
  }
14700
14873
  var modelFallbackServer = async (ctx) => {
14701
- const log7 = makePluginLogger(PLUGIN_NAME13);
14874
+ const log7 = makePluginLogger(PLUGIN_NAME12);
14702
14875
  loadOnce(ctx.directory ?? process.cwd());
14703
- logLifecycle(PLUGIN_NAME13, "activate", {
14876
+ logLifecycle(PLUGIN_NAME12, "activate", {
14704
14877
  directory: ctx.directory,
14705
14878
  config_path: state2.configPath,
14706
14879
  config_loaded: state2.config !== null,
@@ -14710,7 +14883,7 @@ var modelFallbackServer = async (ctx) => {
14710
14883
  });
14711
14884
  return {
14712
14885
  "chat.params": async (input, output) => {
14713
- await safeAsync(PLUGIN_NAME13, "chat.params", async () => {
14886
+ await safeAsync(PLUGIN_NAME12, "chat.params", async () => {
14714
14887
  if (!state2.config)
14715
14888
  return;
14716
14889
  const agent = input.agent;
@@ -14731,7 +14904,7 @@ var modelFallbackServer = async (ctx) => {
14731
14904
  next: meta.next_fallback,
14732
14905
  source: meta.source
14733
14906
  };
14734
- safeWriteLog(PLUGIN_NAME13, {
14907
+ safeWriteLog(PLUGIN_NAME12, {
14735
14908
  hook: "chat.params",
14736
14909
  agent,
14737
14910
  model: currentModel,
@@ -14741,7 +14914,7 @@ var modelFallbackServer = async (ctx) => {
14741
14914
  });
14742
14915
  },
14743
14916
  event: async ({ event }) => {
14744
- await safeAsync(PLUGIN_NAME13, "event", async () => {
14917
+ await safeAsync(PLUGIN_NAME12, "event", async () => {
14745
14918
  if (!state2.config)
14746
14919
  return;
14747
14920
  const e = event;
@@ -14757,8 +14930,8 @@ var modelFallbackServer = async (ctx) => {
14757
14930
  const model = props.model ?? "unknown/unknown";
14758
14931
  const meta = buildFallbackMeta(state2.config, agent, model);
14759
14932
  const suggestion = meta ? buildSuggestion(meta, String(message)) : `⚠️ ${agent}/${model} 失败:${message}`;
14760
- log7.warn(`[${PLUGIN_NAME13}] ${suggestion}`);
14761
- safeWriteLog(PLUGIN_NAME13, {
14933
+ log7.warn(`[${PLUGIN_NAME12}] ${suggestion}`);
14934
+ safeWriteLog(PLUGIN_NAME12, {
14762
14935
  hook: "event.error",
14763
14936
  eventType: e.type,
14764
14937
  agent,
@@ -14770,19 +14943,24 @@ var modelFallbackServer = async (ctx) => {
14770
14943
  }
14771
14944
  };
14772
14945
  };
14773
- var handler13 = modelFallbackServer;
14946
+ var handler12 = modelFallbackServer;
14774
14947
 
14775
14948
  // plugins/subtask-heartbeat.ts
14776
14949
  init_opencode_plugin_helpers();
14777
- var PLUGIN_NAME14 = "subtask-heartbeat";
14778
- logLifecycle(PLUGIN_NAME14, "import", {});
14950
+ var PLUGIN_NAME13 = "subtask-heartbeat";
14951
+ logLifecycle(PLUGIN_NAME13, "import", {});
14779
14952
  var HEARTBEAT_INTERVAL_MS2 = 30000;
14780
14953
  var HEARTBEAT_DEBOUNCE_MS = 25000;
14781
14954
  var TOAST_DURATION_MS3 = 5000;
14782
14955
  var START_TOAST_DURATION_MS = 2000;
14783
- var inflight2 = new Map;
14956
+ var PENDING_TASK_TTL_MS = 60000;
14957
+ var PENDING_TASK_MAX_PARENTS = 64;
14958
+ var PENDING_TASK_MAX_PER_PARENT = 16;
14959
+ var DESCRIPTION_MAX_LEN = 60;
14960
+ var inflight3 = new Map;
14961
+ var pendingTask = new Map;
14784
14962
  function _snapshotInflight() {
14785
- return [...inflight2.values()].map((r) => ({ ...r }));
14963
+ return [...inflight3.values()].map((r) => ({ ...r }));
14786
14964
  }
14787
14965
  function getInflightSnapshot() {
14788
14966
  return _snapshotInflight();
@@ -14824,20 +15002,97 @@ function extractEndedSessionID(event) {
14824
15002
  }
14825
15003
  return null;
14826
15004
  }
15005
+ function extractSubtaskPart(event) {
15006
+ if (!event || typeof event !== "object")
15007
+ return null;
15008
+ const e = event;
15009
+ if (e.type !== "message.part.updated")
15010
+ return null;
15011
+ const part = e.properties?.part;
15012
+ if (!part || typeof part !== "object")
15013
+ return null;
15014
+ const p = part;
15015
+ if (p.type !== "subtask")
15016
+ return null;
15017
+ if (typeof p.sessionID !== "string" || p.sessionID === "")
15018
+ return null;
15019
+ if (typeof p.agent !== "string" || p.agent === "")
15020
+ return null;
15021
+ if (typeof p.description !== "string" || p.description === "")
15022
+ return null;
15023
+ return { parentID: p.sessionID, agent: p.agent, description: p.description };
15024
+ }
15025
+ function enqueuePendingTask(parentID, entry, now = Date.now()) {
15026
+ const ts = entry.ts ?? now;
15027
+ let bucket = pendingTask.get(parentID);
15028
+ if (!bucket) {
15029
+ if (pendingTask.size >= PENDING_TASK_MAX_PARENTS) {
15030
+ let oldestKey = null;
15031
+ let oldestTs = Number.POSITIVE_INFINITY;
15032
+ for (const [k, v] of pendingTask.entries()) {
15033
+ const headTs = v[0]?.ts ?? Number.POSITIVE_INFINITY;
15034
+ if (headTs < oldestTs) {
15035
+ oldestTs = headTs;
15036
+ oldestKey = k;
15037
+ }
15038
+ }
15039
+ if (oldestKey !== null)
15040
+ pendingTask.delete(oldestKey);
15041
+ }
15042
+ bucket = [];
15043
+ pendingTask.set(parentID, bucket);
15044
+ }
15045
+ bucket.push({ agent: entry.agent, description: entry.description, ts });
15046
+ while (bucket.length > PENDING_TASK_MAX_PER_PARENT) {
15047
+ bucket.shift();
15048
+ }
15049
+ }
15050
+ function dequeuePendingTask(parentID, now = Date.now()) {
15051
+ const bucket = pendingTask.get(parentID);
15052
+ if (!bucket || bucket.length === 0) {
15053
+ if (bucket)
15054
+ pendingTask.delete(parentID);
15055
+ return null;
15056
+ }
15057
+ while (bucket.length > 0 && now - bucket[0].ts > PENDING_TASK_TTL_MS) {
15058
+ bucket.shift();
15059
+ }
15060
+ if (bucket.length === 0) {
15061
+ pendingTask.delete(parentID);
15062
+ return null;
15063
+ }
15064
+ const entry = bucket.shift();
15065
+ if (bucket.length === 0)
15066
+ pendingTask.delete(parentID);
15067
+ return entry;
15068
+ }
15069
+ function sweepExpiredPendingTasks(now = Date.now()) {
15070
+ let removed = 0;
15071
+ for (const [parentID, bucket] of [...pendingTask.entries()]) {
15072
+ while (bucket.length > 0 && now - bucket[0].ts > PENDING_TASK_TTL_MS) {
15073
+ bucket.shift();
15074
+ removed++;
15075
+ }
15076
+ if (bucket.length === 0)
15077
+ pendingTask.delete(parentID);
15078
+ }
15079
+ return removed;
15080
+ }
14827
15081
  function registerInflight(payload, now = Date.now()) {
14828
15082
  const r = {
14829
15083
  childID: payload.childID,
14830
15084
  parentID: payload.parentID,
14831
15085
  agent: payload.agent,
15086
+ description: payload.description ?? null,
14832
15087
  startedAt: now,
14833
15088
  lastBeatAt: now,
14834
15089
  lastTool: null
14835
15090
  };
14836
- inflight2.set(payload.childID, r);
15091
+ inflight3.set(payload.childID, r);
14837
15092
  return r;
14838
15093
  }
14839
15094
  function recordToolBeat(sessionID, tool2, now = Date.now()) {
14840
- const r = inflight2.get(sessionID);
15095
+ const r = inflight3.get(sessionID);
14841
15096
  if (!r)
14842
15097
  return null;
14843
15098
  r.lastBeatAt = now;
@@ -14845,15 +15100,15 @@ function recordToolBeat(sessionID, tool2, now = Date.now()) {
14845
15100
  return r;
14846
15101
  }
14847
15102
  function clearInflight2(sessionID) {
14848
- const r = inflight2.get(sessionID);
15103
+ const r = inflight3.get(sessionID);
14849
15104
  if (!r)
14850
15105
  return null;
14851
- inflight2.delete(sessionID);
15106
+ inflight3.delete(sessionID);
14852
15107
  return r;
14853
15108
  }
14854
15109
  function pickHeartbeats(now = Date.now()) {
14855
15110
  const out = [];
14856
- for (const r of inflight2.values()) {
15111
+ for (const r of inflight3.values()) {
14857
15112
  if (now - r.lastBeatAt >= HEARTBEAT_DEBOUNCE_MS)
14858
15113
  out.push(r);
14859
15114
  }
@@ -14865,10 +15120,32 @@ function fmtElapsed(ms) {
14865
15120
  const s = total % 60;
14866
15121
  return m > 0 ? `${m}m${s.toString().padStart(2, "0")}s` : `${s}s`;
14867
15122
  }
15123
+ function titleCase(s) {
15124
+ if (!s)
15125
+ return s;
15126
+ return s.charAt(0).toUpperCase() + s.slice(1);
15127
+ }
15128
+ function sanitizeDescription(s) {
15129
+ const collapsed = s.replace(/[\r\n\t]+/g, " ").replace(/\s+/g, " ").trim();
15130
+ if (collapsed.length <= DESCRIPTION_MAX_LEN)
15131
+ return collapsed;
15132
+ return collapsed.slice(0, DESCRIPTION_MAX_LEN) + "…";
15133
+ }
14868
15134
  function buildStartToast(r) {
14869
- const who = r.agent ?? "subagent";
15135
+ if (r.agent && r.description) {
15136
+ return {
15137
+ message: `\uD83D\uDE80 ${titleCase(r.agent)} 启动 — ${sanitizeDescription(r.description)}`,
15138
+ variant: "info"
15139
+ };
15140
+ }
15141
+ if (r.agent) {
15142
+ return {
15143
+ message: `\uD83D\uDE80 ${titleCase(r.agent)} 启动`,
15144
+ variant: "info"
15145
+ };
15146
+ }
14870
15147
  return {
14871
- message: `\uD83D\uDE80 子 session 启动: ${who}`,
15148
+ message: `\uD83D\uDE80 子 session 启动: subagent`,
14872
15149
  variant: "info"
14873
15150
  };
14874
15151
  }
@@ -14881,15 +15158,32 @@ function buildHeartbeatToast(r, now = Date.now()) {
14881
15158
  };
14882
15159
  }
14883
15160
  function buildEndToast(r, type, now = Date.now()) {
14884
- const who = r.agent ?? "subagent";
14885
15161
  const elapsed = fmtElapsed(now - r.startedAt);
14886
- if (type === "session.error") {
14887
- return { message: `❌ ${who} 失败 (${elapsed})`, variant: "error" };
15162
+ const variant = type === "session.error" || type === "session.deleted" ? "error" : "success";
15163
+ const emoji = type === "session.error" ? "❌" : type === "session.deleted" ? "\uD83D\uDDD1️" : "";
15164
+ const verb = type === "session.error" ? "失败" : type === "session.deleted" ? "被取消" : "完成";
15165
+ if (r.agent && r.description) {
15166
+ if (type === "session.idle" || type !== "session.error" && type !== "session.deleted") {
15167
+ return {
15168
+ message: `${emoji} ${titleCase(r.agent)} Task — ${sanitizeDescription(r.description)} (${elapsed})`,
15169
+ variant
15170
+ };
15171
+ }
15172
+ return {
15173
+ message: `${emoji} ${titleCase(r.agent)} ${verb} — ${sanitizeDescription(r.description)} (${elapsed})`,
15174
+ variant
15175
+ };
14888
15176
  }
14889
- if (type === "session.deleted") {
14890
- return { message: `\uD83D\uDDD1️ ${who} 被取消 (${elapsed})`, variant: "error" };
15177
+ if (r.agent) {
15178
+ return {
15179
+ message: `${emoji} ${titleCase(r.agent)} ${verb} (${elapsed})`,
15180
+ variant
15181
+ };
14891
15182
  }
14892
- return { message: `✅ ${who} 完成 (${elapsed})`, variant: "success" };
15183
+ return {
15184
+ message: `${emoji} subagent ${verb} (${elapsed})`,
15185
+ variant
15186
+ };
14893
15187
  }
14894
15188
  function normalizeVariant3(raw) {
14895
15189
  if (raw === "info" || raw === "warning")
@@ -14918,22 +15212,26 @@ async function showToast3(client, payload, log7) {
14918
15212
  return false;
14919
15213
  }
14920
15214
  }
14921
- var log7 = makePluginLogger(PLUGIN_NAME14);
15215
+ var log7 = makePluginLogger(PLUGIN_NAME13);
14922
15216
  var subtaskHeartbeatServer = async (ctx) => {
14923
- logLifecycle(PLUGIN_NAME14, "activate", {
15217
+ logLifecycle(PLUGIN_NAME13, "activate", {
14924
15218
  directory: ctx.directory,
14925
15219
  intervalMs: HEARTBEAT_INTERVAL_MS2
14926
15220
  });
14927
15221
  const client = ctx.client;
14928
15222
  const interval = setInterval(() => {
14929
- safeAsync(PLUGIN_NAME14, "interval", async () => {
15223
+ safeAsync(PLUGIN_NAME13, "interval", async () => {
15224
+ const swept = sweepExpiredPendingTasks();
15225
+ if (swept > 0) {
15226
+ safeWriteLog(PLUGIN_NAME13, { hook: "interval", pending_task_swept: swept });
15227
+ }
14930
15228
  const beats = pickHeartbeats();
14931
15229
  if (beats.length === 0)
14932
15230
  return;
14933
15231
  for (const r of beats) {
14934
15232
  const t = buildHeartbeatToast(r);
14935
15233
  const sent = await showToast3(client, t, log7);
14936
- safeWriteLog(PLUGIN_NAME14, {
15234
+ safeWriteLog(PLUGIN_NAME13, {
14937
15235
  hook: "interval",
14938
15236
  child: r.childID,
14939
15237
  parent: r.parentID,
@@ -14950,23 +15248,47 @@ var subtaskHeartbeatServer = async (ctx) => {
14950
15248
  }
14951
15249
  return {
14952
15250
  event: async ({ event }) => {
14953
- await safeAsync(PLUGIN_NAME14, "event", async () => {
15251
+ await safeAsync(PLUGIN_NAME13, "event", async () => {
15252
+ const subtask = extractSubtaskPart(event);
15253
+ if (subtask) {
15254
+ enqueuePendingTask(subtask.parentID, {
15255
+ agent: subtask.agent,
15256
+ description: subtask.description
15257
+ });
15258
+ safeWriteLog(PLUGIN_NAME13, {
15259
+ hook: "event",
15260
+ type: "message.part.updated.subtask",
15261
+ parent: subtask.parentID,
15262
+ agent: subtask.agent,
15263
+ description_len: subtask.description.length
15264
+ });
15265
+ return;
15266
+ }
14954
15267
  const created = extractCreatedChild(event);
14955
15268
  if (created) {
14956
- const record = registerInflight(created);
14957
- safeWriteLog(PLUGIN_NAME14, {
15269
+ const pending = dequeuePendingTask(created.parentID);
15270
+ const record = registerInflight({
15271
+ childID: created.childID,
15272
+ parentID: created.parentID,
15273
+ agent: pending?.agent ?? created.agent,
15274
+ description: pending?.description ?? null
15275
+ });
15276
+ safeWriteLog(PLUGIN_NAME13, {
14958
15277
  hook: "event",
14959
15278
  type: "session.created",
14960
15279
  child: created.childID,
14961
- parent: created.parentID
15280
+ parent: created.parentID,
15281
+ pending_task_matched: pending !== null,
15282
+ agent: record.agent
14962
15283
  });
14963
15284
  const startToast = buildStartToast(record);
14964
15285
  const sent = await showToast3(client, { ...startToast, duration: START_TOAST_DURATION_MS }, log7);
14965
- safeWriteLog(PLUGIN_NAME14, {
15286
+ safeWriteLog(PLUGIN_NAME13, {
14966
15287
  hook: "event",
14967
15288
  type: "session.created.toast",
14968
15289
  child: created.childID,
14969
- toast_sent: sent
15290
+ toast_sent: sent,
15291
+ start_toast_message: startToast.message
14970
15292
  });
14971
15293
  return;
14972
15294
  }
@@ -14976,7 +15298,7 @@ var subtaskHeartbeatServer = async (ctx) => {
14976
15298
  if (r) {
14977
15299
  const t = buildEndToast(r, ended.type);
14978
15300
  const sent = await showToast3(client, t, log7);
14979
- safeWriteLog(PLUGIN_NAME14, {
15301
+ safeWriteLog(PLUGIN_NAME13, {
14980
15302
  hook: "event",
14981
15303
  type: ended.type,
14982
15304
  child: r.childID,
@@ -14989,7 +15311,9 @@ var subtaskHeartbeatServer = async (ctx) => {
14989
15311
  });
14990
15312
  },
14991
15313
  "tool.execute.before": async (input) => {
14992
- await safeAsync(PLUGIN_NAME14, "tool.execute.before", async () => {
15314
+ if (inflight3.size === 0)
15315
+ return;
15316
+ await safeAsync(PLUGIN_NAME13, "tool.execute.before", async () => {
14993
15317
  if (!input || typeof input.sessionID !== "string" || typeof input.tool !== "string")
14994
15318
  return;
14995
15319
  recordToolBeat(input.sessionID, input.tool);
@@ -14997,12 +15321,12 @@ var subtaskHeartbeatServer = async (ctx) => {
14997
15321
  }
14998
15322
  };
14999
15323
  };
15000
- var handler14 = subtaskHeartbeatServer;
15324
+ var handler13 = subtaskHeartbeatServer;
15001
15325
 
15002
15326
  // plugins/parallel-status.ts
15003
15327
  init_opencode_plugin_helpers();
15004
- var PLUGIN_NAME15 = "parallel-status";
15005
- logLifecycle(PLUGIN_NAME15, "import");
15328
+ var PLUGIN_NAME14 = "parallel-status";
15329
+ logLifecycle(PLUGIN_NAME14, "import");
15006
15330
  var ID_MAX_LEN = 16;
15007
15331
  var ID_KEEP_LEN = 13;
15008
15332
  function shortId(s) {
@@ -15034,8 +15358,8 @@ function formatInflightMarkdown(snapshot, now = Date.now()) {
15034
15358
  `);
15035
15359
  }
15036
15360
  var parallelStatusServer = async (ctx) => {
15037
- const log8 = makePluginLogger(PLUGIN_NAME15);
15038
- logLifecycle(PLUGIN_NAME15, "activate", { directory: ctx.directory });
15361
+ const log8 = makePluginLogger(PLUGIN_NAME14);
15362
+ logLifecycle(PLUGIN_NAME14, "activate", { directory: ctx.directory });
15039
15363
  return {
15040
15364
  "command.execute.before": async (input, output) => {
15041
15365
  try {
@@ -15046,7 +15370,7 @@ var parallelStatusServer = async (ctx) => {
15046
15370
  if (Array.isArray(output?.parts)) {
15047
15371
  output.parts.length = 0;
15048
15372
  output.parts.push({
15049
- id: `parallel-status-${Date.now()}`,
15373
+ id: makePartId(),
15050
15374
  sessionID: input.sessionID,
15051
15375
  messageID: "",
15052
15376
  type: "text",
@@ -15054,21 +15378,21 @@ var parallelStatusServer = async (ctx) => {
15054
15378
  synthetic: false
15055
15379
  });
15056
15380
  }
15057
- log8.info(`[${PLUGIN_NAME15}] 已回写 ${snapshot.length} 条 inflight`);
15381
+ log8.info(`[${PLUGIN_NAME14}] 已回写 ${snapshot.length} 条 inflight`);
15058
15382
  } catch (err) {
15059
- log8.error(`[${PLUGIN_NAME15}] command.execute.before 异常(已隔离)`, {
15383
+ log8.error(`[${PLUGIN_NAME14}] command.execute.before 异常(已隔离)`, {
15060
15384
  error: err instanceof Error ? err.message : String(err)
15061
15385
  });
15062
15386
  }
15063
15387
  }
15064
15388
  };
15065
15389
  };
15066
- var handler15 = parallelStatusServer;
15390
+ var handler14 = parallelStatusServer;
15067
15391
 
15068
15392
  // plugins/pwsh-utf8.ts
15069
15393
  init_opencode_plugin_helpers();
15070
- var PLUGIN_NAME16 = "pwsh-utf8";
15071
- logLifecycle(PLUGIN_NAME16, "import", {});
15394
+ var PLUGIN_NAME15 = "pwsh-utf8";
15395
+ logLifecycle(PLUGIN_NAME15, "import", {});
15072
15396
  var PRELUDE = "chcp 65001 *> $null; " + "[Console]::OutputEncoding = [System.Text.UTF8Encoding]::new(); " + "$OutputEncoding = [System.Text.UTF8Encoding]::new(); ";
15073
15397
  function prependUtf8Prelude(command) {
15074
15398
  if (typeof command !== "string")
@@ -15081,14 +15405,15 @@ function prependUtf8Prelude(command) {
15081
15405
  return command;
15082
15406
  return PRELUDE + command;
15083
15407
  }
15084
- var handler16 = async (_ctx) => {
15408
+ var handler15 = async (_ctx) => {
15085
15409
  const enabled = process.platform === "win32" && process.env.CODEFORGE_DISABLE_PWSH_UTF8 !== "1";
15086
- logLifecycle(PLUGIN_NAME16, "activate", { enabled, platform: process.platform });
15410
+ const reason = enabled ? "win32" : process.platform !== "win32" ? "non-win32" : "disabled-by-env";
15411
+ logLifecycle(PLUGIN_NAME15, "activate", { enabled, platform: process.platform, reason });
15087
15412
  if (!enabled)
15088
15413
  return {};
15089
15414
  return {
15090
15415
  "tool.execute.before": async (input, output) => {
15091
- await safeAsync(PLUGIN_NAME16, "tool.execute.before", async () => {
15416
+ await safeAsync(PLUGIN_NAME15, "tool.execute.before", async () => {
15092
15417
  if (input.tool !== "bash")
15093
15418
  return;
15094
15419
  const args = output.args ?? {};
@@ -15097,7 +15422,7 @@ var handler16 = async (_ctx) => {
15097
15422
  if (next !== undefined && next !== original) {
15098
15423
  args["command"] = next;
15099
15424
  output.args = args;
15100
- safeWriteLog(PLUGIN_NAME16, {
15425
+ safeWriteLog(PLUGIN_NAME15, {
15101
15426
  hook: "tool.execute.before",
15102
15427
  tool: input.tool,
15103
15428
  callID: input.callID,
@@ -15290,7 +15615,7 @@ function buildRecoveryPlan(events, meta = { id: null }, opts = {}) {
15290
15615
  });
15291
15616
  }
15292
15617
  }
15293
- const inflight3 = [...toolMap.values()].sort((a, b) => b.last_ts - a.last_ts);
15618
+ const inflight4 = [...toolMap.values()].sort((a, b) => b.last_ts - a.last_ts);
15294
15619
  const proposed = window.some((t) => PENDING_CHANGES_TOOLS.has(t.tool));
15295
15620
  const applied = window.some((t) => APPLY_TOOLS.has(t.tool) && t.ok);
15296
15621
  const pending_changes_likely = proposed && !applied;
@@ -15314,7 +15639,7 @@ function buildRecoveryPlan(events, meta = { id: null }, opts = {}) {
15314
15639
  idleMs,
15315
15640
  lastUser,
15316
15641
  lastAgent,
15317
- inflight: inflight3,
15642
+ inflight: inflight4,
15318
15643
  pending_changes_likely,
15319
15644
  open_subtasks_likely,
15320
15645
  reason
@@ -15325,7 +15650,7 @@ function buildRecoveryPlan(events, meta = { id: null }, opts = {}) {
15325
15650
  idle_ms: idleMs,
15326
15651
  last_user_intent: lastUser,
15327
15652
  last_agent: lastAgent,
15328
- inflight_tools: inflight3,
15653
+ inflight_tools: inflight4,
15329
15654
  pending_changes_likely,
15330
15655
  open_subtasks_likely,
15331
15656
  summary
@@ -15430,8 +15755,8 @@ function isRecoveryWorthShowing(plan) {
15430
15755
  }
15431
15756
 
15432
15757
  // plugins/session-recovery.ts
15433
- var PLUGIN_NAME17 = "session-recovery";
15434
- logLifecycle(PLUGIN_NAME17, "import", {});
15758
+ var PLUGIN_NAME16 = "session-recovery";
15759
+ logLifecycle(PLUGIN_NAME16, "import", {});
15435
15760
  async function processSessionStart(currentSessionId, opts = {}) {
15436
15761
  if (opts.disabled) {
15437
15762
  return { ok: true, injected: false, reason: "disabled" };
@@ -15441,7 +15766,7 @@ async function processSessionStart(currentSessionId, opts = {}) {
15441
15766
  excludeIds.add(currentSessionId);
15442
15767
  const r = await scanLastSession({ ...opts, excludeIds: [...excludeIds] });
15443
15768
  if (!r.ok) {
15444
- opts.log?.warn?.(`[${PLUGIN_NAME17}] 扫描失败:${r.error}`);
15769
+ opts.log?.warn?.(`[${PLUGIN_NAME16}] 扫描失败:${r.error}`);
15445
15770
  return { ok: false, injected: false, reason: "scan_error", error: r.error };
15446
15771
  }
15447
15772
  const plan = r.plan;
@@ -15455,7 +15780,7 @@ async function processSessionStart(currentSessionId, opts = {}) {
15455
15780
  await opts.injectRecovery(injection);
15456
15781
  } catch (err) {
15457
15782
  const msg = err instanceof Error ? err.message : String(err);
15458
- opts.log?.warn?.(`[${PLUGIN_NAME17}] injectRecovery 异常:${msg}`);
15783
+ opts.log?.warn?.(`[${PLUGIN_NAME16}] injectRecovery 异常:${msg}`);
15459
15784
  return { ok: false, injected: false, plan, reason: "inject_error", error: msg };
15460
15785
  }
15461
15786
  }
@@ -15484,13 +15809,13 @@ function renderPrompt(plan) {
15484
15809
  return lines.join(`
15485
15810
  `);
15486
15811
  }
15487
- var log8 = makePluginLogger(PLUGIN_NAME17);
15812
+ var log8 = makePluginLogger(PLUGIN_NAME16);
15488
15813
  var _lastInjection = null;
15489
15814
  var sessionRecoveryServer = async (ctx) => {
15490
- logLifecycle(PLUGIN_NAME17, "activate", { directory: ctx.directory });
15815
+ logLifecycle(PLUGIN_NAME16, "activate", { directory: ctx.directory });
15491
15816
  return {
15492
15817
  event: async ({ event }) => {
15493
- await safeAsync(PLUGIN_NAME17, "event", async () => {
15818
+ await safeAsync(PLUGIN_NAME16, "event", async () => {
15494
15819
  const e = event;
15495
15820
  if (!e || typeof e.type !== "string")
15496
15821
  return;
@@ -15505,7 +15830,7 @@ var sessionRecoveryServer = async (ctx) => {
15505
15830
  _lastInjection = inj;
15506
15831
  }
15507
15832
  });
15508
- safeWriteLog(PLUGIN_NAME17, {
15833
+ safeWriteLog(PLUGIN_NAME16, {
15509
15834
  hook: "event",
15510
15835
  type: "session.start",
15511
15836
  ok: r.ok,
@@ -15514,13 +15839,13 @@ var sessionRecoveryServer = async (ctx) => {
15514
15839
  last_session_id: r.plan?.last_session_id
15515
15840
  });
15516
15841
  if (r.injected && r.plan) {
15517
- log8.info(`[${PLUGIN_NAME17}] 注入恢复提示(last=${r.plan.last_session_id?.slice(0, 8) ?? "?"}, reason=${r.plan.reason})`);
15842
+ log8.info(`[${PLUGIN_NAME16}] 注入恢复提示(last=${r.plan.last_session_id?.slice(0, 8) ?? "?"}, reason=${r.plan.reason})`);
15518
15843
  }
15519
15844
  });
15520
15845
  }
15521
15846
  };
15522
15847
  };
15523
- var handler17 = sessionRecoveryServer;
15848
+ var handler16 = sessionRecoveryServer;
15524
15849
 
15525
15850
  // plugins/subtasks.ts
15526
15851
  import { promises as fs10 } from "node:fs";
@@ -15786,6 +16111,7 @@ function pickStatus(r, taskAborted, globalAborted) {
15786
16111
  }
15787
16112
 
15788
16113
  // lib/opencode-runner.ts
16114
+ init_opencode_plugin_helpers();
15789
16115
  function makeOpencodeRunner(opts) {
15790
16116
  const log9 = opts.log ?? (() => {});
15791
16117
  return async (spec, runCtx) => {
@@ -16013,17 +16339,20 @@ async function sendParentNotice(client, sessionID, text, opts = {}) {
16013
16339
  }
16014
16340
  try {
16015
16341
  const res = await sessionAny.promptAsync({
16016
- sessionID,
16017
- directory: opts.directory,
16018
- noReply: true,
16019
- parts: [
16020
- {
16021
- type: "text",
16022
- text,
16023
- synthetic: true,
16024
- ignored: true
16025
- }
16026
- ]
16342
+ path: { id: sessionID },
16343
+ query: opts.directory ? { directory: opts.directory } : undefined,
16344
+ body: {
16345
+ noReply: true,
16346
+ parts: [
16347
+ {
16348
+ id: makePartId(),
16349
+ type: "text",
16350
+ text,
16351
+ synthetic: false,
16352
+ ignored: false
16353
+ }
16354
+ ]
16355
+ }
16027
16356
  });
16028
16357
  if (res && typeof res === "object" && "error" in res && res.error) {
16029
16358
  log9("warn", "[sendParentNotice] promptAsync 返回 error", { error: res.error });
@@ -16041,7 +16370,7 @@ async function sendParentNotice(client, sessionID, text, opts = {}) {
16041
16370
  // plugins/subtasks.ts
16042
16371
  init_opencode_plugin_helpers();
16043
16372
  init_runtime_paths();
16044
- var PLUGIN_NAME18 = "subtasks";
16373
+ var PLUGIN_NAME17 = "subtasks";
16045
16374
  function getLogFile(root = process.cwd()) {
16046
16375
  return path13.join(runtimeDir(root), "logs", "subtasks.log");
16047
16376
  }
@@ -16120,7 +16449,7 @@ async function handleParallelCommand(raw) {
16120
16449
  specs = splitDescriptions(args.description, { parentId });
16121
16450
  }
16122
16451
  if (specs.length === 0) {
16123
- log9?.warn(`[${PLUGIN_NAME18}] /parallel 缺有效子任务`);
16452
+ log9?.warn(`[${PLUGIN_NAME17}] /parallel 缺有效子任务`);
16124
16453
  await safeReply2(ctx, "⚠ /parallel 需要至少 1 个子任务(用 ; 或换行分隔)");
16125
16454
  return { ok: false, reason: "no_subtasks" };
16126
16455
  }
@@ -16178,7 +16507,7 @@ async function handleParallelCommand(raw) {
16178
16507
  });
16179
16508
  } catch (err) {
16180
16509
  const msg = err instanceof Error ? err.message : String(err);
16181
- log9?.error(`[${PLUGIN_NAME18}] schedule 抛错`, { error: msg });
16510
+ log9?.error(`[${PLUGIN_NAME17}] schedule 抛错`, { error: msg });
16182
16511
  await safeReply2(ctx, `❌ 并发调度失败:${msg}`);
16183
16512
  return { ok: false, reason: msg };
16184
16513
  }
@@ -16186,7 +16515,7 @@ async function handleParallelCommand(raw) {
16186
16515
  const summaryLines = [head, "", "```", result.digest.text, "```"];
16187
16516
  await safeReply2(ctx, summaryLines.join(`
16188
16517
  `));
16189
- log9?.info(`[${PLUGIN_NAME18}] schedule 完成`, {
16518
+ log9?.info(`[${PLUGIN_NAME17}] schedule 完成`, {
16190
16519
  parentId,
16191
16520
  success: result.digest.success,
16192
16521
  failed: result.digest.failed,
@@ -16196,7 +16525,7 @@ async function handleParallelCommand(raw) {
16196
16525
  try {
16197
16526
  await ctx.onCompleted(result);
16198
16527
  } catch (err) {
16199
- log9?.warn(`[${PLUGIN_NAME18}] onCompleted hook 抛错(已隔离)`, {
16528
+ log9?.warn(`[${PLUGIN_NAME17}] onCompleted hook 抛错(已隔离)`, {
16200
16529
  error: err instanceof Error ? err.message : String(err)
16201
16530
  });
16202
16531
  }
@@ -16237,7 +16566,7 @@ async function writeLog(level, msg, data) {
16237
16566
  const line = JSON.stringify({
16238
16567
  ts: new Date().toISOString(),
16239
16568
  level,
16240
- plugin: PLUGIN_NAME18,
16569
+ plugin: PLUGIN_NAME17,
16241
16570
  msg,
16242
16571
  data
16243
16572
  }) + `
@@ -16248,11 +16577,11 @@ async function writeLog(level, msg, data) {
16248
16577
  await fs10.appendFile(logFile, line, "utf8");
16249
16578
  } catch {}
16250
16579
  }
16251
- logLifecycle(PLUGIN_NAME18, "import");
16580
+ logLifecycle(PLUGIN_NAME17, "import");
16252
16581
  var subtasksServer = async (ctx) => {
16253
- const log9 = makePluginLogger(PLUGIN_NAME18);
16582
+ const log9 = makePluginLogger(PLUGIN_NAME17);
16254
16583
  const client = ctx?.client ?? undefined;
16255
- logLifecycle(PLUGIN_NAME18, "activate", {
16584
+ logLifecycle(PLUGIN_NAME17, "activate", {
16256
16585
  directory: ctx.directory,
16257
16586
  hasClient: Boolean(client)
16258
16587
  });
@@ -16301,7 +16630,7 @@ var subtasksServer = async (ctx) => {
16301
16630
  if (replyLines.length > 0 && Array.isArray(output?.parts)) {
16302
16631
  output.parts.length = 0;
16303
16632
  output.parts.push({
16304
- id: `parallel-${Date.now()}`,
16633
+ id: makePartId(),
16305
16634
  sessionID: input.sessionID,
16306
16635
  messageID: "",
16307
16636
  type: "text",
@@ -16312,20 +16641,20 @@ var subtasksServer = async (ctx) => {
16312
16641
  });
16313
16642
  }
16314
16643
  } catch (err) {
16315
- log9.error(`[${PLUGIN_NAME18}] command.execute.before 异常(已隔离)`, {
16644
+ log9.error(`[${PLUGIN_NAME17}] command.execute.before 异常(已隔离)`, {
16316
16645
  error: err instanceof Error ? err.message : String(err)
16317
16646
  });
16318
16647
  }
16319
16648
  }
16320
16649
  };
16321
16650
  };
16322
- var handler18 = subtasksServer;
16651
+ var handler17 = subtasksServer;
16323
16652
 
16324
16653
  // plugins/terminal-monitor.ts
16325
16654
  init_opencode_plugin_helpers();
16326
16655
  import * as crypto5 from "node:crypto";
16327
- var PLUGIN_NAME19 = "terminal-monitor";
16328
- logLifecycle(PLUGIN_NAME19, "import", {});
16656
+ var PLUGIN_NAME18 = "terminal-monitor";
16657
+ logLifecycle(PLUGIN_NAME18, "import", {});
16329
16658
  var DEFAULT_CONFIG7 = {
16330
16659
  minScore: 0.6,
16331
16660
  cooldownMs: 30000,
@@ -16607,17 +16936,17 @@ function describeError(err) {
16607
16936
  return String(err);
16608
16937
  }
16609
16938
  }
16610
- var log9 = makePluginLogger(PLUGIN_NAME19);
16939
+ var log9 = makePluginLogger(PLUGIN_NAME18);
16611
16940
  var lru = new FingerprintLRU2;
16612
16941
  var _lastNotification = null;
16613
16942
  var terminalMonitorServer = async (ctx) => {
16614
- logLifecycle(PLUGIN_NAME19, "activate", {
16943
+ logLifecycle(PLUGIN_NAME18, "activate", {
16615
16944
  directory: ctx.directory,
16616
16945
  triggerEventTypes: ["terminal.output", "terminal.exit"]
16617
16946
  });
16618
16947
  return {
16619
16948
  event: async ({ event }) => {
16620
- await safeAsync(PLUGIN_NAME19, "event", async () => {
16949
+ await safeAsync(PLUGIN_NAME18, "event", async () => {
16621
16950
  const e = event;
16622
16951
  if (!e || typeof e.type !== "string")
16623
16952
  return;
@@ -16631,7 +16960,7 @@ var terminalMonitorServer = async (ctx) => {
16631
16960
  _lastNotification = msg;
16632
16961
  }
16633
16962
  });
16634
- safeWriteLog(PLUGIN_NAME19, {
16963
+ safeWriteLog(PLUGIN_NAME18, {
16635
16964
  hook: "event",
16636
16965
  type: e.type,
16637
16966
  notified: r.notified,
@@ -16639,18 +16968,18 @@ var terminalMonitorServer = async (ctx) => {
16639
16968
  findings: r.notification?.findings.length ?? 0
16640
16969
  });
16641
16970
  if (r.notified && r.notification) {
16642
- log9.info(`[${PLUGIN_NAME19}] 反馈 ${r.notification.findings.length} 条 → ${r.notification.summary}`);
16971
+ log9.info(`[${PLUGIN_NAME18}] 反馈 ${r.notification.findings.length} 条 → ${r.notification.summary}`);
16643
16972
  }
16644
16973
  });
16645
16974
  }
16646
16975
  };
16647
16976
  };
16648
- var handler19 = terminalMonitorServer;
16977
+ var handler18 = terminalMonitorServer;
16649
16978
 
16650
16979
  // plugins/token-manager.ts
16651
16980
  init_opencode_plugin_helpers();
16652
- var PLUGIN_NAME20 = "token-manager";
16653
- logLifecycle(PLUGIN_NAME20, "import", {});
16981
+ var PLUGIN_NAME19 = "token-manager";
16982
+ logLifecycle(PLUGIN_NAME19, "import", {});
16654
16983
  async function handleMessageBefore(raw, log10, defaults) {
16655
16984
  const ctx = raw ?? {};
16656
16985
  if (!Array.isArray(ctx.messages) || ctx.messages.length === 0)
@@ -16670,21 +16999,21 @@ async function handleMessageBefore(raw, log10, defaults) {
16670
16999
  };
16671
17000
  if (r.compressed) {
16672
17001
  ctx.messages = r.messages;
16673
- log10?.info(`[${PLUGIN_NAME20}] 压缩 ${r.before.count}→${r.after.count} 条 / ${r.before.tokens}→${r.after.tokens} tokens`);
17002
+ log10?.info(`[${PLUGIN_NAME19}] 压缩 ${r.before.count}→${r.after.count} 条 / ${r.before.tokens}→${r.after.tokens} tokens`);
16674
17003
  }
16675
17004
  return r;
16676
17005
  } catch (err) {
16677
- log10?.warn(`[${PLUGIN_NAME20}] 压缩异常(已隔离)`, {
17006
+ log10?.warn(`[${PLUGIN_NAME19}] 压缩异常(已隔离)`, {
16678
17007
  error: err instanceof Error ? err.message : String(err)
16679
17008
  });
16680
17009
  return null;
16681
17010
  }
16682
17011
  }
16683
- var log10 = makePluginLogger(PLUGIN_NAME20);
17012
+ var log10 = makePluginLogger(PLUGIN_NAME19);
16684
17013
  var tokenManagerServer = async (ctx) => {
16685
17014
  const rt = loadRuntimeSync();
16686
17015
  const threshold = rt.runtime.context.condenser_threshold_ratio;
16687
- logLifecycle(PLUGIN_NAME20, "activate", {
17016
+ logLifecycle(PLUGIN_NAME19, "activate", {
16688
17017
  directory: ctx.directory,
16689
17018
  threshold,
16690
17019
  target: DEFAULT_CONDENSE.target,
@@ -16693,7 +17022,7 @@ var tokenManagerServer = async (ctx) => {
16693
17022
  });
16694
17023
  return {
16695
17024
  "experimental.chat.messages.transform": async (_input, output) => {
16696
- await safeAsync(PLUGIN_NAME20, "experimental.chat.messages.transform", async () => {
17025
+ await safeAsync(PLUGIN_NAME19, "experimental.chat.messages.transform", async () => {
16697
17026
  const list = output.messages;
16698
17027
  if (!Array.isArray(list) || list.length === 0)
16699
17028
  return;
@@ -16706,7 +17035,7 @@ var tokenManagerServer = async (ctx) => {
16706
17035
  const r = await handleMessageBefore({ messages: flat }, log10, { threshold });
16707
17036
  if (!r)
16708
17037
  return;
16709
- safeWriteLog(PLUGIN_NAME20, {
17038
+ safeWriteLog(PLUGIN_NAME19, {
16710
17039
  hook: "experimental.chat.messages.transform",
16711
17040
  mode: "observe-only",
16712
17041
  before_msgs: r.before.count,
@@ -16717,13 +17046,13 @@ var tokenManagerServer = async (ctx) => {
16717
17046
  reason: r.reason
16718
17047
  });
16719
17048
  if (r.compressed) {
16720
- log10.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)`);
17049
+ log10.warn(`[${PLUGIN_NAME19}] 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)`);
16721
17050
  }
16722
17051
  });
16723
17052
  }
16724
17053
  };
16725
17054
  };
16726
- var handler20 = tokenManagerServer;
17055
+ var handler19 = tokenManagerServer;
16727
17056
 
16728
17057
  // plugins/tool-policy.ts
16729
17058
  init_opencode_plugin_helpers();
@@ -16966,10 +17295,42 @@ function checkFileAccess(acl, file, op) {
16966
17295
  }
16967
17296
 
16968
17297
  // plugins/tool-policy.ts
16969
- var PLUGIN_NAME21 = "tool-policy";
16970
- logLifecycle(PLUGIN_NAME21, "import", {});
17298
+ var PLUGIN_NAME20 = "tool-policy";
17299
+ logLifecycle(PLUGIN_NAME20, "import", {});
16971
17300
  var EMPTY_ACL = { whitelistMode: false };
16972
- function decideToolCall(ctx, cfg = {}) {
17301
+ var SUBAGENT_APPLY_DENY_LIST = new Set([
17302
+ "coder",
17303
+ "planner",
17304
+ "reviewer"
17305
+ ]);
17306
+ function isSubagentApplyBlocked(tool2, args, currentAgent) {
17307
+ if (tool2 !== "pending_changes")
17308
+ return false;
17309
+ const action = args?.action;
17310
+ if (action !== "apply" && action !== "apply_all")
17311
+ return false;
17312
+ if (!currentAgent)
17313
+ return false;
17314
+ return SUBAGENT_APPLY_DENY_LIST.has(currentAgent);
17315
+ }
17316
+ function decideToolCall(ctx, cfg = {}, currentAgent) {
17317
+ if (isSubagentApplyBlocked(ctx.tool, ctx.args, currentAgent)) {
17318
+ const action2 = String(ctx.args?.action);
17319
+ return {
17320
+ action: "deny",
17321
+ reasons: [
17322
+ `[ADR-0061] subagent '${currentAgent}' 禁止调 pending_changes.${action2}` + ` — apply 必须由 codeforge orchestrator 或用户拍板`,
17323
+ `替代路径:stage 完成后回报 codeforge,由其决定 apply 后通过 task_id 复用启动你跑测试`
17324
+ ],
17325
+ autonomy: {
17326
+ effective_mode: ctx.mode ?? cfg.defaultMode ?? DEFAULT_RUNTIME.autonomy.default_mode,
17327
+ action: "confirm",
17328
+ downgraded: false,
17329
+ detected_risks: ["subagent_apply_blocked"],
17330
+ reason: `subagent '${currentAgent}' 自 apply 越权 (ADR-0061)`
17331
+ }
17332
+ };
17333
+ }
16973
17334
  const fallbackMode = cfg.defaultMode ?? DEFAULT_RUNTIME.autonomy.default_mode;
16974
17335
  const mode = ctx.mode ?? fallbackMode;
16975
17336
  const a = evaluate2(mode, {
@@ -17019,13 +17380,39 @@ function classifyToolKind(toolName) {
17019
17380
  return "webfetch";
17020
17381
  return "other";
17021
17382
  }
17022
- var log11 = makePluginLogger(PLUGIN_NAME21);
17383
+ async function resolveCurrentAgent(client, sessionID, log11) {
17384
+ try {
17385
+ const sessionApi = client?.session;
17386
+ if (!sessionApi || typeof sessionApi.get !== "function") {
17387
+ log11.warn(`client.session.get unavailable`, { sessionID });
17388
+ return;
17389
+ }
17390
+ const res = await sessionApi.get({ path: { id: sessionID } });
17391
+ const data = res?.data ?? res;
17392
+ const rawAgent = data?.agent;
17393
+ if (typeof rawAgent !== "string" || rawAgent === "") {
17394
+ log11.warn(`client.session.get returned no string agent (保守放行)`, {
17395
+ sessionID,
17396
+ dataKeys: data && typeof data === "object" ? Object.keys(data) : null
17397
+ });
17398
+ return;
17399
+ }
17400
+ return rawAgent;
17401
+ } catch (err) {
17402
+ log11.warn(`client.session.get failed (保守放行)`, {
17403
+ sessionID,
17404
+ error: err instanceof Error ? err.message : String(err)
17405
+ });
17406
+ return;
17407
+ }
17408
+ }
17409
+ var log11 = makePluginLogger(PLUGIN_NAME20);
17023
17410
  var toolPolicyServer = async (ctx) => {
17024
17411
  const directory = ctx.directory ?? process.cwd();
17025
17412
  const cfg = await loadPolicy(directory);
17026
17413
  const rt = loadRuntimeSync();
17027
17414
  cfg.defaultMode = rt.runtime.autonomy.default_mode;
17028
- logLifecycle(PLUGIN_NAME21, "activate", {
17415
+ logLifecycle(PLUGIN_NAME20, "activate", {
17029
17416
  directory,
17030
17417
  acl_loaded: !!cfg.acl,
17031
17418
  default_mode: cfg.defaultMode,
@@ -17033,9 +17420,15 @@ var toolPolicyServer = async (ctx) => {
17033
17420
  });
17034
17421
  return {
17035
17422
  "tool.execute.before": async (input, output) => {
17036
- await safeAsync(PLUGIN_NAME21, "tool.execute.before", async () => {
17423
+ let denied;
17424
+ await safeAsync(PLUGIN_NAME20, "tool.execute.before", async () => {
17037
17425
  const toolName = input.tool;
17038
17426
  const argsObj = output.args ?? {};
17427
+ const needsAgentDetection = toolName === "pending_changes" && (argsObj.action === "apply" || argsObj.action === "apply_all");
17428
+ let currentAgent = undefined;
17429
+ if (needsAgentDetection) {
17430
+ currentAgent = await resolveCurrentAgent(ctx.client, input.sessionID, log11);
17431
+ }
17039
17432
  const files = [];
17040
17433
  const filePath = argsObj["path"] ?? argsObj["filePath"] ?? argsObj["file"];
17041
17434
  if (typeof filePath === "string") {
@@ -17048,25 +17441,33 @@ var toolPolicyServer = async (ctx) => {
17048
17441
  args: argsObj,
17049
17442
  mode: cfg.defaultMode,
17050
17443
  files: files.length ? files : undefined
17051
- }, cfg);
17052
- safeWriteLog(PLUGIN_NAME21, {
17444
+ }, cfg, currentAgent);
17445
+ safeWriteLog(PLUGIN_NAME20, {
17053
17446
  hook: "tool.execute.before",
17054
17447
  tool: toolName,
17055
17448
  callID: input.callID,
17056
17449
  sessionID: input.sessionID,
17057
17450
  action: decision.action,
17058
- reasons: decision.reasons
17451
+ reasons: decision.reasons,
17452
+ currentAgent
17059
17453
  });
17060
17454
  if (decision.action === "deny") {
17061
- log11.warn(`[${PLUGIN_NAME21}] DENY ${toolName}: ${decision.reasons.join("; ")}`);
17455
+ log11.warn(`[${PLUGIN_NAME20}] DENY ${toolName}`, {
17456
+ action: argsObj.action,
17457
+ currentAgent,
17458
+ reasons: decision.reasons
17459
+ });
17460
+ denied = new Error(`[tool-policy] DENIED: ${decision.reasons.join(" / ")}`);
17062
17461
  } else if (decision.action === "confirm") {
17063
- log11.info(`[${PLUGIN_NAME21}] CONFIRM ${toolName}: ${decision.reasons.join("; ")}`);
17462
+ log11.info(`[${PLUGIN_NAME20}] CONFIRM ${toolName}: ${decision.reasons.join("; ")}`);
17064
17463
  }
17065
17464
  });
17465
+ if (denied)
17466
+ throw denied;
17066
17467
  }
17067
17468
  };
17068
17469
  };
17069
- var handler21 = toolPolicyServer;
17470
+ var handler20 = toolPolicyServer;
17070
17471
 
17071
17472
  // plugins/update-checker.ts
17072
17473
  init_opencode_plugin_helpers();
@@ -17097,7 +17498,7 @@ import * as zlib from "node:zlib";
17097
17498
  // lib/version-injected.ts
17098
17499
  function getInjectedVersion() {
17099
17500
  try {
17100
- const v = "0.3.8";
17501
+ const v = "0.3.10";
17101
17502
  if (typeof v === "string" && /^\d+\.\d+\.\d+/.test(v)) {
17102
17503
  return v;
17103
17504
  }
@@ -17600,20 +18001,20 @@ function compareOpencodeVersion(opts) {
17600
18001
  }
17601
18002
 
17602
18003
  // plugins/update-checker.ts
17603
- var PLUGIN_NAME22 = "update-checker";
17604
- var PLUGIN_VERSION2 = "2.0.0";
17605
- logLifecycle(PLUGIN_NAME22, "import", { version: PLUGIN_VERSION2 });
18004
+ var PLUGIN_NAME21 = "update-checker";
18005
+ var PLUGIN_VERSION = "2.0.0";
18006
+ logLifecycle(PLUGIN_NAME21, "import", { version: PLUGIN_VERSION });
17606
18007
  var updateCheckerServer = async (ctx) => {
17607
18008
  const yieldResult = shouldYieldToLocalPlugin({ directory: ctx.directory });
17608
18009
  if (yieldResult.yield) {
17609
- safeWriteLog(PLUGIN_NAME22, {
18010
+ safeWriteLog(PLUGIN_NAME21, {
17610
18011
  level: "info",
17611
18012
  msg: "dev_mode_yield_skip",
17612
18013
  reason: yieldResult.reason,
17613
18014
  markerPath: yieldResult.markerPath,
17614
18015
  detail: formatYieldLog(yieldResult)
17615
18016
  });
17616
- logLifecycle(PLUGIN_NAME22, "activate", {
18017
+ logLifecycle(PLUGIN_NAME21, "activate", {
17617
18018
  yield_to_local: true,
17618
18019
  yield_reason: yieldResult.reason,
17619
18020
  skipped: "auto_install + background_check"
@@ -17622,8 +18023,8 @@ var updateCheckerServer = async (ctx) => {
17622
18023
  }
17623
18024
  const rt = loadRuntimeSync();
17624
18025
  const u = rt.runtime.update;
17625
- logLifecycle(PLUGIN_NAME22, "activate", {
17626
- version: PLUGIN_VERSION2,
18026
+ logLifecycle(PLUGIN_NAME21, "activate", {
18027
+ version: PLUGIN_VERSION,
17627
18028
  auto_check_enabled: u.auto_check_enabled,
17628
18029
  interval_hours: u.interval_hours,
17629
18030
  package: u.package,
@@ -17634,14 +18035,14 @@ var updateCheckerServer = async (ctx) => {
17634
18035
  repo_fallback: u.repo,
17635
18036
  config_source: "codeforge.json"
17636
18037
  });
17637
- await safeAsync(PLUGIN_NAME22, "opencode_version_check", async () => {
18038
+ await safeAsync(PLUGIN_NAME21, "opencode_version_check", async () => {
17638
18039
  const compat = loadCompatibility();
17639
18040
  const opencodeVer = detectOpencodeVersion();
17640
18041
  const verdict = compareOpencodeVersion({
17641
18042
  currentOpencodeVer: opencodeVer,
17642
18043
  compat
17643
18044
  });
17644
- safeWriteLog(PLUGIN_NAME22, {
18045
+ safeWriteLog(PLUGIN_NAME21, {
17645
18046
  level: "info",
17646
18047
  msg: "opencode_version_check",
17647
18048
  opencodeVer,
@@ -17658,14 +18059,14 @@ var updateCheckerServer = async (ctx) => {
17658
18059
  }
17659
18060
  });
17660
18061
  if (!u.auto_check_enabled) {
17661
- safeWriteLog(PLUGIN_NAME22, {
18062
+ safeWriteLog(PLUGIN_NAME21, {
17662
18063
  level: "info",
17663
18064
  msg: "auto-check disabled (codeforge.json update.auto_check_enabled=false)"
17664
18065
  });
17665
18066
  return {};
17666
18067
  }
17667
18068
  setImmediate(() => {
17668
- safeAsync(PLUGIN_NAME22, "checkAndMaybeUpdate", async () => {
18069
+ safeAsync(PLUGIN_NAME21, "checkAndMaybeUpdate", async () => {
17669
18070
  const local = readLocalVersion();
17670
18071
  let npmResult = null;
17671
18072
  try {
@@ -17676,7 +18077,7 @@ var updateCheckerServer = async (ctx) => {
17676
18077
  timeoutMs: 5000
17677
18078
  });
17678
18079
  } catch (e) {
17679
- safeWriteLog(PLUGIN_NAME22, {
18080
+ safeWriteLog(PLUGIN_NAME21, {
17680
18081
  level: "warn",
17681
18082
  msg: "npm_fetch_failed",
17682
18083
  error: e.message
@@ -17685,7 +18086,7 @@ var updateCheckerServer = async (ctx) => {
17685
18086
  return;
17686
18087
  }
17687
18088
  if (!npmResult) {
17688
- safeWriteLog(PLUGIN_NAME22, {
18089
+ safeWriteLog(PLUGIN_NAME21, {
17689
18090
  level: "info",
17690
18091
  msg: "npm_no_release",
17691
18092
  package: u.package,
@@ -17694,7 +18095,7 @@ var updateCheckerServer = async (ctx) => {
17694
18095
  return;
17695
18096
  }
17696
18097
  const hasUpdate = cmpVersion(local, npmResult.version) < 0;
17697
- safeWriteLog(PLUGIN_NAME22, {
18098
+ safeWriteLog(PLUGIN_NAME21, {
17698
18099
  level: "info",
17699
18100
  msg: "npm_check_result",
17700
18101
  local,
@@ -17709,10 +18110,10 @@ var updateCheckerServer = async (ctx) => {
17709
18110
  更新命令:npx ${u.package} install --global`);
17710
18111
  return;
17711
18112
  }
17712
- await safeAsync(PLUGIN_NAME22, "auto_install_bundle", async () => {
18113
+ await safeAsync(PLUGIN_NAME21, "auto_install_bundle", async () => {
17713
18114
  const target = getOpencodeBundlePath();
17714
18115
  if (!target) {
17715
- safeWriteLog(PLUGIN_NAME22, {
18116
+ safeWriteLog(PLUGIN_NAME21, {
17716
18117
  level: "warn",
17717
18118
  msg: "auto_install_skip",
17718
18119
  reason: "无法定位 opencode bundle 路径"
@@ -17731,7 +18132,7 @@ var updateCheckerServer = async (ctx) => {
17731
18132
  oldVersion: local,
17732
18133
  keepBackups: u.backup_keep
17733
18134
  });
17734
- safeWriteLog(PLUGIN_NAME22, {
18135
+ safeWriteLog(PLUGIN_NAME21, {
17735
18136
  level: "info",
17736
18137
  msg: "auto_install_success",
17737
18138
  local,
@@ -17771,7 +18172,7 @@ function getOpencodeBundlePath() {
17771
18172
  return candidates[0] ?? null;
17772
18173
  }
17773
18174
  async function fallbackToGitHubReleases(ctx, u) {
17774
- await safeAsync(PLUGIN_NAME22, "github_fallback", async () => {
18175
+ await safeAsync(PLUGIN_NAME21, "github_fallback", async () => {
17775
18176
  const result = await checkUpdateOnce({
17776
18177
  repo: u.repo,
17777
18178
  intervalMs: u.interval_hours * 3600 * 1000
@@ -17781,7 +18182,7 @@ async function fallbackToGitHubReleases(ctx, u) {
17781
18182
  }
17782
18183
  async function reportLegacyResult(ctx, result, repo) {
17783
18184
  if (result.error && !result.fromCache) {
17784
- safeWriteLog(PLUGIN_NAME22, {
18185
+ safeWriteLog(PLUGIN_NAME21, {
17785
18186
  level: "warn",
17786
18187
  msg: `update check failed: ${result.error}`,
17787
18188
  ...result
@@ -17789,7 +18190,7 @@ async function reportLegacyResult(ctx, result, repo) {
17789
18190
  return;
17790
18191
  }
17791
18192
  if (!result.hasUpdate) {
17792
- safeWriteLog(PLUGIN_NAME22, {
18193
+ safeWriteLog(PLUGIN_NAME21, {
17793
18194
  level: "info",
17794
18195
  msg: `up-to-date (local=${result.local}, remote=${result.remote}${result.fromCache ? ", from_cache" : ""})`,
17795
18196
  ...result
@@ -17799,7 +18200,7 @@ async function reportLegacyResult(ctx, result, repo) {
17799
18200
  const updateCmd = `bunx --bun github:${repo} install`;
17800
18201
  const toast = `[CodeForge] 有新版本:${result.local} → ${result.remote}
17801
18202
  更新命令:${updateCmd}`;
17802
- safeWriteLog(PLUGIN_NAME22, {
18203
+ safeWriteLog(PLUGIN_NAME21, {
17803
18204
  level: "info",
17804
18205
  msg: "new_version_available_github_fallback",
17805
18206
  local: result.local,
@@ -17810,17 +18211,17 @@ async function reportLegacyResult(ctx, result, repo) {
17810
18211
  await postToast(ctx, toast);
17811
18212
  }
17812
18213
  async function postToast(ctx, message) {
17813
- await safeAsync(PLUGIN_NAME22, "client.app.log", async () => {
18214
+ await safeAsync(PLUGIN_NAME21, "client.app.log", async () => {
17814
18215
  await ctx.client.app.log({
17815
18216
  body: {
17816
- service: PLUGIN_NAME22,
18217
+ service: PLUGIN_NAME21,
17817
18218
  level: "info",
17818
18219
  message
17819
18220
  }
17820
18221
  });
17821
18222
  });
17822
18223
  }
17823
- var handler22 = updateCheckerServer;
18224
+ var handler21 = updateCheckerServer;
17824
18225
 
17825
18226
  // plugins/workflow-engine.ts
17826
18227
  init_opencode_plugin_helpers();
@@ -18324,9 +18725,9 @@ async function runStepAutoFeedback(step, adapter) {
18324
18725
  }
18325
18726
 
18326
18727
  // plugins/workflow-engine.ts
18327
- var PLUGIN_NAME23 = "workflow-engine";
18328
- logLifecycle(PLUGIN_NAME23, "import", {});
18329
- var fallbackLog2 = makePluginLogger(PLUGIN_NAME23);
18728
+ var PLUGIN_NAME22 = "workflow-engine";
18729
+ logLifecycle(PLUGIN_NAME22, "import", {});
18730
+ var fallbackLog2 = makePluginLogger(PLUGIN_NAME22);
18330
18731
  var _registry = null;
18331
18732
  async function loadRegistry(workflowsDir) {
18332
18733
  const { loaded, failed } = await loadWorkflowsFromDir(workflowsDir);
@@ -18346,32 +18747,32 @@ async function handleCommandInvoked(raw, workflowsDir = "workflows") {
18346
18747
  const log12 = ctx.log ?? fallbackLog2;
18347
18748
  const command = typeof ctx.command === "string" ? ctx.command : null;
18348
18749
  if (!command) {
18349
- log12.warn(`[${PLUGIN_NAME23}] command.invoked 缺 command 字段`, ctx);
18750
+ log12.warn(`[${PLUGIN_NAME22}] command.invoked 缺 command 字段`, ctx);
18350
18751
  return null;
18351
18752
  }
18352
18753
  const reg = await ensureRegistry(workflowsDir);
18353
18754
  if (reg.errors.length) {
18354
- log12.warn(`[${PLUGIN_NAME23}] 有 ${reg.errors.length} 个 workflow 加载失败`, reg.errors);
18755
+ log12.warn(`[${PLUGIN_NAME22}] 有 ${reg.errors.length} 个 workflow 加载失败`, reg.errors);
18355
18756
  }
18356
18757
  const wf = reg.workflows.find((w) => matchesTrigger(w, command));
18357
18758
  if (!wf) {
18358
- log12.info(`[${PLUGIN_NAME23}] no workflow matches "${command}"`);
18759
+ log12.info(`[${PLUGIN_NAME22}] no workflow matches "${command}"`);
18359
18760
  return null;
18360
18761
  }
18361
- log12.info(`[${PLUGIN_NAME23}] dispatch "${command}" → workflow "${wf.name}"`);
18762
+ log12.info(`[${PLUGIN_NAME22}] dispatch "${command}" → workflow "${wf.name}"`);
18362
18763
  try {
18363
18764
  const result = await run(wf, {
18364
18765
  mode: ctx.adapter ? "real" : "dry_run",
18365
18766
  autonomy: ctx.autonomy ?? "semi",
18366
18767
  adapter: ctx.adapter
18367
18768
  });
18368
- log12.info(`[${PLUGIN_NAME23}] workflow "${wf.name}" 完成 (${result.plan.mode})`, {
18769
+ log12.info(`[${PLUGIN_NAME22}] workflow "${wf.name}" 完成 (${result.plan.mode})`, {
18369
18770
  steps: result.plan.steps.length,
18370
18771
  results: result.results.length
18371
18772
  });
18372
18773
  return result;
18373
18774
  } catch (err) {
18374
- log12.error(`[${PLUGIN_NAME23}] workflow "${wf.name}" 执行失败`, {
18775
+ log12.error(`[${PLUGIN_NAME22}] workflow "${wf.name}" 执行失败`, {
18375
18776
  error: err instanceof Error ? err.message : String(err)
18376
18777
  });
18377
18778
  throw err;
@@ -18380,15 +18781,15 @@ async function handleCommandInvoked(raw, workflowsDir = "workflows") {
18380
18781
  var workflowEngineServer = async (ctx) => {
18381
18782
  const directory = ctx.directory ?? process.cwd();
18382
18783
  const workflowsDir = path17.join(directory, "workflows");
18383
- ensureRegistry(workflowsDir).catch((err) => fallbackLog2.warn(`[${PLUGIN_NAME23}] preload workflows failed`, {
18784
+ ensureRegistry(workflowsDir).catch((err) => fallbackLog2.warn(`[${PLUGIN_NAME22}] preload workflows failed`, {
18384
18785
  error: err instanceof Error ? err.message : String(err)
18385
18786
  }));
18386
- logLifecycle(PLUGIN_NAME23, "activate", { directory, workflowsDir });
18787
+ logLifecycle(PLUGIN_NAME22, "activate", { directory, workflowsDir });
18387
18788
  return {
18388
18789
  "command.execute.before": async (input, output) => {
18389
- await safeAsync(PLUGIN_NAME23, "command.execute.before", async () => {
18790
+ await safeAsync(PLUGIN_NAME22, "command.execute.before", async () => {
18390
18791
  const cmd = input.command.startsWith("/") ? input.command : `/${input.command}`;
18391
- safeWriteLog(PLUGIN_NAME23, {
18792
+ safeWriteLog(PLUGIN_NAME22, {
18392
18793
  hook: "command.execute.before",
18393
18794
  command: cmd,
18394
18795
  sessionID: input.sessionID
@@ -18403,14 +18804,14 @@ var workflowEngineServer = async (ctx) => {
18403
18804
  });
18404
18805
  },
18405
18806
  "chat.message": async (input, output) => {
18406
- await safeAsync(PLUGIN_NAME23, "chat.message", async () => {
18807
+ await safeAsync(PLUGIN_NAME22, "chat.message", async () => {
18407
18808
  const text = extractUserText(output).trim();
18408
18809
  if (!text.startsWith("/"))
18409
18810
  return;
18410
18811
  const cmd = text.split(/\s+/)[0];
18411
18812
  if (!cmd)
18412
18813
  return;
18413
- safeWriteLog(PLUGIN_NAME23, {
18814
+ safeWriteLog(PLUGIN_NAME22, {
18414
18815
  hook: "chat.message",
18415
18816
  command: cmd,
18416
18817
  sessionID: input.sessionID
@@ -18420,7 +18821,7 @@ var workflowEngineServer = async (ctx) => {
18420
18821
  }
18421
18822
  };
18422
18823
  };
18423
- var handler23 = workflowEngineServer;
18824
+ var handler22 = workflowEngineServer;
18424
18825
 
18425
18826
  // src/index.ts
18426
18827
  var PLUGIN_ID = "codeforge";
@@ -18434,22 +18835,21 @@ var HANDLERS = [
18434
18835
  { name: "auto-learning", init: handler5 },
18435
18836
  { name: "channels", init: handler6 },
18436
18837
  { name: "codeforge-tools", init: handler8 },
18437
- { name: "hello-world", init: handler9 },
18438
- { name: "kh-auto-context", init: handler10 },
18439
- { name: "kh-reminder", init: handler11 },
18440
- { name: "memories-context", init: handler12 },
18441
- { name: "model-fallback", init: handler13 },
18442
- { name: "pwsh-utf8", init: handler16 },
18443
- { name: "session-recovery", init: handler17 },
18444
- { name: "subtask-heartbeat", init: handler14 },
18445
- { name: "subtasks", init: handler18 },
18446
- { name: "parallel-status", init: handler15 },
18447
- { name: "terminal-monitor", init: handler19 },
18448
- { name: "token-manager", init: handler20 },
18838
+ { name: "kh-auto-context", init: handler9 },
18839
+ { name: "kh-reminder", init: handler10 },
18840
+ { name: "memories-context", init: handler11 },
18841
+ { name: "model-fallback", init: handler12 },
18842
+ { name: "pwsh-utf8", init: handler15 },
18843
+ { name: "session-recovery", init: handler16 },
18844
+ { name: "subtask-heartbeat", init: handler13 },
18845
+ { name: "subtasks", init: handler17 },
18846
+ { name: "parallel-status", init: handler14 },
18847
+ { name: "terminal-monitor", init: handler18 },
18848
+ { name: "token-manager", init: handler19 },
18449
18849
  { name: "tool-heartbeat", init: handler7 },
18450
- { name: "tool-policy", init: handler21 },
18451
- { name: "update-checker", init: handler22 },
18452
- { name: "workflow-engine", init: handler23 }
18850
+ { name: "tool-policy", init: handler20 },
18851
+ { name: "update-checker", init: handler21 },
18852
+ { name: "workflow-engine", init: handler22 }
18453
18853
  ];
18454
18854
  function makeSerialHook(hookName, fns) {
18455
18855
  return async (input, output) => {