@andyqiu/codeforge 0.5.9 → 0.5.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +79 -30
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -13563,8 +13563,8 @@ async function pruneOrphanWorktrees(mainRoot) {
13563
13563
  var DEFAULT_MERGE_LOOP_CONFIG = {
13564
13564
  maxReviewLoops: 3,
13565
13565
  autoCoder: true,
13566
- reviewTimeoutMs: 600000,
13567
- coderTimeoutMs: 1800000,
13566
+ reviewTimeoutMs: 180000,
13567
+ coderTimeoutMs: 600000,
13568
13568
  abortDirtyStrategy: "checkpoint"
13569
13569
  };
13570
13570
  async function runMergeLoop(opts) {
@@ -13614,7 +13614,11 @@ async function runMergeLoop(opts) {
13614
13614
  maxRounds: config.maxReviewLoops,
13615
13615
  ...lastReviewSummary ? { prevSummary: lastReviewSummary } : {},
13616
13616
  ...opts.signal ? { signal: opts.signal } : {}
13617
- }), config.reviewTimeoutMs, `reviewer 第 ${loops} 轮`, opts.signal);
13617
+ }), config.reviewTimeoutMs, `reviewer 第 ${loops} 轮`, opts.signal, {
13618
+ onHeartbeat: (elapsedMs) => {
13619
+ progress("dispatch_review", `reviewer 第 ${loops}/${config.maxReviewLoops} 轮仍在运行,已等待 ${Math.round(elapsedMs / 1000)}s`);
13620
+ }
13621
+ });
13618
13622
  } catch (err) {
13619
13623
  const e = err;
13620
13624
  if (isAbortError2(e)) {
@@ -13683,7 +13687,11 @@ async function runMergeLoop(opts) {
13683
13687
  ...opts.planId ? { planId: opts.planId } : {},
13684
13688
  reviewerSummary: reviewResult.summary,
13685
13689
  ...opts.signal ? { signal: opts.signal } : {}
13686
- }), config.coderTimeoutMs, `coder round ${loops}`, opts.signal);
13690
+ }), config.coderTimeoutMs, `coder round ${loops}`, opts.signal, {
13691
+ onHeartbeat: (elapsedMs) => {
13692
+ progress("dispatch_coder", `coder round ${loops} 仍在运行,已等待 ${Math.round(elapsedMs / 1000)}s`);
13693
+ }
13694
+ });
13687
13695
  progress("wait_coder", `coder 完成: ${coderResult.ok ? "ok" : "fail"} - ${coderResult.summary}`);
