@andyqiu/codeforge 0.5.6 → 0.5.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +760 -783
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -750,81 +750,22 @@ var init_kh_client = __esm(() => {
|
|
|
750
750
|
_warnedKeys = new Set;
|
|
751
751
|
});
|
|
752
752
|
|
|
753
|
-
// lib/
|
|
754
|
-
var
|
|
755
|
-
__export(
|
|
753
|
+
// lib/worktree-ops.ts
|
|
754
|
+
var exports_worktree_ops = {};
|
|
755
|
+
__export(exports_worktree_ops, {
|
|
756
756
|
worktreeHasChanges: () => worktreeHasChanges,
|
|
757
757
|
tryMerge: () => tryMerge,
|
|
758
|
-
renderDecision: () => renderDecision,
|
|
759
758
|
removeWorktree: () => removeWorktree,
|
|
760
759
|
mergeCommit: () => mergeCommit,
|
|
761
760
|
mergeAbort: () => mergeAbort,
|
|
762
761
|
listWorktrees: () => listWorktrees,
|
|
763
762
|
getMergeConflicts: () => getMergeConflicts,
|
|
764
|
-
evaluate: () => evaluate,
|
|
765
763
|
ensureWorktree: () => ensureWorktree,
|
|
766
|
-
commitWorktreeIfDirty: () => commitWorktreeIfDirty
|
|
767
|
-
classifyTool: () => classifyTool,
|
|
768
|
-
DEFAULT_POLICIES: () => DEFAULT_POLICIES
|
|
764
|
+
commitWorktreeIfDirty: () => commitWorktreeIfDirty
|
|
769
765
|
});
|
|
770
766
|
import { execFile as execFile2 } from "node:child_process";
|
|
771
767
|
import { promises as fs8 } from "node:fs";
|
|
772
768
|
import * as path11 from "node:path";
|
|
773
|
-
function evaluate(mode, intent, opts = {}) {
|
|
774
|
-
const ignore = new Set(opts.ignore_risks ?? []);
|
|
775
|
-
const allRisks = [...RISK_PATTERNS, ...opts.extra_risks ?? []];
|
|
776
|
-
const haystack = buildHaystack(intent.args);
|
|
777
|
-
const hits = allRisks.filter((p) => !ignore.has(p.tag)).filter((p) => !p.kinds || p.kinds.includes(intent.kind)).filter((p) => p.re.test(haystack)).map((p) => p.tag);
|
|
778
|
-
let effective = mode;
|
|
779
|
-
let downgraded = false;
|
|
780
|
-
let reason;
|
|
781
|
-
if (hits.length > 0 && mode === "full") {
|
|
782
|
-
effective = "semi";
|
|
783
|
-
downgraded = true;
|
|
784
|
-
reason = `检测到风险操作 [${hits.join(", ")}],自动从 full 降级到 semi`;
|
|
785
|
-
} else if (hits.length > 0 && mode !== "step") {}
|
|
786
|
-
const policy = mergePolicy(effective, opts.policies);
|
|
787
|
-
const baseAction = policy[intent.kind] ?? "confirm";
|
|
788
|
-
const action = hits.length > 0 && effective !== "step" ? "confirm" : baseAction;
|
|
789
|
-
return {
|
|
790
|
-
effective_mode: effective,
|
|
791
|
-
action,
|
|
792
|
-
downgraded,
|
|
793
|
-
reason,
|
|
794
|
-
detected_risks: hits
|
|
795
|
-
};
|
|
796
|
-
}
|
|
797
|
-
function mergePolicy(mode, override) {
|
|
798
|
-
const base = DEFAULT_POLICIES[mode];
|
|
799
|
-
if (!override?.[mode])
|
|
800
|
-
return base;
|
|
801
|
-
return { ...base, ...override[mode] };
|
|
802
|
-
}
|
|
803
|
-
function buildHaystack(args) {
|
|
804
|
-
if (typeof args === "string")
|
|
805
|
-
return args;
|
|
806
|
-
try {
|
|
807
|
-
return JSON.stringify(args);
|
|
808
|
-
} catch {
|
|
809
|
-
return String(args);
|
|
810
|
-
}
|
|
811
|
-
}
|
|
812
|
-
function classifyTool(tool) {
|
|
813
|
-
const lower = tool.toLowerCase();
|
|
814
|
-
if (lower in TOOL_KIND_MAP)
|
|
815
|
-
return TOOL_KIND_MAP[lower];
|
|
816
|
-
if (/(^|[-_])edit($|[-_])|write|patch|apply/i.test(lower))
|
|
817
|
-
return "edit";
|
|
818
|
-
if (/bash|shell|exec|run/i.test(lower))
|
|
819
|
-
return "bash";
|
|
820
|
-
if (/fetch|http|curl/i.test(lower))
|
|
821
|
-
return "webfetch";
|
|
822
|
-
if (/read|view/i.test(lower))
|
|
823
|
-
return "read";
|
|
824
|
-
if (/search|grep|glob|find|list/i.test(lower))
|
|
825
|
-
return "search";
|
|
826
|
-
return "other";
|
|
827
|
-
}
|
|
828
769
|
async function ensureWorktree(opts) {
|
|
829
770
|
const root = path11.resolve(opts.root);
|
|
830
771
|
const dir = opts.worktrees_dir ?? path11.join(root, ".git", "codeforge-worktrees");
|
|
@@ -1000,142 +941,7 @@ async function commitWorktreeIfDirty(opts) {
|
|
|
1000
941
|
await runGit(wt, ["commit", "-m", opts.message], timeout);
|
|
1001
942
|
return { committed: true };
|
|
1002
943
|
}
|
|
1003
|
-
|
|
1004
|
-
const lines = [];
|
|
1005
|
-
lines.push(`mode: ${d.effective_mode}${d.downgraded ? " (downgraded)" : ""}`);
|
|
1006
|
-
lines.push(`action: ${d.action}`);
|
|
1007
|
-
if (d.detected_risks.length > 0) {
|
|
1008
|
-
lines.push(`risks: ${d.detected_risks.join(", ")}`);
|
|
1009
|
-
}
|
|
1010
|
-
if (d.reason)
|
|
1011
|
-
lines.push(`reason: ${d.reason}`);
|
|
1012
|
-
return lines.join(`
|
|
1013
|
-
`);
|
|
1014
|
-
}
|
|
1015
|
-
var DEFAULT_POLICIES, RISK_PATTERNS, TOOL_KIND_MAP;
|
|
1016
|
-
var init_autonomy = __esm(() => {
|
|
1017
|
-
DEFAULT_POLICIES = {
|
|
1018
|
-
step: {
|
|
1019
|
-
bash: "confirm",
|
|
1020
|
-
edit: "confirm",
|
|
1021
|
-
webfetch: "confirm",
|
|
1022
|
-
read: "confirm",
|
|
1023
|
-
search: "confirm",
|
|
1024
|
-
other: "confirm"
|
|
1025
|
-
},
|
|
1026
|
-
semi: {
|
|
1027
|
-
bash: "confirm",
|
|
1028
|
-
edit: "confirm",
|
|
1029
|
-
webfetch: "confirm",
|
|
1030
|
-
read: "auto",
|
|
1031
|
-
search: "auto",
|
|
1032
|
-
other: "auto"
|
|
1033
|
-
},
|
|
1034
|
-
full: {
|
|
1035
|
-
bash: "auto",
|
|
1036
|
-
edit: "auto",
|
|
1037
|
-
webfetch: "auto",
|
|
1038
|
-
read: "auto",
|
|
1039
|
-
search: "auto",
|
|
1040
|
-
other: "auto"
|
|
1041
|
-
},
|
|
1042
|
-
auto: {
|
|
1043
|
-
bash: "auto",
|
|
1044
|
-
edit: "auto",
|
|
1045
|
-
webfetch: "auto",
|
|
1046
|
-
read: "auto",
|
|
1047
|
-
search: "auto",
|
|
1048
|
-
other: "auto"
|
|
1049
|
-
}
|
|
1050
|
-
};
|
|
1051
|
-
RISK_PATTERNS = [
|
|
1052
|
-
{
|
|
1053
|
-
tag: "rm_rf_root",
|
|
1054
|
-
kinds: ["bash"],
|
|
1055
|
-
re: /rm\s+-[rRf]+[^|;`\n]*\s+(\/(?:\s|$|[^/\w*.-])|~|\$HOME(?:\b|\/)|\/(?:bin|etc|usr|var|opt|home|root|boot|lib|sbin)\b)/
|
|
1056
|
-
},
|
|
1057
|
-
{
|
|
1058
|
-
tag: "rm_rf_wildcard",
|
|
1059
|
-
kinds: ["bash"],
|
|
1060
|
-
re: /rm\s+-[rRf]+\s+(?:\.\*|\*)/
|
|
1061
|
-
},
|
|
1062
|
-
{
|
|
1063
|
-
tag: "force_push_protected",
|
|
1064
|
-
kinds: ["bash"],
|
|
1065
|
-
re: /git\s+push\s+(?:--force\b|-f\b)[\s\S]*\b(main|master|release(?:\/[\w.-]+)?)\b/
|
|
1066
|
-
},
|
|
1067
|
-
{
|
|
1068
|
-
tag: "git_reset_hard_main",
|
|
1069
|
-
kinds: ["bash"],
|
|
1070
|
-
re: /git\s+reset\s+--hard\s+(origin\/)?(main|master)/
|
|
1071
|
-
},
|
|
1072
|
-
{ tag: "sudo", kinds: ["bash"], re: /(?<![\w-])sudo(?![\w-])/ },
|
|
1073
|
-
{ tag: "su_root", kinds: ["bash"], re: /(?<![\w-])su\s+(?:-\s+)?root\b/ },
|
|
1074
|
-
{ tag: "mkfs", kinds: ["bash"], re: /\bmkfs(\.[\w]+)?\b/ },
|
|
1075
|
-
{ tag: "dd_disk", kinds: ["bash"], re: /\bdd\s+if=[^|;\s]+\s+of=\/dev\// },
|
|
1076
|
-
{ tag: "chmod_777", kinds: ["bash"], re: /chmod\s+[-+]?[0-7]?777/ },
|
|
1077
|
-
{
|
|
1078
|
-
tag: "curl_pipe_sh",
|
|
1079
|
-
kinds: ["bash"],
|
|
1080
|
-
re: /\b(?:curl|wget)\b[^|]*\|\s*(?:sh|bash|zsh)\b/
|
|
1081
|
-
},
|
|
1082
|
-
{
|
|
1083
|
-
tag: "drop_database",
|
|
1084
|
-
kinds: ["bash", "other"],
|
|
1085
|
-
re: /\b(DROP\s+(DATABASE|TABLE)|TRUNCATE\s+TABLE|DROP\s+SCHEMA)\b/i
|
|
1086
|
-
},
|
|
1087
|
-
{ tag: "write_secrets", re: /(\.env(?:\.\w+)?|id_[edr]sa|\.ssh\/id_|\.pem|\.p12|secret\.json)/i },
|
|
1088
|
-
{ tag: "write_etc", re: /(?:^|\s|"|')\/etc\// },
|
|
1089
|
-
{ tag: "write_usr", re: /(?:^|\s|"|')\/usr\// },
|
|
1090
|
-
{ tag: "write_root_home", re: /(?:^|\s|"|')(\/root|\/home\/root)\// },
|
|
1091
|
-
{
|
|
1092
|
-
tag: "internal_url",
|
|
1093
|
-
kinds: ["webfetch"],
|
|
1094
|
-
re: /https?:\/\/(localhost|127\.0\.0\.1|0\.0\.0\.0|169\.254\.|10\.|192\.168\.|172\.(?:1[6-9]|2\d|3[01])\.)/i
|
|
1095
|
-
},
|
|
1096
|
-
{
|
|
1097
|
-
tag: "kubectl_delete",
|
|
1098
|
-
kinds: ["bash"],
|
|
1099
|
-
re: /kubectl\s+(?:delete|destroy)\s+/
|
|
1100
|
-
},
|
|
1101
|
-
{
|
|
1102
|
-
tag: "terraform_destroy",
|
|
1103
|
-
kinds: ["bash"],
|
|
1104
|
-
re: /terraform\s+destroy/
|
|
1105
|
-
}
|
|
1106
|
-
];
|
|
1107
|
-
TOOL_KIND_MAP = {
|
|
1108
|
-
bash: "bash",
|
|
1109
|
-
shell: "bash",
|
|
1110
|
-
exec: "bash",
|
|
1111
|
-
edit: "edit",
|
|
1112
|
-
write: "edit",
|
|
1113
|
-
"ast-edit": "edit",
|
|
1114
|
-
"pending-changes-apply": "edit",
|
|
1115
|
-
webfetch: "webfetch",
|
|
1116
|
-
fetch: "webfetch",
|
|
1117
|
-
read: "read",
|
|
1118
|
-
cat: "read",
|
|
1119
|
-
glob: "search",
|
|
1120
|
-
grep: "search",
|
|
1121
|
-
search: "search",
|
|
1122
|
-
"repo-map": "search",
|
|
1123
|
-
smart_search: "search",
|
|
1124
|
-
list_recent: "search",
|
|
1125
|
-
get_working_memory: "search",
|
|
1126
|
-
web_search: "search",
|
|
1127
|
-
save_chat_insight: "other",
|
|
1128
|
-
add_knowledge: "other",
|
|
1129
|
-
upload_document: "other",
|
|
1130
|
-
upload_document_from_url: "other",
|
|
1131
|
-
update_knowledge: "other",
|
|
1132
|
-
flag_outdated: "other",
|
|
1133
|
-
delete_knowledge: "other",
|
|
1134
|
-
update_working_memory: "other",
|
|
1135
|
-
update_user_preference: "other",
|
|
1136
|
-
generate_image: "other"
|
|
1137
|
-
};
|
|
1138
|
-
});
|
|
944
|
+
var init_worktree_ops = () => {};
|
|
1139
945
|
|
|
1140
946
|
// lib/decision-parser.ts
|
|
1141
947
|
var exports_decision_parser = {};
|
|
@@ -1263,17 +1069,17 @@ var require_visit = __commonJS((exports) => {
|
|
|
1263
1069
|
visit.BREAK = BREAK;
|
|
1264
1070
|
visit.SKIP = SKIP;
|
|
1265
1071
|
visit.REMOVE = REMOVE;
|
|
1266
|
-
function visit_(key, node, visitor,
|
|
1267
|
-
const ctrl = callVisitor(key, node, visitor,
|
|
1072
|
+
function visit_(key, node, visitor, path18) {
|
|
1073
|
+
const ctrl = callVisitor(key, node, visitor, path18);
|
|
1268
1074
|
if (identity.isNode(ctrl) || identity.isPair(ctrl)) {
|
|
1269
|
-
replaceNode(key,
|
|
1270
|
-
return visit_(key, ctrl, visitor,
|
|
1075
|
+
replaceNode(key, path18, ctrl);
|
|
1076
|
+
return visit_(key, ctrl, visitor, path18);
|
|
1271
1077
|
}
|
|
1272
1078
|
if (typeof ctrl !== "symbol") {
|
|
1273
1079
|
if (identity.isCollection(node)) {
|
|
1274
|
-
|
|
1080
|
+
path18 = Object.freeze(path18.concat(node));
|
|
1275
1081
|
for (let i = 0;i < node.items.length; ++i) {
|
|
1276
|
-
const ci = visit_(i, node.items[i], visitor,
|
|
1082
|
+
const ci = visit_(i, node.items[i], visitor, path18);
|
|
1277
1083
|
if (typeof ci === "number")
|
|
1278
1084
|
i = ci - 1;
|
|
1279
1085
|
else if (ci === BREAK)
|
|
@@ -1284,13 +1090,13 @@ var require_visit = __commonJS((exports) => {
|
|
|
1284
1090
|
}
|
|
1285
1091
|
}
|
|
1286
1092
|
} else if (identity.isPair(node)) {
|
|
1287
|
-
|
|
1288
|
-
const ck = visit_("key", node.key, visitor,
|
|
1093
|
+
path18 = Object.freeze(path18.concat(node));
|
|
1094
|
+
const ck = visit_("key", node.key, visitor, path18);
|
|
1289
1095
|
if (ck === BREAK)
|
|
1290
1096
|
return BREAK;
|
|
1291
1097
|
else if (ck === REMOVE)
|
|
1292
1098
|
node.key = null;
|
|
1293
|
-
const cv = visit_("value", node.value, visitor,
|
|
1099
|
+
const cv = visit_("value", node.value, visitor, path18);
|
|
1294
1100
|
if (cv === BREAK)
|
|
1295
1101
|
return BREAK;
|
|
1296
1102
|
else if (cv === REMOVE)
|
|
@@ -1311,17 +1117,17 @@ var require_visit = __commonJS((exports) => {
|
|
|
1311
1117
|
visitAsync.BREAK = BREAK;
|
|
1312
1118
|
visitAsync.SKIP = SKIP;
|
|
1313
1119
|
visitAsync.REMOVE = REMOVE;
|
|
1314
|
-
async function visitAsync_(key, node, visitor,
|
|
1315
|
-
const ctrl = await callVisitor(key, node, visitor,
|
|
1120
|
+
async function visitAsync_(key, node, visitor, path18) {
|
|
1121
|
+
const ctrl = await callVisitor(key, node, visitor, path18);
|
|
1316
1122
|
if (identity.isNode(ctrl) || identity.isPair(ctrl)) {
|
|
1317
|
-
replaceNode(key,
|
|
1318
|
-
return visitAsync_(key, ctrl, visitor,
|
|
1123
|
+
replaceNode(key, path18, ctrl);
|
|
1124
|
+
return visitAsync_(key, ctrl, visitor, path18);
|
|
1319
1125
|
}
|
|
1320
1126
|
if (typeof ctrl !== "symbol") {
|
|
1321
1127
|
if (identity.isCollection(node)) {
|
|
1322
|
-
|
|
1128
|
+
path18 = Object.freeze(path18.concat(node));
|
|
1323
1129
|
for (let i = 0;i < node.items.length; ++i) {
|
|
1324
|
-
const ci = await visitAsync_(i, node.items[i], visitor,
|
|
1130
|
+
const ci = await visitAsync_(i, node.items[i], visitor, path18);
|
|
1325
1131
|
if (typeof ci === "number")
|
|
1326
1132
|
i = ci - 1;
|
|
1327
1133
|
else if (ci === BREAK)
|
|
@@ -1332,13 +1138,13 @@ var require_visit = __commonJS((exports) => {
|
|
|
1332
1138
|
}
|
|
1333
1139
|
}
|
|
1334
1140
|
} else if (identity.isPair(node)) {
|
|
1335
|
-
|
|
1336
|
-
const ck = await visitAsync_("key", node.key, visitor,
|
|
1141
|
+
path18 = Object.freeze(path18.concat(node));
|
|
1142
|
+
const ck = await visitAsync_("key", node.key, visitor, path18);
|
|
1337
1143
|
if (ck === BREAK)
|
|
1338
1144
|
return BREAK;
|
|
1339
1145
|
else if (ck === REMOVE)
|
|
1340
1146
|
node.key = null;
|
|
1341
|
-
const cv = await visitAsync_("value", node.value, visitor,
|
|
1147
|
+
const cv = await visitAsync_("value", node.value, visitor, path18);
|
|
1342
1148
|
if (cv === BREAK)
|
|
1343
1149
|
return BREAK;
|
|
1344
1150
|
else if (cv === REMOVE)
|
|
@@ -1365,23 +1171,23 @@ var require_visit = __commonJS((exports) => {
|
|
|
1365
1171
|
}
|
|
1366
1172
|
return visitor;
|
|
1367
1173
|
}
|
|
1368
|
-
function callVisitor(key, node, visitor,
|
|
1174
|
+
function callVisitor(key, node, visitor, path18) {
|
|
1369
1175
|
if (typeof visitor === "function")
|
|
1370
|
-
return visitor(key, node,
|
|
1176
|
+
return visitor(key, node, path18);
|
|
1371
1177
|
if (identity.isMap(node))
|
|
1372
|
-
return visitor.Map?.(key, node,
|
|
1178
|
+
return visitor.Map?.(key, node, path18);
|
|
1373
1179
|
if (identity.isSeq(node))
|
|
1374
|
-
return visitor.Seq?.(key, node,
|
|
1180
|
+
return visitor.Seq?.(key, node, path18);
|
|
1375
1181
|
if (identity.isPair(node))
|
|
1376
|
-
return visitor.Pair?.(key, node,
|
|
1182
|
+
return visitor.Pair?.(key, node, path18);
|
|
1377
1183
|
if (identity.isScalar(node))
|
|
1378
|
-
return visitor.Scalar?.(key, node,
|
|
1184
|
+
return visitor.Scalar?.(key, node, path18);
|
|
1379
1185
|
if (identity.isAlias(node))
|
|
1380
|
-
return visitor.Alias?.(key, node,
|
|
1186
|
+
return visitor.Alias?.(key, node, path18);
|
|
1381
1187
|
return;
|
|
1382
1188
|
}
|
|
1383
|
-
function replaceNode(key,
|
|
1384
|
-
const parent =
|
|
1189
|
+
function replaceNode(key, path18, node) {
|
|
1190
|
+
const parent = path18[path18.length - 1];
|
|
1385
1191
|
if (identity.isCollection(parent)) {
|
|
1386
1192
|
parent.items[key] = node;
|
|
1387
1193
|
} else if (identity.isPair(parent)) {
|
|
@@ -1940,10 +1746,10 @@ var require_Collection = __commonJS((exports) => {
|
|
|
1940
1746
|
var createNode = require_createNode();
|
|
1941
1747
|
var identity = require_identity();
|
|
1942
1748
|
var Node = require_Node();
|
|
1943
|
-
function collectionFromPath(schema,
|
|
1749
|
+
function collectionFromPath(schema, path18, value) {
|
|
1944
1750
|
let v = value;
|
|
1945
|
-
for (let i =
|
|
1946
|
-
const k =
|
|
1751
|
+
for (let i = path18.length - 1;i >= 0; --i) {
|
|
1752
|
+
const k = path18[i];
|
|
1947
1753
|
if (typeof k === "number" && Number.isInteger(k) && k >= 0) {
|
|
1948
1754
|
const a = [];
|
|
1949
1755
|
a[k] = v;
|
|
@@ -1962,7 +1768,7 @@ var require_Collection = __commonJS((exports) => {
|
|
|
1962
1768
|
sourceObjects: new Map
|
|
1963
1769
|
});
|
|
1964
1770
|
}
|
|
1965
|
-
var isEmptyPath = (
|
|
1771
|
+
var isEmptyPath = (path18) => path18 == null || typeof path18 === "object" && !!path18[Symbol.iterator]().next().done;
|
|
1966
1772
|
|
|
1967
1773
|
class Collection extends Node.NodeBase {
|
|
1968
1774
|
constructor(type, schema) {
|
|
@@ -1983,11 +1789,11 @@ var require_Collection = __commonJS((exports) => {
|
|
|
1983
1789
|
copy.range = this.range.slice();
|
|
1984
1790
|
return copy;
|
|
1985
1791
|
}
|
|
1986
|
-
addIn(
|
|
1987
|
-
if (isEmptyPath(
|
|
1792
|
+
addIn(path18, value) {
|
|
1793
|
+
if (isEmptyPath(path18))
|
|
1988
1794
|
this.add(value);
|
|
1989
1795
|
else {
|
|
1990
|
-
const [key, ...rest] =
|
|
1796
|
+
const [key, ...rest] = path18;
|
|
1991
1797
|
const node = this.get(key, true);
|
|
1992
1798
|
if (identity.isCollection(node))
|
|
1993
1799
|
node.addIn(rest, value);
|
|
@@ -1997,8 +1803,8 @@ var require_Collection = __commonJS((exports) => {
|
|
|
1997
1803
|
throw new Error(`Expected YAML collection at ${key}. Remaining path: ${rest}`);
|
|
1998
1804
|
}
|
|
1999
1805
|
}
|
|
2000
|
-
deleteIn(
|
|
2001
|
-
const [key, ...rest] =
|
|
1806
|
+
deleteIn(path18) {
|
|
1807
|
+
const [key, ...rest] = path18;
|
|
2002
1808
|
if (rest.length === 0)
|
|
2003
1809
|
return this.delete(key);
|
|
2004
1810
|
const node = this.get(key, true);
|
|
@@ -2007,8 +1813,8 @@ var require_Collection = __commonJS((exports) => {
|
|
|
2007
1813
|
else
|
|
2008
1814
|
throw new Error(`Expected YAML collection at ${key}. Remaining path: ${rest}`);
|
|
2009
1815
|
}
|
|
2010
|
-
getIn(
|
|
2011
|
-
const [key, ...rest] =
|
|
1816
|
+
getIn(path18, keepScalar) {
|
|
1817
|
+
const [key, ...rest] = path18;
|
|
2012
1818
|
const node = this.get(key, true);
|
|
2013
1819
|
if (rest.length === 0)
|
|
2014
1820
|
return !keepScalar && identity.isScalar(node) ? node.value : node;
|
|
@@ -2023,15 +1829,15 @@ var require_Collection = __commonJS((exports) => {
|
|
|
2023
1829
|
return n == null || allowScalar && identity.isScalar(n) && n.value == null && !n.commentBefore && !n.comment && !n.tag;
|
|
2024
1830
|
});
|
|
2025
1831
|
}
|
|
2026
|
-
hasIn(
|
|
2027
|
-
const [key, ...rest] =
|
|
1832
|
+
hasIn(path18) {
|
|
1833
|
+
const [key, ...rest] = path18;
|
|
2028
1834
|
if (rest.length === 0)
|
|
2029
1835
|
return this.has(key);
|
|
2030
1836
|
const node = this.get(key, true);
|
|
2031
1837
|
return identity.isCollection(node) ? node.hasIn(rest) : false;
|
|
2032
1838
|
}
|
|
2033
|
-
setIn(
|
|
2034
|
-
const [key, ...rest] =
|
|
1839
|
+
setIn(path18, value) {
|
|
1840
|
+
const [key, ...rest] = path18;
|
|
2035
1841
|
if (rest.length === 0) {
|
|
2036
1842
|
this.set(key, value);
|
|
2037
1843
|
} else {
|
|
@@ -2837,7 +2643,7 @@ var require_merge = __commonJS((exports) => {
|
|
|
2837
2643
|
|
|
2838
2644
|
// node_modules/yaml/dist/nodes/addPairToJSMap.js
|
|
2839
2645
|
var require_addPairToJSMap = __commonJS((exports) => {
|
|
2840
|
-
var
|
|
2646
|
+
var log6 = require_log();
|
|
2841
2647
|
var merge = require_merge();
|
|
2842
2648
|
var stringify = require_stringify();
|
|
2843
2649
|
var identity = require_identity();
|
|
@@ -2886,7 +2692,7 @@ var require_addPairToJSMap = __commonJS((exports) => {
|
|
|
2886
2692
|
let jsonStr = JSON.stringify(strKey);
|
|
2887
2693
|
if (jsonStr.length > 40)
|
|
2888
2694
|
jsonStr = jsonStr.substring(0, 36) + '..."';
|
|
2889
|
-
|
|
2695
|
+
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.`);
|
|
2890
2696
|
ctx.mapKeyWarned = true;
|
|
2891
2697
|
}
|
|
2892
2698
|
return strKey;
|
|
@@ -4424,9 +4230,9 @@ var require_Document = __commonJS((exports) => {
|
|
|
4424
4230
|
if (assertCollection(this.contents))
|
|
4425
4231
|
this.contents.add(value);
|
|
4426
4232
|
}
|
|
4427
|
-
addIn(
|
|
4233
|
+
addIn(path18, value) {
|
|
4428
4234
|
if (assertCollection(this.contents))
|
|
4429
|
-
this.contents.addIn(
|
|
4235
|
+
this.contents.addIn(path18, value);
|
|
4430
4236
|
}
|
|
4431
4237
|
createAlias(node, name) {
|
|
4432
4238
|
if (!node.anchor) {
|
|
@@ -4475,30 +4281,30 @@ var require_Document = __commonJS((exports) => {
|
|
|
4475
4281
|
delete(key) {
|
|
4476
4282
|
return assertCollection(this.contents) ? this.contents.delete(key) : false;
|
|
4477
4283
|
}
|
|
4478
|
-
deleteIn(
|
|
4479
|
-
if (Collection.isEmptyPath(
|
|
4284
|
+
deleteIn(path18) {
|
|
4285
|
+
if (Collection.isEmptyPath(path18)) {
|
|
4480
4286
|
if (this.contents == null)
|
|
4481
4287
|
return false;
|
|
4482
4288
|
this.contents = null;
|
|
4483
4289
|
return true;
|
|
4484
4290
|
}
|
|
4485
|
-
return assertCollection(this.contents) ? this.contents.deleteIn(
|
|
4291
|
+
return assertCollection(this.contents) ? this.contents.deleteIn(path18) : false;
|
|
4486
4292
|
}
|
|
4487
4293
|
get(key, keepScalar) {
|
|
4488
4294
|
return identity.isCollection(this.contents) ? this.contents.get(key, keepScalar) : undefined;
|
|
4489
4295
|
}
|
|
4490
|
-
getIn(
|
|
4491
|
-
if (Collection.isEmptyPath(
|
|
4296
|
+
getIn(path18, keepScalar) {
|
|
4297
|
+
if (Collection.isEmptyPath(path18))
|
|
4492
4298
|
return !keepScalar && identity.isScalar(this.contents) ? this.contents.value : this.contents;
|
|
4493
|
-
return identity.isCollection(this.contents) ? this.contents.getIn(
|
|
4299
|
+
return identity.isCollection(this.contents) ? this.contents.getIn(path18, keepScalar) : undefined;
|
|
4494
4300
|
}
|
|
4495
4301
|
has(key) {
|
|
4496
4302
|
return identity.isCollection(this.contents) ? this.contents.has(key) : false;
|
|
4497
4303
|
}
|
|
4498
|
-
hasIn(
|
|
4499
|
-
if (Collection.isEmptyPath(
|
|
4304
|
+
hasIn(path18) {
|
|
4305
|
+
if (Collection.isEmptyPath(path18))
|
|
4500
4306
|
return this.contents !== undefined;
|
|
4501
|
-
return identity.isCollection(this.contents) ? this.contents.hasIn(
|
|
4307
|
+
return identity.isCollection(this.contents) ? this.contents.hasIn(path18) : false;
|
|
4502
4308
|
}
|
|
4503
4309
|
set(key, value) {
|
|
4504
4310
|
if (this.contents == null) {
|
|
@@ -4507,13 +4313,13 @@ var require_Document = __commonJS((exports) => {
|
|
|
4507
4313
|
this.contents.set(key, value);
|
|
4508
4314
|
}
|
|
4509
4315
|
}
|
|
4510
|
-
setIn(
|
|
4511
|
-
if (Collection.isEmptyPath(
|
|
4316
|
+
setIn(path18, value) {
|
|
4317
|
+
if (Collection.isEmptyPath(path18)) {
|
|
4512
4318
|
this.contents = value;
|
|
4513
4319
|
} else if (this.contents == null) {
|
|
4514
|
-
this.contents = Collection.collectionFromPath(this.schema, Array.from(
|
|
4320
|
+
this.contents = Collection.collectionFromPath(this.schema, Array.from(path18), value);
|
|
4515
4321
|
} else if (assertCollection(this.contents)) {
|
|
4516
|
-
this.contents.setIn(
|
|
4322
|
+
this.contents.setIn(path18, value);
|
|
4517
4323
|
}
|
|
4518
4324
|
}
|
|
4519
4325
|
setSchema(version, options = {}) {
|
|
@@ -6408,9 +6214,9 @@ var require_cst_visit = __commonJS((exports) => {
|
|
|
6408
6214
|
visit.BREAK = BREAK;
|
|
6409
6215
|
visit.SKIP = SKIP;
|
|
6410
6216
|
visit.REMOVE = REMOVE;
|
|
6411
|
-
visit.itemAtPath = (cst,
|
|
6217
|
+
visit.itemAtPath = (cst, path18) => {
|
|
6412
6218
|
let item = cst;
|
|
6413
|
-
for (const [field, index] of
|
|
6219
|
+
for (const [field, index] of path18) {
|
|
6414
6220
|
const tok = item?.[field];
|
|
6415
6221
|
if (tok && "items" in tok) {
|
|
6416
6222
|
item = tok.items[index];
|
|
@@ -6419,23 +6225,23 @@ var require_cst_visit = __commonJS((exports) => {
|
|
|
6419
6225
|
}
|
|
6420
6226
|
return item;
|
|
6421
6227
|
};
|
|
6422
|
-
visit.parentCollection = (cst,
|
|
6423
|
-
const parent = visit.itemAtPath(cst,
|
|
6424
|
-
const field =
|
|
6228
|
+
visit.parentCollection = (cst, path18) => {
|
|
6229
|
+
const parent = visit.itemAtPath(cst, path18.slice(0, -1));
|
|
6230
|
+
const field = path18[path18.length - 1][0];
|
|
6425
6231
|
const coll = parent?.[field];
|
|
6426
6232
|
if (coll && "items" in coll)
|
|
6427
6233
|
return coll;
|
|
6428
6234
|
throw new Error("Parent collection not found");
|
|
6429
6235
|
};
|
|
6430
|
-
function _visit(
|
|
6431
|
-
let ctrl = visitor(item,
|
|
6236
|
+
function _visit(path18, item, visitor) {
|
|
6237
|
+
let ctrl = visitor(item, path18);
|
|
6432
6238
|
if (typeof ctrl === "symbol")
|
|
6433
6239
|
return ctrl;
|
|
6434
6240
|
for (const field of ["key", "value"]) {
|
|
6435
6241
|
const token = item[field];
|
|
6436
6242
|
if (token && "items" in token) {
|
|
6437
6243
|
for (let i = 0;i < token.items.length; ++i) {
|
|
6438
|
-
const ci = _visit(Object.freeze(
|
|
6244
|
+
const ci = _visit(Object.freeze(path18.concat([[field, i]])), token.items[i], visitor);
|
|
6439
6245
|
if (typeof ci === "number")
|
|
6440
6246
|
i = ci - 1;
|
|
6441
6247
|
else if (ci === BREAK)
|
|
@@ -6446,10 +6252,10 @@ var require_cst_visit = __commonJS((exports) => {
|
|
|
6446
6252
|
}
|
|
6447
6253
|
}
|
|
6448
6254
|
if (typeof ctrl === "function" && field === "key")
|
|
6449
|
-
ctrl = ctrl(item,
|
|
6255
|
+
ctrl = ctrl(item, path18);
|
|
6450
6256
|
}
|
|
6451
6257
|
}
|
|
6452
|
-
return typeof ctrl === "function" ? ctrl(item,
|
|
6258
|
+
return typeof ctrl === "function" ? ctrl(item, path18) : ctrl;
|
|
6453
6259
|
}
|
|
6454
6260
|
exports.visit = visit;
|
|
6455
6261
|
});
|
|
@@ -7718,14 +7524,14 @@ var require_parser = __commonJS((exports) => {
|
|
|
7718
7524
|
case "scalar":
|
|
7719
7525
|
case "single-quoted-scalar":
|
|
7720
7526
|
case "double-quoted-scalar": {
|
|
7721
|
-
const
|
|
7527
|
+
const fs15 = this.flowScalar(this.type);
|
|
7722
7528
|
if (atNextItem || it.value) {
|
|
7723
|
-
map.items.push({ start, key:
|
|
7529
|
+
map.items.push({ start, key: fs15, sep: [] });
|
|
7724
7530
|
this.onKeyLine = true;
|
|
7725
7531
|
} else if (it.sep) {
|
|
7726
|
-
this.stack.push(
|
|
7532
|
+
this.stack.push(fs15);
|
|
7727
7533
|
} else {
|
|
7728
|
-
Object.assign(it, { key:
|
|
7534
|
+
Object.assign(it, { key: fs15, sep: [] });
|
|
7729
7535
|
this.onKeyLine = true;
|
|
7730
7536
|
}
|
|
7731
7537
|
return;
|
|
@@ -7853,13 +7659,13 @@ var require_parser = __commonJS((exports) => {
|
|
|
7853
7659
|
case "scalar":
|
|
7854
7660
|
case "single-quoted-scalar":
|
|
7855
7661
|
case "double-quoted-scalar": {
|
|
7856
|
-
const
|
|
7662
|
+
const fs15 = this.flowScalar(this.type);
|
|
7857
7663
|
if (!it || it.value)
|
|
7858
|
-
fc.items.push({ start: [], key:
|
|
7664
|
+
fc.items.push({ start: [], key: fs15, sep: [] });
|
|
7859
7665
|
else if (it.sep)
|
|
7860
|
-
this.stack.push(
|
|
7666
|
+
this.stack.push(fs15);
|
|
7861
7667
|
else
|
|
7862
|
-
Object.assign(it, { key:
|
|
7668
|
+
Object.assign(it, { key: fs15, sep: [] });
|
|
7863
7669
|
return;
|
|
7864
7670
|
}
|
|
7865
7671
|
case "flow-map-end":
|
|
@@ -8023,7 +7829,7 @@ var require_public_api = __commonJS((exports) => {
|
|
|
8023
7829
|
var composer = require_composer();
|
|
8024
7830
|
var Document = require_Document();
|
|
8025
7831
|
var errors = require_errors();
|
|
8026
|
-
var
|
|
7832
|
+
var log6 = require_log();
|
|
8027
7833
|
var identity = require_identity();
|
|
8028
7834
|
var lineCounter = require_line_counter();
|
|
8029
7835
|
var parser = require_parser();
|
|
@@ -8075,7 +7881,7 @@ var require_public_api = __commonJS((exports) => {
|
|
|
8075
7881
|
const doc = parseDocument(src, options);
|
|
8076
7882
|
if (!doc)
|
|
8077
7883
|
return null;
|
|
8078
|
-
doc.warnings.forEach((warning) =>
|
|
7884
|
+
doc.warnings.forEach((warning) => log6.warn(doc.options.logLevel, warning));
|
|
8079
7885
|
if (doc.errors.length > 0) {
|
|
8080
7886
|
if (doc.options.logLevel !== "silent")
|
|
8081
7887
|
throw doc.errors[0];
|
|
@@ -8241,7 +8047,7 @@ async function runAutoFeedback(cfg, ctx) {
|
|
|
8241
8047
|
if (cmds.length === 0) {
|
|
8242
8048
|
throw new Error("auto-feedback: 至少需要 test_cmd 或 lint_cmd 之一");
|
|
8243
8049
|
}
|
|
8244
|
-
const
|
|
8050
|
+
const log14 = ctx.log ?? (() => {});
|
|
8245
8051
|
const attempt = async () => {
|
|
8246
8052
|
let last = {
|
|
8247
8053
|
cmd: "",
|
|
@@ -8278,7 +8084,7 @@ ${excerpt}
|
|
|
8278
8084
|
|
|
8279
8085
|
请基于错误信息修复 pending-changes(用 ast-edit / pending-changes.stage),完成后我会重新跑测试验证。`;
|
|
8280
8086
|
const result2 = await ctx.dispatchToAgent("coder", prompt);
|
|
8281
|
-
|
|
8087
|
+
log14("info", `auto-feedback round ${round} dispatch summary: ${result2.summary.slice(0, 200)}`);
|
|
8282
8088
|
};
|
|
8283
8089
|
const debugResult = await runWithAutoDebug({
|
|
8284
8090
|
attempt,
|
|
@@ -8299,7 +8105,7 @@ ${excerpt}
|
|
|
8299
8105
|
history: debugResult.history
|
|
8300
8106
|
};
|
|
8301
8107
|
if (!debugResult.ok && debugResult.stopReason === "max-rounds") {
|
|
8302
|
-
|
|
8108
|
+
log14("warn", `auto-feedback 达 max_retries=${cfg.max_retries},escalate to ${cfg.escalate_to}`);
|
|
8303
8109
|
const lastFail = debugResult.history[debugResult.history.length - 1];
|
|
8304
8110
|
const excerpt = lastFail?.result ? extractErrorExcerpt(lastFail.result, cfg.error_excerpt_lines) : "(无失败结果)";
|
|
8305
8111
|
const escalatePrompt = `coder 连续 ${cfg.max_retries} 轮自纠失败,错误片段:
|
|
@@ -13061,7 +12867,7 @@ function toEntry(r) {
|
|
|
13061
12867
|
import { z as z27 } from "zod";
|
|
13062
12868
|
|
|
13063
12869
|
// lib/session-worktree.ts
|
|
13064
|
-
|
|
12870
|
+
init_worktree_ops();
|
|
13065
12871
|
init_runtime_paths();
|
|
13066
12872
|
import { execFile as execFile3 } from "node:child_process";
|
|
13067
12873
|
import { promises as fs10 } from "node:fs";
|
|
@@ -13312,6 +13118,18 @@ async function mergeSessionBack(opts) {
|
|
|
13312
13118
|
});
|
|
13313
13119
|
throw new Error(`mergeSessionBack: squash merge 失败(已 reset 主仓兜底): ${err.message}`);
|
|
13314
13120
|
}
|
|
13121
|
+
const hasDevOnce = await packageHasScript(mainRoot, "dev:once");
|
|
13122
|
+
if (hasDevOnce) {
|
|
13123
|
+
try {
|
|
13124
|
+
await runCmd("npm", ["run", "dev:once"], mainRoot);
|
|
13125
|
+
} catch (err) {
|
|
13126
|
+
await runGit2(mainRoot, ["reset", "--hard", "HEAD"]).catch(() => {});
|
|
13127
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
13128
|
+
throw new Error(`dev:once 失败已 reset 主仓: ${msg}`);
|
|
13129
|
+
}
|
|
13130
|
+
} else {
|
|
13131
|
+
console.log(`[session-worktree] skip dev:once: not configured in ${mainRoot}/package.json`);
|
|
13132
|
+
}
|
|
13315
13133
|
const squashedRaw = await runGit2(wt, ["log", "--format=%s", `${baseSha}..HEAD`]);
|
|
13316
13134
|
const squashedCommits = squashedRaw.split(/\r?\n/).map((s) => s.trim()).filter(Boolean);
|
|
13317
13135
|
const message = opts.commitMessage ?? buildMergeMessage(opts.sessionId, branch, baseSha, squashedCommits);
|
|
@@ -13416,6 +13234,29 @@ function runGit2(cwd, args, timeoutMs = 1e4) {
|
|
|
13416
13234
|
});
|
|
13417
13235
|
});
|
|
13418
13236
|
}
|
|
13237
|
+
async function packageHasScript(mainRoot, scriptName) {
|
|
13238
|
+
try {
|
|
13239
|
+
const pkgPath = path13.join(mainRoot, "package.json");
|
|
13240
|
+
const raw = await fs10.readFile(pkgPath, "utf8");
|
|
13241
|
+
const pkg = JSON.parse(raw);
|
|
13242
|
+
if (!pkg.scripts || typeof pkg.scripts !== "object")
|
|
13243
|
+
return false;
|
|
13244
|
+
return typeof pkg.scripts[scriptName] === "string";
|
|
13245
|
+
} catch {
|
|
13246
|
+
return false;
|
|
13247
|
+
}
|
|
13248
|
+
}
|
|
13249
|
+
function runCmd(cmd, args, cwd, timeoutMs = 120000) {
|
|
13250
|
+
return new Promise((resolve11, reject) => {
|
|
13251
|
+
execFile3(cmd, args, { cwd, timeout: timeoutMs, windowsHide: true, encoding: "utf8" }, (err, stdout, stderr) => {
|
|
13252
|
+
if (err) {
|
|
13253
|
+
reject(new Error(`${cmd} ${args.join(" ")} (cwd=${cwd}) 失败: ${stderr?.trim() || err.message}`));
|
|
13254
|
+
return;
|
|
13255
|
+
}
|
|
13256
|
+
resolve11(stdout);
|
|
13257
|
+
});
|
|
13258
|
+
});
|
|
13259
|
+
}
|
|
13419
13260
|
function buildMergeMessage(sessionId, branch, baseSha, squashed) {
|
|
13420
13261
|
const subject = `session(${sessionId}): merge ${branch}`;
|
|
13421
13262
|
const body = squashed.length > 0 ? `
|
|
@@ -13458,7 +13299,7 @@ async function pruneOrphanWorktrees(mainRoot) {
|
|
|
13458
13299
|
let skipped = 0;
|
|
13459
13300
|
let gitWorktrees = [];
|
|
13460
13301
|
try {
|
|
13461
|
-
const mod = await Promise.resolve().then(() => (
|
|
13302
|
+
const mod = await Promise.resolve().then(() => (init_worktree_ops(), exports_worktree_ops));
|
|
13462
13303
|
gitWorktrees = await mod.listWorktrees({ root: resolved });
|
|
13463
13304
|
} catch {
|
|
13464
13305
|
gitWorktrees = [];
|
|
@@ -14698,6 +14539,212 @@ async function sendParentNotice(client, sessionID, text, opts = {}) {
|
|
|
14698
14539
|
// lib/spawner-production.ts
|
|
14699
14540
|
init_decision_parser();
|
|
14700
14541
|
|
|
14542
|
+
// lib/parent-map-store.ts
|
|
14543
|
+
init_runtime_paths();
|
|
14544
|
+
import { promises as fs13 } from "node:fs";
|
|
14545
|
+
import * as path16 from "node:path";
|
|
14546
|
+
var PARENT_MAP_VERSION = 1;
|
|
14547
|
+
var PARENT_MAP_LOCK_TIMEOUT_MS = 2000;
|
|
14548
|
+
var PARENT_MAP_MAX_ENTRIES = 256;
|
|
14549
|
+
function parentMapDir(mainRoot) {
|
|
14550
|
+
return path16.join(runtimeDir(path16.resolve(mainRoot), { ensure: false }), "session-worktrees");
|
|
14551
|
+
}
|
|
14552
|
+
function parentMapPath(mainRoot) {
|
|
14553
|
+
return path16.join(parentMapDir(mainRoot), "parent-map.json");
|
|
14554
|
+
}
|
|
14555
|
+
function parentMapLockPath(mainRoot) {
|
|
14556
|
+
return path16.join(parentMapDir(mainRoot), "parent-map.lock");
|
|
14557
|
+
}
|
|
14558
|
+
async function readParentMapFile(mainRoot) {
|
|
14559
|
+
const file = parentMapPath(mainRoot);
|
|
14560
|
+
try {
|
|
14561
|
+
const raw = await fs13.readFile(file, "utf8");
|
|
14562
|
+
const parsed = JSON.parse(raw);
|
|
14563
|
+
if (parsed.version !== PARENT_MAP_VERSION || !Array.isArray(parsed.entries)) {
|
|
14564
|
+
return { version: PARENT_MAP_VERSION, entries: [] };
|
|
14565
|
+
}
|
|
14566
|
+
return parsed;
|
|
14567
|
+
} catch (err) {
|
|
14568
|
+
const e = err;
|
|
14569
|
+
if (e.code === "ENOENT")
|
|
14570
|
+
return { version: PARENT_MAP_VERSION, entries: [] };
|
|
14571
|
+
return { version: PARENT_MAP_VERSION, entries: [] };
|
|
14572
|
+
}
|
|
14573
|
+
}
|
|
14574
|
+
async function writeParentMapFile(mainRoot, payload) {
|
|
14575
|
+
const file = parentMapPath(mainRoot);
|
|
14576
|
+
await fs13.mkdir(path16.dirname(file), { recursive: true });
|
|
14577
|
+
const tmp = `${file}.tmp-${process.pid}-${Date.now()}`;
|
|
14578
|
+
await fs13.writeFile(tmp, JSON.stringify(payload, null, 2), "utf8");
|
|
14579
|
+
await fs13.rename(tmp, file);
|
|
14580
|
+
}
|
|
14581
|
+
function capEntriesByTsDesc(entries) {
|
|
14582
|
+
if (entries.length <= PARENT_MAP_MAX_ENTRIES)
|
|
14583
|
+
return entries;
|
|
14584
|
+
return [...entries].sort((a, b) => b.ts - a.ts).slice(0, PARENT_MAP_MAX_ENTRIES);
|
|
14585
|
+
}
|
|
14586
|
+
async function loadParentMap(mainRoot) {
|
|
14587
|
+
const out = new Map;
|
|
14588
|
+
let file;
|
|
14589
|
+
try {
|
|
14590
|
+
file = await readParentMapFile(mainRoot);
|
|
14591
|
+
} catch {
|
|
14592
|
+
return out;
|
|
14593
|
+
}
|
|
14594
|
+
const valid = [];
|
|
14595
|
+
for (const e of file.entries) {
|
|
14596
|
+
if (!e || typeof e.childID !== "string" || typeof e.parentID !== "string")
|
|
14597
|
+
continue;
|
|
14598
|
+
if (!e.childID || !e.parentID)
|
|
14599
|
+
continue;
|
|
14600
|
+
const ts = typeof e.ts === "number" ? e.ts : Date.now();
|
|
14601
|
+
valid.push({ childID: e.childID, parentID: e.parentID, ts });
|
|
14602
|
+
}
|
|
14603
|
+
const capped = capEntriesByTsDesc(valid);
|
|
14604
|
+
for (const e of capped) {
|
|
14605
|
+
out.set(e.childID, { parentID: e.parentID, ts: e.ts });
|
|
14606
|
+
}
|
|
14607
|
+
return out;
|
|
14608
|
+
}
|
|
14609
|
+
async function mutateParentMap(mainRoot, mutator, opts = {}) {
|
|
14610
|
+
const lockPath = parentMapLockPath(mainRoot);
|
|
14611
|
+
await fs13.mkdir(path16.dirname(lockPath), { recursive: true });
|
|
14612
|
+
const lockOpts = {
|
|
14613
|
+
timeoutMs: opts.timeoutMs ?? PARENT_MAP_LOCK_TIMEOUT_MS,
|
|
14614
|
+
...opts
|
|
14615
|
+
};
|
|
14616
|
+
await withFileLock(lockPath, async () => {
|
|
14617
|
+
const current = await readParentMapFile(mainRoot);
|
|
14618
|
+
const input = current.entries.slice();
|
|
14619
|
+
const next = await mutator(input);
|
|
14620
|
+
if (!Array.isArray(next))
|
|
14621
|
+
return;
|
|
14622
|
+
const capped = capEntriesByTsDesc(next);
|
|
14623
|
+
await writeParentMapFile(mainRoot, { version: PARENT_MAP_VERSION, entries: capped });
|
|
14624
|
+
}, lockOpts);
|
|
14625
|
+
}
|
|
14626
|
+
async function appendParentEntry(mainRoot, childID, parentID, ts, opts = {}) {
|
|
14627
|
+
if (!childID || !parentID)
|
|
14628
|
+
return;
|
|
14629
|
+
const lockPath = parentMapLockPath(mainRoot);
|
|
14630
|
+
await fs13.mkdir(path16.dirname(lockPath), { recursive: true });
|
|
14631
|
+
const lockOpts = {
|
|
14632
|
+
timeoutMs: opts.timeoutMs ?? PARENT_MAP_LOCK_TIMEOUT_MS,
|
|
14633
|
+
...opts
|
|
14634
|
+
};
|
|
14635
|
+
await withFileLock(lockPath, async () => {
|
|
14636
|
+
const current = await readParentMapFile(mainRoot);
|
|
14637
|
+
const idx = current.entries.findIndex((e) => e.childID === childID);
|
|
14638
|
+
if (idx >= 0) {
|
|
14639
|
+
current.entries[idx] = { childID, parentID, ts };
|
|
14640
|
+
} else {
|
|
14641
|
+
current.entries.push({ childID, parentID, ts });
|
|
14642
|
+
}
|
|
14643
|
+
const capped = capEntriesByTsDesc(current.entries);
|
|
14644
|
+
await writeParentMapFile(mainRoot, { version: PARENT_MAP_VERSION, entries: capped });
|
|
14645
|
+
}, lockOpts);
|
|
14646
|
+
}
|
|
14647
|
+
|
|
14648
|
+
// lib/parent-map-runtime.ts
|
|
14649
|
+
var log4 = makePluginLogger("parent-map-runtime");
|
|
14650
|
+
var SESSION_PARENT_MAP_TTL_MS = 30 * 60000;
|
|
14651
|
+
var SESSION_PARENT_MAP_MAX_SIZE = PARENT_MAP_MAX_ENTRIES;
|
|
14652
|
+
var sessionParentMap = new Map;
|
|
14653
|
+
var _persistRoot = null;
|
|
14654
|
+
function _setPersistRootForTests(root) {
|
|
14655
|
+
_persistRoot = root;
|
|
14656
|
+
}
|
|
14657
|
+
function _bulkInjectSessionParentMap(entries) {
|
|
14658
|
+
for (const e of entries) {
|
|
14659
|
+
if (!e.childID || !e.parentID)
|
|
14660
|
+
continue;
|
|
14661
|
+
sessionParentMap.set(e.childID, { parentID: e.parentID, ts: e.ts });
|
|
14662
|
+
}
|
|
14663
|
+
}
|
|
14664
|
+
function _capSessionParentMap() {
|
|
14665
|
+
if (sessionParentMap.size <= SESSION_PARENT_MAP_MAX_SIZE)
|
|
14666
|
+
return 0;
|
|
14667
|
+
const sorted = [...sessionParentMap.entries()].sort((a, b) => b[1].ts - a[1].ts);
|
|
14668
|
+
const keep = new Set(sorted.slice(0, SESSION_PARENT_MAP_MAX_SIZE).map(([k]) => k));
|
|
14669
|
+
let removed = 0;
|
|
14670
|
+
for (const k of [...sessionParentMap.keys()]) {
|
|
14671
|
+
if (!keep.has(k)) {
|
|
14672
|
+
sessionParentMap.delete(k);
|
|
14673
|
+
removed++;
|
|
14674
|
+
}
|
|
14675
|
+
}
|
|
14676
|
+
return removed;
|
|
14677
|
+
}
|
|
14678
|
+
function recordSessionParent(childID, parentID, now = Date.now()) {
|
|
14679
|
+
if (!childID || !parentID)
|
|
14680
|
+
return;
|
|
14681
|
+
if (sessionParentMap.has(childID)) {
|
|
14682
|
+
sessionParentMap.set(childID, { parentID, ts: now });
|
|
14683
|
+
} else {
|
|
14684
|
+
if (sessionParentMap.size >= SESSION_PARENT_MAP_MAX_SIZE) {
|
|
14685
|
+
let oldestKey = null;
|
|
14686
|
+
let oldestTs = Number.POSITIVE_INFINITY;
|
|
14687
|
+
for (const [k, v] of sessionParentMap.entries()) {
|
|
14688
|
+
if (v.ts < oldestTs) {
|
|
14689
|
+
oldestTs = v.ts;
|
|
14690
|
+
oldestKey = k;
|
|
14691
|
+
}
|
|
14692
|
+
}
|
|
14693
|
+
if (oldestKey !== null)
|
|
14694
|
+
sessionParentMap.delete(oldestKey);
|
|
14695
|
+
}
|
|
14696
|
+
sessionParentMap.set(childID, { parentID, ts: now });
|
|
14697
|
+
}
|
|
14698
|
+
if (_persistRoot) {
|
|
14699
|
+
appendParentEntry(_persistRoot, childID, parentID, now).catch((err) => {
|
|
14700
|
+
log4.warn("appendParentEntry 失败(已隔离)", {
|
|
14701
|
+
error: err instanceof Error ? err.message : String(err),
|
|
14702
|
+
childID
|
|
14703
|
+
});
|
|
14704
|
+
});
|
|
14705
|
+
}
|
|
14706
|
+
}
|
|
14707
|
+
function lookupParentSessionId(childID, now = Date.now()) {
|
|
14708
|
+
const entry = sessionParentMap.get(childID);
|
|
14709
|
+
if (!entry)
|
|
14710
|
+
return;
|
|
14711
|
+
if (now - entry.ts > SESSION_PARENT_MAP_TTL_MS) {
|
|
14712
|
+
sessionParentMap.delete(childID);
|
|
14713
|
+
return;
|
|
14714
|
+
}
|
|
14715
|
+
return entry.parentID;
|
|
14716
|
+
}
|
|
14717
|
+
function deleteSessionParent(childID) {
|
|
14718
|
+
sessionParentMap.delete(childID);
|
|
14719
|
+
if (_persistRoot) {
|
|
14720
|
+
mutateParentMap(_persistRoot, (entries) => entries.filter((e) => e.childID !== childID)).catch((err) => {
|
|
14721
|
+
log4.warn("mutateParentMap (delete) 失败(已隔离)", {
|
|
14722
|
+
error: err instanceof Error ? err.message : String(err),
|
|
14723
|
+
childID
|
|
14724
|
+
});
|
|
14725
|
+
});
|
|
14726
|
+
}
|
|
14727
|
+
}
|
|
14728
|
+
function sweepExpiredSessionParents(now = Date.now()) {
|
|
14729
|
+
let removed = 0;
|
|
14730
|
+
for (const [k, v] of [...sessionParentMap.entries()]) {
|
|
14731
|
+
if (now - v.ts > SESSION_PARENT_MAP_TTL_MS) {
|
|
14732
|
+
sessionParentMap.delete(k);
|
|
14733
|
+
removed++;
|
|
14734
|
+
}
|
|
14735
|
+
}
|
|
14736
|
+
if (_persistRoot) {
|
|
14737
|
+
mutateParentMap(_persistRoot, (entries) => entries.filter((e) => now - e.ts <= SESSION_PARENT_MAP_TTL_MS)).catch((err) => {
|
|
14738
|
+
log4.warn("mutateParentMap (sweep) 失败(已隔离)", {
|
|
14739
|
+
error: err instanceof Error ? err.message : String(err),
|
|
14740
|
+
removed
|
|
14741
|
+
});
|
|
14742
|
+
});
|
|
14743
|
+
}
|
|
14744
|
+
return removed;
|
|
14745
|
+
}
|
|
14746
|
+
|
|
14747
|
+
// lib/spawner-production.ts
|
|
14701
14748
|
class ProductionSpawner {
|
|
14702
14749
|
opts;
|
|
14703
14750
|
constructor(opts) {
|
|
@@ -14713,7 +14760,7 @@ class ProductionSpawner {
|
|
|
14713
14760
|
title: `[merge-review] sess=${args.sessionId.slice(0, 8)} r=${args.round}/${args.maxRounds}`,
|
|
14714
14761
|
...args.signal ? { signal: args.signal } : {},
|
|
14715
14762
|
timeoutMs: this.opts.reviewerTimeoutMs ?? 600000
|
|
14716
|
-
});
|
|
14763
|
+
}, args.sessionId);
|
|
14717
14764
|
} catch (err) {
|
|
14718
14765
|
throw err;
|
|
14719
14766
|
}
|
|
@@ -14745,7 +14792,7 @@ ${r.text.slice(0, 800)}`
|
|
|
14745
14792
|
title: `[merge-fix] sess=${args.sessionId.slice(0, 8)}`,
|
|
14746
14793
|
...args.signal ? { signal: args.signal } : {},
|
|
14747
14794
|
timeoutMs: this.opts.coderTimeoutMs ?? 1800000
|
|
14748
|
-
});
|
|
14795
|
+
}, args.sessionId);
|
|
14749
14796
|
} catch (err) {
|
|
14750
14797
|
throw err;
|
|
14751
14798
|
}
|
|
@@ -14759,7 +14806,7 @@ ${r.text.slice(0, 800)}`
|
|
|
14759
14806
|
}
|
|
14760
14807
|
return { ok: true, summary: r.text || "(coder 无文本输出)" };
|
|
14761
14808
|
}
|
|
14762
|
-
async runSubagent(opts) {
|
|
14809
|
+
async runSubagent(opts, parentSessionIdOverride) {
|
|
14763
14810
|
let childId;
|
|
14764
14811
|
try {
|
|
14765
14812
|
const created = await this.opts.client.session.create({
|
|
@@ -14770,6 +14817,17 @@ ${r.text.slice(0, 800)}`
|
|
|
14770
14817
|
throw new Error(`session.create 失败: ${describe5(created.error) || "no id"}`);
|
|
14771
14818
|
}
|
|
14772
14819
|
childId = created.data.id;
|
|
14820
|
+
const parentSessionId = parentSessionIdOverride ?? this.opts.parentSessionId ?? "";
|
|
14821
|
+
if (parentSessionId) {
|
|
14822
|
+
try {
|
|
14823
|
+
recordSessionParent(childId, parentSessionId);
|
|
14824
|
+
} catch (err) {
|
|
14825
|
+
this.opts.log?.("warn", `[spawner] recordSessionParent 失败 child=${childId}`, {
|
|
14826
|
+
err: describe5(err),
|
|
14827
|
+
parentSessionId
|
|
14828
|
+
});
|
|
14829
|
+
}
|
|
14830
|
+
}
|
|
14773
14831
|
const promptPromise = Promise.resolve(this.opts.client.session.prompt({
|
|
14774
14832
|
path: { id: childId },
|
|
14775
14833
|
body: {
|
|
@@ -14808,7 +14866,7 @@ async function raceAbortTimeout(p, signal, timeoutMs, label) {
|
|
|
14808
14866
|
e.name = "AbortError";
|
|
14809
14867
|
throw e;
|
|
14810
14868
|
}
|
|
14811
|
-
return await new Promise((
|
|
14869
|
+
return await new Promise((resolve14, reject) => {
|
|
14812
14870
|
let settled = false;
|
|
14813
14871
|
const timer = setTimeout(() => {
|
|
14814
14872
|
if (settled)
|
|
@@ -14833,7 +14891,7 @@ async function raceAbortTimeout(p, signal, timeoutMs, label) {
|
|
|
14833
14891
|
settled = true;
|
|
14834
14892
|
clearTimeout(timer);
|
|
14835
14893
|
signal?.removeEventListener("abort", onAbort);
|
|
14836
|
-
|
|
14894
|
+
resolve14(v);
|
|
14837
14895
|
}, (e) => {
|
|
14838
14896
|
if (settled)
|
|
14839
14897
|
return;
|
|
@@ -14893,8 +14951,8 @@ function clip4(s, max) {
|
|
|
14893
14951
|
}
|
|
14894
14952
|
|
|
14895
14953
|
// lib/codeforge-runtime.ts
|
|
14896
|
-
import { promises as
|
|
14897
|
-
import * as
|
|
14954
|
+
import { promises as fs14 } from "node:fs";
|
|
14955
|
+
import * as path17 from "node:path";
|
|
14898
14956
|
var DEFAULT_RUNTIME = {
|
|
14899
14957
|
autonomy: {
|
|
14900
14958
|
default_mode: "semi",
|
|
@@ -14938,10 +14996,10 @@ function loadRuntimeSync(opts = {}) {
|
|
|
14938
14996
|
}
|
|
14939
14997
|
async function loadRuntime(opts = {}) {
|
|
14940
14998
|
const root = opts.root ?? process.cwd();
|
|
14941
|
-
const abs =
|
|
14999
|
+
const abs = path17.resolve(root, opts.file ?? CONFIG_FILE);
|
|
14942
15000
|
let raw;
|
|
14943
15001
|
try {
|
|
14944
|
-
raw = await
|
|
15002
|
+
raw = await fs14.readFile(abs, "utf8");
|
|
14945
15003
|
} catch (e) {
|
|
14946
15004
|
const code = e.code;
|
|
14947
15005
|
if (code === "ENOENT") {
|
|
@@ -15168,9 +15226,9 @@ function normalizeVariant(raw) {
|
|
|
15168
15226
|
return "default";
|
|
15169
15227
|
return raw;
|
|
15170
15228
|
}
|
|
15171
|
-
async function showToast(client, payload,
|
|
15229
|
+
async function showToast(client, payload, log5) {
|
|
15172
15230
|
if (typeof client?.tui?.showToast !== "function") {
|
|
15173
|
-
|
|
15231
|
+
log5?.debug?.("tui.showToast 不可用,noop");
|
|
15174
15232
|
return false;
|
|
15175
15233
|
}
|
|
15176
15234
|
try {
|
|
@@ -15184,13 +15242,13 @@ async function showToast(client, payload, log4) {
|
|
|
15184
15242
|
});
|
|
15185
15243
|
return true;
|
|
15186
15244
|
} catch (err) {
|
|
15187
|
-
|
|
15245
|
+
log5?.warn("tui.showToast 抛错(已隔离)", {
|
|
15188
15246
|
error: err instanceof Error ? err.message : String(err)
|
|
15189
15247
|
});
|
|
15190
15248
|
return false;
|
|
15191
15249
|
}
|
|
15192
15250
|
}
|
|
15193
|
-
var
|
|
15251
|
+
var log5 = makePluginLogger(PLUGIN_NAME6);
|
|
15194
15252
|
var toolHeartbeatServer = async (ctx) => {
|
|
15195
15253
|
logLifecycle(PLUGIN_NAME6, "activate", {
|
|
15196
15254
|
directory: ctx.directory,
|
|
@@ -15206,7 +15264,7 @@ var toolHeartbeatServer = async (ctx) => {
|
|
|
15206
15264
|
const alert = buildAlert(r);
|
|
15207
15265
|
if (!alert)
|
|
15208
15266
|
continue;
|
|
15209
|
-
const sent = await showToast(client, { message: alert.message, variant: alert.variant },
|
|
15267
|
+
const sent = await showToast(client, { message: alert.message, variant: alert.variant }, log5);
|
|
15210
15268
|
r.alertsSent.add(alert.threshold);
|
|
15211
15269
|
safeWriteLog(PLUGIN_NAME6, {
|
|
15212
15270
|
hook: "interval",
|
|
@@ -15949,7 +16007,7 @@ var handler7 = codeforgeToolsServer;
|
|
|
15949
16007
|
|
|
15950
16008
|
// plugins/discover-spec-suggest.ts
|
|
15951
16009
|
import { readFileSync as readFileSync3, readdirSync, statSync as statSync3 } from "node:fs";
|
|
15952
|
-
import { join as
|
|
16010
|
+
import { join as join14 } from "node:path";
|
|
15953
16011
|
|
|
15954
16012
|
// lib/handoff-schema.ts
|
|
15955
16013
|
import { z as z31 } from "zod";
|
|
@@ -16116,9 +16174,9 @@ function validateHandoff(rawYaml, fileSize) {
|
|
|
16116
16174
|
const result = HandoffSchema.safeParse(parsed);
|
|
16117
16175
|
if (!result.success) {
|
|
16118
16176
|
const first = result.error.issues[0];
|
|
16119
|
-
const
|
|
16177
|
+
const path18 = first?.path?.join(".") ?? "(root)";
|
|
16120
16178
|
const msg = first?.message ?? "unknown";
|
|
16121
|
-
return { ok: false, reason: `schema 校验失败:${
|
|
16179
|
+
return { ok: false, reason: `schema 校验失败:${path18}: ${msg}` };
|
|
16122
16180
|
}
|
|
16123
16181
|
return { ok: true, data: result.data, schemaVersion: result.data.schema_version };
|
|
16124
16182
|
}
|
|
@@ -16135,7 +16193,7 @@ var SESSION_TTL_MS = 24 * 60 * 60 * 1000;
|
|
|
16135
16193
|
var MATCH_THRESHOLD = 0.15;
|
|
16136
16194
|
var MAX_CANDIDATES = 3;
|
|
16137
16195
|
var NUDGE_MAX_LEN = 1500;
|
|
16138
|
-
var SPECS_REL_DIR =
|
|
16196
|
+
var SPECS_REL_DIR = join14(".codeforge", "specs");
|
|
16139
16197
|
var sessionMap = new Map;
|
|
16140
16198
|
function pruneIfOversize() {
|
|
16141
16199
|
while (sessionMap.size > SESSION_CAP) {
|
|
@@ -16241,18 +16299,18 @@ function loadSpecs(rootDir, opts = {}) {
|
|
|
16241
16299
|
const dirReader = opts.dirReader ?? defaultDirReader;
|
|
16242
16300
|
const dirExists = opts.dirExists ?? defaultDirExists;
|
|
16243
16301
|
const statReader = opts.statReader ?? defaultStatReader;
|
|
16244
|
-
const
|
|
16245
|
-
const specsRoot =
|
|
16302
|
+
const log6 = makePluginLogger(PLUGIN_NAME8);
|
|
16303
|
+
const specsRoot = join14(rootDir, SPECS_REL_DIR);
|
|
16246
16304
|
const records = [];
|
|
16247
16305
|
if (!dirExists(specsRoot)) {
|
|
16248
|
-
|
|
16306
|
+
log6.info(`specs 目录不存在,plugin 将 no-op`, { specsRoot });
|
|
16249
16307
|
return records;
|
|
16250
16308
|
}
|
|
16251
16309
|
let entries;
|
|
16252
16310
|
try {
|
|
16253
16311
|
entries = dirReader(specsRoot);
|
|
16254
16312
|
} catch (err) {
|
|
16255
|
-
|
|
16313
|
+
log6.warn(`specs 目录读取失败`, {
|
|
16256
16314
|
specsRoot,
|
|
16257
16315
|
error: err instanceof Error ? err.message : String(err)
|
|
16258
16316
|
});
|
|
@@ -16260,15 +16318,15 @@ function loadSpecs(rootDir, opts = {}) {
|
|
|
16260
16318
|
}
|
|
16261
16319
|
for (const entry of entries) {
|
|
16262
16320
|
if (!isValidSlug(entry)) {
|
|
16263
|
-
|
|
16321
|
+
log6.info(`跳过非合法 slug 命名的条目`, { entry });
|
|
16264
16322
|
continue;
|
|
16265
16323
|
}
|
|
16266
|
-
const specDir =
|
|
16324
|
+
const specDir = join14(specsRoot, entry);
|
|
16267
16325
|
let dirStat;
|
|
16268
16326
|
try {
|
|
16269
16327
|
dirStat = statReader(specDir);
|
|
16270
16328
|
} catch (err) {
|
|
16271
|
-
|
|
16329
|
+
log6.warn(`spec 目录 stat 失败`, {
|
|
16272
16330
|
specDir,
|
|
16273
16331
|
error: err instanceof Error ? err.message : String(err)
|
|
16274
16332
|
});
|
|
@@ -16276,7 +16334,7 @@ function loadSpecs(rootDir, opts = {}) {
|
|
|
16276
16334
|
}
|
|
16277
16335
|
if (!dirStat.isDirectory)
|
|
16278
16336
|
continue;
|
|
16279
|
-
const handoffPath =
|
|
16337
|
+
const handoffPath = join14(specDir, "handoff.yaml");
|
|
16280
16338
|
let fileStat;
|
|
16281
16339
|
try {
|
|
16282
16340
|
fileStat = statReader(handoffPath);
|
|
@@ -16286,7 +16344,7 @@ function loadSpecs(rootDir, opts = {}) {
|
|
|
16286
16344
|
if (fileStat.isDirectory)
|
|
16287
16345
|
continue;
|
|
16288
16346
|
if (fileStat.size > MAX_HANDOFF_SIZE) {
|
|
16289
|
-
|
|
16347
|
+
log6.warn(`handoff.yaml 超过 ${MAX_HANDOFF_SIZE} 字节上限,已跳过`, {
|
|
16290
16348
|
handoffPath,
|
|
16291
16349
|
size: fileStat.size
|
|
16292
16350
|
});
|
|
@@ -16296,7 +16354,7 @@ function loadSpecs(rootDir, opts = {}) {
|
|
|
16296
16354
|
try {
|
|
16297
16355
|
rawYaml = reader(handoffPath);
|
|
16298
16356
|
} catch (err) {
|
|
16299
|
-
|
|
16357
|
+
log6.warn(`handoff.yaml 读取失败`, {
|
|
16300
16358
|
handoffPath,
|
|
16301
16359
|
error: err instanceof Error ? err.message : String(err)
|
|
16302
16360
|
});
|
|
@@ -16304,7 +16362,7 @@ function loadSpecs(rootDir, opts = {}) {
|
|
|
16304
16362
|
}
|
|
16305
16363
|
const result = validateHandoff(rawYaml, fileStat.size);
|
|
16306
16364
|
if (!result.ok) {
|
|
16307
|
-
|
|
16365
|
+
log6.warn(`handoff.yaml 校验失败,已跳过`, {
|
|
16308
16366
|
handoffPath,
|
|
16309
16367
|
reason: result.reason
|
|
16310
16368
|
});
|
|
@@ -16312,7 +16370,7 @@ function loadSpecs(rootDir, opts = {}) {
|
|
|
16312
16370
|
}
|
|
16313
16371
|
const handoff = result.data;
|
|
16314
16372
|
if (handoff.slug !== entry) {
|
|
16315
|
-
|
|
16373
|
+
log6.warn(`handoff.slug 与目录名不一致,以目录名为准`, {
|
|
16316
16374
|
dir: entry,
|
|
16317
16375
|
handoffSlug: handoff.slug
|
|
16318
16376
|
});
|
|
@@ -16335,7 +16393,7 @@ function loadSpecs(rootDir, opts = {}) {
|
|
|
16335
16393
|
handoff
|
|
16336
16394
|
});
|
|
16337
16395
|
}
|
|
16338
|
-
|
|
16396
|
+
log6.info(`specs 加载完成`, {
|
|
16339
16397
|
specsRoot,
|
|
16340
16398
|
loaded: records.length,
|
|
16341
16399
|
total_entries: entries.length
|
|
@@ -16369,14 +16427,14 @@ function renderCandidatesNudge(matched) {
|
|
|
16369
16427
|
return body.slice(0, NUDGE_MAX_LEN - 4) + `
|
|
16370
16428
|
…`;
|
|
16371
16429
|
}
|
|
16372
|
-
var
|
|
16430
|
+
var log6 = makePluginLogger(PLUGIN_NAME8);
|
|
16373
16431
|
var discoverSpecSuggestServer = async (ctx) => {
|
|
16374
16432
|
try {
|
|
16375
16433
|
const loaded = loadSpecs(ctx.directory ?? process.cwd());
|
|
16376
16434
|
specIndex.length = 0;
|
|
16377
16435
|
specIndex.push(...loaded);
|
|
16378
16436
|
} catch (err) {
|
|
16379
|
-
|
|
16437
|
+
log6.warn(`loadSpecs 失败(plugin 将 no-op)`, {
|
|
16380
16438
|
error: err instanceof Error ? err.message : String(err)
|
|
16381
16439
|
});
|
|
16382
16440
|
}
|
|
@@ -16682,9 +16740,9 @@ var CODEFORGE_CONSTRAINTS = [
|
|
|
16682
16740
|
priority: 8
|
|
16683
16741
|
}
|
|
16684
16742
|
];
|
|
16685
|
-
async function seedConstraints(client,
|
|
16743
|
+
async function seedConstraints(client, log7) {
|
|
16686
16744
|
if (!client.hasTransport()) {
|
|
16687
|
-
|
|
16745
|
+
log7?.debug?.(`[${PLUGIN_NAME9}] seedConstraints: transport 不可用,跳过`, {});
|
|
16688
16746
|
return { ok: false, reason: "transport_unavailable" };
|
|
16689
16747
|
}
|
|
16690
16748
|
try {
|
|
@@ -16698,7 +16756,7 @@ async function seedConstraints(client, log6) {
|
|
|
16698
16756
|
});
|
|
16699
16757
|
if (result && typeof result === "object" && "ok" in result && result.ok === false) {
|
|
16700
16758
|
const r = result;
|
|
16701
|
-
|
|
16759
|
+
log7?.warn(`[${PLUGIN_NAME9}] seedConstraints 降级`, {
|
|
16702
16760
|
reason: r.reason,
|
|
16703
16761
|
message: r.message
|
|
16704
16762
|
});
|
|
@@ -16708,15 +16766,15 @@ async function seedConstraints(client, log6) {
|
|
|
16708
16766
|
message: r.message
|
|
16709
16767
|
};
|
|
16710
16768
|
}
|
|
16711
|
-
|
|
16769
|
+
log7?.info(`[${PLUGIN_NAME9}] seedConstraints: 已写入 ${CODEFORGE_CONSTRAINTS.length} 条 constraints`, { count: CODEFORGE_CONSTRAINTS.length });
|
|
16712
16770
|
return { ok: true, itemsWritten: CODEFORGE_CONSTRAINTS.length };
|
|
16713
16771
|
} catch (err) {
|
|
16714
16772
|
const message = err instanceof Error ? err.message : String(err);
|
|
16715
|
-
|
|
16773
|
+
log7?.warn(`[${PLUGIN_NAME9}] seedConstraints 失败(已静默)`, { error: message });
|
|
16716
16774
|
return { ok: false, reason: "exception", message };
|
|
16717
16775
|
}
|
|
16718
16776
|
}
|
|
16719
|
-
function createSystemInjectedHook(client, sessionId,
|
|
16777
|
+
function createSystemInjectedHook(client, sessionId, log7) {
|
|
16720
16778
|
const section = `topic:auto-context-${sessionId ?? "global"}`;
|
|
16721
16779
|
return async (markdown) => {
|
|
16722
16780
|
try {
|
|
@@ -16727,7 +16785,7 @@ function createSystemInjectedHook(client, sessionId, log6) {
|
|
|
16727
16785
|
});
|
|
16728
16786
|
if (result && typeof result === "object" && "ok" in result && result.ok === false) {
|
|
16729
16787
|
const r = result;
|
|
16730
|
-
|
|
16788
|
+
log7?.warn(`[${PLUGIN_NAME9}] system-injected 降级到 observe-only:${r.reason ?? "unknown"}`, {
|
|
16731
16789
|
sessionId,
|
|
16732
16790
|
section,
|
|
16733
16791
|
preview: markdown.slice(0, 200),
|
|
@@ -16736,12 +16794,12 @@ function createSystemInjectedHook(client, sessionId, log6) {
|
|
|
16736
16794
|
});
|
|
16737
16795
|
return;
|
|
16738
16796
|
}
|
|
16739
|
-
|
|
16797
|
+
log7?.info(`[${PLUGIN_NAME9}] system-injected: 写入 KH working_memory 成功 (${markdown.length} chars)`, {
|
|
16740
16798
|
sessionId,
|
|
16741
16799
|
section
|
|
16742
16800
|
});
|
|
16743
16801
|
} catch (err) {
|
|
16744
|
-
|
|
16802
|
+
log7?.warn(`[${PLUGIN_NAME9}] system-injected 抛异常,降级到 observe-only`, {
|
|
16745
16803
|
sessionId,
|
|
16746
16804
|
section,
|
|
16747
16805
|
preview: markdown.slice(0, 200),
|
|
@@ -16758,7 +16816,7 @@ function inflightKey(sessionId, query) {
|
|
|
16758
16816
|
async function runKhSearchAndInject(args) {
|
|
16759
16817
|
const { query, ctx, opts, mode } = args;
|
|
16760
16818
|
const cfg = opts.config ?? DEFAULT_CONFIG4;
|
|
16761
|
-
const
|
|
16819
|
+
const log7 = ctx.log;
|
|
16762
16820
|
const startedAt = Date.now();
|
|
16763
16821
|
const racer = opts.scheduler?.raceTimeout ?? (async (p, ms) => {
|
|
16764
16822
|
let timer = null;
|
|
@@ -16784,7 +16842,7 @@ async function runKhSearchAndInject(args) {
|
|
|
16784
16842
|
});
|
|
16785
16843
|
result = await racer(searchPromise, cfg.timeoutMs);
|
|
16786
16844
|
} catch (err) {
|
|
16787
|
-
|
|
16845
|
+
log7?.warn(`[${PLUGIN_NAME9}] client.search threw (sync or async), return null`, {
|
|
16788
16846
|
query,
|
|
16789
16847
|
elapsedMs: Date.now() - startedAt,
|
|
16790
16848
|
sessionId: ctx.sessionId,
|
|
@@ -16794,7 +16852,7 @@ async function runKhSearchAndInject(args) {
|
|
|
16794
16852
|
return null;
|
|
16795
16853
|
}
|
|
16796
16854
|
if (result === "__timeout__") {
|
|
16797
|
-
|
|
16855
|
+
log7?.warn(`[${PLUGIN_NAME9}] timeout`, {
|
|
16798
16856
|
query,
|
|
16799
16857
|
ms: cfg.timeoutMs,
|
|
16800
16858
|
elapsedMs: Date.now() - startedAt,
|
|
@@ -16804,7 +16862,7 @@ async function runKhSearchAndInject(args) {
|
|
|
16804
16862
|
return null;
|
|
16805
16863
|
}
|
|
16806
16864
|
if (!result.ok) {
|
|
16807
|
-
|
|
16865
|
+
log7?.warn(`[${PLUGIN_NAME9}] kh degraded`, {
|
|
16808
16866
|
reason: result.reason,
|
|
16809
16867
|
query,
|
|
16810
16868
|
elapsedMs: Date.now() - startedAt,
|
|
@@ -16816,7 +16874,7 @@ async function runKhSearchAndInject(args) {
|
|
|
16816
16874
|
const filtered = result.insights.filter((i) => (i.confidence ?? 0) >= cfg.minConfidence);
|
|
16817
16875
|
if (filtered.length === 0) {
|
|
16818
16876
|
opts.cache.record(query, []);
|
|
16819
|
-
|
|
16877
|
+
log7?.debug?.(`[${PLUGIN_NAME9}] no candidate above threshold`, {
|
|
16820
16878
|
query,
|
|
16821
16879
|
rawCount: result.insights.length,
|
|
16822
16880
|
elapsedMs: Date.now() - startedAt,
|
|
@@ -16829,7 +16887,7 @@ async function runKhSearchAndInject(args) {
|
|
|
16829
16887
|
try {
|
|
16830
16888
|
await ctx.injectContext(payload.markdown);
|
|
16831
16889
|
} catch (err) {
|
|
16832
|
-
|
|
16890
|
+
log7?.warn(`[${PLUGIN_NAME9}] injectContext threw`, {
|
|
16833
16891
|
error: err instanceof Error ? err.message : String(err),
|
|
16834
16892
|
query,
|
|
16835
16893
|
sessionId: ctx.sessionId
|
|
@@ -16837,7 +16895,7 @@ async function runKhSearchAndInject(args) {
|
|
|
16837
16895
|
}
|
|
16838
16896
|
}
|
|
16839
16897
|
opts.cache.record(query, filtered);
|
|
16840
|
-
|
|
16898
|
+
log7?.info(`[${PLUGIN_NAME9}] inject complete (${mode})`, {
|
|
16841
16899
|
query,
|
|
16842
16900
|
mode,
|
|
16843
16901
|
candidateCount: filtered.length,
|
|
@@ -16850,21 +16908,21 @@ async function handleMessage2(raw, opts) {
|
|
|
16850
16908
|
const cfg = opts.config ?? DEFAULT_CONFIG4;
|
|
16851
16909
|
const mode = opts.mode ?? INJECTION_MODE;
|
|
16852
16910
|
const ctx = raw ?? {};
|
|
16853
|
-
const
|
|
16911
|
+
const log7 = ctx.log;
|
|
16854
16912
|
const text = (ctx.content ?? "").trim();
|
|
16855
16913
|
if (!shouldInject(text, cfg)) {
|
|
16856
|
-
|
|
16914
|
+
log7?.debug?.(`[${PLUGIN_NAME9}] skip (filter)`, { textLen: text.length });
|
|
16857
16915
|
return;
|
|
16858
16916
|
}
|
|
16859
16917
|
const query = extractQuery(text);
|
|
16860
16918
|
if (!query)
|
|
16861
16919
|
return;
|
|
16862
16920
|
if (opts.cache.shouldSkip(query)) {
|
|
16863
|
-
|
|
16921
|
+
log7?.debug?.(`[${PLUGIN_NAME9}] cache hit, skip`, { query });
|
|
16864
16922
|
return;
|
|
16865
16923
|
}
|
|
16866
16924
|
if (inflight2.size >= INFLIGHT_CAP) {
|
|
16867
|
-
|
|
16925
|
+
log7?.warn(`[${PLUGIN_NAME9}] inflight cap reached, skip`, {
|
|
16868
16926
|
query,
|
|
16869
16927
|
inflightSize: inflight2.size,
|
|
16870
16928
|
cap: INFLIGHT_CAP,
|
|
@@ -16875,7 +16933,7 @@ async function handleMessage2(raw, opts) {
|
|
|
16875
16933
|
const key = inflightKey(ctx.sessionId, query);
|
|
16876
16934
|
inflight2.add(key);
|
|
16877
16935
|
runKhSearchAndInject({ query, ctx, opts, mode }).catch((err) => {
|
|
16878
|
-
|
|
16936
|
+
log7?.warn(`[${PLUGIN_NAME9}] runKhSearchAndInject 顶层兜底捕获`, {
|
|
16879
16937
|
error: err instanceof Error ? err.message : String(err),
|
|
16880
16938
|
query,
|
|
16881
16939
|
sessionId: ctx.sessionId
|
|
@@ -16888,7 +16946,7 @@ logLifecycle(PLUGIN_NAME9, "import");
|
|
|
16888
16946
|
var sharedClient2 = new KhClient;
|
|
16889
16947
|
var sharedCache = new QueryCache(DEFAULT_CONFIG4.cacheTtlMs);
|
|
16890
16948
|
var khAutoContextServer = async (ctx) => {
|
|
16891
|
-
const
|
|
16949
|
+
const log7 = makePluginLogger(PLUGIN_NAME9);
|
|
16892
16950
|
const runtimeMode = resolveInjectionMode(sharedClient2);
|
|
16893
16951
|
logLifecycle(PLUGIN_NAME9, "activate", {
|
|
16894
16952
|
directory: ctx.directory,
|
|
@@ -16897,8 +16955,8 @@ var khAutoContextServer = async (ctx) => {
|
|
|
16897
16955
|
mode: runtimeMode,
|
|
16898
16956
|
transport_available: sharedClient2.hasTransport()
|
|
16899
16957
|
});
|
|
16900
|
-
seedConstraints(sharedClient2,
|
|
16901
|
-
|
|
16958
|
+
seedConstraints(sharedClient2, log7).catch((err) => {
|
|
16959
|
+
log7.warn("seedConstraints 顶层兜底捕获", {
|
|
16902
16960
|
error: err instanceof Error ? err.message : String(err)
|
|
16903
16961
|
});
|
|
16904
16962
|
});
|
|
@@ -16908,8 +16966,8 @@ var khAutoContextServer = async (ctx) => {
|
|
|
16908
16966
|
const text = extractUserText(output);
|
|
16909
16967
|
if (!text)
|
|
16910
16968
|
return;
|
|
16911
|
-
const injectContext = runtimeMode === "system-injected" ? createSystemInjectedHook(sharedClient2, input.sessionID,
|
|
16912
|
-
|
|
16969
|
+
const injectContext = runtimeMode === "system-injected" ? createSystemInjectedHook(sharedClient2, input.sessionID, log7) : async (markdown) => {
|
|
16970
|
+
log7.info(`KH context candidate (${markdown.length} chars)`, {
|
|
16913
16971
|
sessionID: input.sessionID,
|
|
16914
16972
|
mode: runtimeMode,
|
|
16915
16973
|
preview: markdown.slice(0, 200)
|
|
@@ -16919,7 +16977,7 @@ var khAutoContextServer = async (ctx) => {
|
|
|
16919
16977
|
content: text,
|
|
16920
16978
|
sessionId: input.sessionID,
|
|
16921
16979
|
injectContext,
|
|
16922
|
-
log:
|
|
16980
|
+
log: log7
|
|
16923
16981
|
}, { client: sharedClient2, cache: sharedCache, mode: runtimeMode });
|
|
16924
16982
|
});
|
|
16925
16983
|
},
|
|
@@ -16933,7 +16991,7 @@ var khAutoContextServer = async (ctx) => {
|
|
|
16933
16991
|
if (typeof sid !== "string" || !sid)
|
|
16934
16992
|
return;
|
|
16935
16993
|
sharedKhCache.onSessionEnd(sid);
|
|
16936
|
-
|
|
16994
|
+
log7.debug?.(`[${PLUGIN_NAME9}] session.idle: cleared shared cache`, {
|
|
16937
16995
|
sessionID: sid
|
|
16938
16996
|
});
|
|
16939
16997
|
});
|
|
@@ -17165,7 +17223,7 @@ function hasRecentSmartSearch(messages, recentRounds) {
|
|
|
17165
17223
|
}
|
|
17166
17224
|
return false;
|
|
17167
17225
|
}
|
|
17168
|
-
function
|
|
17226
|
+
function evaluate(input) {
|
|
17169
17227
|
const messages = Array.isArray(input.messages) ? input.messages : [];
|
|
17170
17228
|
const sessionId = input.sessionId ?? "unknown";
|
|
17171
17229
|
const threshold = input.threshold ?? DEFAULT_THRESHOLD;
|
|
@@ -17205,15 +17263,15 @@ function evaluate2(input) {
|
|
|
17205
17263
|
lastTriggerAt.set(sessionId, now);
|
|
17206
17264
|
return { triggered: true, reason, sessionId };
|
|
17207
17265
|
}
|
|
17208
|
-
function handleObserve(raw,
|
|
17266
|
+
function handleObserve(raw, log7) {
|
|
17209
17267
|
const ctx = raw ?? {};
|
|
17210
17268
|
if (!Array.isArray(ctx.messages))
|
|
17211
17269
|
return null;
|
|
17212
17270
|
try {
|
|
17213
|
-
const result =
|
|
17271
|
+
const result = evaluate(ctx);
|
|
17214
17272
|
if (result.triggered && result.reason) {
|
|
17215
17273
|
const reasonStr = formatReason(result.reason);
|
|
17216
|
-
|
|
17274
|
+
log7?.info(`[${PLUGIN_NAME10}] ⚡ KH 提醒(observe-only) · session=${result.sessionId} · ${reasonStr}`, {
|
|
17217
17275
|
sessionId: result.sessionId,
|
|
17218
17276
|
reason: result.reason,
|
|
17219
17277
|
suggestion: "调用 smart_search 查项目历史;完成后用 save_chat_insight 沉淀"
|
|
@@ -17221,7 +17279,7 @@ function handleObserve(raw, log6) {
|
|
|
17221
17279
|
}
|
|
17222
17280
|
return result;
|
|
17223
17281
|
} catch (err) {
|
|
17224
|
-
|
|
17282
|
+
log7?.warn(`[${PLUGIN_NAME10}] evaluate 异常(已隔离)`, {
|
|
17225
17283
|
error: err instanceof Error ? err.message : String(err)
|
|
17226
17284
|
});
|
|
17227
17285
|
return null;
|
|
@@ -17237,7 +17295,7 @@ function formatReason(r) {
|
|
|
17237
17295
|
return `no-search-in-recent-rounds (last ${r.rounds})`;
|
|
17238
17296
|
}
|
|
17239
17297
|
}
|
|
17240
|
-
var
|
|
17298
|
+
var log7 = makePluginLogger(PLUGIN_NAME10);
|
|
17241
17299
|
var khReminderServer = async (ctx) => {
|
|
17242
17300
|
logLifecycle(PLUGIN_NAME10, "activate", {
|
|
17243
17301
|
directory: ctx.directory,
|
|
@@ -17263,7 +17321,7 @@ var khReminderServer = async (ctx) => {
|
|
|
17263
17321
|
`);
|
|
17264
17322
|
return { role, content };
|
|
17265
17323
|
});
|
|
17266
|
-
const result = handleObserve({ messages: flat, sessionId },
|
|
17324
|
+
const result = handleObserve({ messages: flat, sessionId }, log7);
|
|
17267
17325
|
if (!result)
|
|
17268
17326
|
return;
|
|
17269
17327
|
safeWriteLog(PLUGIN_NAME10, {
|
|
@@ -17282,14 +17340,14 @@ var khReminderServer = async (ctx) => {
|
|
|
17282
17340
|
var handler10 = khReminderServer;
|
|
17283
17341
|
|
|
17284
17342
|
// lib/memories.ts
|
|
17285
|
-
import { promises as
|
|
17286
|
-
import * as
|
|
17343
|
+
import { promises as fs15 } from "node:fs";
|
|
17344
|
+
import * as path18 from "node:path";
|
|
17287
17345
|
import * as os5 from "node:os";
|
|
17288
17346
|
function resolveConfig(c) {
|
|
17289
17347
|
return {
|
|
17290
17348
|
projectRoot: c.projectRoot,
|
|
17291
17349
|
homeDir: c.homeDir ?? os5.homedir(),
|
|
17292
|
-
projectName: c.projectName ??
|
|
17350
|
+
projectName: c.projectName ?? path18.basename(c.projectRoot),
|
|
17293
17351
|
kh: c.kh,
|
|
17294
17352
|
now: c.now ?? Date.now,
|
|
17295
17353
|
log: c.log ?? (() => {}),
|
|
@@ -17298,13 +17356,13 @@ function resolveConfig(c) {
|
|
|
17298
17356
|
}
|
|
17299
17357
|
function fileFor(scope, cfg) {
|
|
17300
17358
|
if (scope === "project") {
|
|
17301
|
-
return
|
|
17359
|
+
return path18.join(cfg.projectRoot, ".codeforge", "memories.json");
|
|
17302
17360
|
}
|
|
17303
|
-
return
|
|
17361
|
+
return path18.join(cfg.homeDir, ".codeforge", "memories.json");
|
|
17304
17362
|
}
|
|
17305
17363
|
async function readBank(p) {
|
|
17306
17364
|
try {
|
|
17307
|
-
const raw = await
|
|
17365
|
+
const raw = await fs15.readFile(p, "utf8");
|
|
17308
17366
|
const arr = JSON.parse(raw);
|
|
17309
17367
|
if (!Array.isArray(arr))
|
|
17310
17368
|
return [];
|
|
@@ -17314,10 +17372,10 @@ async function readBank(p) {
|
|
|
17314
17372
|
}
|
|
17315
17373
|
}
|
|
17316
17374
|
async function writeBank(p, items) {
|
|
17317
|
-
await
|
|
17375
|
+
await fs15.mkdir(path18.dirname(p), { recursive: true });
|
|
17318
17376
|
const tmp = `${p}.tmp`;
|
|
17319
|
-
await
|
|
17320
|
-
await
|
|
17377
|
+
await fs15.writeFile(tmp, JSON.stringify(items, null, 2), "utf8");
|
|
17378
|
+
await fs15.rename(tmp, p);
|
|
17321
17379
|
}
|
|
17322
17380
|
function isMemory(x) {
|
|
17323
17381
|
if (!x || typeof x !== "object")
|
|
@@ -17569,23 +17627,23 @@ async function handleMessage3(raw, opts) {
|
|
|
17569
17627
|
const cfg = opts.cfg ?? DEFAULT_CONFIG5;
|
|
17570
17628
|
const cache2 = opts.cache ?? new QueryCache2(cfg.cacheTtlMs);
|
|
17571
17629
|
const ctx = raw ?? {};
|
|
17572
|
-
const
|
|
17630
|
+
const log8 = ctx.log;
|
|
17573
17631
|
const text = (ctx.content ?? "").trim();
|
|
17574
17632
|
if (!text)
|
|
17575
17633
|
return { kind: "noop", reason: "empty", mode: INJECTION_MODE2 };
|
|
17576
17634
|
const dir = parseDirective(text);
|
|
17577
17635
|
if (dir.kind !== "none") {
|
|
17578
|
-
return await handleDirective(dir, ctx, opts.memCfg,
|
|
17636
|
+
return await handleDirective(dir, ctx, opts.memCfg, log8);
|
|
17579
17637
|
}
|
|
17580
17638
|
if (!shouldRecall(text, cfg)) {
|
|
17581
|
-
|
|
17639
|
+
log8?.debug?.(`[${PLUGIN_NAME11}] skip (filter)`, { textLen: text.length });
|
|
17582
17640
|
return { kind: "noop", reason: "filtered", mode: INJECTION_MODE2 };
|
|
17583
17641
|
}
|
|
17584
17642
|
const query = extractQuery2(text);
|
|
17585
17643
|
if (!query)
|
|
17586
17644
|
return { kind: "noop", reason: "empty_query", mode: INJECTION_MODE2 };
|
|
17587
17645
|
if (cache2.shouldSkip(query)) {
|
|
17588
|
-
|
|
17646
|
+
log8?.debug?.(`[${PLUGIN_NAME11}] cache hit`, { query });
|
|
17589
17647
|
return { kind: "noop", reason: "cache", mode: INJECTION_MODE2 };
|
|
17590
17648
|
}
|
|
17591
17649
|
const racer = opts.scheduler?.raceTimeout ?? (async (p, ms) => {
|
|
@@ -17610,7 +17668,7 @@ async function handleMessage3(raw, opts) {
|
|
|
17610
17668
|
}, opts.memCfg);
|
|
17611
17669
|
const result = await racer(injectPromise, cfg.timeoutMs);
|
|
17612
17670
|
if (result === "__timeout__") {
|
|
17613
|
-
|
|
17671
|
+
log8?.warn(`[${PLUGIN_NAME11}] timeout`, { query, ms: cfg.timeoutMs });
|
|
17614
17672
|
cache2.record(query, 0);
|
|
17615
17673
|
return { kind: "noop", reason: "timeout", mode: INJECTION_MODE2 };
|
|
17616
17674
|
}
|
|
@@ -17622,29 +17680,29 @@ async function handleMessage3(raw, opts) {
|
|
|
17622
17680
|
try {
|
|
17623
17681
|
await ctx.injectContext(result.text);
|
|
17624
17682
|
} catch (err) {
|
|
17625
|
-
|
|
17683
|
+
log8?.warn(`[${PLUGIN_NAME11}] injectContext threw`, {
|
|
17626
17684
|
error: err instanceof Error ? err.message : String(err)
|
|
17627
17685
|
});
|
|
17628
17686
|
}
|
|
17629
17687
|
}
|
|
17630
17688
|
cache2.record(query, result.recalled);
|
|
17631
|
-
|
|
17689
|
+
log8?.info(`[${PLUGIN_NAME11}] observe-only: ${result.used}/${result.recalled} memories ready`, { query, mode: INJECTION_MODE2 });
|
|
17632
17690
|
return { kind: "injected", payload: result, mode: INJECTION_MODE2 };
|
|
17633
17691
|
}
|
|
17634
|
-
async function handleDirective(dir, ctx, memCfg,
|
|
17692
|
+
async function handleDirective(dir, ctx, memCfg, log8) {
|
|
17635
17693
|
if (dir.kind === "remember") {
|
|
17636
17694
|
const r = await addMemory({ scope: dir.scope, content: dir.content, source: "directive" }, memCfg);
|
|
17637
17695
|
if (r.ok) {
|
|
17638
17696
|
const target = r.written_to === "kh" ? "KH" : "本地";
|
|
17639
17697
|
await safeReply(ctx, `\uD83E\uDDE0 已记住(${dir.scope} / ${target},id=${r.id})`);
|
|
17640
|
-
|
|
17698
|
+
log8?.info(`[${PLUGIN_NAME11}] /remember ok`, {
|
|
17641
17699
|
scope: dir.scope,
|
|
17642
17700
|
id: r.id,
|
|
17643
17701
|
mode: INJECTION_MODE2
|
|
17644
17702
|
});
|
|
17645
17703
|
} else {
|
|
17646
17704
|
await safeReply(ctx, `❌ 记忆失败:${r.error ?? "unknown"}`);
|
|
17647
|
-
|
|
17705
|
+
log8?.warn(`[${PLUGIN_NAME11}] /remember failed`, { error: r.error });
|
|
17648
17706
|
}
|
|
17649
17707
|
return { kind: "remembered", result: r, mode: INJECTION_MODE2 };
|
|
17650
17708
|
}
|
|
@@ -17681,7 +17739,7 @@ function buildMemCfg(directory) {
|
|
|
17681
17739
|
return { projectRoot: directory };
|
|
17682
17740
|
}
|
|
17683
17741
|
var memoriesContextServer = async (ctx) => {
|
|
17684
|
-
const
|
|
17742
|
+
const log8 = makePluginLogger(PLUGIN_NAME11);
|
|
17685
17743
|
const memCfg = buildMemCfg(ctx.directory);
|
|
17686
17744
|
logLifecycle(PLUGIN_NAME11, "activate", {
|
|
17687
17745
|
directory: ctx.directory,
|
|
@@ -17698,19 +17756,19 @@ var memoriesContextServer = async (ctx) => {
|
|
|
17698
17756
|
content: text,
|
|
17699
17757
|
sessionId: input.sessionID,
|
|
17700
17758
|
injectContext: async (markdown) => {
|
|
17701
|
-
|
|
17759
|
+
log8.info(`memories context candidate (${markdown.length} chars)`, {
|
|
17702
17760
|
sessionID: input.sessionID,
|
|
17703
17761
|
mode: INJECTION_MODE2,
|
|
17704
17762
|
preview: markdown.slice(0, 200)
|
|
17705
17763
|
});
|
|
17706
17764
|
},
|
|
17707
17765
|
reply: async (msg) => {
|
|
17708
|
-
|
|
17766
|
+
log8.info(`directive reply (observe-only): ${msg}`, {
|
|
17709
17767
|
sessionID: input.sessionID,
|
|
17710
17768
|
mode: INJECTION_MODE2
|
|
17711
17769
|
});
|
|
17712
17770
|
},
|
|
17713
|
-
log:
|
|
17771
|
+
log: log8
|
|
17714
17772
|
}, { cache: sharedCache2, memCfg });
|
|
17715
17773
|
});
|
|
17716
17774
|
}
|
|
@@ -17785,7 +17843,7 @@ fallback 链已用尽:${meta.chain.join(" → ")}`;
|
|
|
17785
17843
|
完整链:${meta.chain.join(" → ")}`;
|
|
17786
17844
|
}
|
|
17787
17845
|
var modelFallbackServer = async (ctx) => {
|
|
17788
|
-
const
|
|
17846
|
+
const log8 = makePluginLogger(PLUGIN_NAME12);
|
|
17789
17847
|
loadOnce(ctx.directory ?? process.cwd());
|
|
17790
17848
|
logLifecycle(PLUGIN_NAME12, "activate", {
|
|
17791
17849
|
directory: ctx.directory,
|
|
@@ -17844,7 +17902,7 @@ var modelFallbackServer = async (ctx) => {
|
|
|
17844
17902
|
const model = props.model ?? "unknown/unknown";
|
|
17845
17903
|
const meta = buildFallbackMeta(state.config, agent, model);
|
|
17846
17904
|
const suggestion = meta ? buildSuggestion(meta, String(message)) : `⚠️ ${agent}/${model} 失败:${message}`;
|
|
17847
|
-
|
|
17905
|
+
log8.warn(`[${PLUGIN_NAME12}] ${suggestion}`);
|
|
17848
17906
|
safeWriteLog(PLUGIN_NAME12, {
|
|
17849
17907
|
hook: "event.error",
|
|
17850
17908
|
eventType: e.type,
|
|
@@ -17864,100 +17922,13 @@ import { promises as fsPromises } from "node:fs";
|
|
|
17864
17922
|
import * as path19 from "node:path";
|
|
17865
17923
|
init_runtime_paths();
|
|
17866
17924
|
init_global_config();
|
|
17867
|
-
|
|
17868
|
-
|
|
17869
|
-
|
|
17870
|
-
|
|
17871
|
-
|
|
17872
|
-
var
|
|
17873
|
-
var
|
|
17874
|
-
function parentMapDir(mainRoot) {
|
|
17875
|
-
return path18.join(runtimeDir(path18.resolve(mainRoot), { ensure: false }), "session-worktrees");
|
|
17876
|
-
}
|
|
17877
|
-
function parentMapPath(mainRoot) {
|
|
17878
|
-
return path18.join(parentMapDir(mainRoot), "parent-map.json");
|
|
17879
|
-
}
|
|
17880
|
-
function parentMapLockPath(mainRoot) {
|
|
17881
|
-
return path18.join(parentMapDir(mainRoot), "parent-map.lock");
|
|
17882
|
-
}
|
|
17883
|
-
async function readParentMapFile(mainRoot) {
|
|
17884
|
-
const file = parentMapPath(mainRoot);
|
|
17885
|
-
try {
|
|
17886
|
-
const raw = await fs15.readFile(file, "utf8");
|
|
17887
|
-
const parsed = JSON.parse(raw);
|
|
17888
|
-
if (parsed.version !== PARENT_MAP_VERSION || !Array.isArray(parsed.entries)) {
|
|
17889
|
-
return { version: PARENT_MAP_VERSION, entries: [] };
|
|
17890
|
-
}
|
|
17891
|
-
return parsed;
|
|
17892
|
-
} catch (err) {
|
|
17893
|
-
const e = err;
|
|
17894
|
-
if (e.code === "ENOENT")
|
|
17895
|
-
return { version: PARENT_MAP_VERSION, entries: [] };
|
|
17896
|
-
return { version: PARENT_MAP_VERSION, entries: [] };
|
|
17897
|
-
}
|
|
17898
|
-
}
|
|
17899
|
-
async function writeParentMapFile(mainRoot, payload) {
|
|
17900
|
-
const file = parentMapPath(mainRoot);
|
|
17901
|
-
await fs15.mkdir(path18.dirname(file), { recursive: true });
|
|
17902
|
-
const tmp = `${file}.tmp-${process.pid}-${Date.now()}`;
|
|
17903
|
-
await fs15.writeFile(tmp, JSON.stringify(payload, null, 2), "utf8");
|
|
17904
|
-
await fs15.rename(tmp, file);
|
|
17905
|
-
}
|
|
17906
|
-
async function loadParentMap(mainRoot) {
|
|
17907
|
-
const out = new Map;
|
|
17908
|
-
let file;
|
|
17909
|
-
try {
|
|
17910
|
-
file = await readParentMapFile(mainRoot);
|
|
17911
|
-
} catch {
|
|
17912
|
-
return out;
|
|
17913
|
-
}
|
|
17914
|
-
for (const e of file.entries) {
|
|
17915
|
-
if (!e || typeof e.childID !== "string" || typeof e.parentID !== "string")
|
|
17916
|
-
continue;
|
|
17917
|
-
if (!e.childID || !e.parentID)
|
|
17918
|
-
continue;
|
|
17919
|
-
const ts = typeof e.ts === "number" ? e.ts : Date.now();
|
|
17920
|
-
out.set(e.childID, { parentID: e.parentID, ts });
|
|
17921
|
-
}
|
|
17922
|
-
return out;
|
|
17923
|
-
}
|
|
17924
|
-
async function writeParentMap(mainRoot, snapshot, opts = {}) {
|
|
17925
|
-
const lockPath = parentMapLockPath(mainRoot);
|
|
17926
|
-
await fs15.mkdir(path18.dirname(lockPath), { recursive: true });
|
|
17927
|
-
const lockOpts = {
|
|
17928
|
-
timeoutMs: opts.timeoutMs ?? PARENT_MAP_LOCK_TIMEOUT_MS,
|
|
17929
|
-
...opts
|
|
17930
|
-
};
|
|
17931
|
-
await withFileLock(lockPath, async () => {
|
|
17932
|
-
const entries = [];
|
|
17933
|
-
for (const [childID, v] of snapshot.entries()) {
|
|
17934
|
-
entries.push({ childID, parentID: v.parentID, ts: v.ts });
|
|
17935
|
-
}
|
|
17936
|
-
await writeParentMapFile(mainRoot, { version: PARENT_MAP_VERSION, entries });
|
|
17937
|
-
}, lockOpts);
|
|
17938
|
-
}
|
|
17939
|
-
async function appendParentEntry(mainRoot, childID, parentID, ts, opts = {}) {
|
|
17940
|
-
if (!childID || !parentID)
|
|
17941
|
-
return;
|
|
17942
|
-
const lockPath = parentMapLockPath(mainRoot);
|
|
17943
|
-
await fs15.mkdir(path18.dirname(lockPath), { recursive: true });
|
|
17944
|
-
const lockOpts = {
|
|
17945
|
-
timeoutMs: opts.timeoutMs ?? PARENT_MAP_LOCK_TIMEOUT_MS,
|
|
17946
|
-
...opts
|
|
17947
|
-
};
|
|
17948
|
-
await withFileLock(lockPath, async () => {
|
|
17949
|
-
const current = await readParentMapFile(mainRoot);
|
|
17950
|
-
const idx = current.entries.findIndex((e) => e.childID === childID);
|
|
17951
|
-
if (idx >= 0) {
|
|
17952
|
-
current.entries[idx] = { childID, parentID, ts };
|
|
17953
|
-
} else {
|
|
17954
|
-
current.entries.push({ childID, parentID, ts });
|
|
17955
|
-
}
|
|
17956
|
-
await writeParentMapFile(mainRoot, current);
|
|
17957
|
-
}, lockOpts);
|
|
17958
|
-
}
|
|
17959
|
-
|
|
17960
|
-
// plugins/subtask-heartbeat.ts
|
|
17925
|
+
var recordSessionParent2 = recordSessionParent;
|
|
17926
|
+
var lookupParentSessionId2 = lookupParentSessionId;
|
|
17927
|
+
var deleteSessionParent2 = deleteSessionParent;
|
|
17928
|
+
var sweepExpiredSessionParents2 = sweepExpiredSessionParents;
|
|
17929
|
+
var _bulkInjectSessionParentMap2 = _bulkInjectSessionParentMap;
|
|
17930
|
+
var _capSessionParentMap2 = _capSessionParentMap;
|
|
17931
|
+
var _setPersistRootForTests2 = _setPersistRootForTests;
|
|
17961
17932
|
var PLUGIN_NAME13 = "subtask-heartbeat";
|
|
17962
17933
|
logLifecycle(PLUGIN_NAME13, "import", {});
|
|
17963
17934
|
var HEARTBEAT_INTERVAL_MS2 = 30000;
|
|
@@ -17968,94 +17939,13 @@ var PENDING_TASK_TTL_MS = 60000;
|
|
|
17968
17939
|
var PENDING_TASK_MAX_PARENTS = 64;
|
|
17969
17940
|
var PENDING_TASK_MAX_PER_PARENT = 16;
|
|
17970
17941
|
var DESCRIPTION_MAX_LEN = 60;
|
|
17971
|
-
var SESSION_PARENT_MAP_TTL_MS = 30 * 60000;
|
|
17972
|
-
var SESSION_PARENT_MAP_MAX_SIZE = 256;
|
|
17973
17942
|
var PARENT_PARSE_FAIL_MAX_LOG = 10;
|
|
17974
17943
|
var inflight3 = new Map;
|
|
17975
17944
|
var pendingTask = new Map;
|
|
17976
|
-
var sessionParentMap = new Map;
|
|
17977
17945
|
var _parentParseFailLogged = 0;
|
|
17978
|
-
var _persistRoot = null;
|
|
17979
|
-
function _bulkInjectSessionParentMap(entries) {
|
|
17980
|
-
for (const e of entries) {
|
|
17981
|
-
if (!e.childID || !e.parentID)
|
|
17982
|
-
continue;
|
|
17983
|
-
sessionParentMap.set(e.childID, { parentID: e.parentID, ts: e.ts });
|
|
17984
|
-
}
|
|
17985
|
-
}
|
|
17986
17946
|
function _snapshotInflight() {
|
|
17987
17947
|
return [...inflight3.values()].map((r) => ({ ...r }));
|
|
17988
17948
|
}
|
|
17989
|
-
function recordSessionParent(childID, parentID, now = Date.now()) {
|
|
17990
|
-
if (!childID || !parentID)
|
|
17991
|
-
return;
|
|
17992
|
-
if (sessionParentMap.has(childID)) {
|
|
17993
|
-
sessionParentMap.set(childID, { parentID, ts: now });
|
|
17994
|
-
} else {
|
|
17995
|
-
if (sessionParentMap.size >= SESSION_PARENT_MAP_MAX_SIZE) {
|
|
17996
|
-
let oldestKey = null;
|
|
17997
|
-
let oldestTs = Number.POSITIVE_INFINITY;
|
|
17998
|
-
for (const [k, v] of sessionParentMap.entries()) {
|
|
17999
|
-
if (v.ts < oldestTs) {
|
|
18000
|
-
oldestTs = v.ts;
|
|
18001
|
-
oldestKey = k;
|
|
18002
|
-
}
|
|
18003
|
-
}
|
|
18004
|
-
if (oldestKey !== null)
|
|
18005
|
-
sessionParentMap.delete(oldestKey);
|
|
18006
|
-
}
|
|
18007
|
-
sessionParentMap.set(childID, { parentID, ts: now });
|
|
18008
|
-
}
|
|
18009
|
-
if (_persistRoot) {
|
|
18010
|
-
appendParentEntry(_persistRoot, childID, parentID, now).catch((err) => {
|
|
18011
|
-
log7.warn("appendParentEntry 失败(已隔离)", {
|
|
18012
|
-
error: err instanceof Error ? err.message : String(err),
|
|
18013
|
-
childID
|
|
18014
|
-
});
|
|
18015
|
-
});
|
|
18016
|
-
}
|
|
18017
|
-
}
|
|
18018
|
-
function lookupParentSessionId(childID, now = Date.now()) {
|
|
18019
|
-
const entry = sessionParentMap.get(childID);
|
|
18020
|
-
if (!entry)
|
|
18021
|
-
return;
|
|
18022
|
-
if (now - entry.ts > SESSION_PARENT_MAP_TTL_MS) {
|
|
18023
|
-
sessionParentMap.delete(childID);
|
|
18024
|
-
return;
|
|
18025
|
-
}
|
|
18026
|
-
return entry.parentID;
|
|
18027
|
-
}
|
|
18028
|
-
function deleteSessionParent(childID) {
|
|
18029
|
-
sessionParentMap.delete(childID);
|
|
18030
|
-
if (_persistRoot) {
|
|
18031
|
-
const snapshot = new Map(sessionParentMap);
|
|
18032
|
-
writeParentMap(_persistRoot, snapshot).catch((err) => {
|
|
18033
|
-
log7.warn("writeParentMap (delete) 失败(已隔离)", {
|
|
18034
|
-
error: err instanceof Error ? err.message : String(err),
|
|
18035
|
-
childID
|
|
18036
|
-
});
|
|
18037
|
-
});
|
|
18038
|
-
}
|
|
18039
|
-
}
|
|
18040
|
-
function sweepExpiredSessionParents(now = Date.now()) {
|
|
18041
|
-
let removed = 0;
|
|
18042
|
-
for (const [k, v] of [...sessionParentMap.entries()]) {
|
|
18043
|
-
if (now - v.ts > SESSION_PARENT_MAP_TTL_MS) {
|
|
18044
|
-
sessionParentMap.delete(k);
|
|
18045
|
-
removed++;
|
|
18046
|
-
}
|
|
18047
|
-
}
|
|
18048
|
-
if (removed > 0 && _persistRoot) {
|
|
18049
|
-
const snapshot = new Map(sessionParentMap);
|
|
18050
|
-
writeParentMap(_persistRoot, snapshot).catch((err) => {
|
|
18051
|
-
log7.warn("writeParentMap (sweep) 失败(已隔离)", {
|
|
18052
|
-
error: err instanceof Error ? err.message : String(err),
|
|
18053
|
-
removed
|
|
18054
|
-
});
|
|
18055
|
-
});
|
|
18056
|
-
}
|
|
18057
|
-
return removed;
|
|
18058
|
-
}
|
|
18059
17949
|
function detectUnparsedParentID(event) {
|
|
18060
17950
|
if (!event || typeof event !== "object")
|
|
18061
17951
|
return false;
|
|
@@ -18329,33 +18219,13 @@ function buildAfterLogLine(toolName, ok, durationMs, now = Date.now()) {
|
|
|
18329
18219
|
duration_ms: durationMs
|
|
18330
18220
|
});
|
|
18331
18221
|
}
|
|
18332
|
-
function
|
|
18333
|
-
const agent = r.agent ? titleCase(r.agent) : "subagent";
|
|
18334
|
-
const elapsed = fmtElapsed(now - r.startedAt);
|
|
18335
|
-
return [`✅ ${agent} 完成(${elapsed})`, `\uD83D\uDCC4 完整日志: ${logPath}`].join(`
|
|
18336
|
-
`);
|
|
18337
|
-
}
|
|
18338
|
-
function buildFailureNotice(r, endedType, logPath, worktreePath, now = Date.now()) {
|
|
18339
|
-
const agent = r.agent ? titleCase(r.agent) : "subagent";
|
|
18340
|
-
const elapsed = fmtElapsed(now - r.startedAt);
|
|
18341
|
-
const verb = endedType === "session.deleted" ? "被取消" : "失败";
|
|
18342
|
-
const lines = [
|
|
18343
|
-
`❌ ${agent} ${verb}(${endedType}, ${elapsed})`,
|
|
18344
|
-
`\uD83D\uDCC4 完整日志: ${logPath}`
|
|
18345
|
-
];
|
|
18346
|
-
if (worktreePath)
|
|
18347
|
-
lines.push(`\uD83D\uDD0D worktree 保留: ${worktreePath}`);
|
|
18348
|
-
lines.push(`\uD83D\uDCA1 排查: cat ${logPath} | tail -50`);
|
|
18349
|
-
return lines.join(`
|
|
18350
|
-
`);
|
|
18351
|
-
}
|
|
18352
|
-
async function appendSubagentLog(filePath, line, log7) {
|
|
18222
|
+
async function appendSubagentLog(filePath, line, log8) {
|
|
18353
18223
|
try {
|
|
18354
18224
|
await fsPromises.mkdir(path19.dirname(filePath), { recursive: true });
|
|
18355
18225
|
await fsPromises.appendFile(filePath, line + `
|
|
18356
18226
|
`, "utf8");
|
|
18357
18227
|
} catch (err) {
|
|
18358
|
-
|
|
18228
|
+
log8?.debug?.("appendSubagentLog 失败(已隔离)", {
|
|
18359
18229
|
error: err instanceof Error ? err.message : String(err),
|
|
18360
18230
|
file: filePath
|
|
18361
18231
|
});
|
|
@@ -18380,9 +18250,9 @@ function normalizeVariant2(raw) {
|
|
|
18380
18250
|
return "default";
|
|
18381
18251
|
return raw;
|
|
18382
18252
|
}
|
|
18383
|
-
async function showToast2(client, payload,
|
|
18253
|
+
async function showToast2(client, payload, log8) {
|
|
18384
18254
|
if (typeof client?.tui?.showToast !== "function") {
|
|
18385
|
-
|
|
18255
|
+
log8?.debug?.("tui.showToast 不可用,noop");
|
|
18386
18256
|
return false;
|
|
18387
18257
|
}
|
|
18388
18258
|
try {
|
|
@@ -18396,13 +18266,13 @@ async function showToast2(client, payload, log7) {
|
|
|
18396
18266
|
});
|
|
18397
18267
|
return true;
|
|
18398
18268
|
} catch (err) {
|
|
18399
|
-
|
|
18269
|
+
log8?.warn("tui.showToast 抛错(已隔离)", {
|
|
18400
18270
|
error: err instanceof Error ? err.message : String(err)
|
|
18401
18271
|
});
|
|
18402
18272
|
return false;
|
|
18403
18273
|
}
|
|
18404
18274
|
}
|
|
18405
|
-
var
|
|
18275
|
+
var log8 = makePluginLogger(PLUGIN_NAME13);
|
|
18406
18276
|
var subtaskHeartbeatServer = async (ctx) => {
|
|
18407
18277
|
logLifecycle(PLUGIN_NAME13, "activate", {
|
|
18408
18278
|
directory: ctx.directory,
|
|
@@ -18410,7 +18280,7 @@ var subtaskHeartbeatServer = async (ctx) => {
|
|
|
18410
18280
|
});
|
|
18411
18281
|
const client = ctx.client;
|
|
18412
18282
|
const cwd = ctx.directory;
|
|
18413
|
-
|
|
18283
|
+
_setPersistRootForTests2(cwd);
|
|
18414
18284
|
try {
|
|
18415
18285
|
const restored = await loadParentMap(cwd);
|
|
18416
18286
|
if (restored.size > 0) {
|
|
@@ -18419,15 +18289,17 @@ var subtaskHeartbeatServer = async (ctx) => {
|
|
|
18419
18289
|
parentID: v.parentID,
|
|
18420
18290
|
ts: v.ts
|
|
18421
18291
|
}));
|
|
18422
|
-
|
|
18292
|
+
_bulkInjectSessionParentMap2(entries);
|
|
18293
|
+
const cappedOut = _capSessionParentMap2();
|
|
18423
18294
|
safeWriteLog(PLUGIN_NAME13, {
|
|
18424
18295
|
hook: "activate",
|
|
18425
18296
|
type: "parent-map.restore",
|
|
18426
|
-
restored: restored.size
|
|
18297
|
+
restored: restored.size,
|
|
18298
|
+
capped: cappedOut
|
|
18427
18299
|
});
|
|
18428
18300
|
}
|
|
18429
18301
|
} catch (err) {
|
|
18430
|
-
|
|
18302
|
+
log8.warn("loadParentMap 失败(已隔离),降级为空表", {
|
|
18431
18303
|
error: err instanceof Error ? err.message : String(err)
|
|
18432
18304
|
});
|
|
18433
18305
|
}
|
|
@@ -18437,7 +18309,7 @@ var subtaskHeartbeatServer = async (ctx) => {
|
|
|
18437
18309
|
if (swept > 0) {
|
|
18438
18310
|
safeWriteLog(PLUGIN_NAME13, { hook: "interval", pending_task_swept: swept });
|
|
18439
18311
|
}
|
|
18440
|
-
const sweptParents =
|
|
18312
|
+
const sweptParents = sweepExpiredSessionParents2();
|
|
18441
18313
|
if (sweptParents > 0) {
|
|
18442
18314
|
safeWriteLog(PLUGIN_NAME13, { hook: "interval", session_parent_swept: sweptParents });
|
|
18443
18315
|
}
|
|
@@ -18446,7 +18318,7 @@ var subtaskHeartbeatServer = async (ctx) => {
|
|
|
18446
18318
|
return;
|
|
18447
18319
|
for (const r of beats) {
|
|
18448
18320
|
const t = buildHeartbeatToast(r);
|
|
18449
|
-
const sent = await showToast2(client, t,
|
|
18321
|
+
const sent = await showToast2(client, t, log8);
|
|
18450
18322
|
safeWriteLog(PLUGIN_NAME13, {
|
|
18451
18323
|
hook: "interval",
|
|
18452
18324
|
child: r.childID,
|
|
@@ -18468,7 +18340,7 @@ var subtaskHeartbeatServer = async (ctx) => {
|
|
|
18468
18340
|
try {
|
|
18469
18341
|
if (detectUnparsedParentID(event) && _parentParseFailLogged < PARENT_PARSE_FAIL_MAX_LOG) {
|
|
18470
18342
|
_parentParseFailLogged++;
|
|
18471
|
-
|
|
18343
|
+
log8.warn("session.created 含 parentID 关键字但 extractCreatedChild 解析失败,可能 SDK schema 变更", {
|
|
18472
18344
|
sample_count: _parentParseFailLogged,
|
|
18473
18345
|
hint: "如频繁出现请检查 plugins/subtask-heartbeat.ts::extractCreatedChild 是否适配新 SDK"
|
|
18474
18346
|
});
|
|
@@ -18482,9 +18354,9 @@ var subtaskHeartbeatServer = async (ctx) => {
|
|
|
18482
18354
|
const created = extractCreatedChild(event);
|
|
18483
18355
|
if (created) {
|
|
18484
18356
|
try {
|
|
18485
|
-
|
|
18357
|
+
recordSessionParent2(created.childID, created.parentID);
|
|
18486
18358
|
} catch (err) {
|
|
18487
|
-
|
|
18359
|
+
log8.warn("recordSessionParent 抛错(已隔离)", {
|
|
18488
18360
|
error: err instanceof Error ? err.message : String(err)
|
|
18489
18361
|
});
|
|
18490
18362
|
}
|
|
@@ -18505,7 +18377,7 @@ var subtaskHeartbeatServer = async (ctx) => {
|
|
|
18505
18377
|
description_len: record.description?.length ?? 0
|
|
18506
18378
|
});
|
|
18507
18379
|
const startToast = buildStartToast(record);
|
|
18508
|
-
const sent = await showToast2(client, { ...startToast, duration: START_TOAST_DURATION_MS },
|
|
18380
|
+
const sent = await showToast2(client, { ...startToast, duration: START_TOAST_DURATION_MS }, log8);
|
|
18509
18381
|
safeWriteLog(PLUGIN_NAME13, {
|
|
18510
18382
|
hook: "event",
|
|
18511
18383
|
type: "session.created.toast",
|
|
@@ -18519,13 +18391,13 @@ var subtaskHeartbeatServer = async (ctx) => {
|
|
|
18519
18391
|
if (ended) {
|
|
18520
18392
|
if (ended.type === "session.deleted") {
|
|
18521
18393
|
try {
|
|
18522
|
-
|
|
18394
|
+
deleteSessionParent2(ended.sessionID);
|
|
18523
18395
|
} catch {}
|
|
18524
18396
|
}
|
|
18525
18397
|
const r = clearInflight2(ended.sessionID);
|
|
18526
18398
|
if (r) {
|
|
18527
18399
|
const t = buildEndToast(r, ended.type);
|
|
18528
|
-
const sent = await showToast2(client, t,
|
|
18400
|
+
const sent = await showToast2(client, t, log8);
|
|
18529
18401
|
safeWriteLog(PLUGIN_NAME13, {
|
|
18530
18402
|
hook: "event",
|
|
18531
18403
|
type: ended.type,
|
|
@@ -18534,32 +18406,10 @@ var subtaskHeartbeatServer = async (ctx) => {
|
|
|
18534
18406
|
toast_sent: sent,
|
|
18535
18407
|
end_toast_message: t.message
|
|
18536
18408
|
});
|
|
18537
|
-
|
|
18538
|
-
|
|
18539
|
-
|
|
18540
|
-
if (isSuccess) {
|
|
18541
|
-
text = buildSuccessNotice(r, logPath);
|
|
18542
|
-
} else {
|
|
18543
|
-
let worktreePath = null;
|
|
18544
|
-
try {
|
|
18545
|
-
const entry = await getSessionWorktree(r.childID, cwd);
|
|
18546
|
-
worktreePath = entry?.worktreePath ?? null;
|
|
18547
|
-
} catch {}
|
|
18548
|
-
text = buildFailureNotice(r, ended.type, logPath, worktreePath);
|
|
18409
|
+
if (ended.type === "session.deleted") {
|
|
18410
|
+
const logFile = subagentLogPath(cwd, r.parentID, r.childID);
|
|
18411
|
+
fsPromises.unlink(logFile).catch(() => {});
|
|
18549
18412
|
}
|
|
18550
|
-
const ocClient = ctx.client;
|
|
18551
|
-
const noticeSent = await sendParentNotice(ocClient, r.parentID, text, {
|
|
18552
|
-
directory: cwd,
|
|
18553
|
-
log: (lvl, msg, data) => log7[lvl === "info" ? "info" : "warn"](msg, data)
|
|
18554
|
-
});
|
|
18555
|
-
safeWriteLog(PLUGIN_NAME13, {
|
|
18556
|
-
hook: "event",
|
|
18557
|
-
type: `${ended.type}.notice`,
|
|
18558
|
-
child: r.childID,
|
|
18559
|
-
parent: r.parentID,
|
|
18560
|
-
notice_sent: noticeSent,
|
|
18561
|
-
success: isSuccess
|
|
18562
|
-
});
|
|
18563
18413
|
}
|
|
18564
18414
|
}
|
|
18565
18415
|
});
|
|
@@ -18605,7 +18455,7 @@ var subtaskHeartbeatServer = async (ctx) => {
|
|
|
18605
18455
|
const args = output?.args ?? null;
|
|
18606
18456
|
const line = buildBeforeLogLine(input.tool, args);
|
|
18607
18457
|
const file = subagentLogPath(cwd, rec.parentID, rec.childID);
|
|
18608
|
-
appendSubagentLog(file, line,
|
|
18458
|
+
appendSubagentLog(file, line, log8);
|
|
18609
18459
|
}
|
|
18610
18460
|
}
|
|
18611
18461
|
});
|
|
@@ -18630,7 +18480,7 @@ var subtaskHeartbeatServer = async (ctx) => {
|
|
|
18630
18480
|
if (isLogPersistenceEnabled(cwd)) {
|
|
18631
18481
|
const line = buildAfterLogLine(input.tool, true, durationMs);
|
|
18632
18482
|
const file = subagentLogPath(cwd, rec.parentID, rec.childID);
|
|
18633
|
-
appendSubagentLog(file, line,
|
|
18483
|
+
appendSubagentLog(file, line, log8);
|
|
18634
18484
|
}
|
|
18635
18485
|
});
|
|
18636
18486
|
}
|
|
@@ -18672,7 +18522,7 @@ function formatInflightMarkdown(snapshot, now = Date.now()) {
|
|
|
18672
18522
|
`);
|
|
18673
18523
|
}
|
|
18674
18524
|
var parallelStatusServer = async (ctx) => {
|
|
18675
|
-
const
|
|
18525
|
+
const log9 = makePluginLogger(PLUGIN_NAME14);
|
|
18676
18526
|
logLifecycle(PLUGIN_NAME14, "activate", { directory: ctx.directory });
|
|
18677
18527
|
return {
|
|
18678
18528
|
"command.execute.before": async (input, output) => {
|
|
@@ -18692,9 +18542,9 @@ var parallelStatusServer = async (ctx) => {
|
|
|
18692
18542
|
synthetic: false
|
|
18693
18543
|
});
|
|
18694
18544
|
}
|
|
18695
|
-
|
|
18545
|
+
log9.info(`[${PLUGIN_NAME14}] 已回写 ${snapshot.length} 条 inflight`);
|
|
18696
18546
|
} catch (err) {
|
|
18697
|
-
|
|
18547
|
+
log9.error(`[${PLUGIN_NAME14}] command.execute.before 异常(已隔离)`, {
|
|
18698
18548
|
error: err instanceof Error ? err.message : String(err)
|
|
18699
18549
|
});
|
|
18700
18550
|
}
|
|
@@ -18770,7 +18620,7 @@ function loadAgentToolsMap(rootDir, opts = {}) {
|
|
|
18770
18620
|
const result = new Map;
|
|
18771
18621
|
const safeSet = new Set(PARALLEL_SAFE_TOOLS);
|
|
18772
18622
|
const unionTools = new Set;
|
|
18773
|
-
const
|
|
18623
|
+
const log9 = makePluginLogger(PLUGIN_NAME15);
|
|
18774
18624
|
for (const dir of candidateDirs) {
|
|
18775
18625
|
if (!dirExists(dir))
|
|
18776
18626
|
continue;
|
|
@@ -18778,7 +18628,7 @@ function loadAgentToolsMap(rootDir, opts = {}) {
|
|
|
18778
18628
|
try {
|
|
18779
18629
|
entries = dirReader(dir);
|
|
18780
18630
|
} catch (err) {
|
|
18781
|
-
|
|
18631
|
+
log9.warn(`agents 目录读取失败(已跳过)`, {
|
|
18782
18632
|
dir,
|
|
18783
18633
|
error: err instanceof Error ? err.message : String(err)
|
|
18784
18634
|
});
|
|
@@ -18792,7 +18642,7 @@ function loadAgentToolsMap(rootDir, opts = {}) {
|
|
|
18792
18642
|
try {
|
|
18793
18643
|
content = reader(path20);
|
|
18794
18644
|
} catch (err) {
|
|
18795
|
-
|
|
18645
|
+
log9.warn(`agent.md 读取失败(已跳过)`, {
|
|
18796
18646
|
path: path20,
|
|
18797
18647
|
error: err instanceof Error ? err.message : String(err)
|
|
18798
18648
|
});
|
|
@@ -18800,7 +18650,7 @@ function loadAgentToolsMap(rootDir, opts = {}) {
|
|
|
18800
18650
|
}
|
|
18801
18651
|
const parsed = parseAgentFrontmatter(content);
|
|
18802
18652
|
if (!parsed) {
|
|
18803
|
-
|
|
18653
|
+
log9.warn(`agent frontmatter 解析失败(已跳过)`, { path: path20 });
|
|
18804
18654
|
continue;
|
|
18805
18655
|
}
|
|
18806
18656
|
if (result.has(parsed.name))
|
|
@@ -18878,7 +18728,7 @@ This directive is re-injected every turn — it is not optional advice.
|
|
|
18878
18728
|
return body.slice(0, NUDGE_MAX_LEN2 - 4) + `
|
|
18879
18729
|
…`;
|
|
18880
18730
|
}
|
|
18881
|
-
var
|
|
18731
|
+
var log9 = makePluginLogger(PLUGIN_NAME15);
|
|
18882
18732
|
var parallelToolNudgeServer = async (ctx) => {
|
|
18883
18733
|
try {
|
|
18884
18734
|
const loaded = loadAgentToolsMap(ctx.directory ?? process.cwd());
|
|
@@ -18886,14 +18736,14 @@ var parallelToolNudgeServer = async (ctx) => {
|
|
|
18886
18736
|
for (const [k, v] of loaded.entries())
|
|
18887
18737
|
agentToolsMap.set(k, v);
|
|
18888
18738
|
} catch (err) {
|
|
18889
|
-
|
|
18739
|
+
log9.warn(`loadAgentToolsMap 失败(plugin 将 no-op)`, {
|
|
18890
18740
|
error: err instanceof Error ? err.message : String(err)
|
|
18891
18741
|
});
|
|
18892
18742
|
}
|
|
18893
18743
|
const realAgentCount = Math.max(0, agentToolsMap.size - (agentToolsMap.has(DEFAULT_AGENT_KEY) ? 1 : 0));
|
|
18894
18744
|
if (realAgentCount === 0) {
|
|
18895
18745
|
agentToolsMap.clear();
|
|
18896
|
-
|
|
18746
|
+
log9.warn(`0 real agents loaded; plugin will be no-op for this session`, {
|
|
18897
18747
|
directory: ctx.directory
|
|
18898
18748
|
});
|
|
18899
18749
|
}
|
|
@@ -19486,7 +19336,7 @@ function renderPrompt(plan) {
|
|
|
19486
19336
|
return lines.join(`
|
|
19487
19337
|
`);
|
|
19488
19338
|
}
|
|
19489
|
-
var
|
|
19339
|
+
var log10 = makePluginLogger(PLUGIN_NAME17);
|
|
19490
19340
|
var _lastInjection = null;
|
|
19491
19341
|
var sessionRecoveryServer = async (ctx) => {
|
|
19492
19342
|
logLifecycle(PLUGIN_NAME17, "activate", { directory: ctx.directory });
|
|
@@ -19502,7 +19352,7 @@ var sessionRecoveryServer = async (ctx) => {
|
|
|
19502
19352
|
const root = typeof e.properties?.["root"] === "string" ? e.properties["root"] : ctx.directory ?? process.cwd();
|
|
19503
19353
|
const r = await processSessionStart(sid, {
|
|
19504
19354
|
root,
|
|
19505
|
-
log:
|
|
19355
|
+
log: log10,
|
|
19506
19356
|
injectRecovery: (inj) => {
|
|
19507
19357
|
_lastInjection = inj;
|
|
19508
19358
|
}
|
|
@@ -19517,7 +19367,7 @@ var sessionRecoveryServer = async (ctx) => {
|
|
|
19517
19367
|
pending_blocks_count: r.pendingBlocks?.length ?? 0
|
|
19518
19368
|
});
|
|
19519
19369
|
if (r.injected && r.plan) {
|
|
19520
|
-
|
|
19370
|
+
log10.info(`[${PLUGIN_NAME17}] 注入恢复提示(last=${r.plan.last_session_id?.slice(0, 8) ?? "?"}, reason=${r.plan.reason}, pending_blocks=${r.pendingBlocks?.length ?? 0})`);
|
|
19521
19371
|
}
|
|
19522
19372
|
});
|
|
19523
19373
|
}
|
|
@@ -19530,7 +19380,7 @@ import { promises as fs18 } from "node:fs";
|
|
|
19530
19380
|
import * as path22 from "node:path";
|
|
19531
19381
|
|
|
19532
19382
|
// lib/parallel-merge.ts
|
|
19533
|
-
|
|
19383
|
+
init_worktree_ops();
|
|
19534
19384
|
function resolveMergeFns(deps) {
|
|
19535
19385
|
return {
|
|
19536
19386
|
commitWorktreeIfDirtyFn: deps.commitWorktreeIfDirty ?? commitWorktreeIfDirty,
|
|
@@ -19541,7 +19391,7 @@ function resolveMergeFns(deps) {
|
|
|
19541
19391
|
worktreeHasChangesFn: deps.worktreeHasChanges ?? worktreeHasChanges
|
|
19542
19392
|
};
|
|
19543
19393
|
}
|
|
19544
|
-
async function mergeOneAttempt(r, opts, mergeFns,
|
|
19394
|
+
async function mergeOneAttempt(r, opts, mergeFns, log11) {
|
|
19545
19395
|
const t0 = Date.now();
|
|
19546
19396
|
const root = opts.mergeRoot;
|
|
19547
19397
|
const {
|
|
@@ -19623,7 +19473,7 @@ async function mergeOneAttempt(r, opts, mergeFns, log10) {
|
|
|
19623
19473
|
await mergeAbortFn({ root });
|
|
19624
19474
|
} catch (abortErr) {
|
|
19625
19475
|
abortFailed = true;
|
|
19626
|
-
|
|
19476
|
+
log11("error", `[parallel-merge] mergeAbort 失败(仓库可能锁死)`, {
|
|
19627
19477
|
id: r.id,
|
|
19628
19478
|
error: describe6(abortErr)
|
|
19629
19479
|
});
|
|
@@ -19639,13 +19489,13 @@ async function mergeOneAttempt(r, opts, mergeFns, log10) {
|
|
|
19639
19489
|
}
|
|
19640
19490
|
attempt.ok = true;
|
|
19641
19491
|
attempt.durationMs = Date.now() - t0;
|
|
19642
|
-
await safeRemoveWorktree(removeWorktreeFn, root, wt,
|
|
19492
|
+
await safeRemoveWorktree(removeWorktreeFn, root, wt, log11, r.id);
|
|
19643
19493
|
return { attempt };
|
|
19644
19494
|
} else {
|
|
19645
19495
|
attempt.ok = true;
|
|
19646
19496
|
attempt.skippedReason = "no_changes";
|
|
19647
19497
|
attempt.durationMs = Date.now() - t0;
|
|
19648
|
-
await safeRemoveWorktree(removeWorktreeFn, root, wt,
|
|
19498
|
+
await safeRemoveWorktree(removeWorktreeFn, root, wt, log11, r.id);
|
|
19649
19499
|
return { attempt };
|
|
19650
19500
|
}
|
|
19651
19501
|
} catch (err) {
|
|
@@ -19659,7 +19509,7 @@ async function mergeOneAttempt(r, opts, mergeFns, log10) {
|
|
|
19659
19509
|
}
|
|
19660
19510
|
}
|
|
19661
19511
|
async function mergeWorktrees(results, opts, deps) {
|
|
19662
|
-
const
|
|
19512
|
+
const log11 = deps.log ?? (() => {});
|
|
19663
19513
|
const root = opts.mergeRoot;
|
|
19664
19514
|
const mergeFns = resolveMergeFns(deps);
|
|
19665
19515
|
const report = {
|
|
@@ -19701,10 +19551,10 @@ async function mergeWorktrees(results, opts, deps) {
|
|
|
19701
19551
|
};
|
|
19702
19552
|
report.attempts.push(attempt);
|
|
19703
19553
|
report.skipped++;
|
|
19704
|
-
await fireMergeAttempt(opts.onMergeAttempt, attempt, idx++,
|
|
19554
|
+
await fireMergeAttempt(opts.onMergeAttempt, attempt, idx++, log11);
|
|
19705
19555
|
}
|
|
19706
19556
|
for (const r of mergeable) {
|
|
19707
|
-
const { attempt, pendingItem } = await mergeOneAttempt(r, { mergeRoot: root }, mergeFns,
|
|
19557
|
+
const { attempt, pendingItem } = await mergeOneAttempt(r, { mergeRoot: root }, mergeFns, log11);
|
|
19708
19558
|
report.attempts.push(attempt);
|
|
19709
19559
|
if (attempt.ok && !attempt.skippedReason)
|
|
19710
19560
|
report.merged++;
|
|
@@ -19714,12 +19564,12 @@ async function mergeWorktrees(results, opts, deps) {
|
|
|
19714
19564
|
report.conflicted++;
|
|
19715
19565
|
if (pendingItem)
|
|
19716
19566
|
report.pendingWorktrees.push(pendingItem);
|
|
19717
|
-
await fireMergeAttempt(opts.onMergeAttempt, attempt, idx++,
|
|
19567
|
+
await fireMergeAttempt(opts.onMergeAttempt, attempt, idx++, log11);
|
|
19718
19568
|
}
|
|
19719
19569
|
return report;
|
|
19720
19570
|
}
|
|
19721
19571
|
function createMergeQueue(opts, deps = {}) {
|
|
19722
|
-
const
|
|
19572
|
+
const log11 = deps.log ?? (() => {});
|
|
19723
19573
|
const mergeFns = {
|
|
19724
19574
|
commitWorktreeIfDirtyFn: deps.commitWorktreeIfDirtyFn ?? commitWorktreeIfDirty,
|
|
19725
19575
|
tryMergeFn: deps.tryMergeFn ?? tryMerge,
|
|
@@ -19760,10 +19610,10 @@ function createMergeQueue(opts, deps = {}) {
|
|
|
19760
19610
|
conflicts: []
|
|
19761
19611
|
});
|
|
19762
19612
|
}
|
|
19763
|
-
await fireMergeAttempt(opts.onMergeAttempt, attempt2, idx++,
|
|
19613
|
+
await fireMergeAttempt(opts.onMergeAttempt, attempt2, idx++, log11);
|
|
19764
19614
|
return;
|
|
19765
19615
|
}
|
|
19766
|
-
const { attempt, abortFailed, pendingItem } = await mergeOneAttempt(result, { mergeRoot: opts.mergeRoot, summaryCharLimit: opts.summaryCharLimit }, mergeFns,
|
|
19616
|
+
const { attempt, abortFailed, pendingItem } = await mergeOneAttempt(result, { mergeRoot: opts.mergeRoot, summaryCharLimit: opts.summaryCharLimit }, mergeFns, log11);
|
|
19767
19617
|
report.attempts.push(attempt);
|
|
19768
19618
|
if (attempt.ok && !attempt.skippedReason)
|
|
19769
19619
|
report.merged++;
|
|
@@ -19775,11 +19625,11 @@ function createMergeQueue(opts, deps = {}) {
|
|
|
19775
19625
|
report.pendingWorktrees.push(pendingItem);
|
|
19776
19626
|
if (abortFailed) {
|
|
19777
19627
|
queueAborted = true;
|
|
19778
|
-
|
|
19628
|
+
log11("error", `[parallel-merge] queue aborted (mergeAbort failed)`, {
|
|
19779
19629
|
id: result.id
|
|
19780
19630
|
});
|
|
19781
19631
|
}
|
|
19782
|
-
await fireMergeAttempt(opts.onMergeAttempt, attempt, idx++,
|
|
19632
|
+
await fireMergeAttempt(opts.onMergeAttempt, attempt, idx++, log11);
|
|
19783
19633
|
});
|
|
19784
19634
|
},
|
|
19785
19635
|
async flushAndReport() {
|
|
@@ -19788,23 +19638,23 @@ function createMergeQueue(opts, deps = {}) {
|
|
|
19788
19638
|
}
|
|
19789
19639
|
};
|
|
19790
19640
|
}
|
|
19791
|
-
async function safeRemoveWorktree(fn, root, wt,
|
|
19641
|
+
async function safeRemoveWorktree(fn, root, wt, log11, subtaskId) {
|
|
19792
19642
|
try {
|
|
19793
19643
|
await fn({ root, worktree_path: wt, force: true });
|
|
19794
19644
|
} catch (err) {
|
|
19795
|
-
|
|
19645
|
+
log11("warn", `[parallel] removeWorktree 失败 ${subtaskId}`, {
|
|
19796
19646
|
error: describe6(err),
|
|
19797
19647
|
worktree: wt
|
|
19798
19648
|
});
|
|
19799
19649
|
}
|
|
19800
19650
|
}
|
|
19801
|
-
async function fireMergeAttempt(cb, attempt, idx,
|
|
19651
|
+
async function fireMergeAttempt(cb, attempt, idx, log11) {
|
|
19802
19652
|
if (!cb)
|
|
19803
19653
|
return;
|
|
19804
19654
|
try {
|
|
19805
19655
|
await cb(attempt, idx);
|
|
19806
19656
|
} catch (err) {
|
|
19807
|
-
|
|
19657
|
+
log11("warn", `[parallel] onMergeAttempt 抛错(已隔离)`, {
|
|
19808
19658
|
id: attempt.subtaskId,
|
|
19809
19659
|
error: describe6(err)
|
|
19810
19660
|
});
|
|
@@ -19830,7 +19680,7 @@ async function schedule(opts) {
|
|
|
19830
19680
|
}
|
|
19831
19681
|
const now = opts.deps.now ?? Date.now;
|
|
19832
19682
|
const start = now();
|
|
19833
|
-
const
|
|
19683
|
+
const log11 = opts.deps.log ?? (() => {});
|
|
19834
19684
|
const concurrency = Math.max(1, opts.maxConcurrency ?? 4);
|
|
19835
19685
|
const totalTimeout = opts.totalTimeout_ms ?? 30 * 60000;
|
|
19836
19686
|
const limit = Math.max(1, opts.summaryCharLimit ?? 500);
|
|
@@ -19863,7 +19713,7 @@ async function schedule(opts) {
|
|
|
19863
19713
|
mergeAbortFn: opts.deps.mergeAbort,
|
|
19864
19714
|
removeWorktreeFn: opts.deps.removeWorktree,
|
|
19865
19715
|
worktreeHasChangesFn: opts.deps.worktreeHasChanges,
|
|
19866
|
-
log:
|
|
19716
|
+
log: log11
|
|
19867
19717
|
});
|
|
19868
19718
|
}
|
|
19869
19719
|
const fireFinish = async (i, res) => {
|
|
@@ -19871,7 +19721,7 @@ async function schedule(opts) {
|
|
|
19871
19721
|
try {
|
|
19872
19722
|
queue.enqueue(res);
|
|
19873
19723
|
} catch (err) {
|
|
19874
|
-
|
|
19724
|
+
log11("warn", `[parallel] queue.enqueue 抛错(已隔离)`, {
|
|
19875
19725
|
id: res.id,
|
|
19876
19726
|
error: describe7(err)
|
|
19877
19727
|
});
|
|
@@ -19882,7 +19732,7 @@ async function schedule(opts) {
|
|
|
19882
19732
|
try {
|
|
19883
19733
|
await opts.onSubtaskFinish(res, i);
|
|
19884
19734
|
} catch (err) {
|
|
19885
|
-
|
|
19735
|
+
log11("warn", `[parallel] onSubtaskFinish 抛错(已隔离)`, {
|
|
19886
19736
|
id: res.id,
|
|
19887
19737
|
error: describe7(err)
|
|
19888
19738
|
});
|
|
@@ -19912,7 +19762,7 @@ async function schedule(opts) {
|
|
|
19912
19762
|
try {
|
|
19913
19763
|
await opts.onSubtaskStart(spec, i);
|
|
19914
19764
|
} catch (err) {
|
|
19915
|
-
|
|
19765
|
+
log11("warn", `[parallel] onSubtaskStart 抛错(已隔离)`, {
|
|
19916
19766
|
id: spec.id,
|
|
19917
19767
|
error: describe7(err)
|
|
19918
19768
|
});
|
|
@@ -19963,7 +19813,7 @@ async function schedule(opts) {
|
|
|
19963
19813
|
try {
|
|
19964
19814
|
await alloc.cleanup();
|
|
19965
19815
|
} catch (err) {
|
|
19966
|
-
|
|
19816
|
+
log11("warn", `[parallel] worktree 清理失败 ${spec.id}`, {
|
|
19967
19817
|
error: describe7(err)
|
|
19968
19818
|
});
|
|
19969
19819
|
}
|
|
@@ -20121,7 +19971,7 @@ function pickStatus(r, taskAborted, globalAborted) {
|
|
|
20121
19971
|
}
|
|
20122
19972
|
|
|
20123
19973
|
// plugins/subtasks.ts
|
|
20124
|
-
|
|
19974
|
+
init_worktree_ops();
|
|
20125
19975
|
|
|
20126
19976
|
// lib/decompose.ts
|
|
20127
19977
|
var DEFAULT_MAX_SUBTASKS = 8;
|
|
@@ -20143,9 +19993,9 @@ function buildSystemPrompt(maxSubtasks) {
|
|
|
20143
19993
|
`);
|
|
20144
19994
|
}
|
|
20145
19995
|
async function decomposeTask(description29, opts) {
|
|
20146
|
-
const
|
|
19996
|
+
const log11 = opts.log ?? (() => {});
|
|
20147
19997
|
if (opts.mockResponse) {
|
|
20148
|
-
return validateAndFinalize(opts.mockResponse, undefined,
|
|
19998
|
+
return validateAndFinalize(opts.mockResponse, undefined, log11, opts.maxSubtasks ?? DEFAULT_MAX_SUBTASKS);
|
|
20149
19999
|
}
|
|
20150
20000
|
const maxSubtasks = opts.maxSubtasks ?? DEFAULT_MAX_SUBTASKS;
|
|
20151
20001
|
const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS2;
|
|
@@ -20156,7 +20006,7 @@ async function decomposeTask(description29, opts) {
|
|
|
20156
20006
|
query: opts.directory ? { directory: opts.directory } : undefined
|
|
20157
20007
|
});
|
|
20158
20008
|
if (created.error || !created.data?.id) {
|
|
20159
|
-
|
|
20009
|
+
log11("warn", "[decompose] session.create 失败", { error: describe8(created.error) });
|
|
20160
20010
|
return {
|
|
20161
20011
|
ok: false,
|
|
20162
20012
|
subtasks: [],
|
|
@@ -20178,22 +20028,22 @@ async function decomposeTask(description29, opts) {
|
|
|
20178
20028
|
sleep2(timeoutMs).then(() => ({ kind: "timeout" }))
|
|
20179
20029
|
]);
|
|
20180
20030
|
if (raced.kind === "timeout") {
|
|
20181
|
-
|
|
20031
|
+
log11("warn", "[decompose] LLM 调用超时", { timeoutMs });
|
|
20182
20032
|
return { ok: false, subtasks: [], reason: "llm_unavailable" };
|
|
20183
20033
|
}
|
|
20184
20034
|
const r = raced.value;
|
|
20185
20035
|
if (r.error || !r.data) {
|
|
20186
|
-
|
|
20036
|
+
log11("warn", "[decompose] session.prompt 返回错误", { error: describe8(r.error) });
|
|
20187
20037
|
return { ok: false, subtasks: [], reason: "llm_unavailable" };
|
|
20188
20038
|
}
|
|
20189
20039
|
const rawText = pickLastText2(r.data.parts ?? []);
|
|
20190
20040
|
if (!rawText) {
|
|
20191
|
-
|
|
20041
|
+
log11("warn", "[decompose] LLM 输出为空");
|
|
20192
20042
|
return { ok: false, subtasks: [], reason: "parse_failed", raw: "" };
|
|
20193
20043
|
}
|
|
20194
20044
|
const parsed = extractJson(rawText);
|
|
20195
20045
|
if (!parsed) {
|
|
20196
|
-
|
|
20046
|
+
log11("warn", "[decompose] JSON 解析失败", { raw: clip6(rawText, 200) });
|
|
20197
20047
|
return { ok: false, subtasks: [], reason: "parse_failed", raw: rawText };
|
|
20198
20048
|
}
|
|
20199
20049
|
if (parsed && typeof parsed === "object" && parsed.single_task === true) {
|
|
@@ -20201,7 +20051,7 @@ async function decomposeTask(description29, opts) {
|
|
|
20201
20051
|
}
|
|
20202
20052
|
const subtasksRaw = parsed.subtasks;
|
|
20203
20053
|
if (!Array.isArray(subtasksRaw)) {
|
|
20204
|
-
|
|
20054
|
+
log11("warn", "[decompose] JSON 缺 subtasks 数组");
|
|
20205
20055
|
return { ok: false, subtasks: [], reason: "parse_failed", raw: rawText };
|
|
20206
20056
|
}
|
|
20207
20057
|
const normalized = [];
|
|
@@ -20217,12 +20067,12 @@ async function decomposeTask(description29, opts) {
|
|
|
20217
20067
|
normalized.push({ description: desc, hintFiles });
|
|
20218
20068
|
}
|
|
20219
20069
|
if (normalized.length > maxSubtasks) {
|
|
20220
|
-
|
|
20070
|
+
log11("warn", "[decompose] LLM 返回子任务超过上限", { count: normalized.length, maxSubtasks });
|
|
20221
20071
|
return { ok: false, subtasks: [], reason: "parse_failed", raw: rawText };
|
|
20222
20072
|
}
|
|
20223
|
-
return validateAndFinalize(normalized, rawText,
|
|
20073
|
+
return validateAndFinalize(normalized, rawText, log11, maxSubtasks);
|
|
20224
20074
|
} catch (err) {
|
|
20225
|
-
|
|
20075
|
+
log11("warn", "[decompose] 抛错", { error: describe8(err) });
|
|
20226
20076
|
return { ok: false, subtasks: [], reason: "llm_unavailable" };
|
|
20227
20077
|
} finally {
|
|
20228
20078
|
if (childSessionId) {
|
|
@@ -20232,12 +20082,12 @@ async function decomposeTask(description29, opts) {
|
|
|
20232
20082
|
query: opts.directory ? { directory: opts.directory } : undefined
|
|
20233
20083
|
});
|
|
20234
20084
|
} catch (err) {
|
|
20235
|
-
|
|
20085
|
+
log11("warn", "[decompose] session.delete 失败", { error: describe8(err) });
|
|
20236
20086
|
}
|
|
20237
20087
|
}
|
|
20238
20088
|
}
|
|
20239
20089
|
}
|
|
20240
|
-
function validateAndFinalize(subtasks, raw,
|
|
20090
|
+
function validateAndFinalize(subtasks, raw, log11, maxSubtasks) {
|
|
20241
20091
|
if (subtasks.length < 2) {
|
|
20242
20092
|
return { ok: false, subtasks: [], reason: "single_task", raw };
|
|
20243
20093
|
}
|
|
@@ -20247,7 +20097,7 @@ function validateAndFinalize(subtasks, raw, log10, maxSubtasks) {
|
|
|
20247
20097
|
}
|
|
20248
20098
|
}
|
|
20249
20099
|
if (subtasks.length > maxSubtasks) {
|
|
20250
|
-
|
|
20100
|
+
log11("warn", "[decompose] LLM 返回子任务超过上限", { count: subtasks.length, maxSubtasks });
|
|
20251
20101
|
return { ok: false, subtasks: [], reason: "parse_failed", raw };
|
|
20252
20102
|
}
|
|
20253
20103
|
for (let i = 0;i < subtasks.length; i++) {
|
|
@@ -20261,7 +20111,7 @@ function validateAndFinalize(subtasks, raw, log10, maxSubtasks) {
|
|
|
20261
20111
|
continue;
|
|
20262
20112
|
for (const f of b) {
|
|
20263
20113
|
if (setA.has(f.trim())) {
|
|
20264
|
-
|
|
20114
|
+
log11("info", "[decompose] hintFiles 有交集,降级 single_task", {
|
|
20265
20115
|
a: subtasks[i].description,
|
|
20266
20116
|
b: subtasks[j].description,
|
|
20267
20117
|
file: f
|
|
@@ -20416,7 +20266,7 @@ var mockRunner = async (spec) => {
|
|
|
20416
20266
|
};
|
|
20417
20267
|
async function handleParallelCommand(raw) {
|
|
20418
20268
|
const ctx = raw ?? {};
|
|
20419
|
-
const
|
|
20269
|
+
const log11 = ctx.log;
|
|
20420
20270
|
const args = ctx.args ?? {};
|
|
20421
20271
|
const parentId = (args.parentId ?? `task-${Date.now().toString(36)}`).replace(/[^a-zA-Z0-9._-]/g, "-");
|
|
20422
20272
|
const rawDescription = typeof args.description === "string" ? args.description.trim() : "";
|
|
@@ -20430,7 +20280,7 @@ async function handleParallelCommand(raw) {
|
|
|
20430
20280
|
specs = splitDescriptions(args.description, { parentId });
|
|
20431
20281
|
}
|
|
20432
20282
|
if (specs.length === 0) {
|
|
20433
|
-
|
|
20283
|
+
log11?.warn(`[${PLUGIN_NAME18}] /parallel 缺有效子任务`);
|
|
20434
20284
|
await safeReply2(ctx, "⚠ /parallel 需要至少 1 个子任务(用 ; 或换行分隔)");
|
|
20435
20285
|
return { ok: false, reason: "no_subtasks" };
|
|
20436
20286
|
}
|
|
@@ -20456,7 +20306,7 @@ async function handleParallelCommand(raw) {
|
|
|
20456
20306
|
if (!gitOk) {
|
|
20457
20307
|
autoMerge = false;
|
|
20458
20308
|
downgradedAutoMerge = true;
|
|
20459
|
-
|
|
20309
|
+
log11?.warn("[subtasks] mergeRoot 非 git 仓库,降级 autoMerge=false");
|
|
20460
20310
|
const canNoticeEarly = Boolean(ctx.client && ctx.parentSessionID);
|
|
20461
20311
|
if (canNoticeEarly) {
|
|
20462
20312
|
await sendParentNotice(ctx.client, ctx.parentSessionID, "ℹ 当前目录不是 git 仓库,worktree 隔离已自动关闭,以单目录模式运行", { directory: ctx.directory });
|
|
@@ -20465,17 +20315,17 @@ async function handleParallelCommand(raw) {
|
|
|
20465
20315
|
}
|
|
20466
20316
|
const canNotice = Boolean(ctx.client && ctx.parentSessionID);
|
|
20467
20317
|
if (specs.length === 1 && rawDescription.length >= 30 && ctx.client) {
|
|
20468
|
-
|
|
20318
|
+
log11?.info(`[${PLUGIN_NAME18}] 触发 AI 拆分(描述长度=${rawDescription.length})`);
|
|
20469
20319
|
const dec = await decomposeTask(rawDescription, {
|
|
20470
20320
|
client: ctx.client,
|
|
20471
20321
|
directory: ctx.directory,
|
|
20472
20322
|
log: (lvl, msg, data) => {
|
|
20473
20323
|
if (lvl === "error")
|
|
20474
|
-
|
|
20324
|
+
log11?.error(msg, data);
|
|
20475
20325
|
else if (lvl === "warn")
|
|
20476
|
-
|
|
20326
|
+
log11?.warn(msg, data);
|
|
20477
20327
|
else
|
|
20478
|
-
|
|
20328
|
+
log11?.info(msg, data);
|
|
20479
20329
|
},
|
|
20480
20330
|
mockResponse: ctx.decomposeMockResponse
|
|
20481
20331
|
});
|
|
@@ -20486,7 +20336,7 @@ async function handleParallelCommand(raw) {
|
|
|
20486
20336
|
timeout_ms: undefined,
|
|
20487
20337
|
args: d.hintFiles && d.hintFiles.length > 0 ? { hintFiles: d.hintFiles } : undefined
|
|
20488
20338
|
}));
|
|
20489
|
-
|
|
20339
|
+
log11?.info(`[${PLUGIN_NAME18}] AI 拆分成功 → ${specs.length} 个子任务`);
|
|
20490
20340
|
if (canNotice) {
|
|
20491
20341
|
const lines = [
|
|
20492
20342
|
`\uD83E\uDD16 AI 已自动拆为 ${specs.length} 个并行子任务:`,
|
|
@@ -20498,7 +20348,7 @@ async function handleParallelCommand(raw) {
|
|
|
20498
20348
|
});
|
|
20499
20349
|
}
|
|
20500
20350
|
} else if (dec.reason === "llm_unavailable" || dec.reason === "parse_failed") {
|
|
20501
|
-
|
|
20351
|
+
log11?.warn(`[${PLUGIN_NAME18}] AI 拆分失败(${dec.reason}),按单任务执行`);
|
|
20502
20352
|
}
|
|
20503
20353
|
}
|
|
20504
20354
|
if (canNotice) {
|
|
@@ -20513,11 +20363,11 @@ async function handleParallelCommand(raw) {
|
|
|
20513
20363
|
directory: ctx.directory,
|
|
20514
20364
|
log: (lvl, msg, data) => {
|
|
20515
20365
|
if (lvl === "error")
|
|
20516
|
-
|
|
20366
|
+
log11?.error(msg, data);
|
|
20517
20367
|
else if (lvl === "warn")
|
|
20518
|
-
|
|
20368
|
+
log11?.warn(msg, data);
|
|
20519
20369
|
else
|
|
20520
|
-
|
|
20370
|
+
log11?.info(msg, data);
|
|
20521
20371
|
}
|
|
20522
20372
|
});
|
|
20523
20373
|
}
|
|
@@ -20561,11 +20411,11 @@ async function handleParallelCommand(raw) {
|
|
|
20561
20411
|
allocateWorktree: downgradedAutoMerge ? undefined : ctx.allocateWorktree,
|
|
20562
20412
|
log: (lvl, msg, data) => {
|
|
20563
20413
|
if (lvl === "error")
|
|
20564
|
-
|
|
20414
|
+
log11?.error(msg, data);
|
|
20565
20415
|
else if (lvl === "warn")
|
|
20566
|
-
|
|
20416
|
+
log11?.warn(msg, data);
|
|
20567
20417
|
else
|
|
20568
|
-
|
|
20418
|
+
log11?.info(msg, data);
|
|
20569
20419
|
},
|
|
20570
20420
|
tryMerge: ctx.tryMerge,
|
|
20571
20421
|
mergeCommit: ctx.mergeCommit,
|
|
@@ -20577,7 +20427,7 @@ async function handleParallelCommand(raw) {
|
|
|
20577
20427
|
});
|
|
20578
20428
|
} catch (err) {
|
|
20579
20429
|
const msg = err instanceof Error ? err.message : String(err);
|
|
20580
|
-
|
|
20430
|
+
log11?.error(`[${PLUGIN_NAME18}] schedule 抛错`, { error: msg });
|
|
20581
20431
|
await safeReply2(ctx, `❌ 并发调度失败:${msg}`);
|
|
20582
20432
|
return { ok: false, reason: msg };
|
|
20583
20433
|
}
|
|
@@ -20595,7 +20445,7 @@ async function handleParallelCommand(raw) {
|
|
|
20595
20445
|
}
|
|
20596
20446
|
await safeReply2(ctx, summaryLines.join(`
|
|
20597
20447
|
`));
|
|
20598
|
-
|
|
20448
|
+
log11?.info(`[${PLUGIN_NAME18}] schedule 完成`, {
|
|
20599
20449
|
parentId,
|
|
20600
20450
|
success: result.digest.success,
|
|
20601
20451
|
failed: result.digest.failed,
|
|
@@ -20608,7 +20458,7 @@ async function handleParallelCommand(raw) {
|
|
|
20608
20458
|
try {
|
|
20609
20459
|
await ctx.onCompleted(result);
|
|
20610
20460
|
} catch (err) {
|
|
20611
|
-
|
|
20461
|
+
log11?.warn(`[${PLUGIN_NAME18}] onCompleted hook 抛错(已隔离)`, {
|
|
20612
20462
|
error: err instanceof Error ? err.message : String(err)
|
|
20613
20463
|
});
|
|
20614
20464
|
}
|
|
@@ -20663,7 +20513,7 @@ async function writeLog(level, msg, data) {
|
|
|
20663
20513
|
}
|
|
20664
20514
|
logLifecycle(PLUGIN_NAME18, "import");
|
|
20665
20515
|
var subtasksServer = async (ctx) => {
|
|
20666
|
-
const
|
|
20516
|
+
const log11 = makePluginLogger(PLUGIN_NAME18);
|
|
20667
20517
|
const client = ctx?.client ?? undefined;
|
|
20668
20518
|
logLifecycle(PLUGIN_NAME18, "activate", {
|
|
20669
20519
|
directory: ctx.directory,
|
|
@@ -20690,7 +20540,7 @@ var subtasksServer = async (ctx) => {
|
|
|
20690
20540
|
const gitOk = await isGitRepo({ root: repoRoot }).catch(() => false);
|
|
20691
20541
|
if (!gitOk) {
|
|
20692
20542
|
autoMerge = false;
|
|
20693
|
-
|
|
20543
|
+
log11?.warn("[subtasks] worktree_isolation=true 但非 git 仓库,自动降级 autoMerge=false");
|
|
20694
20544
|
const canNotice = Boolean(client);
|
|
20695
20545
|
if (canNotice) {
|
|
20696
20546
|
await sendParentNotice(client, input.sessionID, "ℹ 当前目录不是 git 仓库,worktree 隔离已自动关闭,以单目录模式运行", { directory: ctx.directory });
|
|
@@ -20719,7 +20569,7 @@ var subtasksServer = async (ctx) => {
|
|
|
20719
20569
|
replyLines.push(s);
|
|
20720
20570
|
return Promise.resolve();
|
|
20721
20571
|
},
|
|
20722
|
-
log:
|
|
20572
|
+
log: log11,
|
|
20723
20573
|
client,
|
|
20724
20574
|
parentSessionID: input.sessionID,
|
|
20725
20575
|
directory: ctx.directory,
|
|
@@ -20732,11 +20582,11 @@ var subtasksServer = async (ctx) => {
|
|
|
20732
20582
|
perTaskTimeoutMs: 5 * 60000,
|
|
20733
20583
|
log: (lvl, msg, data) => {
|
|
20734
20584
|
if (lvl === "error")
|
|
20735
|
-
|
|
20585
|
+
log11.error(msg, data);
|
|
20736
20586
|
else if (lvl === "warn")
|
|
20737
|
-
|
|
20587
|
+
log11.warn(msg, data);
|
|
20738
20588
|
else
|
|
20739
|
-
|
|
20589
|
+
log11.info(msg, data);
|
|
20740
20590
|
}
|
|
20741
20591
|
}) : undefined
|
|
20742
20592
|
};
|
|
@@ -20762,7 +20612,7 @@ var subtasksServer = async (ctx) => {
|
|
|
20762
20612
|
});
|
|
20763
20613
|
}
|
|
20764
20614
|
} catch (err) {
|
|
20765
|
-
|
|
20615
|
+
log11.error(`[${PLUGIN_NAME18}] command.execute.before 异常(已隔离)`, {
|
|
20766
20616
|
error: err instanceof Error ? err.message : String(err)
|
|
20767
20617
|
});
|
|
20768
20618
|
}
|
|
@@ -21056,7 +20906,7 @@ function describeError(err) {
|
|
|
21056
20906
|
return String(err);
|
|
21057
20907
|
}
|
|
21058
20908
|
}
|
|
21059
|
-
var
|
|
20909
|
+
var log11 = makePluginLogger(PLUGIN_NAME19);
|
|
21060
20910
|
var lru = new FingerprintLRU2;
|
|
21061
20911
|
var _lastNotification = null;
|
|
21062
20912
|
var terminalMonitorServer = async (ctx) => {
|
|
@@ -21075,7 +20925,7 @@ var terminalMonitorServer = async (ctx) => {
|
|
|
21075
20925
|
const ev = { type: e.type, ...e.properties ?? {} };
|
|
21076
20926
|
const r = await processTerminalEvent(ev, {
|
|
21077
20927
|
lru,
|
|
21078
|
-
log:
|
|
20928
|
+
log: log11,
|
|
21079
20929
|
notifyAgent: (msg) => {
|
|
21080
20930
|
_lastNotification = msg;
|
|
21081
20931
|
}
|
|
@@ -21088,7 +20938,7 @@ var terminalMonitorServer = async (ctx) => {
|
|
|
21088
20938
|
findings: r.notification?.findings.length ?? 0
|
|
21089
20939
|
});
|
|
21090
20940
|
if (r.notified && r.notification) {
|
|
21091
|
-
|
|
20941
|
+
log11.info(`[${PLUGIN_NAME19}] 反馈 ${r.notification.findings.length} 条 → ${r.notification.summary}`);
|
|
21092
20942
|
}
|
|
21093
20943
|
});
|
|
21094
20944
|
}
|
|
@@ -21099,7 +20949,7 @@ var handler19 = terminalMonitorServer;
|
|
|
21099
20949
|
// plugins/token-manager.ts
|
|
21100
20950
|
var PLUGIN_NAME20 = "token-manager";
|
|
21101
20951
|
logLifecycle(PLUGIN_NAME20, "import", {});
|
|
21102
|
-
async function handleMessageBefore(raw,
|
|
20952
|
+
async function handleMessageBefore(raw, log12, defaults) {
|
|
21103
20953
|
const ctx = raw ?? {};
|
|
21104
20954
|
if (!Array.isArray(ctx.messages) || ctx.messages.length === 0)
|
|
21105
20955
|
return null;
|
|
@@ -21118,17 +20968,17 @@ async function handleMessageBefore(raw, log11, defaults) {
|
|
|
21118
20968
|
};
|
|
21119
20969
|
if (r.compressed) {
|
|
21120
20970
|
ctx.messages = r.messages;
|
|
21121
|
-
|
|
20971
|
+
log12?.info(`[${PLUGIN_NAME20}] 压缩 ${r.before.count}→${r.after.count} 条 / ${r.before.tokens}→${r.after.tokens} tokens`);
|
|
21122
20972
|
}
|
|
21123
20973
|
return r;
|
|
21124
20974
|
} catch (err) {
|
|
21125
|
-
|
|
20975
|
+
log12?.warn(`[${PLUGIN_NAME20}] 压缩异常(已隔离)`, {
|
|
21126
20976
|
error: err instanceof Error ? err.message : String(err)
|
|
21127
20977
|
});
|
|
21128
20978
|
return null;
|
|
21129
20979
|
}
|
|
21130
20980
|
}
|
|
21131
|
-
var
|
|
20981
|
+
var log12 = makePluginLogger(PLUGIN_NAME20);
|
|
21132
20982
|
var tokenManagerServer = async (ctx) => {
|
|
21133
20983
|
const rt = loadRuntimeSync();
|
|
21134
20984
|
const threshold = rt.runtime.context.condenser_threshold_ratio;
|
|
@@ -21151,7 +21001,7 @@ var tokenManagerServer = async (ctx) => {
|
|
|
21151
21001
|
`);
|
|
21152
21002
|
return { role, content };
|
|
21153
21003
|
});
|
|
21154
|
-
const r = await handleMessageBefore({ messages: flat },
|
|
21004
|
+
const r = await handleMessageBefore({ messages: flat }, log12, { threshold });
|
|
21155
21005
|
if (!r)
|
|
21156
21006
|
return;
|
|
21157
21007
|
safeWriteLog(PLUGIN_NAME20, {
|
|
@@ -21165,7 +21015,7 @@ var tokenManagerServer = async (ctx) => {
|
|
|
21165
21015
|
reason: r.reason
|
|
21166
21016
|
});
|
|
21167
21017
|
if (r.compressed) {
|
|
21168
|
-
|
|
21018
|
+
log12.warn(`[${PLUGIN_NAME20}] advise condense: ${r.before.count}→${r.after.count} msgs / ${r.before.tokens}→${r.after.tokens} tokens (observe-only, no write-back to avoid opencode message schema mismatch)`);
|
|
21169
21019
|
}
|
|
21170
21020
|
});
|
|
21171
21021
|
}
|
|
@@ -21176,7 +21026,150 @@ var handler20 = tokenManagerServer;
|
|
|
21176
21026
|
// plugins/tool-policy.ts
|
|
21177
21027
|
import { promises as fs19 } from "node:fs";
|
|
21178
21028
|
import * as path24 from "node:path";
|
|
21179
|
-
|
|
21029
|
+
|
|
21030
|
+
// lib/tool-risk.ts
|
|
21031
|
+
var RISK_PATTERNS = [
|
|
21032
|
+
{
|
|
21033
|
+
tag: "rm_rf_root",
|
|
21034
|
+
kinds: ["bash"],
|
|
21035
|
+
re: /rm\s+-[rRf]+[^|;`\n]*\s+(\/(?:\s|$|[^/\w*.-])|~|\$HOME(?:\b|\/)|\/(?:bin|etc|usr|var|opt|home|root|boot|lib|sbin)\b)/
|
|
21036
|
+
},
|
|
21037
|
+
{
|
|
21038
|
+
tag: "rm_rf_wildcard",
|
|
21039
|
+
kinds: ["bash"],
|
|
21040
|
+
re: /rm\s+-[rRf]+\s+(?:\.\*|\*)/
|
|
21041
|
+
},
|
|
21042
|
+
{
|
|
21043
|
+
tag: "force_push_protected",
|
|
21044
|
+
kinds: ["bash"],
|
|
21045
|
+
re: /git\s+push\s+(?:--force\b|-f\b)[\s\S]*\b(main|master|release(?:\/[\w.-]+)?)\b/
|
|
21046
|
+
},
|
|
21047
|
+
{
|
|
21048
|
+
tag: "git_reset_hard_main",
|
|
21049
|
+
kinds: ["bash"],
|
|
21050
|
+
re: /git\s+reset\s+--hard\s+(origin\/)?(main|master)/
|
|
21051
|
+
},
|
|
21052
|
+
{ tag: "sudo", kinds: ["bash"], re: /(?<![\w-])sudo(?![\w-])/ },
|
|
21053
|
+
{ tag: "su_root", kinds: ["bash"], re: /(?<![\w-])su\s+(?:-\s+)?root\b/ },
|
|
21054
|
+
{ tag: "mkfs", kinds: ["bash"], re: /\bmkfs(\.[\w]+)?\b/ },
|
|
21055
|
+
{ tag: "dd_disk", kinds: ["bash"], re: /\bdd\s+if=[^|;\s]+\s+of=\/dev\// },
|
|
21056
|
+
{ tag: "chmod_777", kinds: ["bash"], re: /chmod\s+[-+]?[0-7]?777/ },
|
|
21057
|
+
{
|
|
21058
|
+
tag: "curl_pipe_sh",
|
|
21059
|
+
kinds: ["bash"],
|
|
21060
|
+
re: /\b(?:curl|wget)\b[^|]*\|\s*(?:sh|bash|zsh)\b/
|
|
21061
|
+
},
|
|
21062
|
+
{
|
|
21063
|
+
tag: "drop_database",
|
|
21064
|
+
kinds: ["bash", "other"],
|
|
21065
|
+
re: /\b(DROP\s+(DATABASE|TABLE)|TRUNCATE\s+TABLE|DROP\s+SCHEMA)\b/i
|
|
21066
|
+
},
|
|
21067
|
+
{ tag: "write_secrets", re: /(\.env(?:\.\w+)?|id_[edr]sa|\.ssh\/id_|\.pem|\.p12|secret\.json)/i },
|
|
21068
|
+
{
|
|
21069
|
+
tag: "write_etc",
|
|
21070
|
+
kinds: ["bash", "edit"],
|
|
21071
|
+
re: /(?:^|\s|"|')\/etc\//
|
|
21072
|
+
},
|
|
21073
|
+
{
|
|
21074
|
+
tag: "write_usr",
|
|
21075
|
+
kinds: ["bash", "edit"],
|
|
21076
|
+
re: /(?:^|\s|"|')\/usr\//
|
|
21077
|
+
},
|
|
21078
|
+
{
|
|
21079
|
+
tag: "write_root_home",
|
|
21080
|
+
kinds: ["bash", "edit"],
|
|
21081
|
+
re: /(?:^|\s|"|')(\/root|\/home\/root)\//
|
|
21082
|
+
},
|
|
21083
|
+
{
|
|
21084
|
+
tag: "internal_url",
|
|
21085
|
+
kinds: ["webfetch"],
|
|
21086
|
+
re: /https?:\/\/(?:10\.\d{1,3}\.\d{1,3}\.\d{1,3}|172\.(?:1[6-9]|2\d|3[01])\.\d{1,3}\.\d{1,3}|192\.168\.\d{1,3}\.\d{1,3}|169\.254\.\d{1,3}\.\d{1,3})(?::\d+)?(?:[/?#]|$)/i
|
|
21087
|
+
},
|
|
21088
|
+
{
|
|
21089
|
+
tag: "kubectl_delete",
|
|
21090
|
+
kinds: ["bash"],
|
|
21091
|
+
re: /kubectl\s+(?:delete|destroy)\s+/
|
|
21092
|
+
},
|
|
21093
|
+
{
|
|
21094
|
+
tag: "terraform_destroy",
|
|
21095
|
+
kinds: ["bash"],
|
|
21096
|
+
re: /terraform\s+destroy/
|
|
21097
|
+
}
|
|
21098
|
+
];
|
|
21099
|
+
var TOOL_KIND_MAP = {
|
|
21100
|
+
bash: "bash",
|
|
21101
|
+
shell: "bash",
|
|
21102
|
+
exec: "bash",
|
|
21103
|
+
edit: "edit",
|
|
21104
|
+
write: "edit",
|
|
21105
|
+
"ast-edit": "edit",
|
|
21106
|
+
"pending-changes-apply": "edit",
|
|
21107
|
+
webfetch: "webfetch",
|
|
21108
|
+
fetch: "webfetch",
|
|
21109
|
+
read: "read",
|
|
21110
|
+
cat: "read",
|
|
21111
|
+
glob: "search",
|
|
21112
|
+
grep: "search",
|
|
21113
|
+
search: "search",
|
|
21114
|
+
"repo-map": "search",
|
|
21115
|
+
smart_search: "search",
|
|
21116
|
+
list_recent: "search",
|
|
21117
|
+
get_working_memory: "search",
|
|
21118
|
+
web_search: "search",
|
|
21119
|
+
save_chat_insight: "other",
|
|
21120
|
+
add_knowledge: "other",
|
|
21121
|
+
upload_document: "other",
|
|
21122
|
+
upload_document_from_url: "other",
|
|
21123
|
+
update_knowledge: "other",
|
|
21124
|
+
flag_outdated: "other",
|
|
21125
|
+
delete_knowledge: "other",
|
|
21126
|
+
update_working_memory: "other",
|
|
21127
|
+
update_user_preference: "other",
|
|
21128
|
+
generate_image: "other"
|
|
21129
|
+
};
|
|
21130
|
+
function classifyTool(tool2) {
|
|
21131
|
+
const lower = tool2.toLowerCase();
|
|
21132
|
+
if (lower in TOOL_KIND_MAP)
|
|
21133
|
+
return TOOL_KIND_MAP[lower];
|
|
21134
|
+
if (/(^|[-_])edit($|[-_])|write|patch|apply/i.test(lower))
|
|
21135
|
+
return "edit";
|
|
21136
|
+
if (/bash|shell|exec|run/i.test(lower))
|
|
21137
|
+
return "bash";
|
|
21138
|
+
if (/fetch|http|curl/i.test(lower))
|
|
21139
|
+
return "webfetch";
|
|
21140
|
+
if (/read|view/i.test(lower))
|
|
21141
|
+
return "read";
|
|
21142
|
+
if (/search|grep|glob|find|list/i.test(lower))
|
|
21143
|
+
return "search";
|
|
21144
|
+
return "other";
|
|
21145
|
+
}
|
|
21146
|
+
function evaluateRisk(tool2, args) {
|
|
21147
|
+
const kind = classifyTool(tool2);
|
|
21148
|
+
const haystack = buildHaystack(args);
|
|
21149
|
+
const hits = [];
|
|
21150
|
+
for (const pattern of RISK_PATTERNS) {
|
|
21151
|
+
if (pattern.kinds && !pattern.kinds.includes(kind))
|
|
21152
|
+
continue;
|
|
21153
|
+
const m = haystack.match(pattern.re);
|
|
21154
|
+
if (m) {
|
|
21155
|
+
hits.push({
|
|
21156
|
+
label: pattern.tag,
|
|
21157
|
+
pattern: pattern.re.source,
|
|
21158
|
+
matched_on: m[0]
|
|
21159
|
+
});
|
|
21160
|
+
}
|
|
21161
|
+
}
|
|
21162
|
+
return hits;
|
|
21163
|
+
}
|
|
21164
|
+
function buildHaystack(args) {
|
|
21165
|
+
if (typeof args === "string")
|
|
21166
|
+
return args;
|
|
21167
|
+
try {
|
|
21168
|
+
return JSON.stringify(args);
|
|
21169
|
+
} catch {
|
|
21170
|
+
return String(args);
|
|
21171
|
+
}
|
|
21172
|
+
}
|
|
21180
21173
|
|
|
21181
21174
|
// lib/file-regex-acl.ts
|
|
21182
21175
|
import * as path23 from "node:path";
|
|
@@ -21315,28 +21308,18 @@ function decideToolCall(ctx, cfg = {}, currentAgent) {
|
|
|
21315
21308
|
return {
|
|
21316
21309
|
action: "deny",
|
|
21317
21310
|
reasons: [
|
|
21318
|
-
`[ADR:
|
|
21311
|
+
`[ADR:worktree-session-isolation] subagent '${currentAgent}' 禁止调 pending_changes.${action2}` + ` — apply 必须由 codeforge orchestrator 或用户拍板`,
|
|
21319
21312
|
`替代路径:stage 完成后回报 codeforge,由其决定 apply 后通过 task_id 复用启动你跑测试`
|
|
21320
21313
|
],
|
|
21321
|
-
|
|
21322
|
-
effective_mode: ctx.mode ?? cfg.defaultMode ?? DEFAULT_RUNTIME.autonomy.default_mode,
|
|
21323
|
-
action: "confirm",
|
|
21324
|
-
downgraded: false,
|
|
21325
|
-
detected_risks: ["subagent_apply_blocked"],
|
|
21326
|
-
reason: `subagent '${currentAgent}' 自 apply 越权 (ADR:prevent-subagent-self-apply)`
|
|
21327
|
-
}
|
|
21314
|
+
risks: []
|
|
21328
21315
|
};
|
|
21329
21316
|
}
|
|
21330
|
-
const
|
|
21331
|
-
const mode = ctx.mode ?? fallbackMode;
|
|
21332
|
-
const a = evaluate(mode, {
|
|
21333
|
-
tool: ctx.tool,
|
|
21334
|
-
kind: ctx.kind ?? "other",
|
|
21335
|
-
args: ctx.args ?? {}
|
|
21336
|
-
});
|
|
21317
|
+
const risks = evaluateRisk(ctx.tool, ctx.args ?? {});
|
|
21337
21318
|
const reasons = [];
|
|
21338
|
-
if (
|
|
21339
|
-
|
|
21319
|
+
if (risks.length > 0) {
|
|
21320
|
+
const labels = risks.map((r) => r.label).join(", ");
|
|
21321
|
+
reasons.push(`risk: 命中高危规则 [${labels}]`);
|
|
21322
|
+
}
|
|
21340
21323
|
let aclResults;
|
|
21341
21324
|
let worstAcl = "allow";
|
|
21342
21325
|
if (ctx.files?.length) {
|
|
@@ -21350,10 +21333,8 @@ function decideToolCall(ctx, cfg = {}, currentAgent) {
|
|
|
21350
21333
|
return d;
|
|
21351
21334
|
});
|
|
21352
21335
|
}
|
|
21353
|
-
|
|
21354
|
-
|
|
21355
|
-
action = "deny";
|
|
21356
|
-
return { action, reasons, autonomy: a, acl: aclResults };
|
|
21336
|
+
const action = risks.length > 0 || worstAcl === "deny" ? "deny" : "allow";
|
|
21337
|
+
return { action, reasons, risks, acl: aclResults };
|
|
21357
21338
|
}
|
|
21358
21339
|
var POLICY_PATH = path24.join(".codeforge", "policy.json");
|
|
21359
21340
|
async function loadPolicy(root = process.cwd()) {
|
|
@@ -21367,27 +21348,20 @@ async function loadPolicy(root = process.cwd()) {
|
|
|
21367
21348
|
}
|
|
21368
21349
|
}
|
|
21369
21350
|
function classifyToolKind(toolName) {
|
|
21370
|
-
|
|
21371
|
-
if (t === "bash" || t === "shell" || t === "execute")
|
|
21372
|
-
return "bash";
|
|
21373
|
-
if (t === "edit" || t === "write" || t === "patch" || t === "ast_edit")
|
|
21374
|
-
return "edit";
|
|
21375
|
-
if (t === "webfetch" || t === "fetch" || t === "browser")
|
|
21376
|
-
return "webfetch";
|
|
21377
|
-
return "other";
|
|
21351
|
+
return classifyTool(toolName);
|
|
21378
21352
|
}
|
|
21379
|
-
async function resolveCurrentAgent(client, sessionID,
|
|
21353
|
+
async function resolveCurrentAgent(client, sessionID, log13) {
|
|
21380
21354
|
try {
|
|
21381
21355
|
const sessionApi = client?.session;
|
|
21382
21356
|
if (!sessionApi || typeof sessionApi.get !== "function") {
|
|
21383
|
-
|
|
21357
|
+
log13.warn(`client.session.get unavailable`, { sessionID });
|
|
21384
21358
|
return;
|
|
21385
21359
|
}
|
|
21386
21360
|
const res = await sessionApi.get({ path: { id: sessionID } });
|
|
21387
21361
|
const data = res?.data ?? res;
|
|
21388
21362
|
const rawAgent = data?.agent;
|
|
21389
21363
|
if (typeof rawAgent !== "string" || rawAgent === "") {
|
|
21390
|
-
|
|
21364
|
+
log13.warn(`client.session.get returned no string agent (保守放行)`, {
|
|
21391
21365
|
sessionID,
|
|
21392
21366
|
dataKeys: data && typeof data === "object" ? Object.keys(data) : null
|
|
21393
21367
|
});
|
|
@@ -21395,24 +21369,20 @@ async function resolveCurrentAgent(client, sessionID, log12) {
|
|
|
21395
21369
|
}
|
|
21396
21370
|
return rawAgent;
|
|
21397
21371
|
} catch (err) {
|
|
21398
|
-
|
|
21372
|
+
log13.warn(`client.session.get failed (保守放行)`, {
|
|
21399
21373
|
sessionID,
|
|
21400
21374
|
error: err instanceof Error ? err.message : String(err)
|
|
21401
21375
|
});
|
|
21402
21376
|
return;
|
|
21403
21377
|
}
|
|
21404
21378
|
}
|
|
21405
|
-
var
|
|
21379
|
+
var log13 = makePluginLogger(PLUGIN_NAME21);
|
|
21406
21380
|
var toolPolicyServer = async (ctx) => {
|
|
21407
21381
|
const directory = ctx.directory ?? process.cwd();
|
|
21408
21382
|
const cfg = await loadPolicy(directory);
|
|
21409
|
-
const rt = loadRuntimeSync();
|
|
21410
|
-
cfg.defaultMode = rt.runtime.autonomy.default_mode;
|
|
21411
21383
|
logLifecycle(PLUGIN_NAME21, "activate", {
|
|
21412
21384
|
directory,
|
|
21413
|
-
acl_loaded: !!cfg.acl
|
|
21414
|
-
default_mode: cfg.defaultMode,
|
|
21415
|
-
config_source: rt.ok ? "codeforge.json" : "built-in"
|
|
21385
|
+
acl_loaded: !!cfg.acl
|
|
21416
21386
|
});
|
|
21417
21387
|
return {
|
|
21418
21388
|
"tool.execute.before": async (input, output) => {
|
|
@@ -21423,7 +21393,7 @@ var toolPolicyServer = async (ctx) => {
|
|
|
21423
21393
|
const needsAgentDetection = toolName === "pending_changes" && (argsObj.action === "apply" || argsObj.action === "apply_all");
|
|
21424
21394
|
let currentAgent = undefined;
|
|
21425
21395
|
if (needsAgentDetection) {
|
|
21426
|
-
currentAgent = await resolveCurrentAgent(ctx.client, input.sessionID,
|
|
21396
|
+
currentAgent = await resolveCurrentAgent(ctx.client, input.sessionID, log13);
|
|
21427
21397
|
}
|
|
21428
21398
|
const files = [];
|
|
21429
21399
|
const filePath = argsObj["path"] ?? argsObj["filePath"] ?? argsObj["file"];
|
|
@@ -21435,7 +21405,6 @@ var toolPolicyServer = async (ctx) => {
|
|
|
21435
21405
|
tool: toolName,
|
|
21436
21406
|
kind: classifyToolKind(toolName),
|
|
21437
21407
|
args: argsObj,
|
|
21438
|
-
mode: cfg.defaultMode,
|
|
21439
21408
|
files: files.length ? files : undefined
|
|
21440
21409
|
}, cfg, currentAgent);
|
|
21441
21410
|
safeWriteLog(PLUGIN_NAME21, {
|
|
@@ -21445,18 +21414,18 @@ var toolPolicyServer = async (ctx) => {
|
|
|
21445
21414
|
sessionID: input.sessionID,
|
|
21446
21415
|
action: decision.action,
|
|
21447
21416
|
reasons: decision.reasons,
|
|
21417
|
+
risks: decision.risks.map((r) => r.label),
|
|
21448
21418
|
currentAgent
|
|
21449
21419
|
});
|
|
21450
21420
|
if (decision.action === "deny") {
|
|
21451
|
-
|
|
21421
|
+
log13.warn(`[${PLUGIN_NAME21}] DENY ${toolName}`, {
|
|
21452
21422
|
action: argsObj.action,
|
|
21453
21423
|
currentAgent,
|
|
21454
|
-
reasons: decision.reasons
|
|
21424
|
+
reasons: decision.reasons,
|
|
21425
|
+
risks: decision.risks.map((r) => r.label)
|
|
21455
21426
|
});
|
|
21456
21427
|
denied = new DeniedError(`[tool-policy] DENIED: ${decision.reasons.join(" / ")}`);
|
|
21457
21428
|
return;
|
|
21458
|
-
} else if (decision.action === "confirm") {
|
|
21459
|
-
log12.info(`[${PLUGIN_NAME21}] CONFIRM ${toolName}: ${decision.reasons.join("; ")}`);
|
|
21460
21429
|
}
|
|
21461
21430
|
});
|
|
21462
21431
|
if (denied)
|
|
@@ -21494,7 +21463,7 @@ import * as zlib from "node:zlib";
|
|
|
21494
21463
|
// lib/version-injected.ts
|
|
21495
21464
|
function getInjectedVersion() {
|
|
21496
21465
|
try {
|
|
21497
|
-
const v = "0.5.
|
|
21466
|
+
const v = "0.5.8";
|
|
21498
21467
|
if (typeof v === "string" && /^\d+\.\d+\.\d+/.test(v)) {
|
|
21499
21468
|
return v;
|
|
21500
21469
|
}
|
|
@@ -22691,35 +22660,35 @@ async function ensureRegistry(workflowsDir = "workflows") {
|
|
|
22691
22660
|
}
|
|
22692
22661
|
async function handleCommandInvoked(raw, workflowsDir = "workflows") {
|
|
22693
22662
|
const ctx = raw ?? {};
|
|
22694
|
-
const
|
|
22663
|
+
const log14 = ctx.log ?? fallbackLog2;
|
|
22695
22664
|
const command = typeof ctx.command === "string" ? ctx.command : null;
|
|
22696
22665
|
if (!command) {
|
|
22697
|
-
|
|
22666
|
+
log14.warn(`[${PLUGIN_NAME23}] command.invoked 缺 command 字段`, ctx);
|
|
22698
22667
|
return null;
|
|
22699
22668
|
}
|
|
22700
22669
|
const reg = await ensureRegistry(workflowsDir);
|
|
22701
22670
|
if (reg.errors.length) {
|
|
22702
|
-
|
|
22671
|
+
log14.warn(`[${PLUGIN_NAME23}] 有 ${reg.errors.length} 个 workflow 加载失败`, reg.errors);
|
|
22703
22672
|
}
|
|
22704
22673
|
const wf = reg.workflows.find((w) => matchesTrigger(w, command));
|
|
22705
22674
|
if (!wf) {
|
|
22706
|
-
|
|
22675
|
+
log14.info(`[${PLUGIN_NAME23}] no workflow matches "${command}"`);
|
|
22707
22676
|
return null;
|
|
22708
22677
|
}
|
|
22709
|
-
|
|
22678
|
+
log14.info(`[${PLUGIN_NAME23}] dispatch "${command}" → workflow "${wf.name}"`);
|
|
22710
22679
|
try {
|
|
22711
22680
|
const result = await run(wf, {
|
|
22712
22681
|
mode: ctx.adapter ? "real" : "dry_run",
|
|
22713
22682
|
autonomy: ctx.autonomy ?? "semi",
|
|
22714
22683
|
adapter: ctx.adapter
|
|
22715
22684
|
});
|
|
22716
|
-
|
|
22685
|
+
log14.info(`[${PLUGIN_NAME23}] workflow "${wf.name}" 完成 (${result.plan.mode})`, {
|
|
22717
22686
|
steps: result.plan.steps.length,
|
|
22718
22687
|
results: result.results.length
|
|
22719
22688
|
});
|
|
22720
22689
|
return result;
|
|
22721
22690
|
} catch (err) {
|
|
22722
|
-
|
|
22691
|
+
log14.error(`[${PLUGIN_NAME23}] workflow "${wf.name}" 执行失败`, {
|
|
22723
22692
|
error: err instanceof Error ? err.message : String(err)
|
|
22724
22693
|
});
|
|
22725
22694
|
throw err;
|
|
@@ -22852,9 +22821,17 @@ function isWriteOperation(toolName, argsObj, mainRoot) {
|
|
|
22852
22821
|
return true;
|
|
22853
22822
|
return false;
|
|
22854
22823
|
}
|
|
22855
|
-
var
|
|
22824
|
+
var log14 = makePluginLogger(PLUGIN_NAME24);
|
|
22825
|
+
function resolveMainRoot2(rawDir) {
|
|
22826
|
+
const worktreeMarker = "/.git/codeforge-worktrees/";
|
|
22827
|
+
const idx = rawDir.indexOf(worktreeMarker);
|
|
22828
|
+
if (idx !== -1) {
|
|
22829
|
+
return rawDir.slice(0, idx);
|
|
22830
|
+
}
|
|
22831
|
+
return rawDir;
|
|
22832
|
+
}
|
|
22856
22833
|
var sessionWorktreeGuardPlugin = async (ctx) => {
|
|
22857
|
-
const mainRoot = ctx.directory ?? process.cwd();
|
|
22834
|
+
const mainRoot = resolveMainRoot2(ctx.directory ?? process.cwd());
|
|
22858
22835
|
logLifecycle(PLUGIN_NAME24, "activate", {
|
|
22859
22836
|
mainRoot,
|
|
22860
22837
|
CODEFORGE_SESSION_ID: process.env["CODEFORGE_SESSION_ID"] ?? "(not set)"
|
|
@@ -22872,7 +22849,7 @@ var sessionWorktreeGuardPlugin = async (ctx) => {
|
|
|
22872
22849
|
try {
|
|
22873
22850
|
entry = await getSessionWorktree(sessionId, mainRoot);
|
|
22874
22851
|
} catch (err) {
|
|
22875
|
-
|
|
22852
|
+
log14.warn(`getSessionWorktree failed (跳过本次检查)`, {
|
|
22876
22853
|
sessionId,
|
|
22877
22854
|
mainRoot,
|
|
22878
22855
|
error: err instanceof Error ? err.message : String(err)
|
|
@@ -22884,12 +22861,12 @@ var sessionWorktreeGuardPlugin = async (ctx) => {
|
|
|
22884
22861
|
return;
|
|
22885
22862
|
}
|
|
22886
22863
|
try {
|
|
22887
|
-
const parentId =
|
|
22864
|
+
const parentId = lookupParentSessionId2(sessionId);
|
|
22888
22865
|
if (parentId) {
|
|
22889
22866
|
const parentEntry = await getSessionWorktree(parentId, mainRoot);
|
|
22890
22867
|
if (parentEntry && parentEntry.status === "active") {
|
|
22891
22868
|
entry = parentEntry;
|
|
22892
|
-
|
|
22869
|
+
log14.debug?.(`[child-inherit] session ${sessionId} 继承父 ${parentId} 的 worktree`, { parentSessionId: parentId, worktreePath: parentEntry.worktreePath });
|
|
22893
22870
|
safeWriteLog(PLUGIN_NAME24, {
|
|
22894
22871
|
hook: "tool.execute.before",
|
|
22895
22872
|
tool: toolName,
|
|
@@ -22902,13 +22879,13 @@ var sessionWorktreeGuardPlugin = async (ctx) => {
|
|
|
22902
22879
|
}
|
|
22903
22880
|
}
|
|
22904
22881
|
} catch (lookupErr) {
|
|
22905
|
-
|
|
22882
|
+
log14.debug?.("[child-inherit] lookupParentSessionId 抛错(已隔离,退回 lazy-bind)", { error: lookupErr instanceof Error ? lookupErr.message : String(lookupErr) });
|
|
22906
22883
|
}
|
|
22907
22884
|
}
|
|
22908
22885
|
if (!entry) {
|
|
22909
22886
|
try {
|
|
22910
22887
|
entry = await bindSessionWorktree({ sessionId, mainRoot });
|
|
22911
|
-
|
|
22888
|
+
log14.info(`[lazy-bind] auto-created worktree for session ${sessionId}`, { branch: entry.branch, path: entry.worktreePath });
|
|
22912
22889
|
safeWriteLog(PLUGIN_NAME24, {
|
|
22913
22890
|
hook: "tool.execute.before",
|
|
22914
22891
|
tool: toolName,
|
|
@@ -22918,7 +22895,7 @@ var sessionWorktreeGuardPlugin = async (ctx) => {
|
|
|
22918
22895
|
worktreePath: entry.worktreePath
|
|
22919
22896
|
});
|
|
22920
22897
|
} catch (err) {
|
|
22921
|
-
|
|
22898
|
+
log14.warn(`[lazy-bind] failed (落到主仓写)`, { sessionId, error: err instanceof Error ? err.message : String(err) });
|
|
22922
22899
|
safeWriteLog(PLUGIN_NAME24, {
|
|
22923
22900
|
hook: "tool.execute.before",
|
|
22924
22901
|
tool: toolName,
|
|
@@ -22936,7 +22913,7 @@ var sessionWorktreeGuardPlugin = async (ctx) => {
|
|
|
22936
22913
|
const caller = input.agent;
|
|
22937
22914
|
if (caller !== undefined && caller !== "codeforge") {
|
|
22938
22915
|
const reason = `[session-worktree-guard] DENIED: session_merge action=merge 仅 codeforge orchestrator 或用户可调;当前 caller=${caller}`;
|
|
22939
|
-
|
|
22916
|
+
log14.warn(reason, {
|
|
22940
22917
|
sessionId,
|
|
22941
22918
|
tool: toolName,
|
|
22942
22919
|
action,
|
|
@@ -22978,7 +22955,7 @@ var sessionWorktreeGuardPlugin = async (ctx) => {
|
|
|
22978
22955
|
const reasonBase = `[session-worktree-guard] DENIED: 当前 session 要求先调用 plan_read(plan_id="${entry.requiredPlanId}") 再执行写操作`;
|
|
22979
22956
|
const reason = inherited ? `${reasonBase}
|
|
22980
22957
|
[gate-deny] child session=${sessionId} 继承父 entry 但父 session planReadOk=false,父 session=${entry.sessionId} 需先调 plan_read(plan_id="${entry.requiredPlanId}")` : reasonBase;
|
|
22981
|
-
|
|
22958
|
+
log14.warn(reason, {
|
|
22982
22959
|
tool: toolName,
|
|
22983
22960
|
sessionId,
|
|
22984
22961
|
requiredPlanId: entry.requiredPlanId,
|
|
@@ -23001,7 +22978,7 @@ var sessionWorktreeGuardPlugin = async (ctx) => {
|
|
|
23001
22978
|
if (typeof command === "string" && commandContainsMainRoot(command, mainRoot) && detectBashWriteIntent(command, mainRoot)) {
|
|
23002
22979
|
const snippet = command.length > 60 ? command.slice(0, 60) + "…" : command;
|
|
23003
22980
|
const reason = `[session-worktree-guard] DENIED: bash.command 含主仓绝对路径写操作 (${snippet}),请在当前 session worktree (${worktreePath}) 内操作`;
|
|
23004
|
-
|
|
22981
|
+
log14.warn(reason, { sessionId, command: command.slice(0, 200) });
|
|
23005
22982
|
safeWriteLog(PLUGIN_NAME24, {
|
|
23006
22983
|
hook: "tool.execute.before",
|
|
23007
22984
|
tool: toolName,
|
|
@@ -23019,7 +22996,7 @@ var sessionWorktreeGuardPlugin = async (ctx) => {
|
|
|
23019
22996
|
if (typeof filePath === "string") {
|
|
23020
22997
|
const newPath = rewritePath(filePath, mainRoot, worktreePath);
|
|
23021
22998
|
if (newPath !== null) {
|
|
23022
|
-
|
|
22999
|
+
log14.info(`rewrote ${toolName}.filePath: ${filePath} → ${newPath}`);
|
|
23023
23000
|
safeWriteLog(PLUGIN_NAME24, {
|
|
23024
23001
|
hook: "tool.execute.before",
|
|
23025
23002
|
tool: toolName,
|
|
@@ -23037,7 +23014,7 @@ var sessionWorktreeGuardPlugin = async (ctx) => {
|
|
|
23037
23014
|
if (typeof target === "string") {
|
|
23038
23015
|
const newTarget = rewritePath(target, mainRoot, worktreePath);
|
|
23039
23016
|
if (newTarget !== null) {
|
|
23040
|
-
|
|
23017
|
+
log14.info(`rewrote ast_edit.target: ${target} → ${newTarget}`);
|
|
23041
23018
|
safeWriteLog(PLUGIN_NAME24, {
|
|
23042
23019
|
hook: "tool.execute.before",
|
|
23043
23020
|
tool: toolName,
|
|
@@ -23053,7 +23030,7 @@ var sessionWorktreeGuardPlugin = async (ctx) => {
|
|
|
23053
23030
|
if (typeof root === "string") {
|
|
23054
23031
|
const newRoot = rewritePath(root, mainRoot, worktreePath);
|
|
23055
23032
|
if (newRoot !== null) {
|
|
23056
|
-
|
|
23033
|
+
log14.info(`rewrote ast_edit.root: ${root} → ${newRoot}`);
|
|
23057
23034
|
safeWriteLog(PLUGIN_NAME24, {
|
|
23058
23035
|
hook: "tool.execute.before",
|
|
23059
23036
|
tool: toolName,
|
|
@@ -23071,7 +23048,7 @@ var sessionWorktreeGuardPlugin = async (ctx) => {
|
|
|
23071
23048
|
if (typeof workdir === "string") {
|
|
23072
23049
|
const newWorkdir = rewritePath(workdir, mainRoot, worktreePath);
|
|
23073
23050
|
if (newWorkdir !== null) {
|
|
23074
|
-
|
|
23051
|
+
log14.info(`rewrote bash.workdir: ${workdir} → ${newWorkdir}`);
|
|
23075
23052
|
safeWriteLog(PLUGIN_NAME24, {
|
|
23076
23053
|
hook: "tool.execute.before",
|
|
23077
23054
|
tool: toolName,
|
|
@@ -23099,7 +23076,7 @@ var IDLE_TOAST_THROTTLE_MS = 60 * 60000;
|
|
|
23099
23076
|
var IDLE_TOAST_DURATION_MS = 8000;
|
|
23100
23077
|
var IDLE_TOAST_REMINDER_INTERVAL_MS = 30 * 60000;
|
|
23101
23078
|
var lastIdleToastAt = new Map;
|
|
23102
|
-
var
|
|
23079
|
+
var log15 = makePluginLogger(PLUGIN_NAME25);
|
|
23103
23080
|
var worktreeLifecyclePlugin = async (ctx) => {
|
|
23104
23081
|
const mainRoot = ctx.directory;
|
|
23105
23082
|
logLifecycle(PLUGIN_NAME25, "activate", {
|
|
@@ -23114,7 +23091,7 @@ var worktreeLifecyclePlugin = async (ctx) => {
|
|
|
23114
23091
|
safeAsync(PLUGIN_NAME25, "activate.pruneOrphan", async () => {
|
|
23115
23092
|
const result = await pruneOrphanWorktrees(mainRoot);
|
|
23116
23093
|
if (result.cleaned.length > 0 || result.failed.length > 0) {
|
|
23117
|
-
|
|
23094
|
+
log15.info(`[pruneOrphan] cleaned=${result.cleaned.length} failed=${result.failed.length} skipped=${result.skipped}`);
|
|
23118
23095
|
safeWriteLog(PLUGIN_NAME25, {
|
|
23119
23096
|
hook: "activate.pruneOrphan",
|
|
23120
23097
|
cleaned: result.cleaned,
|
|
@@ -23161,7 +23138,7 @@ var worktreeLifecyclePlugin = async (ctx) => {
|
|
|
23161
23138
|
}
|
|
23162
23139
|
}
|
|
23163
23140
|
} catch (err) {
|
|
23164
|
-
|
|
23141
|
+
log15.warn(`[lifecycle] empty-worktree fast-path 检测失败 (回退到常规路径)`, {
|
|
23165
23142
|
sessionId: ended.sessionID,
|
|
23166
23143
|
error: err instanceof Error ? err.message : String(err)
|
|
23167
23144
|
});
|
|
@@ -23176,7 +23153,7 @@ var worktreeLifecyclePlugin = async (ctx) => {
|
|
|
23176
23153
|
});
|
|
23177
23154
|
}
|
|
23178
23155
|
} catch (err) {
|
|
23179
|
-
|
|
23156
|
+
log15.warn(`[lifecycle] checkpointCommit failed (继续 discard)`, {
|
|
23180
23157
|
sessionId: ended.sessionID,
|
|
23181
23158
|
error: err instanceof Error ? err.message : String(err)
|
|
23182
23159
|
});
|
|
@@ -23193,7 +23170,7 @@ var worktreeLifecyclePlugin = async (ctx) => {
|
|
|
23193
23170
|
worktreePath: entry.worktreePath
|
|
23194
23171
|
});
|
|
23195
23172
|
} catch (err) {
|
|
23196
|
-
|
|
23173
|
+
log15.warn(`[lifecycle] discardSession failed`, {
|
|
23197
23174
|
sessionId: ended.sessionID,
|
|
23198
23175
|
error: err instanceof Error ? err.message : String(err)
|
|
23199
23176
|
});
|
|
@@ -23216,7 +23193,7 @@ var worktreeLifecyclePlugin = async (ctx) => {
|
|
|
23216
23193
|
lastIdleToastAt.set(ended.sessionID, now);
|
|
23217
23194
|
const idleMin = Math.round(idleMs / 60000);
|
|
23218
23195
|
const msg = `\uD83D\uDCA4 Session ${ended.sessionID.slice(0, 8)} worktree 已空闲 ${idleMin}min,` + `用 /merge 收尾或 /discard-session 放弃`;
|
|
23219
|
-
const sent = await showToast2(client, { message: msg, variant: "default", duration: IDLE_TOAST_DURATION_MS, title: "CodeForge" },
|
|
23196
|
+
const sent = await showToast2(client, { message: msg, variant: "default", duration: IDLE_TOAST_DURATION_MS, title: "CodeForge" }, log15);
|
|
23220
23197
|
safeWriteLog(PLUGIN_NAME25, {
|
|
23221
23198
|
hook: "event",
|
|
23222
23199
|
type: ended.type,
|
|
@@ -23234,7 +23211,7 @@ var handler25 = worktreeLifecyclePlugin;
|
|
|
23234
23211
|
|
|
23235
23212
|
// src/index.ts
|
|
23236
23213
|
var PLUGIN_ID = "codeforge";
|
|
23237
|
-
var
|
|
23214
|
+
var log16 = makePluginLogger(PLUGIN_ID);
|
|
23238
23215
|
logLifecycle(PLUGIN_ID, "import", { entry: "src/index.ts" });
|
|
23239
23216
|
var HANDLERS = [
|
|
23240
23217
|
{ name: "agent-router", init: handler },
|
|
@@ -23271,7 +23248,7 @@ function makeSerialHook(hookName, fns) {
|
|
|
23271
23248
|
} catch (err) {
|
|
23272
23249
|
if (isDeniedError(err))
|
|
23273
23250
|
throw err;
|
|
23274
|
-
|
|
23251
|
+
log16.warn(`[${PLUGIN_ID}] ${hookName} handler 异常(已隔离)`, {
|
|
23275
23252
|
error: err instanceof Error ? err.message : String(err)
|
|
23276
23253
|
});
|
|
23277
23254
|
}
|
|
@@ -23284,7 +23261,7 @@ function createCodeforgeServer(opts) {
|
|
|
23284
23261
|
const yieldResult = shouldYieldToLocalPlugin({ directory: input.directory });
|
|
23285
23262
|
if (yieldResult.yield) {
|
|
23286
23263
|
const msg = formatYieldLog(yieldResult);
|
|
23287
|
-
|
|
23264
|
+
log16.info(msg, { reason: yieldResult.reason, markerPath: yieldResult.markerPath });
|
|
23288
23265
|
logLifecycle(PLUGIN_ID, "activate", {
|
|
23289
23266
|
yield_to_local: true,
|
|
23290
23267
|
yield_reason: yieldResult.reason,
|
|
@@ -23302,7 +23279,7 @@ function createCodeforgeServer(opts) {
|
|
|
23302
23279
|
if (r.status === "fulfilled" && r.value && typeof r.value === "object") {
|
|
23303
23280
|
hooksList.push(r.value);
|
|
23304
23281
|
} else if (r.status === "rejected") {
|
|
23305
|
-
|
|
23282
|
+
log16.warn(`[${PLUGIN_ID}] handler ${HANDLERS[i].name} init failed (隔离,其他 handler 继续)`, { error: r.reason instanceof Error ? r.reason.message : String(r.reason) });
|
|
23306
23283
|
}
|
|
23307
23284
|
});
|
|
23308
23285
|
logLifecycle(PLUGIN_ID, "activate", {
|
|
@@ -23357,7 +23334,7 @@ function createCodeforgeServer(opts) {
|
|
|
23357
23334
|
} catch (err) {
|
|
23358
23335
|
if (isDeniedError(err))
|
|
23359
23336
|
throw err;
|
|
23360
|
-
|
|
23337
|
+
log16.warn(`[${PLUGIN_ID}] event handler 异常(已隔离)`, {
|
|
23361
23338
|
error: err instanceof Error ? err.message : String(err)
|
|
23362
23339
|
});
|
|
23363
23340
|
}
|