@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/README.md +45 -3
- package/agents/codeforge.md +5 -4
- package/agents/coder-deep.md +3 -0
- package/agents/coder-quick.md +3 -0
- package/agents/coder.md +3 -0
- package/agents/planner.md +12 -8
- package/agents/reviewer.md +3 -0
- package/assets/adr-init/.github/pull_request_template.md +22 -0
- package/assets/adr-init/docs/adr/README.md +105 -0
- package/assets/adr-init/docs/adr/template.md +83 -0
- package/assets/adr-init/githooks/pre-commit.sh +46 -0
- package/assets/adr-init/githooks/pre-push.sh +23 -0
- package/assets/adr-init/scripts/adr-check.mjs +428 -0
- package/assets/adr-init/scripts/adr-index-sync.mjs +151 -0
- package/bin/codeforge.mjs +96 -3
- package/commands/adr-init.md +67 -0
- package/dist/adr-init.js +207 -0
- package/dist/index.js +1691 -580
- package/package.json +16 -5
- package/scripts/sync-agent-models.mjs +1 -1
- package/workflows/bugfix.yaml +3 -3
- package/workflows/feature-dev.yaml +3 -3
- package/workflows/parallel-explore.yaml +1 -1
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,
|
|
1038
|
-
const ctrl = callVisitor(key, node, visitor,
|
|
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,
|
|
1041
|
-
return visit_(key, ctrl, visitor,
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
1059
|
-
const ck = visit_("key", node.key, visitor,
|
|
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,
|
|
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,
|
|
1086
|
-
const ctrl = await callVisitor(key, node, visitor,
|
|
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,
|
|
1089
|
-
return visitAsync_(key, ctrl, visitor,
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
1107
|
-
const ck = await visitAsync_("key", node.key, visitor,
|
|
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,
|
|
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,
|
|
1139
|
+
function callVisitor(key, node, visitor, path13) {
|
|
1140
1140
|
if (typeof visitor === "function")
|
|
1141
|
-
return visitor(key, node,
|
|
1141
|
+
return visitor(key, node, path13);
|
|
1142
1142
|
if (identity.isMap(node))
|
|
1143
|
-
return visitor.Map?.(key, node,
|
|
1143
|
+
return visitor.Map?.(key, node, path13);
|
|
1144
1144
|
if (identity.isSeq(node))
|
|
1145
|
-
return visitor.Seq?.(key, node,
|
|
1145
|
+
return visitor.Seq?.(key, node, path13);
|
|
1146
1146
|
if (identity.isPair(node))
|
|
1147
|
-
return visitor.Pair?.(key, node,
|
|
1147
|
+
return visitor.Pair?.(key, node, path13);
|
|
1148
1148
|
if (identity.isScalar(node))
|
|
1149
|
-
return visitor.Scalar?.(key, node,
|
|
1149
|
+
return visitor.Scalar?.(key, node, path13);
|
|
1150
1150
|
if (identity.isAlias(node))
|
|
1151
|
-
return visitor.Alias?.(key, node,
|
|
1151
|
+
return visitor.Alias?.(key, node, path13);
|
|
1152
1152
|
return;
|
|
1153
1153
|
}
|
|
1154
|
-
function replaceNode(key,
|
|
1155
|
-
const parent =
|
|
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,
|
|
1714
|
+
function collectionFromPath(schema, path13, value) {
|
|
1715
1715
|
let v = value;
|
|
1716
|
-
for (let i =
|
|
1717
|
-
const k =
|
|
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 = (
|
|
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(
|
|
1758
|
-
if (isEmptyPath(
|
|
1757
|
+
addIn(path13, value) {
|
|
1758
|
+
if (isEmptyPath(path13))
|
|
1759
1759
|
this.add(value);
|
|
1760
1760
|
else {
|
|
1761
|
-
const [key, ...rest] =
|
|
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(
|
|
1772
|
-
const [key, ...rest] =
|
|
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(
|
|
1782
|
-
const [key, ...rest] =
|
|
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(
|
|
1798
|
-
const [key, ...rest] =
|
|
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(
|
|
1805
|
-
const [key, ...rest] =
|
|
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
|
|
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
|
-
|
|
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(
|
|
4198
|
+
addIn(path13, value) {
|
|
4199
4199
|
if (assertCollection(this.contents))
|
|
4200
|
-
this.contents.addIn(
|
|
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(
|
|
4250
|
-
if (Collection.isEmptyPath(
|
|
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(
|
|
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(
|
|
4262
|
-
if (Collection.isEmptyPath(
|
|
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(
|
|
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(
|
|
4270
|
-
if (Collection.isEmptyPath(
|
|
4269
|
+
hasIn(path13) {
|
|
4270
|
+
if (Collection.isEmptyPath(path13))
|
|
4271
4271
|
return this.contents !== undefined;
|
|
4272
|
-
return identity.isCollection(this.contents) ? this.contents.hasIn(
|
|
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(
|
|
4282
|
-
if (Collection.isEmptyPath(
|
|
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(
|
|
4285
|
+
this.contents = Collection.collectionFromPath(this.schema, Array.from(path13), value);
|
|
4286
4286
|
} else if (assertCollection(this.contents)) {
|
|
4287
|
-
this.contents.setIn(
|
|
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,
|
|
6182
|
+
visit.itemAtPath = (cst, path13) => {
|
|
6183
6183
|
let item = cst;
|
|
6184
|
-
for (const [field, index] of
|
|
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,
|
|
6194
|
-
const parent = visit.itemAtPath(cst,
|
|
6195
|
-
const field =
|
|
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(
|
|
6202
|
-
let ctrl = visitor(item,
|
|
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(
|
|
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,
|
|
6220
|
+
ctrl = ctrl(item, path13);
|
|
6221
6221
|
}
|
|
6222
6222
|
}
|
|
6223
|
-
return typeof ctrl === "function" ? ctrl(item,
|
|
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
|
|
7492
|
+
const fs10 = this.flowScalar(this.type);
|
|
7493
7493
|
if (atNextItem || it.value) {
|
|
7494
|
-
map.items.push({ start, key:
|
|
7494
|
+
map.items.push({ start, key: fs10, sep: [] });
|
|
7495
7495
|
this.onKeyLine = true;
|
|
7496
7496
|
} else if (it.sep) {
|
|
7497
|
-
this.stack.push(
|
|
7497
|
+
this.stack.push(fs10);
|
|
7498
7498
|
} else {
|
|
7499
|
-
Object.assign(it, { key:
|
|
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
|
|
7627
|
+
const fs10 = this.flowScalar(this.type);
|
|
7628
7628
|
if (!it || it.value)
|
|
7629
|
-
fc.items.push({ start: [], key:
|
|
7629
|
+
fc.items.push({ start: [], key: fs10, sep: [] });
|
|
7630
7630
|
else if (it.sep)
|
|
7631
|
-
this.stack.push(
|
|
7631
|
+
this.stack.push(fs10);
|
|
7632
7632
|
else
|
|
7633
|
-
Object.assign(it, { key:
|
|
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
|
|
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) =>
|
|
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((
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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-
|
|
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-
|
|
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-
|
|
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
|
|
14834
|
-
import * as
|
|
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 ??
|
|
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
|
|
14866
|
+
return path12.join(cfg.projectRoot, ".codeforge", "memories.json");
|
|
14850
14867
|
}
|
|
14851
|
-
return
|
|
14868
|
+
return path12.join(cfg.homeDir, ".codeforge", "memories.json");
|
|
14852
14869
|
}
|
|
14853
14870
|
async function readBank(p) {
|
|
14854
14871
|
try {
|
|
14855
|
-
const raw = await
|
|
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
|
|
14882
|
+
await fs9.mkdir(path12.dirname(p), { recursive: true });
|
|
14866
14883
|
const tmp = `${p}.tmp`;
|
|
14867
|
-
await
|
|
14868
|
-
await
|
|
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/
|
|
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 = "
|
|
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
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
|
16217
|
+
import { promises as fs10 } from "node:fs";
|
|
15910
16218
|
init_runtime_paths();
|
|
15911
|
-
import * as
|
|
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
|
|
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
|
|
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 =
|
|
16239
|
+
const file = path13.join(dir, e.name);
|
|
15932
16240
|
const id = e.name.replace(/\.jsonl$/, "");
|
|
15933
16241
|
try {
|
|
15934
|
-
const stat = await
|
|
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 =
|
|
15959
|
-
return opts.sessions_dir ?
|
|
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
|
|
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
|
|
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
|
|
16226
|
-
logLifecycle(
|
|
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?.(`[${
|
|
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?.(`[${
|
|
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
|
|
16587
|
+
var log9 = makePluginLogger(PLUGIN_NAME17);
|
|
16280
16588
|
var _lastInjection = null;
|
|
16281
16589
|
var sessionRecoveryServer = async (ctx) => {
|
|
16282
|
-
logLifecycle(
|
|
16590
|
+
logLifecycle(PLUGIN_NAME17, "activate", { directory: ctx.directory });
|
|
16283
16591
|
return {
|
|
16284
16592
|
event: async ({ event }) => {
|
|
16285
|
-
await safeAsync(
|
|
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:
|
|
16603
|
+
log: log9,
|
|
16296
16604
|
injectRecovery: (inj) => {
|
|
16297
16605
|
_lastInjection = inj;
|
|
16298
16606
|
}
|
|
16299
16607
|
});
|
|
16300
|
-
safeWriteLog(
|
|
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
|
-
|
|
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
|
|
16623
|
+
var handler17 = sessionRecoveryServer;
|
|
16316
16624
|
|
|
16317
16625
|
// plugins/subtasks.ts
|
|
16318
|
-
import { promises as
|
|
16319
|
-
import * as
|
|
16626
|
+
import { promises as fs12 } from "node:fs";
|
|
16627
|
+
import * as path15 from "node:path";
|
|
16320
16628
|
|
|
16321
|
-
// lib/
|
|
16322
|
-
|
|
16323
|
-
|
|
16324
|
-
|
|
16325
|
-
|
|
16326
|
-
|
|
16327
|
-
|
|
16328
|
-
|
|
16329
|
-
|
|
16330
|
-
|
|
16331
|
-
|
|
16332
|
-
|
|
16333
|
-
|
|
16334
|
-
|
|
16335
|
-
|
|
16336
|
-
|
|
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
|
-
|
|
16339
|
-
|
|
16340
|
-
|
|
16341
|
-
|
|
16342
|
-
|
|
16343
|
-
|
|
16344
|
-
|
|
16345
|
-
|
|
16346
|
-
|
|
16347
|
-
|
|
16348
|
-
|
|
16349
|
-
|
|
16350
|
-
|
|
16351
|
-
|
|
16352
|
-
|
|
16353
|
-
|
|
16354
|
-
|
|
16355
|
-
|
|
16356
|
-
|
|
16357
|
-
|
|
16358
|
-
|
|
16359
|
-
|
|
16360
|
-
|
|
16361
|
-
|
|
16362
|
-
|
|
16363
|
-
|
|
16364
|
-
|
|
16365
|
-
|
|
16366
|
-
|
|
16367
|
-
|
|
16368
|
-
|
|
16369
|
-
|
|
16370
|
-
|
|
16371
|
-
|
|
16372
|
-
|
|
16373
|
-
|
|
16374
|
-
|
|
16375
|
-
|
|
16376
|
-
|
|
16377
|
-
|
|
16378
|
-
|
|
16379
|
-
|
|
16380
|
-
|
|
16381
|
-
|
|
16382
|
-
|
|
16383
|
-
|
|
16384
|
-
|
|
16385
|
-
|
|
16386
|
-
|
|
16387
|
-
|
|
16388
|
-
|
|
16389
|
-
|
|
16390
|
-
|
|
16391
|
-
|
|
16392
|
-
|
|
16393
|
-
|
|
16394
|
-
|
|
16395
|
-
|
|
16396
|
-
|
|
16397
|
-
|
|
16398
|
-
|
|
16399
|
-
|
|
16400
|
-
|
|
16401
|
-
|
|
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 抛错:${
|
|
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:
|
|
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
|
-
|
|
16433
|
-
error:
|
|
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
|
|
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
|
|
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 失败:${
|
|
17506
|
+
summary: `session.create 失败:${describe6(created.error) || "no id"}`,
|
|
16599
17507
|
status: "failed",
|
|
16600
|
-
error:
|
|
17508
|
+
error: describe6(created.error) || "session.create 无 id"
|
|
16601
17509
|
};
|
|
16602
17510
|
}
|
|
16603
17511
|
childSessionId = created.data.id;
|
|
16604
|
-
|
|
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 失败:${
|
|
17542
|
+
summary: `session.prompt 失败:${describe6(r.error)}`,
|
|
16635
17543
|
status: "failed",
|
|
16636
|
-
error:
|
|
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
|
-
|
|
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 ?
|
|
17574
|
+
error: llmError ? describe6(llmError) : undefined
|
|
16667
17575
|
};
|
|
16668
17576
|
} catch (err) {
|
|
16669
17577
|
return {
|
|
16670
17578
|
ok: false,
|
|
16671
|
-
summary: `runner 抛错:${
|
|
17579
|
+
summary: `runner 抛错:${describe6(err)}`,
|
|
16672
17580
|
status: "failed",
|
|
16673
|
-
error:
|
|
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
|
-
|
|
16684
|
-
error:
|
|
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((
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
17705
|
+
const log10 = opts.log ?? (() => {});
|
|
16798
17706
|
if (!client?.session) {
|
|
16799
|
-
|
|
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
|
-
|
|
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
|
-
|
|
17733
|
+
log10("warn", "[sendParentNotice] promptAsync 返回 error", { error: res.error });
|
|
16826
17734
|
return false;
|
|
16827
17735
|
}
|
|
16828
17736
|
return true;
|
|
16829
17737
|
} catch (err) {
|
|
16830
|
-
|
|
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
|
|
17975
|
+
var PLUGIN_NAME18 = "subtasks";
|
|
16841
17976
|
function getLogFile(root = process.cwd()) {
|
|
16842
|
-
return
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
18138
|
+
log10?.error(msg, data);
|
|
16939
18139
|
else if (lvl === "warn")
|
|
16940
|
-
|
|
18140
|
+
log10?.warn(msg, data);
|
|
16941
18141
|
else
|
|
16942
|
-
|
|
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
|
-
|
|
18186
|
+
log10?.error(msg, data);
|
|
16968
18187
|
else if (lvl === "warn")
|
|
16969
|
-
|
|
18188
|
+
log10?.warn(msg, data);
|
|
16970
18189
|
else
|
|
16971
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
18275
|
+
plugin: PLUGIN_NAME18,
|
|
17037
18276
|
msg,
|
|
17038
18277
|
data
|
|
17039
18278
|
}) + `
|
|
17040
18279
|
`;
|
|
17041
18280
|
try {
|
|
17042
18281
|
const logFile = getLogFile();
|
|
17043
|
-
await
|
|
17044
|
-
await
|
|
18282
|
+
await fs12.mkdir(path15.dirname(logFile), { recursive: true });
|
|
18283
|
+
await fs12.appendFile(logFile, line, "utf8");
|
|
17045
18284
|
} catch {}
|
|
17046
18285
|
}
|
|
17047
|
-
logLifecycle(
|
|
18286
|
+
logLifecycle(PLUGIN_NAME18, "import");
|
|
17048
18287
|
var subtasksServer = async (ctx) => {
|
|
17049
|
-
const
|
|
18288
|
+
const log10 = makePluginLogger(PLUGIN_NAME18);
|
|
17050
18289
|
const client = ctx?.client ?? undefined;
|
|
17051
|
-
logLifecycle(
|
|
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:
|
|
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
|
-
|
|
18357
|
+
log10.error(msg, data);
|
|
17083
18358
|
else if (lvl === "warn")
|
|
17084
|
-
|
|
18359
|
+
log10.warn(msg, data);
|
|
17085
18360
|
else
|
|
17086
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
17124
|
-
logLifecycle(
|
|
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 =
|
|
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
|
|
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(`\`${
|
|
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}: ${
|
|
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
|
|
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(
|
|
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(
|
|
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:
|
|
18701
|
+
log: log10,
|
|
17426
18702
|
notifyAgent: (msg) => {
|
|
17427
18703
|
_lastNotification = msg;
|
|
17428
18704
|
}
|
|
17429
18705
|
});
|
|
17430
|
-
safeWriteLog(
|
|
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
|
-
|
|
18714
|
+
log10.info(`[${PLUGIN_NAME19}] 反馈 ${r.notification.findings.length} 条 → ${r.notification.summary}`);
|
|
17439
18715
|
}
|
|
17440
18716
|
});
|
|
17441
18717
|
}
|
|
17442
18718
|
};
|
|
17443
18719
|
};
|
|
17444
|
-
var
|
|
18720
|
+
var handler19 = terminalMonitorServer;
|
|
17445
18721
|
|
|
17446
18722
|
// plugins/token-manager.ts
|
|
17447
18723
|
init_opencode_plugin_helpers();
|
|
17448
|
-
var
|
|
17449
|
-
logLifecycle(
|
|
17450
|
-
async function handleMessageBefore(raw,
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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(
|
|
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(
|
|
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 },
|
|
18778
|
+
const r = await handleMessageBefore({ messages: flat }, log11, { threshold });
|
|
17503
18779
|
if (!r)
|
|
17504
18780
|
return;
|
|
17505
|
-
safeWriteLog(
|
|
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
|
-
|
|
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
|
|
18798
|
+
var handler20 = tokenManagerServer;
|
|
17523
18799
|
|
|
17524
18800
|
// plugins/tool-policy.ts
|
|
17525
18801
|
init_opencode_plugin_helpers();
|
|
17526
|
-
import { promises as
|
|
17527
|
-
import * as
|
|
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
|
|
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 =
|
|
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
|
|
17766
|
-
logLifecycle(
|
|
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-
|
|
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-
|
|
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 =
|
|
18982
|
+
var POLICY_PATH = path17.join(".codeforge", "policy.json");
|
|
17830
18983
|
async function loadPolicy(root = process.cwd()) {
|
|
17831
|
-
const file =
|
|
18984
|
+
const file = path17.join(root, POLICY_PATH);
|
|
17832
18985
|
try {
|
|
17833
|
-
const raw = await
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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(
|
|
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(
|
|
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,
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
17943
|
-
import { join as
|
|
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
|
|
17953
|
-
readdirSync,
|
|
19105
|
+
readFileSync as readFileSync5,
|
|
19106
|
+
readdirSync as readdirSync2,
|
|
17954
19107
|
renameSync,
|
|
17955
|
-
statSync as
|
|
19108
|
+
statSync as statSync4,
|
|
17956
19109
|
unlinkSync,
|
|
17957
19110
|
writeFileSync as writeFileSync2
|
|
17958
19111
|
} from "node:fs";
|
|
17959
|
-
import { homedir as
|
|
17960
|
-
import { dirname as dirname7, join as
|
|
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.
|
|
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(
|
|
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"] ??
|
|
19218
|
+
return process.env["CODEFORGE_CACHE_DIR"] ?? join17(homedir7(), ".cache", "codeforge");
|
|
18066
19219
|
}
|
|
18067
19220
|
function defaultCacheFile() {
|
|
18068
|
-
return
|
|
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 =
|
|
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((
|
|
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(
|
|
19282
|
+
getJsonWithRedirect(next, hopsLeft - 1).then(resolve13, reject);
|
|
18130
19283
|
return;
|
|
18131
19284
|
}
|
|
18132
19285
|
if (status === 404) {
|
|
18133
19286
|
res.resume();
|
|
18134
|
-
|
|
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", () =>
|
|
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((
|
|
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
|
-
|
|
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", () =>
|
|
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(
|
|
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 =
|
|
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 =
|
|
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(
|
|
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((
|
|
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(
|
|
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", () =>
|
|
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 =
|
|
18384
|
-
const full =
|
|
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 =
|
|
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 =
|
|
19559
|
+
file = join17(root, "compatibility.json");
|
|
18407
19560
|
}
|
|
18408
19561
|
if (!existsSync4(file))
|
|
18409
19562
|
return null;
|
|
18410
|
-
const raw =
|
|
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
|
|
19624
|
+
var PLUGIN_NAME22 = "update-checker";
|
|
18472
19625
|
var PLUGIN_VERSION = "2.0.0";
|
|
18473
|
-
logLifecycle(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
19733
|
+
await safeAsync(PLUGIN_NAME22, "auto_install_bundle", async () => {
|
|
18581
19734
|
const target = getOpencodeBundlePath();
|
|
18582
19735
|
if (!target) {
|
|
18583
|
-
safeWriteLog(
|
|
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(
|
|
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(
|
|
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(
|
|
19783
|
+
candidates.push(join18(appData, "opencode", "codeforge", "index.js"));
|
|
18631
19784
|
const localAppData = process.env["LOCALAPPDATA"];
|
|
18632
19785
|
if (localAppData)
|
|
18633
|
-
candidates.push(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
19834
|
+
await safeAsync(PLUGIN_NAME22, "client.app.log", async () => {
|
|
18682
19835
|
await ctx.client.app.log({
|
|
18683
19836
|
body: {
|
|
18684
|
-
service:
|
|
19837
|
+
service: PLUGIN_NAME22,
|
|
18685
19838
|
level: "info",
|
|
18686
19839
|
message
|
|
18687
19840
|
}
|
|
18688
19841
|
});
|
|
18689
19842
|
});
|
|
18690
19843
|
}
|
|
18691
|
-
var
|
|
19844
|
+
var handler22 = updateCheckerServer;
|
|
18692
19845
|
|
|
18693
19846
|
// plugins/workflow-engine.ts
|
|
18694
19847
|
init_opencode_plugin_helpers();
|
|
18695
|
-
import * as
|
|
19848
|
+
import * as path19 from "node:path";
|
|
18696
19849
|
|
|
18697
19850
|
// lib/workflow-loader.ts
|
|
18698
|
-
import { promises as
|
|
18699
|
-
import * as
|
|
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
|
|
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
|
|
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 =
|
|
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-
|
|
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
|
|
19196
|
-
logLifecycle(
|
|
19197
|
-
var fallbackLog2 = makePluginLogger(
|
|
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
|
|
20319
|
+
const log13 = ctx.log ?? fallbackLog2;
|
|
19215
20320
|
const command = typeof ctx.command === "string" ? ctx.command : null;
|
|
19216
20321
|
if (!command) {
|
|
19217
|
-
|
|
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
|
-
|
|
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
|
-
|
|
20331
|
+
log13.info(`[${PLUGIN_NAME23}] no workflow matches "${command}"`);
|
|
19227
20332
|
return null;
|
|
19228
20333
|
}
|
|
19229
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
19251
|
-
ensureRegistry(workflowsDir).catch((err) => fallbackLog2.warn(`[${
|
|
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(
|
|
20359
|
+
logLifecycle(PLUGIN_NAME23, "activate", { directory, workflowsDir });
|
|
19255
20360
|
return {
|
|
19256
20361
|
"command.execute.before": async (input, output) => {
|
|
19257
|
-
await safeAsync(
|
|
20362
|
+
await safeAsync(PLUGIN_NAME23, "command.execute.before", async () => {
|
|
19258
20363
|
const cmd = input.command.startsWith("/") ? input.command : `/${input.command}`;
|
|
19259
|
-
safeWriteLog(
|
|
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(
|
|
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(
|
|
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
|
|
20396
|
+
var handler23 = workflowEngineServer;
|
|
19292
20397
|
|
|
19293
20398
|
// src/index.ts
|
|
19294
20399
|
var PLUGIN_ID = "codeforge";
|
|
19295
|
-
var
|
|
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: "
|
|
19310
|
-
{ name: "
|
|
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:
|
|
20418
|
+
{ name: "subtasks", init: handler18 },
|
|
19313
20419
|
{ name: "parallel-status", init: handler14 },
|
|
19314
|
-
{ name: "terminal-monitor", init:
|
|
19315
|
-
{ name: "token-manager", init:
|
|
20420
|
+
{ name: "terminal-monitor", init: handler19 },
|
|
20421
|
+
{ name: "token-manager", init: handler20 },
|
|
19316
20422
|
{ name: "tool-heartbeat", init: handler7 },
|
|
19317
|
-
{ name: "tool-policy", init:
|
|
19318
|
-
{ name: "update-checker", init:
|
|
19319
|
-
{ name: "workflow-engine", init:
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
20513
|
+
log13.warn(`[${PLUGIN_ID}] event handler 异常(已隔离)`, {
|
|
19403
20514
|
error: err instanceof Error ? err.message : String(err)
|
|
19404
20515
|
});
|
|
19405
20516
|
}
|