@andyqiu/codeforge 0.3.7 → 0.3.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -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");
@@ -262,7 +267,7 @@ var init_auto_review_trigger = __esm(() => {
262
267
  });
263
268
 
264
269
  // lib/runtime-paths.ts
265
- import { mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync, existsSync } from "node:fs";
270
+ import { mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync, existsSync as existsSync2 } from "node:fs";
266
271
  import * as path2 from "node:path";
267
272
  import * as os from "node:os";
268
273
  import * as crypto from "node:crypto";
@@ -296,7 +301,7 @@ function runtimeDir(absRoot, opts = {}) {
296
301
  return dir;
297
302
  mkdirSync2(dir, { recursive: true });
298
303
  const metaFile = path2.join(dir, ".meta.json");
299
- if (existsSync(metaFile)) {
304
+ if (existsSync2(metaFile)) {
300
305
  const existing = readMetaSafe(metaFile);
301
306
  if (existing && existing.absPath && existing.absPath !== resolvedRoot) {
302
307
  throw new Error(`runtime dir hash collision: ${projectKey(resolvedRoot)} already used by ${existing.absPath}, current is ${resolvedRoot}`);
@@ -335,7 +340,7 @@ function readMetaSafe(file) {
335
340
  var init_runtime_paths = () => {};
336
341
 
337
342
  // lib/global-config.ts
338
- import { readFileSync as readFileSync3, existsSync as existsSync2, statSync } from "node:fs";
343
+ import { readFileSync as readFileSync3, existsSync as existsSync3, statSync } from "node:fs";
339
344
  import * as path3 from "node:path";
340
345
  import * as os2 from "node:os";
341
346
  function __resetGlobalConfigCache() {
@@ -360,7 +365,7 @@ function loadJsonIfExists(filePath) {
360
365
  const cached = cacheGet(cacheKey);
361
366
  if (cached !== undefined)
362
367
  return cached;
363
- if (!existsSync2(filePath)) {
368
+ if (!existsSync3(filePath)) {
364
369
  cacheSet(cacheKey, null);
365
370
  return null;
366
371
  }
@@ -641,7 +646,7 @@ function isAbortError(err) {
641
646
  return name === "AbortError" || name === "TimeoutError";
642
647
  }
643
648
  function defaultSleep(ms) {
644
- return new Promise((resolve4) => setTimeout(resolve4, ms));
649
+ return new Promise((resolve5) => setTimeout(resolve5, ms));
645
650
  }
646
651
  function errorMessage2(err) {
647
652
  return err instanceof Error ? err.message : String(err);
@@ -8025,11 +8030,11 @@ function shouldStopByStuck(history, cfg) {
8025
8030
  async function withTimeout3(p, timeoutMs) {
8026
8031
  if (timeoutMs <= 0)
8027
8032
  return await p;
8028
- return await new Promise((resolve10, reject) => {
8033
+ return await new Promise((resolve11, reject) => {
8029
8034
  const timer = setTimeout(() => reject(new Error(`timeout after ${timeoutMs}ms`)), timeoutMs);
8030
8035
  Promise.resolve(p).then((v) => {
8031
8036
  clearTimeout(timer);
8032
- resolve10(v);
8037
+ resolve11(v);
8033
8038
  }, (err) => {
8034
8039
  clearTimeout(timer);
8035
8040
  reject(err);
@@ -8151,6 +8156,47 @@ var init_auto_feedback = __esm(() => {
8151
8156
  // src/index.ts
8152
8157
  init_opencode_plugin_helpers();
8153
8158
 
8159
+ // lib/dev-isolation.ts
8160
+ import { existsSync } from "node:fs";
8161
+ import { resolve, dirname } from "node:path";
8162
+ var MARKER_REL = ".codeforge/.dev-marker";
8163
+ var ENV_KEY = "CODEFORGE_DEV";
8164
+ function shouldYieldToLocalPlugin(opts = {}) {
8165
+ const env = opts.env ?? process.env;
8166
+ const fileExists = opts.fileExists ?? existsSync;
8167
+ const envVal = env[ENV_KEY];
8168
+ if (envVal === "1" || envVal === "true" || envVal === "yes") {
8169
+ return { yield: true, reason: "env" };
8170
+ }
8171
+ const startDir = opts.directory ? resolve(opts.directory) : process.cwd();
8172
+ const maxDepth = Math.max(1, opts.maxDepth ?? 20);
8173
+ let cur = startDir;
8174
+ for (let i = 0;i < maxDepth; i++) {
8175
+ const markerPath = resolve(cur, MARKER_REL);
8176
+ try {
8177
+ if (fileExists(markerPath)) {
8178
+ return { yield: true, reason: "marker", markerPath };
8179
+ }
8180
+ } catch {}
8181
+ const parent = dirname(cur);
8182
+ if (parent === cur)
8183
+ break;
8184
+ cur = parent;
8185
+ }
8186
+ return { yield: false, reason: null };
8187
+ }
8188
+ function formatYieldLog(result) {
8189
+ if (!result.yield)
8190
+ return "[codeforge] stable plugin 正常加载";
8191
+ if (result.reason === "env") {
8192
+ return "[codeforge] 检测到 CODEFORGE_DEV env,stable plugin 让位";
8193
+ }
8194
+ if (result.reason === "marker") {
8195
+ return `[codeforge] 检测到 dev marker (${result.markerPath}),stable plugin 让位`;
8196
+ }
8197
+ return "[codeforge] stable plugin 让位(未知原因)";
8198
+ }
8199
+
8154
8200
  // plugins/agent-router.ts
8155
8201
  init_opencode_plugin_helpers();
8156
8202
  var PLUGIN_NAME = "agent-router";
@@ -8317,7 +8363,7 @@ init_opencode_plugin_helpers();
8317
8363
  // lib/arena.ts
8318
8364
  var DEFAULT_TIMEOUT = 90000;
8319
8365
  async function withTimeout(p, timeoutMs, signal) {
8320
- return new Promise((resolve, reject) => {
8366
+ return new Promise((resolve2, reject) => {
8321
8367
  if (signal?.aborted)
8322
8368
  return reject(new Error("aborted"));
8323
8369
  const onAbort = () => {
@@ -8335,7 +8381,7 @@ async function withTimeout(p, timeoutMs, signal) {
8335
8381
  }
8336
8382
  Promise.resolve().then(() => p).then((v) => {
8337
8383
  cleanup();
8338
- resolve(v);
8384
+ resolve2(v);
8339
8385
  }, (err) => {
8340
8386
  cleanup();
8341
8387
  reject(err);
@@ -8561,14 +8607,14 @@ async function git(opts, args) {
8561
8607
  };
8562
8608
  }
8563
8609
  }
8564
- function resolve2(o) {
8610
+ function resolve3(o) {
8565
8611
  return {
8566
8612
  root: path.resolve(o?.root ?? process.cwd()),
8567
8613
  timeoutMs: o?.timeoutMs ?? DEFAULTS.timeoutMs
8568
8614
  };
8569
8615
  }
8570
8616
  async function isGitRepo(o) {
8571
- const opts = resolve2(o);
8617
+ const opts = resolve3(o);
8572
8618
  try {
8573
8619
  await fs.access(path.join(opts.root, ".git"));
8574
8620
  return true;
@@ -8577,7 +8623,7 @@ async function isGitRepo(o) {
8577
8623
  }
8578
8624
  }
8579
8625
  async function listChanged(o) {
8580
- const opts = resolve2(o);
8626
+ const opts = resolve3(o);
8581
8627
  if (!await isGitRepo(opts))
8582
8628
  return [];
8583
8629
  const r = await git(opts, ["status", "--porcelain"]);
@@ -8604,7 +8650,7 @@ ${input.body.trim()}
8604
8650
  `;
8605
8651
  }
8606
8652
  async function commit(input, o) {
8607
- const opts = resolve2(o);
8653
+ const opts = resolve3(o);
8608
8654
  if (!await isGitRepo(opts)) {
8609
8655
  return { ok: false, reason: "not-a-git-repo" };
8610
8656
  }
@@ -14731,10 +14777,303 @@ var modelFallbackServer = async (ctx) => {
14731
14777
  };
14732
14778
  var handler13 = modelFallbackServer;
14733
14779
 
14734
- // plugins/pwsh-utf8.ts
14780
+ // plugins/subtask-heartbeat.ts
14735
14781
  init_opencode_plugin_helpers();
14736
- var PLUGIN_NAME14 = "pwsh-utf8";
14782
+ var PLUGIN_NAME14 = "subtask-heartbeat";
14737
14783
  logLifecycle(PLUGIN_NAME14, "import", {});
14784
+ var HEARTBEAT_INTERVAL_MS2 = 30000;
14785
+ var HEARTBEAT_DEBOUNCE_MS = 25000;
14786
+ var TOAST_DURATION_MS3 = 5000;
14787
+ var START_TOAST_DURATION_MS = 2000;
14788
+ var inflight2 = new Map;
14789
+ function _snapshotInflight() {
14790
+ return [...inflight2.values()].map((r) => ({ ...r }));
14791
+ }
14792
+ function getInflightSnapshot() {
14793
+ return _snapshotInflight();
14794
+ }
14795
+ function extractCreatedChild(event) {
14796
+ if (!event || typeof event !== "object")
14797
+ return null;
14798
+ const e = event;
14799
+ if (e.type !== "session.created")
14800
+ return null;
14801
+ const info = e.properties?.info;
14802
+ if (!info || typeof info !== "object")
14803
+ return null;
14804
+ const session = info;
14805
+ if (typeof session.id !== "string")
14806
+ return null;
14807
+ if (typeof session.parentID !== "string" || session.parentID === "")
14808
+ return null;
14809
+ return { childID: session.id, parentID: session.parentID, agent: null };
14810
+ }
14811
+ function extractEndedSessionID(event) {
14812
+ if (!event || typeof event !== "object")
14813
+ return null;
14814
+ const e = event;
14815
+ if (typeof e.type !== "string")
14816
+ return null;
14817
+ if (e.type !== "session.idle" && e.type !== "session.deleted" && e.type !== "session.error") {
14818
+ return null;
14819
+ }
14820
+ const props = e.properties ?? {};
14821
+ const direct = props["sessionID"];
14822
+ if (typeof direct === "string" && direct)
14823
+ return { type: e.type, sessionID: direct };
14824
+ const info = props["info"];
14825
+ if (info && typeof info === "object") {
14826
+ const sid = info.id;
14827
+ if (typeof sid === "string" && sid)
14828
+ return { type: e.type, sessionID: sid };
14829
+ }
14830
+ return null;
14831
+ }
14832
+ function registerInflight(payload, now = Date.now()) {
14833
+ const r = {
14834
+ childID: payload.childID,
14835
+ parentID: payload.parentID,
14836
+ agent: payload.agent,
14837
+ startedAt: now,
14838
+ lastBeatAt: now,
14839
+ lastTool: null
14840
+ };
14841
+ inflight2.set(payload.childID, r);
14842
+ return r;
14843
+ }
14844
+ function recordToolBeat(sessionID, tool2, now = Date.now()) {
14845
+ const r = inflight2.get(sessionID);
14846
+ if (!r)
14847
+ return null;
14848
+ r.lastBeatAt = now;
14849
+ r.lastTool = tool2;
14850
+ return r;
14851
+ }
14852
+ function clearInflight2(sessionID) {
14853
+ const r = inflight2.get(sessionID);
14854
+ if (!r)
14855
+ return null;
14856
+ inflight2.delete(sessionID);
14857
+ return r;
14858
+ }
14859
+ function pickHeartbeats(now = Date.now()) {
14860
+ const out = [];
14861
+ for (const r of inflight2.values()) {
14862
+ if (now - r.lastBeatAt >= HEARTBEAT_DEBOUNCE_MS)
14863
+ out.push(r);
14864
+ }
14865
+ return out;
14866
+ }
14867
+ function fmtElapsed(ms) {
14868
+ const total = Math.max(0, Math.floor(ms / 1000));
14869
+ const m = Math.floor(total / 60);
14870
+ const s = total % 60;
14871
+ return m > 0 ? `${m}m${s.toString().padStart(2, "0")}s` : `${s}s`;
14872
+ }
14873
+ function buildStartToast(r) {
14874
+ const who = r.agent ?? "subagent";
14875
+ return {
14876
+ message: `\uD83D\uDE80 子 session 启动: ${who}`,
14877
+ variant: "info"
14878
+ };
14879
+ }
14880
+ function buildHeartbeatToast(r, now = Date.now()) {
14881
+ const who = r.agent ?? "subagent";
14882
+ const tool2 = r.lastTool ?? "thinking";
14883
+ return {
14884
+ message: `⏳ ${who} 仍在运行 ${fmtElapsed(now - r.startedAt)} | 当前: ${tool2}`,
14885
+ variant: "info"
14886
+ };
14887
+ }
14888
+ function buildEndToast(r, type, now = Date.now()) {
14889
+ const who = r.agent ?? "subagent";
14890
+ const elapsed = fmtElapsed(now - r.startedAt);
14891
+ if (type === "session.error") {
14892
+ return { message: `❌ ${who} 失败 (${elapsed})`, variant: "error" };
14893
+ }
14894
+ if (type === "session.deleted") {
14895
+ return { message: `\uD83D\uDDD1️ ${who} 被取消 (${elapsed})`, variant: "error" };
14896
+ }
14897
+ return { message: `✅ ${who} 完成 (${elapsed})`, variant: "success" };
14898
+ }
14899
+ function normalizeVariant3(raw) {
14900
+ if (raw === "info" || raw === "warning")
14901
+ return "default";
14902
+ return raw;
14903
+ }
14904
+ async function showToast3(client, payload, log7) {
14905
+ if (typeof client?.tui?.showToast !== "function") {
14906
+ log7?.debug?.("tui.showToast 不可用,noop");
14907
+ return false;
14908
+ }
14909
+ try {
14910
+ await client.tui.showToast({
14911
+ body: {
14912
+ message: payload.message,
14913
+ variant: normalizeVariant3(payload.variant),
14914
+ duration: payload.duration ?? TOAST_DURATION_MS3,
14915
+ title: payload.title ?? "CodeForge"
14916
+ }
14917
+ });
14918
+ return true;
14919
+ } catch (err) {
14920
+ log7?.warn("tui.showToast 抛错(已隔离)", {
14921
+ error: err instanceof Error ? err.message : String(err)
14922
+ });
14923
+ return false;
14924
+ }
14925
+ }
14926
+ var log7 = makePluginLogger(PLUGIN_NAME14);
14927
+ var subtaskHeartbeatServer = async (ctx) => {
14928
+ logLifecycle(PLUGIN_NAME14, "activate", {
14929
+ directory: ctx.directory,
14930
+ intervalMs: HEARTBEAT_INTERVAL_MS2
14931
+ });
14932
+ const client = ctx.client;
14933
+ const interval = setInterval(() => {
14934
+ safeAsync(PLUGIN_NAME14, "interval", async () => {
14935
+ const beats = pickHeartbeats();
14936
+ if (beats.length === 0)
14937
+ return;
14938
+ for (const r of beats) {
14939
+ const t = buildHeartbeatToast(r);
14940
+ const sent = await showToast3(client, t, log7);
14941
+ safeWriteLog(PLUGIN_NAME14, {
14942
+ hook: "interval",
14943
+ child: r.childID,
14944
+ parent: r.parentID,
14945
+ tool: r.lastTool,
14946
+ elapsed_ms: Date.now() - r.startedAt,
14947
+ toast_sent: sent
14948
+ });
14949
+ r.lastBeatAt = Date.now();
14950
+ }
14951
+ });
14952
+ }, HEARTBEAT_INTERVAL_MS2);
14953
+ if (typeof interval.unref === "function") {
14954
+ interval.unref();
14955
+ }
14956
+ return {
14957
+ event: async ({ event }) => {
14958
+ await safeAsync(PLUGIN_NAME14, "event", async () => {
14959
+ const created = extractCreatedChild(event);
14960
+ if (created) {
14961
+ const record = registerInflight(created);
14962
+ safeWriteLog(PLUGIN_NAME14, {
14963
+ hook: "event",
14964
+ type: "session.created",
14965
+ child: created.childID,
14966
+ parent: created.parentID
14967
+ });
14968
+ const startToast = buildStartToast(record);
14969
+ const sent = await showToast3(client, { ...startToast, duration: START_TOAST_DURATION_MS }, log7);
14970
+ safeWriteLog(PLUGIN_NAME14, {
14971
+ hook: "event",
14972
+ type: "session.created.toast",
14973
+ child: created.childID,
14974
+ toast_sent: sent
14975
+ });
14976
+ return;
14977
+ }
14978
+ const ended = extractEndedSessionID(event);
14979
+ if (ended) {
14980
+ const r = clearInflight2(ended.sessionID);
14981
+ if (r) {
14982
+ const t = buildEndToast(r, ended.type);
14983
+ const sent = await showToast3(client, t, log7);
14984
+ safeWriteLog(PLUGIN_NAME14, {
14985
+ hook: "event",
14986
+ type: ended.type,
14987
+ child: r.childID,
14988
+ elapsed_ms: Date.now() - r.startedAt,
14989
+ toast_sent: sent,
14990
+ end_toast_message: t.message
14991
+ });
14992
+ }
14993
+ }
14994
+ });
14995
+ },
14996
+ "tool.execute.before": async (input) => {
14997
+ await safeAsync(PLUGIN_NAME14, "tool.execute.before", async () => {
14998
+ if (!input || typeof input.sessionID !== "string" || typeof input.tool !== "string")
14999
+ return;
15000
+ recordToolBeat(input.sessionID, input.tool);
15001
+ });
15002
+ }
15003
+ };
15004
+ };
15005
+ var handler14 = subtaskHeartbeatServer;
15006
+
15007
+ // plugins/parallel-status.ts
15008
+ init_opencode_plugin_helpers();
15009
+ var PLUGIN_NAME15 = "parallel-status";
15010
+ logLifecycle(PLUGIN_NAME15, "import");
15011
+ var ID_MAX_LEN = 16;
15012
+ var ID_KEEP_LEN = 13;
15013
+ function shortId(s) {
15014
+ return s.length > ID_MAX_LEN ? s.slice(0, ID_KEEP_LEN) + "..." : s;
15015
+ }
15016
+ function formatElapsed(ms) {
15017
+ const total = Math.max(0, Math.floor(ms / 1000));
15018
+ const m = Math.floor(total / 60);
15019
+ const s = total % 60;
15020
+ return m > 0 ? `${m}m${s.toString().padStart(2, "0")}s` : `${s}s`;
15021
+ }
15022
+ function formatInflightMarkdown(snapshot, now = Date.now()) {
15023
+ if (snapshot.length === 0) {
15024
+ return "✅ 当前无 inflight subagent";
15025
+ }
15026
+ const lines = [];
15027
+ lines.push(`\uD83D\uDCCA 当前 ${snapshot.length} 个 subagent 在跑:`, "");
15028
+ lines.push("| # | child id | parent id | agent | 已跑 | 最近工具 |");
15029
+ lines.push("|---|----------|-----------|-------|------|----------|");
15030
+ snapshot.forEach((r, i) => {
15031
+ const elapsed = formatElapsed(now - r.startedAt);
15032
+ const agent = r.agent ?? "(unknown)";
15033
+ const tool2 = r.lastTool ?? "(thinking)";
15034
+ const child = shortId(r.childID);
15035
+ const parent = shortId(r.parentID);
15036
+ lines.push(`| ${i + 1} | \`${child}\` | \`${parent}\` | ${agent} | ${elapsed} | ${tool2} |`);
15037
+ });
15038
+ return lines.join(`
15039
+ `);
15040
+ }
15041
+ var parallelStatusServer = async (ctx) => {
15042
+ const log8 = makePluginLogger(PLUGIN_NAME15);
15043
+ logLifecycle(PLUGIN_NAME15, "activate", { directory: ctx.directory });
15044
+ return {
15045
+ "command.execute.before": async (input, output) => {
15046
+ try {
15047
+ if (!input || input.command !== "parallel-status")
15048
+ return;
15049
+ const snapshot = getInflightSnapshot();
15050
+ const text = formatInflightMarkdown(snapshot);
15051
+ if (Array.isArray(output?.parts)) {
15052
+ output.parts.length = 0;
15053
+ output.parts.push({
15054
+ id: makePartId(),
15055
+ sessionID: input.sessionID,
15056
+ messageID: "",
15057
+ type: "text",
15058
+ text,
15059
+ synthetic: false
15060
+ });
15061
+ }
15062
+ log8.info(`[${PLUGIN_NAME15}] 已回写 ${snapshot.length} 条 inflight`);
15063
+ } catch (err) {
15064
+ log8.error(`[${PLUGIN_NAME15}] command.execute.before 异常(已隔离)`, {
15065
+ error: err instanceof Error ? err.message : String(err)
15066
+ });
15067
+ }
15068
+ }
15069
+ };
15070
+ };
15071
+ var handler15 = parallelStatusServer;
15072
+
15073
+ // plugins/pwsh-utf8.ts
15074
+ init_opencode_plugin_helpers();
15075
+ var PLUGIN_NAME16 = "pwsh-utf8";
15076
+ logLifecycle(PLUGIN_NAME16, "import", {});
14738
15077
  var PRELUDE = "chcp 65001 *> $null; " + "[Console]::OutputEncoding = [System.Text.UTF8Encoding]::new(); " + "$OutputEncoding = [System.Text.UTF8Encoding]::new(); ";
14739
15078
  function prependUtf8Prelude(command) {
14740
15079
  if (typeof command !== "string")
@@ -14747,14 +15086,14 @@ function prependUtf8Prelude(command) {
14747
15086
  return command;
14748
15087
  return PRELUDE + command;
14749
15088
  }
14750
- var handler14 = async (_ctx) => {
15089
+ var handler16 = async (_ctx) => {
14751
15090
  const enabled = process.platform === "win32" && process.env.CODEFORGE_DISABLE_PWSH_UTF8 !== "1";
14752
- logLifecycle(PLUGIN_NAME14, "activate", { enabled, platform: process.platform });
15091
+ logLifecycle(PLUGIN_NAME16, "activate", { enabled, platform: process.platform });
14753
15092
  if (!enabled)
14754
15093
  return {};
14755
15094
  return {
14756
15095
  "tool.execute.before": async (input, output) => {
14757
- await safeAsync(PLUGIN_NAME14, "tool.execute.before", async () => {
15096
+ await safeAsync(PLUGIN_NAME16, "tool.execute.before", async () => {
14758
15097
  if (input.tool !== "bash")
14759
15098
  return;
14760
15099
  const args = output.args ?? {};
@@ -14763,7 +15102,7 @@ var handler14 = async (_ctx) => {
14763
15102
  if (next !== undefined && next !== original) {
14764
15103
  args["command"] = next;
14765
15104
  output.args = args;
14766
- safeWriteLog(PLUGIN_NAME14, {
15105
+ safeWriteLog(PLUGIN_NAME16, {
14767
15106
  hook: "tool.execute.before",
14768
15107
  tool: input.tool,
14769
15108
  callID: input.callID,
@@ -14956,7 +15295,7 @@ function buildRecoveryPlan(events, meta = { id: null }, opts = {}) {
14956
15295
  });
14957
15296
  }
14958
15297
  }
14959
- const inflight2 = [...toolMap.values()].sort((a, b) => b.last_ts - a.last_ts);
15298
+ const inflight3 = [...toolMap.values()].sort((a, b) => b.last_ts - a.last_ts);
14960
15299
  const proposed = window.some((t) => PENDING_CHANGES_TOOLS.has(t.tool));
14961
15300
  const applied = window.some((t) => APPLY_TOOLS.has(t.tool) && t.ok);
14962
15301
  const pending_changes_likely = proposed && !applied;
@@ -14980,7 +15319,7 @@ function buildRecoveryPlan(events, meta = { id: null }, opts = {}) {
14980
15319
  idleMs,
14981
15320
  lastUser,
14982
15321
  lastAgent,
14983
- inflight: inflight2,
15322
+ inflight: inflight3,
14984
15323
  pending_changes_likely,
14985
15324
  open_subtasks_likely,
14986
15325
  reason
@@ -14991,7 +15330,7 @@ function buildRecoveryPlan(events, meta = { id: null }, opts = {}) {
14991
15330
  idle_ms: idleMs,
14992
15331
  last_user_intent: lastUser,
14993
15332
  last_agent: lastAgent,
14994
- inflight_tools: inflight2,
15333
+ inflight_tools: inflight3,
14995
15334
  pending_changes_likely,
14996
15335
  open_subtasks_likely,
14997
15336
  summary
@@ -15096,8 +15435,8 @@ function isRecoveryWorthShowing(plan) {
15096
15435
  }
15097
15436
 
15098
15437
  // plugins/session-recovery.ts
15099
- var PLUGIN_NAME15 = "session-recovery";
15100
- logLifecycle(PLUGIN_NAME15, "import", {});
15438
+ var PLUGIN_NAME17 = "session-recovery";
15439
+ logLifecycle(PLUGIN_NAME17, "import", {});
15101
15440
  async function processSessionStart(currentSessionId, opts = {}) {
15102
15441
  if (opts.disabled) {
15103
15442
  return { ok: true, injected: false, reason: "disabled" };
@@ -15107,7 +15446,7 @@ async function processSessionStart(currentSessionId, opts = {}) {
15107
15446
  excludeIds.add(currentSessionId);
15108
15447
  const r = await scanLastSession({ ...opts, excludeIds: [...excludeIds] });
15109
15448
  if (!r.ok) {
15110
- opts.log?.warn?.(`[${PLUGIN_NAME15}] 扫描失败:${r.error}`);
15449
+ opts.log?.warn?.(`[${PLUGIN_NAME17}] 扫描失败:${r.error}`);
15111
15450
  return { ok: false, injected: false, reason: "scan_error", error: r.error };
15112
15451
  }
15113
15452
  const plan = r.plan;
@@ -15121,7 +15460,7 @@ async function processSessionStart(currentSessionId, opts = {}) {
15121
15460
  await opts.injectRecovery(injection);
15122
15461
  } catch (err) {
15123
15462
  const msg = err instanceof Error ? err.message : String(err);
15124
- opts.log?.warn?.(`[${PLUGIN_NAME15}] injectRecovery 异常:${msg}`);
15463
+ opts.log?.warn?.(`[${PLUGIN_NAME17}] injectRecovery 异常:${msg}`);
15125
15464
  return { ok: false, injected: false, plan, reason: "inject_error", error: msg };
15126
15465
  }
15127
15466
  }
@@ -15150,13 +15489,13 @@ function renderPrompt(plan) {
15150
15489
  return lines.join(`
15151
15490
  `);
15152
15491
  }
15153
- var log7 = makePluginLogger(PLUGIN_NAME15);
15492
+ var log8 = makePluginLogger(PLUGIN_NAME17);
15154
15493
  var _lastInjection = null;
15155
15494
  var sessionRecoveryServer = async (ctx) => {
15156
- logLifecycle(PLUGIN_NAME15, "activate", { directory: ctx.directory });
15495
+ logLifecycle(PLUGIN_NAME17, "activate", { directory: ctx.directory });
15157
15496
  return {
15158
15497
  event: async ({ event }) => {
15159
- await safeAsync(PLUGIN_NAME15, "event", async () => {
15498
+ await safeAsync(PLUGIN_NAME17, "event", async () => {
15160
15499
  const e = event;
15161
15500
  if (!e || typeof e.type !== "string")
15162
15501
  return;
@@ -15166,12 +15505,12 @@ var sessionRecoveryServer = async (ctx) => {
15166
15505
  const root = typeof e.properties?.["root"] === "string" ? e.properties["root"] : ctx.directory ?? process.cwd();
15167
15506
  const r = await processSessionStart(sid, {
15168
15507
  root,
15169
- log: log7,
15508
+ log: log8,
15170
15509
  injectRecovery: (inj) => {
15171
15510
  _lastInjection = inj;
15172
15511
  }
15173
15512
  });
15174
- safeWriteLog(PLUGIN_NAME15, {
15513
+ safeWriteLog(PLUGIN_NAME17, {
15175
15514
  hook: "event",
15176
15515
  type: "session.start",
15177
15516
  ok: r.ok,
@@ -15180,234 +15519,13 @@ var sessionRecoveryServer = async (ctx) => {
15180
15519
  last_session_id: r.plan?.last_session_id
15181
15520
  });
15182
15521
  if (r.injected && r.plan) {
15183
- log7.info(`[${PLUGIN_NAME15}] 注入恢复提示(last=${r.plan.last_session_id?.slice(0, 8) ?? "?"}, reason=${r.plan.reason})`);
15522
+ log8.info(`[${PLUGIN_NAME17}] 注入恢复提示(last=${r.plan.last_session_id?.slice(0, 8) ?? "?"}, reason=${r.plan.reason})`);
15184
15523
  }
15185
15524
  });
15186
15525
  }
15187
15526
  };
15188
15527
  };
15189
- var handler15 = sessionRecoveryServer;
15190
-
15191
- // plugins/subtask-heartbeat.ts
15192
- init_opencode_plugin_helpers();
15193
- var PLUGIN_NAME16 = "subtask-heartbeat";
15194
- logLifecycle(PLUGIN_NAME16, "import", {});
15195
- var HEARTBEAT_INTERVAL_MS2 = 30000;
15196
- var HEARTBEAT_DEBOUNCE_MS = 25000;
15197
- var TOAST_DURATION_MS3 = 5000;
15198
- var START_TOAST_DURATION_MS = 2000;
15199
- var inflight2 = new Map;
15200
- function extractCreatedChild(event) {
15201
- if (!event || typeof event !== "object")
15202
- return null;
15203
- const e = event;
15204
- if (e.type !== "session.created")
15205
- return null;
15206
- const info = e.properties?.info;
15207
- if (!info || typeof info !== "object")
15208
- return null;
15209
- const session = info;
15210
- if (typeof session.id !== "string")
15211
- return null;
15212
- if (typeof session.parentID !== "string" || session.parentID === "")
15213
- return null;
15214
- return { childID: session.id, parentID: session.parentID, agent: null };
15215
- }
15216
- function extractEndedSessionID(event) {
15217
- if (!event || typeof event !== "object")
15218
- return null;
15219
- const e = event;
15220
- if (typeof e.type !== "string")
15221
- return null;
15222
- if (e.type !== "session.idle" && e.type !== "session.deleted" && e.type !== "session.error") {
15223
- return null;
15224
- }
15225
- const props = e.properties ?? {};
15226
- const direct = props["sessionID"];
15227
- if (typeof direct === "string" && direct)
15228
- return { type: e.type, sessionID: direct };
15229
- const info = props["info"];
15230
- if (info && typeof info === "object") {
15231
- const sid = info.id;
15232
- if (typeof sid === "string" && sid)
15233
- return { type: e.type, sessionID: sid };
15234
- }
15235
- return null;
15236
- }
15237
- function registerInflight(payload, now = Date.now()) {
15238
- const r = {
15239
- childID: payload.childID,
15240
- parentID: payload.parentID,
15241
- agent: payload.agent,
15242
- startedAt: now,
15243
- lastBeatAt: now,
15244
- lastTool: null
15245
- };
15246
- inflight2.set(payload.childID, r);
15247
- return r;
15248
- }
15249
- function recordToolBeat(sessionID, tool2, now = Date.now()) {
15250
- const r = inflight2.get(sessionID);
15251
- if (!r)
15252
- return null;
15253
- r.lastBeatAt = now;
15254
- r.lastTool = tool2;
15255
- return r;
15256
- }
15257
- function clearInflight2(sessionID) {
15258
- const r = inflight2.get(sessionID);
15259
- if (!r)
15260
- return null;
15261
- inflight2.delete(sessionID);
15262
- return r;
15263
- }
15264
- function pickHeartbeats(now = Date.now()) {
15265
- const out = [];
15266
- for (const r of inflight2.values()) {
15267
- if (now - r.lastBeatAt >= HEARTBEAT_DEBOUNCE_MS)
15268
- out.push(r);
15269
- }
15270
- return out;
15271
- }
15272
- function fmtElapsed(ms) {
15273
- const total = Math.max(0, Math.floor(ms / 1000));
15274
- const m = Math.floor(total / 60);
15275
- const s = total % 60;
15276
- return m > 0 ? `${m}m${s.toString().padStart(2, "0")}s` : `${s}s`;
15277
- }
15278
- function buildStartToast(r) {
15279
- const who = r.agent ?? "subagent";
15280
- return {
15281
- message: `\uD83D\uDE80 子 session 启动: ${who}`,
15282
- variant: "info"
15283
- };
15284
- }
15285
- function buildHeartbeatToast(r, now = Date.now()) {
15286
- const who = r.agent ?? "subagent";
15287
- const tool2 = r.lastTool ?? "thinking";
15288
- return {
15289
- message: `⏳ ${who} 仍在运行 ${fmtElapsed(now - r.startedAt)} | 当前: ${tool2}`,
15290
- variant: "info"
15291
- };
15292
- }
15293
- function buildEndToast(r, type, now = Date.now()) {
15294
- const who = r.agent ?? "subagent";
15295
- const elapsed = fmtElapsed(now - r.startedAt);
15296
- if (type === "session.error") {
15297
- return { message: `❌ ${who} 失败 (${elapsed})`, variant: "error" };
15298
- }
15299
- if (type === "session.deleted") {
15300
- return { message: `\uD83D\uDDD1️ ${who} 被取消 (${elapsed})`, variant: "error" };
15301
- }
15302
- return { message: `✅ ${who} 完成 (${elapsed})`, variant: "success" };
15303
- }
15304
- function normalizeVariant3(raw) {
15305
- if (raw === "info" || raw === "warning")
15306
- return "default";
15307
- return raw;
15308
- }
15309
- async function showToast3(client, payload, log8) {
15310
- if (typeof client?.tui?.showToast !== "function") {
15311
- log8?.debug?.("tui.showToast 不可用,noop");
15312
- return false;
15313
- }
15314
- try {
15315
- await client.tui.showToast({
15316
- body: {
15317
- message: payload.message,
15318
- variant: normalizeVariant3(payload.variant),
15319
- duration: payload.duration ?? TOAST_DURATION_MS3,
15320
- title: payload.title ?? "CodeForge"
15321
- }
15322
- });
15323
- return true;
15324
- } catch (err) {
15325
- log8?.warn("tui.showToast 抛错(已隔离)", {
15326
- error: err instanceof Error ? err.message : String(err)
15327
- });
15328
- return false;
15329
- }
15330
- }
15331
- var log8 = makePluginLogger(PLUGIN_NAME16);
15332
- var subtaskHeartbeatServer = async (ctx) => {
15333
- logLifecycle(PLUGIN_NAME16, "activate", {
15334
- directory: ctx.directory,
15335
- intervalMs: HEARTBEAT_INTERVAL_MS2
15336
- });
15337
- const client = ctx.client;
15338
- const interval = setInterval(() => {
15339
- safeAsync(PLUGIN_NAME16, "interval", async () => {
15340
- const beats = pickHeartbeats();
15341
- if (beats.length === 0)
15342
- return;
15343
- for (const r of beats) {
15344
- const t = buildHeartbeatToast(r);
15345
- const sent = await showToast3(client, t, log8);
15346
- safeWriteLog(PLUGIN_NAME16, {
15347
- hook: "interval",
15348
- child: r.childID,
15349
- parent: r.parentID,
15350
- tool: r.lastTool,
15351
- elapsed_ms: Date.now() - r.startedAt,
15352
- toast_sent: sent
15353
- });
15354
- r.lastBeatAt = Date.now();
15355
- }
15356
- });
15357
- }, HEARTBEAT_INTERVAL_MS2);
15358
- if (typeof interval.unref === "function") {
15359
- interval.unref();
15360
- }
15361
- return {
15362
- event: async ({ event }) => {
15363
- await safeAsync(PLUGIN_NAME16, "event", async () => {
15364
- const created = extractCreatedChild(event);
15365
- if (created) {
15366
- const record = registerInflight(created);
15367
- safeWriteLog(PLUGIN_NAME16, {
15368
- hook: "event",
15369
- type: "session.created",
15370
- child: created.childID,
15371
- parent: created.parentID
15372
- });
15373
- const startToast = buildStartToast(record);
15374
- const sent = await showToast3(client, { ...startToast, duration: START_TOAST_DURATION_MS }, log8);
15375
- safeWriteLog(PLUGIN_NAME16, {
15376
- hook: "event",
15377
- type: "session.created.toast",
15378
- child: created.childID,
15379
- toast_sent: sent
15380
- });
15381
- return;
15382
- }
15383
- const ended = extractEndedSessionID(event);
15384
- if (ended) {
15385
- const r = clearInflight2(ended.sessionID);
15386
- if (r) {
15387
- const t = buildEndToast(r, ended.type);
15388
- const sent = await showToast3(client, t, log8);
15389
- safeWriteLog(PLUGIN_NAME16, {
15390
- hook: "event",
15391
- type: ended.type,
15392
- child: r.childID,
15393
- elapsed_ms: Date.now() - r.startedAt,
15394
- toast_sent: sent,
15395
- end_toast_message: t.message
15396
- });
15397
- }
15398
- }
15399
- });
15400
- },
15401
- "tool.execute.before": async (input) => {
15402
- await safeAsync(PLUGIN_NAME16, "tool.execute.before", async () => {
15403
- if (!input || typeof input.sessionID !== "string" || typeof input.tool !== "string")
15404
- return;
15405
- recordToolBeat(input.sessionID, input.tool);
15406
- });
15407
- }
15408
- };
15409
- };
15410
- var handler16 = subtaskHeartbeatServer;
15528
+ var handler17 = sessionRecoveryServer;
15411
15529
 
15412
15530
  // plugins/subtasks.ts
15413
15531
  import { promises as fs10 } from "node:fs";
@@ -15437,6 +15555,18 @@ async function schedule(opts) {
15437
15555
  const results = new Array(opts.subtasks.length);
15438
15556
  let nextIdx = 0;
15439
15557
  const workers = [];
15558
+ const fireFinish = async (i, res) => {
15559
+ if (!opts.onSubtaskFinish)
15560
+ return;
15561
+ try {
15562
+ await opts.onSubtaskFinish(res, i);
15563
+ } catch (err) {
15564
+ log9("warn", `[parallel] onSubtaskFinish 抛错(已隔离)`, {
15565
+ id: res.id,
15566
+ error: describe4(err)
15567
+ });
15568
+ }
15569
+ };
15440
15570
  const runOne = async (i, spec) => {
15441
15571
  const subStart = now();
15442
15572
  let alloc;
@@ -15444,7 +15574,7 @@ async function schedule(opts) {
15444
15574
  try {
15445
15575
  alloc = await opts.deps.allocateWorktree(spec.id);
15446
15576
  } catch (err) {
15447
- results[i] = {
15577
+ const res2 = {
15448
15578
  id: spec.id,
15449
15579
  ok: false,
15450
15580
  summary: clamp(`worktree 分配失败:${describe4(err)}`, limit),
@@ -15452,9 +15582,21 @@ async function schedule(opts) {
15452
15582
  duration_ms: now() - subStart,
15453
15583
  error: describe4(err)
15454
15584
  };
15585
+ results[i] = res2;
15586
+ await fireFinish(i, res2);
15455
15587
  return;
15456
15588
  }
15457
15589
  }
15590
+ if (opts.onSubtaskStart) {
15591
+ try {
15592
+ await opts.onSubtaskStart(spec, i);
15593
+ } catch (err) {
15594
+ log9("warn", `[parallel] onSubtaskStart 抛错(已隔离)`, {
15595
+ id: spec.id,
15596
+ error: describe4(err)
15597
+ });
15598
+ }
15599
+ }
15458
15600
  const ctl = new AbortController;
15459
15601
  const cascade = () => ctl.abort();
15460
15602
  if (globalCtl.signal.aborted)
@@ -15507,6 +15649,7 @@ async function schedule(opts) {
15507
15649
  }
15508
15650
  }
15509
15651
  results[i] = res;
15652
+ await fireFinish(i, res);
15510
15653
  };
15511
15654
  const launch = async () => {
15512
15655
  while (true) {
@@ -15515,13 +15658,15 @@ async function schedule(opts) {
15515
15658
  return;
15516
15659
  if (globalCtl.signal.aborted) {
15517
15660
  const spec = opts.subtasks[i];
15518
- results[i] = {
15661
+ const res = {
15519
15662
  id: spec.id,
15520
15663
  ok: false,
15521
15664
  summary: clamp("调度器已取消,未启动", limit),
15522
15665
  status: "cancelled",
15523
15666
  duration_ms: 0
15524
15667
  };
15668
+ results[i] = res;
15669
+ await fireFinish(i, res);
15525
15670
  continue;
15526
15671
  }
15527
15672
  await runOne(i, opts.subtasks[i]);
@@ -15646,6 +15791,7 @@ function pickStatus(r, taskAborted, globalAborted) {
15646
15791
  }
15647
15792
 
15648
15793
  // lib/opencode-runner.ts
15794
+ init_opencode_plugin_helpers();
15649
15795
  function makeOpencodeRunner(opts) {
15650
15796
  const log9 = opts.log ?? (() => {});
15651
15797
  return async (spec, runCtx) => {
@@ -15802,20 +15948,20 @@ async function withTimeout2(p, ms, signal) {
15802
15948
  throw err;
15803
15949
  }
15804
15950
  }
15805
- return await new Promise((resolve10, reject) => {
15951
+ return await new Promise((resolve11, reject) => {
15806
15952
  let settled = false;
15807
15953
  const timer = setTimeout(() => {
15808
15954
  if (settled)
15809
15955
  return;
15810
15956
  settled = true;
15811
- resolve10({ kind: "timeout" });
15957
+ resolve11({ kind: "timeout" });
15812
15958
  }, ms);
15813
15959
  const onAbort = () => {
15814
15960
  if (settled)
15815
15961
  return;
15816
15962
  settled = true;
15817
15963
  clearTimeout(timer);
15818
- resolve10({ kind: "aborted" });
15964
+ resolve11({ kind: "aborted" });
15819
15965
  };
15820
15966
  signal?.addEventListener("abort", onAbort, { once: true });
15821
15967
  p.then((value) => {
@@ -15824,7 +15970,7 @@ async function withTimeout2(p, ms, signal) {
15824
15970
  settled = true;
15825
15971
  clearTimeout(timer);
15826
15972
  signal?.removeEventListener("abort", onAbort);
15827
- resolve10({ kind: "ok", value });
15973
+ resolve11({ kind: "ok", value });
15828
15974
  }, (err) => {
15829
15975
  if (settled)
15830
15976
  return;
@@ -15860,11 +16006,51 @@ function clip4(s, max) {
15860
16006
  return "";
15861
16007
  return s.length <= max ? s : s.slice(0, max - 1) + "…";
15862
16008
  }
16009
+ async function sendParentNotice(client, sessionID, text, opts = {}) {
16010
+ const log9 = opts.log ?? (() => {});
16011
+ if (!client?.session) {
16012
+ log9("warn", "[sendParentNotice] client.session 不可用,noop");
16013
+ return false;
16014
+ }
16015
+ const sessionAny = client.session;
16016
+ if (typeof sessionAny.promptAsync !== "function") {
16017
+ log9("warn", "[sendParentNotice] promptAsync 不可用(SDK 太老?),noop");
16018
+ return false;
16019
+ }
16020
+ try {
16021
+ const res = await sessionAny.promptAsync({
16022
+ path: { id: sessionID },
16023
+ query: opts.directory ? { directory: opts.directory } : undefined,
16024
+ body: {
16025
+ noReply: true,
16026
+ parts: [
16027
+ {
16028
+ id: makePartId(),
16029
+ type: "text",
16030
+ text,
16031
+ synthetic: false,
16032
+ ignored: false
16033
+ }
16034
+ ]
16035
+ }
16036
+ });
16037
+ if (res && typeof res === "object" && "error" in res && res.error) {
16038
+ log9("warn", "[sendParentNotice] promptAsync 返回 error", { error: res.error });
16039
+ return false;
16040
+ }
16041
+ return true;
16042
+ } catch (err) {
16043
+ log9("warn", "[sendParentNotice] 抛错(已隔离)", {
16044
+ error: err instanceof Error ? err.message : String(err)
16045
+ });
16046
+ return false;
16047
+ }
16048
+ }
15863
16049
 
15864
16050
  // plugins/subtasks.ts
15865
16051
  init_opencode_plugin_helpers();
15866
16052
  init_runtime_paths();
15867
- var PLUGIN_NAME17 = "subtasks";
16053
+ var PLUGIN_NAME18 = "subtasks";
15868
16054
  function getLogFile(root = process.cwd()) {
15869
16055
  return path13.join(runtimeDir(root), "logs", "subtasks.log");
15870
16056
  }
@@ -15943,18 +16129,49 @@ async function handleParallelCommand(raw) {
15943
16129
  specs = splitDescriptions(args.description, { parentId });
15944
16130
  }
15945
16131
  if (specs.length === 0) {
15946
- log9?.warn(`[${PLUGIN_NAME17}] /parallel 缺有效子任务`);
16132
+ log9?.warn(`[${PLUGIN_NAME18}] /parallel 缺有效子任务`);
15947
16133
  await safeReply2(ctx, "⚠ /parallel 需要至少 1 个子任务(用 ; 或换行分隔)");
15948
16134
  return { ok: false, reason: "no_subtasks" };
15949
16135
  }
16136
+ const maxConcurrency = clampInt(args.maxConcurrency, 1, 16, 4);
16137
+ const totalTimeoutMs = clampInt(args.totalTimeout_ms, 1000, 60 * 60000, 30 * 60000);
16138
+ const canNotice = Boolean(ctx.client && ctx.parentSessionID);
16139
+ if (canNotice) {
16140
+ const lines = [
16141
+ `\uD83D\uDE80 /parallel 已派出 ${specs.length} 个子任务(并发=${maxConcurrency}):`,
16142
+ ...specs.map((s, i) => ` ${i + 1}. \`${s.id}\` — ${s.description}`),
16143
+ "",
16144
+ "\uD83D\uDCA1 用 /parallel-status 随时查进度;TUI 按 Ctrl+→ 进子 session 看实时。"
16145
+ ];
16146
+ await sendParentNotice(ctx.client, ctx.parentSessionID, lines.join(`
16147
+ `), {
16148
+ directory: ctx.directory,
16149
+ log: (lvl, msg, data) => {
16150
+ if (lvl === "error")
16151
+ log9?.error(msg, data);
16152
+ else if (lvl === "warn")
16153
+ log9?.warn(msg, data);
16154
+ else
16155
+ log9?.info(msg, data);
16156
+ }
16157
+ });
16158
+ }
15950
16159
  const runner = ctx.runner ?? mockRunner;
15951
16160
  let result;
15952
16161
  try {
15953
16162
  result = await schedule({
15954
16163
  parentId,
15955
16164
  subtasks: specs,
15956
- maxConcurrency: clampInt(args.maxConcurrency, 1, 16, 4),
15957
- totalTimeout_ms: clampInt(args.totalTimeout_ms, 1000, 60 * 60000, 30 * 60000),
16165
+ maxConcurrency,
16166
+ totalTimeout_ms: totalTimeoutMs,
16167
+ onSubtaskStart: canNotice ? async (spec, idx) => {
16168
+ await sendParentNotice(ctx.client, ctx.parentSessionID, `▶ 子任务 ${idx + 1}/${specs.length} 启动: \`${spec.id}\` — ${spec.description}`, { directory: ctx.directory });
16169
+ } : undefined,
16170
+ onSubtaskFinish: canNotice ? async (res, idx) => {
16171
+ const emoji = res.status === "success" ? "✅" : res.status === "need_review" ? "⚠" : res.status === "timeout" ? "⏱" : res.status === "cancelled" ? "\uD83D\uDDD1" : "❌";
16172
+ const fileCount = res.changedFiles?.length ?? 0;
16173
+ await sendParentNotice(ctx.client, ctx.parentSessionID, `${emoji} 子任务 ${idx + 1}/${specs.length} 完成 [${res.status}] \`${res.id}\` (${res.duration_ms}ms, files=${fileCount})`, { directory: ctx.directory });
16174
+ } : undefined,
15958
16175
  deps: {
15959
16176
  runSubtask: runner,
15960
16177
  allocateWorktree: ctx.allocateWorktree,
@@ -15970,7 +16187,7 @@ async function handleParallelCommand(raw) {
15970
16187
  });
15971
16188
  } catch (err) {
15972
16189
  const msg = err instanceof Error ? err.message : String(err);
15973
- log9?.error(`[${PLUGIN_NAME17}] schedule 抛错`, { error: msg });
16190
+ log9?.error(`[${PLUGIN_NAME18}] schedule 抛错`, { error: msg });
15974
16191
  await safeReply2(ctx, `❌ 并发调度失败:${msg}`);
15975
16192
  return { ok: false, reason: msg };
15976
16193
  }
@@ -15978,7 +16195,7 @@ async function handleParallelCommand(raw) {
15978
16195
  const summaryLines = [head, "", "```", result.digest.text, "```"];
15979
16196
  await safeReply2(ctx, summaryLines.join(`
15980
16197
  `));
15981
- log9?.info(`[${PLUGIN_NAME17}] schedule 完成`, {
16198
+ log9?.info(`[${PLUGIN_NAME18}] schedule 完成`, {
15982
16199
  parentId,
15983
16200
  success: result.digest.success,
15984
16201
  failed: result.digest.failed,
@@ -15988,7 +16205,7 @@ async function handleParallelCommand(raw) {
15988
16205
  try {
15989
16206
  await ctx.onCompleted(result);
15990
16207
  } catch (err) {
15991
- log9?.warn(`[${PLUGIN_NAME17}] onCompleted hook 抛错(已隔离)`, {
16208
+ log9?.warn(`[${PLUGIN_NAME18}] onCompleted hook 抛错(已隔离)`, {
15992
16209
  error: err instanceof Error ? err.message : String(err)
15993
16210
  });
15994
16211
  }
@@ -16019,14 +16236,17 @@ async function maybeHandleMessage(raw) {
16019
16236
  reply: ctx.reply,
16020
16237
  runner: ctx.runner,
16021
16238
  allocateWorktree: ctx.allocateWorktree,
16022
- log: ctx.log
16239
+ log: ctx.log,
16240
+ client: ctx.client,
16241
+ parentSessionID: ctx.parentSessionID,
16242
+ directory: ctx.directory
16023
16243
  });
16024
16244
  }
16025
16245
  async function writeLog(level, msg, data) {
16026
16246
  const line = JSON.stringify({
16027
16247
  ts: new Date().toISOString(),
16028
16248
  level,
16029
- plugin: PLUGIN_NAME17,
16249
+ plugin: PLUGIN_NAME18,
16030
16250
  msg,
16031
16251
  data
16032
16252
  }) + `
@@ -16037,11 +16257,11 @@ async function writeLog(level, msg, data) {
16037
16257
  await fs10.appendFile(logFile, line, "utf8");
16038
16258
  } catch {}
16039
16259
  }
16040
- logLifecycle(PLUGIN_NAME17, "import");
16260
+ logLifecycle(PLUGIN_NAME18, "import");
16041
16261
  var subtasksServer = async (ctx) => {
16042
- const log9 = makePluginLogger(PLUGIN_NAME17);
16262
+ const log9 = makePluginLogger(PLUGIN_NAME18);
16043
16263
  const client = ctx?.client ?? undefined;
16044
- logLifecycle(PLUGIN_NAME17, "activate", {
16264
+ logLifecycle(PLUGIN_NAME18, "activate", {
16045
16265
  directory: ctx.directory,
16046
16266
  hasClient: Boolean(client)
16047
16267
  });
@@ -16062,6 +16282,9 @@ var subtasksServer = async (ctx) => {
16062
16282
  return Promise.resolve();
16063
16283
  },
16064
16284
  log: log9,
16285
+ client,
16286
+ parentSessionID: input.sessionID,
16287
+ directory: ctx.directory,
16065
16288
  runner: client ? makeOpencodeRunner({
16066
16289
  client,
16067
16290
  parentSessionID: input.sessionID,
@@ -16087,7 +16310,7 @@ var subtasksServer = async (ctx) => {
16087
16310
  if (replyLines.length > 0 && Array.isArray(output?.parts)) {
16088
16311
  output.parts.length = 0;
16089
16312
  output.parts.push({
16090
- id: `parallel-${Date.now()}`,
16313
+ id: makePartId(),
16091
16314
  sessionID: input.sessionID,
16092
16315
  messageID: "",
16093
16316
  type: "text",
@@ -16098,20 +16321,20 @@ var subtasksServer = async (ctx) => {
16098
16321
  });
16099
16322
  }
16100
16323
  } catch (err) {
16101
- log9.error(`[${PLUGIN_NAME17}] command.execute.before 异常(已隔离)`, {
16324
+ log9.error(`[${PLUGIN_NAME18}] command.execute.before 异常(已隔离)`, {
16102
16325
  error: err instanceof Error ? err.message : String(err)
16103
16326
  });
16104
16327
  }
16105
16328
  }
16106
16329
  };
16107
16330
  };
16108
- var handler17 = subtasksServer;
16331
+ var handler18 = subtasksServer;
16109
16332
 
16110
16333
  // plugins/terminal-monitor.ts
16111
16334
  init_opencode_plugin_helpers();
16112
16335
  import * as crypto5 from "node:crypto";
16113
- var PLUGIN_NAME18 = "terminal-monitor";
16114
- logLifecycle(PLUGIN_NAME18, "import", {});
16336
+ var PLUGIN_NAME19 = "terminal-monitor";
16337
+ logLifecycle(PLUGIN_NAME19, "import", {});
16115
16338
  var DEFAULT_CONFIG7 = {
16116
16339
  minScore: 0.6,
16117
16340
  cooldownMs: 30000,
@@ -16393,17 +16616,17 @@ function describeError(err) {
16393
16616
  return String(err);
16394
16617
  }
16395
16618
  }
16396
- var log9 = makePluginLogger(PLUGIN_NAME18);
16619
+ var log9 = makePluginLogger(PLUGIN_NAME19);
16397
16620
  var lru = new FingerprintLRU2;
16398
16621
  var _lastNotification = null;
16399
16622
  var terminalMonitorServer = async (ctx) => {
16400
- logLifecycle(PLUGIN_NAME18, "activate", {
16623
+ logLifecycle(PLUGIN_NAME19, "activate", {
16401
16624
  directory: ctx.directory,
16402
16625
  triggerEventTypes: ["terminal.output", "terminal.exit"]
16403
16626
  });
16404
16627
  return {
16405
16628
  event: async ({ event }) => {
16406
- await safeAsync(PLUGIN_NAME18, "event", async () => {
16629
+ await safeAsync(PLUGIN_NAME19, "event", async () => {
16407
16630
  const e = event;
16408
16631
  if (!e || typeof e.type !== "string")
16409
16632
  return;
@@ -16417,7 +16640,7 @@ var terminalMonitorServer = async (ctx) => {
16417
16640
  _lastNotification = msg;
16418
16641
  }
16419
16642
  });
16420
- safeWriteLog(PLUGIN_NAME18, {
16643
+ safeWriteLog(PLUGIN_NAME19, {
16421
16644
  hook: "event",
16422
16645
  type: e.type,
16423
16646
  notified: r.notified,
@@ -16425,18 +16648,18 @@ var terminalMonitorServer = async (ctx) => {
16425
16648
  findings: r.notification?.findings.length ?? 0
16426
16649
  });
16427
16650
  if (r.notified && r.notification) {
16428
- log9.info(`[${PLUGIN_NAME18}] 反馈 ${r.notification.findings.length} 条 → ${r.notification.summary}`);
16651
+ log9.info(`[${PLUGIN_NAME19}] 反馈 ${r.notification.findings.length} 条 → ${r.notification.summary}`);
16429
16652
  }
16430
16653
  });
16431
16654
  }
16432
16655
  };
16433
16656
  };
16434
- var handler18 = terminalMonitorServer;
16657
+ var handler19 = terminalMonitorServer;
16435
16658
 
16436
16659
  // plugins/token-manager.ts
16437
16660
  init_opencode_plugin_helpers();
16438
- var PLUGIN_NAME19 = "token-manager";
16439
- logLifecycle(PLUGIN_NAME19, "import", {});
16661
+ var PLUGIN_NAME20 = "token-manager";
16662
+ logLifecycle(PLUGIN_NAME20, "import", {});
16440
16663
  async function handleMessageBefore(raw, log10, defaults) {
16441
16664
  const ctx = raw ?? {};
16442
16665
  if (!Array.isArray(ctx.messages) || ctx.messages.length === 0)
@@ -16456,21 +16679,21 @@ async function handleMessageBefore(raw, log10, defaults) {
16456
16679
  };
16457
16680
  if (r.compressed) {
16458
16681
  ctx.messages = r.messages;
16459
- log10?.info(`[${PLUGIN_NAME19}] 压缩 ${r.before.count}→${r.after.count} 条 / ${r.before.tokens}→${r.after.tokens} tokens`);
16682
+ log10?.info(`[${PLUGIN_NAME20}] 压缩 ${r.before.count}→${r.after.count} 条 / ${r.before.tokens}→${r.after.tokens} tokens`);
16460
16683
  }
16461
16684
  return r;
16462
16685
  } catch (err) {
16463
- log10?.warn(`[${PLUGIN_NAME19}] 压缩异常(已隔离)`, {
16686
+ log10?.warn(`[${PLUGIN_NAME20}] 压缩异常(已隔离)`, {
16464
16687
  error: err instanceof Error ? err.message : String(err)
16465
16688
  });
16466
16689
  return null;
16467
16690
  }
16468
16691
  }
16469
- var log10 = makePluginLogger(PLUGIN_NAME19);
16692
+ var log10 = makePluginLogger(PLUGIN_NAME20);
16470
16693
  var tokenManagerServer = async (ctx) => {
16471
16694
  const rt = loadRuntimeSync();
16472
16695
  const threshold = rt.runtime.context.condenser_threshold_ratio;
16473
- logLifecycle(PLUGIN_NAME19, "activate", {
16696
+ logLifecycle(PLUGIN_NAME20, "activate", {
16474
16697
  directory: ctx.directory,
16475
16698
  threshold,
16476
16699
  target: DEFAULT_CONDENSE.target,
@@ -16479,7 +16702,7 @@ var tokenManagerServer = async (ctx) => {
16479
16702
  });
16480
16703
  return {
16481
16704
  "experimental.chat.messages.transform": async (_input, output) => {
16482
- await safeAsync(PLUGIN_NAME19, "experimental.chat.messages.transform", async () => {
16705
+ await safeAsync(PLUGIN_NAME20, "experimental.chat.messages.transform", async () => {
16483
16706
  const list = output.messages;
16484
16707
  if (!Array.isArray(list) || list.length === 0)
16485
16708
  return;
@@ -16492,7 +16715,7 @@ var tokenManagerServer = async (ctx) => {
16492
16715
  const r = await handleMessageBefore({ messages: flat }, log10, { threshold });
16493
16716
  if (!r)
16494
16717
  return;
16495
- safeWriteLog(PLUGIN_NAME19, {
16718
+ safeWriteLog(PLUGIN_NAME20, {
16496
16719
  hook: "experimental.chat.messages.transform",
16497
16720
  mode: "observe-only",
16498
16721
  before_msgs: r.before.count,
@@ -16503,13 +16726,13 @@ var tokenManagerServer = async (ctx) => {
16503
16726
  reason: r.reason
16504
16727
  });
16505
16728
  if (r.compressed) {
16506
- 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)`);
16729
+ 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)`);
16507
16730
  }
16508
16731
  });
16509
16732
  }
16510
16733
  };
16511
16734
  };
16512
- var handler19 = tokenManagerServer;
16735
+ var handler20 = tokenManagerServer;
16513
16736
 
16514
16737
  // plugins/tool-policy.ts
16515
16738
  init_opencode_plugin_helpers();
@@ -16752,8 +16975,8 @@ function checkFileAccess(acl, file, op) {
16752
16975
  }
16753
16976
 
16754
16977
  // plugins/tool-policy.ts
16755
- var PLUGIN_NAME20 = "tool-policy";
16756
- logLifecycle(PLUGIN_NAME20, "import", {});
16978
+ var PLUGIN_NAME21 = "tool-policy";
16979
+ logLifecycle(PLUGIN_NAME21, "import", {});
16757
16980
  var EMPTY_ACL = { whitelistMode: false };
16758
16981
  function decideToolCall(ctx, cfg = {}) {
16759
16982
  const fallbackMode = cfg.defaultMode ?? DEFAULT_RUNTIME.autonomy.default_mode;
@@ -16805,13 +17028,13 @@ function classifyToolKind(toolName) {
16805
17028
  return "webfetch";
16806
17029
  return "other";
16807
17030
  }
16808
- var log11 = makePluginLogger(PLUGIN_NAME20);
17031
+ var log11 = makePluginLogger(PLUGIN_NAME21);
16809
17032
  var toolPolicyServer = async (ctx) => {
16810
17033
  const directory = ctx.directory ?? process.cwd();
16811
17034
  const cfg = await loadPolicy(directory);
16812
17035
  const rt = loadRuntimeSync();
16813
17036
  cfg.defaultMode = rt.runtime.autonomy.default_mode;
16814
- logLifecycle(PLUGIN_NAME20, "activate", {
17037
+ logLifecycle(PLUGIN_NAME21, "activate", {
16815
17038
  directory,
16816
17039
  acl_loaded: !!cfg.acl,
16817
17040
  default_mode: cfg.defaultMode,
@@ -16819,7 +17042,7 @@ var toolPolicyServer = async (ctx) => {
16819
17042
  });
16820
17043
  return {
16821
17044
  "tool.execute.before": async (input, output) => {
16822
- await safeAsync(PLUGIN_NAME20, "tool.execute.before", async () => {
17045
+ await safeAsync(PLUGIN_NAME21, "tool.execute.before", async () => {
16823
17046
  const toolName = input.tool;
16824
17047
  const argsObj = output.args ?? {};
16825
17048
  const files = [];
@@ -16835,7 +17058,7 @@ var toolPolicyServer = async (ctx) => {
16835
17058
  mode: cfg.defaultMode,
16836
17059
  files: files.length ? files : undefined
16837
17060
  }, cfg);
16838
- safeWriteLog(PLUGIN_NAME20, {
17061
+ safeWriteLog(PLUGIN_NAME21, {
16839
17062
  hook: "tool.execute.before",
16840
17063
  tool: toolName,
16841
17064
  callID: input.callID,
@@ -16844,19 +17067,19 @@ var toolPolicyServer = async (ctx) => {
16844
17067
  reasons: decision.reasons
16845
17068
  });
16846
17069
  if (decision.action === "deny") {
16847
- log11.warn(`[${PLUGIN_NAME20}] DENY ${toolName}: ${decision.reasons.join("; ")}`);
17070
+ log11.warn(`[${PLUGIN_NAME21}] DENY ${toolName}: ${decision.reasons.join("; ")}`);
16848
17071
  } else if (decision.action === "confirm") {
16849
- log11.info(`[${PLUGIN_NAME20}] CONFIRM ${toolName}: ${decision.reasons.join("; ")}`);
17072
+ log11.info(`[${PLUGIN_NAME21}] CONFIRM ${toolName}: ${decision.reasons.join("; ")}`);
16850
17073
  }
16851
17074
  });
16852
17075
  }
16853
17076
  };
16854
17077
  };
16855
- var handler20 = toolPolicyServer;
17078
+ var handler21 = toolPolicyServer;
16856
17079
 
16857
17080
  // plugins/update-checker.ts
16858
17081
  init_opencode_plugin_helpers();
16859
- import { existsSync as existsSync4 } from "node:fs";
17082
+ import { existsSync as existsSync5 } from "node:fs";
16860
17083
  import { homedir as homedir7 } from "node:os";
16861
17084
  import { join as join15 } from "node:path";
16862
17085
 
@@ -16864,7 +17087,7 @@ import { join as join15 } from "node:path";
16864
17087
  import { createHash as createHash6 } from "node:crypto";
16865
17088
  import {
16866
17089
  copyFileSync,
16867
- existsSync as existsSync3,
17090
+ existsSync as existsSync4,
16868
17091
  mkdirSync as mkdirSync3,
16869
17092
  mkdtempSync,
16870
17093
  readFileSync as readFileSync4,
@@ -16875,7 +17098,7 @@ import {
16875
17098
  writeFileSync as writeFileSync2
16876
17099
  } from "node:fs";
16877
17100
  import { homedir as homedir6, tmpdir } from "node:os";
16878
- import { dirname as dirname6, join as join14 } from "node:path";
17101
+ import { dirname as dirname7, join as join14 } from "node:path";
16879
17102
  import { fileURLToPath } from "node:url";
16880
17103
  import * as https from "node:https";
16881
17104
  import * as zlib from "node:zlib";
@@ -16883,7 +17106,7 @@ import * as zlib from "node:zlib";
16883
17106
  // lib/version-injected.ts
16884
17107
  function getInjectedVersion() {
16885
17108
  try {
16886
- const v = "0.3.7";
17109
+ const v = "0.3.9";
16887
17110
  if (typeof v === "string" && /^\d+\.\d+\.\d+/.test(v)) {
16888
17111
  return v;
16889
17112
  }
@@ -16972,7 +17195,7 @@ function readLocalVersion() {
16972
17195
  return injected;
16973
17196
  try {
16974
17197
  const here = fileURLToPath(import.meta.url);
16975
- const root = dirname6(dirname6(here));
17198
+ const root = dirname7(dirname7(here));
16976
17199
  const pkg = JSON.parse(readFileSync4(join14(root, "package.json"), "utf8"));
16977
17200
  return typeof pkg.version === "string" ? pkg.version : "0.0.0";
16978
17201
  } catch {
@@ -16987,7 +17210,7 @@ function defaultCacheFile() {
16987
17210
  }
16988
17211
  function readCache(file) {
16989
17212
  try {
16990
- if (!existsSync3(file))
17213
+ if (!existsSync4(file))
16991
17214
  return null;
16992
17215
  const raw = readFileSync4(file, "utf8");
16993
17216
  const obj = JSON.parse(raw);
@@ -17001,7 +17224,7 @@ function readCache(file) {
17001
17224
  }
17002
17225
  function writeCache(file, entry) {
17003
17226
  try {
17004
- mkdirSync3(dirname6(file), { recursive: true });
17227
+ mkdirSync3(dirname7(file), { recursive: true });
17005
17228
  writeFileSync2(file, JSON.stringify(entry, null, 2), "utf8");
17006
17229
  } catch {}
17007
17230
  }
@@ -17020,7 +17243,7 @@ function fetchLatestTagFromGitHub(repo) {
17020
17243
  });
17021
17244
  }
17022
17245
  function getJsonWithRedirect(url, hopsLeft) {
17023
- return new Promise((resolve10, reject) => {
17246
+ return new Promise((resolve11, reject) => {
17024
17247
  const u = new URL(url);
17025
17248
  const headers = {
17026
17249
  "User-Agent": "codeforge-update-checker",
@@ -17044,12 +17267,12 @@ function getJsonWithRedirect(url, hopsLeft) {
17044
17267
  return;
17045
17268
  }
17046
17269
  const next = new URL(res.headers.location, url).toString();
17047
- getJsonWithRedirect(next, hopsLeft - 1).then(resolve10, reject);
17270
+ getJsonWithRedirect(next, hopsLeft - 1).then(resolve11, reject);
17048
17271
  return;
17049
17272
  }
17050
17273
  if (status === 404) {
17051
17274
  res.resume();
17052
- resolve10(null);
17275
+ resolve11(null);
17053
17276
  return;
17054
17277
  }
17055
17278
  if (status >= 400) {
@@ -17060,7 +17283,7 @@ function getJsonWithRedirect(url, hopsLeft) {
17060
17283
  let body = "";
17061
17284
  res.setEncoding("utf8");
17062
17285
  res.on("data", (chunk) => body += chunk);
17063
- res.on("end", () => resolve10(body));
17286
+ res.on("end", () => resolve11(body));
17064
17287
  });
17065
17288
  req.on("timeout", () => {
17066
17289
  req.destroy();
@@ -17100,7 +17323,7 @@ async function fetchLatestFromNpm(opts) {
17100
17323
  return { version, tarballUrl, integrity };
17101
17324
  }
17102
17325
  function defaultHttpFetcher(url, timeoutMs) {
17103
- return new Promise((resolve10, reject) => {
17326
+ return new Promise((resolve11, reject) => {
17104
17327
  const u = new URL(url);
17105
17328
  const headers = {
17106
17329
  "User-Agent": "codeforge-update-checker",
@@ -17117,7 +17340,7 @@ function defaultHttpFetcher(url, timeoutMs) {
17117
17340
  const status = res.statusCode ?? 0;
17118
17341
  if (status === 404) {
17119
17342
  res.resume();
17120
- resolve10(null);
17343
+ resolve11(null);
17121
17344
  return;
17122
17345
  }
17123
17346
  if (status >= 400) {
@@ -17128,7 +17351,7 @@ function defaultHttpFetcher(url, timeoutMs) {
17128
17351
  let body = "";
17129
17352
  res.setEncoding("utf8");
17130
17353
  res.on("data", (chunk) => body += chunk);
17131
- res.on("end", () => resolve10(body));
17354
+ res.on("end", () => resolve11(body));
17132
17355
  });
17133
17356
  req.on("timeout", () => {
17134
17357
  req.destroy();
@@ -17147,7 +17370,7 @@ async function downloadAndExtractBundle(opts) {
17147
17370
  const tarBuf = zlib.gunzipSync(tarballBuf);
17148
17371
  extractTarToDir(tarBuf, tmpRoot);
17149
17372
  const bundlePath = join14(tmpRoot, "package", "dist", "index.js");
17150
- if (!existsSync3(bundlePath)) {
17373
+ if (!existsSync4(bundlePath)) {
17151
17374
  throw new Error(`bundle_not_found: ${bundlePath}`);
17152
17375
  }
17153
17376
  return { bundlePath, extractDir: tmpRoot };
@@ -17187,7 +17410,7 @@ function extractTarToDir(tarBuf, destRoot) {
17187
17410
  if (typeFlag === "0" || typeFlag === "" || typeFlag === "\x00") {
17188
17411
  const fileBuf = tarBuf.subarray(offset, offset + size);
17189
17412
  const dest = join14(destRoot, fullName);
17190
- mkdirSync3(dirname6(dest), { recursive: true });
17413
+ mkdirSync3(dirname7(dest), { recursive: true });
17191
17414
  writeFileSync2(dest, fileBuf);
17192
17415
  } else if (typeFlag === "5") {
17193
17416
  mkdirSync3(join14(destRoot, fullName), { recursive: true });
@@ -17199,7 +17422,7 @@ function defaultBinaryFetcher(url) {
17199
17422
  return downloadBinary(url, 3);
17200
17423
  }
17201
17424
  function downloadBinary(url, hopsLeft) {
17202
- return new Promise((resolve10, reject) => {
17425
+ return new Promise((resolve11, reject) => {
17203
17426
  const u = new URL(url);
17204
17427
  const req = https.request({
17205
17428
  host: u.hostname,
@@ -17217,7 +17440,7 @@ function downloadBinary(url, hopsLeft) {
17217
17440
  return;
17218
17441
  }
17219
17442
  const next = new URL(res.headers.location, url).toString();
17220
- downloadBinary(next, hopsLeft - 1).then(resolve10, reject);
17443
+ downloadBinary(next, hopsLeft - 1).then(resolve11, reject);
17221
17444
  return;
17222
17445
  }
17223
17446
  if (status >= 400) {
@@ -17227,7 +17450,7 @@ function downloadBinary(url, hopsLeft) {
17227
17450
  }
17228
17451
  const chunks = [];
17229
17452
  res.on("data", (chunk) => chunks.push(chunk));
17230
- res.on("end", () => resolve10(Buffer.concat(chunks)));
17453
+ res.on("end", () => resolve11(Buffer.concat(chunks)));
17231
17454
  });
17232
17455
  req.on("timeout", () => {
17233
17456
  req.destroy();
@@ -17240,16 +17463,16 @@ function downloadBinary(url, hopsLeft) {
17240
17463
  function atomicReplaceBundle(opts) {
17241
17464
  const { source, target, oldVersion } = opts;
17242
17465
  const keep = opts.keepBackups ?? 3;
17243
- if (!existsSync3(source)) {
17466
+ if (!existsSync4(source)) {
17244
17467
  throw new Error(`atomic_source_missing: ${source}`);
17245
17468
  }
17246
- mkdirSync3(dirname6(target), { recursive: true });
17469
+ mkdirSync3(dirname7(target), { recursive: true });
17247
17470
  const newPath = `${target}.new`;
17248
17471
  const backupPath = `${target}.bak.${oldVersion}`;
17249
17472
  let strategy = "rename";
17250
17473
  try {
17251
17474
  copyFileSync(source, newPath);
17252
- if (existsSync3(target)) {
17475
+ if (existsSync4(target)) {
17253
17476
  try {
17254
17477
  renameSync(target, backupPath);
17255
17478
  } catch (e) {
@@ -17285,7 +17508,7 @@ function atomicReplaceBundle(opts) {
17285
17508
  return { backupPath, strategy };
17286
17509
  } catch (e) {
17287
17510
  try {
17288
- if (existsSync3(newPath))
17511
+ if (existsSync4(newPath))
17289
17512
  unlinkSync(newPath);
17290
17513
  } catch {}
17291
17514
  throw e;
@@ -17295,7 +17518,7 @@ function cleanupOldBackups(target, keep) {
17295
17518
  if (keep <= 0)
17296
17519
  return;
17297
17520
  try {
17298
- const dir = dirname6(target);
17521
+ const dir = dirname7(target);
17299
17522
  const base = target.substring(dir.length + 1);
17300
17523
  const prefix = `${base}.bak.`;
17301
17524
  const all = readdirSync(dir).filter((f) => f.startsWith(prefix)).map((f) => {
@@ -17323,7 +17546,7 @@ function loadCompatibility(opts) {
17323
17546
  return null;
17324
17547
  file = join14(root, "compatibility.json");
17325
17548
  }
17326
- if (!existsSync3(file))
17549
+ if (!existsSync4(file))
17327
17550
  return null;
17328
17551
  const raw = readFileSync4(file, "utf8");
17329
17552
  const obj = JSON.parse(raw);
@@ -17346,7 +17569,7 @@ function loadCompatibility(opts) {
17346
17569
  function inferPluginRoot() {
17347
17570
  try {
17348
17571
  const here = fileURLToPath(import.meta.url);
17349
- return dirname6(dirname6(here));
17572
+ return dirname7(dirname7(here));
17350
17573
  } catch {
17351
17574
  return null;
17352
17575
  }
@@ -17386,13 +17609,29 @@ function compareOpencodeVersion(opts) {
17386
17609
  }
17387
17610
 
17388
17611
  // plugins/update-checker.ts
17389
- var PLUGIN_NAME21 = "update-checker";
17612
+ var PLUGIN_NAME22 = "update-checker";
17390
17613
  var PLUGIN_VERSION2 = "2.0.0";
17391
- logLifecycle(PLUGIN_NAME21, "import", { version: PLUGIN_VERSION2 });
17614
+ logLifecycle(PLUGIN_NAME22, "import", { version: PLUGIN_VERSION2 });
17392
17615
  var updateCheckerServer = async (ctx) => {
17616
+ const yieldResult = shouldYieldToLocalPlugin({ directory: ctx.directory });
17617
+ if (yieldResult.yield) {
17618
+ safeWriteLog(PLUGIN_NAME22, {
17619
+ level: "info",
17620
+ msg: "dev_mode_yield_skip",
17621
+ reason: yieldResult.reason,
17622
+ markerPath: yieldResult.markerPath,
17623
+ detail: formatYieldLog(yieldResult)
17624
+ });
17625
+ logLifecycle(PLUGIN_NAME22, "activate", {
17626
+ yield_to_local: true,
17627
+ yield_reason: yieldResult.reason,
17628
+ skipped: "auto_install + background_check"
17629
+ });
17630
+ return {};
17631
+ }
17393
17632
  const rt = loadRuntimeSync();
17394
17633
  const u = rt.runtime.update;
17395
- logLifecycle(PLUGIN_NAME21, "activate", {
17634
+ logLifecycle(PLUGIN_NAME22, "activate", {
17396
17635
  version: PLUGIN_VERSION2,
17397
17636
  auto_check_enabled: u.auto_check_enabled,
17398
17637
  interval_hours: u.interval_hours,
@@ -17404,14 +17643,14 @@ var updateCheckerServer = async (ctx) => {
17404
17643
  repo_fallback: u.repo,
17405
17644
  config_source: "codeforge.json"
17406
17645
  });
17407
- await safeAsync(PLUGIN_NAME21, "opencode_version_check", async () => {
17646
+ await safeAsync(PLUGIN_NAME22, "opencode_version_check", async () => {
17408
17647
  const compat = loadCompatibility();
17409
17648
  const opencodeVer = detectOpencodeVersion();
17410
17649
  const verdict = compareOpencodeVersion({
17411
17650
  currentOpencodeVer: opencodeVer,
17412
17651
  compat
17413
17652
  });
17414
- safeWriteLog(PLUGIN_NAME21, {
17653
+ safeWriteLog(PLUGIN_NAME22, {
17415
17654
  level: "info",
17416
17655
  msg: "opencode_version_check",
17417
17656
  opencodeVer,
@@ -17428,14 +17667,14 @@ var updateCheckerServer = async (ctx) => {
17428
17667
  }
17429
17668
  });
17430
17669
  if (!u.auto_check_enabled) {
17431
- safeWriteLog(PLUGIN_NAME21, {
17670
+ safeWriteLog(PLUGIN_NAME22, {
17432
17671
  level: "info",
17433
17672
  msg: "auto-check disabled (codeforge.json update.auto_check_enabled=false)"
17434
17673
  });
17435
17674
  return {};
17436
17675
  }
17437
17676
  setImmediate(() => {
17438
- safeAsync(PLUGIN_NAME21, "checkAndMaybeUpdate", async () => {
17677
+ safeAsync(PLUGIN_NAME22, "checkAndMaybeUpdate", async () => {
17439
17678
  const local = readLocalVersion();
17440
17679
  let npmResult = null;
17441
17680
  try {
@@ -17446,7 +17685,7 @@ var updateCheckerServer = async (ctx) => {
17446
17685
  timeoutMs: 5000
17447
17686
  });
17448
17687
  } catch (e) {
17449
- safeWriteLog(PLUGIN_NAME21, {
17688
+ safeWriteLog(PLUGIN_NAME22, {
17450
17689
  level: "warn",
17451
17690
  msg: "npm_fetch_failed",
17452
17691
  error: e.message
@@ -17455,7 +17694,7 @@ var updateCheckerServer = async (ctx) => {
17455
17694
  return;
17456
17695
  }
17457
17696
  if (!npmResult) {
17458
- safeWriteLog(PLUGIN_NAME21, {
17697
+ safeWriteLog(PLUGIN_NAME22, {
17459
17698
  level: "info",
17460
17699
  msg: "npm_no_release",
17461
17700
  package: u.package,
@@ -17464,7 +17703,7 @@ var updateCheckerServer = async (ctx) => {
17464
17703
  return;
17465
17704
  }
17466
17705
  const hasUpdate = cmpVersion(local, npmResult.version) < 0;
17467
- safeWriteLog(PLUGIN_NAME21, {
17706
+ safeWriteLog(PLUGIN_NAME22, {
17468
17707
  level: "info",
17469
17708
  msg: "npm_check_result",
17470
17709
  local,
@@ -17479,10 +17718,10 @@ var updateCheckerServer = async (ctx) => {
17479
17718
  更新命令:npx ${u.package} install --global`);
17480
17719
  return;
17481
17720
  }
17482
- await safeAsync(PLUGIN_NAME21, "auto_install_bundle", async () => {
17721
+ await safeAsync(PLUGIN_NAME22, "auto_install_bundle", async () => {
17483
17722
  const target = getOpencodeBundlePath();
17484
17723
  if (!target) {
17485
- safeWriteLog(PLUGIN_NAME21, {
17724
+ safeWriteLog(PLUGIN_NAME22, {
17486
17725
  level: "warn",
17487
17726
  msg: "auto_install_skip",
17488
17727
  reason: "无法定位 opencode bundle 路径"
@@ -17501,7 +17740,7 @@ var updateCheckerServer = async (ctx) => {
17501
17740
  oldVersion: local,
17502
17741
  keepBackups: u.backup_keep
17503
17742
  });
17504
- safeWriteLog(PLUGIN_NAME21, {
17743
+ safeWriteLog(PLUGIN_NAME22, {
17505
17744
  level: "info",
17506
17745
  msg: "auto_install_success",
17507
17746
  local,
@@ -17535,13 +17774,13 @@ function getOpencodeBundlePath() {
17535
17774
  candidates.push(join15(localAppData, "opencode", "codeforge", "index.js"));
17536
17775
  }
17537
17776
  for (const c of candidates) {
17538
- if (existsSync4(c))
17777
+ if (existsSync5(c))
17539
17778
  return c;
17540
17779
  }
17541
17780
  return candidates[0] ?? null;
17542
17781
  }
17543
17782
  async function fallbackToGitHubReleases(ctx, u) {
17544
- await safeAsync(PLUGIN_NAME21, "github_fallback", async () => {
17783
+ await safeAsync(PLUGIN_NAME22, "github_fallback", async () => {
17545
17784
  const result = await checkUpdateOnce({
17546
17785
  repo: u.repo,
17547
17786
  intervalMs: u.interval_hours * 3600 * 1000
@@ -17551,7 +17790,7 @@ async function fallbackToGitHubReleases(ctx, u) {
17551
17790
  }
17552
17791
  async function reportLegacyResult(ctx, result, repo) {
17553
17792
  if (result.error && !result.fromCache) {
17554
- safeWriteLog(PLUGIN_NAME21, {
17793
+ safeWriteLog(PLUGIN_NAME22, {
17555
17794
  level: "warn",
17556
17795
  msg: `update check failed: ${result.error}`,
17557
17796
  ...result
@@ -17559,7 +17798,7 @@ async function reportLegacyResult(ctx, result, repo) {
17559
17798
  return;
17560
17799
  }
17561
17800
  if (!result.hasUpdate) {
17562
- safeWriteLog(PLUGIN_NAME21, {
17801
+ safeWriteLog(PLUGIN_NAME22, {
17563
17802
  level: "info",
17564
17803
  msg: `up-to-date (local=${result.local}, remote=${result.remote}${result.fromCache ? ", from_cache" : ""})`,
17565
17804
  ...result
@@ -17569,7 +17808,7 @@ async function reportLegacyResult(ctx, result, repo) {
17569
17808
  const updateCmd = `bunx --bun github:${repo} install`;
17570
17809
  const toast = `[CodeForge] 有新版本:${result.local} → ${result.remote}
17571
17810
  更新命令:${updateCmd}`;
17572
- safeWriteLog(PLUGIN_NAME21, {
17811
+ safeWriteLog(PLUGIN_NAME22, {
17573
17812
  level: "info",
17574
17813
  msg: "new_version_available_github_fallback",
17575
17814
  local: result.local,
@@ -17580,17 +17819,17 @@ async function reportLegacyResult(ctx, result, repo) {
17580
17819
  await postToast(ctx, toast);
17581
17820
  }
17582
17821
  async function postToast(ctx, message) {
17583
- await safeAsync(PLUGIN_NAME21, "client.app.log", async () => {
17822
+ await safeAsync(PLUGIN_NAME22, "client.app.log", async () => {
17584
17823
  await ctx.client.app.log({
17585
17824
  body: {
17586
- service: PLUGIN_NAME21,
17825
+ service: PLUGIN_NAME22,
17587
17826
  level: "info",
17588
17827
  message
17589
17828
  }
17590
17829
  });
17591
17830
  });
17592
17831
  }
17593
- var handler21 = updateCheckerServer;
17832
+ var handler22 = updateCheckerServer;
17594
17833
 
17595
17834
  // plugins/workflow-engine.ts
17596
17835
  init_opencode_plugin_helpers();
@@ -18094,9 +18333,9 @@ async function runStepAutoFeedback(step, adapter) {
18094
18333
  }
18095
18334
 
18096
18335
  // plugins/workflow-engine.ts
18097
- var PLUGIN_NAME22 = "workflow-engine";
18098
- logLifecycle(PLUGIN_NAME22, "import", {});
18099
- var fallbackLog2 = makePluginLogger(PLUGIN_NAME22);
18336
+ var PLUGIN_NAME23 = "workflow-engine";
18337
+ logLifecycle(PLUGIN_NAME23, "import", {});
18338
+ var fallbackLog2 = makePluginLogger(PLUGIN_NAME23);
18100
18339
  var _registry = null;
18101
18340
  async function loadRegistry(workflowsDir) {
18102
18341
  const { loaded, failed } = await loadWorkflowsFromDir(workflowsDir);
@@ -18116,32 +18355,32 @@ async function handleCommandInvoked(raw, workflowsDir = "workflows") {
18116
18355
  const log12 = ctx.log ?? fallbackLog2;
18117
18356
  const command = typeof ctx.command === "string" ? ctx.command : null;
18118
18357
  if (!command) {
18119
- log12.warn(`[${PLUGIN_NAME22}] command.invoked 缺 command 字段`, ctx);
18358
+ log12.warn(`[${PLUGIN_NAME23}] command.invoked 缺 command 字段`, ctx);
18120
18359
  return null;
18121
18360
  }
18122
18361
  const reg = await ensureRegistry(workflowsDir);
18123
18362
  if (reg.errors.length) {
18124
- log12.warn(`[${PLUGIN_NAME22}] 有 ${reg.errors.length} 个 workflow 加载失败`, reg.errors);
18363
+ log12.warn(`[${PLUGIN_NAME23}] 有 ${reg.errors.length} 个 workflow 加载失败`, reg.errors);
18125
18364
  }
18126
18365
  const wf = reg.workflows.find((w) => matchesTrigger(w, command));
18127
18366
  if (!wf) {
18128
- log12.info(`[${PLUGIN_NAME22}] no workflow matches "${command}"`);
18367
+ log12.info(`[${PLUGIN_NAME23}] no workflow matches "${command}"`);
18129
18368
  return null;
18130
18369
  }
18131
- log12.info(`[${PLUGIN_NAME22}] dispatch "${command}" → workflow "${wf.name}"`);
18370
+ log12.info(`[${PLUGIN_NAME23}] dispatch "${command}" → workflow "${wf.name}"`);
18132
18371
  try {
18133
18372
  const result = await run(wf, {
18134
18373
  mode: ctx.adapter ? "real" : "dry_run",
18135
18374
  autonomy: ctx.autonomy ?? "semi",
18136
18375
  adapter: ctx.adapter
18137
18376
  });
18138
- log12.info(`[${PLUGIN_NAME22}] workflow "${wf.name}" 完成 (${result.plan.mode})`, {
18377
+ log12.info(`[${PLUGIN_NAME23}] workflow "${wf.name}" 完成 (${result.plan.mode})`, {
18139
18378
  steps: result.plan.steps.length,
18140
18379
  results: result.results.length
18141
18380
  });
18142
18381
  return result;
18143
18382
  } catch (err) {
18144
- log12.error(`[${PLUGIN_NAME22}] workflow "${wf.name}" 执行失败`, {
18383
+ log12.error(`[${PLUGIN_NAME23}] workflow "${wf.name}" 执行失败`, {
18145
18384
  error: err instanceof Error ? err.message : String(err)
18146
18385
  });
18147
18386
  throw err;
@@ -18150,15 +18389,15 @@ async function handleCommandInvoked(raw, workflowsDir = "workflows") {
18150
18389
  var workflowEngineServer = async (ctx) => {
18151
18390
  const directory = ctx.directory ?? process.cwd();
18152
18391
  const workflowsDir = path17.join(directory, "workflows");
18153
- ensureRegistry(workflowsDir).catch((err) => fallbackLog2.warn(`[${PLUGIN_NAME22}] preload workflows failed`, {
18392
+ ensureRegistry(workflowsDir).catch((err) => fallbackLog2.warn(`[${PLUGIN_NAME23}] preload workflows failed`, {
18154
18393
  error: err instanceof Error ? err.message : String(err)
18155
18394
  }));
18156
- logLifecycle(PLUGIN_NAME22, "activate", { directory, workflowsDir });
18395
+ logLifecycle(PLUGIN_NAME23, "activate", { directory, workflowsDir });
18157
18396
  return {
18158
18397
  "command.execute.before": async (input, output) => {
18159
- await safeAsync(PLUGIN_NAME22, "command.execute.before", async () => {
18398
+ await safeAsync(PLUGIN_NAME23, "command.execute.before", async () => {
18160
18399
  const cmd = input.command.startsWith("/") ? input.command : `/${input.command}`;
18161
- safeWriteLog(PLUGIN_NAME22, {
18400
+ safeWriteLog(PLUGIN_NAME23, {
18162
18401
  hook: "command.execute.before",
18163
18402
  command: cmd,
18164
18403
  sessionID: input.sessionID
@@ -18173,14 +18412,14 @@ var workflowEngineServer = async (ctx) => {
18173
18412
  });
18174
18413
  },
18175
18414
  "chat.message": async (input, output) => {
18176
- await safeAsync(PLUGIN_NAME22, "chat.message", async () => {
18415
+ await safeAsync(PLUGIN_NAME23, "chat.message", async () => {
18177
18416
  const text = extractUserText(output).trim();
18178
18417
  if (!text.startsWith("/"))
18179
18418
  return;
18180
18419
  const cmd = text.split(/\s+/)[0];
18181
18420
  if (!cmd)
18182
18421
  return;
18183
- safeWriteLog(PLUGIN_NAME22, {
18422
+ safeWriteLog(PLUGIN_NAME23, {
18184
18423
  hook: "chat.message",
18185
18424
  command: cmd,
18186
18425
  sessionID: input.sessionID
@@ -18190,7 +18429,7 @@ var workflowEngineServer = async (ctx) => {
18190
18429
  }
18191
18430
  };
18192
18431
  };
18193
- var handler22 = workflowEngineServer;
18432
+ var handler23 = workflowEngineServer;
18194
18433
 
18195
18434
  // src/index.ts
18196
18435
  var PLUGIN_ID = "codeforge";
@@ -18209,16 +18448,17 @@ var HANDLERS = [
18209
18448
  { name: "kh-reminder", init: handler11 },
18210
18449
  { name: "memories-context", init: handler12 },
18211
18450
  { name: "model-fallback", init: handler13 },
18212
- { name: "pwsh-utf8", init: handler14 },
18213
- { name: "session-recovery", init: handler15 },
18214
- { name: "subtask-heartbeat", init: handler16 },
18215
- { name: "subtasks", init: handler17 },
18216
- { name: "terminal-monitor", init: handler18 },
18217
- { name: "token-manager", init: handler19 },
18451
+ { name: "pwsh-utf8", init: handler16 },
18452
+ { name: "session-recovery", init: handler17 },
18453
+ { name: "subtask-heartbeat", init: handler14 },
18454
+ { name: "subtasks", init: handler18 },
18455
+ { name: "parallel-status", init: handler15 },
18456
+ { name: "terminal-monitor", init: handler19 },
18457
+ { name: "token-manager", init: handler20 },
18218
18458
  { name: "tool-heartbeat", init: handler7 },
18219
- { name: "tool-policy", init: handler20 },
18220
- { name: "update-checker", init: handler21 },
18221
- { name: "workflow-engine", init: handler22 }
18459
+ { name: "tool-policy", init: handler21 },
18460
+ { name: "update-checker", init: handler22 },
18461
+ { name: "workflow-engine", init: handler23 }
18222
18462
  ];
18223
18463
  function makeSerialHook(hookName, fns) {
18224
18464
  return async (input, output) => {
@@ -18233,69 +18473,91 @@ function makeSerialHook(hookName, fns) {
18233
18473
  }
18234
18474
  };
18235
18475
  }
18236
- var codeforgeServer = async (input) => {
18237
- const results = await Promise.allSettled(HANDLERS.map((h) => h.init(input)));
18238
- const hooksList = [];
18239
- results.forEach((r, i) => {
18240
- if (r.status === "fulfilled" && r.value && typeof r.value === "object") {
18241
- hooksList.push(r.value);
18242
- } else if (r.status === "rejected") {
18243
- log12.warn(`[${PLUGIN_ID}] handler ${HANDLERS[i].name} init failed (隔离,其他 handler 继续)`, { error: r.reason instanceof Error ? r.reason.message : String(r.reason) });
18476
+ function createCodeforgeServer(opts) {
18477
+ return async (input) => {
18478
+ if (opts.enableDevIsolation) {
18479
+ const yieldResult = shouldYieldToLocalPlugin({ directory: input.directory });
18480
+ if (yieldResult.yield) {
18481
+ const msg = formatYieldLog(yieldResult);
18482
+ log12.info(msg, { reason: yieldResult.reason, markerPath: yieldResult.markerPath });
18483
+ logLifecycle(PLUGIN_ID, "activate", {
18484
+ yield_to_local: true,
18485
+ yield_reason: yieldResult.reason,
18486
+ yield_marker_path: yieldResult.markerPath,
18487
+ directory: input.directory,
18488
+ handlers_total: HANDLERS.length,
18489
+ handlers_active: 0
18490
+ });
18491
+ return {};
18492
+ }
18244
18493
  }
18245
- });
18246
- logLifecycle(PLUGIN_ID, "activate", {
18247
- directory: input.directory,
18248
- handlers_total: HANDLERS.length,
18249
- handlers_active: hooksList.length,
18250
- handlers_failed: HANDLERS.length - hooksList.length
18251
- });
18252
- const chatMessageBucket = [];
18253
- const commandExecuteBeforeBucket = [];
18254
- const chatParamsBucket = [];
18255
- const toolExecuteBeforeBucket = [];
18256
- const chatMessagesTransformBucket = [];
18257
- const eventBucket = [];
18258
- const toolMerged = {};
18259
- for (const h of hooksList) {
18260
- if (h["chat.message"])
18261
- chatMessageBucket.push(h["chat.message"]);
18262
- if (h["command.execute.before"])
18263
- commandExecuteBeforeBucket.push(h["command.execute.before"]);
18264
- if (h["chat.params"])
18265
- chatParamsBucket.push(h["chat.params"]);
18266
- if (h["tool.execute.before"])
18267
- toolExecuteBeforeBucket.push(h["tool.execute.before"]);
18268
- if (h["experimental.chat.messages.transform"]) {
18269
- chatMessagesTransformBucket.push(h["experimental.chat.messages.transform"]);
18270
- }
18271
- if (h.event)
18272
- eventBucket.push(h.event);
18273
- if (h.tool)
18274
- Object.assign(toolMerged, h.tool);
18275
- }
18276
- return {
18277
- "chat.message": makeSerialHook("chat.message", chatMessageBucket),
18278
- "command.execute.before": makeSerialHook("command.execute.before", commandExecuteBeforeBucket),
18279
- "chat.params": makeSerialHook("chat.params", chatParamsBucket),
18280
- "tool.execute.before": makeSerialHook("tool.execute.before", toolExecuteBeforeBucket),
18281
- "experimental.chat.messages.transform": makeSerialHook("experimental.chat.messages.transform", chatMessagesTransformBucket),
18282
- event: async (envelope) => {
18283
- await Promise.all(eventBucket.map(async (fn) => {
18284
- try {
18285
- await fn(envelope);
18286
- } catch (err) {
18287
- log12.warn(`[${PLUGIN_ID}] event handler 异常(已隔离)`, {
18288
- error: err instanceof Error ? err.message : String(err)
18289
- });
18290
- }
18291
- }));
18292
- },
18293
- tool: toolMerged
18494
+ const results = await Promise.allSettled(HANDLERS.map((h) => h.init(input)));
18495
+ const hooksList = [];
18496
+ results.forEach((r, i) => {
18497
+ if (r.status === "fulfilled" && r.value && typeof r.value === "object") {
18498
+ hooksList.push(r.value);
18499
+ } else if (r.status === "rejected") {
18500
+ log12.warn(`[${PLUGIN_ID}] handler ${HANDLERS[i].name} init failed (隔离,其他 handler 继续)`, { error: r.reason instanceof Error ? r.reason.message : String(r.reason) });
18501
+ }
18502
+ });
18503
+ logLifecycle(PLUGIN_ID, "activate", {
18504
+ directory: input.directory,
18505
+ handlers_total: HANDLERS.length,
18506
+ handlers_active: hooksList.length,
18507
+ handlers_failed: HANDLERS.length - hooksList.length
18508
+ });
18509
+ const chatMessageBucket = [];
18510
+ const commandExecuteBeforeBucket = [];
18511
+ const chatParamsBucket = [];
18512
+ const toolExecuteBeforeBucket = [];
18513
+ const chatMessagesTransformBucket = [];
18514
+ const eventBucket = [];
18515
+ const toolMerged = {};
18516
+ for (const h of hooksList) {
18517
+ if (h["chat.message"])
18518
+ chatMessageBucket.push(h["chat.message"]);
18519
+ if (h["command.execute.before"])
18520
+ commandExecuteBeforeBucket.push(h["command.execute.before"]);
18521
+ if (h["chat.params"])
18522
+ chatParamsBucket.push(h["chat.params"]);
18523
+ if (h["tool.execute.before"])
18524
+ toolExecuteBeforeBucket.push(h["tool.execute.before"]);
18525
+ if (h["experimental.chat.messages.transform"]) {
18526
+ chatMessagesTransformBucket.push(h["experimental.chat.messages.transform"]);
18527
+ }
18528
+ if (h.event)
18529
+ eventBucket.push(h.event);
18530
+ if (h.tool)
18531
+ Object.assign(toolMerged, h.tool);
18532
+ }
18533
+ return {
18534
+ "chat.message": makeSerialHook("chat.message", chatMessageBucket),
18535
+ "command.execute.before": makeSerialHook("command.execute.before", commandExecuteBeforeBucket),
18536
+ "chat.params": makeSerialHook("chat.params", chatParamsBucket),
18537
+ "tool.execute.before": makeSerialHook("tool.execute.before", toolExecuteBeforeBucket),
18538
+ "experimental.chat.messages.transform": makeSerialHook("experimental.chat.messages.transform", chatMessagesTransformBucket),
18539
+ event: async (envelope) => {
18540
+ await Promise.all(eventBucket.map(async (fn) => {
18541
+ try {
18542
+ await fn(envelope);
18543
+ } catch (err) {
18544
+ log12.warn(`[${PLUGIN_ID}] event handler 异常(已隔离)`, {
18545
+ error: err instanceof Error ? err.message : String(err)
18546
+ });
18547
+ }
18548
+ }));
18549
+ },
18550
+ tool: toolMerged
18551
+ };
18294
18552
  };
18295
- };
18553
+ }
18554
+ var codeforgeServer = createCodeforgeServer({ enableDevIsolation: true });
18555
+ var codeforgeDevServer = createCodeforgeServer({ enableDevIsolation: false });
18296
18556
  var pluginModule = { id: PLUGIN_ID, server: codeforgeServer };
18297
18557
  var src_default = pluginModule;
18298
18558
  export {
18299
18559
  src_default as default,
18300
- codeforgeServer
18560
+ createCodeforgeServer,
18561
+ codeforgeServer,
18562
+ codeforgeDevServer
18301
18563
  };