13688
13696
  if (!coderResult.ok) {
13689
13697
  return {
@@ -13766,34 +13774,50 @@ async function handleAbortDirty(opts, config, entry) {
13766
13774
  function isAbortError2(err) {
13767
13775
  return err instanceof Error && err.name === "AbortError";
13768
13776
  }
13769
- function withTimeout2(p, ms, label, signal) {
13777
+ function withTimeout2(p, ms, label, signal, hbOpts) {
13770
13778
  return new Promise((resolve11, reject) => {
13771
- const timer = setTimeout(() => {
13779
+ const startedAt = Date.now();
13780
+ let hbTimer = null;
13781
+ let timer;
13782
+ const cleanup = () => {
13783
+ clearTimeout(timer);
13784
+ if (hbTimer)
13785
+ clearInterval(hbTimer);
13786
+ if (signal)
13787
+ signal.removeEventListener("abort", onAbort);
13788
+ };
13789
+ timer = setTimeout(() => {
13790
+ cleanup();
13772
13791
  reject(new Error(`${label} 超时 (${ms}ms)`));
13773
13792
  }, ms);
13793
+ const hbInterval = hbOpts?.heartbeatIntervalMs ?? 30000;
13794
+ const hbCb = hbOpts?.onHeartbeat;
13795
+ if (hbCb) {
13796
+ hbTimer = setInterval(() => {
13797
+ try {
13798
+ hbCb(Date.now() - startedAt);
13799
+ } catch {}
13800
+ }, hbInterval);
13801
+ }
13774
13802
  const onAbort = () => {
13775
- clearTimeout(timer);
13803
+ cleanup();
13776
13804
  const err = new Error(`${label} aborted by signal`);
13777
13805
  err.name = "AbortError";
13778
13806
  reject(err);
13779
13807
  };
13780
13808
  if (signal) {
13781
13809
  if (signal.aborted) {
13782
- clearTimeout(timer);
13810
+ cleanup();
13783
13811
  onAbort();
13784
13812
  return;
13785
13813
  }
13786
13814
  signal.addEventListener("abort", onAbort, { once: true });
13787
13815
  }
13788
13816
  p.then((v) => {
13789
- clearTimeout(timer);
13790
- if (signal)
13791
- signal.removeEventListener("abort", onAbort);
13817
+ cleanup();
13792
13818
  resolve11(v);
13793
13819
  }, (e) => {
13794
- clearTimeout(timer);
13795
- if (signal)
13796
- signal.removeEventListener("abort", onAbort);
13820
+ cleanup();
13797
13821
  reject(e);
13798
13822
  });
13799
13823
  });
@@ -14901,7 +14925,7 @@ class ProductionSpawner {
14901
14925
  prompt,
14902
14926
  title: `[merge-review] sess=${args.sessionId.slice(0, 8)} r=${args.round}/${args.maxRounds}`,
14903
14927
  ...args.signal ? { signal: args.signal } : {},
14904
- timeoutMs: this.opts.reviewerTimeoutMs ?? 600000
14928
+ timeoutMs: this.opts.reviewerTimeoutMs ?? 180000
14905
14929
  }, args.sessionId);
14906
14930
  } catch (err) {
14907
14931
  throw err;
@@ -14933,7 +14957,7 @@ ${r.text.slice(0, 800)}`
14933
14957
  prompt,
14934
14958
  title: `[merge-fix] sess=${args.sessionId.slice(0, 8)}`,
14935
14959
  ...args.signal ? { signal: args.signal } : {},
14936
- timeoutMs: this.opts.coderTimeoutMs ?? 1800000
14960
+ timeoutMs: this.opts.coderTimeoutMs ?? 600000
14937
14961
  }, args.sessionId);
14938
14962
  } catch (err) {
14939
14963
  throw err;
@@ -21612,7 +21636,7 @@ import * as zlib from "node:zlib";
21612
21636
  // lib/version-injected.ts
21613
21637
  function getInjectedVersion() {
21614
21638
  try {
21615
- const v = "0.5.9";
21639
+ const v = "0.5.10";
21616
21640
  if (typeof v === "string" && /^\d+\.\d+\.\d+/.test(v)) {
21617
21641
  return v;
21618
21642
  }
@@ -22915,10 +22939,19 @@ function buildGitVcsWriteRegex(mainRoot) {
22915
22939
  return new RegExp(`git\\b[^\\n]*(?:-C\\s+|--work-tree[=\\s])${esc}`);
22916
22940
  }
22917
22941
  var WRITE_TOOLS = new Set(["write", "edit", "ast_edit"]);
22942
+ var CLASS_B_CALLER_WHITELIST = new Set([
22943
+ "codeforge",
22944
+ "reviewer",
22945
+ "general"
22946
+ ]);
22918
22947
  function rewritePath(value, mainRoot, worktreeRoot) {
22919
22948
  if (!value)
22920
22949
  return null;
22921
22950
  const resolved = path27.isAbsolute(value) ? value : path27.resolve(mainRoot, value);
22951
+ const wtPrefix2 = worktreeRoot.endsWith("/") ? worktreeRoot : worktreeRoot + "/";
22952
+ if (resolved === worktreeRoot || resolved.startsWith(wtPrefix2)) {
22953
+ return null;
22954
+ }
22922
22955
  if (resolved === mainRoot)
22923
22956
  return worktreeRoot;
22924
22957
  const prefix = mainRoot.endsWith("/") ? mainRoot : mainRoot + "/";
@@ -23195,19 +23228,35 @@ var sessionWorktreeGuardPlugin = async (ctx) => {
23195
23228
  if (toolName === "bash") {
23196
23229
  const command = argsObj["command"];
23197
23230
  if (typeof command === "string" && commandContainsMainRoot(command, mainRoot) && detectBashWriteIntent(command, mainRoot)) {
23198
- const snippet = command.length > 60 ? command.slice(0, 60) + "…" : command;
23199
- const reason = `[session-worktree-guard] DENIED: bash.command 含主仓绝对路径写操作 (${snippet}),请在当前 session worktree (${worktreePath}) 内操作`;
23200
- log14.warn(reason, { sessionId, command: command.slice(0, 200) });
23201
- safeWriteLog(PLUGIN_NAME25, {
23202
- hook: "tool.execute.before",
23203
- tool: toolName,
23204
- sessionID: input.sessionID,
23205
- action: "deny",
23206
- source: "bash-write-intent",
23207
- command: command.slice(0, 200)
23208
- });
23209
- denied = new DeniedError(reason);
23210
- return;
23231
+ const caller = await resolveAgentForGuard({ sessionID: input.sessionID, agent: input.agent }, ctx.client, log14);
23232
+ if (caller !== null && CLASS_B_CALLER_WHITELIST.has(caller)) {
23233
+ log14.debug?.(`[class-b-whitelist] allow caller=${caller}`, { sessionId, tool: toolName, command: command.slice(0, 200) });
23234
+ safeWriteLog(PLUGIN_NAME25, {
23235
+ hook: "tool.execute.before",
23236
+ tool: toolName,
23237
+ sessionID: input.sessionID,
23238
+ action: "allow-whitelist",
23239
+ source: "class-b-caller-whitelist",
23240
+ caller,
23241
+ command: command.slice(0, 200)
23242
+ });
23243
+ } else {
23244
+ const callerTag = caller === null ? "unresolved" : caller;
23245
+ const snippet = command.length > 60 ? command.slice(0, 60) + "…" : command;
23246
+ const reason = `[session-worktree-guard] DENIED: bash.command 含主仓绝对路径写操作 (${snippet}) [caller=${callerTag}],请在当前 session worktree (${worktreePath}) 内操作`;
23247
+ log14.warn(reason, { sessionId, caller: callerTag, command: command.slice(0, 200) });
23248
+ safeWriteLog(PLUGIN_NAME25, {
23249
+ hook: "tool.execute.before",
23250
+ tool: toolName,
23251
+ sessionID: input.sessionID,
23252
+ action: "deny",
23253
+ source: "bash-write-intent",
23254
+ caller: callerTag,
23255
+ command: command.slice(0, 200)
23256
+ });
23257
+ denied = new DeniedError(reason);
23258
+ return;
23259
+ }
23211
23260
  }
23212
23261
  }
23213
23262
  if (toolName === "write" || toolName === "edit") {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@andyqiu/codeforge",
3
- "version": "0.5.9",
3
+ "version": "0.5.10",
4
4
  "description": "CodeForge — opencode 的零侵入扩展包",
5
5
  "type": "module",
6
6
  "private": false,