@andyqiu/codeforge 0.3.11 → 0.3.12

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
@@ -1034,17 +1034,17 @@ var require_visit = __commonJS((exports) => {
1034
1034
  visit.BREAK = BREAK;
1035
1035
  visit.SKIP = SKIP;
1036
1036
  visit.REMOVE = REMOVE;
1037
- function visit_(key, node, visitor, path16) {
1038
- const ctrl = callVisitor(key, node, visitor, path16);
1037
+ function visit_(key, node, visitor, path13) {
1038
+ const ctrl = callVisitor(key, node, visitor, path13);
1039
1039
  if (identity.isNode(ctrl) || identity.isPair(ctrl)) {
1040
- replaceNode(key, path16, ctrl);
1041
- return visit_(key, ctrl, visitor, path16);
1040
+ replaceNode(key, path13, ctrl);
1041
+ return visit_(key, ctrl, visitor, path13);
1042
1042
  }
1043
1043
  if (typeof ctrl !== "symbol") {
1044
1044
  if (identity.isCollection(node)) {
1045
- path16 = Object.freeze(path16.concat(node));
1045
+ path13 = Object.freeze(path13.concat(node));
1046
1046
  for (let i = 0;i < node.items.length; ++i) {
1047
- const ci = visit_(i, node.items[i], visitor, path16);
1047
+ const ci = visit_(i, node.items[i], visitor, path13);
1048
1048
  if (typeof ci === "number")
1049
1049
  i = ci - 1;
1050
1050
  else if (ci === BREAK)
@@ -1055,13 +1055,13 @@ var require_visit = __commonJS((exports) => {
1055
1055
  }
1056
1056
  }
1057
1057
  } else if (identity.isPair(node)) {
1058
- path16 = Object.freeze(path16.concat(node));
1059
- const ck = visit_("key", node.key, visitor, path16);
1058
+ path13 = Object.freeze(path13.concat(node));
1059
+ const ck = visit_("key", node.key, visitor, path13);
1060
1060
  if (ck === BREAK)
1061
1061
  return BREAK;
1062
1062
  else if (ck === REMOVE)
1063
1063
  node.key = null;
1064
- const cv = visit_("value", node.value, visitor, path16);
1064
+ const cv = visit_("value", node.value, visitor, path13);
1065
1065
  if (cv === BREAK)
1066
1066
  return BREAK;
1067
1067
  else if (cv === REMOVE)
@@ -1082,17 +1082,17 @@ var require_visit = __commonJS((exports) => {
1082
1082
  visitAsync.BREAK = BREAK;
1083
1083
  visitAsync.SKIP = SKIP;
1084
1084
  visitAsync.REMOVE = REMOVE;
1085
- async function visitAsync_(key, node, visitor, path16) {
1086
- const ctrl = await callVisitor(key, node, visitor, path16);
1085
+ async function visitAsync_(key, node, visitor, path13) {
1086
+ const ctrl = await callVisitor(key, node, visitor, path13);
1087
1087
  if (identity.isNode(ctrl) || identity.isPair(ctrl)) {
1088
- replaceNode(key, path16, ctrl);
1089
- return visitAsync_(key, ctrl, visitor, path16);
1088
+ replaceNode(key, path13, ctrl);
1089
+ return visitAsync_(key, ctrl, visitor, path13);
1090
1090
  }
1091
1091
  if (typeof ctrl !== "symbol") {
1092
1092
  if (identity.isCollection(node)) {
1093
- path16 = Object.freeze(path16.concat(node));
1093
+ path13 = Object.freeze(path13.concat(node));
1094
1094
  for (let i = 0;i < node.items.length; ++i) {
1095
- const ci = await visitAsync_(i, node.items[i], visitor, path16);
1095
+ const ci = await visitAsync_(i, node.items[i], visitor, path13);
1096
1096
  if (typeof ci === "number")
1097
1097
  i = ci - 1;
1098
1098
  else if (ci === BREAK)
@@ -1103,13 +1103,13 @@ var require_visit = __commonJS((exports) => {
1103
1103
  }
1104
1104
  }
1105
1105
  } else if (identity.isPair(node)) {
1106
- path16 = Object.freeze(path16.concat(node));
1107
- const ck = await visitAsync_("key", node.key, visitor, path16);
1106
+ path13 = Object.freeze(path13.concat(node));
1107
+ const ck = await visitAsync_("key", node.key, visitor, path13);
1108
1108
  if (ck === BREAK)
1109
1109
  return BREAK;
1110
1110
  else if (ck === REMOVE)
1111
1111
  node.key = null;
1112
- const cv = await visitAsync_("value", node.value, visitor, path16);
1112
+ const cv = await visitAsync_("value", node.value, visitor, path13);
1113
1113
  if (cv === BREAK)
1114
1114
  return BREAK;
1115
1115
  else if (cv === REMOVE)
@@ -1136,23 +1136,23 @@ var require_visit = __commonJS((exports) => {
1136
1136
  }
1137
1137
  return visitor;
1138
1138
  }
1139
- function callVisitor(key, node, visitor, path16) {
1139
+ function callVisitor(key, node, visitor, path13) {
1140
1140
  if (typeof visitor === "function")
1141
- return visitor(key, node, path16);
1141
+ return visitor(key, node, path13);
1142
1142
  if (identity.isMap(node))
1143
- return visitor.Map?.(key, node, path16);
1143
+ return visitor.Map?.(key, node, path13);
1144
1144
  if (identity.isSeq(node))
1145
- return visitor.Seq?.(key, node, path16);
1145
+ return visitor.Seq?.(key, node, path13);
1146
1146
  if (identity.isPair(node))
1147
- return visitor.Pair?.(key, node, path16);
1147
+ return visitor.Pair?.(key, node, path13);
1148
1148
  if (identity.isScalar(node))
1149
- return visitor.Scalar?.(key, node, path16);
1149
+ return visitor.Scalar?.(key, node, path13);
1150
1150
  if (identity.isAlias(node))
1151
- return visitor.Alias?.(key, node, path16);
1151
+ return visitor.Alias?.(key, node, path13);
1152
1152
  return;
1153
1153
  }
1154
- function replaceNode(key, path16, node) {
1155
- const parent = path16[path16.length - 1];
1154
+ function replaceNode(key, path13, node) {
1155
+ const parent = path13[path13.length - 1];
1156
1156
  if (identity.isCollection(parent)) {
1157
1157
  parent.items[key] = node;
1158
1158
  } else if (identity.isPair(parent)) {
@@ -1711,10 +1711,10 @@ var require_Collection = __commonJS((exports) => {
1711
1711
  var createNode = require_createNode();
1712
1712
  var identity = require_identity();
1713
1713
  var Node = require_Node();
1714
- function collectionFromPath(schema, path16, value) {
1714
+ function collectionFromPath(schema, path13, value) {
1715
1715
  let v = value;
1716
- for (let i = path16.length - 1;i >= 0; --i) {
1717
- const k = path16[i];
1716
+ for (let i = path13.length - 1;i >= 0; --i) {
1717
+ const k = path13[i];
1718
1718
  if (typeof k === "number" && Number.isInteger(k) && k >= 0) {
1719
1719
  const a = [];
1720
1720
  a[k] = v;
@@ -1733,7 +1733,7 @@ var require_Collection = __commonJS((exports) => {
1733
1733
  sourceObjects: new Map
1734
1734
  });
1735
1735
  }
1736
- var isEmptyPath = (path16) => path16 == null || typeof path16 === "object" && !!path16[Symbol.iterator]().next().done;
1736
+ var isEmptyPath = (path13) => path13 == null || typeof path13 === "object" && !!path13[Symbol.iterator]().next().done;
1737
1737
 
1738
1738
  class Collection extends Node.NodeBase {
1739
1739
  constructor(type, schema) {
@@ -1754,11 +1754,11 @@ var require_Collection = __commonJS((exports) => {
1754
1754
  copy.range = this.range.slice();
1755
1755
  return copy;
1756
1756
  }
1757
- addIn(path16, value) {
1758
- if (isEmptyPath(path16))
1757
+ addIn(path13, value) {
1758
+ if (isEmptyPath(path13))
1759
1759
  this.add(value);
1760
1760
  else {
1761
- const [key, ...rest] = path16;
1761
+ const [key, ...rest] = path13;
1762
1762
  const node = this.get(key, true);
1763
1763
  if (identity.isCollection(node))
1764
1764
  node.addIn(rest, value);
@@ -1768,8 +1768,8 @@ var require_Collection = __commonJS((exports) => {
1768
1768
  throw new Error(`Expected YAML collection at ${key}. Remaining path: ${rest}`);
1769
1769
  }
1770
1770
  }
1771
- deleteIn(path16) {
1772
- const [key, ...rest] = path16;
1771
+ deleteIn(path13) {
1772
+ const [key, ...rest] = path13;
1773
1773
  if (rest.length === 0)
1774
1774
  return this.delete(key);
1775
1775
  const node = this.get(key, true);
@@ -1778,8 +1778,8 @@ var require_Collection = __commonJS((exports) => {
1778
1778
  else
1779
1779
  throw new Error(`Expected YAML collection at ${key}. Remaining path: ${rest}`);
1780
1780
  }
1781
- getIn(path16, keepScalar) {
1782
- const [key, ...rest] = path16;
1781
+ getIn(path13, keepScalar) {
1782
+ const [key, ...rest] = path13;
1783
1783
  const node = this.get(key, true);
1784
1784
  if (rest.length === 0)
1785
1785
  return !keepScalar && identity.isScalar(node) ? node.value : node;
@@ -1794,15 +1794,15 @@ var require_Collection = __commonJS((exports) => {
1794
1794
  return n == null || allowScalar && identity.isScalar(n) && n.value == null && !n.commentBefore && !n.comment && !n.tag;
1795
1795
  });
1796
1796
  }
1797
- hasIn(path16) {
1798
- const [key, ...rest] = path16;
1797
+ hasIn(path13) {
1798
+ const [key, ...rest] = path13;
1799
1799
  if (rest.length === 0)
1800
1800
  return this.has(key);
1801
1801
  const node = this.get(key, true);
1802
1802
  return identity.isCollection(node) ? node.hasIn(rest) : false;
1803
1803
  }
1804
- setIn(path16, value) {
1805
- const [key, ...rest] = path16;
1804
+ setIn(path13, value) {
1805
+ const [key, ...rest] = path13;
1806
1806
  if (rest.length === 0) {
1807
1807
  this.set(key, value);
1808
1808
  } else {
@@ -2608,7 +2608,7 @@ var require_merge = __commonJS((exports) => {
2608
2608
 
2609
2609
  // node_modules/yaml/dist/nodes/addPairToJSMap.js
2610
2610
  var require_addPairToJSMap = __commonJS((exports) => {
2611
- var log12 = require_log();
2611
+ var log8 = require_log();
2612
2612
  var merge = require_merge();
2613
2613
  var stringify = require_stringify();
2614
2614
  var identity = require_identity();
@@ -2657,7 +2657,7 @@ var require_addPairToJSMap = __commonJS((exports) => {
2657
2657
  let jsonStr = JSON.stringify(strKey);
2658
2658
  if (jsonStr.length > 40)
2659
2659
  jsonStr = jsonStr.substring(0, 36) + '..."';
2660
- log12.warn(ctx.doc.options.logLevel, `Keys with collection values will be stringified due to JS Object restrictions: ${jsonStr}. Set mapAsMap: true to use object keys.`);
2660
+ log8.warn(ctx.doc.options.logLevel, `Keys with collection values will be stringified due to JS Object restrictions: ${jsonStr}. Set mapAsMap: true to use object keys.`);
2661
2661
  ctx.mapKeyWarned = true;
2662
2662
  }
2663
2663
  return strKey;
@@ -4195,9 +4195,9 @@ var require_Document = __commonJS((exports) => {
4195
4195
  if (assertCollection(this.contents))
4196
4196
  this.contents.add(value);
4197
4197
  }
4198
- addIn(path16, value) {
4198
+ addIn(path13, value) {
4199
4199
  if (assertCollection(this.contents))
4200
- this.contents.addIn(path16, value);
4200
+ this.contents.addIn(path13, value);
4201
4201
  }
4202
4202
  createAlias(node, name) {
4203
4203
  if (!node.anchor) {
@@ -4246,30 +4246,30 @@ var require_Document = __commonJS((exports) => {
4246
4246
  delete(key) {
4247
4247
  return assertCollection(this.contents) ? this.contents.delete(key) : false;
4248
4248
  }
4249
- deleteIn(path16) {
4250
- if (Collection.isEmptyPath(path16)) {
4249
+ deleteIn(path13) {
4250
+ if (Collection.isEmptyPath(path13)) {
4251
4251
  if (this.contents == null)
4252
4252
  return false;
4253
4253
  this.contents = null;
4254
4254
  return true;
4255
4255
  }
4256
- return assertCollection(this.contents) ? this.contents.deleteIn(path16) : false;
4256
+ return assertCollection(this.contents) ? this.contents.deleteIn(path13) : false;
4257
4257
  }
4258
4258
  get(key, keepScalar) {
4259
4259
  return identity.isCollection(this.contents) ? this.contents.get(key, keepScalar) : undefined;
4260
4260
  }
4261
- getIn(path16, keepScalar) {
4262
- if (Collection.isEmptyPath(path16))
4261
+ getIn(path13, keepScalar) {
4262
+ if (Collection.isEmptyPath(path13))
4263
4263
  return !keepScalar && identity.isScalar(this.contents) ? this.contents.value : this.contents;
4264
- return identity.isCollection(this.contents) ? this.contents.getIn(path16, keepScalar) : undefined;
4264
+ return identity.isCollection(this.contents) ? this.contents.getIn(path13, keepScalar) : undefined;
4265
4265
  }
4266
4266
  has(key) {
4267
4267
  return identity.isCollection(this.contents) ? this.contents.has(key) : false;
4268
4268
  }
4269
- hasIn(path16) {
4270
- if (Collection.isEmptyPath(path16))
4269
+ hasIn(path13) {
4270
+ if (Collection.isEmptyPath(path13))
4271
4271
  return this.contents !== undefined;
4272
- return identity.isCollection(this.contents) ? this.contents.hasIn(path16) : false;
4272
+ return identity.isCollection(this.contents) ? this.contents.hasIn(path13) : false;
4273
4273
  }
4274
4274
  set(key, value) {
4275
4275
  if (this.contents == null) {
@@ -4278,13 +4278,13 @@ var require_Document = __commonJS((exports) => {
4278
4278
  this.contents.set(key, value);
4279
4279
  }
4280
4280
  }
4281
- setIn(path16, value) {
4282
- if (Collection.isEmptyPath(path16)) {
4281
+ setIn(path13, value) {
4282
+ if (Collection.isEmptyPath(path13)) {
4283
4283
  this.contents = value;
4284
4284
  } else if (this.contents == null) {
4285
- this.contents = Collection.collectionFromPath(this.schema, Array.from(path16), value);
4285
+ this.contents = Collection.collectionFromPath(this.schema, Array.from(path13), value);
4286
4286
  } else if (assertCollection(this.contents)) {
4287
- this.contents.setIn(path16, value);
4287
+ this.contents.setIn(path13, value);
4288
4288
  }
4289
4289
  }
4290
4290
  setSchema(version, options = {}) {
@@ -6179,9 +6179,9 @@ var require_cst_visit = __commonJS((exports) => {
6179
6179
  visit.BREAK = BREAK;
6180
6180
  visit.SKIP = SKIP;
6181
6181
  visit.REMOVE = REMOVE;
6182
- visit.itemAtPath = (cst, path16) => {
6182
+ visit.itemAtPath = (cst, path13) => {
6183
6183
  let item = cst;
6184
- for (const [field, index] of path16) {
6184
+ for (const [field, index] of path13) {
6185
6185
  const tok = item?.[field];
6186
6186
  if (tok && "items" in tok) {
6187
6187
  item = tok.items[index];
@@ -6190,23 +6190,23 @@ var require_cst_visit = __commonJS((exports) => {
6190
6190
  }
6191
6191
  return item;
6192
6192
  };
6193
- visit.parentCollection = (cst, path16) => {
6194
- const parent = visit.itemAtPath(cst, path16.slice(0, -1));
6195
- const field = path16[path16.length - 1][0];
6193
+ visit.parentCollection = (cst, path13) => {
6194
+ const parent = visit.itemAtPath(cst, path13.slice(0, -1));
6195
+ const field = path13[path13.length - 1][0];
6196
6196
  const coll = parent?.[field];
6197
6197
  if (coll && "items" in coll)
6198
6198
  return coll;
6199
6199
  throw new Error("Parent collection not found");
6200
6200
  };
6201
- function _visit(path16, item, visitor) {
6202
- let ctrl = visitor(item, path16);
6201
+ function _visit(path13, item, visitor) {
6202
+ let ctrl = visitor(item, path13);
6203
6203
  if (typeof ctrl === "symbol")
6204
6204
  return ctrl;
6205
6205
  for (const field of ["key", "value"]) {
6206
6206
  const token = item[field];
6207
6207
  if (token && "items" in token) {
6208
6208
  for (let i = 0;i < token.items.length; ++i) {
6209
- const ci = _visit(Object.freeze(path16.concat([[field, i]])), token.items[i], visitor);
6209
+ const ci = _visit(Object.freeze(path13.concat([[field, i]])), token.items[i], visitor);
6210
6210
  if (typeof ci === "number")
6211
6211
  i = ci - 1;
6212
6212
  else if (ci === BREAK)
@@ -6217,10 +6217,10 @@ var require_cst_visit = __commonJS((exports) => {
6217
6217
  }
6218
6218
  }
6219
6219
  if (typeof ctrl === "function" && field === "key")
6220
- ctrl = ctrl(item, path16);
6220
+ ctrl = ctrl(item, path13);
6221
6221
  }
6222
6222
  }
6223
- return typeof ctrl === "function" ? ctrl(item, path16) : ctrl;
6223
+ return typeof ctrl === "function" ? ctrl(item, path13) : ctrl;
6224
6224
  }
6225
6225
  exports.visit = visit;
6226
6226
  });
@@ -7489,14 +7489,14 @@ var require_parser = __commonJS((exports) => {
7489
7489
  case "scalar":
7490
7490
  case "single-quoted-scalar":
7491
7491
  case "double-quoted-scalar": {
7492
- const fs12 = this.flowScalar(this.type);
7492
+ const fs10 = this.flowScalar(this.type);
7493
7493
  if (atNextItem || it.value) {
7494
- map.items.push({ start, key: fs12, sep: [] });
7494
+ map.items.push({ start, key: fs10, sep: [] });
7495
7495
  this.onKeyLine = true;
7496
7496
  } else if (it.sep) {
7497
- this.stack.push(fs12);
7497
+ this.stack.push(fs10);
7498
7498
  } else {
7499
- Object.assign(it, { key: fs12, sep: [] });
7499
+ Object.assign(it, { key: fs10, sep: [] });
7500
7500
  this.onKeyLine = true;
7501
7501
  }
7502
7502
  return;
@@ -7624,13 +7624,13 @@ var require_parser = __commonJS((exports) => {
7624
7624
  case "scalar":
7625
7625
  case "single-quoted-scalar":
7626
7626
  case "double-quoted-scalar": {
7627
- const fs12 = this.flowScalar(this.type);
7627
+ const fs10 = this.flowScalar(this.type);
7628
7628
  if (!it || it.value)
7629
- fc.items.push({ start: [], key: fs12, sep: [] });
7629
+ fc.items.push({ start: [], key: fs10, sep: [] });
7630
7630
  else if (it.sep)
7631
- this.stack.push(fs12);
7631
+ this.stack.push(fs10);
7632
7632
  else
7633
- Object.assign(it, { key: fs12, sep: [] });
7633
+ Object.assign(it, { key: fs10, sep: [] });
7634
7634
  return;
7635
7635
  }
7636
7636
  case "flow-map-end":
@@ -7794,7 +7794,7 @@ var require_public_api = __commonJS((exports) => {
7794
7794
  var composer = require_composer();
7795
7795
  var Document = require_Document();
7796
7796
  var errors = require_errors();
7797
- var log12 = require_log();
7797
+ var log8 = require_log();
7798
7798
  var identity = require_identity();
7799
7799
  var lineCounter = require_line_counter();
7800
7800
  var parser = require_parser();
@@ -7846,7 +7846,7 @@ var require_public_api = __commonJS((exports) => {
7846
7846
  const doc = parseDocument(src, options);
7847
7847
  if (!doc)
7848
7848
  return null;
7849
- doc.warnings.forEach((warning) => log12.warn(doc.options.logLevel, warning));
7849
+ doc.warnings.forEach((warning) => log8.warn(doc.options.logLevel, warning));
7850
7850
  if (doc.errors.length > 0) {
7851
7851
  if (doc.options.logLevel !== "silent")
7852
7852
  throw doc.errors[0];
@@ -8030,11 +8030,11 @@ function shouldStopByStuck(history, cfg) {
8030
8030
  async function withTimeout3(p, timeoutMs) {
8031
8031
  if (timeoutMs <= 0)
8032
8032
  return await p;
8033
- return await new Promise((resolve11, reject) => {
8033
+ return await new Promise((resolve13, reject) => {
8034
8034
  const timer = setTimeout(() => reject(new Error(`timeout after ${timeoutMs}ms`)), timeoutMs);
8035
8035
  Promise.resolve(p).then((v) => {
8036
8036
  clearTimeout(timer);
8037
- resolve11(v);
8037
+ resolve13(v);
8038
8038
  }, (err) => {
8039
8039
  clearTimeout(timer);
8040
8040
  reject(err);
@@ -8066,7 +8066,7 @@ async function runAutoFeedback(cfg, ctx) {
8066
8066
  if (cmds.length === 0) {
8067
8067
  throw new Error("auto-feedback: 至少需要 test_cmd 或 lint_cmd 之一");
8068
8068
  }
8069
- const log12 = ctx.log ?? (() => {});
8069
+ const log13 = ctx.log ?? (() => {});
8070
8070
  const attempt = async () => {
8071
8071
  let last = {
8072
8072
  cmd: "",
@@ -8103,7 +8103,7 @@ ${excerpt}
8103
8103
 
8104
8104
  请基于错误信息修复 pending-changes(用 ast-edit / pending-changes.stage),完成后我会重新跑测试验证。`;
8105
8105
  const result2 = await ctx.dispatchToAgent("coder", prompt);
8106
- log12("info", `auto-feedback round ${round} dispatch summary: ${result2.summary.slice(0, 200)}`);
8106
+ log13("info", `auto-feedback round ${round} dispatch summary: ${result2.summary.slice(0, 200)}`);
8107
8107
  };
8108
8108
  const debugResult = await runWithAutoDebug({
8109
8109
  attempt,
@@ -8124,7 +8124,7 @@ ${excerpt}
8124
8124
  history: debugResult.history
8125
8125
  };
8126
8126
  if (!debugResult.ok && debugResult.stopReason === "max-rounds") {
8127
- log12("warn", `auto-feedback 达 max_retries=${cfg.max_retries},escalate to ${cfg.escalate_to}`);
8127
+ log13("warn", `auto-feedback 达 max_retries=${cfg.max_retries},escalate to ${cfg.escalate_to}`);
8128
8128
  const lastFail = debugResult.history[debugResult.history.length - 1];
8129
8129
  const excerpt = lastFail?.result ? extractErrorExcerpt(lastFail.result, cfg.error_excerpt_lines) : "(无失败结果)";
8130
8130
  const escalatePrompt = `coder 连续 ${cfg.max_retries} 轮自纠失败,错误片段:
@@ -10729,7 +10729,7 @@ class PendingChangesStore {
10729
10729
  const currentNormHash = crypto3.createHash("sha256").update(normalize3(current)).digest("hex");
10730
10730
  const origNormHash = crypto3.createHash("sha256").update(normalize3(origStored)).digest("hex");
10731
10731
  if (currentNormHash === origNormHash) {
10732
- console.warn(`[pending-changes] ADR-0044: ${id} 仅 EOL 漂移,已接受 apply ` + `(raw hash mismatch: ${data.meta.source_hash?.slice(0, 8)}… → ${currentHash.slice(0, 8)}…)`);
10732
+ console.warn(`[pending-changes] ADR:pending-changes-eol-tolerant-apply: ${id} 仅 EOL 漂移,已接受 apply ` + `(raw hash mismatch: ${data.meta.source_hash?.slice(0, 8)}… → ${currentHash.slice(0, 8)}…)`);
10733
10733
  } else {
10734
10734
  throw new Error(`apply: 目标文件已被修改(漂移)。原 hash=${data.meta.source_hash?.slice(0, 8)}… → 现 hash=${currentHash.slice(0, 8)}…,` + `请重新 stage 后再 apply`);
10735
10735
  }
@@ -10875,7 +10875,7 @@ var ArgsSchema15 = z16.discriminatedUnion("action", [
10875
10875
  target: z16.string().min(1).describe("相对路径(相对仓库根),如 src/foo.ts"),
10876
10876
  content: z16.string().describe("整文件新内容(UTF-8 文本)"),
10877
10877
  description: z16.string().optional().describe("一句话说明这次改动的意图(建议 ≤ 80 字)"),
10878
- force_eol: z16.enum(["lf", "crlf", "preserve"]).optional().describe("强制 EOL 转换(ADR-0033):lf/crlf 显式覆盖,preserve=C-EOL 智能保留(默认行为)。" + "严格期望某 EOL 的场景(脚本 shebang / lint 严格的 markdown 等)建议传 lf;" + "不确定时不传,让默认 C-EOL 处理。docs/adr/*.md 自动判定 lf。")
10878
+ force_eol: z16.enum(["lf", "crlf", "preserve"]).optional().describe("强制 EOL 转换(ADR:pending-changes-force-eol):lf/crlf 显式覆盖,preserve=C-EOL 智能保留(默认行为)。" + "严格期望某 EOL 的场景(脚本 shebang / lint 严格的 markdown 等)建议传 lf;" + "不确定时不传,让默认 C-EOL 处理。docs/adr/*.md 自动判定 lf。")
10879
10879
  }),
10880
10880
  z16.object({
10881
10881
  action: z16.literal("list"),
@@ -13040,6 +13040,8 @@ function toEntry(r) {
13040
13040
  init_opencode_plugin_helpers();
13041
13041
 
13042
13042
  // lib/codeforge-runtime.ts
13043
+ import { promises as fs8 } from "node:fs";
13044
+ import * as path11 from "node:path";
13043
13045
  var DEFAULT_RUNTIME = {
13044
13046
  autonomy: {
13045
13047
  default_mode: "semi",
@@ -13081,6 +13083,21 @@ function loadRuntimeSync(opts = {}) {
13081
13083
  }
13082
13084
  return parseRuntime(raw, hit);
13083
13085
  }
13086
+ async function loadRuntime(opts = {}) {
13087
+ const root = opts.root ?? process.cwd();
13088
+ const abs = path11.resolve(root, opts.file ?? CONFIG_FILE);
13089
+ let raw;
13090
+ try {
13091
+ raw = await fs8.readFile(abs, "utf8");
13092
+ } catch (e) {
13093
+ const code = e.code;
13094
+ if (code === "ENOENT") {
13095
+ return emptyResult({ reason: `${CONFIG_FILE} not found at ${abs} (using built-in defaults)` });
13096
+ }
13097
+ return emptyResult({ path: abs, reason: `read_failed: ${e.message}` });
13098
+ }
13099
+ return parseRuntime(raw, abs);
13100
+ }
13084
13101
  function emptyResult(opts) {
13085
13102
  return {
13086
13103
  ok: false,
@@ -13848,7 +13865,7 @@ var codeforgeToolsServer = async (ctx) => {
13848
13865
  target: z27.string().optional().describe("仅 stage:相对仓库根的目标文件路径"),
13849
13866
  content: z27.string().optional().describe("仅 stage:整文件新内容"),
13850
13867
  description: z27.string().optional().describe("仅 stage:一句话说明改动意图"),
13851
- force_eol: z27.enum(["lf", "crlf", "preserve"]).optional().describe("仅 stage:强制 EOL 转换(ADR-0033)。lf/crlf 显式覆盖,preserve=C-EOL 智能保留(默认)。不确定时不传"),
13868
+ force_eol: z27.enum(["lf", "crlf", "preserve"]).optional().describe("仅 stage:强制 EOL 转换(ADR:pending-changes-force-eol)。lf/crlf 显式覆盖,preserve=C-EOL 智能保留(默认)。不确定时不传"),
13852
13869
  id: z27.string().optional().describe("show / diff / apply / discard:pending change ID"),
13853
13870
  status: z27.enum(["pending", "applied", "discarded"]).optional().describe("仅 list:按状态过滤")
13854
13871
  },
@@ -14830,14 +14847,14 @@ var khReminderServer = async (ctx) => {
14830
14847
  var handler10 = khReminderServer;
14831
14848
 
14832
14849
  // lib/memories.ts
14833
- import { promises as fs8 } from "node:fs";
14834
- import * as path11 from "node:path";
14850
+ import { promises as fs9 } from "node:fs";
14851
+ import * as path12 from "node:path";
14835
14852
  import * as os4 from "node:os";
14836
14853
  function resolveConfig(c) {
14837
14854
  return {
14838
14855
  projectRoot: c.projectRoot,
14839
14856
  homeDir: c.homeDir ?? os4.homedir(),
14840
- projectName: c.projectName ?? path11.basename(c.projectRoot),
14857
+ projectName: c.projectName ?? path12.basename(c.projectRoot),
14841
14858
  kh: c.kh,
14842
14859
  now: c.now ?? Date.now,
14843
14860
  log: c.log ?? (() => {}),
@@ -14846,13 +14863,13 @@ function resolveConfig(c) {
14846
14863
  }
14847
14864
  function fileFor(scope, cfg) {
14848
14865
  if (scope === "project") {
14849
- return path11.join(cfg.projectRoot, ".codeforge", "memories.json");
14866
+ return path12.join(cfg.projectRoot, ".codeforge", "memories.json");
14850
14867
  }
14851
- return path11.join(cfg.homeDir, ".codeforge", "memories.json");
14868
+ return path12.join(cfg.homeDir, ".codeforge", "memories.json");
14852
14869
  }
14853
14870
  async function readBank(p) {
14854
14871
  try {
14855
- const raw = await fs8.readFile(p, "utf8");
14872
+ const raw = await fs9.readFile(p, "utf8");
14856
14873
  const arr = JSON.parse(raw);
14857
14874
  if (!Array.isArray(arr))
14858
14875
  return [];
@@ -14862,10 +14879,10 @@ async function readBank(p) {
14862
14879
  }
14863
14880
  }
14864
14881
  async function writeBank(p, items) {
14865
- await fs8.mkdir(path11.dirname(p), { recursive: true });
14882
+ await fs9.mkdir(path12.dirname(p), { recursive: true });
14866
14883
  const tmp = `${p}.tmp`;
14867
- await fs8.writeFile(tmp, JSON.stringify(items, null, 2), "utf8");
14868
- await fs8.rename(tmp, p);
14884
+ await fs9.writeFile(tmp, JSON.stringify(items, null, 2), "utf8");
14885
+ await fs9.rename(tmp, p);
14869
14886
  }
14870
14887
  function isMemory(x) {
14871
14888
  if (!x || typeof x !== "object")
@@ -15856,10 +15873,301 @@ var parallelStatusServer = async (ctx) => {
15856
15873
  };
15857
15874
  var handler14 = parallelStatusServer;
15858
15875
 
15859
- // plugins/pwsh-utf8.ts
15876
+ // plugins/parallel-tool-nudge.ts
15877
+ import { readFileSync as readFileSync4, readdirSync, statSync as statSync3 } from "node:fs";
15878
+ import { join as join12 } from "node:path";
15879
+ import { homedir as homedir6 } from "node:os";
15880
+
15881
+ // node_modules/yaml/dist/index.js
15882
+ var composer = require_composer();
15883
+ var Document = require_Document();
15884
+ var Schema = require_Schema();
15885
+ var errors = require_errors();
15886
+ var Alias = require_Alias();
15887
+ var identity = require_identity();
15888
+ var Pair = require_Pair();
15889
+ var Scalar = require_Scalar();
15890
+ var YAMLMap = require_YAMLMap();
15891
+ var YAMLSeq = require_YAMLSeq();
15892
+ var cst = require_cst();
15893
+ var lexer = require_lexer();
15894
+ var lineCounter = require_line_counter();
15895
+ var parser = require_parser();
15896
+ var publicApi = require_public_api();
15897
+ var visit = require_visit();
15898
+ var $Composer = composer.Composer;
15899
+ var $Document = Document.Document;
15900
+ var $Schema = Schema.Schema;
15901
+ var $YAMLError = errors.YAMLError;
15902
+ var $YAMLParseError = errors.YAMLParseError;
15903
+ var $YAMLWarning = errors.YAMLWarning;
15904
+ var $Alias = Alias.Alias;
15905
+ var $isAlias = identity.isAlias;
15906
+ var $isCollection = identity.isCollection;
15907
+ var $isDocument = identity.isDocument;
15908
+ var $isMap = identity.isMap;
15909
+ var $isNode = identity.isNode;
15910
+ var $isPair = identity.isPair;
15911
+ var $isScalar = identity.isScalar;
15912
+ var $isSeq = identity.isSeq;
15913
+ var $Pair = Pair.Pair;
15914
+ var $Scalar = Scalar.Scalar;
15915
+ var $YAMLMap = YAMLMap.YAMLMap;
15916
+ var $YAMLSeq = YAMLSeq.YAMLSeq;
15917
+ var $Lexer = lexer.Lexer;
15918
+ var $LineCounter = lineCounter.LineCounter;
15919
+ var $Parser = parser.Parser;
15920
+ var $parse = publicApi.parse;
15921
+ var $parseAllDocuments = publicApi.parseAllDocuments;
15922
+ var $parseDocument = publicApi.parseDocument;
15923
+ var $stringify = publicApi.stringify;
15924
+ var $visit = visit.visit;
15925
+ var $visitAsync = visit.visitAsync;
15926
+
15927
+ // plugins/parallel-tool-nudge.ts
15860
15928
  init_opencode_plugin_helpers();
15861
- var PLUGIN_NAME15 = "pwsh-utf8";
15929
+ var PLUGIN_NAME15 = "parallel-tool-nudge";
15862
15930
  logLifecycle(PLUGIN_NAME15, "import", {});
15931
+ var PARALLEL_SAFE_TOOLS = [
15932
+ "read",
15933
+ "smart_search",
15934
+ "repo_map",
15935
+ "webfetch",
15936
+ "list_recent",
15937
+ "get_working_memory"
15938
+ ];
15939
+ var DEFAULT_AGENT_KEY = "__default__";
15940
+ var NUDGE_MAX_LEN = 800;
15941
+ var agentToolsMap = new Map;
15942
+ function defaultReader(p) {
15943
+ return readFileSync4(p, "utf8");
15944
+ }
15945
+ function defaultDirReader(p) {
15946
+ return readdirSync(p);
15947
+ }
15948
+ function defaultDirExists(p) {
15949
+ try {
15950
+ return statSync3(p).isDirectory();
15951
+ } catch {
15952
+ return false;
15953
+ }
15954
+ }
15955
+ function parseAgentFrontmatter(content) {
15956
+ const fmMatch = /^---\r?\n([\s\S]*?)\r?\n---/.exec(content);
15957
+ if (!fmMatch || !fmMatch[1])
15958
+ return null;
15959
+ let parsed;
15960
+ try {
15961
+ parsed = $parse(fmMatch[1]);
15962
+ } catch {
15963
+ return null;
15964
+ }
15965
+ if (!parsed || typeof parsed !== "object")
15966
+ return null;
15967
+ const obj = parsed;
15968
+ const name = typeof obj["name"] === "string" ? obj["name"] : null;
15969
+ const tools = obj["allowed_tools"];
15970
+ if (!name || !Array.isArray(tools))
15971
+ return null;
15972
+ const allowedTools = [];
15973
+ for (const t of tools) {
15974
+ if (typeof t === "string")
15975
+ allowedTools.push(t);
15976
+ }
15977
+ return { name, allowedTools };
15978
+ }
15979
+ function loadAgentToolsMap(rootDir, opts = {}) {
15980
+ const reader = opts.reader ?? defaultReader;
15981
+ const dirReader = opts.dirReader ?? defaultDirReader;
15982
+ const dirExists = opts.dirExists ?? defaultDirExists;
15983
+ const homeAgentsDir = opts.homeAgentsDir ?? join12(homedir6(), ".config", "opencode", "agents");
15984
+ const candidateDirs = [
15985
+ join12(rootDir, ".codeforge", "agents"),
15986
+ join12(rootDir, "agents"),
15987
+ homeAgentsDir
15988
+ ];
15989
+ const result = new Map;
15990
+ const safeSet = new Set(PARALLEL_SAFE_TOOLS);
15991
+ const unionTools = new Set;
15992
+ const log8 = makePluginLogger(PLUGIN_NAME15);
15993
+ for (const dir of candidateDirs) {
15994
+ if (!dirExists(dir))
15995
+ continue;
15996
+ let entries;
15997
+ try {
15998
+ entries = dirReader(dir);
15999
+ } catch (err) {
16000
+ log8.warn(`agents 目录读取失败(已跳过)`, {
16001
+ dir,
16002
+ error: err instanceof Error ? err.message : String(err)
16003
+ });
16004
+ continue;
16005
+ }
16006
+ for (const entry of entries) {
16007
+ if (!entry.endsWith(".md"))
16008
+ continue;
16009
+ const path13 = join12(dir, entry);
16010
+ let content;
16011
+ try {
16012
+ content = reader(path13);
16013
+ } catch (err) {
16014
+ log8.warn(`agent.md 读取失败(已跳过)`, {
16015
+ path: path13,
16016
+ error: err instanceof Error ? err.message : String(err)
16017
+ });
16018
+ continue;
16019
+ }
16020
+ const parsed = parseAgentFrontmatter(content);
16021
+ if (!parsed) {
16022
+ log8.warn(`agent frontmatter 解析失败(已跳过)`, { path: path13 });
16023
+ continue;
16024
+ }
16025
+ if (result.has(parsed.name))
16026
+ continue;
16027
+ const intersect = [];
16028
+ const seen = new Set;
16029
+ for (const t of parsed.allowedTools) {
16030
+ if (safeSet.has(t) && !seen.has(t)) {
16031
+ intersect.push(t);
16032
+ seen.add(t);
16033
+ unionTools.add(t);
16034
+ }
16035
+ }
16036
+ result.set(parsed.name, intersect);
16037
+ }
16038
+ }
16039
+ result.set(DEFAULT_AGENT_KEY, Array.from(unionTools));
16040
+ return result;
16041
+ }
16042
+ var sessionAgentMap = new Map;
16043
+ var SESSION_CAP = 200;
16044
+ var SESSION_TTL_MS = 24 * 60 * 60 * 1000;
16045
+ function pruneIfOversize() {
16046
+ while (sessionAgentMap.size > SESSION_CAP) {
16047
+ const oldestKey = sessionAgentMap.keys().next().value;
16048
+ if (oldestKey === undefined)
16049
+ break;
16050
+ sessionAgentMap.delete(oldestKey);
16051
+ }
16052
+ }
16053
+ function isExpired(entry, now) {
16054
+ return now - entry.ts > SESSION_TTL_MS;
16055
+ }
16056
+ function resolveAgent(sessionID, nowFn = Date.now) {
16057
+ if (!sessionID)
16058
+ return "unknown";
16059
+ const entry = sessionAgentMap.get(sessionID);
16060
+ if (!entry)
16061
+ return "unknown";
16062
+ const now = nowFn();
16063
+ if (isExpired(entry, now)) {
16064
+ sessionAgentMap.delete(sessionID);
16065
+ return "unknown";
16066
+ }
16067
+ sessionAgentMap.delete(sessionID);
16068
+ sessionAgentMap.set(sessionID, entry);
16069
+ return entry.agent;
16070
+ }
16071
+ function renderNudge(agent, tools) {
16072
+ const isUnknown = agent === "unknown";
16073
+ const agentLine = isUnknown ? `You are running as a CodeForge agent (specific agent unknown for this turn).` : `You are the \`${agent}\` agent.`;
16074
+ const toolsBulleted = tools.length > 0 ? tools.map((t) => ` - \`${t}\``).join(`
16075
+ `) : ` - (no parallel-safe tools registered for this agent)`;
16076
+ const body = `────────────────────────────────────────
16077
+ PARALLEL TOOL CALL DIRECTIVE (auto-injected by parallel-tool-nudge plugin)
16078
+ ────────────────────────────────────────
16079
+ ${agentLine} The opencode runtime executes tool calls in your single
16080
+ response **truly in parallel** (verified: up to 22 tools in 72ms).
16081
+ You MUST batch-emit independent read-only tool calls in one response
16082
+ whenever possible.
16083
+
16084
+ Parallel-safe tools available to you:
16085
+ ${toolsBulleted}
16086
+
16087
+ Heuristics:
16088
+ - Need to read 3 files? → emit 3 \`read\` calls in ONE response, not 3 sequential turns
16089
+ - Need search + map? → emit \`smart_search\` + \`repo_map\` in ONE response
16090
+ - DO NOT batch write tools (ast_edit / pending_changes.apply / bash)
16091
+ - DO NOT chain reads where each read's path depends on previous read's output
16092
+
16093
+ This directive is re-injected every turn — it is not optional advice.
16094
+ ────────────────────────────────────────`;
16095
+ if (body.length <= NUDGE_MAX_LEN)
16096
+ return body;
16097
+ return body.slice(0, NUDGE_MAX_LEN - 4) + `
16098
+ …`;
16099
+ }
16100
+ var log8 = makePluginLogger(PLUGIN_NAME15);
16101
+ var parallelToolNudgeServer = async (ctx) => {
16102
+ try {
16103
+ const loaded = loadAgentToolsMap(ctx.directory ?? process.cwd());
16104
+ agentToolsMap.clear();
16105
+ for (const [k, v] of loaded.entries())
16106
+ agentToolsMap.set(k, v);
16107
+ } catch (err) {
16108
+ log8.warn(`loadAgentToolsMap 失败(plugin 将 no-op)`, {
16109
+ error: err instanceof Error ? err.message : String(err)
16110
+ });
16111
+ }
16112
+ const realAgentCount = Math.max(0, agentToolsMap.size - (agentToolsMap.has(DEFAULT_AGENT_KEY) ? 1 : 0));
16113
+ if (realAgentCount === 0) {
16114
+ agentToolsMap.clear();
16115
+ log8.warn(`0 real agents loaded; plugin will be no-op for this session`, {
16116
+ directory: ctx.directory
16117
+ });
16118
+ }
16119
+ const defaultTools = agentToolsMap.get(DEFAULT_AGENT_KEY) ?? [];
16120
+ logLifecycle(PLUGIN_NAME15, "activate", {
16121
+ directory: ctx.directory,
16122
+ real_agents_loaded: realAgentCount,
16123
+ default_tools_union: defaultTools,
16124
+ parallel_safe_whitelist: PARALLEL_SAFE_TOOLS,
16125
+ session_cap: SESSION_CAP,
16126
+ session_ttl_ms: SESSION_TTL_MS,
16127
+ no_op: agentToolsMap.size === 0
16128
+ });
16129
+ return {
16130
+ "chat.params": async (input, _output) => {
16131
+ await safeAsync(PLUGIN_NAME15, "chat.params", async () => {
16132
+ if (agentToolsMap.size === 0)
16133
+ return;
16134
+ const sid = input?.sessionID;
16135
+ const agent = input?.agent;
16136
+ if (!sid || !agent)
16137
+ return;
16138
+ sessionAgentMap.set(sid, { agent, ts: Date.now() });
16139
+ pruneIfOversize();
16140
+ });
16141
+ },
16142
+ "experimental.chat.system.transform": async (input, output) => {
16143
+ await safeAsync(PLUGIN_NAME15, "experimental.chat.system.transform", async () => {
16144
+ if (agentToolsMap.size === 0)
16145
+ return;
16146
+ if (!output || !Array.isArray(output.system))
16147
+ return;
16148
+ const sid = input?.sessionID;
16149
+ const agent = resolveAgent(sid);
16150
+ const tools = agent !== "unknown" ? agentToolsMap.get(agent) ?? agentToolsMap.get(DEFAULT_AGENT_KEY) ?? [] : agentToolsMap.get(DEFAULT_AGENT_KEY) ?? [];
16151
+ const nudge = renderNudge(agent, tools);
16152
+ output.system.push(nudge);
16153
+ safeWriteLog(PLUGIN_NAME15, {
16154
+ hook: "experimental.chat.system.transform",
16155
+ sessionID: sid,
16156
+ agent,
16157
+ injected_tools: tools,
16158
+ injected_chars: nudge.length,
16159
+ fallback: agent === "unknown"
16160
+ });
16161
+ });
16162
+ }
16163
+ };
16164
+ };
16165
+ var handler15 = parallelToolNudgeServer;
16166
+
16167
+ // plugins/pwsh-utf8.ts
16168
+ init_opencode_plugin_helpers();
16169
+ var PLUGIN_NAME16 = "pwsh-utf8";
16170
+ logLifecycle(PLUGIN_NAME16, "import", {});
15863
16171
  var PRELUDE = "chcp 65001 *> $null; " + "[Console]::OutputEncoding = [System.Text.UTF8Encoding]::new(); " + "$OutputEncoding = [System.Text.UTF8Encoding]::new(); ";
15864
16172
  function prependUtf8Prelude(command) {
15865
16173
  if (typeof command !== "string")
@@ -15872,15 +16180,15 @@ function prependUtf8Prelude(command) {
15872
16180
  return command;
15873
16181
  return PRELUDE + command;
15874
16182
  }
15875
- var handler15 = async (_ctx) => {
16183
+ var handler16 = async (_ctx) => {
15876
16184
  const enabled = process.platform === "win32" && process.env.CODEFORGE_DISABLE_PWSH_UTF8 !== "1";
15877
16185
  const reason = enabled ? "win32" : process.platform !== "win32" ? "non-win32" : "disabled-by-env";
15878
- logLifecycle(PLUGIN_NAME15, "activate", { enabled, platform: process.platform, reason });
16186
+ logLifecycle(PLUGIN_NAME16, "activate", { enabled, platform: process.platform, reason });
15879
16187
  if (!enabled)
15880
16188
  return {};
15881
16189
  return {
15882
16190
  "tool.execute.before": async (input, output) => {
15883
- await safeAsync(PLUGIN_NAME15, "tool.execute.before", async () => {
16191
+ await safeAsync(PLUGIN_NAME16, "tool.execute.before", async () => {
15884
16192
  if (input.tool !== "bash")
15885
16193
  return;
15886
16194
  const args = output.args ?? {};
@@ -15889,7 +16197,7 @@ var handler15 = async (_ctx) => {
15889
16197
  if (next !== undefined && next !== original) {
15890
16198
  args["command"] = next;
15891
16199
  output.args = args;
15892
- safeWriteLog(PLUGIN_NAME15, {
16200
+ safeWriteLog(PLUGIN_NAME16, {
15893
16201
  hook: "tool.execute.before",
15894
16202
  tool: input.tool,
15895
16203
  callID: input.callID,
@@ -15906,19 +16214,19 @@ var handler15 = async (_ctx) => {
15906
16214
  init_opencode_plugin_helpers();
15907
16215
 
15908
16216
  // lib/event-stream.ts
15909
- import { promises as fs9 } from "node:fs";
16217
+ import { promises as fs10 } from "node:fs";
15910
16218
  init_runtime_paths();
15911
- import * as path12 from "node:path";
16219
+ import * as path13 from "node:path";
15912
16220
  async function loadSession(id, opts = {}) {
15913
16221
  const file = resolveSessionFile(id, opts);
15914
- const raw = await fs9.readFile(file, "utf8");
16222
+ const raw = await fs10.readFile(file, "utf8");
15915
16223
  return parseJsonl(id, raw);
15916
16224
  }
15917
16225
  async function listSessions(opts = {}) {
15918
16226
  const dir = resolveDir(opts);
15919
16227
  let entries;
15920
16228
  try {
15921
- entries = await fs9.readdir(dir, { withFileTypes: true });
16229
+ entries = await fs10.readdir(dir, { withFileTypes: true });
15922
16230
  } catch (err) {
15923
16231
  if (err.code === "ENOENT")
15924
16232
  return [];
@@ -15928,10 +16236,10 @@ async function listSessions(opts = {}) {
15928
16236
  for (const e of entries) {
15929
16237
  if (!e.isFile() || !e.name.endsWith(".jsonl"))
15930
16238
  continue;
15931
- const file = path12.join(dir, e.name);
16239
+ const file = path13.join(dir, e.name);
15932
16240
  const id = e.name.replace(/\.jsonl$/, "");
15933
16241
  try {
15934
- const stat = await fs9.stat(file);
16242
+ const stat = await fs10.stat(file);
15935
16243
  const headerLine = await readFirstLine(file);
15936
16244
  let started_at = stat.birthtimeMs;
15937
16245
  if (headerLine) {
@@ -15955,11 +16263,11 @@ async function listSessions(opts = {}) {
15955
16263
  return out;
15956
16264
  }
15957
16265
  function resolveDir(opts = {}) {
15958
- const root = path12.resolve(opts.root ?? process.cwd());
15959
- return opts.sessions_dir ? path12.resolve(root, opts.sessions_dir) : path12.join(runtimeDir(root), "sessions");
16266
+ const root = path13.resolve(opts.root ?? process.cwd());
16267
+ return opts.sessions_dir ? path13.resolve(root, opts.sessions_dir) : path13.join(runtimeDir(root), "sessions");
15960
16268
  }
15961
16269
  function resolveSessionFile(id, opts = {}) {
15962
- return path12.join(resolveDir(opts), `${id}.jsonl`);
16270
+ return path13.join(resolveDir(opts), `${id}.jsonl`);
15963
16271
  }
15964
16272
  function parseJsonl(id, raw) {
15965
16273
  const events = [];
@@ -15994,7 +16302,7 @@ function isEvent(obj) {
15994
16302
  }
15995
16303
  async function readFirstLine(file) {
15996
16304
  const buf = Buffer.alloc(4096);
15997
- const fh = await fs9.open(file, "r");
16305
+ const fh = await fs10.open(file, "r");
15998
16306
  try {
15999
16307
  const { bytesRead } = await fh.read(buf, 0, buf.length, 0);
16000
16308
  const s = buf.subarray(0, bytesRead).toString("utf8");
@@ -16222,8 +16530,8 @@ function isRecoveryWorthShowing(plan) {
16222
16530
  }
16223
16531
 
16224
16532
  // plugins/session-recovery.ts
16225
- var PLUGIN_NAME16 = "session-recovery";
16226
- logLifecycle(PLUGIN_NAME16, "import", {});
16533
+ var PLUGIN_NAME17 = "session-recovery";
16534
+ logLifecycle(PLUGIN_NAME17, "import", {});
16227
16535
  async function processSessionStart(currentSessionId, opts = {}) {
16228
16536
  if (opts.disabled) {
16229
16537
  return { ok: true, injected: false, reason: "disabled" };
@@ -16233,7 +16541,7 @@ async function processSessionStart(currentSessionId, opts = {}) {
16233
16541
  excludeIds.add(currentSessionId);
16234
16542
  const r = await scanLastSession({ ...opts, excludeIds: [...excludeIds] });
16235
16543
  if (!r.ok) {
16236
- opts.log?.warn?.(`[${PLUGIN_NAME16}] 扫描失败:${r.error}`);
16544
+ opts.log?.warn?.(`[${PLUGIN_NAME17}] 扫描失败:${r.error}`);
16237
16545
  return { ok: false, injected: false, reason: "scan_error", error: r.error };
16238
16546
  }
16239
16547
  const plan = r.plan;
@@ -16247,7 +16555,7 @@ async function processSessionStart(currentSessionId, opts = {}) {
16247
16555
  await opts.injectRecovery(injection);
16248
16556
  } catch (err) {
16249
16557
  const msg = err instanceof Error ? err.message : String(err);
16250
- opts.log?.warn?.(`[${PLUGIN_NAME16}] injectRecovery 异常:${msg}`);
16558
+ opts.log?.warn?.(`[${PLUGIN_NAME17}] injectRecovery 异常:${msg}`);
16251
16559
  return { ok: false, injected: false, plan, reason: "inject_error", error: msg };
16252
16560
  }
16253
16561
  }
@@ -16276,13 +16584,13 @@ function renderPrompt(plan) {
16276
16584
  return lines.join(`
16277
16585
  `);
16278
16586
  }
16279
- var log8 = makePluginLogger(PLUGIN_NAME16);
16587
+ var log9 = makePluginLogger(PLUGIN_NAME17);
16280
16588
  var _lastInjection = null;
16281
16589
  var sessionRecoveryServer = async (ctx) => {
16282
- logLifecycle(PLUGIN_NAME16, "activate", { directory: ctx.directory });
16590
+ logLifecycle(PLUGIN_NAME17, "activate", { directory: ctx.directory });
16283
16591
  return {
16284
16592
  event: async ({ event }) => {
16285
- await safeAsync(PLUGIN_NAME16, "event", async () => {
16593
+ await safeAsync(PLUGIN_NAME17, "event", async () => {
16286
16594
  const e = event;
16287
16595
  if (!e || typeof e.type !== "string")
16288
16596
  return;
@@ -16292,12 +16600,12 @@ var sessionRecoveryServer = async (ctx) => {
16292
16600
  const root = typeof e.properties?.["root"] === "string" ? e.properties["root"] : ctx.directory ?? process.cwd();
16293
16601
  const r = await processSessionStart(sid, {
16294
16602
  root,
16295
- log: log8,
16603
+ log: log9,
16296
16604
  injectRecovery: (inj) => {
16297
16605
  _lastInjection = inj;
16298
16606
  }
16299
16607
  });
16300
- safeWriteLog(PLUGIN_NAME16, {
16608
+ safeWriteLog(PLUGIN_NAME17, {
16301
16609
  hook: "event",
16302
16610
  type: "session.start",
16303
16611
  ok: r.ok,
@@ -16306,100 +16614,691 @@ var sessionRecoveryServer = async (ctx) => {
16306
16614
  last_session_id: r.plan?.last_session_id
16307
16615
  });
16308
16616
  if (r.injected && r.plan) {
16309
- log8.info(`[${PLUGIN_NAME16}] 注入恢复提示(last=${r.plan.last_session_id?.slice(0, 8) ?? "?"}, reason=${r.plan.reason})`);
16617
+ log9.info(`[${PLUGIN_NAME17}] 注入恢复提示(last=${r.plan.last_session_id?.slice(0, 8) ?? "?"}, reason=${r.plan.reason})`);
16310
16618
  }
16311
16619
  });
16312
16620
  }
16313
16621
  };
16314
16622
  };
16315
- var handler16 = sessionRecoveryServer;
16623
+ var handler17 = sessionRecoveryServer;
16316
16624
 
16317
16625
  // plugins/subtasks.ts
16318
- import { promises as fs10 } from "node:fs";
16319
- import * as path13 from "node:path";
16626
+ import { promises as fs12 } from "node:fs";
16627
+ import * as path15 from "node:path";
16320
16628
 
16321
- // lib/parallel.ts
16322
- async function schedule(opts) {
16323
- validateSpecs(opts.subtasks);
16324
- const now = opts.deps.now ?? Date.now;
16325
- const start = now();
16326
- const log9 = opts.deps.log ?? (() => {});
16327
- const concurrency = Math.max(1, opts.maxConcurrency ?? 4);
16328
- const totalTimeout = opts.totalTimeout_ms ?? 30 * 60000;
16329
- const limit = Math.max(1, opts.summaryCharLimit ?? 500);
16330
- const globalCtl = new AbortController;
16331
- const onParentAbort = () => globalCtl.abort();
16332
- if (opts.signal) {
16333
- if (opts.signal.aborted)
16334
- globalCtl.abort();
16335
- else
16336
- opts.signal.addEventListener("abort", onParentAbort, { once: true });
16629
+ // lib/autonomy.ts
16630
+ import { execFile as execFile2 } from "node:child_process";
16631
+ import { promises as fs11 } from "node:fs";
16632
+ import * as path14 from "node:path";
16633
+ var DEFAULT_POLICIES = {
16634
+ step: {
16635
+ bash: "confirm",
16636
+ edit: "confirm",
16637
+ webfetch: "confirm",
16638
+ read: "confirm",
16639
+ search: "confirm",
16640
+ other: "confirm"
16641
+ },
16642
+ semi: {
16643
+ bash: "confirm",
16644
+ edit: "confirm",
16645
+ webfetch: "confirm",
16646
+ read: "auto",
16647
+ search: "auto",
16648
+ other: "auto"
16649
+ },
16650
+ full: {
16651
+ bash: "auto",
16652
+ edit: "auto",
16653
+ webfetch: "auto",
16654
+ read: "auto",
16655
+ search: "auto",
16656
+ other: "auto"
16337
16657
  }
16338
- const totalTimer = setTimeout(() => {
16339
- if (!globalCtl.signal.aborted)
16340
- globalCtl.abort();
16341
- }, totalTimeout);
16342
- const results = new Array(opts.subtasks.length);
16343
- let nextIdx = 0;
16344
- const workers = [];
16345
- const fireFinish = async (i, res) => {
16346
- if (!opts.onSubtaskFinish)
16347
- return;
16348
- try {
16349
- await opts.onSubtaskFinish(res, i);
16350
- } catch (err) {
16351
- log9("warn", `[parallel] onSubtaskFinish 抛错(已隔离)`, {
16352
- id: res.id,
16353
- error: describe4(err)
16354
- });
16355
- }
16356
- };
16357
- const runOne = async (i, spec) => {
16358
- const subStart = now();
16359
- let alloc;
16360
- if (opts.deps.allocateWorktree) {
16361
- try {
16362
- alloc = await opts.deps.allocateWorktree(spec.id);
16363
- } catch (err) {
16364
- const res2 = {
16365
- id: spec.id,
16366
- ok: false,
16367
- summary: clamp(`worktree 分配失败:${describe4(err)}`, limit),
16368
- status: "failed",
16369
- duration_ms: now() - subStart,
16370
- error: describe4(err)
16371
- };
16372
- results[i] = res2;
16373
- await fireFinish(i, res2);
16374
- return;
16375
- }
16376
- }
16377
- if (opts.onSubtaskStart) {
16378
- try {
16379
- await opts.onSubtaskStart(spec, i);
16380
- } catch (err) {
16381
- log9("warn", `[parallel] onSubtaskStart 抛错(已隔离)`, {
16382
- id: spec.id,
16383
- error: describe4(err)
16384
- });
16385
- }
16386
- }
16387
- const ctl = new AbortController;
16388
- const cascade = () => ctl.abort();
16389
- if (globalCtl.signal.aborted)
16390
- ctl.abort();
16391
- else
16392
- globalCtl.signal.addEventListener("abort", cascade, { once: true });
16393
- const perTimer = setTimeout(() => {
16394
- if (!ctl.signal.aborted)
16395
- ctl.abort();
16396
- }, Math.max(1, spec.timeout_ms ?? 5 * 60000));
16397
- let res;
16398
- try {
16399
- const r = await opts.deps.runSubtask(spec, {
16400
- worktree: alloc?.path,
16401
- signal: ctl.signal
16402
- });
16658
+ };
16659
+ var RISK_PATTERNS = [
16660
+ {
16661
+ tag: "rm_rf_root",
16662
+ kinds: ["bash"],
16663
+ re: /rm\s+-[rRf]+[^|;`\n]*\s+(\/(?:\s|$|[^/\w*.-])|~|\$HOME(?:\b|\/)|\/(?:bin|etc|usr|var|opt|home|root|boot|lib|sbin)\b)/
16664
+ },
16665
+ {
16666
+ tag: "rm_rf_wildcard",
16667
+ kinds: ["bash"],
16668
+ re: /rm\s+-[rRf]+\s+(?:\.\*|\*)/
16669
+ },
16670
+ {
16671
+ tag: "force_push_protected",
16672
+ kinds: ["bash"],
16673
+ re: /git\s+push\s+(?:--force\b|-f\b)[\s\S]*\b(main|master|release(?:\/[\w.-]+)?)\b/
16674
+ },
16675
+ {
16676
+ tag: "git_reset_hard_main",
16677
+ kinds: ["bash"],
16678
+ re: /git\s+reset\s+--hard\s+(origin\/)?(main|master)/
16679
+ },
16680
+ { tag: "sudo", kinds: ["bash"], re: /(?<![\w-])sudo(?![\w-])/ },
16681
+ { tag: "su_root", kinds: ["bash"], re: /(?<![\w-])su\s+(?:-\s+)?root\b/ },
16682
+ { tag: "mkfs", kinds: ["bash"], re: /\bmkfs(\.[\w]+)?\b/ },
16683
+ { tag: "dd_disk", kinds: ["bash"], re: /\bdd\s+if=[^|;\s]+\s+of=\/dev\// },
16684
+ { tag: "chmod_777", kinds: ["bash"], re: /chmod\s+[-+]?[0-7]?777/ },
16685
+ {
16686
+ tag: "curl_pipe_sh",
16687
+ kinds: ["bash"],
16688
+ re: /\b(?:curl|wget)\b[^|]*\|\s*(?:sh|bash|zsh)\b/
16689
+ },
16690
+ {
16691
+ tag: "drop_database",
16692
+ kinds: ["bash", "other"],
16693
+ re: /\b(DROP\s+(DATABASE|TABLE)|TRUNCATE\s+TABLE|DROP\s+SCHEMA)\b/i
16694
+ },
16695
+ { tag: "write_secrets", re: /(\.env(?:\.\w+)?|id_[edr]sa|\.ssh\/id_|\.pem|\.p12|secret\.json)/i },
16696
+ { tag: "write_etc", re: /(?:^|\s|"|')\/etc\// },
16697
+ { tag: "write_usr", re: /(?:^|\s|"|')\/usr\// },
16698
+ { tag: "write_root_home", re: /(?:^|\s|"|')(\/root|\/home\/root)\// },
16699
+ {
16700
+ tag: "internal_url",
16701
+ kinds: ["webfetch"],
16702
+ re: /https?:\/\/(localhost|127\.0\.0\.1|0\.0\.0\.0|169\.254\.|10\.|192\.168\.|172\.(?:1[6-9]|2\d|3[01])\.)/i
16703
+ },
16704
+ {
16705
+ tag: "kubectl_delete",
16706
+ kinds: ["bash"],
16707
+ re: /kubectl\s+(?:delete|destroy)\s+/
16708
+ },
16709
+ {
16710
+ tag: "terraform_destroy",
16711
+ kinds: ["bash"],
16712
+ re: /terraform\s+destroy/
16713
+ }
16714
+ ];
16715
+ function evaluate2(mode, intent, opts = {}) {
16716
+ const ignore = new Set(opts.ignore_risks ?? []);
16717
+ const allRisks = [...RISK_PATTERNS, ...opts.extra_risks ?? []];
16718
+ const haystack = buildHaystack(intent.args);
16719
+ const hits = allRisks.filter((p) => !ignore.has(p.tag)).filter((p) => !p.kinds || p.kinds.includes(intent.kind)).filter((p) => p.re.test(haystack)).map((p) => p.tag);
16720
+ let effective = mode;
16721
+ let downgraded = false;
16722
+ let reason;
16723
+ if (hits.length > 0 && mode === "full") {
16724
+ effective = "semi";
16725
+ downgraded = true;
16726
+ reason = `检测到风险操作 [${hits.join(", ")}],自动从 full 降级到 semi`;
16727
+ } else if (hits.length > 0 && mode !== "step") {}
16728
+ const policy = mergePolicy(effective, opts.policies);
16729
+ const baseAction = policy[intent.kind] ?? "confirm";
16730
+ const action = hits.length > 0 && effective !== "step" ? "confirm" : baseAction;
16731
+ return {
16732
+ effective_mode: effective,
16733
+ action,
16734
+ downgraded,
16735
+ reason,
16736
+ detected_risks: hits
16737
+ };
16738
+ }
16739
+ function mergePolicy(mode, override) {
16740
+ const base = DEFAULT_POLICIES[mode];
16741
+ if (!override?.[mode])
16742
+ return base;
16743
+ return { ...base, ...override[mode] };
16744
+ }
16745
+ function buildHaystack(args) {
16746
+ if (typeof args === "string")
16747
+ return args;
16748
+ try {
16749
+ return JSON.stringify(args);
16750
+ } catch {
16751
+ return String(args);
16752
+ }
16753
+ }
16754
+ async function ensureWorktree(opts) {
16755
+ const root = path14.resolve(opts.root);
16756
+ const dir = opts.worktrees_dir ?? path14.join(root, ".git", "codeforge-worktrees");
16757
+ await fs11.mkdir(dir, { recursive: true });
16758
+ const wtPath = path14.join(dir, sanitizeBranch(opts.branch));
16759
+ try {
16760
+ const stat = await fs11.stat(wtPath);
16761
+ if (stat.isDirectory()) {
16762
+ return { path: wtPath, branch: opts.branch, created: false };
16763
+ }
16764
+ } catch {}
16765
+ await runGit(root, ["rev-parse", "--is-inside-work-tree"], opts.git_timeout_ms ?? 3000);
16766
+ let branchExists = false;
16767
+ try {
16768
+ await runGit(root, ["rev-parse", "--verify", `refs/heads/${opts.branch}`], opts.git_timeout_ms ?? 3000);
16769
+ branchExists = true;
16770
+ } catch {
16771
+ branchExists = false;
16772
+ }
16773
+ const args = branchExists ? ["worktree", "add", wtPath, opts.branch] : ["worktree", "add", "-b", opts.branch, wtPath, opts.base ?? "HEAD"];
16774
+ await runGit(root, args, opts.git_timeout_ms ?? 1e4);
16775
+ return { path: wtPath, branch: opts.branch, created: true };
16776
+ }
16777
+ async function removeWorktree(opts) {
16778
+ const args = ["worktree", "remove", opts.worktree_path];
16779
+ if (opts.force)
16780
+ args.push("--force");
16781
+ await runGit(opts.root, args, opts.git_timeout_ms ?? 5000);
16782
+ }
16783
+ function sanitizeBranch(name) {
16784
+ return name.replace(/[/\\:*?"<>|]/g, "-");
16785
+ }
16786
+ function runGitAllowFail(cwd, args, timeout) {
16787
+ return new Promise((resolve13, reject) => {
16788
+ execFile2("git", args, { cwd, timeout, windowsHide: true, encoding: "utf8" }, (err, stdout, stderr) => {
16789
+ if (err) {
16790
+ const errCode = err.code;
16791
+ if (typeof errCode === "string") {
16792
+ reject(new Error(`git ${args.join(" ")} 失败: ${stderr || err.message}`));
16793
+ return;
16794
+ }
16795
+ resolve13({
16796
+ code: typeof errCode === "number" ? errCode : 1,
16797
+ stdout: stdout ?? "",
16798
+ stderr: stderr ?? err.message ?? ""
16799
+ });
16800
+ return;
16801
+ }
16802
+ resolve13({ code: 0, stdout, stderr: stderr ?? "" });
16803
+ });
16804
+ });
16805
+ }
16806
+ function runGit(cwd, args, timeout) {
16807
+ return new Promise((resolve13, reject) => {
16808
+ execFile2("git", args, { cwd, timeout, windowsHide: true, encoding: "utf8" }, (err, stdout, stderr) => {
16809
+ if (err) {
16810
+ reject(new Error(`git ${args.join(" ")} 失败: ${stderr || err.message}`));
16811
+ return;
16812
+ }
16813
+ resolve13(stdout);
16814
+ });
16815
+ });
16816
+ }
16817
+ async function tryMerge(opts) {
16818
+ const root = path14.resolve(opts.root);
16819
+ const timeout = opts.git_timeout_ms ?? 1e4;
16820
+ const status = await runGitAllowFail(root, ["status", "--porcelain"], 3000);
16821
+ if (status.code !== 0) {
16822
+ return {
16823
+ ok: false,
16824
+ conflicts: [],
16825
+ hadChanges: false,
16826
+ message: `git status 失败: ${status.stderr.trim() || "unknown"}`
16827
+ };
16828
+ }
16829
+ if (status.stdout.trim().length > 0) {
16830
+ return {
16831
+ ok: false,
16832
+ conflicts: [],
16833
+ hadChanges: false,
16834
+ message: "main repo dirty"
16835
+ };
16836
+ }
16837
+ const diff = await runGitAllowFail(root, ["diff", "--name-only", "HEAD", opts.branch], 3000);
16838
+ if (diff.code !== 0) {
16839
+ return {
16840
+ ok: false,
16841
+ conflicts: [],
16842
+ hadChanges: false,
16843
+ message: `git diff HEAD ${opts.branch} 失败: ${diff.stderr.trim() || "unknown"}`
16844
+ };
16845
+ }
16846
+ const hadChanges = diff.stdout.trim().length > 0;
16847
+ if (!hadChanges) {
16848
+ return { ok: true, conflicts: [], hadChanges: false };
16849
+ }
16850
+ const merge = await runGitAllowFail(root, ["merge", "--no-commit", "--no-ff", opts.branch], timeout);
16851
+ if (merge.code === 0) {
16852
+ return { ok: true, conflicts: [], hadChanges: true };
16853
+ }
16854
+ const conflicts = await getMergeConflicts({ root, git_timeout_ms: 3000 });
16855
+ await runGitAllowFail(root, ["merge", "--abort"], 5000);
16856
+ return {
16857
+ ok: false,
16858
+ conflicts,
16859
+ hadChanges: true,
16860
+ message: merge.stderr.trim() || `merge exit code ${merge.code}`
16861
+ };
16862
+ }
16863
+ async function mergeCommit(opts) {
16864
+ const root = path14.resolve(opts.root);
16865
+ await runGit(root, ["commit", "-m", opts.message], opts.git_timeout_ms ?? 1e4);
16866
+ }
16867
+ async function mergeAbort(opts) {
16868
+ const root = path14.resolve(opts.root);
16869
+ await runGitAllowFail(root, ["merge", "--abort"], opts.git_timeout_ms ?? 5000);
16870
+ }
16871
+ async function getMergeConflicts(opts) {
16872
+ const out = await runGitAllowFail(path14.resolve(opts.root), ["diff", "--name-only", "--diff-filter=U"], opts.git_timeout_ms ?? 3000);
16873
+ if (out.code !== 0)
16874
+ return [];
16875
+ return out.stdout.split(/\r?\n/).map((s) => s.trim()).filter(Boolean);
16876
+ }
16877
+ async function worktreeHasChanges(opts) {
16878
+ const out = await runGitAllowFail(path14.resolve(opts.worktree_path), ["status", "--porcelain"], opts.git_timeout_ms ?? 3000);
16879
+ if (out.code !== 0)
16880
+ return false;
16881
+ return out.stdout.trim().length > 0;
16882
+ }
16883
+ async function commitWorktreeIfDirty(opts) {
16884
+ const wt = path14.resolve(opts.worktree_path);
16885
+ const timeout = opts.git_timeout_ms ?? 1e4;
16886
+ const status = await runGitAllowFail(wt, ["status", "--porcelain"], 3000);
16887
+ if (status.code !== 0) {
16888
+ return { committed: false };
16889
+ }
16890
+ if (status.stdout.trim().length === 0) {
16891
+ return { committed: false };
16892
+ }
16893
+ await runGit(wt, ["add", "-A"], timeout);
16894
+ await runGit(wt, ["commit", "-m", opts.message], timeout);
16895
+ return { committed: true };
16896
+ }
16897
+
16898
+ // lib/parallel-merge.ts
16899
+ function resolveMergeFns(deps) {
16900
+ return {
16901
+ commitWorktreeIfDirtyFn: deps.commitWorktreeIfDirty ?? commitWorktreeIfDirty,
16902
+ tryMergeFn: deps.tryMerge ?? tryMerge,
16903
+ mergeCommitFn: deps.mergeCommit ?? mergeCommit,
16904
+ mergeAbortFn: deps.mergeAbort ?? mergeAbort,
16905
+ removeWorktreeFn: deps.removeWorktree ?? removeWorktree,
16906
+ worktreeHasChangesFn: deps.worktreeHasChanges ?? worktreeHasChanges
16907
+ };
16908
+ }
16909
+ async function mergeOneAttempt(r, opts, mergeFns, log10) {
16910
+ const t0 = Date.now();
16911
+ const root = opts.mergeRoot;
16912
+ const {
16913
+ commitWorktreeIfDirtyFn,
16914
+ tryMergeFn,
16915
+ mergeCommitFn,
16916
+ mergeAbortFn,
16917
+ removeWorktreeFn
16918
+ } = mergeFns;
16919
+ const eligibleStatus = r.status === "success" || r.status === "need_review";
16920
+ if (!eligibleStatus) {
16921
+ return {
16922
+ attempt: {
16923
+ subtaskId: r.id,
16924
+ branch: r.id,
16925
+ worktree: r.worktree ?? null,
16926
+ ok: false,
16927
+ conflicts: [],
16928
+ skippedReason: "subtask_failed",
16929
+ durationMs: 0
16930
+ }
16931
+ };
16932
+ }
16933
+ if (!r.worktree) {
16934
+ return {
16935
+ attempt: {
16936
+ subtaskId: r.id,
16937
+ branch: r.id,
16938
+ worktree: null,
16939
+ ok: false,
16940
+ conflicts: [],
16941
+ skippedReason: "no_worktree",
16942
+ durationMs: 0
16943
+ }
16944
+ };
16945
+ }
16946
+ const wt = r.worktree;
16947
+ const branch = r.id;
16948
+ const attempt = {
16949
+ subtaskId: r.id,
16950
+ branch,
16951
+ worktree: wt,
16952
+ ok: false,
16953
+ conflicts: [],
16954
+ durationMs: 0
16955
+ };
16956
+ try {
16957
+ try {
16958
+ await commitWorktreeIfDirtyFn({
16959
+ worktree_path: wt,
16960
+ message: `parallel: auto-commit ${r.id} before merge`
16961
+ });
16962
+ } catch (commitErr) {
16963
+ attempt.ok = false;
16964
+ attempt.message = `commitWorktreeIfDirty 失败:${describe4(commitErr)}`;
16965
+ attempt.durationMs = Date.now() - t0;
16966
+ return {
16967
+ attempt,
16968
+ pendingItem: { subtaskId: r.id, path: wt, conflicts: [] }
16969
+ };
16970
+ }
16971
+ const merge = await tryMergeFn({ root, branch });
16972
+ if (!merge.ok) {
16973
+ attempt.ok = false;
16974
+ attempt.conflicts = merge.conflicts;
16975
+ attempt.message = merge.message;
16976
+ attempt.durationMs = Date.now() - t0;
16977
+ return {
16978
+ attempt,
16979
+ pendingItem: { subtaskId: r.id, path: wt, conflicts: merge.conflicts }
16980
+ };
16981
+ }
16982
+ if (merge.hadChanges) {
16983
+ try {
16984
+ await mergeCommitFn({ root, message: `parallel: merge ${r.id}` });
16985
+ } catch (commitErr) {
16986
+ let abortFailed = false;
16987
+ try {
16988
+ await mergeAbortFn({ root });
16989
+ } catch (abortErr) {
16990
+ abortFailed = true;
16991
+ log10("error", `[parallel-merge] mergeAbort 失败(仓库可能锁死)`, {
16992
+ id: r.id,
16993
+ error: describe4(abortErr)
16994
+ });
16995
+ }
16996
+ attempt.ok = false;
16997
+ attempt.message = `mergeCommit 失败:${describe4(commitErr)}`;
16998
+ attempt.durationMs = Date.now() - t0;
16999
+ return {
17000
+ attempt,
17001
+ abortFailed,
17002
+ pendingItem: { subtaskId: r.id, path: wt, conflicts: [] }
17003
+ };
17004
+ }
17005
+ attempt.ok = true;
17006
+ attempt.durationMs = Date.now() - t0;
17007
+ await safeRemoveWorktree(removeWorktreeFn, root, wt, log10, r.id);
17008
+ return { attempt };
17009
+ } else {
17010
+ attempt.ok = true;
17011
+ attempt.skippedReason = "no_changes";
17012
+ attempt.durationMs = Date.now() - t0;
17013
+ await safeRemoveWorktree(removeWorktreeFn, root, wt, log10, r.id);
17014
+ return { attempt };
17015
+ }
17016
+ } catch (err) {
17017
+ attempt.ok = false;
17018
+ attempt.message = `mergeWorktrees 异常:${describe4(err)}`;
17019
+ attempt.durationMs = Date.now() - t0;
17020
+ return {
17021
+ attempt,
17022
+ pendingItem: { subtaskId: r.id, path: wt, conflicts: [] }
17023
+ };
17024
+ }
17025
+ }
17026
+ async function mergeWorktrees(results, opts, deps) {
17027
+ const log10 = deps.log ?? (() => {});
17028
+ const root = opts.mergeRoot;
17029
+ const mergeFns = resolveMergeFns(deps);
17030
+ const report = {
17031
+ attempts: [],
17032
+ merged: 0,
17033
+ skipped: 0,
17034
+ conflicted: 0,
17035
+ pendingWorktrees: []
17036
+ };
17037
+ const mergeable = [];
17038
+ const skipped = [];
17039
+ for (const r of results) {
17040
+ const eligibleStatus = r.status === "success" || r.status === "need_review";
17041
+ if (!eligibleStatus) {
17042
+ skipped.push({ res: r, reason: "subtask_failed" });
17043
+ continue;
17044
+ }
17045
+ if (!r.worktree) {
17046
+ skipped.push({ res: r, reason: "no_worktree" });
17047
+ continue;
17048
+ }
17049
+ mergeable.push(r);
17050
+ }
17051
+ mergeable.sort((a, b) => {
17052
+ const la = a.changedFiles?.length ?? 0;
17053
+ const lb = b.changedFiles?.length ?? 0;
17054
+ return la - lb;
17055
+ });
17056
+ let idx = 0;
17057
+ for (const s of skipped) {
17058
+ const attempt = {
17059
+ subtaskId: s.res.id,
17060
+ branch: s.res.id,
17061
+ worktree: s.res.worktree ?? null,
17062
+ ok: false,
17063
+ conflicts: [],
17064
+ skippedReason: s.reason,
17065
+ durationMs: 0
17066
+ };
17067
+ report.attempts.push(attempt);
17068
+ report.skipped++;
17069
+ await fireMergeAttempt(opts.onMergeAttempt, attempt, idx++, log10);
17070
+ }
17071
+ for (const r of mergeable) {
17072
+ const { attempt, pendingItem } = await mergeOneAttempt(r, { mergeRoot: root }, mergeFns, log10);
17073
+ report.attempts.push(attempt);
17074
+ if (attempt.ok && !attempt.skippedReason)
17075
+ report.merged++;
17076
+ else if (attempt.skippedReason === "no_changes")
17077
+ report.skipped++;
17078
+ else
17079
+ report.conflicted++;
17080
+ if (pendingItem)
17081
+ report.pendingWorktrees.push(pendingItem);
17082
+ await fireMergeAttempt(opts.onMergeAttempt, attempt, idx++, log10);
17083
+ }
17084
+ return report;
17085
+ }
17086
+ function createMergeQueue(opts, deps = {}) {
17087
+ const log10 = deps.log ?? (() => {});
17088
+ const mergeFns = {
17089
+ commitWorktreeIfDirtyFn: deps.commitWorktreeIfDirtyFn ?? commitWorktreeIfDirty,
17090
+ tryMergeFn: deps.tryMergeFn ?? tryMerge,
17091
+ mergeCommitFn: deps.mergeCommitFn ?? mergeCommit,
17092
+ mergeAbortFn: deps.mergeAbortFn ?? mergeAbort,
17093
+ removeWorktreeFn: deps.removeWorktreeFn ?? removeWorktree,
17094
+ worktreeHasChangesFn: deps.worktreeHasChangesFn ?? worktreeHasChanges
17095
+ };
17096
+ const report = {
17097
+ attempts: [],
17098
+ merged: 0,
17099
+ skipped: 0,
17100
+ conflicted: 0,
17101
+ pendingWorktrees: []
17102
+ };
17103
+ let tail = Promise.resolve();
17104
+ let idx = 0;
17105
+ let queueAborted = false;
17106
+ return {
17107
+ enqueue(result) {
17108
+ tail = tail.then(async () => {
17109
+ if (queueAborted) {
17110
+ const attempt2 = {
17111
+ subtaskId: result.id,
17112
+ branch: result.id,
17113
+ worktree: result.worktree ?? null,
17114
+ ok: false,
17115
+ conflicts: [],
17116
+ skippedReason: "queue_aborted",
17117
+ durationMs: 0
17118
+ };
17119
+ report.attempts.push(attempt2);
17120
+ report.skipped++;
17121
+ if (result.worktree) {
17122
+ report.pendingWorktrees.push({
17123
+ subtaskId: result.id,
17124
+ path: result.worktree,
17125
+ conflicts: []
17126
+ });
17127
+ }
17128
+ await fireMergeAttempt(opts.onMergeAttempt, attempt2, idx++, log10);
17129
+ return;
17130
+ }
17131
+ const { attempt, abortFailed, pendingItem } = await mergeOneAttempt(result, { mergeRoot: opts.mergeRoot, summaryCharLimit: opts.summaryCharLimit }, mergeFns, log10);
17132
+ report.attempts.push(attempt);
17133
+ if (attempt.ok && !attempt.skippedReason)
17134
+ report.merged++;
17135
+ else if (attempt.skippedReason === "no_changes" || attempt.skippedReason === "subtask_failed" || attempt.skippedReason === "no_worktree" || attempt.skippedReason === "autonomy_off")
17136
+ report.skipped++;
17137
+ else
17138
+ report.conflicted++;
17139
+ if (pendingItem)
17140
+ report.pendingWorktrees.push(pendingItem);
17141
+ if (abortFailed) {
17142
+ queueAborted = true;
17143
+ log10("error", `[parallel-merge] queue aborted (mergeAbort failed)`, {
17144
+ id: result.id
17145
+ });
17146
+ }
17147
+ await fireMergeAttempt(opts.onMergeAttempt, attempt, idx++, log10);
17148
+ });
17149
+ },
17150
+ async flushAndReport() {
17151
+ await tail;
17152
+ return report;
17153
+ }
17154
+ };
17155
+ }
17156
+ async function safeRemoveWorktree(fn, root, wt, log10, subtaskId) {
17157
+ try {
17158
+ await fn({ root, worktree_path: wt, force: true });
17159
+ } catch (err) {
17160
+ log10("warn", `[parallel] removeWorktree 失败 ${subtaskId}`, {
17161
+ error: describe4(err),
17162
+ worktree: wt
17163
+ });
17164
+ }
17165
+ }
17166
+ async function fireMergeAttempt(cb, attempt, idx, log10) {
17167
+ if (!cb)
17168
+ return;
17169
+ try {
17170
+ await cb(attempt, idx);
17171
+ } catch (err) {
17172
+ log10("warn", `[parallel] onMergeAttempt 抛错(已隔离)`, {
17173
+ id: attempt.subtaskId,
17174
+ error: describe4(err)
17175
+ });
17176
+ }
17177
+ }
17178
+ function describe4(err) {
17179
+ if (err instanceof Error)
17180
+ return err.message;
17181
+ if (typeof err === "string")
17182
+ return err;
17183
+ try {
17184
+ return JSON.stringify(err);
17185
+ } catch {
17186
+ return String(err);
17187
+ }
17188
+ }
17189
+
17190
+ // lib/parallel.ts
17191
+ async function schedule(opts) {
17192
+ validateSpecs(opts.subtasks);
17193
+ if (opts.autoMerge && !opts.mergeRoot) {
17194
+ throw new Error("mergeRoot is required when autoMerge=true");
17195
+ }
17196
+ const now = opts.deps.now ?? Date.now;
17197
+ const start = now();
17198
+ const log10 = opts.deps.log ?? (() => {});
17199
+ const concurrency = Math.max(1, opts.maxConcurrency ?? 4);
17200
+ const totalTimeout = opts.totalTimeout_ms ?? 30 * 60000;
17201
+ const limit = Math.max(1, opts.summaryCharLimit ?? 500);
17202
+ const globalCtl = new AbortController;
17203
+ const onParentAbort = () => globalCtl.abort();
17204
+ if (opts.signal) {
17205
+ if (opts.signal.aborted)
17206
+ globalCtl.abort();
17207
+ else
17208
+ opts.signal.addEventListener("abort", onParentAbort, { once: true });
17209
+ }
17210
+ const totalTimer = setTimeout(() => {
17211
+ if (!globalCtl.signal.aborted)
17212
+ globalCtl.abort();
17213
+ }, totalTimeout);
17214
+ const results = new Array(opts.subtasks.length);
17215
+ let nextIdx = 0;
17216
+ const workers = [];
17217
+ const pipelineEnabled = Boolean(opts.autoMerge && opts.pipelineMerge);
17218
+ let queue;
17219
+ if (pipelineEnabled) {
17220
+ queue = createMergeQueue({
17221
+ mergeRoot: opts.mergeRoot,
17222
+ summaryCharLimit: opts.summaryCharLimit,
17223
+ onMergeAttempt: opts.onMergeAttempt
17224
+ }, {
17225
+ commitWorktreeIfDirtyFn: opts.deps.commitWorktreeIfDirty,
17226
+ tryMergeFn: opts.deps.tryMerge,
17227
+ mergeCommitFn: opts.deps.mergeCommit,
17228
+ mergeAbortFn: opts.deps.mergeAbort,
17229
+ removeWorktreeFn: opts.deps.removeWorktree,
17230
+ worktreeHasChangesFn: opts.deps.worktreeHasChanges,
17231
+ log: log10
17232
+ });
17233
+ }
17234
+ const fireFinish = async (i, res) => {
17235
+ if (queue) {
17236
+ try {
17237
+ queue.enqueue(res);
17238
+ } catch (err) {
17239
+ log10("warn", `[parallel] queue.enqueue 抛错(已隔离)`, {
17240
+ id: res.id,
17241
+ error: describe5(err)
17242
+ });
17243
+ }
17244
+ }
17245
+ if (!opts.onSubtaskFinish)
17246
+ return;
17247
+ try {
17248
+ await opts.onSubtaskFinish(res, i);
17249
+ } catch (err) {
17250
+ log10("warn", `[parallel] onSubtaskFinish 抛错(已隔离)`, {
17251
+ id: res.id,
17252
+ error: describe5(err)
17253
+ });
17254
+ }
17255
+ };
17256
+ const runOne = async (i, spec) => {
17257
+ const subStart = now();
17258
+ let alloc;
17259
+ if (opts.deps.allocateWorktree) {
17260
+ try {
17261
+ alloc = await opts.deps.allocateWorktree(spec.id);
17262
+ } catch (err) {
17263
+ const res2 = {
17264
+ id: spec.id,
17265
+ ok: false,
17266
+ summary: clamp(`worktree 分配失败:${describe5(err)}`, limit),
17267
+ status: "failed",
17268
+ duration_ms: now() - subStart,
17269
+ error: describe5(err)
17270
+ };
17271
+ results[i] = res2;
17272
+ await fireFinish(i, res2);
17273
+ return;
17274
+ }
17275
+ }
17276
+ if (opts.onSubtaskStart) {
17277
+ try {
17278
+ await opts.onSubtaskStart(spec, i);
17279
+ } catch (err) {
17280
+ log10("warn", `[parallel] onSubtaskStart 抛错(已隔离)`, {
17281
+ id: spec.id,
17282
+ error: describe5(err)
17283
+ });
17284
+ }
17285
+ }
17286
+ const ctl = new AbortController;
17287
+ const cascade = () => ctl.abort();
17288
+ if (globalCtl.signal.aborted)
17289
+ ctl.abort();
17290
+ else
17291
+ globalCtl.signal.addEventListener("abort", cascade, { once: true });
17292
+ const perTimer = setTimeout(() => {
17293
+ if (!ctl.signal.aborted)
17294
+ ctl.abort();
17295
+ }, Math.max(1, spec.timeout_ms ?? 5 * 60000));
17296
+ let res;
17297
+ try {
17298
+ const r = await opts.deps.runSubtask(spec, {
17299
+ worktree: alloc?.path,
17300
+ signal: ctl.signal
17301
+ });
16403
17302
  const status = pickStatus(r, ctl.signal.aborted, globalCtl.signal.aborted);
16404
17303
  res = {
16405
17304
  id: spec.id,
@@ -16416,21 +17315,21 @@ async function schedule(opts) {
16416
17315
  res = {
16417
17316
  id: spec.id,
16418
17317
  ok: false,
16419
- summary: clamp(`runSubtask 抛错:${describe4(err)}`, limit),
17318
+ summary: clamp(`runSubtask 抛错:${describe5(err)}`, limit),
16420
17319
  status: isAborted ? globalCtl.signal.aborted ? "cancelled" : "timeout" : "failed",
16421
17320
  duration_ms: now() - subStart,
16422
17321
  worktree: alloc?.path,
16423
- error: describe4(err)
17322
+ error: describe5(err)
16424
17323
  };
16425
17324
  } finally {
16426
17325
  clearTimeout(perTimer);
16427
17326
  globalCtl.signal.removeEventListener("abort", cascade);
16428
- if (alloc) {
17327
+ if (alloc && !opts.autoMerge) {
16429
17328
  try {
16430
17329
  await alloc.cleanup();
16431
17330
  } catch (err) {
16432
- log9("warn", `[parallel] worktree 清理失败 ${spec.id}`, {
16433
- error: describe4(err)
17331
+ log10("warn", `[parallel] worktree 清理失败 ${spec.id}`, {
17332
+ error: describe5(err)
16434
17333
  });
16435
17334
  }
16436
17335
  }
@@ -16468,12 +17367,21 @@ async function schedule(opts) {
16468
17367
  opts.signal.removeEventListener("abort", onParentAbort);
16469
17368
  const conflicts = detectConflicts(results);
16470
17369
  const digest = buildDigest(results, conflicts);
17370
+ let mergeReport;
17371
+ if (opts.autoMerge) {
17372
+ if (queue) {
17373
+ mergeReport = await queue.flushAndReport();
17374
+ } else {
17375
+ mergeReport = await mergeWorktrees(results, opts, opts.deps);
17376
+ }
17377
+ }
16471
17378
  return {
16472
17379
  parentId: opts.parentId,
16473
17380
  results,
16474
17381
  conflicts,
16475
17382
  digest,
16476
- duration_ms: now() - start
17383
+ duration_ms: now() - start,
17384
+ mergeReport
16477
17385
  };
16478
17386
  }
16479
17387
  var SUBTASK_ID_RE = /^[a-zA-Z0-9._-]+$/;
@@ -16545,7 +17453,7 @@ function buildDigest(results, conflicts) {
16545
17453
  conflicts
16546
17454
  };
16547
17455
  }
16548
- function describe4(err) {
17456
+ function describe5(err) {
16549
17457
  if (err instanceof Error)
16550
17458
  return err.message;
16551
17459
  if (typeof err === "string")
@@ -16580,7 +17488,7 @@ function pickStatus(r, taskAborted, globalAborted) {
16580
17488
  // lib/opencode-runner.ts
16581
17489
  init_opencode_plugin_helpers();
16582
17490
  function makeOpencodeRunner(opts) {
16583
- const log9 = opts.log ?? (() => {});
17491
+ const log10 = opts.log ?? (() => {});
16584
17492
  return async (spec, runCtx) => {
16585
17493
  const startedAt = Date.now();
16586
17494
  let childSessionId;
@@ -16595,13 +17503,13 @@ function makeOpencodeRunner(opts) {
16595
17503
  if (created.error || !created.data?.id) {
16596
17504
  return {
16597
17505
  ok: false,
16598
- summary: `session.create 失败:${describe5(created.error) || "no id"}`,
17506
+ summary: `session.create 失败:${describe6(created.error) || "no id"}`,
16599
17507
  status: "failed",
16600
- error: describe5(created.error) || "session.create 无 id"
17508
+ error: describe6(created.error) || "session.create 无 id"
16601
17509
  };
16602
17510
  }
16603
17511
  childSessionId = created.data.id;
16604
- log9("info", `[opencode-runner] subtask=${spec.id} session=${childSessionId} created`);
17512
+ log10("info", `[opencode-runner] subtask=${spec.id} session=${childSessionId} created`);
16605
17513
  const promptText = composePromptText(spec);
16606
17514
  const promptPromise = opts.client.session.prompt({
16607
17515
  path: { id: childSessionId },
@@ -16631,9 +17539,9 @@ function makeOpencodeRunner(opts) {
16631
17539
  if (r.error || !r.data) {
16632
17540
  return {
16633
17541
  ok: false,
16634
- summary: `session.prompt 失败:${describe5(r.error)}`,
17542
+ summary: `session.prompt 失败:${describe6(r.error)}`,
16635
17543
  status: "failed",
16636
- error: describe5(r.error) || "no data"
17544
+ error: describe6(r.error) || "no data"
16637
17545
  };
16638
17546
  }
16639
17547
  const lastText = pickLastText(r.data.parts ?? []);
@@ -16654,7 +17562,7 @@ function makeOpencodeRunner(opts) {
16654
17562
  });
16655
17563
  changedFiles = pickDiffFiles(diff.data);
16656
17564
  } catch (err) {
16657
- log9("warn", `[opencode-runner] diff 取失败 ${spec.id}`, { error: describe5(err) });
17565
+ log10("warn", `[opencode-runner] diff 取失败 ${spec.id}`, { error: describe6(err) });
16658
17566
  }
16659
17567
  const elapsed = Date.now() - startedAt;
16660
17568
  const summary = lastText || `subtask ${spec.id} 完成(${elapsed}ms,无文本输出,finish=${finishReason || "unknown"})`;
@@ -16663,14 +17571,14 @@ function makeOpencodeRunner(opts) {
16663
17571
  summary,
16664
17572
  status,
16665
17573
  changedFiles,
16666
- error: llmError ? describe5(llmError) : undefined
17574
+ error: llmError ? describe6(llmError) : undefined
16667
17575
  };
16668
17576
  } catch (err) {
16669
17577
  return {
16670
17578
  ok: false,
16671
- summary: `runner 抛错:${describe5(err)}`,
17579
+ summary: `runner 抛错:${describe6(err)}`,
16672
17580
  status: "failed",
16673
- error: describe5(err)
17581
+ error: describe6(err)
16674
17582
  };
16675
17583
  } finally {
16676
17584
  if (childSessionId) {
@@ -16680,8 +17588,8 @@ function makeOpencodeRunner(opts) {
16680
17588
  query: opts.directory ? { directory: opts.directory } : undefined
16681
17589
  });
16682
17590
  } catch (err) {
16683
- log9("warn", `[opencode-runner] session.delete 失败 ${childSessionId}`, {
16684
- error: describe5(err)
17591
+ log10("warn", `[opencode-runner] session.delete 失败 ${childSessionId}`, {
17592
+ error: describe6(err)
16685
17593
  });
16686
17594
  }
16687
17595
  }
@@ -16735,20 +17643,20 @@ async function withTimeout2(p, ms, signal) {
16735
17643
  throw err;
16736
17644
  }
16737
17645
  }
16738
- return await new Promise((resolve11, reject) => {
17646
+ return await new Promise((resolve13, reject) => {
16739
17647
  let settled = false;
16740
17648
  const timer = setTimeout(() => {
16741
17649
  if (settled)
16742
17650
  return;
16743
17651
  settled = true;
16744
- resolve11({ kind: "timeout" });
17652
+ resolve13({ kind: "timeout" });
16745
17653
  }, ms);
16746
17654
  const onAbort = () => {
16747
17655
  if (settled)
16748
17656
  return;
16749
17657
  settled = true;
16750
17658
  clearTimeout(timer);
16751
- resolve11({ kind: "aborted" });
17659
+ resolve13({ kind: "aborted" });
16752
17660
  };
16753
17661
  signal?.addEventListener("abort", onAbort, { once: true });
16754
17662
  p.then((value) => {
@@ -16757,7 +17665,7 @@ async function withTimeout2(p, ms, signal) {
16757
17665
  settled = true;
16758
17666
  clearTimeout(timer);
16759
17667
  signal?.removeEventListener("abort", onAbort);
16760
- resolve11({ kind: "ok", value });
17668
+ resolve13({ kind: "ok", value });
16761
17669
  }, (err) => {
16762
17670
  if (settled)
16763
17671
  return;
@@ -16768,7 +17676,7 @@ async function withTimeout2(p, ms, signal) {
16768
17676
  });
16769
17677
  });
16770
17678
  }
16771
- function describe5(err) {
17679
+ function describe6(err) {
16772
17680
  if (!err)
16773
17681
  return "";
16774
17682
  if (err instanceof Error)
@@ -16794,14 +17702,14 @@ function clip4(s, max) {
16794
17702
  return s.length <= max ? s : s.slice(0, max - 1) + "…";
16795
17703
  }
16796
17704
  async function sendParentNotice(client, sessionID, text, opts = {}) {
16797
- const log9 = opts.log ?? (() => {});
17705
+ const log10 = opts.log ?? (() => {});
16798
17706
  if (!client?.session) {
16799
- log9("warn", "[sendParentNotice] client.session 不可用,noop");
17707
+ log10("warn", "[sendParentNotice] client.session 不可用,noop");
16800
17708
  return false;
16801
17709
  }
16802
17710
  const sessionAny = client.session;
16803
17711
  if (typeof sessionAny.promptAsync !== "function") {
16804
- log9("warn", "[sendParentNotice] promptAsync 不可用(SDK 太老?),noop");
17712
+ log10("warn", "[sendParentNotice] promptAsync 不可用(SDK 太老?),noop");
16805
17713
  return false;
16806
17714
  }
16807
17715
  try {
@@ -16822,12 +17730,12 @@ async function sendParentNotice(client, sessionID, text, opts = {}) {
16822
17730
  }
16823
17731
  });
16824
17732
  if (res && typeof res === "object" && "error" in res && res.error) {
16825
- log9("warn", "[sendParentNotice] promptAsync 返回 error", { error: res.error });
17733
+ log10("warn", "[sendParentNotice] promptAsync 返回 error", { error: res.error });
16826
17734
  return false;
16827
17735
  }
16828
17736
  return true;
16829
17737
  } catch (err) {
16830
- log9("warn", "[sendParentNotice] 抛错(已隔离)", {
17738
+ log10("warn", "[sendParentNotice] 抛错(已隔离)", {
16831
17739
  error: err instanceof Error ? err.message : String(err)
16832
17740
  });
16833
17741
  return false;
@@ -16835,11 +17743,238 @@ async function sendParentNotice(client, sessionID, text, opts = {}) {
16835
17743
  }
16836
17744
 
16837
17745
  // plugins/subtasks.ts
16838
- init_opencode_plugin_helpers();
17746
+ init_opencode_plugin_helpers();
17747
+
17748
+ // lib/decompose.ts
17749
+ var DEFAULT_MAX_SUBTASKS = 8;
17750
+ var DEFAULT_TIMEOUT_MS2 = 30000;
17751
+ function buildSystemPrompt(maxSubtasks) {
17752
+ return [
17753
+ `你是任务拆分助手。把用户描述拆成 2 到 ${maxSubtasks} 个【可并行执行】的独立子任务。`,
17754
+ `硬约束:`,
17755
+ `(a) 子任务之间不互相依赖(不能"先 A 再 B"的串行关系)`,
17756
+ `(b) 不同子任务尽量改不同文件(hintFiles 填你预测会改的文件路径,用于冲突预检)`,
17757
+ `(c) 每个子任务自包含(单独给一个 agent 执行即可完成)`,
17758
+ `(d) 如果任务不能拆(如"先实现再测试"这种强依赖链),返回 {"single_task": true, "reason": "..."}`,
17759
+ ``,
17760
+ `输出格式(必须是合法 JSON,不要加 markdown 代码围栏之外的内容):`,
17761
+ `{"subtasks": [{"description": "...", "hintFiles": ["path/to/file.ts"]}]}`,
17762
+ `或`,
17763
+ `{"single_task": true, "reason": "无法并行,因为..."}`
17764
+ ].join(`
17765
+ `);
17766
+ }
17767
+ async function decomposeTask(description26, opts) {
17768
+ const log10 = opts.log ?? (() => {});
17769
+ if (opts.mockResponse) {
17770
+ return validateAndFinalize(opts.mockResponse, undefined, log10, opts.maxSubtasks ?? DEFAULT_MAX_SUBTASKS);
17771
+ }
17772
+ const maxSubtasks = opts.maxSubtasks ?? DEFAULT_MAX_SUBTASKS;
17773
+ const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS2;
17774
+ let childSessionId;
17775
+ try {
17776
+ const created = await opts.client.session.create({
17777
+ body: { title: `decompose:${clip5(description26, 60)}` },
17778
+ query: opts.directory ? { directory: opts.directory } : undefined
17779
+ });
17780
+ if (created.error || !created.data?.id) {
17781
+ log10("warn", "[decompose] session.create 失败", { error: describe7(created.error) });
17782
+ return {
17783
+ ok: false,
17784
+ subtasks: [],
17785
+ reason: "llm_unavailable"
17786
+ };
17787
+ }
17788
+ childSessionId = created.data.id;
17789
+ const systemText = buildSystemPrompt(maxSubtasks);
17790
+ const promptPromise = Promise.resolve(opts.client.session.prompt({
17791
+ path: { id: childSessionId },
17792
+ body: {
17793
+ system: systemText,
17794
+ parts: [{ type: "text", text: description26 }]
17795
+ },
17796
+ query: opts.directory ? { directory: opts.directory } : undefined
17797
+ }));
17798
+ const raced = await Promise.race([
17799
+ promptPromise.then((v) => ({ kind: "ok", value: v })),
17800
+ sleep(timeoutMs).then(() => ({ kind: "timeout" }))
17801
+ ]);
17802
+ if (raced.kind === "timeout") {
17803
+ log10("warn", "[decompose] LLM 调用超时", { timeoutMs });
17804
+ return { ok: false, subtasks: [], reason: "llm_unavailable" };
17805
+ }
17806
+ const r = raced.value;
17807
+ if (r.error || !r.data) {
17808
+ log10("warn", "[decompose] session.prompt 返回错误", { error: describe7(r.error) });
17809
+ return { ok: false, subtasks: [], reason: "llm_unavailable" };
17810
+ }
17811
+ const rawText = pickLastText2(r.data.parts ?? []);
17812
+ if (!rawText) {
17813
+ log10("warn", "[decompose] LLM 输出为空");
17814
+ return { ok: false, subtasks: [], reason: "parse_failed", raw: "" };
17815
+ }
17816
+ const parsed = extractJson(rawText);
17817
+ if (!parsed) {
17818
+ log10("warn", "[decompose] JSON 解析失败", { raw: clip5(rawText, 200) });
17819
+ return { ok: false, subtasks: [], reason: "parse_failed", raw: rawText };
17820
+ }
17821
+ if (parsed && typeof parsed === "object" && parsed.single_task === true) {
17822
+ return { ok: false, subtasks: [], reason: "single_task", raw: rawText };
17823
+ }
17824
+ const subtasksRaw = parsed.subtasks;
17825
+ if (!Array.isArray(subtasksRaw)) {
17826
+ log10("warn", "[decompose] JSON 缺 subtasks 数组");
17827
+ return { ok: false, subtasks: [], reason: "parse_failed", raw: rawText };
17828
+ }
17829
+ const normalized = [];
17830
+ for (const item of subtasksRaw) {
17831
+ if (!item || typeof item !== "object")
17832
+ continue;
17833
+ const o = item;
17834
+ const desc = typeof o.description === "string" ? o.description.trim() : "";
17835
+ if (!desc) {
17836
+ return { ok: false, subtasks: [], reason: "parse_failed", raw: rawText };
17837
+ }
17838
+ const hintFiles = Array.isArray(o.hintFiles) ? o.hintFiles.filter((x) => typeof x === "string" && x.trim().length > 0) : undefined;
17839
+ normalized.push({ description: desc, hintFiles });
17840
+ }
17841
+ if (normalized.length > maxSubtasks) {
17842
+ log10("warn", "[decompose] LLM 返回子任务超过上限", { count: normalized.length, maxSubtasks });
17843
+ return { ok: false, subtasks: [], reason: "parse_failed", raw: rawText };
17844
+ }
17845
+ return validateAndFinalize(normalized, rawText, log10, maxSubtasks);
17846
+ } catch (err) {
17847
+ log10("warn", "[decompose] 抛错", { error: describe7(err) });
17848
+ return { ok: false, subtasks: [], reason: "llm_unavailable" };
17849
+ } finally {
17850
+ if (childSessionId) {
17851
+ try {
17852
+ await opts.client.session.delete({
17853
+ path: { id: childSessionId },
17854
+ query: opts.directory ? { directory: opts.directory } : undefined
17855
+ });
17856
+ } catch (err) {
17857
+ log10("warn", "[decompose] session.delete 失败", { error: describe7(err) });
17858
+ }
17859
+ }
17860
+ }
17861
+ }
17862
+ function validateAndFinalize(subtasks, raw, log10, maxSubtasks) {
17863
+ if (subtasks.length < 2) {
17864
+ return { ok: false, subtasks: [], reason: "single_task", raw };
17865
+ }
17866
+ for (const t of subtasks) {
17867
+ if (!t.description || !t.description.trim()) {
17868
+ return { ok: false, subtasks: [], reason: "parse_failed", raw };
17869
+ }
17870
+ }
17871
+ if (subtasks.length > maxSubtasks) {
17872
+ log10("warn", "[decompose] LLM 返回子任务超过上限", { count: subtasks.length, maxSubtasks });
17873
+ return { ok: false, subtasks: [], reason: "parse_failed", raw };
17874
+ }
17875
+ for (let i = 0;i < subtasks.length; i++) {
17876
+ const a = subtasks[i].hintFiles;
17877
+ if (!a || a.length === 0)
17878
+ continue;
17879
+ const setA = new Set(a.map((p) => p.trim()).filter(Boolean));
17880
+ for (let j = i + 1;j < subtasks.length; j++) {
17881
+ const b = subtasks[j].hintFiles;
17882
+ if (!b || b.length === 0)
17883
+ continue;
17884
+ for (const f of b) {
17885
+ if (setA.has(f.trim())) {
17886
+ log10("info", "[decompose] hintFiles 有交集,降级 single_task", {
17887
+ a: subtasks[i].description,
17888
+ b: subtasks[j].description,
17889
+ file: f
17890
+ });
17891
+ return { ok: false, subtasks: [], reason: "single_task", raw };
17892
+ }
17893
+ }
17894
+ }
17895
+ }
17896
+ return { ok: true, subtasks, reason: "ok", raw: undefined };
17897
+ }
17898
+ function pickLastText2(parts) {
17899
+ for (let i = parts.length - 1;i >= 0; i--) {
17900
+ const p = parts[i];
17901
+ if (p && p.type === "text" && typeof p.text === "string" && !p.synthetic && !p.ignored) {
17902
+ return p.text.trim();
17903
+ }
17904
+ }
17905
+ return "";
17906
+ }
17907
+ function extractJson(text) {
17908
+ if (!text)
17909
+ return null;
17910
+ const fenceMatch = text.match(/```(?:json)?\s*\n?([\s\S]*?)\n?```/i);
17911
+ if (fenceMatch && fenceMatch[1]) {
17912
+ const inner = fenceMatch[1].trim();
17913
+ const parsed = tryParse(inner);
17914
+ if (parsed !== null)
17915
+ return parsed;
17916
+ }
17917
+ const direct = tryParse(text.trim());
17918
+ if (direct !== null)
17919
+ return direct;
17920
+ for (let end = text.length - 1;end >= 0; end--) {
17921
+ if (text[end] !== "}")
17922
+ continue;
17923
+ let depth = 0;
17924
+ for (let start = end;start >= 0; start--) {
17925
+ const ch = text[start];
17926
+ if (ch === "}")
17927
+ depth++;
17928
+ else if (ch === "{") {
17929
+ depth--;
17930
+ if (depth === 0) {
17931
+ const slice = text.slice(start, end + 1);
17932
+ const parsed = tryParse(slice);
17933
+ if (parsed !== null)
17934
+ return parsed;
17935
+ break;
17936
+ }
17937
+ }
17938
+ }
17939
+ }
17940
+ return null;
17941
+ }
17942
+ function tryParse(s) {
17943
+ if (!s)
17944
+ return null;
17945
+ try {
17946
+ return JSON.parse(s);
17947
+ } catch {
17948
+ return null;
17949
+ }
17950
+ }
17951
+ function clip5(s, n) {
17952
+ if (!s)
17953
+ return "";
17954
+ return s.length <= n ? s : s.slice(0, n - 1) + "…";
17955
+ }
17956
+ function describe7(err) {
17957
+ if (!err)
17958
+ return "";
17959
+ if (err instanceof Error)
17960
+ return err.message;
17961
+ if (typeof err === "string")
17962
+ return err;
17963
+ try {
17964
+ return JSON.stringify(err);
17965
+ } catch {
17966
+ return String(err);
17967
+ }
17968
+ }
17969
+ function sleep(ms) {
17970
+ return new Promise((resolve13) => setTimeout(resolve13, ms));
17971
+ }
17972
+
17973
+ // plugins/subtasks.ts
16839
17974
  init_runtime_paths();
16840
- var PLUGIN_NAME17 = "subtasks";
17975
+ var PLUGIN_NAME18 = "subtasks";
16841
17976
  function getLogFile(root = process.cwd()) {
16842
- return path13.join(runtimeDir(root), "logs", "subtasks.log");
17977
+ return path15.join(runtimeDir(root), "logs", "subtasks.log");
16843
17978
  }
16844
17979
  var VERB_RE = /^([a-zA-Z]{3,12})/;
16845
17980
  var CN_VERBS = [
@@ -16903,9 +18038,10 @@ var mockRunner = async (spec) => {
16903
18038
  };
16904
18039
  async function handleParallelCommand(raw) {
16905
18040
  const ctx = raw ?? {};
16906
- const log9 = ctx.log;
18041
+ const log10 = ctx.log;
16907
18042
  const args = ctx.args ?? {};
16908
18043
  const parentId = (args.parentId ?? `task-${Date.now().toString(36)}`).replace(/[^a-zA-Z0-9._-]/g, "-");
18044
+ const rawDescription = typeof args.description === "string" ? args.description.trim() : "";
16909
18045
  let specs = [];
16910
18046
  if (Array.isArray(args.subtasks) && args.subtasks.length > 0) {
16911
18047
  specs = args.subtasks.map((s, i) => ({
@@ -16916,16 +18052,80 @@ async function handleParallelCommand(raw) {
16916
18052
  specs = splitDescriptions(args.description, { parentId });
16917
18053
  }
16918
18054
  if (specs.length === 0) {
16919
- log9?.warn(`[${PLUGIN_NAME17}] /parallel 缺有效子任务`);
18055
+ log10?.warn(`[${PLUGIN_NAME18}] /parallel 缺有效子任务`);
16920
18056
  await safeReply2(ctx, "⚠ /parallel 需要至少 1 个子任务(用 ; 或换行分隔)");
16921
18057
  return { ok: false, reason: "no_subtasks" };
16922
18058
  }
16923
18059
  const maxConcurrency = clampInt(args.maxConcurrency, 1, 16, 4);
16924
18060
  const totalTimeoutMs = clampInt(args.totalTimeout_ms, 1000, 60 * 60000, 30 * 60000);
18061
+ let autoMerge = ctx.autoMerge;
18062
+ if (autoMerge === undefined) {
18063
+ if (ctx.allocateWorktree !== undefined) {
18064
+ autoMerge = false;
18065
+ } else {
18066
+ try {
18067
+ const rt = await loadRuntime({ root: ctx.directory });
18068
+ autoMerge = rt.runtime.runtime.worktree_isolation === true;
18069
+ } catch {
18070
+ autoMerge = false;
18071
+ }
18072
+ }
18073
+ }
18074
+ let downgradedAutoMerge = false;
18075
+ const mergeRoot = autoMerge ? ctx.directory ?? process.cwd() : undefined;
18076
+ if (autoMerge && mergeRoot) {
18077
+ const gitOk = await isGitRepo({ root: mergeRoot }).catch(() => false);
18078
+ if (!gitOk) {
18079
+ autoMerge = false;
18080
+ downgradedAutoMerge = true;
18081
+ log10?.warn("[subtasks] mergeRoot 非 git 仓库,降级 autoMerge=false");
18082
+ const canNoticeEarly = Boolean(ctx.client && ctx.parentSessionID);
18083
+ if (canNoticeEarly) {
18084
+ await sendParentNotice(ctx.client, ctx.parentSessionID, "ℹ 当前目录不是 git 仓库,worktree 隔离已自动关闭,以单目录模式运行", { directory: ctx.directory });
18085
+ }
18086
+ }
18087
+ }
16925
18088
  const canNotice = Boolean(ctx.client && ctx.parentSessionID);
18089
+ if (specs.length === 1 && rawDescription.length >= 30 && ctx.client) {
18090
+ log10?.info(`[${PLUGIN_NAME18}] 触发 AI 拆分(描述长度=${rawDescription.length})`);
18091
+ const dec = await decomposeTask(rawDescription, {
18092
+ client: ctx.client,
18093
+ directory: ctx.directory,
18094
+ log: (lvl, msg, data) => {
18095
+ if (lvl === "error")
18096
+ log10?.error(msg, data);
18097
+ else if (lvl === "warn")
18098
+ log10?.warn(msg, data);
18099
+ else
18100
+ log10?.info(msg, data);
18101
+ },
18102
+ mockResponse: ctx.decomposeMockResponse
18103
+ });
18104
+ if (dec.ok && dec.subtasks.length >= 2) {
18105
+ specs = dec.subtasks.map((d, i) => ({
18106
+ id: `${parentId}-${i + 1}-${pickVerb(d.description, "task")}`,
18107
+ description: d.description,
18108
+ timeout_ms: undefined,
18109
+ args: d.hintFiles && d.hintFiles.length > 0 ? { hintFiles: d.hintFiles } : undefined
18110
+ }));
18111
+ log10?.info(`[${PLUGIN_NAME18}] AI 拆分成功 → ${specs.length} 个子任务`);
18112
+ if (canNotice) {
18113
+ const lines = [
18114
+ `\uD83E\uDD16 AI 已自动拆为 ${specs.length} 个并行子任务:`,
18115
+ ...specs.map((s, i) => ` ${i + 1}. ${s.description}`)
18116
+ ];
18117
+ await sendParentNotice(ctx.client, ctx.parentSessionID, lines.join(`
18118
+ `), {
18119
+ directory: ctx.directory
18120
+ });
18121
+ }
18122
+ } else if (dec.reason === "llm_unavailable" || dec.reason === "parse_failed") {
18123
+ log10?.warn(`[${PLUGIN_NAME18}] AI 拆分失败(${dec.reason}),按单任务执行`);
18124
+ }
18125
+ }
16926
18126
  if (canNotice) {
16927
18127
  const lines = [
16928
- `\uD83D\uDE80 /parallel 已派出 ${specs.length} 个子任务(并发=${maxConcurrency}):`,
18128
+ `\uD83D\uDE80 /parallel 已派出 ${specs.length} 个子任务(并发=${maxConcurrency}, worktree 隔离=${autoMerge ? "ON" : "OFF"}):`,
16929
18129
  ...specs.map((s, i) => ` ${i + 1}. \`${s.id}\` — ${s.description}`),
16930
18130
  "",
16931
18131
  "\uD83D\uDCA1 用 /parallel-status 随时查进度;TUI 按 Ctrl+→ 进子 session 看实时。"
@@ -16935,11 +18135,11 @@ async function handleParallelCommand(raw) {
16935
18135
  directory: ctx.directory,
16936
18136
  log: (lvl, msg, data) => {
16937
18137
  if (lvl === "error")
16938
- log9?.error(msg, data);
18138
+ log10?.error(msg, data);
16939
18139
  else if (lvl === "warn")
16940
- log9?.warn(msg, data);
18140
+ log10?.warn(msg, data);
16941
18141
  else
16942
- log9?.info(msg, data);
18142
+ log10?.info(msg, data);
16943
18143
  }
16944
18144
  });
16945
18145
  }
@@ -16951,6 +18151,9 @@ async function handleParallelCommand(raw) {
16951
18151
  subtasks: specs,
16952
18152
  maxConcurrency,
16953
18153
  totalTimeout_ms: totalTimeoutMs,
18154
+ autoMerge,
18155
+ mergeRoot: autoMerge ? ctx.directory ?? process.cwd() : undefined,
18156
+ pipelineMerge: true,
16954
18157
  onSubtaskStart: canNotice ? async (spec, idx) => {
16955
18158
  await sendParentNotice(ctx.client, ctx.parentSessionID, `▶ 子任务 ${idx + 1}/${specs.length} 启动: \`${spec.id}\` — ${spec.description}`, { directory: ctx.directory });
16956
18159
  } : undefined,
@@ -16959,40 +18162,75 @@ async function handleParallelCommand(raw) {
16959
18162
  const fileCount = res.changedFiles?.length ?? 0;
16960
18163
  await sendParentNotice(ctx.client, ctx.parentSessionID, `${emoji} 子任务 ${idx + 1}/${specs.length} 完成 [${res.status}] \`${res.id}\` (${res.duration_ms}ms, files=${fileCount})`, { directory: ctx.directory });
16961
18164
  } : undefined,
18165
+ onMergeAttempt: canNotice ? async (attempt) => {
18166
+ if (attempt.skippedReason) {
18167
+ if (attempt.skippedReason === "no_changes") {
18168
+ await sendParentNotice(ctx.client, ctx.parentSessionID, `⏭ merge \`${attempt.subtaskId}\`: 无改动,跳过`, { directory: ctx.directory });
18169
+ } else if (attempt.skippedReason === "queue_aborted") {
18170
+ await sendParentNotice(ctx.client, ctx.parentSessionID, `\uD83D\uDED1 merge \`${attempt.subtaskId}\`: 队列已锁死,跳过(worktree 保留)`, { directory: ctx.directory });
18171
+ }
18172
+ return;
18173
+ }
18174
+ if (attempt.ok) {
18175
+ await sendParentNotice(ctx.client, ctx.parentSessionID, `\uD83D\uDD00 merge \`${attempt.subtaskId}\`: ✅ 成功`, { directory: ctx.directory });
18176
+ } else {
18177
+ const n = attempt.conflicts.length;
18178
+ await sendParentNotice(ctx.client, ctx.parentSessionID, `\uD83D\uDD00 merge \`${attempt.subtaskId}\`: ⚠ 冲突 ${n} 个文件`, { directory: ctx.directory });
18179
+ }
18180
+ } : undefined,
16962
18181
  deps: {
16963
18182
  runSubtask: runner,
16964
- allocateWorktree: ctx.allocateWorktree,
18183
+ allocateWorktree: downgradedAutoMerge ? undefined : ctx.allocateWorktree,
16965
18184
  log: (lvl, msg, data) => {
16966
18185
  if (lvl === "error")
16967
- log9?.error(msg, data);
18186
+ log10?.error(msg, data);
16968
18187
  else if (lvl === "warn")
16969
- log9?.warn(msg, data);
18188
+ log10?.warn(msg, data);
16970
18189
  else
16971
- log9?.info(msg, data);
16972
- }
18190
+ log10?.info(msg, data);
18191
+ },
18192
+ tryMerge: ctx.tryMerge,
18193
+ mergeCommit: ctx.mergeCommit,
18194
+ mergeAbort: ctx.mergeAbort,
18195
+ worktreeHasChanges: ctx.worktreeHasChanges,
18196
+ removeWorktree: ctx.removeWorktree,
18197
+ commitWorktreeIfDirty: ctx.commitWorktreeIfDirty
16973
18198
  }
16974
18199
  });
16975
18200
  } catch (err) {
16976
18201
  const msg = err instanceof Error ? err.message : String(err);
16977
- log9?.error(`[${PLUGIN_NAME17}] schedule 抛错`, { error: msg });
18202
+ log10?.error(`[${PLUGIN_NAME18}] schedule 抛错`, { error: msg });
16978
18203
  await safeReply2(ctx, `❌ 并发调度失败:${msg}`);
16979
18204
  return { ok: false, reason: msg };
16980
18205
  }
16981
18206
  const head = `\uD83D\uDCCA 并发完成 ${result.results.length} 个子任务(${result.duration_ms}ms)`;
16982
18207
  const summaryLines = [head, "", "```", result.digest.text, "```"];
18208
+ if (result.mergeReport && result.mergeReport.pendingWorktrees.length > 0) {
18209
+ const repoCwd = mergeRoot ?? ctx.directory ?? process.cwd();
18210
+ summaryLines.push("", `⚠ ${result.mergeReport.pendingWorktrees.length} 个 worktree 有冲突,请手动解决:`);
18211
+ for (const p of result.mergeReport.pendingWorktrees) {
18212
+ const n = p.conflicts.length;
18213
+ const hint = n > 0 ? `(${n} files)` : "";
18214
+ summaryLines.push(` • ${p.path} ${hint} → cd ${repoCwd} && git merge ${p.subtaskId}`);
18215
+ }
18216
+ summaryLines.push("", `(解决冲突后在主仓库执行上面的 \`git merge\` 命令;冲突 worktree 路径已保留,可 cd 进去查看 status)`);
18217
+ }
16983
18218
  await safeReply2(ctx, summaryLines.join(`
16984
18219
  `));
16985
- log9?.info(`[${PLUGIN_NAME17}] schedule 完成`, {
18220
+ log10?.info(`[${PLUGIN_NAME18}] schedule 完成`, {
16986
18221
  parentId,
16987
18222
  success: result.digest.success,
16988
18223
  failed: result.digest.failed,
16989
- conflicts: result.conflicts.hasConflict
18224
+ conflicts: result.conflicts.hasConflict,
18225
+ autoMerge,
18226
+ merged: result.mergeReport?.merged ?? 0,
18227
+ conflicted: result.mergeReport?.conflicted ?? 0
16990
18228
  });
16991
18229
  if (typeof ctx.onCompleted === "function") {
16992
18230
  try {
16993
18231
  await ctx.onCompleted(result);
16994
18232
  } catch (err) {
16995
- log9?.warn(`[${PLUGIN_NAME17}] onCompleted hook 抛错(已隔离)`, {
18233
+ log10?.warn(`[${PLUGIN_NAME18}] onCompleted hook 抛错(已隔离)`, {
16996
18234
  error: err instanceof Error ? err.message : String(err)
16997
18235
  });
16998
18236
  }
@@ -17026,29 +18264,30 @@ async function maybeHandleMessage(raw) {
17026
18264
  log: ctx.log,
17027
18265
  client: ctx.client,
17028
18266
  parentSessionID: ctx.parentSessionID,
17029
- directory: ctx.directory
18267
+ directory: ctx.directory,
18268
+ autoMerge: ctx.autoMerge
17030
18269
  });
17031
18270
  }
17032
18271
  async function writeLog(level, msg, data) {
17033
18272
  const line = JSON.stringify({
17034
18273
  ts: new Date().toISOString(),
17035
18274
  level,
17036
- plugin: PLUGIN_NAME17,
18275
+ plugin: PLUGIN_NAME18,
17037
18276
  msg,
17038
18277
  data
17039
18278
  }) + `
17040
18279
  `;
17041
18280
  try {
17042
18281
  const logFile = getLogFile();
17043
- await fs10.mkdir(path13.dirname(logFile), { recursive: true });
17044
- await fs10.appendFile(logFile, line, "utf8");
18282
+ await fs12.mkdir(path15.dirname(logFile), { recursive: true });
18283
+ await fs12.appendFile(logFile, line, "utf8");
17045
18284
  } catch {}
17046
18285
  }
17047
- logLifecycle(PLUGIN_NAME17, "import");
18286
+ logLifecycle(PLUGIN_NAME18, "import");
17048
18287
  var subtasksServer = async (ctx) => {
17049
- const log9 = makePluginLogger(PLUGIN_NAME17);
18288
+ const log10 = makePluginLogger(PLUGIN_NAME18);
17050
18289
  const client = ctx?.client ?? undefined;
17051
- logLifecycle(PLUGIN_NAME17, "activate", {
18290
+ logLifecycle(PLUGIN_NAME18, "activate", {
17052
18291
  directory: ctx.directory,
17053
18292
  hasClient: Boolean(client)
17054
18293
  });
@@ -17061,6 +18300,40 @@ var subtasksServer = async (ctx) => {
17061
18300
  if (!description26) {
17062
18301
  return;
17063
18302
  }
18303
+ let autoMerge = false;
18304
+ try {
18305
+ const rt = await loadRuntime({ root: ctx.directory });
18306
+ autoMerge = rt.runtime.runtime.worktree_isolation === true;
18307
+ } catch {
18308
+ autoMerge = false;
18309
+ }
18310
+ const repoRoot = ctx.directory ?? process.cwd();
18311
+ if (autoMerge) {
18312
+ const gitOk = await isGitRepo({ root: repoRoot }).catch(() => false);
18313
+ if (!gitOk) {
18314
+ autoMerge = false;
18315
+ log10?.warn("[subtasks] worktree_isolation=true 但非 git 仓库,自动降级 autoMerge=false");
18316
+ const canNotice = Boolean(client);
18317
+ if (canNotice) {
18318
+ await sendParentNotice(client, input.sessionID, "ℹ 当前目录不是 git 仓库,worktree 隔离已自动关闭,以单目录模式运行", { directory: ctx.directory });
18319
+ }
18320
+ }
18321
+ }
18322
+ const allocateWorktree = autoMerge ? async (subtaskId) => {
18323
+ const wt = await ensureWorktree({ root: repoRoot, branch: subtaskId });
18324
+ return {
18325
+ path: wt.path,
18326
+ cleanup: async () => {
18327
+ try {
18328
+ await removeWorktree({
18329
+ root: repoRoot,
18330
+ worktree_path: wt.path,
18331
+ force: true
18332
+ });
18333
+ } catch {}
18334
+ }
18335
+ };
18336
+ } : undefined;
17064
18337
  const replyLines = [];
17065
18338
  const messageCtx = {
17066
18339
  content: `/parallel ${description26}`,
@@ -17068,10 +18341,12 @@ var subtasksServer = async (ctx) => {
17068
18341
  replyLines.push(s);
17069
18342
  return Promise.resolve();
17070
18343
  },
17071
- log: log9,
18344
+ log: log10,
17072
18345
  client,
17073
18346
  parentSessionID: input.sessionID,
17074
18347
  directory: ctx.directory,
18348
+ allocateWorktree,
18349
+ autoMerge,
17075
18350
  runner: client ? makeOpencodeRunner({
17076
18351
  client,
17077
18352
  parentSessionID: input.sessionID,
@@ -17079,11 +18354,11 @@ var subtasksServer = async (ctx) => {
17079
18354
  perTaskTimeoutMs: 5 * 60000,
17080
18355
  log: (lvl, msg, data) => {
17081
18356
  if (lvl === "error")
17082
- log9.error(msg, data);
18357
+ log10.error(msg, data);
17083
18358
  else if (lvl === "warn")
17084
- log9.warn(msg, data);
18359
+ log10.warn(msg, data);
17085
18360
  else
17086
- log9.info(msg, data);
18361
+ log10.info(msg, data);
17087
18362
  }
17088
18363
  }) : undefined
17089
18364
  };
@@ -17092,7 +18367,8 @@ var subtasksServer = async (ctx) => {
17092
18367
  sessionID: input.sessionID,
17093
18368
  ok: result?.ok,
17094
18369
  subtasks: result?.scheduled?.results?.length ?? 0,
17095
- usedMockRunner: !client
18370
+ usedMockRunner: !client,
18371
+ autoMerge
17096
18372
  });
17097
18373
  if (replyLines.length > 0 && Array.isArray(output?.parts)) {
17098
18374
  output.parts.length = 0;
@@ -17108,20 +18384,20 @@ var subtasksServer = async (ctx) => {
17108
18384
  });
17109
18385
  }
17110
18386
  } catch (err) {
17111
- log9.error(`[${PLUGIN_NAME17}] command.execute.before 异常(已隔离)`, {
18387
+ log10.error(`[${PLUGIN_NAME18}] command.execute.before 异常(已隔离)`, {
17112
18388
  error: err instanceof Error ? err.message : String(err)
17113
18389
  });
17114
18390
  }
17115
18391
  }
17116
18392
  };
17117
18393
  };
17118
- var handler17 = subtasksServer;
18394
+ var handler18 = subtasksServer;
17119
18395
 
17120
18396
  // plugins/terminal-monitor.ts
17121
18397
  init_opencode_plugin_helpers();
17122
18398
  import * as crypto5 from "node:crypto";
17123
- var PLUGIN_NAME18 = "terminal-monitor";
17124
- logLifecycle(PLUGIN_NAME18, "import", {});
18399
+ var PLUGIN_NAME19 = "terminal-monitor";
18400
+ logLifecycle(PLUGIN_NAME19, "import", {});
17125
18401
  var DEFAULT_CONFIG7 = {
17126
18402
  minScore: 0.6,
17127
18403
  cooldownMs: 30000,
@@ -17264,7 +18540,7 @@ function parseTerminalOutput(ev, cfg = {}, rules = DEFAULT_RULES) {
17264
18540
  `).length;
17265
18541
  const lineFromStart = text.split(`
17266
18542
  `)[lineIdx - 1] ?? m[0];
17267
- const excerpt = clip5(lineFromStart.trim(), c.maxExcerpt);
18543
+ const excerpt = clip6(lineFromStart.trim(), c.maxExcerpt);
17268
18544
  if (!out.has(rule.kind)) {
17269
18545
  out.set(rule.kind, {
17270
18546
  severity: rule.severity,
@@ -17286,7 +18562,7 @@ function parseTerminalOutput(ev, cfg = {}, rules = DEFAULT_RULES) {
17286
18562
  }
17287
18563
  return [...out.values()].sort((a, b) => b.score - a.score);
17288
18564
  }
17289
- function clip5(s, max) {
18565
+ function clip6(s, max) {
17290
18566
  if (s.length <= max)
17291
18567
  return s;
17292
18568
  return s.slice(0, max - 1) + "…";
@@ -17345,7 +18621,7 @@ function shouldNotify(findings, ev, lru, cfg = {}, now = Date.now()) {
17345
18621
  function buildSummary(ev, findings) {
17346
18622
  const parts = [];
17347
18623
  if (ev.cmd)
17348
- parts.push(`\`${clip5(ev.cmd, 60)}\``);
18624
+ parts.push(`\`${clip6(ev.cmd, 60)}\``);
17349
18625
  if (ev.type === "terminal.exit" && typeof ev.exit_code === "number") {
17350
18626
  parts.push(`exit=${ev.exit_code}`);
17351
18627
  }
@@ -17357,7 +18633,7 @@ function buildSummary(ev, findings) {
17357
18633
  return parts.join(" · ");
17358
18634
  }
17359
18635
  const top = findings[0];
17360
- parts.push(`${top.severity}/${top.kind}: ${clip5(top.excerpt, 80)}`);
18636
+ parts.push(`${top.severity}/${top.kind}: ${clip6(top.excerpt, 80)}`);
17361
18637
  if (findings.length > 1)
17362
18638
  parts.push(`(+${findings.length - 1} 条)`);
17363
18639
  return parts.join(" · ");
@@ -17403,17 +18679,17 @@ function describeError(err) {
17403
18679
  return String(err);
17404
18680
  }
17405
18681
  }
17406
- var log9 = makePluginLogger(PLUGIN_NAME18);
18682
+ var log10 = makePluginLogger(PLUGIN_NAME19);
17407
18683
  var lru = new FingerprintLRU2;
17408
18684
  var _lastNotification = null;
17409
18685
  var terminalMonitorServer = async (ctx) => {
17410
- logLifecycle(PLUGIN_NAME18, "activate", {
18686
+ logLifecycle(PLUGIN_NAME19, "activate", {
17411
18687
  directory: ctx.directory,
17412
18688
  triggerEventTypes: ["terminal.output", "terminal.exit"]
17413
18689
  });
17414
18690
  return {
17415
18691
  event: async ({ event }) => {
17416
- await safeAsync(PLUGIN_NAME18, "event", async () => {
18692
+ await safeAsync(PLUGIN_NAME19, "event", async () => {
17417
18693
  const e = event;
17418
18694
  if (!e || typeof e.type !== "string")
17419
18695
  return;
@@ -17422,12 +18698,12 @@ var terminalMonitorServer = async (ctx) => {
17422
18698
  const ev = { type: e.type, ...e.properties ?? {} };
17423
18699
  const r = await processTerminalEvent(ev, {
17424
18700
  lru,
17425
- log: log9,
18701
+ log: log10,
17426
18702
  notifyAgent: (msg) => {
17427
18703
  _lastNotification = msg;
17428
18704
  }
17429
18705
  });
17430
- safeWriteLog(PLUGIN_NAME18, {
18706
+ safeWriteLog(PLUGIN_NAME19, {
17431
18707
  hook: "event",
17432
18708
  type: e.type,
17433
18709
  notified: r.notified,
@@ -17435,19 +18711,19 @@ var terminalMonitorServer = async (ctx) => {
17435
18711
  findings: r.notification?.findings.length ?? 0
17436
18712
  });
17437
18713
  if (r.notified && r.notification) {
17438
- log9.info(`[${PLUGIN_NAME18}] 反馈 ${r.notification.findings.length} 条 → ${r.notification.summary}`);
18714
+ log10.info(`[${PLUGIN_NAME19}] 反馈 ${r.notification.findings.length} 条 → ${r.notification.summary}`);
17439
18715
  }
17440
18716
  });
17441
18717
  }
17442
18718
  };
17443
18719
  };
17444
- var handler18 = terminalMonitorServer;
18720
+ var handler19 = terminalMonitorServer;
17445
18721
 
17446
18722
  // plugins/token-manager.ts
17447
18723
  init_opencode_plugin_helpers();
17448
- var PLUGIN_NAME19 = "token-manager";
17449
- logLifecycle(PLUGIN_NAME19, "import", {});
17450
- async function handleMessageBefore(raw, log10, defaults) {
18724
+ var PLUGIN_NAME20 = "token-manager";
18725
+ logLifecycle(PLUGIN_NAME20, "import", {});
18726
+ async function handleMessageBefore(raw, log11, defaults) {
17451
18727
  const ctx = raw ?? {};
17452
18728
  if (!Array.isArray(ctx.messages) || ctx.messages.length === 0)
17453
18729
  return null;
@@ -17466,21 +18742,21 @@ async function handleMessageBefore(raw, log10, defaults) {
17466
18742
  };
17467
18743
  if (r.compressed) {
17468
18744
  ctx.messages = r.messages;
17469
- log10?.info(`[${PLUGIN_NAME19}] 压缩 ${r.before.count}→${r.after.count} 条 / ${r.before.tokens}→${r.after.tokens} tokens`);
18745
+ log11?.info(`[${PLUGIN_NAME20}] 压缩 ${r.before.count}→${r.after.count} 条 / ${r.before.tokens}→${r.after.tokens} tokens`);
17470
18746
  }
17471
18747
  return r;
17472
18748
  } catch (err) {
17473
- log10?.warn(`[${PLUGIN_NAME19}] 压缩异常(已隔离)`, {
18749
+ log11?.warn(`[${PLUGIN_NAME20}] 压缩异常(已隔离)`, {
17474
18750
  error: err instanceof Error ? err.message : String(err)
17475
18751
  });
17476
18752
  return null;
17477
18753
  }
17478
18754
  }
17479
- var log10 = makePluginLogger(PLUGIN_NAME19);
18755
+ var log11 = makePluginLogger(PLUGIN_NAME20);
17480
18756
  var tokenManagerServer = async (ctx) => {
17481
18757
  const rt = loadRuntimeSync();
17482
18758
  const threshold = rt.runtime.context.condenser_threshold_ratio;
17483
- logLifecycle(PLUGIN_NAME19, "activate", {
18759
+ logLifecycle(PLUGIN_NAME20, "activate", {
17484
18760
  directory: ctx.directory,
17485
18761
  threshold,
17486
18762
  target: DEFAULT_CONDENSE.target,
@@ -17489,7 +18765,7 @@ var tokenManagerServer = async (ctx) => {
17489
18765
  });
17490
18766
  return {
17491
18767
  "experimental.chat.messages.transform": async (_input, output) => {
17492
- await safeAsync(PLUGIN_NAME19, "experimental.chat.messages.transform", async () => {
18768
+ await safeAsync(PLUGIN_NAME20, "experimental.chat.messages.transform", async () => {
17493
18769
  const list = output.messages;
17494
18770
  if (!Array.isArray(list) || list.length === 0)
17495
18771
  return;
@@ -17499,10 +18775,10 @@ var tokenManagerServer = async (ctx) => {
17499
18775
  `);
17500
18776
  return { role, content };
17501
18777
  });
17502
- const r = await handleMessageBefore({ messages: flat }, log10, { threshold });
18778
+ const r = await handleMessageBefore({ messages: flat }, log11, { threshold });
17503
18779
  if (!r)
17504
18780
  return;
17505
- safeWriteLog(PLUGIN_NAME19, {
18781
+ safeWriteLog(PLUGIN_NAME20, {
17506
18782
  hook: "experimental.chat.messages.transform",
17507
18783
  mode: "observe-only",
17508
18784
  before_msgs: r.before.count,
@@ -17513,144 +18789,21 @@ var tokenManagerServer = async (ctx) => {
17513
18789
  reason: r.reason
17514
18790
  });
17515
18791
  if (r.compressed) {
17516
- 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)`);
18792
+ log11.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)`);
17517
18793
  }
17518
18794
  });
17519
18795
  }
17520
18796
  };
17521
18797
  };
17522
- var handler19 = tokenManagerServer;
18798
+ var handler20 = tokenManagerServer;
17523
18799
 
17524
18800
  // plugins/tool-policy.ts
17525
18801
  init_opencode_plugin_helpers();
17526
- import { promises as fs11 } from "node:fs";
17527
- import * as path15 from "node:path";
17528
-
17529
- // lib/autonomy.ts
17530
- var DEFAULT_POLICIES = {
17531
- step: {
17532
- bash: "confirm",
17533
- edit: "confirm",
17534
- webfetch: "confirm",
17535
- read: "confirm",
17536
- search: "confirm",
17537
- other: "confirm"
17538
- },
17539
- semi: {
17540
- bash: "confirm",
17541
- edit: "confirm",
17542
- webfetch: "confirm",
17543
- read: "auto",
17544
- search: "auto",
17545
- other: "auto"
17546
- },
17547
- full: {
17548
- bash: "auto",
17549
- edit: "auto",
17550
- webfetch: "auto",
17551
- read: "auto",
17552
- search: "auto",
17553
- other: "auto"
17554
- }
17555
- };
17556
- var RISK_PATTERNS = [
17557
- {
17558
- tag: "rm_rf_root",
17559
- kinds: ["bash"],
17560
- re: /rm\s+-[rRf]+[^|;`\n]*\s+(\/(?:\s|$|[^/\w*.-])|~|\$HOME(?:\b|\/)|\/(?:bin|etc|usr|var|opt|home|root|boot|lib|sbin)\b)/
17561
- },
17562
- {
17563
- tag: "rm_rf_wildcard",
17564
- kinds: ["bash"],
17565
- re: /rm\s+-[rRf]+\s+(?:\.\*|\*)/
17566
- },
17567
- {
17568
- tag: "force_push_protected",
17569
- kinds: ["bash"],
17570
- re: /git\s+push\s+(?:--force\b|-f\b)[\s\S]*\b(main|master|release(?:\/[\w.-]+)?)\b/
17571
- },
17572
- {
17573
- tag: "git_reset_hard_main",
17574
- kinds: ["bash"],
17575
- re: /git\s+reset\s+--hard\s+(origin\/)?(main|master)/
17576
- },
17577
- { tag: "sudo", kinds: ["bash"], re: /(?<![\w-])sudo(?![\w-])/ },
17578
- { tag: "su_root", kinds: ["bash"], re: /(?<![\w-])su\s+(?:-\s+)?root\b/ },
17579
- { tag: "mkfs", kinds: ["bash"], re: /\bmkfs(\.[\w]+)?\b/ },
17580
- { tag: "dd_disk", kinds: ["bash"], re: /\bdd\s+if=[^|;\s]+\s+of=\/dev\// },
17581
- { tag: "chmod_777", kinds: ["bash"], re: /chmod\s+[-+]?[0-7]?777/ },
17582
- {
17583
- tag: "curl_pipe_sh",
17584
- kinds: ["bash"],
17585
- re: /\b(?:curl|wget)\b[^|]*\|\s*(?:sh|bash|zsh)\b/
17586
- },
17587
- {
17588
- tag: "drop_database",
17589
- kinds: ["bash", "other"],
17590
- re: /\b(DROP\s+(DATABASE|TABLE)|TRUNCATE\s+TABLE|DROP\s+SCHEMA)\b/i
17591
- },
17592
- { tag: "write_secrets", re: /(\.env(?:\.\w+)?|id_[edr]sa|\.ssh\/id_|\.pem|\.p12|secret\.json)/i },
17593
- { tag: "write_etc", re: /(?:^|\s|"|')\/etc\// },
17594
- { tag: "write_usr", re: /(?:^|\s|"|')\/usr\// },
17595
- { tag: "write_root_home", re: /(?:^|\s|"|')(\/root|\/home\/root)\// },
17596
- {
17597
- tag: "internal_url",
17598
- kinds: ["webfetch"],
17599
- re: /https?:\/\/(localhost|127\.0\.0\.1|0\.0\.0\.0|169\.254\.|10\.|192\.168\.|172\.(?:1[6-9]|2\d|3[01])\.)/i
17600
- },
17601
- {
17602
- tag: "kubectl_delete",
17603
- kinds: ["bash"],
17604
- re: /kubectl\s+(?:delete|destroy)\s+/
17605
- },
17606
- {
17607
- tag: "terraform_destroy",
17608
- kinds: ["bash"],
17609
- re: /terraform\s+destroy/
17610
- }
17611
- ];
17612
- function evaluate2(mode, intent, opts = {}) {
17613
- const ignore = new Set(opts.ignore_risks ?? []);
17614
- const allRisks = [...RISK_PATTERNS, ...opts.extra_risks ?? []];
17615
- const haystack = buildHaystack(intent.args);
17616
- const hits = allRisks.filter((p) => !ignore.has(p.tag)).filter((p) => !p.kinds || p.kinds.includes(intent.kind)).filter((p) => p.re.test(haystack)).map((p) => p.tag);
17617
- let effective = mode;
17618
- let downgraded = false;
17619
- let reason;
17620
- if (hits.length > 0 && mode === "full") {
17621
- effective = "semi";
17622
- downgraded = true;
17623
- reason = `检测到风险操作 [${hits.join(", ")}],自动从 full 降级到 semi`;
17624
- } else if (hits.length > 0 && mode !== "step") {}
17625
- const policy = mergePolicy(effective, opts.policies);
17626
- const baseAction = policy[intent.kind] ?? "confirm";
17627
- const action = hits.length > 0 && effective !== "step" ? "confirm" : baseAction;
17628
- return {
17629
- effective_mode: effective,
17630
- action,
17631
- downgraded,
17632
- reason,
17633
- detected_risks: hits
17634
- };
17635
- }
17636
- function mergePolicy(mode, override) {
17637
- const base = DEFAULT_POLICIES[mode];
17638
- if (!override?.[mode])
17639
- return base;
17640
- return { ...base, ...override[mode] };
17641
- }
17642
- function buildHaystack(args) {
17643
- if (typeof args === "string")
17644
- return args;
17645
- try {
17646
- return JSON.stringify(args);
17647
- } catch {
17648
- return String(args);
17649
- }
17650
- }
18802
+ import { promises as fs13 } from "node:fs";
18803
+ import * as path17 from "node:path";
17651
18804
 
17652
18805
  // lib/file-regex-acl.ts
17653
- import * as path14 from "node:path";
18806
+ import * as path16 from "node:path";
17654
18807
  function compileRule(r) {
17655
18808
  if (r instanceof RegExp)
17656
18809
  return r;
@@ -17716,7 +18869,7 @@ function normalizePath2(p) {
17716
18869
  let s = p.replace(/\\/g, "/");
17717
18870
  if (s.startsWith("./"))
17718
18871
  s = s.slice(2);
17719
- s = path14.posix.normalize(s);
18872
+ s = path16.posix.normalize(s);
17720
18873
  return s;
17721
18874
  }
17722
18875
  function checkFileAccess(acl, file, op) {
@@ -17762,8 +18915,8 @@ function checkFileAccess(acl, file, op) {
17762
18915
  }
17763
18916
 
17764
18917
  // plugins/tool-policy.ts
17765
- var PLUGIN_NAME20 = "tool-policy";
17766
- logLifecycle(PLUGIN_NAME20, "import", {});
18918
+ var PLUGIN_NAME21 = "tool-policy";
18919
+ logLifecycle(PLUGIN_NAME21, "import", {});
17767
18920
  var EMPTY_ACL = { whitelistMode: false };
17768
18921
  var SUBAGENT_APPLY_DENY_LIST = new Set([
17769
18922
  "coder",
@@ -17786,7 +18939,7 @@ function decideToolCall(ctx, cfg = {}, currentAgent) {
17786
18939
  return {
17787
18940
  action: "deny",
17788
18941
  reasons: [
17789
- `[ADR-0061] subagent '${currentAgent}' 禁止调 pending_changes.${action2}` + ` — apply 必须由 codeforge orchestrator 或用户拍板`,
18942
+ `[ADR:prevent-subagent-self-apply] subagent '${currentAgent}' 禁止调 pending_changes.${action2}` + ` — apply 必须由 codeforge orchestrator 或用户拍板`,
17790
18943
  `替代路径:stage 完成后回报 codeforge,由其决定 apply 后通过 task_id 复用启动你跑测试`
17791
18944
  ],
17792
18945
  autonomy: {
@@ -17794,7 +18947,7 @@ function decideToolCall(ctx, cfg = {}, currentAgent) {
17794
18947
  action: "confirm",
17795
18948
  downgraded: false,
17796
18949
  detected_risks: ["subagent_apply_blocked"],
17797
- reason: `subagent '${currentAgent}' 自 apply 越权 (ADR-0061)`
18950
+ reason: `subagent '${currentAgent}' 自 apply 越权 (ADR:prevent-subagent-self-apply)`
17798
18951
  }
17799
18952
  };
17800
18953
  }
@@ -17826,11 +18979,11 @@ function decideToolCall(ctx, cfg = {}, currentAgent) {
17826
18979
  action = "deny";
17827
18980
  return { action, reasons, autonomy: a, acl: aclResults };
17828
18981
  }
17829
- var POLICY_PATH = path15.join(".codeforge", "policy.json");
18982
+ var POLICY_PATH = path17.join(".codeforge", "policy.json");
17830
18983
  async function loadPolicy(root = process.cwd()) {
17831
- const file = path15.join(root, POLICY_PATH);
18984
+ const file = path17.join(root, POLICY_PATH);
17832
18985
  try {
17833
- const raw = await fs11.readFile(file, "utf8");
18986
+ const raw = await fs13.readFile(file, "utf8");
17834
18987
  const data = JSON.parse(raw);
17835
18988
  return data;
17836
18989
  } catch {
@@ -17847,18 +19000,18 @@ function classifyToolKind(toolName) {
17847
19000
  return "webfetch";
17848
19001
  return "other";
17849
19002
  }
17850
- async function resolveCurrentAgent(client, sessionID, log11) {
19003
+ async function resolveCurrentAgent(client, sessionID, log12) {
17851
19004
  try {
17852
19005
  const sessionApi = client?.session;
17853
19006
  if (!sessionApi || typeof sessionApi.get !== "function") {
17854
- log11.warn(`client.session.get unavailable`, { sessionID });
19007
+ log12.warn(`client.session.get unavailable`, { sessionID });
17855
19008
  return;
17856
19009
  }
17857
19010
  const res = await sessionApi.get({ path: { id: sessionID } });
17858
19011
  const data = res?.data ?? res;
17859
19012
  const rawAgent = data?.agent;
17860
19013
  if (typeof rawAgent !== "string" || rawAgent === "") {
17861
- log11.warn(`client.session.get returned no string agent (保守放行)`, {
19014
+ log12.warn(`client.session.get returned no string agent (保守放行)`, {
17862
19015
  sessionID,
17863
19016
  dataKeys: data && typeof data === "object" ? Object.keys(data) : null
17864
19017
  });
@@ -17866,20 +19019,20 @@ async function resolveCurrentAgent(client, sessionID, log11) {
17866
19019
  }
17867
19020
  return rawAgent;
17868
19021
  } catch (err) {
17869
- log11.warn(`client.session.get failed (保守放行)`, {
19022
+ log12.warn(`client.session.get failed (保守放行)`, {
17870
19023
  sessionID,
17871
19024
  error: err instanceof Error ? err.message : String(err)
17872
19025
  });
17873
19026
  return;
17874
19027
  }
17875
19028
  }
17876
- var log11 = makePluginLogger(PLUGIN_NAME20);
19029
+ var log12 = makePluginLogger(PLUGIN_NAME21);
17877
19030
  var toolPolicyServer = async (ctx) => {
17878
19031
  const directory = ctx.directory ?? process.cwd();
17879
19032
  const cfg = await loadPolicy(directory);
17880
19033
  const rt = loadRuntimeSync();
17881
19034
  cfg.defaultMode = rt.runtime.autonomy.default_mode;
17882
- logLifecycle(PLUGIN_NAME20, "activate", {
19035
+ logLifecycle(PLUGIN_NAME21, "activate", {
17883
19036
  directory,
17884
19037
  acl_loaded: !!cfg.acl,
17885
19038
  default_mode: cfg.defaultMode,
@@ -17888,13 +19041,13 @@ var toolPolicyServer = async (ctx) => {
17888
19041
  return {
17889
19042
  "tool.execute.before": async (input, output) => {
17890
19043
  let denied;
17891
- await safeAsync(PLUGIN_NAME20, "tool.execute.before", async () => {
19044
+ await safeAsync(PLUGIN_NAME21, "tool.execute.before", async () => {
17892
19045
  const toolName = input.tool;
17893
19046
  const argsObj = output.args ?? {};
17894
19047
  const needsAgentDetection = toolName === "pending_changes" && (argsObj.action === "apply" || argsObj.action === "apply_all");
17895
19048
  let currentAgent = undefined;
17896
19049
  if (needsAgentDetection) {
17897
- currentAgent = await resolveCurrentAgent(ctx.client, input.sessionID, log11);
19050
+ currentAgent = await resolveCurrentAgent(ctx.client, input.sessionID, log12);
17898
19051
  }
17899
19052
  const files = [];
17900
19053
  const filePath = argsObj["path"] ?? argsObj["filePath"] ?? argsObj["file"];
@@ -17909,7 +19062,7 @@ var toolPolicyServer = async (ctx) => {
17909
19062
  mode: cfg.defaultMode,
17910
19063
  files: files.length ? files : undefined
17911
19064
  }, cfg, currentAgent);
17912
- safeWriteLog(PLUGIN_NAME20, {
19065
+ safeWriteLog(PLUGIN_NAME21, {
17913
19066
  hook: "tool.execute.before",
17914
19067
  tool: toolName,
17915
19068
  callID: input.callID,
@@ -17919,14 +19072,14 @@ var toolPolicyServer = async (ctx) => {
17919
19072
  currentAgent
17920
19073
  });
17921
19074
  if (decision.action === "deny") {
17922
- log11.warn(`[${PLUGIN_NAME20}] DENY ${toolName}`, {
19075
+ log12.warn(`[${PLUGIN_NAME21}] DENY ${toolName}`, {
17923
19076
  action: argsObj.action,
17924
19077
  currentAgent,
17925
19078
  reasons: decision.reasons
17926
19079
  });
17927
19080
  denied = new Error(`[tool-policy] DENIED: ${decision.reasons.join(" / ")}`);
17928
19081
  } else if (decision.action === "confirm") {
17929
- log11.info(`[${PLUGIN_NAME20}] CONFIRM ${toolName}: ${decision.reasons.join("; ")}`);
19082
+ log12.info(`[${PLUGIN_NAME21}] CONFIRM ${toolName}: ${decision.reasons.join("; ")}`);
17930
19083
  }
17931
19084
  });
17932
19085
  if (denied)
@@ -17934,13 +19087,13 @@ var toolPolicyServer = async (ctx) => {
17934
19087
  }
17935
19088
  };
17936
19089
  };
17937
- var handler20 = toolPolicyServer;
19090
+ var handler21 = toolPolicyServer;
17938
19091
 
17939
19092
  // plugins/update-checker.ts
17940
19093
  init_opencode_plugin_helpers();
17941
19094
  import { existsSync as existsSync5 } from "node:fs";
17942
- import { homedir as homedir7 } from "node:os";
17943
- import { join as join16 } from "node:path";
19095
+ import { homedir as homedir8 } from "node:os";
19096
+ import { join as join18 } from "node:path";
17944
19097
 
17945
19098
  // lib/update-checker-impl.ts
17946
19099
  import { createHash as createHash6 } from "node:crypto";
@@ -17949,15 +19102,15 @@ import {
17949
19102
  existsSync as existsSync4,
17950
19103
  mkdirSync as mkdirSync3,
17951
19104
  mkdtempSync,
17952
- readFileSync as readFileSync4,
17953
- readdirSync,
19105
+ readFileSync as readFileSync5,
19106
+ readdirSync as readdirSync2,
17954
19107
  renameSync,
17955
- statSync as statSync3,
19108
+ statSync as statSync4,
17956
19109
  unlinkSync,
17957
19110
  writeFileSync as writeFileSync2
17958
19111
  } from "node:fs";
17959
- import { homedir as homedir6, tmpdir } from "node:os";
17960
- import { dirname as dirname7, join as join15 } from "node:path";
19112
+ import { homedir as homedir7, tmpdir } from "node:os";
19113
+ import { dirname as dirname7, join as join17 } from "node:path";
17961
19114
  import { fileURLToPath } from "node:url";
17962
19115
  import * as https from "node:https";
17963
19116
  import * as zlib from "node:zlib";
@@ -17965,7 +19118,7 @@ import * as zlib from "node:zlib";
17965
19118
  // lib/version-injected.ts
17966
19119
  function getInjectedVersion() {
17967
19120
  try {
17968
- const v = "0.3.11";
19121
+ const v = "0.3.12";
17969
19122
  if (typeof v === "string" && /^\d+\.\d+\.\d+/.test(v)) {
17970
19123
  return v;
17971
19124
  }
@@ -18055,23 +19208,23 @@ function readLocalVersion() {
18055
19208
  try {
18056
19209
  const here = fileURLToPath(import.meta.url);
18057
19210
  const root = dirname7(dirname7(here));
18058
- const pkg = JSON.parse(readFileSync4(join15(root, "package.json"), "utf8"));
19211
+ const pkg = JSON.parse(readFileSync5(join17(root, "package.json"), "utf8"));
18059
19212
  return typeof pkg.version === "string" ? pkg.version : "0.0.0";
18060
19213
  } catch {
18061
19214
  return "0.0.0";
18062
19215
  }
18063
19216
  }
18064
19217
  function defaultCacheDir() {
18065
- return process.env["CODEFORGE_CACHE_DIR"] ?? join15(homedir6(), ".cache", "codeforge");
19218
+ return process.env["CODEFORGE_CACHE_DIR"] ?? join17(homedir7(), ".cache", "codeforge");
18066
19219
  }
18067
19220
  function defaultCacheFile() {
18068
- return join15(defaultCacheDir(), "update-check.json");
19221
+ return join17(defaultCacheDir(), "update-check.json");
18069
19222
  }
18070
19223
  function readCache(file) {
18071
19224
  try {
18072
19225
  if (!existsSync4(file))
18073
19226
  return null;
18074
- const raw = readFileSync4(file, "utf8");
19227
+ const raw = readFileSync5(file, "utf8");
18075
19228
  const obj = JSON.parse(raw);
18076
19229
  if (obj && typeof obj === "object" && typeof obj.checkedAt === "number" && typeof obj.remote === "string" && typeof obj.repo === "string") {
18077
19230
  return obj;
@@ -18102,7 +19255,7 @@ function fetchLatestTagFromGitHub(repo) {
18102
19255
  });
18103
19256
  }
18104
19257
  function getJsonWithRedirect(url, hopsLeft) {
18105
- return new Promise((resolve11, reject) => {
19258
+ return new Promise((resolve13, reject) => {
18106
19259
  const u = new URL(url);
18107
19260
  const headers = {
18108
19261
  "User-Agent": "codeforge-update-checker",
@@ -18126,12 +19279,12 @@ function getJsonWithRedirect(url, hopsLeft) {
18126
19279
  return;
18127
19280
  }
18128
19281
  const next = new URL(res.headers.location, url).toString();
18129
- getJsonWithRedirect(next, hopsLeft - 1).then(resolve11, reject);
19282
+ getJsonWithRedirect(next, hopsLeft - 1).then(resolve13, reject);
18130
19283
  return;
18131
19284
  }
18132
19285
  if (status === 404) {
18133
19286
  res.resume();
18134
- resolve11(null);
19287
+ resolve13(null);
18135
19288
  return;
18136
19289
  }
18137
19290
  if (status >= 400) {
@@ -18142,7 +19295,7 @@ function getJsonWithRedirect(url, hopsLeft) {
18142
19295
  let body = "";
18143
19296
  res.setEncoding("utf8");
18144
19297
  res.on("data", (chunk) => body += chunk);
18145
- res.on("end", () => resolve11(body));
19298
+ res.on("end", () => resolve13(body));
18146
19299
  });
18147
19300
  req.on("timeout", () => {
18148
19301
  req.destroy();
@@ -18182,7 +19335,7 @@ async function fetchLatestFromNpm(opts) {
18182
19335
  return { version, tarballUrl, integrity };
18183
19336
  }
18184
19337
  function defaultHttpFetcher(url, timeoutMs) {
18185
- return new Promise((resolve11, reject) => {
19338
+ return new Promise((resolve13, reject) => {
18186
19339
  const u = new URL(url);
18187
19340
  const headers = {
18188
19341
  "User-Agent": "codeforge-update-checker",
@@ -18199,7 +19352,7 @@ function defaultHttpFetcher(url, timeoutMs) {
18199
19352
  const status = res.statusCode ?? 0;
18200
19353
  if (status === 404) {
18201
19354
  res.resume();
18202
- resolve11(null);
19355
+ resolve13(null);
18203
19356
  return;
18204
19357
  }
18205
19358
  if (status >= 400) {
@@ -18210,7 +19363,7 @@ function defaultHttpFetcher(url, timeoutMs) {
18210
19363
  let body = "";
18211
19364
  res.setEncoding("utf8");
18212
19365
  res.on("data", (chunk) => body += chunk);
18213
- res.on("end", () => resolve11(body));
19366
+ res.on("end", () => resolve13(body));
18214
19367
  });
18215
19368
  req.on("timeout", () => {
18216
19369
  req.destroy();
@@ -18221,14 +19374,14 @@ function defaultHttpFetcher(url, timeoutMs) {
18221
19374
  });
18222
19375
  }
18223
19376
  async function downloadAndExtractBundle(opts) {
18224
- const tmpRoot = opts.tmpDir ?? mkdtempSync(join15(tmpdir(), "codeforge-update-"));
19377
+ const tmpRoot = opts.tmpDir ?? mkdtempSync(join17(tmpdir(), "codeforge-update-"));
18225
19378
  mkdirSync3(tmpRoot, { recursive: true });
18226
19379
  const fetcher = opts.tarballFetcher ?? defaultBinaryFetcher;
18227
19380
  const tarballBuf = await fetcher(opts.tarballUrl);
18228
19381
  verifyIntegrity(tarballBuf, opts.expectedIntegrity);
18229
19382
  const tarBuf = zlib.gunzipSync(tarballBuf);
18230
19383
  extractTarToDir(tarBuf, tmpRoot);
18231
- const bundlePath = join15(tmpRoot, "package", "dist", "index.js");
19384
+ const bundlePath = join17(tmpRoot, "package", "dist", "index.js");
18232
19385
  if (!existsSync4(bundlePath)) {
18233
19386
  throw new Error(`bundle_not_found: ${bundlePath}`);
18234
19387
  }
@@ -18268,11 +19421,11 @@ function extractTarToDir(tarBuf, destRoot) {
18268
19421
  offset += 512;
18269
19422
  if (typeFlag === "0" || typeFlag === "" || typeFlag === "\x00") {
18270
19423
  const fileBuf = tarBuf.subarray(offset, offset + size);
18271
- const dest = join15(destRoot, fullName);
19424
+ const dest = join17(destRoot, fullName);
18272
19425
  mkdirSync3(dirname7(dest), { recursive: true });
18273
19426
  writeFileSync2(dest, fileBuf);
18274
19427
  } else if (typeFlag === "5") {
18275
- mkdirSync3(join15(destRoot, fullName), { recursive: true });
19428
+ mkdirSync3(join17(destRoot, fullName), { recursive: true });
18276
19429
  }
18277
19430
  offset += Math.ceil(size / 512) * 512;
18278
19431
  }
@@ -18281,7 +19434,7 @@ function defaultBinaryFetcher(url) {
18281
19434
  return downloadBinary(url, 3);
18282
19435
  }
18283
19436
  function downloadBinary(url, hopsLeft) {
18284
- return new Promise((resolve11, reject) => {
19437
+ return new Promise((resolve13, reject) => {
18285
19438
  const u = new URL(url);
18286
19439
  const req = https.request({
18287
19440
  host: u.hostname,
@@ -18299,7 +19452,7 @@ function downloadBinary(url, hopsLeft) {
18299
19452
  return;
18300
19453
  }
18301
19454
  const next = new URL(res.headers.location, url).toString();
18302
- downloadBinary(next, hopsLeft - 1).then(resolve11, reject);
19455
+ downloadBinary(next, hopsLeft - 1).then(resolve13, reject);
18303
19456
  return;
18304
19457
  }
18305
19458
  if (status >= 400) {
@@ -18309,7 +19462,7 @@ function downloadBinary(url, hopsLeft) {
18309
19462
  }
18310
19463
  const chunks = [];
18311
19464
  res.on("data", (chunk) => chunks.push(chunk));
18312
- res.on("end", () => resolve11(Buffer.concat(chunks)));
19465
+ res.on("end", () => resolve13(Buffer.concat(chunks)));
18313
19466
  });
18314
19467
  req.on("timeout", () => {
18315
19468
  req.destroy();
@@ -18380,11 +19533,11 @@ function cleanupOldBackups(target, keep) {
18380
19533
  const dir = dirname7(target);
18381
19534
  const base = target.substring(dir.length + 1);
18382
19535
  const prefix = `${base}.bak.`;
18383
- const all = readdirSync(dir).filter((f) => f.startsWith(prefix)).map((f) => {
18384
- const full = join15(dir, f);
19536
+ const all = readdirSync2(dir).filter((f) => f.startsWith(prefix)).map((f) => {
19537
+ const full = join17(dir, f);
18385
19538
  let mtimeMs = 0;
18386
19539
  try {
18387
- mtimeMs = statSync3(full).mtimeMs;
19540
+ mtimeMs = statSync4(full).mtimeMs;
18388
19541
  } catch {}
18389
19542
  return { full, mtimeMs };
18390
19543
  }).sort((a, b) => b.mtimeMs - a.mtimeMs);
@@ -18403,11 +19556,11 @@ function loadCompatibility(opts) {
18403
19556
  const root = opts?.cwd ?? inferPluginRoot();
18404
19557
  if (!root)
18405
19558
  return null;
18406
- file = join15(root, "compatibility.json");
19559
+ file = join17(root, "compatibility.json");
18407
19560
  }
18408
19561
  if (!existsSync4(file))
18409
19562
  return null;
18410
- const raw = readFileSync4(file, "utf8");
19563
+ const raw = readFileSync5(file, "utf8");
18411
19564
  const obj = JSON.parse(raw);
18412
19565
  if (!obj || typeof obj !== "object")
18413
19566
  return null;
@@ -18468,20 +19621,20 @@ function compareOpencodeVersion(opts) {
18468
19621
  }
18469
19622
 
18470
19623
  // plugins/update-checker.ts
18471
- var PLUGIN_NAME21 = "update-checker";
19624
+ var PLUGIN_NAME22 = "update-checker";
18472
19625
  var PLUGIN_VERSION = "2.0.0";
18473
- logLifecycle(PLUGIN_NAME21, "import", { version: PLUGIN_VERSION });
19626
+ logLifecycle(PLUGIN_NAME22, "import", { version: PLUGIN_VERSION });
18474
19627
  var updateCheckerServer = async (ctx) => {
18475
19628
  const yieldResult = shouldYieldToLocalPlugin({ directory: ctx.directory });
18476
19629
  if (yieldResult.yield) {
18477
- safeWriteLog(PLUGIN_NAME21, {
19630
+ safeWriteLog(PLUGIN_NAME22, {
18478
19631
  level: "info",
18479
19632
  msg: "dev_mode_yield_skip",
18480
19633
  reason: yieldResult.reason,
18481
19634
  markerPath: yieldResult.markerPath,
18482
19635
  detail: formatYieldLog(yieldResult)
18483
19636
  });
18484
- logLifecycle(PLUGIN_NAME21, "activate", {
19637
+ logLifecycle(PLUGIN_NAME22, "activate", {
18485
19638
  yield_to_local: true,
18486
19639
  yield_reason: yieldResult.reason,
18487
19640
  skipped: "auto_install + background_check"
@@ -18490,7 +19643,7 @@ var updateCheckerServer = async (ctx) => {
18490
19643
  }
18491
19644
  const rt = loadRuntimeSync();
18492
19645
  const u = rt.runtime.update;
18493
- logLifecycle(PLUGIN_NAME21, "activate", {
19646
+ logLifecycle(PLUGIN_NAME22, "activate", {
18494
19647
  version: PLUGIN_VERSION,
18495
19648
  auto_check_enabled: u.auto_check_enabled,
18496
19649
  interval_hours: u.interval_hours,
@@ -18502,14 +19655,14 @@ var updateCheckerServer = async (ctx) => {
18502
19655
  repo_fallback: u.repo,
18503
19656
  config_source: "codeforge.json"
18504
19657
  });
18505
- await safeAsync(PLUGIN_NAME21, "opencode_version_check", async () => {
19658
+ await safeAsync(PLUGIN_NAME22, "opencode_version_check", async () => {
18506
19659
  const compat = loadCompatibility();
18507
19660
  const opencodeVer = detectOpencodeVersion();
18508
19661
  const verdict = compareOpencodeVersion({
18509
19662
  currentOpencodeVer: opencodeVer,
18510
19663
  compat
18511
19664
  });
18512
- safeWriteLog(PLUGIN_NAME21, {
19665
+ safeWriteLog(PLUGIN_NAME22, {
18513
19666
  level: "info",
18514
19667
  msg: "opencode_version_check",
18515
19668
  opencodeVer,
@@ -18526,14 +19679,14 @@ var updateCheckerServer = async (ctx) => {
18526
19679
  }
18527
19680
  });
18528
19681
  if (!u.auto_check_enabled) {
18529
- safeWriteLog(PLUGIN_NAME21, {
19682
+ safeWriteLog(PLUGIN_NAME22, {
18530
19683
  level: "info",
18531
19684
  msg: "auto-check disabled (codeforge.json update.auto_check_enabled=false)"
18532
19685
  });
18533
19686
  return {};
18534
19687
  }
18535
19688
  setImmediate(() => {
18536
- safeAsync(PLUGIN_NAME21, "checkAndMaybeUpdate", async () => {
19689
+ safeAsync(PLUGIN_NAME22, "checkAndMaybeUpdate", async () => {
18537
19690
  const local = readLocalVersion();
18538
19691
  let npmResult = null;
18539
19692
  try {
@@ -18544,7 +19697,7 @@ var updateCheckerServer = async (ctx) => {
18544
19697
  timeoutMs: 5000
18545
19698
  });
18546
19699
  } catch (e) {
18547
- safeWriteLog(PLUGIN_NAME21, {
19700
+ safeWriteLog(PLUGIN_NAME22, {
18548
19701
  level: "warn",
18549
19702
  msg: "npm_fetch_failed",
18550
19703
  error: e.message
@@ -18553,7 +19706,7 @@ var updateCheckerServer = async (ctx) => {
18553
19706
  return;
18554
19707
  }
18555
19708
  if (!npmResult) {
18556
- safeWriteLog(PLUGIN_NAME21, {
19709
+ safeWriteLog(PLUGIN_NAME22, {
18557
19710
  level: "info",
18558
19711
  msg: "npm_no_release",
18559
19712
  package: u.package,
@@ -18562,7 +19715,7 @@ var updateCheckerServer = async (ctx) => {
18562
19715
  return;
18563
19716
  }
18564
19717
  const hasUpdate = cmpVersion(local, npmResult.version) < 0;
18565
- safeWriteLog(PLUGIN_NAME21, {
19718
+ safeWriteLog(PLUGIN_NAME22, {
18566
19719
  level: "info",
18567
19720
  msg: "npm_check_result",
18568
19721
  local,
@@ -18577,10 +19730,10 @@ var updateCheckerServer = async (ctx) => {
18577
19730
  更新命令:npx ${u.package} install --global`);
18578
19731
  return;
18579
19732
  }
18580
- await safeAsync(PLUGIN_NAME21, "auto_install_bundle", async () => {
19733
+ await safeAsync(PLUGIN_NAME22, "auto_install_bundle", async () => {
18581
19734
  const target = getOpencodeBundlePath();
18582
19735
  if (!target) {
18583
- safeWriteLog(PLUGIN_NAME21, {
19736
+ safeWriteLog(PLUGIN_NAME22, {
18584
19737
  level: "warn",
18585
19738
  msg: "auto_install_skip",
18586
19739
  reason: "无法定位 opencode bundle 路径"
@@ -18599,7 +19752,7 @@ var updateCheckerServer = async (ctx) => {
18599
19752
  oldVersion: local,
18600
19753
  keepBackups: u.backup_keep
18601
19754
  });
18602
- safeWriteLog(PLUGIN_NAME21, {
19755
+ safeWriteLog(PLUGIN_NAME22, {
18603
19756
  level: "info",
18604
19757
  msg: "auto_install_success",
18605
19758
  local,
@@ -18623,14 +19776,14 @@ function detectOpencodeVersion() {
18623
19776
  }
18624
19777
  function getOpencodeBundlePath() {
18625
19778
  const candidates = [];
18626
- candidates.push(join16(homedir7(), ".config", "opencode", "codeforge", "index.js"));
19779
+ candidates.push(join18(homedir8(), ".config", "opencode", "codeforge", "index.js"));
18627
19780
  if (process.platform === "win32") {
18628
19781
  const appData = process.env["APPDATA"];
18629
19782
  if (appData)
18630
- candidates.push(join16(appData, "opencode", "codeforge", "index.js"));
19783
+ candidates.push(join18(appData, "opencode", "codeforge", "index.js"));
18631
19784
  const localAppData = process.env["LOCALAPPDATA"];
18632
19785
  if (localAppData)
18633
- candidates.push(join16(localAppData, "opencode", "codeforge", "index.js"));
19786
+ candidates.push(join18(localAppData, "opencode", "codeforge", "index.js"));
18634
19787
  }
18635
19788
  for (const c of candidates) {
18636
19789
  if (existsSync5(c))
@@ -18639,7 +19792,7 @@ function getOpencodeBundlePath() {
18639
19792
  return candidates[0] ?? null;
18640
19793
  }
18641
19794
  async function fallbackToGitHubReleases(ctx, u) {
18642
- await safeAsync(PLUGIN_NAME21, "github_fallback", async () => {
19795
+ await safeAsync(PLUGIN_NAME22, "github_fallback", async () => {
18643
19796
  const result = await checkUpdateOnce({
18644
19797
  repo: u.repo,
18645
19798
  intervalMs: u.interval_hours * 3600 * 1000
@@ -18649,7 +19802,7 @@ async function fallbackToGitHubReleases(ctx, u) {
18649
19802
  }
18650
19803
  async function reportLegacyResult(ctx, result, repo) {
18651
19804
  if (result.error && !result.fromCache) {
18652
- safeWriteLog(PLUGIN_NAME21, {
19805
+ safeWriteLog(PLUGIN_NAME22, {
18653
19806
  level: "warn",
18654
19807
  msg: `update check failed: ${result.error}`,
18655
19808
  ...result
@@ -18657,7 +19810,7 @@ async function reportLegacyResult(ctx, result, repo) {
18657
19810
  return;
18658
19811
  }
18659
19812
  if (!result.hasUpdate) {
18660
- safeWriteLog(PLUGIN_NAME21, {
19813
+ safeWriteLog(PLUGIN_NAME22, {
18661
19814
  level: "info",
18662
19815
  msg: `up-to-date (local=${result.local}, remote=${result.remote}${result.fromCache ? ", from_cache" : ""})`,
18663
19816
  ...result
@@ -18667,7 +19820,7 @@ async function reportLegacyResult(ctx, result, repo) {
18667
19820
  const updateCmd = `bunx --bun github:${repo} install`;
18668
19821
  const toast = `[CodeForge] 有新版本:${result.local} → ${result.remote}
18669
19822
  更新命令:${updateCmd}`;
18670
- safeWriteLog(PLUGIN_NAME21, {
19823
+ safeWriteLog(PLUGIN_NAME22, {
18671
19824
  level: "info",
18672
19825
  msg: "new_version_available_github_fallback",
18673
19826
  local: result.local,
@@ -18678,74 +19831,26 @@ async function reportLegacyResult(ctx, result, repo) {
18678
19831
  await postToast(ctx, toast);
18679
19832
  }
18680
19833
  async function postToast(ctx, message) {
18681
- await safeAsync(PLUGIN_NAME21, "client.app.log", async () => {
19834
+ await safeAsync(PLUGIN_NAME22, "client.app.log", async () => {
18682
19835
  await ctx.client.app.log({
18683
19836
  body: {
18684
- service: PLUGIN_NAME21,
19837
+ service: PLUGIN_NAME22,
18685
19838
  level: "info",
18686
19839
  message
18687
19840
  }
18688
19841
  });
18689
19842
  });
18690
19843
  }
18691
- var handler21 = updateCheckerServer;
19844
+ var handler22 = updateCheckerServer;
18692
19845
 
18693
19846
  // plugins/workflow-engine.ts
18694
19847
  init_opencode_plugin_helpers();
18695
- import * as path17 from "node:path";
19848
+ import * as path19 from "node:path";
18696
19849
 
18697
19850
  // lib/workflow-loader.ts
18698
- import { promises as fs12 } from "node:fs";
18699
- import * as path16 from "node:path";
19851
+ import { promises as fs14 } from "node:fs";
19852
+ import * as path18 from "node:path";
18700
19853
  import { z as z28 } from "zod";
18701
-
18702
- // node_modules/yaml/dist/index.js
18703
- var composer = require_composer();
18704
- var Document = require_Document();
18705
- var Schema = require_Schema();
18706
- var errors = require_errors();
18707
- var Alias = require_Alias();
18708
- var identity = require_identity();
18709
- var Pair = require_Pair();
18710
- var Scalar = require_Scalar();
18711
- var YAMLMap = require_YAMLMap();
18712
- var YAMLSeq = require_YAMLSeq();
18713
- var cst = require_cst();
18714
- var lexer = require_lexer();
18715
- var lineCounter = require_line_counter();
18716
- var parser = require_parser();
18717
- var publicApi = require_public_api();
18718
- var visit = require_visit();
18719
- var $Composer = composer.Composer;
18720
- var $Document = Document.Document;
18721
- var $Schema = Schema.Schema;
18722
- var $YAMLError = errors.YAMLError;
18723
- var $YAMLParseError = errors.YAMLParseError;
18724
- var $YAMLWarning = errors.YAMLWarning;
18725
- var $Alias = Alias.Alias;
18726
- var $isAlias = identity.isAlias;
18727
- var $isCollection = identity.isCollection;
18728
- var $isDocument = identity.isDocument;
18729
- var $isMap = identity.isMap;
18730
- var $isNode = identity.isNode;
18731
- var $isPair = identity.isPair;
18732
- var $isScalar = identity.isScalar;
18733
- var $isSeq = identity.isSeq;
18734
- var $Pair = Pair.Pair;
18735
- var $Scalar = Scalar.Scalar;
18736
- var $YAMLMap = YAMLMap.YAMLMap;
18737
- var $YAMLSeq = YAMLSeq.YAMLSeq;
18738
- var $Lexer = lexer.Lexer;
18739
- var $LineCounter = lineCounter.LineCounter;
18740
- var $Parser = parser.Parser;
18741
- var $parse = publicApi.parse;
18742
- var $parseAllDocuments = publicApi.parseAllDocuments;
18743
- var $parseDocument = publicApi.parseDocument;
18744
- var $stringify = publicApi.stringify;
18745
- var $visit = visit.visit;
18746
- var $visitAsync = visit.visitAsync;
18747
-
18748
- // lib/workflow-loader.ts
18749
19854
  var ActionSchema = z28.object({
18750
19855
  tool: z28.string().min(1, "action.tool 不能为空"),
18751
19856
  args: z28.record(z28.string(), z28.unknown()).optional().default({}),
@@ -18830,7 +19935,7 @@ function parseWorkflowYaml(yaml, sourcePath = "<inline>") {
18830
19935
  async function loadWorkflowFromFile(filePath) {
18831
19936
  let txt;
18832
19937
  try {
18833
- txt = await fs12.readFile(filePath, "utf8");
19938
+ txt = await fs14.readFile(filePath, "utf8");
18834
19939
  } catch (err) {
18835
19940
  return {
18836
19941
  ok: false,
@@ -18845,7 +19950,7 @@ async function loadWorkflowsFromDir(dir) {
18845
19950
  const failed = [];
18846
19951
  let entries;
18847
19952
  try {
18848
- entries = await fs12.readdir(dir);
19953
+ entries = await fs14.readdir(dir);
18849
19954
  } catch (err) {
18850
19955
  const e = err;
18851
19956
  if (e.code === "ENOENT")
@@ -18857,7 +19962,7 @@ async function loadWorkflowsFromDir(dir) {
18857
19962
  continue;
18858
19963
  if (!/\.ya?ml$/i.test(name))
18859
19964
  continue;
18860
- const full = path16.join(dir, name);
19965
+ const full = path18.join(dir, name);
18861
19966
  const r = await loadWorkflowFromFile(full);
18862
19967
  if (r.ok)
18863
19968
  loaded.push(r);
@@ -19060,7 +20165,7 @@ async function run(workflow, opts = {}) {
19060
20165
  } else {
19061
20166
  const output = await adapter.getLastAgentOutput();
19062
20167
  if (output === null || output === "") {
19063
- adapter.log("warn", `[${step.name}] getLastAgentOutput 返回空 → 降级 abort (ADR-0036)`);
20168
+ adapter.log("warn", `[${step.name}] getLastAgentOutput 返回空 → 降级 abort (ADR:on-decision-empty-output-abort)`);
19064
20169
  const last = results[results.length - 1];
19065
20170
  results[results.length - 1] = {
19066
20171
  ...last,
@@ -19192,9 +20297,9 @@ async function runStepAutoFeedback(step, adapter) {
19192
20297
  }
19193
20298
 
19194
20299
  // plugins/workflow-engine.ts
19195
- var PLUGIN_NAME22 = "workflow-engine";
19196
- logLifecycle(PLUGIN_NAME22, "import", {});
19197
- var fallbackLog2 = makePluginLogger(PLUGIN_NAME22);
20300
+ var PLUGIN_NAME23 = "workflow-engine";
20301
+ logLifecycle(PLUGIN_NAME23, "import", {});
20302
+ var fallbackLog2 = makePluginLogger(PLUGIN_NAME23);
19198
20303
  var _registry = null;
19199
20304
  async function loadRegistry(workflowsDir) {
19200
20305
  const { loaded, failed } = await loadWorkflowsFromDir(workflowsDir);
@@ -19211,35 +20316,35 @@ async function ensureRegistry(workflowsDir = "workflows") {
19211
20316
  }
19212
20317
  async function handleCommandInvoked(raw, workflowsDir = "workflows") {
19213
20318
  const ctx = raw ?? {};
19214
- const log12 = ctx.log ?? fallbackLog2;
20319
+ const log13 = ctx.log ?? fallbackLog2;
19215
20320
  const command = typeof ctx.command === "string" ? ctx.command : null;
19216
20321
  if (!command) {
19217
- log12.warn(`[${PLUGIN_NAME22}] command.invoked 缺 command 字段`, ctx);
20322
+ log13.warn(`[${PLUGIN_NAME23}] command.invoked 缺 command 字段`, ctx);
19218
20323
  return null;
19219
20324
  }
19220
20325
  const reg = await ensureRegistry(workflowsDir);
19221
20326
  if (reg.errors.length) {
19222
- log12.warn(`[${PLUGIN_NAME22}] 有 ${reg.errors.length} 个 workflow 加载失败`, reg.errors);
20327
+ log13.warn(`[${PLUGIN_NAME23}] 有 ${reg.errors.length} 个 workflow 加载失败`, reg.errors);
19223
20328
  }
19224
20329
  const wf = reg.workflows.find((w) => matchesTrigger(w, command));
19225
20330
  if (!wf) {
19226
- log12.info(`[${PLUGIN_NAME22}] no workflow matches "${command}"`);
20331
+ log13.info(`[${PLUGIN_NAME23}] no workflow matches "${command}"`);
19227
20332
  return null;
19228
20333
  }
19229
- log12.info(`[${PLUGIN_NAME22}] dispatch "${command}" → workflow "${wf.name}"`);
20334
+ log13.info(`[${PLUGIN_NAME23}] dispatch "${command}" → workflow "${wf.name}"`);
19230
20335
  try {
19231
20336
  const result = await run(wf, {
19232
20337
  mode: ctx.adapter ? "real" : "dry_run",
19233
20338
  autonomy: ctx.autonomy ?? "semi",
19234
20339
  adapter: ctx.adapter
19235
20340
  });
19236
- log12.info(`[${PLUGIN_NAME22}] workflow "${wf.name}" 完成 (${result.plan.mode})`, {
20341
+ log13.info(`[${PLUGIN_NAME23}] workflow "${wf.name}" 完成 (${result.plan.mode})`, {
19237
20342
  steps: result.plan.steps.length,
19238
20343
  results: result.results.length
19239
20344
  });
19240
20345
  return result;
19241
20346
  } catch (err) {
19242
- log12.error(`[${PLUGIN_NAME22}] workflow "${wf.name}" 执行失败`, {
20347
+ log13.error(`[${PLUGIN_NAME23}] workflow "${wf.name}" 执行失败`, {
19243
20348
  error: err instanceof Error ? err.message : String(err)
19244
20349
  });
19245
20350
  throw err;
@@ -19247,16 +20352,16 @@ async function handleCommandInvoked(raw, workflowsDir = "workflows") {
19247
20352
  }
19248
20353
  var workflowEngineServer = async (ctx) => {
19249
20354
  const directory = ctx.directory ?? process.cwd();
19250
- const workflowsDir = path17.join(directory, "workflows");
19251
- ensureRegistry(workflowsDir).catch((err) => fallbackLog2.warn(`[${PLUGIN_NAME22}] preload workflows failed`, {
20355
+ const workflowsDir = path19.join(directory, "workflows");
20356
+ ensureRegistry(workflowsDir).catch((err) => fallbackLog2.warn(`[${PLUGIN_NAME23}] preload workflows failed`, {
19252
20357
  error: err instanceof Error ? err.message : String(err)
19253
20358
  }));
19254
- logLifecycle(PLUGIN_NAME22, "activate", { directory, workflowsDir });
20359
+ logLifecycle(PLUGIN_NAME23, "activate", { directory, workflowsDir });
19255
20360
  return {
19256
20361
  "command.execute.before": async (input, output) => {
19257
- await safeAsync(PLUGIN_NAME22, "command.execute.before", async () => {
20362
+ await safeAsync(PLUGIN_NAME23, "command.execute.before", async () => {
19258
20363
  const cmd = input.command.startsWith("/") ? input.command : `/${input.command}`;
19259
- safeWriteLog(PLUGIN_NAME22, {
20364
+ safeWriteLog(PLUGIN_NAME23, {
19260
20365
  hook: "command.execute.before",
19261
20366
  command: cmd,
19262
20367
  sessionID: input.sessionID
@@ -19271,14 +20376,14 @@ var workflowEngineServer = async (ctx) => {
19271
20376
  });
19272
20377
  },
19273
20378
  "chat.message": async (input, output) => {
19274
- await safeAsync(PLUGIN_NAME22, "chat.message", async () => {
20379
+ await safeAsync(PLUGIN_NAME23, "chat.message", async () => {
19275
20380
  const text = extractUserText(output).trim();
19276
20381
  if (!text.startsWith("/"))
19277
20382
  return;
19278
20383
  const cmd = text.split(/\s+/)[0];
19279
20384
  if (!cmd)
19280
20385
  return;
19281
- safeWriteLog(PLUGIN_NAME22, {
20386
+ safeWriteLog(PLUGIN_NAME23, {
19282
20387
  hook: "chat.message",
19283
20388
  command: cmd,
19284
20389
  sessionID: input.sessionID
@@ -19288,11 +20393,11 @@ var workflowEngineServer = async (ctx) => {
19288
20393
  }
19289
20394
  };
19290
20395
  };
19291
- var handler22 = workflowEngineServer;
20396
+ var handler23 = workflowEngineServer;
19292
20397
 
19293
20398
  // src/index.ts
19294
20399
  var PLUGIN_ID = "codeforge";
19295
- var log12 = makePluginLogger(PLUGIN_ID);
20400
+ var log13 = makePluginLogger(PLUGIN_ID);
19296
20401
  logLifecycle(PLUGIN_ID, "import", { entry: "src/index.ts" });
19297
20402
  var HANDLERS = [
19298
20403
  { name: "agent-router", init: handler },
@@ -19306,17 +20411,18 @@ var HANDLERS = [
19306
20411
  { name: "kh-reminder", init: handler10 },
19307
20412
  { name: "memories-context", init: handler11 },
19308
20413
  { name: "model-fallback", init: handler12 },
19309
- { name: "pwsh-utf8", init: handler15 },
19310
- { name: "session-recovery", init: handler16 },
20414
+ { name: "parallel-tool-nudge", init: handler15 },
20415
+ { name: "pwsh-utf8", init: handler16 },
20416
+ { name: "session-recovery", init: handler17 },
19311
20417
  { name: "subtask-heartbeat", init: handler13 },
19312
- { name: "subtasks", init: handler17 },
20418
+ { name: "subtasks", init: handler18 },
19313
20419
  { name: "parallel-status", init: handler14 },
19314
- { name: "terminal-monitor", init: handler18 },
19315
- { name: "token-manager", init: handler19 },
20420
+ { name: "terminal-monitor", init: handler19 },
20421
+ { name: "token-manager", init: handler20 },
19316
20422
  { name: "tool-heartbeat", init: handler7 },
19317
- { name: "tool-policy", init: handler20 },
19318
- { name: "update-checker", init: handler21 },
19319
- { name: "workflow-engine", init: handler22 }
20423
+ { name: "tool-policy", init: handler21 },
20424
+ { name: "update-checker", init: handler22 },
20425
+ { name: "workflow-engine", init: handler23 }
19320
20426
  ];
19321
20427
  function makeSerialHook(hookName, fns) {
19322
20428
  return async (input, output) => {
@@ -19324,7 +20430,7 @@ function makeSerialHook(hookName, fns) {
19324
20430
  try {
19325
20431
  await fn(input, output);
19326
20432
  } catch (err) {
19327
- log12.warn(`[${PLUGIN_ID}] ${hookName} handler 异常(已隔离)`, {
20433
+ log13.warn(`[${PLUGIN_ID}] ${hookName} handler 异常(已隔离)`, {
19328
20434
  error: err instanceof Error ? err.message : String(err)
19329
20435
  });
19330
20436
  }
@@ -19337,7 +20443,7 @@ function createCodeforgeServer(opts) {
19337
20443
  const yieldResult = shouldYieldToLocalPlugin({ directory: input.directory });
19338
20444
  if (yieldResult.yield) {
19339
20445
  const msg = formatYieldLog(yieldResult);
19340
- log12.info(msg, { reason: yieldResult.reason, markerPath: yieldResult.markerPath });
20446
+ log13.info(msg, { reason: yieldResult.reason, markerPath: yieldResult.markerPath });
19341
20447
  logLifecycle(PLUGIN_ID, "activate", {
19342
20448
  yield_to_local: true,
19343
20449
  yield_reason: yieldResult.reason,
@@ -19355,7 +20461,7 @@ function createCodeforgeServer(opts) {
19355
20461
  if (r.status === "fulfilled" && r.value && typeof r.value === "object") {
19356
20462
  hooksList.push(r.value);
19357
20463
  } else if (r.status === "rejected") {
19358
- log12.warn(`[${PLUGIN_ID}] handler ${HANDLERS[i].name} init failed (隔离,其他 handler 继续)`, { error: r.reason instanceof Error ? r.reason.message : String(r.reason) });
20464
+ log13.warn(`[${PLUGIN_ID}] handler ${HANDLERS[i].name} init failed (隔离,其他 handler 继续)`, { error: r.reason instanceof Error ? r.reason.message : String(r.reason) });
19359
20465
  }
19360
20466
  });
19361
20467
  logLifecycle(PLUGIN_ID, "activate", {
@@ -19369,6 +20475,7 @@ function createCodeforgeServer(opts) {
19369
20475
  const chatParamsBucket = [];
19370
20476
  const toolExecuteBeforeBucket = [];
19371
20477
  const chatMessagesTransformBucket = [];
20478
+ const chatSystemTransformBucket = [];
19372
20479
  const eventBucket = [];
19373
20480
  const toolMerged = {};
19374
20481
  for (const h of hooksList) {
@@ -19383,6 +20490,9 @@ function createCodeforgeServer(opts) {
19383
20490
  if (h["experimental.chat.messages.transform"]) {
19384
20491
  chatMessagesTransformBucket.push(h["experimental.chat.messages.transform"]);
19385
20492
  }
20493
+ if (h["experimental.chat.system.transform"]) {
20494
+ chatSystemTransformBucket.push(h["experimental.chat.system.transform"]);
20495
+ }
19386
20496
  if (h.event)
19387
20497
  eventBucket.push(h.event);
19388
20498
  if (h.tool)
@@ -19394,12 +20504,13 @@ function createCodeforgeServer(opts) {
19394
20504
  "chat.params": makeSerialHook("chat.params", chatParamsBucket),
19395
20505
  "tool.execute.before": makeSerialHook("tool.execute.before", toolExecuteBeforeBucket),
19396
20506
  "experimental.chat.messages.transform": makeSerialHook("experimental.chat.messages.transform", chatMessagesTransformBucket),
20507
+ "experimental.chat.system.transform": makeSerialHook("experimental.chat.system.transform", chatSystemTransformBucket),
19397
20508
  event: async (envelope) => {
19398
20509
  await Promise.all(eventBucket.map(async (fn) => {
19399
20510
  try {
19400
20511
  await fn(envelope);
19401
20512
  } catch (err) {
19402
- log12.warn(`[${PLUGIN_ID}] event handler 异常(已隔离)`, {
20513
+ log13.warn(`[${PLUGIN_ID}] event handler 异常(已隔离)`, {
19403
20514
  error: err instanceof Error ? err.message : String(err)
19404
20515
  });
19405
20516
  }