@andyqiu/codeforge 0.3.13 → 0.3.14
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 +20 -0
- package/agents/codeforge.md +3 -0
- package/agents/coder.md +13 -6
- package/agents/discover.md +55 -6
- package/agents/planner.md +11 -6
- package/assets/adr-init/scripts/adr-check.mjs +2 -2
- package/dist/index.js +1003 -549
- package/package.json +3 -1
- package/scripts/postinstall.mjs +125 -0
- package/skills/weighted-dimensions/SKILL.md +7 -0
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, path12) {
|
|
1038
|
+
const ctrl = callVisitor(key, node, visitor, path12);
|
|
1039
1039
|
if (identity.isNode(ctrl) || identity.isPair(ctrl)) {
|
|
1040
|
-
replaceNode(key,
|
|
1041
|
-
return visit_(key, ctrl, visitor,
|
|
1040
|
+
replaceNode(key, path12, ctrl);
|
|
1041
|
+
return visit_(key, ctrl, visitor, path12);
|
|
1042
1042
|
}
|
|
1043
1043
|
if (typeof ctrl !== "symbol") {
|
|
1044
1044
|
if (identity.isCollection(node)) {
|
|
1045
|
-
|
|
1045
|
+
path12 = Object.freeze(path12.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, path12);
|
|
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
|
+
path12 = Object.freeze(path12.concat(node));
|
|
1059
|
+
const ck = visit_("key", node.key, visitor, path12);
|
|
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, path12);
|
|
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, path12) {
|
|
1086
|
+
const ctrl = await callVisitor(key, node, visitor, path12);
|
|
1087
1087
|
if (identity.isNode(ctrl) || identity.isPair(ctrl)) {
|
|
1088
|
-
replaceNode(key,
|
|
1089
|
-
return visitAsync_(key, ctrl, visitor,
|
|
1088
|
+
replaceNode(key, path12, ctrl);
|
|
1089
|
+
return visitAsync_(key, ctrl, visitor, path12);
|
|
1090
1090
|
}
|
|
1091
1091
|
if (typeof ctrl !== "symbol") {
|
|
1092
1092
|
if (identity.isCollection(node)) {
|
|
1093
|
-
|
|
1093
|
+
path12 = Object.freeze(path12.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, path12);
|
|
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
|
+
path12 = Object.freeze(path12.concat(node));
|
|
1107
|
+
const ck = await visitAsync_("key", node.key, visitor, path12);
|
|
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, path12);
|
|
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, path12) {
|
|
1140
1140
|
if (typeof visitor === "function")
|
|
1141
|
-
return visitor(key, node,
|
|
1141
|
+
return visitor(key, node, path12);
|
|
1142
1142
|
if (identity.isMap(node))
|
|
1143
|
-
return visitor.Map?.(key, node,
|
|
1143
|
+
return visitor.Map?.(key, node, path12);
|
|
1144
1144
|
if (identity.isSeq(node))
|
|
1145
|
-
return visitor.Seq?.(key, node,
|
|
1145
|
+
return visitor.Seq?.(key, node, path12);
|
|
1146
1146
|
if (identity.isPair(node))
|
|
1147
|
-
return visitor.Pair?.(key, node,
|
|
1147
|
+
return visitor.Pair?.(key, node, path12);
|
|
1148
1148
|
if (identity.isScalar(node))
|
|
1149
|
-
return visitor.Scalar?.(key, node,
|
|
1149
|
+
return visitor.Scalar?.(key, node, path12);
|
|
1150
1150
|
if (identity.isAlias(node))
|
|
1151
|
-
return visitor.Alias?.(key, node,
|
|
1151
|
+
return visitor.Alias?.(key, node, path12);
|
|
1152
1152
|
return;
|
|
1153
1153
|
}
|
|
1154
|
-
function replaceNode(key,
|
|
1155
|
-
const parent =
|
|
1154
|
+
function replaceNode(key, path12, node) {
|
|
1155
|
+
const parent = path12[path12.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, path12, value) {
|
|
1715
1715
|
let v = value;
|
|
1716
|
-
for (let i =
|
|
1717
|
-
const k =
|
|
1716
|
+
for (let i = path12.length - 1;i >= 0; --i) {
|
|
1717
|
+
const k = path12[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 = (path12) => path12 == null || typeof path12 === "object" && !!path12[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(path12, value) {
|
|
1758
|
+
if (isEmptyPath(path12))
|
|
1759
1759
|
this.add(value);
|
|
1760
1760
|
else {
|
|
1761
|
-
const [key, ...rest] =
|
|
1761
|
+
const [key, ...rest] = path12;
|
|
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(path12) {
|
|
1772
|
+
const [key, ...rest] = path12;
|
|
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(path12, keepScalar) {
|
|
1782
|
+
const [key, ...rest] = path12;
|
|
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(path12) {
|
|
1798
|
+
const [key, ...rest] = path12;
|
|
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(path12, value) {
|
|
1805
|
+
const [key, ...rest] = path12;
|
|
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 log6 = 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
|
+
log6.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(path12, value) {
|
|
4199
4199
|
if (assertCollection(this.contents))
|
|
4200
|
-
this.contents.addIn(
|
|
4200
|
+
this.contents.addIn(path12, 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(path12) {
|
|
4250
|
+
if (Collection.isEmptyPath(path12)) {
|
|
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(path12) : 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(path12, keepScalar) {
|
|
4262
|
+
if (Collection.isEmptyPath(path12))
|
|
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(path12, 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(path12) {
|
|
4270
|
+
if (Collection.isEmptyPath(path12))
|
|
4271
4271
|
return this.contents !== undefined;
|
|
4272
|
-
return identity.isCollection(this.contents) ? this.contents.hasIn(
|
|
4272
|
+
return identity.isCollection(this.contents) ? this.contents.hasIn(path12) : 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(path12, value) {
|
|
4282
|
+
if (Collection.isEmptyPath(path12)) {
|
|
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(path12), value);
|
|
4286
4286
|
} else if (assertCollection(this.contents)) {
|
|
4287
|
-
this.contents.setIn(
|
|
4287
|
+
this.contents.setIn(path12, 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, path12) => {
|
|
6183
6183
|
let item = cst;
|
|
6184
|
-
for (const [field, index] of
|
|
6184
|
+
for (const [field, index] of path12) {
|
|
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, path12) => {
|
|
6194
|
+
const parent = visit.itemAtPath(cst, path12.slice(0, -1));
|
|
6195
|
+
const field = path12[path12.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(path12, item, visitor) {
|
|
6202
|
+
let ctrl = visitor(item, path12);
|
|
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(path12.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, path12);
|
|
6221
6221
|
}
|
|
6222
6222
|
}
|
|
6223
|
-
return typeof ctrl === "function" ? ctrl(item,
|
|
6223
|
+
return typeof ctrl === "function" ? ctrl(item, path12) : ctrl;
|
|
6224
6224
|
}
|
|
6225
6225
|
exports.visit = visit;
|
|
6226
6226
|
});
|
|
@@ -6429,11 +6429,11 @@ var require_lexer = __commonJS((exports) => {
|
|
|
6429
6429
|
hasChars(n) {
|
|
6430
6430
|
return this.pos + n <= this.buffer.length;
|
|
6431
6431
|
}
|
|
6432
|
-
setNext(
|
|
6432
|
+
setNext(state2) {
|
|
6433
6433
|
this.buffer = this.buffer.substring(this.pos);
|
|
6434
6434
|
this.pos = 0;
|
|
6435
6435
|
this.lineEndPos = null;
|
|
6436
|
-
this.next =
|
|
6436
|
+
this.next = state2;
|
|
6437
6437
|
return null;
|
|
6438
6438
|
}
|
|
6439
6439
|
peek(n) {
|
|
@@ -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 fs9 = this.flowScalar(this.type);
|
|
7493
7493
|
if (atNextItem || it.value) {
|
|
7494
|
-
map.items.push({ start, key:
|
|
7494
|
+
map.items.push({ start, key: fs9, sep: [] });
|
|
7495
7495
|
this.onKeyLine = true;
|
|
7496
7496
|
} else if (it.sep) {
|
|
7497
|
-
this.stack.push(
|
|
7497
|
+
this.stack.push(fs9);
|
|
7498
7498
|
} else {
|
|
7499
|
-
Object.assign(it, { key:
|
|
7499
|
+
Object.assign(it, { key: fs9, 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 fs9 = this.flowScalar(this.type);
|
|
7628
7628
|
if (!it || it.value)
|
|
7629
|
-
fc.items.push({ start: [], key:
|
|
7629
|
+
fc.items.push({ start: [], key: fs9, sep: [] });
|
|
7630
7630
|
else if (it.sep)
|
|
7631
|
-
this.stack.push(
|
|
7631
|
+
this.stack.push(fs9);
|
|
7632
7632
|
else
|
|
7633
|
-
Object.assign(it, { key:
|
|
7633
|
+
Object.assign(it, { key: fs9, 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 log6 = 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) => log6.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];
|
|
@@ -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 log14 = 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
|
+
log14("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
|
+
log14("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} 轮自纠失败,错误片段:
|
|
@@ -14008,6 +14008,507 @@ var codeforgeToolsServer = async (ctx) => {
|
|
|
14008
14008
|
};
|
|
14009
14009
|
var handler8 = codeforgeToolsServer;
|
|
14010
14010
|
|
|
14011
|
+
// plugins/discover-spec-suggest.ts
|
|
14012
|
+
init_opencode_plugin_helpers();
|
|
14013
|
+
import { readFileSync as readFileSync4, readdirSync, statSync as statSync3 } from "node:fs";
|
|
14014
|
+
import { join as join11 } from "node:path";
|
|
14015
|
+
|
|
14016
|
+
// lib/handoff-schema.ts
|
|
14017
|
+
import { z as z28 } from "zod";
|
|
14018
|
+
|
|
14019
|
+
// node_modules/yaml/dist/index.js
|
|
14020
|
+
var composer = require_composer();
|
|
14021
|
+
var Document = require_Document();
|
|
14022
|
+
var Schema = require_Schema();
|
|
14023
|
+
var errors = require_errors();
|
|
14024
|
+
var Alias = require_Alias();
|
|
14025
|
+
var identity = require_identity();
|
|
14026
|
+
var Pair = require_Pair();
|
|
14027
|
+
var Scalar = require_Scalar();
|
|
14028
|
+
var YAMLMap = require_YAMLMap();
|
|
14029
|
+
var YAMLSeq = require_YAMLSeq();
|
|
14030
|
+
var cst = require_cst();
|
|
14031
|
+
var lexer = require_lexer();
|
|
14032
|
+
var lineCounter = require_line_counter();
|
|
14033
|
+
var parser = require_parser();
|
|
14034
|
+
var publicApi = require_public_api();
|
|
14035
|
+
var visit = require_visit();
|
|
14036
|
+
var $Composer = composer.Composer;
|
|
14037
|
+
var $Document = Document.Document;
|
|
14038
|
+
var $Schema = Schema.Schema;
|
|
14039
|
+
var $YAMLError = errors.YAMLError;
|
|
14040
|
+
var $YAMLParseError = errors.YAMLParseError;
|
|
14041
|
+
var $YAMLWarning = errors.YAMLWarning;
|
|
14042
|
+
var $Alias = Alias.Alias;
|
|
14043
|
+
var $isAlias = identity.isAlias;
|
|
14044
|
+
var $isCollection = identity.isCollection;
|
|
14045
|
+
var $isDocument = identity.isDocument;
|
|
14046
|
+
var $isMap = identity.isMap;
|
|
14047
|
+
var $isNode = identity.isNode;
|
|
14048
|
+
var $isPair = identity.isPair;
|
|
14049
|
+
var $isScalar = identity.isScalar;
|
|
14050
|
+
var $isSeq = identity.isSeq;
|
|
14051
|
+
var $Pair = Pair.Pair;
|
|
14052
|
+
var $Scalar = Scalar.Scalar;
|
|
14053
|
+
var $YAMLMap = YAMLMap.YAMLMap;
|
|
14054
|
+
var $YAMLSeq = YAMLSeq.YAMLSeq;
|
|
14055
|
+
var $Lexer = lexer.Lexer;
|
|
14056
|
+
var $LineCounter = lineCounter.LineCounter;
|
|
14057
|
+
var $Parser = parser.Parser;
|
|
14058
|
+
var $parse = publicApi.parse;
|
|
14059
|
+
var $parseAllDocuments = publicApi.parseAllDocuments;
|
|
14060
|
+
var $parseDocument = publicApi.parseDocument;
|
|
14061
|
+
var $stringify = publicApi.stringify;
|
|
14062
|
+
var $visit = visit.visit;
|
|
14063
|
+
var $visitAsync = visit.visitAsync;
|
|
14064
|
+
|
|
14065
|
+
// lib/handoff-schema.ts
|
|
14066
|
+
var MAX_HANDOFF_SIZE = 100 * 1024;
|
|
14067
|
+
var SLUG_REGEX = /^[a-z0-9][a-z0-9-]{0,49}$/;
|
|
14068
|
+
var SCORE_DIMENSIONS = ["functional", "ux", "technical", "constraints", "edge_cases"];
|
|
14069
|
+
var COMBO_VALUES = ["A", "B", "C", "D"];
|
|
14070
|
+
var NeedSchema = z28.object({
|
|
14071
|
+
id: z28.string().min(1),
|
|
14072
|
+
type: z28.enum(["must", "should", "nice-to-have"]),
|
|
14073
|
+
statement: z28.string().min(1),
|
|
14074
|
+
rationale: z28.string().optional(),
|
|
14075
|
+
acceptance: z28.array(z28.string()).optional()
|
|
14076
|
+
});
|
|
14077
|
+
var BoundarySchema = z28.object({
|
|
14078
|
+
excluded: z28.string().min(1),
|
|
14079
|
+
reason: z28.string().min(1)
|
|
14080
|
+
});
|
|
14081
|
+
var AssumptionSchema = z28.object({
|
|
14082
|
+
statement: z28.string().min(1),
|
|
14083
|
+
confidence: z28.enum(["verified", "speculation", "high-risk-unknown"]),
|
|
14084
|
+
needs_validation_by: z28.enum(["plan", "coder", "runtime"]).optional()
|
|
14085
|
+
});
|
|
14086
|
+
var OpenIssueSchema = z28.union([
|
|
14087
|
+
z28.string().min(1),
|
|
14088
|
+
z28.object({
|
|
14089
|
+
id: z28.string().optional(),
|
|
14090
|
+
question: z28.string().min(1),
|
|
14091
|
+
blocking: z28.boolean().optional()
|
|
14092
|
+
})
|
|
14093
|
+
]);
|
|
14094
|
+
var RedFlagsSchema = z28.object({
|
|
14095
|
+
raised: z28.boolean(),
|
|
14096
|
+
combos: z28.array(z28.enum(COMBO_VALUES)).default([]),
|
|
14097
|
+
reasons: z28.array(z28.string()).default([]),
|
|
14098
|
+
user_persisted_rounds: z28.number().int().nonnegative().default(0),
|
|
14099
|
+
downstream_advisory: z28.string().nullable().optional()
|
|
14100
|
+
}).superRefine((rf, ctx) => {
|
|
14101
|
+
if (rf.raised) {
|
|
14102
|
+
if (rf.combos.length === 0) {
|
|
14103
|
+
ctx.addIssue({
|
|
14104
|
+
code: z28.ZodIssueCode.custom,
|
|
14105
|
+
message: "red_flags.raised=true 时 combos 必须 >=1",
|
|
14106
|
+
path: ["combos"]
|
|
14107
|
+
});
|
|
14108
|
+
}
|
|
14109
|
+
if (rf.reasons.length === 0) {
|
|
14110
|
+
ctx.addIssue({
|
|
14111
|
+
code: z28.ZodIssueCode.custom,
|
|
14112
|
+
message: "red_flags.raised=true 时 reasons 必须 >=1",
|
|
14113
|
+
path: ["reasons"]
|
|
14114
|
+
});
|
|
14115
|
+
}
|
|
14116
|
+
}
|
|
14117
|
+
});
|
|
14118
|
+
var ScoresSchema = z28.object(Object.fromEntries(SCORE_DIMENSIONS.map((d) => [d, z28.number().min(0).max(1)])));
|
|
14119
|
+
var PreCodingBlockerSchema = z28.object({
|
|
14120
|
+
id: z28.string().regex(/^PRE-\d+$/, "id 必须形如 PRE-1 / PRE-2"),
|
|
14121
|
+
blocker: z28.string().min(1),
|
|
14122
|
+
source: z28.enum(["assumption", "red_flag", "open_issue"]),
|
|
14123
|
+
must_resolve_by: z28.enum(["user", "codeforge"])
|
|
14124
|
+
});
|
|
14125
|
+
var KhRefSchema = z28.object({
|
|
14126
|
+
kh_id: z28.string(),
|
|
14127
|
+
title: z28.string(),
|
|
14128
|
+
relevance: z28.enum(["positive", "negative", "neutral"]).optional()
|
|
14129
|
+
});
|
|
14130
|
+
var HandoffSchema = z28.object({
|
|
14131
|
+
schema_version: z28.string().regex(/^\d+\.\d+\.\d+$/, "schema_version 必须形如 1.1.0 / 1.2.0"),
|
|
14132
|
+
slug: z28.string().regex(SLUG_REGEX, "slug 仅允许 [a-z0-9-],长度 1-50,首字符为字母数字"),
|
|
14133
|
+
title: z28.string().min(1).max(200),
|
|
14134
|
+
created_at: z28.string().optional(),
|
|
14135
|
+
discover_session_id: z28.string().optional(),
|
|
14136
|
+
weighted_score: z28.number().min(0).max(1),
|
|
14137
|
+
scores: ScoresSchema,
|
|
14138
|
+
needs: z28.array(NeedSchema).min(1, "needs 必须 >=1"),
|
|
14139
|
+
boundaries: z28.array(BoundarySchema).min(1, "boundaries 必须 >=1"),
|
|
14140
|
+
assumptions: z28.array(AssumptionSchema),
|
|
14141
|
+
open_issues: z28.array(OpenIssueSchema).default([]),
|
|
14142
|
+
rejected_alternatives: z28.array(z28.object({ option: z28.string(), reason: z28.string() })).default([]),
|
|
14143
|
+
acceptance_criteria: z28.array(z28.object({
|
|
14144
|
+
id: z28.string().regex(/^AC-/, "AC id 必须以 AC- 开头"),
|
|
14145
|
+
description: z28.string().min(1),
|
|
14146
|
+
measurable: z28.boolean().optional(),
|
|
14147
|
+
metric: z28.string().optional()
|
|
14148
|
+
})).default([]),
|
|
14149
|
+
red_flags: RedFlagsSchema,
|
|
14150
|
+
pre_coding_blockers: z28.array(PreCodingBlockerSchema).default([]),
|
|
14151
|
+
kh_references: z28.array(KhRefSchema).default([]),
|
|
14152
|
+
adr_refs: z28.array(z28.string()).default([]),
|
|
14153
|
+
related_artifacts: z28.object({
|
|
14154
|
+
prd: z28.string().optional(),
|
|
14155
|
+
transcript: z28.string().optional()
|
|
14156
|
+
}).optional()
|
|
14157
|
+
});
|
|
14158
|
+
function validateHandoff(rawYaml, fileSize) {
|
|
14159
|
+
if (fileSize > MAX_HANDOFF_SIZE) {
|
|
14160
|
+
return {
|
|
14161
|
+
ok: false,
|
|
14162
|
+
reason: `handoff.yaml 超过 ${MAX_HANDOFF_SIZE} 字节上限(实际 ${fileSize} 字节)`
|
|
14163
|
+
};
|
|
14164
|
+
}
|
|
14165
|
+
if (typeof rawYaml !== "string" || rawYaml.length === 0) {
|
|
14166
|
+
return { ok: false, reason: "handoff.yaml 内容为空" };
|
|
14167
|
+
}
|
|
14168
|
+
let parsed;
|
|
14169
|
+
try {
|
|
14170
|
+
parsed = $parse(rawYaml);
|
|
14171
|
+
} catch (err) {
|
|
14172
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
14173
|
+
return { ok: false, reason: `YAML 解析失败:${msg}` };
|
|
14174
|
+
}
|
|
14175
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
14176
|
+
return { ok: false, reason: "handoff.yaml 顶层必须是 object" };
|
|
14177
|
+
}
|
|
14178
|
+
const result = HandoffSchema.safeParse(parsed);
|
|
14179
|
+
if (!result.success) {
|
|
14180
|
+
const first = result.error.issues[0];
|
|
14181
|
+
const path12 = first?.path?.join(".") ?? "(root)";
|
|
14182
|
+
const msg = first?.message ?? "unknown";
|
|
14183
|
+
return { ok: false, reason: `schema 校验失败:${path12}: ${msg}` };
|
|
14184
|
+
}
|
|
14185
|
+
return { ok: true, data: result.data, schemaVersion: result.data.schema_version };
|
|
14186
|
+
}
|
|
14187
|
+
function isValidSlug(slug) {
|
|
14188
|
+
return typeof slug === "string" && SLUG_REGEX.test(slug);
|
|
14189
|
+
}
|
|
14190
|
+
|
|
14191
|
+
// plugins/discover-spec-suggest.ts
|
|
14192
|
+
var PLUGIN_NAME9 = "discover-spec-suggest";
|
|
14193
|
+
logLifecycle(PLUGIN_NAME9, "import", {});
|
|
14194
|
+
var TARGET_AGENT = "codeforge";
|
|
14195
|
+
var SESSION_CAP = 200;
|
|
14196
|
+
var SESSION_TTL_MS = 24 * 60 * 60 * 1000;
|
|
14197
|
+
var MATCH_THRESHOLD = 0.15;
|
|
14198
|
+
var MAX_CANDIDATES = 3;
|
|
14199
|
+
var NUDGE_MAX_LEN = 1500;
|
|
14200
|
+
var SPECS_REL_DIR = join11(".codeforge", "specs");
|
|
14201
|
+
var sessionMap = new Map;
|
|
14202
|
+
function pruneIfOversize() {
|
|
14203
|
+
while (sessionMap.size > SESSION_CAP) {
|
|
14204
|
+
const oldestKey = sessionMap.keys().next().value;
|
|
14205
|
+
if (oldestKey === undefined)
|
|
14206
|
+
break;
|
|
14207
|
+
sessionMap.delete(oldestKey);
|
|
14208
|
+
}
|
|
14209
|
+
}
|
|
14210
|
+
function isExpired(entry, now = Date.now()) {
|
|
14211
|
+
return now - entry.ts > SESSION_TTL_MS;
|
|
14212
|
+
}
|
|
14213
|
+
var specIndex = [];
|
|
14214
|
+
function defaultReader(p) {
|
|
14215
|
+
return readFileSync4(p, "utf8");
|
|
14216
|
+
}
|
|
14217
|
+
function defaultDirReader(p) {
|
|
14218
|
+
return readdirSync(p);
|
|
14219
|
+
}
|
|
14220
|
+
function defaultDirExists(p) {
|
|
14221
|
+
try {
|
|
14222
|
+
return statSync3(p).isDirectory();
|
|
14223
|
+
} catch {
|
|
14224
|
+
return false;
|
|
14225
|
+
}
|
|
14226
|
+
}
|
|
14227
|
+
function defaultStatReader(p) {
|
|
14228
|
+
const st = statSync3(p);
|
|
14229
|
+
return { size: st.size, mtimeMs: st.mtimeMs, isDirectory: st.isDirectory() };
|
|
14230
|
+
}
|
|
14231
|
+
function tokenize(s) {
|
|
14232
|
+
if (typeof s !== "string" || s.length === 0)
|
|
14233
|
+
return [];
|
|
14234
|
+
const lower = s.toLowerCase();
|
|
14235
|
+
const parts = lower.split(/[^a-z0-9\u4e00-\u9fff]+/u).filter(Boolean);
|
|
14236
|
+
return parts.filter((t) => /[\u4e00-\u9fff]/u.test(t) || t.length >= 2);
|
|
14237
|
+
}
|
|
14238
|
+
function jaccard(a, b) {
|
|
14239
|
+
if (a.size === 0 || b.size === 0)
|
|
14240
|
+
return 0;
|
|
14241
|
+
let inter = 0;
|
|
14242
|
+
for (const t of a)
|
|
14243
|
+
if (b.has(t))
|
|
14244
|
+
inter++;
|
|
14245
|
+
const union = a.size + b.size - inter;
|
|
14246
|
+
if (union === 0)
|
|
14247
|
+
return 0;
|
|
14248
|
+
return inter / union;
|
|
14249
|
+
}
|
|
14250
|
+
function escapeRegExp2(s) {
|
|
14251
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
14252
|
+
}
|
|
14253
|
+
function isEnglishToken(t) {
|
|
14254
|
+
return /^[a-zA-Z0-9_-]+$/.test(t);
|
|
14255
|
+
}
|
|
14256
|
+
function titleHit(queryTokens, titleLower) {
|
|
14257
|
+
if (typeof titleLower !== "string" || titleLower.length === 0)
|
|
14258
|
+
return 0;
|
|
14259
|
+
let hits = 0;
|
|
14260
|
+
for (const t of queryTokens) {
|
|
14261
|
+
if (typeof t !== "string" || t.length === 0)
|
|
14262
|
+
continue;
|
|
14263
|
+
const tLower = t.toLowerCase();
|
|
14264
|
+
if (isEnglishToken(tLower)) {
|
|
14265
|
+
const re = new RegExp(`\\b${escapeRegExp2(tLower)}\\b`);
|
|
14266
|
+
if (re.test(titleLower))
|
|
14267
|
+
hits++;
|
|
14268
|
+
} else {
|
|
14269
|
+
if (tLower.length >= 2 && titleLower.includes(tLower))
|
|
14270
|
+
hits++;
|
|
14271
|
+
}
|
|
14272
|
+
}
|
|
14273
|
+
return hits;
|
|
14274
|
+
}
|
|
14275
|
+
function matchSpecs(query, index) {
|
|
14276
|
+
const qTokens = new Set(tokenize(query));
|
|
14277
|
+
if (qTokens.size === 0)
|
|
14278
|
+
return [];
|
|
14279
|
+
const scored = [];
|
|
14280
|
+
for (const rec of index) {
|
|
14281
|
+
const j = jaccard(qTokens, rec.tokens);
|
|
14282
|
+
const t = titleHit(qTokens, rec.titleLower);
|
|
14283
|
+
const score = j * 0.6 + Math.min(t, 1) * 0.4;
|
|
14284
|
+
if (score >= MATCH_THRESHOLD) {
|
|
14285
|
+
scored.push({
|
|
14286
|
+
slug: rec.slug,
|
|
14287
|
+
title: rec.title,
|
|
14288
|
+
score,
|
|
14289
|
+
schemaVersion: rec.schemaVersion,
|
|
14290
|
+
mtime: rec.mtime
|
|
14291
|
+
});
|
|
14292
|
+
}
|
|
14293
|
+
}
|
|
14294
|
+
scored.sort((a, b) => {
|
|
14295
|
+
if (b.score !== a.score)
|
|
14296
|
+
return b.score - a.score;
|
|
14297
|
+
return b.mtime - a.mtime;
|
|
14298
|
+
});
|
|
14299
|
+
return scored.slice(0, MAX_CANDIDATES);
|
|
14300
|
+
}
|
|
14301
|
+
function loadSpecs(rootDir, opts = {}) {
|
|
14302
|
+
const reader = opts.reader ?? defaultReader;
|
|
14303
|
+
const dirReader = opts.dirReader ?? defaultDirReader;
|
|
14304
|
+
const dirExists = opts.dirExists ?? defaultDirExists;
|
|
14305
|
+
const statReader = opts.statReader ?? defaultStatReader;
|
|
14306
|
+
const log6 = makePluginLogger(PLUGIN_NAME9);
|
|
14307
|
+
const specsRoot = join11(rootDir, SPECS_REL_DIR);
|
|
14308
|
+
const records = [];
|
|
14309
|
+
if (!dirExists(specsRoot)) {
|
|
14310
|
+
log6.info(`specs 目录不存在,plugin 将 no-op`, { specsRoot });
|
|
14311
|
+
return records;
|
|
14312
|
+
}
|
|
14313
|
+
let entries;
|
|
14314
|
+
try {
|
|
14315
|
+
entries = dirReader(specsRoot);
|
|
14316
|
+
} catch (err) {
|
|
14317
|
+
log6.warn(`specs 目录读取失败`, {
|
|
14318
|
+
specsRoot,
|
|
14319
|
+
error: err instanceof Error ? err.message : String(err)
|
|
14320
|
+
});
|
|
14321
|
+
return records;
|
|
14322
|
+
}
|
|
14323
|
+
for (const entry of entries) {
|
|
14324
|
+
if (!isValidSlug(entry)) {
|
|
14325
|
+
log6.info(`跳过非合法 slug 命名的条目`, { entry });
|
|
14326
|
+
continue;
|
|
14327
|
+
}
|
|
14328
|
+
const specDir = join11(specsRoot, entry);
|
|
14329
|
+
let dirStat;
|
|
14330
|
+
try {
|
|
14331
|
+
dirStat = statReader(specDir);
|
|
14332
|
+
} catch (err) {
|
|
14333
|
+
log6.warn(`spec 目录 stat 失败`, {
|
|
14334
|
+
specDir,
|
|
14335
|
+
error: err instanceof Error ? err.message : String(err)
|
|
14336
|
+
});
|
|
14337
|
+
continue;
|
|
14338
|
+
}
|
|
14339
|
+
if (!dirStat.isDirectory)
|
|
14340
|
+
continue;
|
|
14341
|
+
const handoffPath = join11(specDir, "handoff.yaml");
|
|
14342
|
+
let fileStat;
|
|
14343
|
+
try {
|
|
14344
|
+
fileStat = statReader(handoffPath);
|
|
14345
|
+
} catch {
|
|
14346
|
+
continue;
|
|
14347
|
+
}
|
|
14348
|
+
if (fileStat.isDirectory)
|
|
14349
|
+
continue;
|
|
14350
|
+
if (fileStat.size > MAX_HANDOFF_SIZE) {
|
|
14351
|
+
log6.warn(`handoff.yaml 超过 ${MAX_HANDOFF_SIZE} 字节上限,已跳过`, {
|
|
14352
|
+
handoffPath,
|
|
14353
|
+
size: fileStat.size
|
|
14354
|
+
});
|
|
14355
|
+
continue;
|
|
14356
|
+
}
|
|
14357
|
+
let rawYaml;
|
|
14358
|
+
try {
|
|
14359
|
+
rawYaml = reader(handoffPath);
|
|
14360
|
+
} catch (err) {
|
|
14361
|
+
log6.warn(`handoff.yaml 读取失败`, {
|
|
14362
|
+
handoffPath,
|
|
14363
|
+
error: err instanceof Error ? err.message : String(err)
|
|
14364
|
+
});
|
|
14365
|
+
continue;
|
|
14366
|
+
}
|
|
14367
|
+
const result = validateHandoff(rawYaml, fileStat.size);
|
|
14368
|
+
if (!result.ok) {
|
|
14369
|
+
log6.warn(`handoff.yaml 校验失败,已跳过`, {
|
|
14370
|
+
handoffPath,
|
|
14371
|
+
reason: result.reason
|
|
14372
|
+
});
|
|
14373
|
+
continue;
|
|
14374
|
+
}
|
|
14375
|
+
const handoff = result.data;
|
|
14376
|
+
if (handoff.slug !== entry) {
|
|
14377
|
+
log6.warn(`handoff.slug 与目录名不一致,以目录名为准`, {
|
|
14378
|
+
dir: entry,
|
|
14379
|
+
handoffSlug: handoff.slug
|
|
14380
|
+
});
|
|
14381
|
+
}
|
|
14382
|
+
const corpus = [handoff.title];
|
|
14383
|
+
for (const need of handoff.needs ?? []) {
|
|
14384
|
+
if (need.statement)
|
|
14385
|
+
corpus.push(need.statement);
|
|
14386
|
+
if (need.rationale)
|
|
14387
|
+
corpus.push(need.rationale);
|
|
14388
|
+
}
|
|
14389
|
+
const tokens = new Set(tokenize(corpus.join(" ")));
|
|
14390
|
+
records.push({
|
|
14391
|
+
slug: entry,
|
|
14392
|
+
title: handoff.title,
|
|
14393
|
+
schemaVersion: result.schemaVersion,
|
|
14394
|
+
tokens,
|
|
14395
|
+
titleLower: handoff.title.toLowerCase(),
|
|
14396
|
+
mtime: fileStat.mtimeMs,
|
|
14397
|
+
handoff
|
|
14398
|
+
});
|
|
14399
|
+
}
|
|
14400
|
+
log6.info(`specs 加载完成`, {
|
|
14401
|
+
specsRoot,
|
|
14402
|
+
loaded: records.length,
|
|
14403
|
+
total_entries: entries.length
|
|
14404
|
+
});
|
|
14405
|
+
return records;
|
|
14406
|
+
}
|
|
14407
|
+
function renderCandidatesNudge(matched) {
|
|
14408
|
+
if (matched.length === 0)
|
|
14409
|
+
return "";
|
|
14410
|
+
const lines = [];
|
|
14411
|
+
lines.push("────────────────────────────────────────");
|
|
14412
|
+
lines.push("DISCOVER SPEC CANDIDATES (auto-injected by discover-spec-suggest plugin)");
|
|
14413
|
+
lines.push("────────────────────────────────────────");
|
|
14414
|
+
lines.push("以下 spec 与用户当前需求**可能**相关(jaccard + title 加权匹配)。");
|
|
14415
|
+
lines.push("**必须明文跟用户确认**是否走该 spec 路径;用户否认 → 忽略本提示继续原计划。");
|
|
14416
|
+
lines.push("");
|
|
14417
|
+
for (const m of matched) {
|
|
14418
|
+
const scorePct = (m.score * 100).toFixed(0);
|
|
14419
|
+
lines.push(` - \`${m.slug}\` (${m.schemaVersion}, score ${scorePct}%): ${m.title}`);
|
|
14420
|
+
}
|
|
14421
|
+
lines.push("");
|
|
14422
|
+
lines.push("若用户确认走 spec:");
|
|
14423
|
+
lines.push(" 1. 先 `read .codeforge/specs/<slug>/PRD.md` + `read .codeforge/specs/<slug>/handoff.yaml`");
|
|
14424
|
+
lines.push(" 2. 派 planner / coder 时 prompt 必须含 `spec=<slug>`");
|
|
14425
|
+
lines.push(" 3. 若 handoff 含 pre_coding_blockers[],派 coder 时需附 `pre_ack=<PRE-id>`");
|
|
14426
|
+
lines.push("────────────────────────────────────────");
|
|
14427
|
+
const body = lines.join(`
|
|
14428
|
+
`);
|
|
14429
|
+
if (body.length <= NUDGE_MAX_LEN)
|
|
14430
|
+
return body;
|
|
14431
|
+
return body.slice(0, NUDGE_MAX_LEN - 4) + `
|
|
14432
|
+
…`;
|
|
14433
|
+
}
|
|
14434
|
+
var log6 = makePluginLogger(PLUGIN_NAME9);
|
|
14435
|
+
var discoverSpecSuggestServer = async (ctx) => {
|
|
14436
|
+
try {
|
|
14437
|
+
const loaded = loadSpecs(ctx.directory ?? process.cwd());
|
|
14438
|
+
specIndex.length = 0;
|
|
14439
|
+
specIndex.push(...loaded);
|
|
14440
|
+
} catch (err) {
|
|
14441
|
+
log6.warn(`loadSpecs 失败(plugin 将 no-op)`, {
|
|
14442
|
+
error: err instanceof Error ? err.message : String(err)
|
|
14443
|
+
});
|
|
14444
|
+
}
|
|
14445
|
+
logLifecycle(PLUGIN_NAME9, "activate", {
|
|
14446
|
+
directory: ctx.directory,
|
|
14447
|
+
specs_loaded: specIndex.length,
|
|
14448
|
+
target_agent: TARGET_AGENT,
|
|
14449
|
+
match_threshold: MATCH_THRESHOLD,
|
|
14450
|
+
max_candidates: MAX_CANDIDATES,
|
|
14451
|
+
session_cap: SESSION_CAP,
|
|
14452
|
+
session_ttl_ms: SESSION_TTL_MS,
|
|
14453
|
+
no_op: specIndex.length === 0
|
|
14454
|
+
});
|
|
14455
|
+
return {
|
|
14456
|
+
"chat.message": async (input, output) => {
|
|
14457
|
+
await safeAsync(PLUGIN_NAME9, "chat.message", async () => {
|
|
14458
|
+
if (specIndex.length === 0)
|
|
14459
|
+
return;
|
|
14460
|
+
const text = extractUserText(output);
|
|
14461
|
+
if (!text)
|
|
14462
|
+
return;
|
|
14463
|
+
const agent = input?.agent ?? "unknown";
|
|
14464
|
+
const sid = input?.sessionID;
|
|
14465
|
+
if (!sid)
|
|
14466
|
+
return;
|
|
14467
|
+
sessionMap.set(sid, { text, agent, ts: Date.now() });
|
|
14468
|
+
pruneIfOversize();
|
|
14469
|
+
});
|
|
14470
|
+
},
|
|
14471
|
+
"experimental.chat.system.transform": async (input, output) => {
|
|
14472
|
+
await safeAsync(PLUGIN_NAME9, "experimental.chat.system.transform", async () => {
|
|
14473
|
+
if (specIndex.length === 0)
|
|
14474
|
+
return;
|
|
14475
|
+
if (!output || !Array.isArray(output.system))
|
|
14476
|
+
return;
|
|
14477
|
+
const sid = input?.sessionID;
|
|
14478
|
+
if (!sid)
|
|
14479
|
+
return;
|
|
14480
|
+
const entry = sessionMap.get(sid);
|
|
14481
|
+
if (!entry)
|
|
14482
|
+
return;
|
|
14483
|
+
if (isExpired(entry)) {
|
|
14484
|
+
sessionMap.delete(sid);
|
|
14485
|
+
return;
|
|
14486
|
+
}
|
|
14487
|
+
if (entry.agent !== TARGET_AGENT)
|
|
14488
|
+
return;
|
|
14489
|
+
if (!entry.text)
|
|
14490
|
+
return;
|
|
14491
|
+
const matched = matchSpecs(entry.text, specIndex);
|
|
14492
|
+
if (matched.length === 0)
|
|
14493
|
+
return;
|
|
14494
|
+
const nudge = renderCandidatesNudge(matched);
|
|
14495
|
+
if (!nudge)
|
|
14496
|
+
return;
|
|
14497
|
+
output.system.push(nudge);
|
|
14498
|
+
safeWriteLog(PLUGIN_NAME9, {
|
|
14499
|
+
hook: "experimental.chat.system.transform",
|
|
14500
|
+
sessionID: sid,
|
|
14501
|
+
agent: entry.agent,
|
|
14502
|
+
matched_slugs: matched.map((m) => m.slug),
|
|
14503
|
+
matched_count: matched.length,
|
|
14504
|
+
injected_chars: nudge.length
|
|
14505
|
+
});
|
|
14506
|
+
});
|
|
14507
|
+
}
|
|
14508
|
+
};
|
|
14509
|
+
};
|
|
14510
|
+
var handler9 = discoverSpecSuggestServer;
|
|
14511
|
+
|
|
14011
14512
|
// plugins/kh-auto-context.ts
|
|
14012
14513
|
init_kh_client();
|
|
14013
14514
|
|
|
@@ -14031,16 +14532,16 @@ class SessionScopedCache {
|
|
|
14031
14532
|
const { client, sessionId, query, limit, timeoutMs } = args;
|
|
14032
14533
|
const queryHash = this.hashQuery(sessionId, query);
|
|
14033
14534
|
const inflightKey = `${sessionId}::${queryHash}`;
|
|
14034
|
-
const
|
|
14035
|
-
if (
|
|
14036
|
-
const entry =
|
|
14535
|
+
const sessionMap2 = this.cache.get(sessionId);
|
|
14536
|
+
if (sessionMap2) {
|
|
14537
|
+
const entry = sessionMap2.get(queryHash);
|
|
14037
14538
|
if (entry && this.now() - entry.cachedAt < this.ttlMs) {
|
|
14038
|
-
|
|
14039
|
-
|
|
14539
|
+
sessionMap2.delete(queryHash);
|
|
14540
|
+
sessionMap2.set(queryHash, entry);
|
|
14040
14541
|
return entry.result;
|
|
14041
14542
|
}
|
|
14042
14543
|
if (entry)
|
|
14043
|
-
|
|
14544
|
+
sessionMap2.delete(queryHash);
|
|
14044
14545
|
}
|
|
14045
14546
|
const pending = this.inflight.get(inflightKey);
|
|
14046
14547
|
if (pending)
|
|
@@ -14128,18 +14629,18 @@ class SessionScopedCache {
|
|
|
14128
14629
|
}
|
|
14129
14630
|
}
|
|
14130
14631
|
writeCache(sessionId, queryHash, query, result) {
|
|
14131
|
-
let
|
|
14132
|
-
if (!
|
|
14133
|
-
|
|
14134
|
-
this.cache.set(sessionId,
|
|
14135
|
-
}
|
|
14136
|
-
|
|
14137
|
-
|
|
14138
|
-
while (
|
|
14139
|
-
const oldest =
|
|
14632
|
+
let sessionMap2 = this.cache.get(sessionId);
|
|
14633
|
+
if (!sessionMap2) {
|
|
14634
|
+
sessionMap2 = new Map;
|
|
14635
|
+
this.cache.set(sessionId, sessionMap2);
|
|
14636
|
+
}
|
|
14637
|
+
sessionMap2.delete(queryHash);
|
|
14638
|
+
sessionMap2.set(queryHash, { query, result, cachedAt: this.now() });
|
|
14639
|
+
while (sessionMap2.size > this.maxPerSession) {
|
|
14640
|
+
const oldest = sessionMap2.keys().next().value;
|
|
14140
14641
|
if (oldest === undefined)
|
|
14141
14642
|
break;
|
|
14142
|
-
|
|
14643
|
+
sessionMap2.delete(oldest);
|
|
14143
14644
|
}
|
|
14144
14645
|
}
|
|
14145
14646
|
}
|
|
@@ -14147,7 +14648,7 @@ var sharedKhCache = new SessionScopedCache;
|
|
|
14147
14648
|
|
|
14148
14649
|
// plugins/kh-auto-context.ts
|
|
14149
14650
|
init_opencode_plugin_helpers();
|
|
14150
|
-
var
|
|
14651
|
+
var PLUGIN_NAME10 = "kh-auto-context";
|
|
14151
14652
|
var INJECTION_MODE = "observe-only";
|
|
14152
14653
|
function resolveInjectionMode(client) {
|
|
14153
14654
|
return client.hasTransport() ? "system-injected" : "observe-only";
|
|
@@ -14244,9 +14745,9 @@ var CODEFORGE_CONSTRAINTS = [
|
|
|
14244
14745
|
priority: 8
|
|
14245
14746
|
}
|
|
14246
14747
|
];
|
|
14247
|
-
async function seedConstraints(client,
|
|
14748
|
+
async function seedConstraints(client, log7) {
|
|
14248
14749
|
if (!client.hasTransport()) {
|
|
14249
|
-
|
|
14750
|
+
log7?.debug?.(`[${PLUGIN_NAME10}] seedConstraints: transport 不可用,跳过`, {});
|
|
14250
14751
|
return { ok: false, reason: "transport_unavailable" };
|
|
14251
14752
|
}
|
|
14252
14753
|
try {
|
|
@@ -14260,7 +14761,7 @@ async function seedConstraints(client, log6) {
|
|
|
14260
14761
|
});
|
|
14261
14762
|
if (result && typeof result === "object" && "ok" in result && result.ok === false) {
|
|
14262
14763
|
const r = result;
|
|
14263
|
-
|
|
14764
|
+
log7?.warn(`[${PLUGIN_NAME10}] seedConstraints 降级`, {
|
|
14264
14765
|
reason: r.reason,
|
|
14265
14766
|
message: r.message
|
|
14266
14767
|
});
|
|
@@ -14270,15 +14771,15 @@ async function seedConstraints(client, log6) {
|
|
|
14270
14771
|
message: r.message
|
|
14271
14772
|
};
|
|
14272
14773
|
}
|
|
14273
|
-
|
|
14774
|
+
log7?.info(`[${PLUGIN_NAME10}] seedConstraints: 已写入 ${CODEFORGE_CONSTRAINTS.length} 条 constraints`, { count: CODEFORGE_CONSTRAINTS.length });
|
|
14274
14775
|
return { ok: true, itemsWritten: CODEFORGE_CONSTRAINTS.length };
|
|
14275
14776
|
} catch (err) {
|
|
14276
14777
|
const message = err instanceof Error ? err.message : String(err);
|
|
14277
|
-
|
|
14778
|
+
log7?.warn(`[${PLUGIN_NAME10}] seedConstraints 失败(已静默)`, { error: message });
|
|
14278
14779
|
return { ok: false, reason: "exception", message };
|
|
14279
14780
|
}
|
|
14280
14781
|
}
|
|
14281
|
-
function createSystemInjectedHook(client, sessionId,
|
|
14782
|
+
function createSystemInjectedHook(client, sessionId, log7) {
|
|
14282
14783
|
const section = `topic:auto-context-${sessionId ?? "global"}`;
|
|
14283
14784
|
return async (markdown) => {
|
|
14284
14785
|
try {
|
|
@@ -14289,7 +14790,7 @@ function createSystemInjectedHook(client, sessionId, log6) {
|
|
|
14289
14790
|
});
|
|
14290
14791
|
if (result && typeof result === "object" && "ok" in result && result.ok === false) {
|
|
14291
14792
|
const r = result;
|
|
14292
|
-
|
|
14793
|
+
log7?.warn(`[${PLUGIN_NAME10}] system-injected 降级到 observe-only:${r.reason ?? "unknown"}`, {
|
|
14293
14794
|
sessionId,
|
|
14294
14795
|
section,
|
|
14295
14796
|
preview: markdown.slice(0, 200),
|
|
@@ -14298,12 +14799,12 @@ function createSystemInjectedHook(client, sessionId, log6) {
|
|
|
14298
14799
|
});
|
|
14299
14800
|
return;
|
|
14300
14801
|
}
|
|
14301
|
-
|
|
14802
|
+
log7?.info(`[${PLUGIN_NAME10}] system-injected: 写入 KH working_memory 成功 (${markdown.length} chars)`, {
|
|
14302
14803
|
sessionId,
|
|
14303
14804
|
section
|
|
14304
14805
|
});
|
|
14305
14806
|
} catch (err) {
|
|
14306
|
-
|
|
14807
|
+
log7?.warn(`[${PLUGIN_NAME10}] system-injected 抛异常,降级到 observe-only`, {
|
|
14307
14808
|
sessionId,
|
|
14308
14809
|
section,
|
|
14309
14810
|
preview: markdown.slice(0, 200),
|
|
@@ -14320,7 +14821,7 @@ function inflightKey(sessionId, query) {
|
|
|
14320
14821
|
async function runKhSearchAndInject(args) {
|
|
14321
14822
|
const { query, ctx, opts, mode } = args;
|
|
14322
14823
|
const cfg = opts.config ?? DEFAULT_CONFIG5;
|
|
14323
|
-
const
|
|
14824
|
+
const log7 = ctx.log;
|
|
14324
14825
|
const startedAt = Date.now();
|
|
14325
14826
|
const racer = opts.scheduler?.raceTimeout ?? (async (p, ms) => {
|
|
14326
14827
|
let timer = null;
|
|
@@ -14346,7 +14847,7 @@ async function runKhSearchAndInject(args) {
|
|
|
14346
14847
|
});
|
|
14347
14848
|
result = await racer(searchPromise, cfg.timeoutMs);
|
|
14348
14849
|
} catch (err) {
|
|
14349
|
-
|
|
14850
|
+
log7?.warn(`[${PLUGIN_NAME10}] client.search threw (sync or async), return null`, {
|
|
14350
14851
|
query,
|
|
14351
14852
|
elapsedMs: Date.now() - startedAt,
|
|
14352
14853
|
sessionId: ctx.sessionId,
|
|
@@ -14356,7 +14857,7 @@ async function runKhSearchAndInject(args) {
|
|
|
14356
14857
|
return null;
|
|
14357
14858
|
}
|
|
14358
14859
|
if (result === "__timeout__") {
|
|
14359
|
-
|
|
14860
|
+
log7?.warn(`[${PLUGIN_NAME10}] timeout`, {
|
|
14360
14861
|
query,
|
|
14361
14862
|
ms: cfg.timeoutMs,
|
|
14362
14863
|
elapsedMs: Date.now() - startedAt,
|
|
@@ -14366,7 +14867,7 @@ async function runKhSearchAndInject(args) {
|
|
|
14366
14867
|
return null;
|
|
14367
14868
|
}
|
|
14368
14869
|
if (!result.ok) {
|
|
14369
|
-
|
|
14870
|
+
log7?.warn(`[${PLUGIN_NAME10}] kh degraded`, {
|
|
14370
14871
|
reason: result.reason,
|
|
14371
14872
|
query,
|
|
14372
14873
|
elapsedMs: Date.now() - startedAt,
|
|
@@ -14378,7 +14879,7 @@ async function runKhSearchAndInject(args) {
|
|
|
14378
14879
|
const filtered = result.insights.filter((i) => (i.confidence ?? 0) >= cfg.minConfidence);
|
|
14379
14880
|
if (filtered.length === 0) {
|
|
14380
14881
|
opts.cache.record(query, []);
|
|
14381
|
-
|
|
14882
|
+
log7?.debug?.(`[${PLUGIN_NAME10}] no candidate above threshold`, {
|
|
14382
14883
|
query,
|
|
14383
14884
|
rawCount: result.insights.length,
|
|
14384
14885
|
elapsedMs: Date.now() - startedAt,
|
|
@@ -14391,7 +14892,7 @@ async function runKhSearchAndInject(args) {
|
|
|
14391
14892
|
try {
|
|
14392
14893
|
await ctx.injectContext(payload.markdown);
|
|
14393
14894
|
} catch (err) {
|
|
14394
|
-
|
|
14895
|
+
log7?.warn(`[${PLUGIN_NAME10}] injectContext threw`, {
|
|
14395
14896
|
error: err instanceof Error ? err.message : String(err),
|
|
14396
14897
|
query,
|
|
14397
14898
|
sessionId: ctx.sessionId
|
|
@@ -14399,7 +14900,7 @@ async function runKhSearchAndInject(args) {
|
|
|
14399
14900
|
}
|
|
14400
14901
|
}
|
|
14401
14902
|
opts.cache.record(query, filtered);
|
|
14402
|
-
|
|
14903
|
+
log7?.info(`[${PLUGIN_NAME10}] inject complete (${mode})`, {
|
|
14403
14904
|
query,
|
|
14404
14905
|
mode,
|
|
14405
14906
|
candidateCount: filtered.length,
|
|
@@ -14412,21 +14913,21 @@ async function handleMessage2(raw, opts) {
|
|
|
14412
14913
|
const cfg = opts.config ?? DEFAULT_CONFIG5;
|
|
14413
14914
|
const mode = opts.mode ?? INJECTION_MODE;
|
|
14414
14915
|
const ctx = raw ?? {};
|
|
14415
|
-
const
|
|
14916
|
+
const log7 = ctx.log;
|
|
14416
14917
|
const text = (ctx.content ?? "").trim();
|
|
14417
14918
|
if (!shouldInject(text, cfg)) {
|
|
14418
|
-
|
|
14919
|
+
log7?.debug?.(`[${PLUGIN_NAME10}] skip (filter)`, { textLen: text.length });
|
|
14419
14920
|
return;
|
|
14420
14921
|
}
|
|
14421
14922
|
const query = extractQuery(text);
|
|
14422
14923
|
if (!query)
|
|
14423
14924
|
return;
|
|
14424
14925
|
if (opts.cache.shouldSkip(query)) {
|
|
14425
|
-
|
|
14926
|
+
log7?.debug?.(`[${PLUGIN_NAME10}] cache hit, skip`, { query });
|
|
14426
14927
|
return;
|
|
14427
14928
|
}
|
|
14428
14929
|
if (inflight2.size >= INFLIGHT_CAP) {
|
|
14429
|
-
|
|
14930
|
+
log7?.warn(`[${PLUGIN_NAME10}] inflight cap reached, skip`, {
|
|
14430
14931
|
query,
|
|
14431
14932
|
inflightSize: inflight2.size,
|
|
14432
14933
|
cap: INFLIGHT_CAP,
|
|
@@ -14437,7 +14938,7 @@ async function handleMessage2(raw, opts) {
|
|
|
14437
14938
|
const key = inflightKey(ctx.sessionId, query);
|
|
14438
14939
|
inflight2.add(key);
|
|
14439
14940
|
runKhSearchAndInject({ query, ctx, opts, mode }).catch((err) => {
|
|
14440
|
-
|
|
14941
|
+
log7?.warn(`[${PLUGIN_NAME10}] runKhSearchAndInject 顶层兜底捕获`, {
|
|
14441
14942
|
error: err instanceof Error ? err.message : String(err),
|
|
14442
14943
|
query,
|
|
14443
14944
|
sessionId: ctx.sessionId
|
|
@@ -14446,32 +14947,32 @@ async function handleMessage2(raw, opts) {
|
|
|
14446
14947
|
inflight2.delete(key);
|
|
14447
14948
|
});
|
|
14448
14949
|
}
|
|
14449
|
-
logLifecycle(
|
|
14950
|
+
logLifecycle(PLUGIN_NAME10, "import");
|
|
14450
14951
|
var sharedClient2 = new KhClient;
|
|
14451
14952
|
var sharedCache = new QueryCache(DEFAULT_CONFIG5.cacheTtlMs);
|
|
14452
14953
|
var khAutoContextServer = async (ctx) => {
|
|
14453
|
-
const
|
|
14954
|
+
const log7 = makePluginLogger(PLUGIN_NAME10);
|
|
14454
14955
|
const runtimeMode = resolveInjectionMode(sharedClient2);
|
|
14455
|
-
logLifecycle(
|
|
14956
|
+
logLifecycle(PLUGIN_NAME10, "activate", {
|
|
14456
14957
|
directory: ctx.directory,
|
|
14457
14958
|
minConfidence: DEFAULT_CONFIG5.minConfidence,
|
|
14458
14959
|
timeoutMs: DEFAULT_CONFIG5.timeoutMs,
|
|
14459
14960
|
mode: runtimeMode,
|
|
14460
14961
|
transport_available: sharedClient2.hasTransport()
|
|
14461
14962
|
});
|
|
14462
|
-
seedConstraints(sharedClient2,
|
|
14463
|
-
|
|
14963
|
+
seedConstraints(sharedClient2, log7).catch((err) => {
|
|
14964
|
+
log7.warn("seedConstraints 顶层兜底捕获", {
|
|
14464
14965
|
error: err instanceof Error ? err.message : String(err)
|
|
14465
14966
|
});
|
|
14466
14967
|
});
|
|
14467
14968
|
return {
|
|
14468
14969
|
"chat.message": async (input, output) => {
|
|
14469
|
-
await safeAsync(
|
|
14970
|
+
await safeAsync(PLUGIN_NAME10, "chat.message", async () => {
|
|
14470
14971
|
const text = extractUserText(output);
|
|
14471
14972
|
if (!text)
|
|
14472
14973
|
return;
|
|
14473
|
-
const injectContext = runtimeMode === "system-injected" ? createSystemInjectedHook(sharedClient2, input.sessionID,
|
|
14474
|
-
|
|
14974
|
+
const injectContext = runtimeMode === "system-injected" ? createSystemInjectedHook(sharedClient2, input.sessionID, log7) : async (markdown) => {
|
|
14975
|
+
log7.info(`KH context candidate (${markdown.length} chars)`, {
|
|
14475
14976
|
sessionID: input.sessionID,
|
|
14476
14977
|
mode: runtimeMode,
|
|
14477
14978
|
preview: markdown.slice(0, 200)
|
|
@@ -14481,12 +14982,12 @@ var khAutoContextServer = async (ctx) => {
|
|
|
14481
14982
|
content: text,
|
|
14482
14983
|
sessionId: input.sessionID,
|
|
14483
14984
|
injectContext,
|
|
14484
|
-
log:
|
|
14985
|
+
log: log7
|
|
14485
14986
|
}, { client: sharedClient2, cache: sharedCache, mode: runtimeMode });
|
|
14486
14987
|
});
|
|
14487
14988
|
},
|
|
14488
14989
|
event: async ({ event }) => {
|
|
14489
|
-
await safeAsync(
|
|
14990
|
+
await safeAsync(PLUGIN_NAME10, "event", async () => {
|
|
14490
14991
|
const e = event;
|
|
14491
14992
|
if (e.type !== "session.idle")
|
|
14492
14993
|
return;
|
|
@@ -14495,14 +14996,14 @@ var khAutoContextServer = async (ctx) => {
|
|
|
14495
14996
|
if (typeof sid !== "string" || !sid)
|
|
14496
14997
|
return;
|
|
14497
14998
|
sharedKhCache.onSessionEnd(sid);
|
|
14498
|
-
|
|
14999
|
+
log7.debug?.(`[${PLUGIN_NAME10}] session.idle: cleared shared cache`, {
|
|
14499
15000
|
sessionID: sid
|
|
14500
15001
|
});
|
|
14501
15002
|
});
|
|
14502
15003
|
}
|
|
14503
15004
|
};
|
|
14504
15005
|
};
|
|
14505
|
-
var
|
|
15006
|
+
var handler10 = khAutoContextServer;
|
|
14506
15007
|
|
|
14507
15008
|
// plugins/kh-reminder.ts
|
|
14508
15009
|
init_opencode_plugin_helpers();
|
|
@@ -14623,8 +15124,8 @@ async function condense(input, opts = {}) {
|
|
|
14623
15124
|
}
|
|
14624
15125
|
|
|
14625
15126
|
// plugins/kh-reminder.ts
|
|
14626
|
-
var
|
|
14627
|
-
logLifecycle(
|
|
15127
|
+
var PLUGIN_NAME11 = "kh-reminder";
|
|
15128
|
+
logLifecycle(PLUGIN_NAME11, "import", {});
|
|
14628
15129
|
var TRIGGER_WORDS_ZH = [
|
|
14629
15130
|
"怎么",
|
|
14630
15131
|
"怎样",
|
|
@@ -14770,7 +15271,7 @@ function evaluate(input) {
|
|
|
14770
15271
|
lastTriggerAt.set(sessionId, now);
|
|
14771
15272
|
return { triggered: true, reason, sessionId };
|
|
14772
15273
|
}
|
|
14773
|
-
function handleObserve(raw,
|
|
15274
|
+
function handleObserve(raw, log7) {
|
|
14774
15275
|
const ctx = raw ?? {};
|
|
14775
15276
|
if (!Array.isArray(ctx.messages))
|
|
14776
15277
|
return null;
|
|
@@ -14778,7 +15279,7 @@ function handleObserve(raw, log6) {
|
|
|
14778
15279
|
const result = evaluate(ctx);
|
|
14779
15280
|
if (result.triggered && result.reason) {
|
|
14780
15281
|
const reasonStr = formatReason(result.reason);
|
|
14781
|
-
|
|
15282
|
+
log7?.info(`[${PLUGIN_NAME11}] ⚡ KH 提醒(observe-only) · session=${result.sessionId} · ${reasonStr}`, {
|
|
14782
15283
|
sessionId: result.sessionId,
|
|
14783
15284
|
reason: result.reason,
|
|
14784
15285
|
suggestion: "调用 smart_search 查项目历史;完成后用 save_chat_insight 沉淀"
|
|
@@ -14786,7 +15287,7 @@ function handleObserve(raw, log6) {
|
|
|
14786
15287
|
}
|
|
14787
15288
|
return result;
|
|
14788
15289
|
} catch (err) {
|
|
14789
|
-
|
|
15290
|
+
log7?.warn(`[${PLUGIN_NAME11}] evaluate 异常(已隔离)`, {
|
|
14790
15291
|
error: err instanceof Error ? err.message : String(err)
|
|
14791
15292
|
});
|
|
14792
15293
|
return null;
|
|
@@ -14802,9 +15303,9 @@ function formatReason(r) {
|
|
|
14802
15303
|
return `no-search-in-recent-rounds (last ${r.rounds})`;
|
|
14803
15304
|
}
|
|
14804
15305
|
}
|
|
14805
|
-
var
|
|
15306
|
+
var log7 = makePluginLogger(PLUGIN_NAME11);
|
|
14806
15307
|
var khReminderServer = async (ctx) => {
|
|
14807
|
-
logLifecycle(
|
|
15308
|
+
logLifecycle(PLUGIN_NAME11, "activate", {
|
|
14808
15309
|
directory: ctx.directory,
|
|
14809
15310
|
threshold: DEFAULT_THRESHOLD,
|
|
14810
15311
|
cooldown_ms: COOLDOWN_MS,
|
|
@@ -14812,7 +15313,7 @@ var khReminderServer = async (ctx) => {
|
|
|
14812
15313
|
});
|
|
14813
15314
|
return {
|
|
14814
15315
|
"experimental.chat.messages.transform": async (_input, output) => {
|
|
14815
|
-
await safeAsync(
|
|
15316
|
+
await safeAsync(PLUGIN_NAME11, "experimental.chat.messages.transform", async () => {
|
|
14816
15317
|
const list = output.messages;
|
|
14817
15318
|
if (!Array.isArray(list) || list.length === 0)
|
|
14818
15319
|
return;
|
|
@@ -14828,10 +15329,10 @@ var khReminderServer = async (ctx) => {
|
|
|
14828
15329
|
`);
|
|
14829
15330
|
return { role, content };
|
|
14830
15331
|
});
|
|
14831
|
-
const result = handleObserve({ messages: flat, sessionId },
|
|
15332
|
+
const result = handleObserve({ messages: flat, sessionId }, log7);
|
|
14832
15333
|
if (!result)
|
|
14833
15334
|
return;
|
|
14834
|
-
safeWriteLog(
|
|
15335
|
+
safeWriteLog(PLUGIN_NAME11, {
|
|
14835
15336
|
hook: "experimental.chat.messages.transform",
|
|
14836
15337
|
mode: "observe-only",
|
|
14837
15338
|
sessionId: result.sessionId,
|
|
@@ -14844,7 +15345,7 @@ var khReminderServer = async (ctx) => {
|
|
|
14844
15345
|
}
|
|
14845
15346
|
};
|
|
14846
15347
|
};
|
|
14847
|
-
var
|
|
15348
|
+
var handler11 = khReminderServer;
|
|
14848
15349
|
|
|
14849
15350
|
// lib/memories.ts
|
|
14850
15351
|
import { promises as fs9 } from "node:fs";
|
|
@@ -14965,13 +15466,13 @@ async function recallMemories(query, opts, cfgRaw) {
|
|
|
14965
15466
|
}
|
|
14966
15467
|
}
|
|
14967
15468
|
const all = await listMemories({ scope: opts.scope, project: opts.project ?? cfg.projectName }, cfgRaw);
|
|
14968
|
-
const tokens =
|
|
15469
|
+
const tokens = tokenize2(query);
|
|
14969
15470
|
if (tokens.length === 0)
|
|
14970
15471
|
return [];
|
|
14971
15472
|
const now = cfg.now();
|
|
14972
15473
|
const scored = [];
|
|
14973
15474
|
for (const m of all) {
|
|
14974
|
-
const sim = bagOfWordsScore(tokens,
|
|
15475
|
+
const sim = bagOfWordsScore(tokens, tokenize2(m.content + " " + (m.tags ?? []).join(" ")));
|
|
14975
15476
|
if (sim <= 0)
|
|
14976
15477
|
continue;
|
|
14977
15478
|
const age = Math.max(0, now - m.updated_at);
|
|
@@ -15021,7 +15522,7 @@ ${lines.join(`
|
|
|
15021
15522
|
recalled: recalled.length
|
|
15022
15523
|
};
|
|
15023
15524
|
}
|
|
15024
|
-
function
|
|
15525
|
+
function tokenize2(s) {
|
|
15025
15526
|
return s.toLowerCase().split(/[^a-z0-9_\u4e00-\u9fa5]+/u).filter((t) => t.length >= 2);
|
|
15026
15527
|
}
|
|
15027
15528
|
function bagOfWordsScore(query, doc) {
|
|
@@ -15046,7 +15547,7 @@ function bagOfWordsScore(query, doc) {
|
|
|
15046
15547
|
|
|
15047
15548
|
// plugins/memories-context.ts
|
|
15048
15549
|
init_opencode_plugin_helpers();
|
|
15049
|
-
var
|
|
15550
|
+
var PLUGIN_NAME12 = "memories-context";
|
|
15050
15551
|
var INJECTION_MODE2 = "observe-only";
|
|
15051
15552
|
var DEFAULT_CONFIG6 = {
|
|
15052
15553
|
minTextLength: 8,
|
|
@@ -15135,23 +15636,23 @@ async function handleMessage3(raw, opts) {
|
|
|
15135
15636
|
const cfg = opts.cfg ?? DEFAULT_CONFIG6;
|
|
15136
15637
|
const cache2 = opts.cache ?? new QueryCache2(cfg.cacheTtlMs);
|
|
15137
15638
|
const ctx = raw ?? {};
|
|
15138
|
-
const
|
|
15639
|
+
const log8 = ctx.log;
|
|
15139
15640
|
const text = (ctx.content ?? "").trim();
|
|
15140
15641
|
if (!text)
|
|
15141
15642
|
return { kind: "noop", reason: "empty", mode: INJECTION_MODE2 };
|
|
15142
15643
|
const dir = parseDirective(text);
|
|
15143
15644
|
if (dir.kind !== "none") {
|
|
15144
|
-
return await handleDirective(dir, ctx, opts.memCfg,
|
|
15645
|
+
return await handleDirective(dir, ctx, opts.memCfg, log8);
|
|
15145
15646
|
}
|
|
15146
15647
|
if (!shouldRecall(text, cfg)) {
|
|
15147
|
-
|
|
15648
|
+
log8?.debug?.(`[${PLUGIN_NAME12}] skip (filter)`, { textLen: text.length });
|
|
15148
15649
|
return { kind: "noop", reason: "filtered", mode: INJECTION_MODE2 };
|
|
15149
15650
|
}
|
|
15150
15651
|
const query = extractQuery2(text);
|
|
15151
15652
|
if (!query)
|
|
15152
15653
|
return { kind: "noop", reason: "empty_query", mode: INJECTION_MODE2 };
|
|
15153
15654
|
if (cache2.shouldSkip(query)) {
|
|
15154
|
-
|
|
15655
|
+
log8?.debug?.(`[${PLUGIN_NAME12}] cache hit`, { query });
|
|
15155
15656
|
return { kind: "noop", reason: "cache", mode: INJECTION_MODE2 };
|
|
15156
15657
|
}
|
|
15157
15658
|
const racer = opts.scheduler?.raceTimeout ?? (async (p, ms) => {
|
|
@@ -15176,7 +15677,7 @@ async function handleMessage3(raw, opts) {
|
|
|
15176
15677
|
}, opts.memCfg);
|
|
15177
15678
|
const result = await racer(injectPromise, cfg.timeoutMs);
|
|
15178
15679
|
if (result === "__timeout__") {
|
|
15179
|
-
|
|
15680
|
+
log8?.warn(`[${PLUGIN_NAME12}] timeout`, { query, ms: cfg.timeoutMs });
|
|
15180
15681
|
cache2.record(query, 0);
|
|
15181
15682
|
return { kind: "noop", reason: "timeout", mode: INJECTION_MODE2 };
|
|
15182
15683
|
}
|
|
@@ -15188,29 +15689,29 @@ async function handleMessage3(raw, opts) {
|
|
|
15188
15689
|
try {
|
|
15189
15690
|
await ctx.injectContext(result.text);
|
|
15190
15691
|
} catch (err) {
|
|
15191
|
-
|
|
15692
|
+
log8?.warn(`[${PLUGIN_NAME12}] injectContext threw`, {
|
|
15192
15693
|
error: err instanceof Error ? err.message : String(err)
|
|
15193
15694
|
});
|
|
15194
15695
|
}
|
|
15195
15696
|
}
|
|
15196
15697
|
cache2.record(query, result.recalled);
|
|
15197
|
-
|
|
15698
|
+
log8?.info(`[${PLUGIN_NAME12}] observe-only: ${result.used}/${result.recalled} memories ready`, { query, mode: INJECTION_MODE2 });
|
|
15198
15699
|
return { kind: "injected", payload: result, mode: INJECTION_MODE2 };
|
|
15199
15700
|
}
|
|
15200
|
-
async function handleDirective(dir, ctx, memCfg,
|
|
15701
|
+
async function handleDirective(dir, ctx, memCfg, log8) {
|
|
15201
15702
|
if (dir.kind === "remember") {
|
|
15202
15703
|
const r = await addMemory({ scope: dir.scope, content: dir.content, source: "directive" }, memCfg);
|
|
15203
15704
|
if (r.ok) {
|
|
15204
15705
|
const target = r.written_to === "kh" ? "KH" : "本地";
|
|
15205
15706
|
await safeReply(ctx, `\uD83E\uDDE0 已记住(${dir.scope} / ${target},id=${r.id})`);
|
|
15206
|
-
|
|
15707
|
+
log8?.info(`[${PLUGIN_NAME12}] /remember ok`, {
|
|
15207
15708
|
scope: dir.scope,
|
|
15208
15709
|
id: r.id,
|
|
15209
15710
|
mode: INJECTION_MODE2
|
|
15210
15711
|
});
|
|
15211
15712
|
} else {
|
|
15212
15713
|
await safeReply(ctx, `❌ 记忆失败:${r.error ?? "unknown"}`);
|
|
15213
|
-
|
|
15714
|
+
log8?.warn(`[${PLUGIN_NAME12}] /remember failed`, { error: r.error });
|
|
15214
15715
|
}
|
|
15215
15716
|
return { kind: "remembered", result: r, mode: INJECTION_MODE2 };
|
|
15216
15717
|
}
|
|
@@ -15241,22 +15742,22 @@ async function safeReply(ctx, text) {
|
|
|
15241
15742
|
await ctx.reply(text);
|
|
15242
15743
|
} catch {}
|
|
15243
15744
|
}
|
|
15244
|
-
logLifecycle(
|
|
15745
|
+
logLifecycle(PLUGIN_NAME12, "import");
|
|
15245
15746
|
var sharedCache2 = new QueryCache2(DEFAULT_CONFIG6.cacheTtlMs);
|
|
15246
15747
|
function buildMemCfg(directory) {
|
|
15247
15748
|
return { projectRoot: directory };
|
|
15248
15749
|
}
|
|
15249
15750
|
var memoriesContextServer = async (ctx) => {
|
|
15250
|
-
const
|
|
15751
|
+
const log8 = makePluginLogger(PLUGIN_NAME12);
|
|
15251
15752
|
const memCfg = buildMemCfg(ctx.directory);
|
|
15252
|
-
logLifecycle(
|
|
15753
|
+
logLifecycle(PLUGIN_NAME12, "activate", {
|
|
15253
15754
|
directory: ctx.directory,
|
|
15254
15755
|
projectRoot: memCfg.projectRoot,
|
|
15255
15756
|
mode: INJECTION_MODE2
|
|
15256
15757
|
});
|
|
15257
15758
|
return {
|
|
15258
15759
|
"chat.message": async (input, output) => {
|
|
15259
|
-
await safeAsync(
|
|
15760
|
+
await safeAsync(PLUGIN_NAME12, "chat.message", async () => {
|
|
15260
15761
|
const text = extractUserText(output);
|
|
15261
15762
|
if (!text)
|
|
15262
15763
|
return;
|
|
@@ -15264,36 +15765,36 @@ var memoriesContextServer = async (ctx) => {
|
|
|
15264
15765
|
content: text,
|
|
15265
15766
|
sessionId: input.sessionID,
|
|
15266
15767
|
injectContext: async (markdown) => {
|
|
15267
|
-
|
|
15768
|
+
log8.info(`memories context candidate (${markdown.length} chars)`, {
|
|
15268
15769
|
sessionID: input.sessionID,
|
|
15269
15770
|
mode: INJECTION_MODE2,
|
|
15270
15771
|
preview: markdown.slice(0, 200)
|
|
15271
15772
|
});
|
|
15272
15773
|
},
|
|
15273
15774
|
reply: async (msg) => {
|
|
15274
|
-
|
|
15775
|
+
log8.info(`directive reply (observe-only): ${msg}`, {
|
|
15275
15776
|
sessionID: input.sessionID,
|
|
15276
15777
|
mode: INJECTION_MODE2
|
|
15277
15778
|
});
|
|
15278
15779
|
},
|
|
15279
|
-
log:
|
|
15780
|
+
log: log8
|
|
15280
15781
|
}, { cache: sharedCache2, memCfg });
|
|
15281
15782
|
});
|
|
15282
15783
|
}
|
|
15283
15784
|
};
|
|
15284
15785
|
};
|
|
15285
|
-
var
|
|
15786
|
+
var handler12 = memoriesContextServer;
|
|
15286
15787
|
|
|
15287
15788
|
// plugins/model-fallback.ts
|
|
15288
15789
|
init_opencode_plugin_helpers();
|
|
15289
|
-
var
|
|
15790
|
+
var PLUGIN_NAME13 = "model-fallback";
|
|
15290
15791
|
var state2 = {
|
|
15291
15792
|
config: null,
|
|
15292
15793
|
configPath: null,
|
|
15293
15794
|
warnings: [],
|
|
15294
15795
|
error: null
|
|
15295
15796
|
};
|
|
15296
|
-
logLifecycle(
|
|
15797
|
+
logLifecycle(PLUGIN_NAME13, "import");
|
|
15297
15798
|
function loadOnce(root) {
|
|
15298
15799
|
if (state2.config !== null || state2.error !== null)
|
|
15299
15800
|
return;
|
|
@@ -15303,11 +15804,11 @@ function loadOnce(root) {
|
|
|
15303
15804
|
state2.configPath = r.path ?? null;
|
|
15304
15805
|
state2.warnings = r.warnings;
|
|
15305
15806
|
if (r.warnings.length > 0) {
|
|
15306
|
-
safeWriteLog(
|
|
15807
|
+
safeWriteLog(PLUGIN_NAME13, { phase: "load.warnings", warnings: r.warnings });
|
|
15307
15808
|
}
|
|
15308
15809
|
} else {
|
|
15309
15810
|
state2.error = r.error ?? "unknown_load_error";
|
|
15310
|
-
safeWriteLog(
|
|
15811
|
+
safeWriteLog(PLUGIN_NAME13, { phase: "load.failed", error: state2.error, path: r.path });
|
|
15311
15812
|
}
|
|
15312
15813
|
}
|
|
15313
15814
|
var MODEL_ERR_PATTERNS = [
|
|
@@ -15352,9 +15853,9 @@ fallback 链已用尽:${meta.chain.join(" → ")}`;
|
|
|
15352
15853
|
完整链:${meta.chain.join(" → ")}`;
|
|
15353
15854
|
}
|
|
15354
15855
|
var modelFallbackServer = async (ctx) => {
|
|
15355
|
-
const
|
|
15856
|
+
const log8 = makePluginLogger(PLUGIN_NAME13);
|
|
15356
15857
|
loadOnce(ctx.directory ?? process.cwd());
|
|
15357
|
-
logLifecycle(
|
|
15858
|
+
logLifecycle(PLUGIN_NAME13, "activate", {
|
|
15358
15859
|
directory: ctx.directory,
|
|
15359
15860
|
config_path: state2.configPath,
|
|
15360
15861
|
config_loaded: state2.config !== null,
|
|
@@ -15364,7 +15865,7 @@ var modelFallbackServer = async (ctx) => {
|
|
|
15364
15865
|
});
|
|
15365
15866
|
return {
|
|
15366
15867
|
"chat.params": async (input, output) => {
|
|
15367
|
-
await safeAsync(
|
|
15868
|
+
await safeAsync(PLUGIN_NAME13, "chat.params", async () => {
|
|
15368
15869
|
if (!state2.config)
|
|
15369
15870
|
return;
|
|
15370
15871
|
const agent = input.agent;
|
|
@@ -15385,7 +15886,7 @@ var modelFallbackServer = async (ctx) => {
|
|
|
15385
15886
|
next: meta.next_fallback,
|
|
15386
15887
|
source: meta.source
|
|
15387
15888
|
};
|
|
15388
|
-
safeWriteLog(
|
|
15889
|
+
safeWriteLog(PLUGIN_NAME13, {
|
|
15389
15890
|
hook: "chat.params",
|
|
15390
15891
|
agent,
|
|
15391
15892
|
model: currentModel,
|
|
@@ -15395,7 +15896,7 @@ var modelFallbackServer = async (ctx) => {
|
|
|
15395
15896
|
});
|
|
15396
15897
|
},
|
|
15397
15898
|
event: async ({ event }) => {
|
|
15398
|
-
await safeAsync(
|
|
15899
|
+
await safeAsync(PLUGIN_NAME13, "event", async () => {
|
|
15399
15900
|
if (!state2.config)
|
|
15400
15901
|
return;
|
|
15401
15902
|
const e = event;
|
|
@@ -15411,8 +15912,8 @@ var modelFallbackServer = async (ctx) => {
|
|
|
15411
15912
|
const model = props.model ?? "unknown/unknown";
|
|
15412
15913
|
const meta = buildFallbackMeta(state2.config, agent, model);
|
|
15413
15914
|
const suggestion = meta ? buildSuggestion(meta, String(message)) : `⚠️ ${agent}/${model} 失败:${message}`;
|
|
15414
|
-
|
|
15415
|
-
safeWriteLog(
|
|
15915
|
+
log8.warn(`[${PLUGIN_NAME13}] ${suggestion}`);
|
|
15916
|
+
safeWriteLog(PLUGIN_NAME13, {
|
|
15416
15917
|
hook: "event.error",
|
|
15417
15918
|
eventType: e.type,
|
|
15418
15919
|
agent,
|
|
@@ -15424,12 +15925,12 @@ var modelFallbackServer = async (ctx) => {
|
|
|
15424
15925
|
}
|
|
15425
15926
|
};
|
|
15426
15927
|
};
|
|
15427
|
-
var
|
|
15928
|
+
var handler13 = modelFallbackServer;
|
|
15428
15929
|
|
|
15429
15930
|
// plugins/subtask-heartbeat.ts
|
|
15430
15931
|
init_opencode_plugin_helpers();
|
|
15431
|
-
var
|
|
15432
|
-
logLifecycle(
|
|
15932
|
+
var PLUGIN_NAME14 = "subtask-heartbeat";
|
|
15933
|
+
logLifecycle(PLUGIN_NAME14, "import", {});
|
|
15433
15934
|
var HEARTBEAT_INTERVAL_MS2 = 30000;
|
|
15434
15935
|
var HEARTBEAT_DEBOUNCE_MS = 25000;
|
|
15435
15936
|
var TOAST_DURATION_MS3 = 5000;
|
|
@@ -15663,9 +16164,9 @@ function normalizeVariant3(raw) {
|
|
|
15663
16164
|
return "default";
|
|
15664
16165
|
return raw;
|
|
15665
16166
|
}
|
|
15666
|
-
async function showToast3(client, payload,
|
|
16167
|
+
async function showToast3(client, payload, log8) {
|
|
15667
16168
|
if (typeof client?.tui?.showToast !== "function") {
|
|
15668
|
-
|
|
16169
|
+
log8?.debug?.("tui.showToast 不可用,noop");
|
|
15669
16170
|
return false;
|
|
15670
16171
|
}
|
|
15671
16172
|
try {
|
|
@@ -15679,32 +16180,32 @@ async function showToast3(client, payload, log7) {
|
|
|
15679
16180
|
});
|
|
15680
16181
|
return true;
|
|
15681
16182
|
} catch (err) {
|
|
15682
|
-
|
|
16183
|
+
log8?.warn("tui.showToast 抛错(已隔离)", {
|
|
15683
16184
|
error: err instanceof Error ? err.message : String(err)
|
|
15684
16185
|
});
|
|
15685
16186
|
return false;
|
|
15686
16187
|
}
|
|
15687
16188
|
}
|
|
15688
|
-
var
|
|
16189
|
+
var log8 = makePluginLogger(PLUGIN_NAME14);
|
|
15689
16190
|
var subtaskHeartbeatServer = async (ctx) => {
|
|
15690
|
-
logLifecycle(
|
|
16191
|
+
logLifecycle(PLUGIN_NAME14, "activate", {
|
|
15691
16192
|
directory: ctx.directory,
|
|
15692
16193
|
intervalMs: HEARTBEAT_INTERVAL_MS2
|
|
15693
16194
|
});
|
|
15694
16195
|
const client = ctx.client;
|
|
15695
16196
|
const interval = setInterval(() => {
|
|
15696
|
-
safeAsync(
|
|
16197
|
+
safeAsync(PLUGIN_NAME14, "interval", async () => {
|
|
15697
16198
|
const swept = sweepExpiredPendingTasks();
|
|
15698
16199
|
if (swept > 0) {
|
|
15699
|
-
safeWriteLog(
|
|
16200
|
+
safeWriteLog(PLUGIN_NAME14, { hook: "interval", pending_task_swept: swept });
|
|
15700
16201
|
}
|
|
15701
16202
|
const beats = pickHeartbeats();
|
|
15702
16203
|
if (beats.length === 0)
|
|
15703
16204
|
return;
|
|
15704
16205
|
for (const r of beats) {
|
|
15705
16206
|
const t = buildHeartbeatToast(r);
|
|
15706
|
-
const sent = await showToast3(client, t,
|
|
15707
|
-
safeWriteLog(
|
|
16207
|
+
const sent = await showToast3(client, t, log8);
|
|
16208
|
+
safeWriteLog(PLUGIN_NAME14, {
|
|
15708
16209
|
hook: "interval",
|
|
15709
16210
|
child: r.childID,
|
|
15710
16211
|
parent: r.parentID,
|
|
@@ -15721,7 +16222,7 @@ var subtaskHeartbeatServer = async (ctx) => {
|
|
|
15721
16222
|
}
|
|
15722
16223
|
return {
|
|
15723
16224
|
event: async ({ event }) => {
|
|
15724
|
-
await safeAsync(
|
|
16225
|
+
await safeAsync(PLUGIN_NAME14, "event", async () => {
|
|
15725
16226
|
const created = extractCreatedChild(event);
|
|
15726
16227
|
if (created) {
|
|
15727
16228
|
const pending = dequeuePendingTask(created.parentID);
|
|
@@ -15731,7 +16232,7 @@ var subtaskHeartbeatServer = async (ctx) => {
|
|
|
15731
16232
|
agent: pending?.agent ?? created.agent,
|
|
15732
16233
|
description: pending?.description ?? null
|
|
15733
16234
|
});
|
|
15734
|
-
safeWriteLog(
|
|
16235
|
+
safeWriteLog(PLUGIN_NAME14, {
|
|
15735
16236
|
hook: "event",
|
|
15736
16237
|
type: "session.created",
|
|
15737
16238
|
child: created.childID,
|
|
@@ -15741,8 +16242,8 @@ var subtaskHeartbeatServer = async (ctx) => {
|
|
|
15741
16242
|
description_len: record.description?.length ?? 0
|
|
15742
16243
|
});
|
|
15743
16244
|
const startToast = buildStartToast(record);
|
|
15744
|
-
const sent = await showToast3(client, { ...startToast, duration: START_TOAST_DURATION_MS },
|
|
15745
|
-
safeWriteLog(
|
|
16245
|
+
const sent = await showToast3(client, { ...startToast, duration: START_TOAST_DURATION_MS }, log8);
|
|
16246
|
+
safeWriteLog(PLUGIN_NAME14, {
|
|
15746
16247
|
hook: "event",
|
|
15747
16248
|
type: "session.created.toast",
|
|
15748
16249
|
child: created.childID,
|
|
@@ -15756,8 +16257,8 @@ var subtaskHeartbeatServer = async (ctx) => {
|
|
|
15756
16257
|
const r = clearInflight2(ended.sessionID);
|
|
15757
16258
|
if (r) {
|
|
15758
16259
|
const t = buildEndToast(r, ended.type);
|
|
15759
|
-
const sent = await showToast3(client, t,
|
|
15760
|
-
safeWriteLog(
|
|
16260
|
+
const sent = await showToast3(client, t, log8);
|
|
16261
|
+
safeWriteLog(PLUGIN_NAME14, {
|
|
15761
16262
|
hook: "event",
|
|
15762
16263
|
type: ended.type,
|
|
15763
16264
|
child: r.childID,
|
|
@@ -15773,12 +16274,12 @@ var subtaskHeartbeatServer = async (ctx) => {
|
|
|
15773
16274
|
const isTaskTool = input?.tool === "task";
|
|
15774
16275
|
if (inflight3.size === 0 && !isTaskTool)
|
|
15775
16276
|
return;
|
|
15776
|
-
await safeAsync(
|
|
16277
|
+
await safeAsync(PLUGIN_NAME14, "tool.execute.before", async () => {
|
|
15777
16278
|
if (!input || typeof input.sessionID !== "string" || typeof input.tool !== "string")
|
|
15778
16279
|
return;
|
|
15779
16280
|
if (isTaskTool) {
|
|
15780
16281
|
const args = output?.args ?? null;
|
|
15781
|
-
safeWriteLog(
|
|
16282
|
+
safeWriteLog(PLUGIN_NAME14, {
|
|
15782
16283
|
hook: "tool.execute.before.task",
|
|
15783
16284
|
sessionID: input.sessionID,
|
|
15784
16285
|
args_keys: args && typeof args === "object" ? Object.keys(args) : null,
|
|
@@ -15790,7 +16291,7 @@ var subtaskHeartbeatServer = async (ctx) => {
|
|
|
15790
16291
|
agent: extracted.subagentType,
|
|
15791
16292
|
description: extracted.description
|
|
15792
16293
|
});
|
|
15793
|
-
safeWriteLog(
|
|
16294
|
+
safeWriteLog(PLUGIN_NAME14, {
|
|
15794
16295
|
hook: "tool.execute.before.task.enqueued",
|
|
15795
16296
|
parent: input.sessionID,
|
|
15796
16297
|
agent: extracted.subagentType,
|
|
@@ -15805,12 +16306,12 @@ var subtaskHeartbeatServer = async (ctx) => {
|
|
|
15805
16306
|
}
|
|
15806
16307
|
};
|
|
15807
16308
|
};
|
|
15808
|
-
var
|
|
16309
|
+
var handler14 = subtaskHeartbeatServer;
|
|
15809
16310
|
|
|
15810
16311
|
// plugins/parallel-status.ts
|
|
15811
16312
|
init_opencode_plugin_helpers();
|
|
15812
|
-
var
|
|
15813
|
-
logLifecycle(
|
|
16313
|
+
var PLUGIN_NAME15 = "parallel-status";
|
|
16314
|
+
logLifecycle(PLUGIN_NAME15, "import");
|
|
15814
16315
|
var ID_MAX_LEN = 16;
|
|
15815
16316
|
var ID_KEEP_LEN = 13;
|
|
15816
16317
|
function shortId(s) {
|
|
@@ -15842,8 +16343,8 @@ function formatInflightMarkdown(snapshot, now = Date.now()) {
|
|
|
15842
16343
|
`);
|
|
15843
16344
|
}
|
|
15844
16345
|
var parallelStatusServer = async (ctx) => {
|
|
15845
|
-
const
|
|
15846
|
-
logLifecycle(
|
|
16346
|
+
const log9 = makePluginLogger(PLUGIN_NAME15);
|
|
16347
|
+
logLifecycle(PLUGIN_NAME15, "activate", { directory: ctx.directory });
|
|
15847
16348
|
return {
|
|
15848
16349
|
"command.execute.before": async (input, output) => {
|
|
15849
16350
|
try {
|
|
@@ -15862,72 +16363,24 @@ var parallelStatusServer = async (ctx) => {
|
|
|
15862
16363
|
synthetic: false
|
|
15863
16364
|
});
|
|
15864
16365
|
}
|
|
15865
|
-
|
|
16366
|
+
log9.info(`[${PLUGIN_NAME15}] 已回写 ${snapshot.length} 条 inflight`);
|
|
15866
16367
|
} catch (err) {
|
|
15867
|
-
|
|
16368
|
+
log9.error(`[${PLUGIN_NAME15}] command.execute.before 异常(已隔离)`, {
|
|
15868
16369
|
error: err instanceof Error ? err.message : String(err)
|
|
15869
16370
|
});
|
|
15870
16371
|
}
|
|
15871
16372
|
}
|
|
15872
16373
|
};
|
|
15873
16374
|
};
|
|
15874
|
-
var
|
|
16375
|
+
var handler15 = parallelStatusServer;
|
|
15875
16376
|
|
|
15876
16377
|
// plugins/parallel-tool-nudge.ts
|
|
15877
|
-
import { readFileSync as
|
|
15878
|
-
import { join as
|
|
16378
|
+
import { readFileSync as readFileSync5, readdirSync as readdirSync2, statSync as statSync4 } from "node:fs";
|
|
16379
|
+
import { join as join13 } from "node:path";
|
|
15879
16380
|
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
|
|
15928
16381
|
init_opencode_plugin_helpers();
|
|
15929
|
-
var
|
|
15930
|
-
logLifecycle(
|
|
16382
|
+
var PLUGIN_NAME16 = "parallel-tool-nudge";
|
|
16383
|
+
logLifecycle(PLUGIN_NAME16, "import", {});
|
|
15931
16384
|
var PARALLEL_SAFE_TOOLS = [
|
|
15932
16385
|
"read",
|
|
15933
16386
|
"smart_search",
|
|
@@ -15937,17 +16390,17 @@ var PARALLEL_SAFE_TOOLS = [
|
|
|
15937
16390
|
"get_working_memory"
|
|
15938
16391
|
];
|
|
15939
16392
|
var DEFAULT_AGENT_KEY = "__default__";
|
|
15940
|
-
var
|
|
16393
|
+
var NUDGE_MAX_LEN2 = 800;
|
|
15941
16394
|
var agentToolsMap = new Map;
|
|
15942
|
-
function
|
|
15943
|
-
return
|
|
16395
|
+
function defaultReader2(p) {
|
|
16396
|
+
return readFileSync5(p, "utf8");
|
|
15944
16397
|
}
|
|
15945
|
-
function
|
|
15946
|
-
return
|
|
16398
|
+
function defaultDirReader2(p) {
|
|
16399
|
+
return readdirSync2(p);
|
|
15947
16400
|
}
|
|
15948
|
-
function
|
|
16401
|
+
function defaultDirExists2(p) {
|
|
15949
16402
|
try {
|
|
15950
|
-
return
|
|
16403
|
+
return statSync4(p).isDirectory();
|
|
15951
16404
|
} catch {
|
|
15952
16405
|
return false;
|
|
15953
16406
|
}
|
|
@@ -15977,19 +16430,19 @@ function parseAgentFrontmatter(content) {
|
|
|
15977
16430
|
return { name, allowedTools };
|
|
15978
16431
|
}
|
|
15979
16432
|
function loadAgentToolsMap(rootDir, opts = {}) {
|
|
15980
|
-
const reader = opts.reader ??
|
|
15981
|
-
const dirReader = opts.dirReader ??
|
|
15982
|
-
const dirExists = opts.dirExists ??
|
|
15983
|
-
const homeAgentsDir = opts.homeAgentsDir ??
|
|
16433
|
+
const reader = opts.reader ?? defaultReader2;
|
|
16434
|
+
const dirReader = opts.dirReader ?? defaultDirReader2;
|
|
16435
|
+
const dirExists = opts.dirExists ?? defaultDirExists2;
|
|
16436
|
+
const homeAgentsDir = opts.homeAgentsDir ?? join13(homedir6(), ".config", "opencode", "agents");
|
|
15984
16437
|
const candidateDirs = [
|
|
15985
|
-
|
|
15986
|
-
|
|
16438
|
+
join13(rootDir, ".codeforge", "agents"),
|
|
16439
|
+
join13(rootDir, "agents"),
|
|
15987
16440
|
homeAgentsDir
|
|
15988
16441
|
];
|
|
15989
16442
|
const result = new Map;
|
|
15990
16443
|
const safeSet = new Set(PARALLEL_SAFE_TOOLS);
|
|
15991
16444
|
const unionTools = new Set;
|
|
15992
|
-
const
|
|
16445
|
+
const log9 = makePluginLogger(PLUGIN_NAME16);
|
|
15993
16446
|
for (const dir of candidateDirs) {
|
|
15994
16447
|
if (!dirExists(dir))
|
|
15995
16448
|
continue;
|
|
@@ -15997,7 +16450,7 @@ function loadAgentToolsMap(rootDir, opts = {}) {
|
|
|
15997
16450
|
try {
|
|
15998
16451
|
entries = dirReader(dir);
|
|
15999
16452
|
} catch (err) {
|
|
16000
|
-
|
|
16453
|
+
log9.warn(`agents 目录读取失败(已跳过)`, {
|
|
16001
16454
|
dir,
|
|
16002
16455
|
error: err instanceof Error ? err.message : String(err)
|
|
16003
16456
|
});
|
|
@@ -16006,12 +16459,12 @@ function loadAgentToolsMap(rootDir, opts = {}) {
|
|
|
16006
16459
|
for (const entry of entries) {
|
|
16007
16460
|
if (!entry.endsWith(".md"))
|
|
16008
16461
|
continue;
|
|
16009
|
-
const path13 =
|
|
16462
|
+
const path13 = join13(dir, entry);
|
|
16010
16463
|
let content;
|
|
16011
16464
|
try {
|
|
16012
16465
|
content = reader(path13);
|
|
16013
16466
|
} catch (err) {
|
|
16014
|
-
|
|
16467
|
+
log9.warn(`agent.md 读取失败(已跳过)`, {
|
|
16015
16468
|
path: path13,
|
|
16016
16469
|
error: err instanceof Error ? err.message : String(err)
|
|
16017
16470
|
});
|
|
@@ -16019,7 +16472,7 @@ function loadAgentToolsMap(rootDir, opts = {}) {
|
|
|
16019
16472
|
}
|
|
16020
16473
|
const parsed = parseAgentFrontmatter(content);
|
|
16021
16474
|
if (!parsed) {
|
|
16022
|
-
|
|
16475
|
+
log9.warn(`agent frontmatter 解析失败(已跳过)`, { path: path13 });
|
|
16023
16476
|
continue;
|
|
16024
16477
|
}
|
|
16025
16478
|
if (result.has(parsed.name))
|
|
@@ -16040,18 +16493,18 @@ function loadAgentToolsMap(rootDir, opts = {}) {
|
|
|
16040
16493
|
return result;
|
|
16041
16494
|
}
|
|
16042
16495
|
var sessionAgentMap = new Map;
|
|
16043
|
-
var
|
|
16044
|
-
var
|
|
16045
|
-
function
|
|
16046
|
-
while (sessionAgentMap.size >
|
|
16496
|
+
var SESSION_CAP2 = 200;
|
|
16497
|
+
var SESSION_TTL_MS2 = 24 * 60 * 60 * 1000;
|
|
16498
|
+
function pruneIfOversize2() {
|
|
16499
|
+
while (sessionAgentMap.size > SESSION_CAP2) {
|
|
16047
16500
|
const oldestKey = sessionAgentMap.keys().next().value;
|
|
16048
16501
|
if (oldestKey === undefined)
|
|
16049
16502
|
break;
|
|
16050
16503
|
sessionAgentMap.delete(oldestKey);
|
|
16051
16504
|
}
|
|
16052
16505
|
}
|
|
16053
|
-
function
|
|
16054
|
-
return now - entry.ts >
|
|
16506
|
+
function isExpired2(entry, now) {
|
|
16507
|
+
return now - entry.ts > SESSION_TTL_MS2;
|
|
16055
16508
|
}
|
|
16056
16509
|
function resolveAgent(sessionID, nowFn = Date.now) {
|
|
16057
16510
|
if (!sessionID)
|
|
@@ -16060,7 +16513,7 @@ function resolveAgent(sessionID, nowFn = Date.now) {
|
|
|
16060
16513
|
if (!entry)
|
|
16061
16514
|
return "unknown";
|
|
16062
16515
|
const now = nowFn();
|
|
16063
|
-
if (
|
|
16516
|
+
if (isExpired2(entry, now)) {
|
|
16064
16517
|
sessionAgentMap.delete(sessionID);
|
|
16065
16518
|
return "unknown";
|
|
16066
16519
|
}
|
|
@@ -16092,12 +16545,12 @@ Heuristics:
|
|
|
16092
16545
|
|
|
16093
16546
|
This directive is re-injected every turn — it is not optional advice.
|
|
16094
16547
|
────────────────────────────────────────`;
|
|
16095
|
-
if (body.length <=
|
|
16548
|
+
if (body.length <= NUDGE_MAX_LEN2)
|
|
16096
16549
|
return body;
|
|
16097
|
-
return body.slice(0,
|
|
16550
|
+
return body.slice(0, NUDGE_MAX_LEN2 - 4) + `
|
|
16098
16551
|
…`;
|
|
16099
16552
|
}
|
|
16100
|
-
var
|
|
16553
|
+
var log9 = makePluginLogger(PLUGIN_NAME16);
|
|
16101
16554
|
var parallelToolNudgeServer = async (ctx) => {
|
|
16102
16555
|
try {
|
|
16103
16556
|
const loaded = loadAgentToolsMap(ctx.directory ?? process.cwd());
|
|
@@ -16105,30 +16558,30 @@ var parallelToolNudgeServer = async (ctx) => {
|
|
|
16105
16558
|
for (const [k, v] of loaded.entries())
|
|
16106
16559
|
agentToolsMap.set(k, v);
|
|
16107
16560
|
} catch (err) {
|
|
16108
|
-
|
|
16561
|
+
log9.warn(`loadAgentToolsMap 失败(plugin 将 no-op)`, {
|
|
16109
16562
|
error: err instanceof Error ? err.message : String(err)
|
|
16110
16563
|
});
|
|
16111
16564
|
}
|
|
16112
16565
|
const realAgentCount = Math.max(0, agentToolsMap.size - (agentToolsMap.has(DEFAULT_AGENT_KEY) ? 1 : 0));
|
|
16113
16566
|
if (realAgentCount === 0) {
|
|
16114
16567
|
agentToolsMap.clear();
|
|
16115
|
-
|
|
16568
|
+
log9.warn(`0 real agents loaded; plugin will be no-op for this session`, {
|
|
16116
16569
|
directory: ctx.directory
|
|
16117
16570
|
});
|
|
16118
16571
|
}
|
|
16119
16572
|
const defaultTools = agentToolsMap.get(DEFAULT_AGENT_KEY) ?? [];
|
|
16120
|
-
logLifecycle(
|
|
16573
|
+
logLifecycle(PLUGIN_NAME16, "activate", {
|
|
16121
16574
|
directory: ctx.directory,
|
|
16122
16575
|
real_agents_loaded: realAgentCount,
|
|
16123
16576
|
default_tools_union: defaultTools,
|
|
16124
16577
|
parallel_safe_whitelist: PARALLEL_SAFE_TOOLS,
|
|
16125
|
-
session_cap:
|
|
16126
|
-
session_ttl_ms:
|
|
16578
|
+
session_cap: SESSION_CAP2,
|
|
16579
|
+
session_ttl_ms: SESSION_TTL_MS2,
|
|
16127
16580
|
no_op: agentToolsMap.size === 0
|
|
16128
16581
|
});
|
|
16129
16582
|
return {
|
|
16130
16583
|
"chat.params": async (input, _output) => {
|
|
16131
|
-
await safeAsync(
|
|
16584
|
+
await safeAsync(PLUGIN_NAME16, "chat.params", async () => {
|
|
16132
16585
|
if (agentToolsMap.size === 0)
|
|
16133
16586
|
return;
|
|
16134
16587
|
const sid = input?.sessionID;
|
|
@@ -16136,11 +16589,11 @@ var parallelToolNudgeServer = async (ctx) => {
|
|
|
16136
16589
|
if (!sid || !agent)
|
|
16137
16590
|
return;
|
|
16138
16591
|
sessionAgentMap.set(sid, { agent, ts: Date.now() });
|
|
16139
|
-
|
|
16592
|
+
pruneIfOversize2();
|
|
16140
16593
|
});
|
|
16141
16594
|
},
|
|
16142
16595
|
"experimental.chat.system.transform": async (input, output) => {
|
|
16143
|
-
await safeAsync(
|
|
16596
|
+
await safeAsync(PLUGIN_NAME16, "experimental.chat.system.transform", async () => {
|
|
16144
16597
|
if (agentToolsMap.size === 0)
|
|
16145
16598
|
return;
|
|
16146
16599
|
if (!output || !Array.isArray(output.system))
|
|
@@ -16150,7 +16603,7 @@ var parallelToolNudgeServer = async (ctx) => {
|
|
|
16150
16603
|
const tools = agent !== "unknown" ? agentToolsMap.get(agent) ?? agentToolsMap.get(DEFAULT_AGENT_KEY) ?? [] : agentToolsMap.get(DEFAULT_AGENT_KEY) ?? [];
|
|
16151
16604
|
const nudge = renderNudge(agent, tools);
|
|
16152
16605
|
output.system.push(nudge);
|
|
16153
|
-
safeWriteLog(
|
|
16606
|
+
safeWriteLog(PLUGIN_NAME16, {
|
|
16154
16607
|
hook: "experimental.chat.system.transform",
|
|
16155
16608
|
sessionID: sid,
|
|
16156
16609
|
agent,
|
|
@@ -16162,12 +16615,12 @@ var parallelToolNudgeServer = async (ctx) => {
|
|
|
16162
16615
|
}
|
|
16163
16616
|
};
|
|
16164
16617
|
};
|
|
16165
|
-
var
|
|
16618
|
+
var handler16 = parallelToolNudgeServer;
|
|
16166
16619
|
|
|
16167
16620
|
// plugins/pwsh-utf8.ts
|
|
16168
16621
|
init_opencode_plugin_helpers();
|
|
16169
|
-
var
|
|
16170
|
-
logLifecycle(
|
|
16622
|
+
var PLUGIN_NAME17 = "pwsh-utf8";
|
|
16623
|
+
logLifecycle(PLUGIN_NAME17, "import", {});
|
|
16171
16624
|
var PRELUDE = "chcp 65001 *> $null; " + "[Console]::OutputEncoding = [System.Text.UTF8Encoding]::new(); " + "$OutputEncoding = [System.Text.UTF8Encoding]::new(); ";
|
|
16172
16625
|
function prependUtf8Prelude(command) {
|
|
16173
16626
|
if (typeof command !== "string")
|
|
@@ -16180,15 +16633,15 @@ function prependUtf8Prelude(command) {
|
|
|
16180
16633
|
return command;
|
|
16181
16634
|
return PRELUDE + command;
|
|
16182
16635
|
}
|
|
16183
|
-
var
|
|
16636
|
+
var handler17 = async (_ctx) => {
|
|
16184
16637
|
const enabled = process.platform === "win32" && process.env.CODEFORGE_DISABLE_PWSH_UTF8 !== "1";
|
|
16185
16638
|
const reason = enabled ? "win32" : process.platform !== "win32" ? "non-win32" : "disabled-by-env";
|
|
16186
|
-
logLifecycle(
|
|
16639
|
+
logLifecycle(PLUGIN_NAME17, "activate", { enabled, platform: process.platform, reason });
|
|
16187
16640
|
if (!enabled)
|
|
16188
16641
|
return {};
|
|
16189
16642
|
return {
|
|
16190
16643
|
"tool.execute.before": async (input, output) => {
|
|
16191
|
-
await safeAsync(
|
|
16644
|
+
await safeAsync(PLUGIN_NAME17, "tool.execute.before", async () => {
|
|
16192
16645
|
if (input.tool !== "bash")
|
|
16193
16646
|
return;
|
|
16194
16647
|
const args = output.args ?? {};
|
|
@@ -16197,7 +16650,7 @@ var handler16 = async (_ctx) => {
|
|
|
16197
16650
|
if (next !== undefined && next !== original) {
|
|
16198
16651
|
args["command"] = next;
|
|
16199
16652
|
output.args = args;
|
|
16200
|
-
safeWriteLog(
|
|
16653
|
+
safeWriteLog(PLUGIN_NAME17, {
|
|
16201
16654
|
hook: "tool.execute.before",
|
|
16202
16655
|
tool: input.tool,
|
|
16203
16656
|
callID: input.callID,
|
|
@@ -16530,8 +16983,8 @@ function isRecoveryWorthShowing(plan) {
|
|
|
16530
16983
|
}
|
|
16531
16984
|
|
|
16532
16985
|
// plugins/session-recovery.ts
|
|
16533
|
-
var
|
|
16534
|
-
logLifecycle(
|
|
16986
|
+
var PLUGIN_NAME18 = "session-recovery";
|
|
16987
|
+
logLifecycle(PLUGIN_NAME18, "import", {});
|
|
16535
16988
|
async function processSessionStart(currentSessionId, opts = {}) {
|
|
16536
16989
|
if (opts.disabled) {
|
|
16537
16990
|
return { ok: true, injected: false, reason: "disabled" };
|
|
@@ -16541,7 +16994,7 @@ async function processSessionStart(currentSessionId, opts = {}) {
|
|
|
16541
16994
|
excludeIds.add(currentSessionId);
|
|
16542
16995
|
const r = await scanLastSession({ ...opts, excludeIds: [...excludeIds] });
|
|
16543
16996
|
if (!r.ok) {
|
|
16544
|
-
opts.log?.warn?.(`[${
|
|
16997
|
+
opts.log?.warn?.(`[${PLUGIN_NAME18}] 扫描失败:${r.error}`);
|
|
16545
16998
|
return { ok: false, injected: false, reason: "scan_error", error: r.error };
|
|
16546
16999
|
}
|
|
16547
17000
|
const plan = r.plan;
|
|
@@ -16555,7 +17008,7 @@ async function processSessionStart(currentSessionId, opts = {}) {
|
|
|
16555
17008
|
await opts.injectRecovery(injection);
|
|
16556
17009
|
} catch (err) {
|
|
16557
17010
|
const msg = err instanceof Error ? err.message : String(err);
|
|
16558
|
-
opts.log?.warn?.(`[${
|
|
17011
|
+
opts.log?.warn?.(`[${PLUGIN_NAME18}] injectRecovery 异常:${msg}`);
|
|
16559
17012
|
return { ok: false, injected: false, plan, reason: "inject_error", error: msg };
|
|
16560
17013
|
}
|
|
16561
17014
|
}
|
|
@@ -16584,13 +17037,13 @@ function renderPrompt(plan) {
|
|
|
16584
17037
|
return lines.join(`
|
|
16585
17038
|
`);
|
|
16586
17039
|
}
|
|
16587
|
-
var
|
|
17040
|
+
var log10 = makePluginLogger(PLUGIN_NAME18);
|
|
16588
17041
|
var _lastInjection = null;
|
|
16589
17042
|
var sessionRecoveryServer = async (ctx) => {
|
|
16590
|
-
logLifecycle(
|
|
17043
|
+
logLifecycle(PLUGIN_NAME18, "activate", { directory: ctx.directory });
|
|
16591
17044
|
return {
|
|
16592
17045
|
event: async ({ event }) => {
|
|
16593
|
-
await safeAsync(
|
|
17046
|
+
await safeAsync(PLUGIN_NAME18, "event", async () => {
|
|
16594
17047
|
const e = event;
|
|
16595
17048
|
if (!e || typeof e.type !== "string")
|
|
16596
17049
|
return;
|
|
@@ -16600,12 +17053,12 @@ var sessionRecoveryServer = async (ctx) => {
|
|
|
16600
17053
|
const root = typeof e.properties?.["root"] === "string" ? e.properties["root"] : ctx.directory ?? process.cwd();
|
|
16601
17054
|
const r = await processSessionStart(sid, {
|
|
16602
17055
|
root,
|
|
16603
|
-
log:
|
|
17056
|
+
log: log10,
|
|
16604
17057
|
injectRecovery: (inj) => {
|
|
16605
17058
|
_lastInjection = inj;
|
|
16606
17059
|
}
|
|
16607
17060
|
});
|
|
16608
|
-
safeWriteLog(
|
|
17061
|
+
safeWriteLog(PLUGIN_NAME18, {
|
|
16609
17062
|
hook: "event",
|
|
16610
17063
|
type: "session.start",
|
|
16611
17064
|
ok: r.ok,
|
|
@@ -16614,13 +17067,13 @@ var sessionRecoveryServer = async (ctx) => {
|
|
|
16614
17067
|
last_session_id: r.plan?.last_session_id
|
|
16615
17068
|
});
|
|
16616
17069
|
if (r.injected && r.plan) {
|
|
16617
|
-
|
|
17070
|
+
log10.info(`[${PLUGIN_NAME18}] 注入恢复提示(last=${r.plan.last_session_id?.slice(0, 8) ?? "?"}, reason=${r.plan.reason})`);
|
|
16618
17071
|
}
|
|
16619
17072
|
});
|
|
16620
17073
|
}
|
|
16621
17074
|
};
|
|
16622
17075
|
};
|
|
16623
|
-
var
|
|
17076
|
+
var handler18 = sessionRecoveryServer;
|
|
16624
17077
|
|
|
16625
17078
|
// plugins/subtasks.ts
|
|
16626
17079
|
import { promises as fs12 } from "node:fs";
|
|
@@ -16906,7 +17359,7 @@ function resolveMergeFns(deps) {
|
|
|
16906
17359
|
worktreeHasChangesFn: deps.worktreeHasChanges ?? worktreeHasChanges
|
|
16907
17360
|
};
|
|
16908
17361
|
}
|
|
16909
|
-
async function mergeOneAttempt(r, opts, mergeFns,
|
|
17362
|
+
async function mergeOneAttempt(r, opts, mergeFns, log11) {
|
|
16910
17363
|
const t0 = Date.now();
|
|
16911
17364
|
const root = opts.mergeRoot;
|
|
16912
17365
|
const {
|
|
@@ -16988,7 +17441,7 @@ async function mergeOneAttempt(r, opts, mergeFns, log10) {
|
|
|
16988
17441
|
await mergeAbortFn({ root });
|
|
16989
17442
|
} catch (abortErr) {
|
|
16990
17443
|
abortFailed = true;
|
|
16991
|
-
|
|
17444
|
+
log11("error", `[parallel-merge] mergeAbort 失败(仓库可能锁死)`, {
|
|
16992
17445
|
id: r.id,
|
|
16993
17446
|
error: describe4(abortErr)
|
|
16994
17447
|
});
|
|
@@ -17004,13 +17457,13 @@ async function mergeOneAttempt(r, opts, mergeFns, log10) {
|
|
|
17004
17457
|
}
|
|
17005
17458
|
attempt.ok = true;
|
|
17006
17459
|
attempt.durationMs = Date.now() - t0;
|
|
17007
|
-
await safeRemoveWorktree(removeWorktreeFn, root, wt,
|
|
17460
|
+
await safeRemoveWorktree(removeWorktreeFn, root, wt, log11, r.id);
|
|
17008
17461
|
return { attempt };
|
|
17009
17462
|
} else {
|
|
17010
17463
|
attempt.ok = true;
|
|
17011
17464
|
attempt.skippedReason = "no_changes";
|
|
17012
17465
|
attempt.durationMs = Date.now() - t0;
|
|
17013
|
-
await safeRemoveWorktree(removeWorktreeFn, root, wt,
|
|
17466
|
+
await safeRemoveWorktree(removeWorktreeFn, root, wt, log11, r.id);
|
|
17014
17467
|
return { attempt };
|
|
17015
17468
|
}
|
|
17016
17469
|
} catch (err) {
|
|
@@ -17024,7 +17477,7 @@ async function mergeOneAttempt(r, opts, mergeFns, log10) {
|
|
|
17024
17477
|
}
|
|
17025
17478
|
}
|
|
17026
17479
|
async function mergeWorktrees(results, opts, deps) {
|
|
17027
|
-
const
|
|
17480
|
+
const log11 = deps.log ?? (() => {});
|
|
17028
17481
|
const root = opts.mergeRoot;
|
|
17029
17482
|
const mergeFns = resolveMergeFns(deps);
|
|
17030
17483
|
const report = {
|
|
@@ -17066,10 +17519,10 @@ async function mergeWorktrees(results, opts, deps) {
|
|
|
17066
17519
|
};
|
|
17067
17520
|
report.attempts.push(attempt);
|
|
17068
17521
|
report.skipped++;
|
|
17069
|
-
await fireMergeAttempt(opts.onMergeAttempt, attempt, idx++,
|
|
17522
|
+
await fireMergeAttempt(opts.onMergeAttempt, attempt, idx++, log11);
|
|
17070
17523
|
}
|
|
17071
17524
|
for (const r of mergeable) {
|
|
17072
|
-
const { attempt, pendingItem } = await mergeOneAttempt(r, { mergeRoot: root }, mergeFns,
|
|
17525
|
+
const { attempt, pendingItem } = await mergeOneAttempt(r, { mergeRoot: root }, mergeFns, log11);
|
|
17073
17526
|
report.attempts.push(attempt);
|
|
17074
17527
|
if (attempt.ok && !attempt.skippedReason)
|
|
17075
17528
|
report.merged++;
|
|
@@ -17079,12 +17532,12 @@ async function mergeWorktrees(results, opts, deps) {
|
|
|
17079
17532
|
report.conflicted++;
|
|
17080
17533
|
if (pendingItem)
|
|
17081
17534
|
report.pendingWorktrees.push(pendingItem);
|
|
17082
|
-
await fireMergeAttempt(opts.onMergeAttempt, attempt, idx++,
|
|
17535
|
+
await fireMergeAttempt(opts.onMergeAttempt, attempt, idx++, log11);
|
|
17083
17536
|
}
|
|
17084
17537
|
return report;
|
|
17085
17538
|
}
|
|
17086
17539
|
function createMergeQueue(opts, deps = {}) {
|
|
17087
|
-
const
|
|
17540
|
+
const log11 = deps.log ?? (() => {});
|
|
17088
17541
|
const mergeFns = {
|
|
17089
17542
|
commitWorktreeIfDirtyFn: deps.commitWorktreeIfDirtyFn ?? commitWorktreeIfDirty,
|
|
17090
17543
|
tryMergeFn: deps.tryMergeFn ?? tryMerge,
|
|
@@ -17125,10 +17578,10 @@ function createMergeQueue(opts, deps = {}) {
|
|
|
17125
17578
|
conflicts: []
|
|
17126
17579
|
});
|
|
17127
17580
|
}
|
|
17128
|
-
await fireMergeAttempt(opts.onMergeAttempt, attempt2, idx++,
|
|
17581
|
+
await fireMergeAttempt(opts.onMergeAttempt, attempt2, idx++, log11);
|
|
17129
17582
|
return;
|
|
17130
17583
|
}
|
|
17131
|
-
const { attempt, abortFailed, pendingItem } = await mergeOneAttempt(result, { mergeRoot: opts.mergeRoot, summaryCharLimit: opts.summaryCharLimit }, mergeFns,
|
|
17584
|
+
const { attempt, abortFailed, pendingItem } = await mergeOneAttempt(result, { mergeRoot: opts.mergeRoot, summaryCharLimit: opts.summaryCharLimit }, mergeFns, log11);
|
|
17132
17585
|
report.attempts.push(attempt);
|
|
17133
17586
|
if (attempt.ok && !attempt.skippedReason)
|
|
17134
17587
|
report.merged++;
|
|
@@ -17140,11 +17593,11 @@ function createMergeQueue(opts, deps = {}) {
|
|
|
17140
17593
|
report.pendingWorktrees.push(pendingItem);
|
|
17141
17594
|
if (abortFailed) {
|
|
17142
17595
|
queueAborted = true;
|
|
17143
|
-
|
|
17596
|
+
log11("error", `[parallel-merge] queue aborted (mergeAbort failed)`, {
|
|
17144
17597
|
id: result.id
|
|
17145
17598
|
});
|
|
17146
17599
|
}
|
|
17147
|
-
await fireMergeAttempt(opts.onMergeAttempt, attempt, idx++,
|
|
17600
|
+
await fireMergeAttempt(opts.onMergeAttempt, attempt, idx++, log11);
|
|
17148
17601
|
});
|
|
17149
17602
|
},
|
|
17150
17603
|
async flushAndReport() {
|
|
@@ -17153,23 +17606,23 @@ function createMergeQueue(opts, deps = {}) {
|
|
|
17153
17606
|
}
|
|
17154
17607
|
};
|
|
17155
17608
|
}
|
|
17156
|
-
async function safeRemoveWorktree(fn, root, wt,
|
|
17609
|
+
async function safeRemoveWorktree(fn, root, wt, log11, subtaskId) {
|
|
17157
17610
|
try {
|
|
17158
17611
|
await fn({ root, worktree_path: wt, force: true });
|
|
17159
17612
|
} catch (err) {
|
|
17160
|
-
|
|
17613
|
+
log11("warn", `[parallel] removeWorktree 失败 ${subtaskId}`, {
|
|
17161
17614
|
error: describe4(err),
|
|
17162
17615
|
worktree: wt
|
|
17163
17616
|
});
|
|
17164
17617
|
}
|
|
17165
17618
|
}
|
|
17166
|
-
async function fireMergeAttempt(cb, attempt, idx,
|
|
17619
|
+
async function fireMergeAttempt(cb, attempt, idx, log11) {
|
|
17167
17620
|
if (!cb)
|
|
17168
17621
|
return;
|
|
17169
17622
|
try {
|
|
17170
17623
|
await cb(attempt, idx);
|
|
17171
17624
|
} catch (err) {
|
|
17172
|
-
|
|
17625
|
+
log11("warn", `[parallel] onMergeAttempt 抛错(已隔离)`, {
|
|
17173
17626
|
id: attempt.subtaskId,
|
|
17174
17627
|
error: describe4(err)
|
|
17175
17628
|
});
|
|
@@ -17195,7 +17648,7 @@ async function schedule(opts) {
|
|
|
17195
17648
|
}
|
|
17196
17649
|
const now = opts.deps.now ?? Date.now;
|
|
17197
17650
|
const start = now();
|
|
17198
|
-
const
|
|
17651
|
+
const log11 = opts.deps.log ?? (() => {});
|
|
17199
17652
|
const concurrency = Math.max(1, opts.maxConcurrency ?? 4);
|
|
17200
17653
|
const totalTimeout = opts.totalTimeout_ms ?? 30 * 60000;
|
|
17201
17654
|
const limit = Math.max(1, opts.summaryCharLimit ?? 500);
|
|
@@ -17228,7 +17681,7 @@ async function schedule(opts) {
|
|
|
17228
17681
|
mergeAbortFn: opts.deps.mergeAbort,
|
|
17229
17682
|
removeWorktreeFn: opts.deps.removeWorktree,
|
|
17230
17683
|
worktreeHasChangesFn: opts.deps.worktreeHasChanges,
|
|
17231
|
-
log:
|
|
17684
|
+
log: log11
|
|
17232
17685
|
});
|
|
17233
17686
|
}
|
|
17234
17687
|
const fireFinish = async (i, res) => {
|
|
@@ -17236,7 +17689,7 @@ async function schedule(opts) {
|
|
|
17236
17689
|
try {
|
|
17237
17690
|
queue.enqueue(res);
|
|
17238
17691
|
} catch (err) {
|
|
17239
|
-
|
|
17692
|
+
log11("warn", `[parallel] queue.enqueue 抛错(已隔离)`, {
|
|
17240
17693
|
id: res.id,
|
|
17241
17694
|
error: describe5(err)
|
|
17242
17695
|
});
|
|
@@ -17247,7 +17700,7 @@ async function schedule(opts) {
|
|
|
17247
17700
|
try {
|
|
17248
17701
|
await opts.onSubtaskFinish(res, i);
|
|
17249
17702
|
} catch (err) {
|
|
17250
|
-
|
|
17703
|
+
log11("warn", `[parallel] onSubtaskFinish 抛错(已隔离)`, {
|
|
17251
17704
|
id: res.id,
|
|
17252
17705
|
error: describe5(err)
|
|
17253
17706
|
});
|
|
@@ -17277,7 +17730,7 @@ async function schedule(opts) {
|
|
|
17277
17730
|
try {
|
|
17278
17731
|
await opts.onSubtaskStart(spec, i);
|
|
17279
17732
|
} catch (err) {
|
|
17280
|
-
|
|
17733
|
+
log11("warn", `[parallel] onSubtaskStart 抛错(已隔离)`, {
|
|
17281
17734
|
id: spec.id,
|
|
17282
17735
|
error: describe5(err)
|
|
17283
17736
|
});
|
|
@@ -17328,7 +17781,7 @@ async function schedule(opts) {
|
|
|
17328
17781
|
try {
|
|
17329
17782
|
await alloc.cleanup();
|
|
17330
17783
|
} catch (err) {
|
|
17331
|
-
|
|
17784
|
+
log11("warn", `[parallel] worktree 清理失败 ${spec.id}`, {
|
|
17332
17785
|
error: describe5(err)
|
|
17333
17786
|
});
|
|
17334
17787
|
}
|
|
@@ -17488,7 +17941,7 @@ function pickStatus(r, taskAborted, globalAborted) {
|
|
|
17488
17941
|
// lib/opencode-runner.ts
|
|
17489
17942
|
init_opencode_plugin_helpers();
|
|
17490
17943
|
function makeOpencodeRunner(opts) {
|
|
17491
|
-
const
|
|
17944
|
+
const log11 = opts.log ?? (() => {});
|
|
17492
17945
|
return async (spec, runCtx) => {
|
|
17493
17946
|
const startedAt = Date.now();
|
|
17494
17947
|
let childSessionId;
|
|
@@ -17509,7 +17962,7 @@ function makeOpencodeRunner(opts) {
|
|
|
17509
17962
|
};
|
|
17510
17963
|
}
|
|
17511
17964
|
childSessionId = created.data.id;
|
|
17512
|
-
|
|
17965
|
+
log11("info", `[opencode-runner] subtask=${spec.id} session=${childSessionId} created`);
|
|
17513
17966
|
const promptText = composePromptText(spec);
|
|
17514
17967
|
const promptPromise = opts.client.session.prompt({
|
|
17515
17968
|
path: { id: childSessionId },
|
|
@@ -17562,7 +18015,7 @@ function makeOpencodeRunner(opts) {
|
|
|
17562
18015
|
});
|
|
17563
18016
|
changedFiles = pickDiffFiles(diff.data);
|
|
17564
18017
|
} catch (err) {
|
|
17565
|
-
|
|
18018
|
+
log11("warn", `[opencode-runner] diff 取失败 ${spec.id}`, { error: describe6(err) });
|
|
17566
18019
|
}
|
|
17567
18020
|
const elapsed = Date.now() - startedAt;
|
|
17568
18021
|
const summary = lastText || `subtask ${spec.id} 完成(${elapsed}ms,无文本输出,finish=${finishReason || "unknown"})`;
|
|
@@ -17588,7 +18041,7 @@ function makeOpencodeRunner(opts) {
|
|
|
17588
18041
|
query: opts.directory ? { directory: opts.directory } : undefined
|
|
17589
18042
|
});
|
|
17590
18043
|
} catch (err) {
|
|
17591
|
-
|
|
18044
|
+
log11("warn", `[opencode-runner] session.delete 失败 ${childSessionId}`, {
|
|
17592
18045
|
error: describe6(err)
|
|
17593
18046
|
});
|
|
17594
18047
|
}
|
|
@@ -17702,14 +18155,14 @@ function clip4(s, max) {
|
|
|
17702
18155
|
return s.length <= max ? s : s.slice(0, max - 1) + "…";
|
|
17703
18156
|
}
|
|
17704
18157
|
async function sendParentNotice(client, sessionID, text, opts = {}) {
|
|
17705
|
-
const
|
|
18158
|
+
const log11 = opts.log ?? (() => {});
|
|
17706
18159
|
if (!client?.session) {
|
|
17707
|
-
|
|
18160
|
+
log11("warn", "[sendParentNotice] client.session 不可用,noop");
|
|
17708
18161
|
return false;
|
|
17709
18162
|
}
|
|
17710
18163
|
const sessionAny = client.session;
|
|
17711
18164
|
if (typeof sessionAny.promptAsync !== "function") {
|
|
17712
|
-
|
|
18165
|
+
log11("warn", "[sendParentNotice] promptAsync 不可用(SDK 太老?),noop");
|
|
17713
18166
|
return false;
|
|
17714
18167
|
}
|
|
17715
18168
|
try {
|
|
@@ -17730,12 +18183,12 @@ async function sendParentNotice(client, sessionID, text, opts = {}) {
|
|
|
17730
18183
|
}
|
|
17731
18184
|
});
|
|
17732
18185
|
if (res && typeof res === "object" && "error" in res && res.error) {
|
|
17733
|
-
|
|
18186
|
+
log11("warn", "[sendParentNotice] promptAsync 返回 error", { error: res.error });
|
|
17734
18187
|
return false;
|
|
17735
18188
|
}
|
|
17736
18189
|
return true;
|
|
17737
18190
|
} catch (err) {
|
|
17738
|
-
|
|
18191
|
+
log11("warn", "[sendParentNotice] 抛错(已隔离)", {
|
|
17739
18192
|
error: err instanceof Error ? err.message : String(err)
|
|
17740
18193
|
});
|
|
17741
18194
|
return false;
|
|
@@ -17765,9 +18218,9 @@ function buildSystemPrompt(maxSubtasks) {
|
|
|
17765
18218
|
`);
|
|
17766
18219
|
}
|
|
17767
18220
|
async function decomposeTask(description26, opts) {
|
|
17768
|
-
const
|
|
18221
|
+
const log11 = opts.log ?? (() => {});
|
|
17769
18222
|
if (opts.mockResponse) {
|
|
17770
|
-
return validateAndFinalize(opts.mockResponse, undefined,
|
|
18223
|
+
return validateAndFinalize(opts.mockResponse, undefined, log11, opts.maxSubtasks ?? DEFAULT_MAX_SUBTASKS);
|
|
17771
18224
|
}
|
|
17772
18225
|
const maxSubtasks = opts.maxSubtasks ?? DEFAULT_MAX_SUBTASKS;
|
|
17773
18226
|
const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS2;
|
|
@@ -17778,7 +18231,7 @@ async function decomposeTask(description26, opts) {
|
|
|
17778
18231
|
query: opts.directory ? { directory: opts.directory } : undefined
|
|
17779
18232
|
});
|
|
17780
18233
|
if (created.error || !created.data?.id) {
|
|
17781
|
-
|
|
18234
|
+
log11("warn", "[decompose] session.create 失败", { error: describe7(created.error) });
|
|
17782
18235
|
return {
|
|
17783
18236
|
ok: false,
|
|
17784
18237
|
subtasks: [],
|
|
@@ -17800,22 +18253,22 @@ async function decomposeTask(description26, opts) {
|
|
|
17800
18253
|
sleep(timeoutMs).then(() => ({ kind: "timeout" }))
|
|
17801
18254
|
]);
|
|
17802
18255
|
if (raced.kind === "timeout") {
|
|
17803
|
-
|
|
18256
|
+
log11("warn", "[decompose] LLM 调用超时", { timeoutMs });
|
|
17804
18257
|
return { ok: false, subtasks: [], reason: "llm_unavailable" };
|
|
17805
18258
|
}
|
|
17806
18259
|
const r = raced.value;
|
|
17807
18260
|
if (r.error || !r.data) {
|
|
17808
|
-
|
|
18261
|
+
log11("warn", "[decompose] session.prompt 返回错误", { error: describe7(r.error) });
|
|
17809
18262
|
return { ok: false, subtasks: [], reason: "llm_unavailable" };
|
|
17810
18263
|
}
|
|
17811
18264
|
const rawText = pickLastText2(r.data.parts ?? []);
|
|
17812
18265
|
if (!rawText) {
|
|
17813
|
-
|
|
18266
|
+
log11("warn", "[decompose] LLM 输出为空");
|
|
17814
18267
|
return { ok: false, subtasks: [], reason: "parse_failed", raw: "" };
|
|
17815
18268
|
}
|
|
17816
18269
|
const parsed = extractJson(rawText);
|
|
17817
18270
|
if (!parsed) {
|
|
17818
|
-
|
|
18271
|
+
log11("warn", "[decompose] JSON 解析失败", { raw: clip5(rawText, 200) });
|
|
17819
18272
|
return { ok: false, subtasks: [], reason: "parse_failed", raw: rawText };
|
|
17820
18273
|
}
|
|
17821
18274
|
if (parsed && typeof parsed === "object" && parsed.single_task === true) {
|
|
@@ -17823,7 +18276,7 @@ async function decomposeTask(description26, opts) {
|
|
|
17823
18276
|
}
|
|
17824
18277
|
const subtasksRaw = parsed.subtasks;
|
|
17825
18278
|
if (!Array.isArray(subtasksRaw)) {
|
|
17826
|
-
|
|
18279
|
+
log11("warn", "[decompose] JSON 缺 subtasks 数组");
|
|
17827
18280
|
return { ok: false, subtasks: [], reason: "parse_failed", raw: rawText };
|
|
17828
18281
|
}
|
|
17829
18282
|
const normalized = [];
|
|
@@ -17839,12 +18292,12 @@ async function decomposeTask(description26, opts) {
|
|
|
17839
18292
|
normalized.push({ description: desc, hintFiles });
|
|
17840
18293
|
}
|
|
17841
18294
|
if (normalized.length > maxSubtasks) {
|
|
17842
|
-
|
|
18295
|
+
log11("warn", "[decompose] LLM 返回子任务超过上限", { count: normalized.length, maxSubtasks });
|
|
17843
18296
|
return { ok: false, subtasks: [], reason: "parse_failed", raw: rawText };
|
|
17844
18297
|
}
|
|
17845
|
-
return validateAndFinalize(normalized, rawText,
|
|
18298
|
+
return validateAndFinalize(normalized, rawText, log11, maxSubtasks);
|
|
17846
18299
|
} catch (err) {
|
|
17847
|
-
|
|
18300
|
+
log11("warn", "[decompose] 抛错", { error: describe7(err) });
|
|
17848
18301
|
return { ok: false, subtasks: [], reason: "llm_unavailable" };
|
|
17849
18302
|
} finally {
|
|
17850
18303
|
if (childSessionId) {
|
|
@@ -17854,12 +18307,12 @@ async function decomposeTask(description26, opts) {
|
|
|
17854
18307
|
query: opts.directory ? { directory: opts.directory } : undefined
|
|
17855
18308
|
});
|
|
17856
18309
|
} catch (err) {
|
|
17857
|
-
|
|
18310
|
+
log11("warn", "[decompose] session.delete 失败", { error: describe7(err) });
|
|
17858
18311
|
}
|
|
17859
18312
|
}
|
|
17860
18313
|
}
|
|
17861
18314
|
}
|
|
17862
|
-
function validateAndFinalize(subtasks, raw,
|
|
18315
|
+
function validateAndFinalize(subtasks, raw, log11, maxSubtasks) {
|
|
17863
18316
|
if (subtasks.length < 2) {
|
|
17864
18317
|
return { ok: false, subtasks: [], reason: "single_task", raw };
|
|
17865
18318
|
}
|
|
@@ -17869,7 +18322,7 @@ function validateAndFinalize(subtasks, raw, log10, maxSubtasks) {
|
|
|
17869
18322
|
}
|
|
17870
18323
|
}
|
|
17871
18324
|
if (subtasks.length > maxSubtasks) {
|
|
17872
|
-
|
|
18325
|
+
log11("warn", "[decompose] LLM 返回子任务超过上限", { count: subtasks.length, maxSubtasks });
|
|
17873
18326
|
return { ok: false, subtasks: [], reason: "parse_failed", raw };
|
|
17874
18327
|
}
|
|
17875
18328
|
for (let i = 0;i < subtasks.length; i++) {
|
|
@@ -17883,7 +18336,7 @@ function validateAndFinalize(subtasks, raw, log10, maxSubtasks) {
|
|
|
17883
18336
|
continue;
|
|
17884
18337
|
for (const f of b) {
|
|
17885
18338
|
if (setA.has(f.trim())) {
|
|
17886
|
-
|
|
18339
|
+
log11("info", "[decompose] hintFiles 有交集,降级 single_task", {
|
|
17887
18340
|
a: subtasks[i].description,
|
|
17888
18341
|
b: subtasks[j].description,
|
|
17889
18342
|
file: f
|
|
@@ -17972,7 +18425,7 @@ function sleep(ms) {
|
|
|
17972
18425
|
|
|
17973
18426
|
// plugins/subtasks.ts
|
|
17974
18427
|
init_runtime_paths();
|
|
17975
|
-
var
|
|
18428
|
+
var PLUGIN_NAME19 = "subtasks";
|
|
17976
18429
|
function getLogFile(root = process.cwd()) {
|
|
17977
18430
|
return path15.join(runtimeDir(root), "logs", "subtasks.log");
|
|
17978
18431
|
}
|
|
@@ -18038,7 +18491,7 @@ var mockRunner = async (spec) => {
|
|
|
18038
18491
|
};
|
|
18039
18492
|
async function handleParallelCommand(raw) {
|
|
18040
18493
|
const ctx = raw ?? {};
|
|
18041
|
-
const
|
|
18494
|
+
const log11 = ctx.log;
|
|
18042
18495
|
const args = ctx.args ?? {};
|
|
18043
18496
|
const parentId = (args.parentId ?? `task-${Date.now().toString(36)}`).replace(/[^a-zA-Z0-9._-]/g, "-");
|
|
18044
18497
|
const rawDescription = typeof args.description === "string" ? args.description.trim() : "";
|
|
@@ -18052,7 +18505,7 @@ async function handleParallelCommand(raw) {
|
|
|
18052
18505
|
specs = splitDescriptions(args.description, { parentId });
|
|
18053
18506
|
}
|
|
18054
18507
|
if (specs.length === 0) {
|
|
18055
|
-
|
|
18508
|
+
log11?.warn(`[${PLUGIN_NAME19}] /parallel 缺有效子任务`);
|
|
18056
18509
|
await safeReply2(ctx, "⚠ /parallel 需要至少 1 个子任务(用 ; 或换行分隔)");
|
|
18057
18510
|
return { ok: false, reason: "no_subtasks" };
|
|
18058
18511
|
}
|
|
@@ -18078,7 +18531,7 @@ async function handleParallelCommand(raw) {
|
|
|
18078
18531
|
if (!gitOk) {
|
|
18079
18532
|
autoMerge = false;
|
|
18080
18533
|
downgradedAutoMerge = true;
|
|
18081
|
-
|
|
18534
|
+
log11?.warn("[subtasks] mergeRoot 非 git 仓库,降级 autoMerge=false");
|
|
18082
18535
|
const canNoticeEarly = Boolean(ctx.client && ctx.parentSessionID);
|
|
18083
18536
|
if (canNoticeEarly) {
|
|
18084
18537
|
await sendParentNotice(ctx.client, ctx.parentSessionID, "ℹ 当前目录不是 git 仓库,worktree 隔离已自动关闭,以单目录模式运行", { directory: ctx.directory });
|
|
@@ -18087,17 +18540,17 @@ async function handleParallelCommand(raw) {
|
|
|
18087
18540
|
}
|
|
18088
18541
|
const canNotice = Boolean(ctx.client && ctx.parentSessionID);
|
|
18089
18542
|
if (specs.length === 1 && rawDescription.length >= 30 && ctx.client) {
|
|
18090
|
-
|
|
18543
|
+
log11?.info(`[${PLUGIN_NAME19}] 触发 AI 拆分(描述长度=${rawDescription.length})`);
|
|
18091
18544
|
const dec = await decomposeTask(rawDescription, {
|
|
18092
18545
|
client: ctx.client,
|
|
18093
18546
|
directory: ctx.directory,
|
|
18094
18547
|
log: (lvl, msg, data) => {
|
|
18095
18548
|
if (lvl === "error")
|
|
18096
|
-
|
|
18549
|
+
log11?.error(msg, data);
|
|
18097
18550
|
else if (lvl === "warn")
|
|
18098
|
-
|
|
18551
|
+
log11?.warn(msg, data);
|
|
18099
18552
|
else
|
|
18100
|
-
|
|
18553
|
+
log11?.info(msg, data);
|
|
18101
18554
|
},
|
|
18102
18555
|
mockResponse: ctx.decomposeMockResponse
|
|
18103
18556
|
});
|
|
@@ -18108,7 +18561,7 @@ async function handleParallelCommand(raw) {
|
|
|
18108
18561
|
timeout_ms: undefined,
|
|
18109
18562
|
args: d.hintFiles && d.hintFiles.length > 0 ? { hintFiles: d.hintFiles } : undefined
|
|
18110
18563
|
}));
|
|
18111
|
-
|
|
18564
|
+
log11?.info(`[${PLUGIN_NAME19}] AI 拆分成功 → ${specs.length} 个子任务`);
|
|
18112
18565
|
if (canNotice) {
|
|
18113
18566
|
const lines = [
|
|
18114
18567
|
`\uD83E\uDD16 AI 已自动拆为 ${specs.length} 个并行子任务:`,
|
|
@@ -18120,7 +18573,7 @@ async function handleParallelCommand(raw) {
|
|
|
18120
18573
|
});
|
|
18121
18574
|
}
|
|
18122
18575
|
} else if (dec.reason === "llm_unavailable" || dec.reason === "parse_failed") {
|
|
18123
|
-
|
|
18576
|
+
log11?.warn(`[${PLUGIN_NAME19}] AI 拆分失败(${dec.reason}),按单任务执行`);
|
|
18124
18577
|
}
|
|
18125
18578
|
}
|
|
18126
18579
|
if (canNotice) {
|
|
@@ -18135,11 +18588,11 @@ async function handleParallelCommand(raw) {
|
|
|
18135
18588
|
directory: ctx.directory,
|
|
18136
18589
|
log: (lvl, msg, data) => {
|
|
18137
18590
|
if (lvl === "error")
|
|
18138
|
-
|
|
18591
|
+
log11?.error(msg, data);
|
|
18139
18592
|
else if (lvl === "warn")
|
|
18140
|
-
|
|
18593
|
+
log11?.warn(msg, data);
|
|
18141
18594
|
else
|
|
18142
|
-
|
|
18595
|
+
log11?.info(msg, data);
|
|
18143
18596
|
}
|
|
18144
18597
|
});
|
|
18145
18598
|
}
|
|
@@ -18183,11 +18636,11 @@ async function handleParallelCommand(raw) {
|
|
|
18183
18636
|
allocateWorktree: downgradedAutoMerge ? undefined : ctx.allocateWorktree,
|
|
18184
18637
|
log: (lvl, msg, data) => {
|
|
18185
18638
|
if (lvl === "error")
|
|
18186
|
-
|
|
18639
|
+
log11?.error(msg, data);
|
|
18187
18640
|
else if (lvl === "warn")
|
|
18188
|
-
|
|
18641
|
+
log11?.warn(msg, data);
|
|
18189
18642
|
else
|
|
18190
|
-
|
|
18643
|
+
log11?.info(msg, data);
|
|
18191
18644
|
},
|
|
18192
18645
|
tryMerge: ctx.tryMerge,
|
|
18193
18646
|
mergeCommit: ctx.mergeCommit,
|
|
@@ -18199,7 +18652,7 @@ async function handleParallelCommand(raw) {
|
|
|
18199
18652
|
});
|
|
18200
18653
|
} catch (err) {
|
|
18201
18654
|
const msg = err instanceof Error ? err.message : String(err);
|
|
18202
|
-
|
|
18655
|
+
log11?.error(`[${PLUGIN_NAME19}] schedule 抛错`, { error: msg });
|
|
18203
18656
|
await safeReply2(ctx, `❌ 并发调度失败:${msg}`);
|
|
18204
18657
|
return { ok: false, reason: msg };
|
|
18205
18658
|
}
|
|
@@ -18217,7 +18670,7 @@ async function handleParallelCommand(raw) {
|
|
|
18217
18670
|
}
|
|
18218
18671
|
await safeReply2(ctx, summaryLines.join(`
|
|
18219
18672
|
`));
|
|
18220
|
-
|
|
18673
|
+
log11?.info(`[${PLUGIN_NAME19}] schedule 完成`, {
|
|
18221
18674
|
parentId,
|
|
18222
18675
|
success: result.digest.success,
|
|
18223
18676
|
failed: result.digest.failed,
|
|
@@ -18230,7 +18683,7 @@ async function handleParallelCommand(raw) {
|
|
|
18230
18683
|
try {
|
|
18231
18684
|
await ctx.onCompleted(result);
|
|
18232
18685
|
} catch (err) {
|
|
18233
|
-
|
|
18686
|
+
log11?.warn(`[${PLUGIN_NAME19}] onCompleted hook 抛错(已隔离)`, {
|
|
18234
18687
|
error: err instanceof Error ? err.message : String(err)
|
|
18235
18688
|
});
|
|
18236
18689
|
}
|
|
@@ -18272,7 +18725,7 @@ async function writeLog(level, msg, data) {
|
|
|
18272
18725
|
const line = JSON.stringify({
|
|
18273
18726
|
ts: new Date().toISOString(),
|
|
18274
18727
|
level,
|
|
18275
|
-
plugin:
|
|
18728
|
+
plugin: PLUGIN_NAME19,
|
|
18276
18729
|
msg,
|
|
18277
18730
|
data
|
|
18278
18731
|
}) + `
|
|
@@ -18283,11 +18736,11 @@ async function writeLog(level, msg, data) {
|
|
|
18283
18736
|
await fs12.appendFile(logFile, line, "utf8");
|
|
18284
18737
|
} catch {}
|
|
18285
18738
|
}
|
|
18286
|
-
logLifecycle(
|
|
18739
|
+
logLifecycle(PLUGIN_NAME19, "import");
|
|
18287
18740
|
var subtasksServer = async (ctx) => {
|
|
18288
|
-
const
|
|
18741
|
+
const log11 = makePluginLogger(PLUGIN_NAME19);
|
|
18289
18742
|
const client = ctx?.client ?? undefined;
|
|
18290
|
-
logLifecycle(
|
|
18743
|
+
logLifecycle(PLUGIN_NAME19, "activate", {
|
|
18291
18744
|
directory: ctx.directory,
|
|
18292
18745
|
hasClient: Boolean(client)
|
|
18293
18746
|
});
|
|
@@ -18312,7 +18765,7 @@ var subtasksServer = async (ctx) => {
|
|
|
18312
18765
|
const gitOk = await isGitRepo({ root: repoRoot }).catch(() => false);
|
|
18313
18766
|
if (!gitOk) {
|
|
18314
18767
|
autoMerge = false;
|
|
18315
|
-
|
|
18768
|
+
log11?.warn("[subtasks] worktree_isolation=true 但非 git 仓库,自动降级 autoMerge=false");
|
|
18316
18769
|
const canNotice = Boolean(client);
|
|
18317
18770
|
if (canNotice) {
|
|
18318
18771
|
await sendParentNotice(client, input.sessionID, "ℹ 当前目录不是 git 仓库,worktree 隔离已自动关闭,以单目录模式运行", { directory: ctx.directory });
|
|
@@ -18341,7 +18794,7 @@ var subtasksServer = async (ctx) => {
|
|
|
18341
18794
|
replyLines.push(s);
|
|
18342
18795
|
return Promise.resolve();
|
|
18343
18796
|
},
|
|
18344
|
-
log:
|
|
18797
|
+
log: log11,
|
|
18345
18798
|
client,
|
|
18346
18799
|
parentSessionID: input.sessionID,
|
|
18347
18800
|
directory: ctx.directory,
|
|
@@ -18354,11 +18807,11 @@ var subtasksServer = async (ctx) => {
|
|
|
18354
18807
|
perTaskTimeoutMs: 5 * 60000,
|
|
18355
18808
|
log: (lvl, msg, data) => {
|
|
18356
18809
|
if (lvl === "error")
|
|
18357
|
-
|
|
18810
|
+
log11.error(msg, data);
|
|
18358
18811
|
else if (lvl === "warn")
|
|
18359
|
-
|
|
18812
|
+
log11.warn(msg, data);
|
|
18360
18813
|
else
|
|
18361
|
-
|
|
18814
|
+
log11.info(msg, data);
|
|
18362
18815
|
}
|
|
18363
18816
|
}) : undefined
|
|
18364
18817
|
};
|
|
@@ -18384,20 +18837,20 @@ var subtasksServer = async (ctx) => {
|
|
|
18384
18837
|
});
|
|
18385
18838
|
}
|
|
18386
18839
|
} catch (err) {
|
|
18387
|
-
|
|
18840
|
+
log11.error(`[${PLUGIN_NAME19}] command.execute.before 异常(已隔离)`, {
|
|
18388
18841
|
error: err instanceof Error ? err.message : String(err)
|
|
18389
18842
|
});
|
|
18390
18843
|
}
|
|
18391
18844
|
}
|
|
18392
18845
|
};
|
|
18393
18846
|
};
|
|
18394
|
-
var
|
|
18847
|
+
var handler19 = subtasksServer;
|
|
18395
18848
|
|
|
18396
18849
|
// plugins/terminal-monitor.ts
|
|
18397
18850
|
init_opencode_plugin_helpers();
|
|
18398
18851
|
import * as crypto5 from "node:crypto";
|
|
18399
|
-
var
|
|
18400
|
-
logLifecycle(
|
|
18852
|
+
var PLUGIN_NAME20 = "terminal-monitor";
|
|
18853
|
+
logLifecycle(PLUGIN_NAME20, "import", {});
|
|
18401
18854
|
var DEFAULT_CONFIG7 = {
|
|
18402
18855
|
minScore: 0.6,
|
|
18403
18856
|
cooldownMs: 30000,
|
|
@@ -18679,17 +19132,17 @@ function describeError(err) {
|
|
|
18679
19132
|
return String(err);
|
|
18680
19133
|
}
|
|
18681
19134
|
}
|
|
18682
|
-
var
|
|
19135
|
+
var log11 = makePluginLogger(PLUGIN_NAME20);
|
|
18683
19136
|
var lru = new FingerprintLRU2;
|
|
18684
19137
|
var _lastNotification = null;
|
|
18685
19138
|
var terminalMonitorServer = async (ctx) => {
|
|
18686
|
-
logLifecycle(
|
|
19139
|
+
logLifecycle(PLUGIN_NAME20, "activate", {
|
|
18687
19140
|
directory: ctx.directory,
|
|
18688
19141
|
triggerEventTypes: ["terminal.output", "terminal.exit"]
|
|
18689
19142
|
});
|
|
18690
19143
|
return {
|
|
18691
19144
|
event: async ({ event }) => {
|
|
18692
|
-
await safeAsync(
|
|
19145
|
+
await safeAsync(PLUGIN_NAME20, "event", async () => {
|
|
18693
19146
|
const e = event;
|
|
18694
19147
|
if (!e || typeof e.type !== "string")
|
|
18695
19148
|
return;
|
|
@@ -18698,12 +19151,12 @@ var terminalMonitorServer = async (ctx) => {
|
|
|
18698
19151
|
const ev = { type: e.type, ...e.properties ?? {} };
|
|
18699
19152
|
const r = await processTerminalEvent(ev, {
|
|
18700
19153
|
lru,
|
|
18701
|
-
log:
|
|
19154
|
+
log: log11,
|
|
18702
19155
|
notifyAgent: (msg) => {
|
|
18703
19156
|
_lastNotification = msg;
|
|
18704
19157
|
}
|
|
18705
19158
|
});
|
|
18706
|
-
safeWriteLog(
|
|
19159
|
+
safeWriteLog(PLUGIN_NAME20, {
|
|
18707
19160
|
hook: "event",
|
|
18708
19161
|
type: e.type,
|
|
18709
19162
|
notified: r.notified,
|
|
@@ -18711,19 +19164,19 @@ var terminalMonitorServer = async (ctx) => {
|
|
|
18711
19164
|
findings: r.notification?.findings.length ?? 0
|
|
18712
19165
|
});
|
|
18713
19166
|
if (r.notified && r.notification) {
|
|
18714
|
-
|
|
19167
|
+
log11.info(`[${PLUGIN_NAME20}] 反馈 ${r.notification.findings.length} 条 → ${r.notification.summary}`);
|
|
18715
19168
|
}
|
|
18716
19169
|
});
|
|
18717
19170
|
}
|
|
18718
19171
|
};
|
|
18719
19172
|
};
|
|
18720
|
-
var
|
|
19173
|
+
var handler20 = terminalMonitorServer;
|
|
18721
19174
|
|
|
18722
19175
|
// plugins/token-manager.ts
|
|
18723
19176
|
init_opencode_plugin_helpers();
|
|
18724
|
-
var
|
|
18725
|
-
logLifecycle(
|
|
18726
|
-
async function handleMessageBefore(raw,
|
|
19177
|
+
var PLUGIN_NAME21 = "token-manager";
|
|
19178
|
+
logLifecycle(PLUGIN_NAME21, "import", {});
|
|
19179
|
+
async function handleMessageBefore(raw, log12, defaults) {
|
|
18727
19180
|
const ctx = raw ?? {};
|
|
18728
19181
|
if (!Array.isArray(ctx.messages) || ctx.messages.length === 0)
|
|
18729
19182
|
return null;
|
|
@@ -18742,21 +19195,21 @@ async function handleMessageBefore(raw, log11, defaults) {
|
|
|
18742
19195
|
};
|
|
18743
19196
|
if (r.compressed) {
|
|
18744
19197
|
ctx.messages = r.messages;
|
|
18745
|
-
|
|
19198
|
+
log12?.info(`[${PLUGIN_NAME21}] 压缩 ${r.before.count}→${r.after.count} 条 / ${r.before.tokens}→${r.after.tokens} tokens`);
|
|
18746
19199
|
}
|
|
18747
19200
|
return r;
|
|
18748
19201
|
} catch (err) {
|
|
18749
|
-
|
|
19202
|
+
log12?.warn(`[${PLUGIN_NAME21}] 压缩异常(已隔离)`, {
|
|
18750
19203
|
error: err instanceof Error ? err.message : String(err)
|
|
18751
19204
|
});
|
|
18752
19205
|
return null;
|
|
18753
19206
|
}
|
|
18754
19207
|
}
|
|
18755
|
-
var
|
|
19208
|
+
var log12 = makePluginLogger(PLUGIN_NAME21);
|
|
18756
19209
|
var tokenManagerServer = async (ctx) => {
|
|
18757
19210
|
const rt = loadRuntimeSync();
|
|
18758
19211
|
const threshold = rt.runtime.context.condenser_threshold_ratio;
|
|
18759
|
-
logLifecycle(
|
|
19212
|
+
logLifecycle(PLUGIN_NAME21, "activate", {
|
|
18760
19213
|
directory: ctx.directory,
|
|
18761
19214
|
threshold,
|
|
18762
19215
|
target: DEFAULT_CONDENSE.target,
|
|
@@ -18765,7 +19218,7 @@ var tokenManagerServer = async (ctx) => {
|
|
|
18765
19218
|
});
|
|
18766
19219
|
return {
|
|
18767
19220
|
"experimental.chat.messages.transform": async (_input, output) => {
|
|
18768
|
-
await safeAsync(
|
|
19221
|
+
await safeAsync(PLUGIN_NAME21, "experimental.chat.messages.transform", async () => {
|
|
18769
19222
|
const list = output.messages;
|
|
18770
19223
|
if (!Array.isArray(list) || list.length === 0)
|
|
18771
19224
|
return;
|
|
@@ -18775,10 +19228,10 @@ var tokenManagerServer = async (ctx) => {
|
|
|
18775
19228
|
`);
|
|
18776
19229
|
return { role, content };
|
|
18777
19230
|
});
|
|
18778
|
-
const r = await handleMessageBefore({ messages: flat },
|
|
19231
|
+
const r = await handleMessageBefore({ messages: flat }, log12, { threshold });
|
|
18779
19232
|
if (!r)
|
|
18780
19233
|
return;
|
|
18781
|
-
safeWriteLog(
|
|
19234
|
+
safeWriteLog(PLUGIN_NAME21, {
|
|
18782
19235
|
hook: "experimental.chat.messages.transform",
|
|
18783
19236
|
mode: "observe-only",
|
|
18784
19237
|
before_msgs: r.before.count,
|
|
@@ -18789,13 +19242,13 @@ var tokenManagerServer = async (ctx) => {
|
|
|
18789
19242
|
reason: r.reason
|
|
18790
19243
|
});
|
|
18791
19244
|
if (r.compressed) {
|
|
18792
|
-
|
|
19245
|
+
log12.warn(`[${PLUGIN_NAME21}] 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)`);
|
|
18793
19246
|
}
|
|
18794
19247
|
});
|
|
18795
19248
|
}
|
|
18796
19249
|
};
|
|
18797
19250
|
};
|
|
18798
|
-
var
|
|
19251
|
+
var handler21 = tokenManagerServer;
|
|
18799
19252
|
|
|
18800
19253
|
// plugins/tool-policy.ts
|
|
18801
19254
|
init_opencode_plugin_helpers();
|
|
@@ -18915,8 +19368,8 @@ function checkFileAccess(acl, file, op) {
|
|
|
18915
19368
|
}
|
|
18916
19369
|
|
|
18917
19370
|
// plugins/tool-policy.ts
|
|
18918
|
-
var
|
|
18919
|
-
logLifecycle(
|
|
19371
|
+
var PLUGIN_NAME22 = "tool-policy";
|
|
19372
|
+
logLifecycle(PLUGIN_NAME22, "import", {});
|
|
18920
19373
|
var EMPTY_ACL = { whitelistMode: false };
|
|
18921
19374
|
var SUBAGENT_APPLY_DENY_LIST = new Set([
|
|
18922
19375
|
"coder",
|
|
@@ -19000,18 +19453,18 @@ function classifyToolKind(toolName) {
|
|
|
19000
19453
|
return "webfetch";
|
|
19001
19454
|
return "other";
|
|
19002
19455
|
}
|
|
19003
|
-
async function resolveCurrentAgent(client, sessionID,
|
|
19456
|
+
async function resolveCurrentAgent(client, sessionID, log13) {
|
|
19004
19457
|
try {
|
|
19005
19458
|
const sessionApi = client?.session;
|
|
19006
19459
|
if (!sessionApi || typeof sessionApi.get !== "function") {
|
|
19007
|
-
|
|
19460
|
+
log13.warn(`client.session.get unavailable`, { sessionID });
|
|
19008
19461
|
return;
|
|
19009
19462
|
}
|
|
19010
19463
|
const res = await sessionApi.get({ path: { id: sessionID } });
|
|
19011
19464
|
const data = res?.data ?? res;
|
|
19012
19465
|
const rawAgent = data?.agent;
|
|
19013
19466
|
if (typeof rawAgent !== "string" || rawAgent === "") {
|
|
19014
|
-
|
|
19467
|
+
log13.warn(`client.session.get returned no string agent (保守放行)`, {
|
|
19015
19468
|
sessionID,
|
|
19016
19469
|
dataKeys: data && typeof data === "object" ? Object.keys(data) : null
|
|
19017
19470
|
});
|
|
@@ -19019,20 +19472,20 @@ async function resolveCurrentAgent(client, sessionID, log12) {
|
|
|
19019
19472
|
}
|
|
19020
19473
|
return rawAgent;
|
|
19021
19474
|
} catch (err) {
|
|
19022
|
-
|
|
19475
|
+
log13.warn(`client.session.get failed (保守放行)`, {
|
|
19023
19476
|
sessionID,
|
|
19024
19477
|
error: err instanceof Error ? err.message : String(err)
|
|
19025
19478
|
});
|
|
19026
19479
|
return;
|
|
19027
19480
|
}
|
|
19028
19481
|
}
|
|
19029
|
-
var
|
|
19482
|
+
var log13 = makePluginLogger(PLUGIN_NAME22);
|
|
19030
19483
|
var toolPolicyServer = async (ctx) => {
|
|
19031
19484
|
const directory = ctx.directory ?? process.cwd();
|
|
19032
19485
|
const cfg = await loadPolicy(directory);
|
|
19033
19486
|
const rt = loadRuntimeSync();
|
|
19034
19487
|
cfg.defaultMode = rt.runtime.autonomy.default_mode;
|
|
19035
|
-
logLifecycle(
|
|
19488
|
+
logLifecycle(PLUGIN_NAME22, "activate", {
|
|
19036
19489
|
directory,
|
|
19037
19490
|
acl_loaded: !!cfg.acl,
|
|
19038
19491
|
default_mode: cfg.defaultMode,
|
|
@@ -19041,13 +19494,13 @@ var toolPolicyServer = async (ctx) => {
|
|
|
19041
19494
|
return {
|
|
19042
19495
|
"tool.execute.before": async (input, output) => {
|
|
19043
19496
|
let denied;
|
|
19044
|
-
await safeAsync(
|
|
19497
|
+
await safeAsync(PLUGIN_NAME22, "tool.execute.before", async () => {
|
|
19045
19498
|
const toolName = input.tool;
|
|
19046
19499
|
const argsObj = output.args ?? {};
|
|
19047
19500
|
const needsAgentDetection = toolName === "pending_changes" && (argsObj.action === "apply" || argsObj.action === "apply_all");
|
|
19048
19501
|
let currentAgent = undefined;
|
|
19049
19502
|
if (needsAgentDetection) {
|
|
19050
|
-
currentAgent = await resolveCurrentAgent(ctx.client, input.sessionID,
|
|
19503
|
+
currentAgent = await resolveCurrentAgent(ctx.client, input.sessionID, log13);
|
|
19051
19504
|
}
|
|
19052
19505
|
const files = [];
|
|
19053
19506
|
const filePath = argsObj["path"] ?? argsObj["filePath"] ?? argsObj["file"];
|
|
@@ -19062,7 +19515,7 @@ var toolPolicyServer = async (ctx) => {
|
|
|
19062
19515
|
mode: cfg.defaultMode,
|
|
19063
19516
|
files: files.length ? files : undefined
|
|
19064
19517
|
}, cfg, currentAgent);
|
|
19065
|
-
safeWriteLog(
|
|
19518
|
+
safeWriteLog(PLUGIN_NAME22, {
|
|
19066
19519
|
hook: "tool.execute.before",
|
|
19067
19520
|
tool: toolName,
|
|
19068
19521
|
callID: input.callID,
|
|
@@ -19072,14 +19525,14 @@ var toolPolicyServer = async (ctx) => {
|
|
|
19072
19525
|
currentAgent
|
|
19073
19526
|
});
|
|
19074
19527
|
if (decision.action === "deny") {
|
|
19075
|
-
|
|
19528
|
+
log13.warn(`[${PLUGIN_NAME22}] DENY ${toolName}`, {
|
|
19076
19529
|
action: argsObj.action,
|
|
19077
19530
|
currentAgent,
|
|
19078
19531
|
reasons: decision.reasons
|
|
19079
19532
|
});
|
|
19080
19533
|
denied = new Error(`[tool-policy] DENIED: ${decision.reasons.join(" / ")}`);
|
|
19081
19534
|
} else if (decision.action === "confirm") {
|
|
19082
|
-
|
|
19535
|
+
log13.info(`[${PLUGIN_NAME22}] CONFIRM ${toolName}: ${decision.reasons.join("; ")}`);
|
|
19083
19536
|
}
|
|
19084
19537
|
});
|
|
19085
19538
|
if (denied)
|
|
@@ -19087,13 +19540,13 @@ var toolPolicyServer = async (ctx) => {
|
|
|
19087
19540
|
}
|
|
19088
19541
|
};
|
|
19089
19542
|
};
|
|
19090
|
-
var
|
|
19543
|
+
var handler22 = toolPolicyServer;
|
|
19091
19544
|
|
|
19092
19545
|
// plugins/update-checker.ts
|
|
19093
19546
|
init_opencode_plugin_helpers();
|
|
19094
19547
|
import { existsSync as existsSync5 } from "node:fs";
|
|
19095
19548
|
import { homedir as homedir8 } from "node:os";
|
|
19096
|
-
import { join as
|
|
19549
|
+
import { join as join19 } from "node:path";
|
|
19097
19550
|
|
|
19098
19551
|
// lib/update-checker-impl.ts
|
|
19099
19552
|
import { createHash as createHash6 } from "node:crypto";
|
|
@@ -19102,15 +19555,15 @@ import {
|
|
|
19102
19555
|
existsSync as existsSync4,
|
|
19103
19556
|
mkdirSync as mkdirSync3,
|
|
19104
19557
|
mkdtempSync,
|
|
19105
|
-
readFileSync as
|
|
19106
|
-
readdirSync as
|
|
19558
|
+
readFileSync as readFileSync6,
|
|
19559
|
+
readdirSync as readdirSync3,
|
|
19107
19560
|
renameSync,
|
|
19108
|
-
statSync as
|
|
19561
|
+
statSync as statSync5,
|
|
19109
19562
|
unlinkSync,
|
|
19110
19563
|
writeFileSync as writeFileSync2
|
|
19111
19564
|
} from "node:fs";
|
|
19112
19565
|
import { homedir as homedir7, tmpdir } from "node:os";
|
|
19113
|
-
import { dirname as dirname7, join as
|
|
19566
|
+
import { dirname as dirname7, join as join18 } from "node:path";
|
|
19114
19567
|
import { fileURLToPath } from "node:url";
|
|
19115
19568
|
import * as https from "node:https";
|
|
19116
19569
|
import * as zlib from "node:zlib";
|
|
@@ -19118,7 +19571,7 @@ import * as zlib from "node:zlib";
|
|
|
19118
19571
|
// lib/version-injected.ts
|
|
19119
19572
|
function getInjectedVersion() {
|
|
19120
19573
|
try {
|
|
19121
|
-
const v = "0.3.
|
|
19574
|
+
const v = "0.3.14";
|
|
19122
19575
|
if (typeof v === "string" && /^\d+\.\d+\.\d+/.test(v)) {
|
|
19123
19576
|
return v;
|
|
19124
19577
|
}
|
|
@@ -19208,23 +19661,23 @@ function readLocalVersion() {
|
|
|
19208
19661
|
try {
|
|
19209
19662
|
const here = fileURLToPath(import.meta.url);
|
|
19210
19663
|
const root = dirname7(dirname7(here));
|
|
19211
|
-
const pkg = JSON.parse(
|
|
19664
|
+
const pkg = JSON.parse(readFileSync6(join18(root, "package.json"), "utf8"));
|
|
19212
19665
|
return typeof pkg.version === "string" ? pkg.version : "0.0.0";
|
|
19213
19666
|
} catch {
|
|
19214
19667
|
return "0.0.0";
|
|
19215
19668
|
}
|
|
19216
19669
|
}
|
|
19217
19670
|
function defaultCacheDir() {
|
|
19218
|
-
return process.env["CODEFORGE_CACHE_DIR"] ??
|
|
19671
|
+
return process.env["CODEFORGE_CACHE_DIR"] ?? join18(homedir7(), ".cache", "codeforge");
|
|
19219
19672
|
}
|
|
19220
19673
|
function defaultCacheFile() {
|
|
19221
|
-
return
|
|
19674
|
+
return join18(defaultCacheDir(), "update-check.json");
|
|
19222
19675
|
}
|
|
19223
19676
|
function readCache(file) {
|
|
19224
19677
|
try {
|
|
19225
19678
|
if (!existsSync4(file))
|
|
19226
19679
|
return null;
|
|
19227
|
-
const raw =
|
|
19680
|
+
const raw = readFileSync6(file, "utf8");
|
|
19228
19681
|
const obj = JSON.parse(raw);
|
|
19229
19682
|
if (obj && typeof obj === "object" && typeof obj.checkedAt === "number" && typeof obj.remote === "string" && typeof obj.repo === "string") {
|
|
19230
19683
|
return obj;
|
|
@@ -19374,14 +19827,14 @@ function defaultHttpFetcher(url, timeoutMs) {
|
|
|
19374
19827
|
});
|
|
19375
19828
|
}
|
|
19376
19829
|
async function downloadAndExtractBundle(opts) {
|
|
19377
|
-
const tmpRoot = opts.tmpDir ?? mkdtempSync(
|
|
19830
|
+
const tmpRoot = opts.tmpDir ?? mkdtempSync(join18(tmpdir(), "codeforge-update-"));
|
|
19378
19831
|
mkdirSync3(tmpRoot, { recursive: true });
|
|
19379
19832
|
const fetcher = opts.tarballFetcher ?? defaultBinaryFetcher;
|
|
19380
19833
|
const tarballBuf = await fetcher(opts.tarballUrl);
|
|
19381
19834
|
verifyIntegrity(tarballBuf, opts.expectedIntegrity);
|
|
19382
19835
|
const tarBuf = zlib.gunzipSync(tarballBuf);
|
|
19383
19836
|
extractTarToDir(tarBuf, tmpRoot);
|
|
19384
|
-
const bundlePath =
|
|
19837
|
+
const bundlePath = join18(tmpRoot, "package", "dist", "index.js");
|
|
19385
19838
|
if (!existsSync4(bundlePath)) {
|
|
19386
19839
|
throw new Error(`bundle_not_found: ${bundlePath}`);
|
|
19387
19840
|
}
|
|
@@ -19421,11 +19874,11 @@ function extractTarToDir(tarBuf, destRoot) {
|
|
|
19421
19874
|
offset += 512;
|
|
19422
19875
|
if (typeFlag === "0" || typeFlag === "" || typeFlag === "\x00") {
|
|
19423
19876
|
const fileBuf = tarBuf.subarray(offset, offset + size);
|
|
19424
|
-
const dest =
|
|
19877
|
+
const dest = join18(destRoot, fullName);
|
|
19425
19878
|
mkdirSync3(dirname7(dest), { recursive: true });
|
|
19426
19879
|
writeFileSync2(dest, fileBuf);
|
|
19427
19880
|
} else if (typeFlag === "5") {
|
|
19428
|
-
mkdirSync3(
|
|
19881
|
+
mkdirSync3(join18(destRoot, fullName), { recursive: true });
|
|
19429
19882
|
}
|
|
19430
19883
|
offset += Math.ceil(size / 512) * 512;
|
|
19431
19884
|
}
|
|
@@ -19533,11 +19986,11 @@ function cleanupOldBackups(target, keep) {
|
|
|
19533
19986
|
const dir = dirname7(target);
|
|
19534
19987
|
const base = target.substring(dir.length + 1);
|
|
19535
19988
|
const prefix = `${base}.bak.`;
|
|
19536
|
-
const all =
|
|
19537
|
-
const full =
|
|
19989
|
+
const all = readdirSync3(dir).filter((f) => f.startsWith(prefix)).map((f) => {
|
|
19990
|
+
const full = join18(dir, f);
|
|
19538
19991
|
let mtimeMs = 0;
|
|
19539
19992
|
try {
|
|
19540
|
-
mtimeMs =
|
|
19993
|
+
mtimeMs = statSync5(full).mtimeMs;
|
|
19541
19994
|
} catch {}
|
|
19542
19995
|
return { full, mtimeMs };
|
|
19543
19996
|
}).sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
@@ -19556,11 +20009,11 @@ function loadCompatibility(opts) {
|
|
|
19556
20009
|
const root = opts?.cwd ?? inferPluginRoot();
|
|
19557
20010
|
if (!root)
|
|
19558
20011
|
return null;
|
|
19559
|
-
file =
|
|
20012
|
+
file = join18(root, "compatibility.json");
|
|
19560
20013
|
}
|
|
19561
20014
|
if (!existsSync4(file))
|
|
19562
20015
|
return null;
|
|
19563
|
-
const raw =
|
|
20016
|
+
const raw = readFileSync6(file, "utf8");
|
|
19564
20017
|
const obj = JSON.parse(raw);
|
|
19565
20018
|
if (!obj || typeof obj !== "object")
|
|
19566
20019
|
return null;
|
|
@@ -19621,20 +20074,20 @@ function compareOpencodeVersion(opts) {
|
|
|
19621
20074
|
}
|
|
19622
20075
|
|
|
19623
20076
|
// plugins/update-checker.ts
|
|
19624
|
-
var
|
|
20077
|
+
var PLUGIN_NAME23 = "update-checker";
|
|
19625
20078
|
var PLUGIN_VERSION = "2.0.0";
|
|
19626
|
-
logLifecycle(
|
|
20079
|
+
logLifecycle(PLUGIN_NAME23, "import", { version: PLUGIN_VERSION });
|
|
19627
20080
|
var updateCheckerServer = async (ctx) => {
|
|
19628
20081
|
const yieldResult = shouldYieldToLocalPlugin({ directory: ctx.directory });
|
|
19629
20082
|
if (yieldResult.yield) {
|
|
19630
|
-
safeWriteLog(
|
|
20083
|
+
safeWriteLog(PLUGIN_NAME23, {
|
|
19631
20084
|
level: "info",
|
|
19632
20085
|
msg: "dev_mode_yield_skip",
|
|
19633
20086
|
reason: yieldResult.reason,
|
|
19634
20087
|
markerPath: yieldResult.markerPath,
|
|
19635
20088
|
detail: formatYieldLog(yieldResult)
|
|
19636
20089
|
});
|
|
19637
|
-
logLifecycle(
|
|
20090
|
+
logLifecycle(PLUGIN_NAME23, "activate", {
|
|
19638
20091
|
yield_to_local: true,
|
|
19639
20092
|
yield_reason: yieldResult.reason,
|
|
19640
20093
|
skipped: "auto_install + background_check"
|
|
@@ -19643,7 +20096,7 @@ var updateCheckerServer = async (ctx) => {
|
|
|
19643
20096
|
}
|
|
19644
20097
|
const rt = loadRuntimeSync();
|
|
19645
20098
|
const u = rt.runtime.update;
|
|
19646
|
-
logLifecycle(
|
|
20099
|
+
logLifecycle(PLUGIN_NAME23, "activate", {
|
|
19647
20100
|
version: PLUGIN_VERSION,
|
|
19648
20101
|
auto_check_enabled: u.auto_check_enabled,
|
|
19649
20102
|
interval_hours: u.interval_hours,
|
|
@@ -19655,14 +20108,14 @@ var updateCheckerServer = async (ctx) => {
|
|
|
19655
20108
|
repo_fallback: u.repo,
|
|
19656
20109
|
config_source: "codeforge.json"
|
|
19657
20110
|
});
|
|
19658
|
-
await safeAsync(
|
|
20111
|
+
await safeAsync(PLUGIN_NAME23, "opencode_version_check", async () => {
|
|
19659
20112
|
const compat = loadCompatibility();
|
|
19660
20113
|
const opencodeVer = detectOpencodeVersion();
|
|
19661
20114
|
const verdict = compareOpencodeVersion({
|
|
19662
20115
|
currentOpencodeVer: opencodeVer,
|
|
19663
20116
|
compat
|
|
19664
20117
|
});
|
|
19665
|
-
safeWriteLog(
|
|
20118
|
+
safeWriteLog(PLUGIN_NAME23, {
|
|
19666
20119
|
level: "info",
|
|
19667
20120
|
msg: "opencode_version_check",
|
|
19668
20121
|
opencodeVer,
|
|
@@ -19679,14 +20132,14 @@ var updateCheckerServer = async (ctx) => {
|
|
|
19679
20132
|
}
|
|
19680
20133
|
});
|
|
19681
20134
|
if (!u.auto_check_enabled) {
|
|
19682
|
-
safeWriteLog(
|
|
20135
|
+
safeWriteLog(PLUGIN_NAME23, {
|
|
19683
20136
|
level: "info",
|
|
19684
20137
|
msg: "auto-check disabled (codeforge.json update.auto_check_enabled=false)"
|
|
19685
20138
|
});
|
|
19686
20139
|
return {};
|
|
19687
20140
|
}
|
|
19688
20141
|
setImmediate(() => {
|
|
19689
|
-
safeAsync(
|
|
20142
|
+
safeAsync(PLUGIN_NAME23, "checkAndMaybeUpdate", async () => {
|
|
19690
20143
|
const local = readLocalVersion();
|
|
19691
20144
|
let npmResult = null;
|
|
19692
20145
|
try {
|
|
@@ -19697,7 +20150,7 @@ var updateCheckerServer = async (ctx) => {
|
|
|
19697
20150
|
timeoutMs: 5000
|
|
19698
20151
|
});
|
|
19699
20152
|
} catch (e) {
|
|
19700
|
-
safeWriteLog(
|
|
20153
|
+
safeWriteLog(PLUGIN_NAME23, {
|
|
19701
20154
|
level: "warn",
|
|
19702
20155
|
msg: "npm_fetch_failed",
|
|
19703
20156
|
error: e.message
|
|
@@ -19706,7 +20159,7 @@ var updateCheckerServer = async (ctx) => {
|
|
|
19706
20159
|
return;
|
|
19707
20160
|
}
|
|
19708
20161
|
if (!npmResult) {
|
|
19709
|
-
safeWriteLog(
|
|
20162
|
+
safeWriteLog(PLUGIN_NAME23, {
|
|
19710
20163
|
level: "info",
|
|
19711
20164
|
msg: "npm_no_release",
|
|
19712
20165
|
package: u.package,
|
|
@@ -19715,7 +20168,7 @@ var updateCheckerServer = async (ctx) => {
|
|
|
19715
20168
|
return;
|
|
19716
20169
|
}
|
|
19717
20170
|
const hasUpdate = cmpVersion(local, npmResult.version) < 0;
|
|
19718
|
-
safeWriteLog(
|
|
20171
|
+
safeWriteLog(PLUGIN_NAME23, {
|
|
19719
20172
|
level: "info",
|
|
19720
20173
|
msg: "npm_check_result",
|
|
19721
20174
|
local,
|
|
@@ -19730,10 +20183,10 @@ var updateCheckerServer = async (ctx) => {
|
|
|
19730
20183
|
更新命令:npx ${u.package} install --global`);
|
|
19731
20184
|
return;
|
|
19732
20185
|
}
|
|
19733
|
-
await safeAsync(
|
|
20186
|
+
await safeAsync(PLUGIN_NAME23, "auto_install_bundle", async () => {
|
|
19734
20187
|
const target = getOpencodeBundlePath();
|
|
19735
20188
|
if (!target) {
|
|
19736
|
-
safeWriteLog(
|
|
20189
|
+
safeWriteLog(PLUGIN_NAME23, {
|
|
19737
20190
|
level: "warn",
|
|
19738
20191
|
msg: "auto_install_skip",
|
|
19739
20192
|
reason: "无法定位 opencode bundle 路径"
|
|
@@ -19752,7 +20205,7 @@ var updateCheckerServer = async (ctx) => {
|
|
|
19752
20205
|
oldVersion: local,
|
|
19753
20206
|
keepBackups: u.backup_keep
|
|
19754
20207
|
});
|
|
19755
|
-
safeWriteLog(
|
|
20208
|
+
safeWriteLog(PLUGIN_NAME23, {
|
|
19756
20209
|
level: "info",
|
|
19757
20210
|
msg: "auto_install_success",
|
|
19758
20211
|
local,
|
|
@@ -19776,14 +20229,14 @@ function detectOpencodeVersion() {
|
|
|
19776
20229
|
}
|
|
19777
20230
|
function getOpencodeBundlePath() {
|
|
19778
20231
|
const candidates = [];
|
|
19779
|
-
candidates.push(
|
|
20232
|
+
candidates.push(join19(homedir8(), ".config", "opencode", "codeforge", "index.js"));
|
|
19780
20233
|
if (process.platform === "win32") {
|
|
19781
20234
|
const appData = process.env["APPDATA"];
|
|
19782
20235
|
if (appData)
|
|
19783
|
-
candidates.push(
|
|
20236
|
+
candidates.push(join19(appData, "opencode", "codeforge", "index.js"));
|
|
19784
20237
|
const localAppData = process.env["LOCALAPPDATA"];
|
|
19785
20238
|
if (localAppData)
|
|
19786
|
-
candidates.push(
|
|
20239
|
+
candidates.push(join19(localAppData, "opencode", "codeforge", "index.js"));
|
|
19787
20240
|
}
|
|
19788
20241
|
for (const c of candidates) {
|
|
19789
20242
|
if (existsSync5(c))
|
|
@@ -19792,7 +20245,7 @@ function getOpencodeBundlePath() {
|
|
|
19792
20245
|
return candidates[0] ?? null;
|
|
19793
20246
|
}
|
|
19794
20247
|
async function fallbackToGitHubReleases(ctx, u) {
|
|
19795
|
-
await safeAsync(
|
|
20248
|
+
await safeAsync(PLUGIN_NAME23, "github_fallback", async () => {
|
|
19796
20249
|
const result = await checkUpdateOnce({
|
|
19797
20250
|
repo: u.repo,
|
|
19798
20251
|
intervalMs: u.interval_hours * 3600 * 1000
|
|
@@ -19802,7 +20255,7 @@ async function fallbackToGitHubReleases(ctx, u) {
|
|
|
19802
20255
|
}
|
|
19803
20256
|
async function reportLegacyResult(ctx, result, repo) {
|
|
19804
20257
|
if (result.error && !result.fromCache) {
|
|
19805
|
-
safeWriteLog(
|
|
20258
|
+
safeWriteLog(PLUGIN_NAME23, {
|
|
19806
20259
|
level: "warn",
|
|
19807
20260
|
msg: `update check failed: ${result.error}`,
|
|
19808
20261
|
...result
|
|
@@ -19810,7 +20263,7 @@ async function reportLegacyResult(ctx, result, repo) {
|
|
|
19810
20263
|
return;
|
|
19811
20264
|
}
|
|
19812
20265
|
if (!result.hasUpdate) {
|
|
19813
|
-
safeWriteLog(
|
|
20266
|
+
safeWriteLog(PLUGIN_NAME23, {
|
|
19814
20267
|
level: "info",
|
|
19815
20268
|
msg: `up-to-date (local=${result.local}, remote=${result.remote}${result.fromCache ? ", from_cache" : ""})`,
|
|
19816
20269
|
...result
|
|
@@ -19820,7 +20273,7 @@ async function reportLegacyResult(ctx, result, repo) {
|
|
|
19820
20273
|
const updateCmd = `bunx --bun github:${repo} install`;
|
|
19821
20274
|
const toast = `[CodeForge] 有新版本:${result.local} → ${result.remote}
|
|
19822
20275
|
更新命令:${updateCmd}`;
|
|
19823
|
-
safeWriteLog(
|
|
20276
|
+
safeWriteLog(PLUGIN_NAME23, {
|
|
19824
20277
|
level: "info",
|
|
19825
20278
|
msg: "new_version_available_github_fallback",
|
|
19826
20279
|
local: result.local,
|
|
@@ -19831,17 +20284,17 @@ async function reportLegacyResult(ctx, result, repo) {
|
|
|
19831
20284
|
await postToast(ctx, toast);
|
|
19832
20285
|
}
|
|
19833
20286
|
async function postToast(ctx, message) {
|
|
19834
|
-
await safeAsync(
|
|
20287
|
+
await safeAsync(PLUGIN_NAME23, "client.app.log", async () => {
|
|
19835
20288
|
await ctx.client.app.log({
|
|
19836
20289
|
body: {
|
|
19837
|
-
service:
|
|
20290
|
+
service: PLUGIN_NAME23,
|
|
19838
20291
|
level: "info",
|
|
19839
20292
|
message
|
|
19840
20293
|
}
|
|
19841
20294
|
});
|
|
19842
20295
|
});
|
|
19843
20296
|
}
|
|
19844
|
-
var
|
|
20297
|
+
var handler23 = updateCheckerServer;
|
|
19845
20298
|
|
|
19846
20299
|
// plugins/workflow-engine.ts
|
|
19847
20300
|
init_opencode_plugin_helpers();
|
|
@@ -19850,55 +20303,55 @@ import * as path19 from "node:path";
|
|
|
19850
20303
|
// lib/workflow-loader.ts
|
|
19851
20304
|
import { promises as fs14 } from "node:fs";
|
|
19852
20305
|
import * as path18 from "node:path";
|
|
19853
|
-
import { z as
|
|
19854
|
-
var ActionSchema =
|
|
19855
|
-
tool:
|
|
19856
|
-
args:
|
|
19857
|
-
on_error:
|
|
20306
|
+
import { z as z29 } from "zod";
|
|
20307
|
+
var ActionSchema = z29.object({
|
|
20308
|
+
tool: z29.string().min(1, "action.tool 不能为空"),
|
|
20309
|
+
args: z29.record(z29.string(), z29.unknown()).optional().default({}),
|
|
20310
|
+
on_error: z29.enum(["retry", "skip", "abort"]).optional()
|
|
19858
20311
|
});
|
|
19859
|
-
var StepSchema =
|
|
19860
|
-
name:
|
|
19861
|
-
agent:
|
|
19862
|
-
description:
|
|
19863
|
-
inject_context:
|
|
19864
|
-
requires_human_approval:
|
|
19865
|
-
actions:
|
|
19866
|
-
on_error:
|
|
19867
|
-
max_retries:
|
|
19868
|
-
timeout:
|
|
19869
|
-
auto_feedback:
|
|
19870
|
-
test_cmd:
|
|
19871
|
-
lint_cmd:
|
|
19872
|
-
max_retries:
|
|
19873
|
-
error_excerpt_lines:
|
|
19874
|
-
escalate_to:
|
|
20312
|
+
var StepSchema = z29.object({
|
|
20313
|
+
name: z29.string().min(1, "step.name 不能为空"),
|
|
20314
|
+
agent: z29.string().min(1, "step.agent 不能为空").describe("agent 名(与 agents/<name>.md 对应)"),
|
|
20315
|
+
description: z29.string().optional(),
|
|
20316
|
+
inject_context: z29.record(z29.string(), z29.unknown()).optional(),
|
|
20317
|
+
requires_human_approval: z29.boolean().optional().default(false),
|
|
20318
|
+
actions: z29.array(ActionSchema).optional().default([]),
|
|
20319
|
+
on_error: z29.enum(["retry", "skip", "abort"]).optional().default("abort"),
|
|
20320
|
+
max_retries: z29.number().int().min(0).max(10).optional().default(2),
|
|
20321
|
+
timeout: z29.string().regex(/^\d+(?:ms|s|m|h)$/, "timeout 必须是 数字+单位(ms/s/m/h),如 5m").optional(),
|
|
20322
|
+
auto_feedback: z29.object({
|
|
20323
|
+
test_cmd: z29.string().optional().describe("测试命令,如 npm test"),
|
|
20324
|
+
lint_cmd: z29.string().optional().describe("lint 命令,如 npm run lint"),
|
|
20325
|
+
max_retries: z29.number().int().min(1).max(10).optional().default(3),
|
|
20326
|
+
error_excerpt_lines: z29.number().int().min(1).max(50).optional().default(5),
|
|
20327
|
+
escalate_to: z29.string().optional().default("reviewer").describe("超上限后兜底 agent,默认 reviewer")
|
|
19875
20328
|
}).refine((d) => Boolean(d.test_cmd || d.lint_cmd), { message: "auto_feedback 必须至少配置 test_cmd 或 lint_cmd 之一" }).optional(),
|
|
19876
|
-
on_decision:
|
|
19877
|
-
APPROVE:
|
|
19878
|
-
|
|
19879
|
-
|
|
19880
|
-
|
|
20329
|
+
on_decision: z29.object({
|
|
20330
|
+
APPROVE: z29.union([
|
|
20331
|
+
z29.literal("continue"),
|
|
20332
|
+
z29.literal("abort"),
|
|
20333
|
+
z29.object({ action: z29.literal("goto"), target: z29.string().min(1) })
|
|
19881
20334
|
]).optional(),
|
|
19882
|
-
REQUEST_CHANGES:
|
|
19883
|
-
|
|
19884
|
-
|
|
19885
|
-
|
|
20335
|
+
REQUEST_CHANGES: z29.union([
|
|
20336
|
+
z29.literal("continue"),
|
|
20337
|
+
z29.literal("abort"),
|
|
20338
|
+
z29.object({ action: z29.literal("goto"), target: z29.string().min(1) })
|
|
19886
20339
|
]).optional(),
|
|
19887
|
-
BLOCK:
|
|
19888
|
-
|
|
19889
|
-
|
|
19890
|
-
|
|
20340
|
+
BLOCK: z29.union([
|
|
20341
|
+
z29.literal("continue"),
|
|
20342
|
+
z29.literal("abort"),
|
|
20343
|
+
z29.object({ action: z29.literal("goto"), target: z29.string().min(1) })
|
|
19891
20344
|
]).optional()
|
|
19892
20345
|
}).refine((d) => Boolean(d.APPROVE || d.REQUEST_CHANGES || d.BLOCK), { message: "on_decision 必须至少配置 APPROVE / REQUEST_CHANGES / BLOCK 之一" }).optional()
|
|
19893
20346
|
}).strict();
|
|
19894
|
-
var WorkflowSchema =
|
|
19895
|
-
name:
|
|
19896
|
-
description:
|
|
19897
|
-
version:
|
|
19898
|
-
trigger:
|
|
19899
|
-
context_template:
|
|
19900
|
-
max_loops:
|
|
19901
|
-
steps:
|
|
20347
|
+
var WorkflowSchema = z29.object({
|
|
20348
|
+
name: z29.string().min(1, "workflow.name 不能为空"),
|
|
20349
|
+
description: z29.string().optional().default(""),
|
|
20350
|
+
version: z29.string().optional().default("1.0.0"),
|
|
20351
|
+
trigger: z29.string().min(1).refine((v) => /^\/[a-z][\w-]*$/.test(v) || /^event:[a-z][\w.-]+$/.test(v), "trigger 必须是 /command-name 或 event:xxx 形式"),
|
|
20352
|
+
context_template: z29.string().optional(),
|
|
20353
|
+
max_loops: z29.number().int().min(1).max(10).optional().default(3),
|
|
20354
|
+
steps: z29.array(StepSchema).min(1, "workflow.steps 不能为空")
|
|
19902
20355
|
}).strict();
|
|
19903
20356
|
function parseWorkflowYaml(yaml, sourcePath = "<inline>") {
|
|
19904
20357
|
let raw;
|
|
@@ -20297,9 +20750,9 @@ async function runStepAutoFeedback(step, adapter) {
|
|
|
20297
20750
|
}
|
|
20298
20751
|
|
|
20299
20752
|
// plugins/workflow-engine.ts
|
|
20300
|
-
var
|
|
20301
|
-
logLifecycle(
|
|
20302
|
-
var fallbackLog2 = makePluginLogger(
|
|
20753
|
+
var PLUGIN_NAME24 = "workflow-engine";
|
|
20754
|
+
logLifecycle(PLUGIN_NAME24, "import", {});
|
|
20755
|
+
var fallbackLog2 = makePluginLogger(PLUGIN_NAME24);
|
|
20303
20756
|
var _registry = null;
|
|
20304
20757
|
async function loadRegistry(workflowsDir) {
|
|
20305
20758
|
const { loaded, failed } = await loadWorkflowsFromDir(workflowsDir);
|
|
@@ -20316,35 +20769,35 @@ async function ensureRegistry(workflowsDir = "workflows") {
|
|
|
20316
20769
|
}
|
|
20317
20770
|
async function handleCommandInvoked(raw, workflowsDir = "workflows") {
|
|
20318
20771
|
const ctx = raw ?? {};
|
|
20319
|
-
const
|
|
20772
|
+
const log14 = ctx.log ?? fallbackLog2;
|
|
20320
20773
|
const command = typeof ctx.command === "string" ? ctx.command : null;
|
|
20321
20774
|
if (!command) {
|
|
20322
|
-
|
|
20775
|
+
log14.warn(`[${PLUGIN_NAME24}] command.invoked 缺 command 字段`, ctx);
|
|
20323
20776
|
return null;
|
|
20324
20777
|
}
|
|
20325
20778
|
const reg = await ensureRegistry(workflowsDir);
|
|
20326
20779
|
if (reg.errors.length) {
|
|
20327
|
-
|
|
20780
|
+
log14.warn(`[${PLUGIN_NAME24}] 有 ${reg.errors.length} 个 workflow 加载失败`, reg.errors);
|
|
20328
20781
|
}
|
|
20329
20782
|
const wf = reg.workflows.find((w) => matchesTrigger(w, command));
|
|
20330
20783
|
if (!wf) {
|
|
20331
|
-
|
|
20784
|
+
log14.info(`[${PLUGIN_NAME24}] no workflow matches "${command}"`);
|
|
20332
20785
|
return null;
|
|
20333
20786
|
}
|
|
20334
|
-
|
|
20787
|
+
log14.info(`[${PLUGIN_NAME24}] dispatch "${command}" → workflow "${wf.name}"`);
|
|
20335
20788
|
try {
|
|
20336
20789
|
const result = await run(wf, {
|
|
20337
20790
|
mode: ctx.adapter ? "real" : "dry_run",
|
|
20338
20791
|
autonomy: ctx.autonomy ?? "semi",
|
|
20339
20792
|
adapter: ctx.adapter
|
|
20340
20793
|
});
|
|
20341
|
-
|
|
20794
|
+
log14.info(`[${PLUGIN_NAME24}] workflow "${wf.name}" 完成 (${result.plan.mode})`, {
|
|
20342
20795
|
steps: result.plan.steps.length,
|
|
20343
20796
|
results: result.results.length
|
|
20344
20797
|
});
|
|
20345
20798
|
return result;
|
|
20346
20799
|
} catch (err) {
|
|
20347
|
-
|
|
20800
|
+
log14.error(`[${PLUGIN_NAME24}] workflow "${wf.name}" 执行失败`, {
|
|
20348
20801
|
error: err instanceof Error ? err.message : String(err)
|
|
20349
20802
|
});
|
|
20350
20803
|
throw err;
|
|
@@ -20353,15 +20806,15 @@ async function handleCommandInvoked(raw, workflowsDir = "workflows") {
|
|
|
20353
20806
|
var workflowEngineServer = async (ctx) => {
|
|
20354
20807
|
const directory = ctx.directory ?? process.cwd();
|
|
20355
20808
|
const workflowsDir = path19.join(directory, "workflows");
|
|
20356
|
-
ensureRegistry(workflowsDir).catch((err) => fallbackLog2.warn(`[${
|
|
20809
|
+
ensureRegistry(workflowsDir).catch((err) => fallbackLog2.warn(`[${PLUGIN_NAME24}] preload workflows failed`, {
|
|
20357
20810
|
error: err instanceof Error ? err.message : String(err)
|
|
20358
20811
|
}));
|
|
20359
|
-
logLifecycle(
|
|
20812
|
+
logLifecycle(PLUGIN_NAME24, "activate", { directory, workflowsDir });
|
|
20360
20813
|
return {
|
|
20361
20814
|
"command.execute.before": async (input, output) => {
|
|
20362
|
-
await safeAsync(
|
|
20815
|
+
await safeAsync(PLUGIN_NAME24, "command.execute.before", async () => {
|
|
20363
20816
|
const cmd = input.command.startsWith("/") ? input.command : `/${input.command}`;
|
|
20364
|
-
safeWriteLog(
|
|
20817
|
+
safeWriteLog(PLUGIN_NAME24, {
|
|
20365
20818
|
hook: "command.execute.before",
|
|
20366
20819
|
command: cmd,
|
|
20367
20820
|
sessionID: input.sessionID
|
|
@@ -20376,14 +20829,14 @@ var workflowEngineServer = async (ctx) => {
|
|
|
20376
20829
|
});
|
|
20377
20830
|
},
|
|
20378
20831
|
"chat.message": async (input, output) => {
|
|
20379
|
-
await safeAsync(
|
|
20832
|
+
await safeAsync(PLUGIN_NAME24, "chat.message", async () => {
|
|
20380
20833
|
const text = extractUserText(output).trim();
|
|
20381
20834
|
if (!text.startsWith("/"))
|
|
20382
20835
|
return;
|
|
20383
20836
|
const cmd = text.split(/\s+/)[0];
|
|
20384
20837
|
if (!cmd)
|
|
20385
20838
|
return;
|
|
20386
|
-
safeWriteLog(
|
|
20839
|
+
safeWriteLog(PLUGIN_NAME24, {
|
|
20387
20840
|
hook: "chat.message",
|
|
20388
20841
|
command: cmd,
|
|
20389
20842
|
sessionID: input.sessionID
|
|
@@ -20393,11 +20846,11 @@ var workflowEngineServer = async (ctx) => {
|
|
|
20393
20846
|
}
|
|
20394
20847
|
};
|
|
20395
20848
|
};
|
|
20396
|
-
var
|
|
20849
|
+
var handler24 = workflowEngineServer;
|
|
20397
20850
|
|
|
20398
20851
|
// src/index.ts
|
|
20399
20852
|
var PLUGIN_ID = "codeforge";
|
|
20400
|
-
var
|
|
20853
|
+
var log14 = makePluginLogger(PLUGIN_ID);
|
|
20401
20854
|
logLifecycle(PLUGIN_ID, "import", { entry: "src/index.ts" });
|
|
20402
20855
|
var HANDLERS = [
|
|
20403
20856
|
{ name: "agent-router", init: handler },
|
|
@@ -20407,22 +20860,23 @@ var HANDLERS = [
|
|
|
20407
20860
|
{ name: "auto-learning", init: handler5 },
|
|
20408
20861
|
{ name: "channels", init: handler6 },
|
|
20409
20862
|
{ name: "codeforge-tools", init: handler8 },
|
|
20410
|
-
{ name: "
|
|
20411
|
-
{ name: "kh-
|
|
20412
|
-
{ name: "
|
|
20413
|
-
{ name: "
|
|
20414
|
-
{ name: "
|
|
20415
|
-
{ name: "
|
|
20416
|
-
{ name: "
|
|
20417
|
-
{ name: "
|
|
20418
|
-
{ name: "
|
|
20419
|
-
{ name: "
|
|
20420
|
-
{ name: "
|
|
20421
|
-
{ name: "
|
|
20863
|
+
{ name: "discover-spec-suggest", init: handler9 },
|
|
20864
|
+
{ name: "kh-auto-context", init: handler10 },
|
|
20865
|
+
{ name: "kh-reminder", init: handler11 },
|
|
20866
|
+
{ name: "memories-context", init: handler12 },
|
|
20867
|
+
{ name: "model-fallback", init: handler13 },
|
|
20868
|
+
{ name: "parallel-tool-nudge", init: handler16 },
|
|
20869
|
+
{ name: "pwsh-utf8", init: handler17 },
|
|
20870
|
+
{ name: "session-recovery", init: handler18 },
|
|
20871
|
+
{ name: "subtask-heartbeat", init: handler14 },
|
|
20872
|
+
{ name: "subtasks", init: handler19 },
|
|
20873
|
+
{ name: "parallel-status", init: handler15 },
|
|
20874
|
+
{ name: "terminal-monitor", init: handler20 },
|
|
20875
|
+
{ name: "token-manager", init: handler21 },
|
|
20422
20876
|
{ name: "tool-heartbeat", init: handler7 },
|
|
20423
|
-
{ name: "tool-policy", init:
|
|
20424
|
-
{ name: "update-checker", init:
|
|
20425
|
-
{ name: "workflow-engine", init:
|
|
20877
|
+
{ name: "tool-policy", init: handler22 },
|
|
20878
|
+
{ name: "update-checker", init: handler23 },
|
|
20879
|
+
{ name: "workflow-engine", init: handler24 }
|
|
20426
20880
|
];
|
|
20427
20881
|
function makeSerialHook(hookName, fns) {
|
|
20428
20882
|
return async (input, output) => {
|
|
@@ -20430,7 +20884,7 @@ function makeSerialHook(hookName, fns) {
|
|
|
20430
20884
|
try {
|
|
20431
20885
|
await fn(input, output);
|
|
20432
20886
|
} catch (err) {
|
|
20433
|
-
|
|
20887
|
+
log14.warn(`[${PLUGIN_ID}] ${hookName} handler 异常(已隔离)`, {
|
|
20434
20888
|
error: err instanceof Error ? err.message : String(err)
|
|
20435
20889
|
});
|
|
20436
20890
|
}
|
|
@@ -20443,7 +20897,7 @@ function createCodeforgeServer(opts) {
|
|
|
20443
20897
|
const yieldResult = shouldYieldToLocalPlugin({ directory: input.directory });
|
|
20444
20898
|
if (yieldResult.yield) {
|
|
20445
20899
|
const msg = formatYieldLog(yieldResult);
|
|
20446
|
-
|
|
20900
|
+
log14.info(msg, { reason: yieldResult.reason, markerPath: yieldResult.markerPath });
|
|
20447
20901
|
logLifecycle(PLUGIN_ID, "activate", {
|
|
20448
20902
|
yield_to_local: true,
|
|
20449
20903
|
yield_reason: yieldResult.reason,
|
|
@@ -20461,7 +20915,7 @@ function createCodeforgeServer(opts) {
|
|
|
20461
20915
|
if (r.status === "fulfilled" && r.value && typeof r.value === "object") {
|
|
20462
20916
|
hooksList.push(r.value);
|
|
20463
20917
|
} else if (r.status === "rejected") {
|
|
20464
|
-
|
|
20918
|
+
log14.warn(`[${PLUGIN_ID}] handler ${HANDLERS[i].name} init failed (隔离,其他 handler 继续)`, { error: r.reason instanceof Error ? r.reason.message : String(r.reason) });
|
|
20465
20919
|
}
|
|
20466
20920
|
});
|
|
20467
20921
|
logLifecycle(PLUGIN_ID, "activate", {
|
|
@@ -20510,7 +20964,7 @@ function createCodeforgeServer(opts) {
|
|
|
20510
20964
|
try {
|
|
20511
20965
|
await fn(envelope);
|
|
20512
20966
|
} catch (err) {
|
|
20513
|
-
|
|
20967
|
+
log14.warn(`[${PLUGIN_ID}] event handler 异常(已隔离)`, {
|
|
20514
20968
|
error: err instanceof Error ? err.message : String(err)
|
|
20515
20969
|
});
|
|
20516
20970
|
}
|