@andyqiu/codeforge 0.5.20 → 0.5.22
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 +655 -1616
- package/install.sh +15 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -8047,7 +8047,7 @@ async function runAutoFeedback(cfg, ctx) {
|
|
|
8047
8047
|
if (cmds.length === 0) {
|
|
8048
8048
|
throw new Error("auto-feedback: 至少需要 test_cmd 或 lint_cmd 之一");
|
|
8049
8049
|
}
|
|
8050
|
-
const
|
|
8050
|
+
const log13 = ctx.log ?? (() => {});
|
|
8051
8051
|
const attempt = async () => {
|
|
8052
8052
|
let last = {
|
|
8053
8053
|
cmd: "",
|
|
@@ -8084,7 +8084,7 @@ ${excerpt}
|
|
|
8084
8084
|
|
|
8085
8085
|
请基于错误信息修复 pending-changes(用 ast-edit / pending-changes.stage),完成后我会重新跑测试验证。`;
|
|
8086
8086
|
const result2 = await ctx.dispatchToAgent("coder", prompt);
|
|
8087
|
-
|
|
8087
|
+
log13("info", `auto-feedback round ${round} dispatch summary: ${result2.summary.slice(0, 200)}`);
|
|
8088
8088
|
};
|
|
8089
8089
|
const debugResult = await runWithAutoDebug({
|
|
8090
8090
|
attempt,
|
|
@@ -8105,7 +8105,7 @@ ${excerpt}
|
|
|
8105
8105
|
history: debugResult.history
|
|
8106
8106
|
};
|
|
8107
8107
|
if (!debugResult.ok && debugResult.stopReason === "max-rounds") {
|
|
8108
|
-
|
|
8108
|
+
log13("warn", `auto-feedback 达 max_retries=${cfg.max_retries},escalate to ${cfg.escalate_to}`);
|
|
8109
8109
|
const lastFail = debugResult.history[debugResult.history.length - 1];
|
|
8110
8110
|
const excerpt = lastFail?.result ? extractErrorExcerpt(lastFail.result, cfg.error_excerpt_lines) : "(无失败结果)";
|
|
8111
8111
|
const escalatePrompt = `coder 连续 ${cfg.max_retries} 轮自纠失败,错误片段:
|
|
@@ -8825,248 +8825,6 @@ var autoCommitServer = async (ctx) => {
|
|
|
8825
8825
|
};
|
|
8826
8826
|
var handler3 = autoCommitServer;
|
|
8827
8827
|
|
|
8828
|
-
// plugins/auto-learning.ts
|
|
8829
|
-
init_kh_client();
|
|
8830
|
-
import * as crypto2 from "node:crypto";
|
|
8831
|
-
var PLUGIN_NAME4 = "auto-learning";
|
|
8832
|
-
var DEFAULT_CONFIG2 = {
|
|
8833
|
-
minScore: 0.5,
|
|
8834
|
-
cooldownMs: 24 * 60 * 60 * 1000,
|
|
8835
|
-
fingerprintLimit: 256,
|
|
8836
|
-
events: ["bug.fixed", "workflow.completed", "session.ended"]
|
|
8837
|
-
};
|
|
8838
|
-
var NOISE_TITLE_RE = /^\s*(test|wip|tmp|fix typo|update|chore|debug|todo|hello)\b/i;
|
|
8839
|
-
var TRIVIAL_KEYWORDS = ["typo", "格式化", "format", "rename", "重命名"];
|
|
8840
|
-
function extractInsights(ctx) {
|
|
8841
|
-
if (!ctx || typeof ctx !== "object")
|
|
8842
|
-
return [];
|
|
8843
|
-
const out = [];
|
|
8844
|
-
if (ctx.bug && ctx.bug.symptom && ctx.bug.fix) {
|
|
8845
|
-
const rootCause = ctx.bug.rootCause?.trim() ?? "";
|
|
8846
|
-
const baseScore = rootCause ? 0.85 : 0.55;
|
|
8847
|
-
const score = adjustScore(baseScore, ctx);
|
|
8848
|
-
const title = clip2(`Bug 修复:${ctx.bug.symptom}`, 80);
|
|
8849
|
-
if (!isTrivial(title)) {
|
|
8850
|
-
out.push({
|
|
8851
|
-
title,
|
|
8852
|
-
content: clip2([
|
|
8853
|
-
`**症状**:${ctx.bug.symptom}`,
|
|
8854
|
-
rootCause ? `**根因**:${rootCause}` : "",
|
|
8855
|
-
`**修复**:${ctx.bug.fix}`,
|
|
8856
|
-
ctx.bug.files?.length ? `**涉及**:${ctx.bug.files.slice(0, 5).join(", ")}` : ""
|
|
8857
|
-
].filter(Boolean).join(`
|
|
8858
|
-
|
|
8859
|
-
`), 500),
|
|
8860
|
-
kind: "bug-fix",
|
|
8861
|
-
category: "踩坑记录",
|
|
8862
|
-
tags: ctx.bug.files?.slice(0, 5),
|
|
8863
|
-
score
|
|
8864
|
-
});
|
|
8865
|
-
}
|
|
8866
|
-
}
|
|
8867
|
-
if (ctx.workflow?.learnings?.length) {
|
|
8868
|
-
for (const lesson of ctx.workflow.learnings) {
|
|
8869
|
-
const text = lesson.trim();
|
|
8870
|
-
if (!text || isTrivial(text))
|
|
8871
|
-
continue;
|
|
8872
|
-
const score = adjustScore(0.7, ctx);
|
|
8873
|
-
out.push({
|
|
8874
|
-
title: clip2(`${ctx.workflow.name ?? "workflow"}:${text}`, 80),
|
|
8875
|
-
content: clip2(text, 500),
|
|
8876
|
-
kind: "decision",
|
|
8877
|
-
category: "操作指南",
|
|
8878
|
-
tags: ctx.workflow.name ? [ctx.workflow.name] : undefined,
|
|
8879
|
-
score
|
|
8880
|
-
});
|
|
8881
|
-
}
|
|
8882
|
-
}
|
|
8883
|
-
if (ctx.summary && ctx.positiveFeedback && !ctx.rolledBack) {
|
|
8884
|
-
const text = ctx.summary.trim();
|
|
8885
|
-
if (text && !isTrivial(text)) {
|
|
8886
|
-
out.push({
|
|
8887
|
-
title: clip2(text.split(/[。.\n]/)[0] || "会话洞察", 80),
|
|
8888
|
-
content: clip2(text, 500),
|
|
8889
|
-
kind: "decision",
|
|
8890
|
-
category: "其它",
|
|
8891
|
-
score: adjustScore(0.55, ctx)
|
|
8892
|
-
});
|
|
8893
|
-
}
|
|
8894
|
-
}
|
|
8895
|
-
for (const i of out)
|
|
8896
|
-
i.fingerprint = fingerprint(i);
|
|
8897
|
-
return out.filter((i) => i.score > 0);
|
|
8898
|
-
}
|
|
8899
|
-
function adjustScore(base, ctx) {
|
|
8900
|
-
let s = base;
|
|
8901
|
-
if (ctx.positiveFeedback)
|
|
8902
|
-
s += 0.1;
|
|
8903
|
-
if (ctx.rolledBack)
|
|
8904
|
-
s -= 0.5;
|
|
8905
|
-
return Math.max(-1, Math.min(1, s));
|
|
8906
|
-
}
|
|
8907
|
-
function isTrivial(text) {
|
|
8908
|
-
if (!text)
|
|
8909
|
-
return true;
|
|
8910
|
-
if (NOISE_TITLE_RE.test(text))
|
|
8911
|
-
return true;
|
|
8912
|
-
const lower = text.toLowerCase();
|
|
8913
|
-
return TRIVIAL_KEYWORDS.some((k) => lower.includes(k));
|
|
8914
|
-
}
|
|
8915
|
-
function clip2(s, max) {
|
|
8916
|
-
if (!s)
|
|
8917
|
-
return "";
|
|
8918
|
-
if (s.length <= max)
|
|
8919
|
-
return s;
|
|
8920
|
-
return s.slice(0, max - 1) + "…";
|
|
8921
|
-
}
|
|
8922
|
-
function fingerprint(i) {
|
|
8923
|
-
const seed = `${i.kind}|${i.title}|${i.content}`;
|
|
8924
|
-
return crypto2.createHash("sha1").update(seed).digest("hex").slice(0, 16);
|
|
8925
|
-
}
|
|
8926
|
-
|
|
8927
|
-
class FingerprintLRU {
|
|
8928
|
-
limit;
|
|
8929
|
-
list = [];
|
|
8930
|
-
constructor(limit = 256) {
|
|
8931
|
-
this.limit = limit;
|
|
8932
|
-
}
|
|
8933
|
-
has(fp, withinMs, now = Date.now()) {
|
|
8934
|
-
const e = this.list.find((x) => x.fp === fp);
|
|
8935
|
-
if (!e)
|
|
8936
|
-
return false;
|
|
8937
|
-
return now - e.ts < withinMs;
|
|
8938
|
-
}
|
|
8939
|
-
add(fp, now = Date.now()) {
|
|
8940
|
-
this.list = this.list.filter((x) => x.fp !== fp);
|
|
8941
|
-
this.list.unshift({ fp, ts: now });
|
|
8942
|
-
if (this.list.length > this.limit)
|
|
8943
|
-
this.list.length = this.limit;
|
|
8944
|
-
}
|
|
8945
|
-
size() {
|
|
8946
|
-
return this.list.length;
|
|
8947
|
-
}
|
|
8948
|
-
}
|
|
8949
|
-
function shouldSave(insight, lru, cfg, now = Date.now()) {
|
|
8950
|
-
if (insight.score < cfg.minScore) {
|
|
8951
|
-
return { decision: "skip", reason: `score ${insight.score.toFixed(2)} < ${cfg.minScore}`, insight };
|
|
8952
|
-
}
|
|
8953
|
-
if (insight.fingerprint && lru.has(insight.fingerprint, cfg.cooldownMs, now)) {
|
|
8954
|
-
return { decision: "skip", reason: "duplicate within cooldown", insight };
|
|
8955
|
-
}
|
|
8956
|
-
return { decision: "save", reason: "ok", insight };
|
|
8957
|
-
}
|
|
8958
|
-
var moduleLRU = new FingerprintLRU(DEFAULT_CONFIG2.fingerprintLimit);
|
|
8959
|
-
async function processEvent(raw, opts = {}) {
|
|
8960
|
-
const cfg = opts.config ?? DEFAULT_CONFIG2;
|
|
8961
|
-
const lru = opts.lru ?? moduleLRU;
|
|
8962
|
-
const now = opts.now ?? Date.now();
|
|
8963
|
-
const ctx = raw ?? {};
|
|
8964
|
-
const insights = extractInsights(ctx);
|
|
8965
|
-
const decisions = insights.map((i) => shouldSave(i, lru, cfg, now));
|
|
8966
|
-
let saved = 0;
|
|
8967
|
-
let skipped = 0;
|
|
8968
|
-
for (const d of decisions) {
|
|
8969
|
-
if (d.decision === "skip") {
|
|
8970
|
-
skipped++;
|
|
8971
|
-
continue;
|
|
8972
|
-
}
|
|
8973
|
-
if (typeof opts.saveInsight !== "function") {
|
|
8974
|
-
if (d.insight.fingerprint)
|
|
8975
|
-
lru.add(d.insight.fingerprint, now);
|
|
8976
|
-
skipped++;
|
|
8977
|
-
continue;
|
|
8978
|
-
}
|
|
8979
|
-
try {
|
|
8980
|
-
const res = await opts.saveInsight({
|
|
8981
|
-
title: d.insight.title,
|
|
8982
|
-
content: d.insight.content,
|
|
8983
|
-
category: d.insight.category,
|
|
8984
|
-
tags: d.insight.tags
|
|
8985
|
-
});
|
|
8986
|
-
if (res?.ok) {
|
|
8987
|
-
if (d.insight.fingerprint)
|
|
8988
|
-
lru.add(d.insight.fingerprint, now);
|
|
8989
|
-
saved++;
|
|
8990
|
-
opts.log?.info(`[${PLUGIN_NAME4}] saved insight: ${d.insight.title}`, { id: res.id });
|
|
8991
|
-
if (typeof opts.onSaved === "function") {
|
|
8992
|
-
try {
|
|
8993
|
-
await opts.onSaved(d.insight, { id: res.id });
|
|
8994
|
-
} catch (err) {
|
|
8995
|
-
opts.log?.warn(`[${PLUGIN_NAME4}] onSaved hook 抛错(已隔离)`, {
|
|
8996
|
-
error: err instanceof Error ? err.message : String(err)
|
|
8997
|
-
});
|
|
8998
|
-
}
|
|
8999
|
-
}
|
|
9000
|
-
} else {
|
|
9001
|
-
skipped++;
|
|
9002
|
-
opts.log?.warn(`[${PLUGIN_NAME4}] save failed: ${res?.error ?? "unknown"}`);
|
|
9003
|
-
}
|
|
9004
|
-
} catch (err) {
|
|
9005
|
-
skipped++;
|
|
9006
|
-
opts.log?.warn(`[${PLUGIN_NAME4}] save threw(已隔离)`, {
|
|
9007
|
-
error: err instanceof Error ? err.message : String(err)
|
|
9008
|
-
});
|
|
9009
|
-
}
|
|
9010
|
-
}
|
|
9011
|
-
return { saved, skipped, decisions };
|
|
9012
|
-
}
|
|
9013
|
-
function createKhSaveInsightHook(client, log3) {
|
|
9014
|
-
if (!client.hasTransport()) {
|
|
9015
|
-
log3?.debug?.(`[${PLUGIN_NAME4}] createKhSaveInsightHook: transport 不可用,返 undefined(走 noop)`, {});
|
|
9016
|
-
return;
|
|
9017
|
-
}
|
|
9018
|
-
return async ({ title, content, category, tags }) => {
|
|
9019
|
-
const insight = `**${title}**
|
|
9020
|
-
|
|
9021
|
-
${content}`;
|
|
9022
|
-
try {
|
|
9023
|
-
const r = await client.save({ insight, category, tags });
|
|
9024
|
-
if (r.ok) {
|
|
9025
|
-
return { ok: true, id: r.id };
|
|
9026
|
-
}
|
|
9027
|
-
return { ok: false, error: `${r.reason}: ${r.message}` };
|
|
9028
|
-
} catch (err) {
|
|
9029
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
9030
|
-
log3?.warn(`[${PLUGIN_NAME4}] saveInsight hook 抛异常(已隔离)`, { error: message });
|
|
9031
|
-
return { ok: false, error: message };
|
|
9032
|
-
}
|
|
9033
|
-
};
|
|
9034
|
-
}
|
|
9035
|
-
logLifecycle(PLUGIN_NAME4, "import");
|
|
9036
|
-
var TRIGGER_EVENT_TYPES2 = new Set([
|
|
9037
|
-
"session.idle",
|
|
9038
|
-
"session.error"
|
|
9039
|
-
]);
|
|
9040
|
-
var sharedClient = new KhClient;
|
|
9041
|
-
var autoLearningServer = async (ctx) => {
|
|
9042
|
-
const log3 = makePluginLogger(PLUGIN_NAME4);
|
|
9043
|
-
const saveInsight = createKhSaveInsightHook(sharedClient, log3);
|
|
9044
|
-
logLifecycle(PLUGIN_NAME4, "activate", {
|
|
9045
|
-
directory: ctx.directory,
|
|
9046
|
-
triggerEventTypes: [...TRIGGER_EVENT_TYPES2],
|
|
9047
|
-
minScore: DEFAULT_CONFIG2.minScore,
|
|
9048
|
-
transport_available: sharedClient.hasTransport(),
|
|
9049
|
-
save_insight_wired: typeof saveInsight === "function"
|
|
9050
|
-
});
|
|
9051
|
-
return {
|
|
9052
|
-
event: async ({ event }) => {
|
|
9053
|
-
await safeAsync(PLUGIN_NAME4, "event", async () => {
|
|
9054
|
-
const e = event;
|
|
9055
|
-
if (!e.type || !TRIGGER_EVENT_TYPES2.has(e.type))
|
|
9056
|
-
return;
|
|
9057
|
-
const r = await processEvent({
|
|
9058
|
-
type: e.type,
|
|
9059
|
-
...e.properties && typeof e.properties === "object" ? e.properties : {}
|
|
9060
|
-
}, { log: log3, saveInsight });
|
|
9061
|
-
if (r.saved > 0 || r.skipped > 0) {
|
|
9062
|
-
log3.info(`event ${e.type}: saved=${r.saved} skipped=${r.skipped}`);
|
|
9063
|
-
}
|
|
9064
|
-
});
|
|
9065
|
-
}
|
|
9066
|
-
};
|
|
9067
|
-
};
|
|
9068
|
-
var handler4 = autoLearningServer;
|
|
9069
|
-
|
|
9070
8828
|
// plugins/channels.ts
|
|
9071
8829
|
import { promises as fs2, statSync as statSync2 } from "node:fs";
|
|
9072
8830
|
import * as path4 from "node:path";
|
|
@@ -9755,9 +9513,9 @@ function makeChannelEvent(event, data = {}) {
|
|
|
9755
9513
|
|
|
9756
9514
|
// plugins/channels.ts
|
|
9757
9515
|
init_global_config();
|
|
9758
|
-
var
|
|
9759
|
-
logLifecycle(
|
|
9760
|
-
var fallbackLog = makePluginLogger(
|
|
9516
|
+
var PLUGIN_NAME4 = "channels";
|
|
9517
|
+
logLifecycle(PLUGIN_NAME4, "import", {});
|
|
9518
|
+
var fallbackLog = makePluginLogger(PLUGIN_NAME4);
|
|
9761
9519
|
var _channelsCache = null;
|
|
9762
9520
|
var _activatedDirectory;
|
|
9763
9521
|
var KNOWN_TYPES = new Set([
|
|
@@ -10074,8 +9832,8 @@ function pickSeverity(eventName) {
|
|
|
10074
9832
|
return 10;
|
|
10075
9833
|
return 10;
|
|
10076
9834
|
}
|
|
10077
|
-
var log3 = makePluginLogger(
|
|
10078
|
-
var
|
|
9835
|
+
var log3 = makePluginLogger(PLUGIN_NAME4);
|
|
9836
|
+
var TRIGGER_EVENT_TYPES2 = new Set([
|
|
10079
9837
|
"workflow.completed",
|
|
10080
9838
|
"workflow.failed",
|
|
10081
9839
|
"approval.required",
|
|
@@ -10087,26 +9845,26 @@ var TRIGGER_EVENT_TYPES3 = new Set([
|
|
|
10087
9845
|
]);
|
|
10088
9846
|
var channelsServer = async (ctx) => {
|
|
10089
9847
|
_activatedDirectory = ctx.directory;
|
|
10090
|
-
logLifecycle(
|
|
9848
|
+
logLifecycle(PLUGIN_NAME4, "activate", {
|
|
10091
9849
|
directory: ctx.directory,
|
|
10092
|
-
triggerEventTypes: [...
|
|
9850
|
+
triggerEventTypes: [...TRIGGER_EVENT_TYPES2],
|
|
10093
9851
|
channelsConfigured: ensureChannels().length
|
|
10094
9852
|
});
|
|
10095
9853
|
return {
|
|
10096
9854
|
event: async ({ event }) => {
|
|
10097
|
-
await safeAsync(
|
|
9855
|
+
await safeAsync(PLUGIN_NAME4, "event", async () => {
|
|
10098
9856
|
const e = event;
|
|
10099
9857
|
if (!e || typeof e.type !== "string")
|
|
10100
9858
|
return;
|
|
10101
|
-
if (!
|
|
9859
|
+
if (!TRIGGER_EVENT_TYPES2.has(e.type))
|
|
10102
9860
|
return;
|
|
10103
9861
|
const summary = await bridgeEvent(e.type, e.properties ?? {});
|
|
10104
9862
|
if (summary.results.length > 0) {
|
|
10105
|
-
log3.info(`[${
|
|
9863
|
+
log3.info(`[${PLUGIN_NAME4}] ${e.type} → ${summary.results.length} channels`, {
|
|
10106
9864
|
any_sent: summary.any_sent,
|
|
10107
9865
|
rate_limited: summary.rate_limited
|
|
10108
9866
|
});
|
|
10109
|
-
safeWriteLog(
|
|
9867
|
+
safeWriteLog(PLUGIN_NAME4, {
|
|
10110
9868
|
hook: "event",
|
|
10111
9869
|
type: e.type,
|
|
10112
9870
|
results: summary.results.length,
|
|
@@ -10118,7 +9876,7 @@ var channelsServer = async (ctx) => {
|
|
|
10118
9876
|
}
|
|
10119
9877
|
};
|
|
10120
9878
|
};
|
|
10121
|
-
var
|
|
9879
|
+
var handler4 = channelsServer;
|
|
10122
9880
|
|
|
10123
9881
|
// lib/agent-resolver.ts
|
|
10124
9882
|
var chatAgentCacheReader = null;
|
|
@@ -10178,8 +9936,8 @@ async function resolveAgentForGuard(input, client, log4, opts = {}) {
|
|
|
10178
9936
|
}
|
|
10179
9937
|
|
|
10180
9938
|
// plugins/chat-agent-cache.ts
|
|
10181
|
-
var
|
|
10182
|
-
logLifecycle(
|
|
9939
|
+
var PLUGIN_NAME5 = "chat-agent-cache";
|
|
9940
|
+
logLifecycle(PLUGIN_NAME5, "import", {});
|
|
10183
9941
|
var SESSION_CAP = 500;
|
|
10184
9942
|
var SESSION_TTL_MS = 24 * 60 * 60 * 1000;
|
|
10185
9943
|
var sessionAgentMap = new Map;
|
|
@@ -10216,14 +9974,14 @@ function readSessionAgent(sessionID) {
|
|
|
10216
9974
|
}
|
|
10217
9975
|
var chatAgentCachePlugin = async (ctx) => {
|
|
10218
9976
|
setChatAgentCacheReader(readSessionAgent);
|
|
10219
|
-
logLifecycle(
|
|
9977
|
+
logLifecycle(PLUGIN_NAME5, "activate", {
|
|
10220
9978
|
directory: ctx.directory,
|
|
10221
9979
|
session_cap: SESSION_CAP,
|
|
10222
9980
|
session_ttl_ms: SESSION_TTL_MS
|
|
10223
9981
|
});
|
|
10224
9982
|
return {
|
|
10225
9983
|
"chat.params": async (input, _output) => {
|
|
10226
|
-
await safeAsync(
|
|
9984
|
+
await safeAsync(PLUGIN_NAME5, "chat.params", async () => {
|
|
10227
9985
|
const sid = input?.sessionID;
|
|
10228
9986
|
const agent = input?.agent;
|
|
10229
9987
|
if (typeof sid !== "string" || sid.length === 0)
|
|
@@ -10231,7 +9989,7 @@ var chatAgentCachePlugin = async (ctx) => {
|
|
|
10231
9989
|
if (typeof agent !== "string" || agent.length === 0)
|
|
10232
9990
|
return;
|
|
10233
9991
|
rememberSessionAgent(sid, agent);
|
|
10234
|
-
safeWriteLog(
|
|
9992
|
+
safeWriteLog(PLUGIN_NAME5, {
|
|
10235
9993
|
hook: "chat.params",
|
|
10236
9994
|
sessionID: sid,
|
|
10237
9995
|
agent,
|
|
@@ -10241,7 +9999,7 @@ var chatAgentCachePlugin = async (ctx) => {
|
|
|
10241
9999
|
});
|
|
10242
10000
|
},
|
|
10243
10001
|
"chat.message": async (input, _output) => {
|
|
10244
|
-
await safeAsync(
|
|
10002
|
+
await safeAsync(PLUGIN_NAME5, "chat.message", async () => {
|
|
10245
10003
|
const sid = input?.sessionID;
|
|
10246
10004
|
const agent = input?.agent;
|
|
10247
10005
|
if (typeof sid !== "string" || sid.length === 0)
|
|
@@ -10249,7 +10007,7 @@ var chatAgentCachePlugin = async (ctx) => {
|
|
|
10249
10007
|
if (typeof agent !== "string" || agent.length === 0)
|
|
10250
10008
|
return;
|
|
10251
10009
|
rememberSessionAgent(sid, agent);
|
|
10252
|
-
safeWriteLog(
|
|
10010
|
+
safeWriteLog(PLUGIN_NAME5, {
|
|
10253
10011
|
hook: "chat.message",
|
|
10254
10012
|
sessionID: sid,
|
|
10255
10013
|
agent,
|
|
@@ -10260,7 +10018,7 @@ var chatAgentCachePlugin = async (ctx) => {
|
|
|
10260
10018
|
}
|
|
10261
10019
|
};
|
|
10262
10020
|
};
|
|
10263
|
-
var
|
|
10021
|
+
var handler5 = chatAgentCachePlugin;
|
|
10264
10022
|
|
|
10265
10023
|
// plugins/codeforge-tools.ts
|
|
10266
10024
|
import { tool } from "@opencode-ai/plugin";
|
|
@@ -10766,10 +10524,10 @@ import * as path5 from "node:path";
|
|
|
10766
10524
|
import { z as z16 } from "zod";
|
|
10767
10525
|
|
|
10768
10526
|
// lib/ast-edit-engine.ts
|
|
10769
|
-
import * as
|
|
10527
|
+
import * as crypto2 from "node:crypto";
|
|
10770
10528
|
var IDENT_RE = /^[A-Za-z_$][\w$]*$/;
|
|
10771
10529
|
function sha256(s) {
|
|
10772
|
-
return
|
|
10530
|
+
return crypto2.createHash("sha256").update(s).digest("hex");
|
|
10773
10531
|
}
|
|
10774
10532
|
function detectEol(content) {
|
|
10775
10533
|
let crlf = 0;
|
|
@@ -12047,7 +11805,7 @@ import { z as z20 } from "zod";
|
|
|
12047
11805
|
// lib/browser-control.ts
|
|
12048
11806
|
init_runtime_paths();
|
|
12049
11807
|
import * as path9 from "node:path";
|
|
12050
|
-
var
|
|
11808
|
+
var DEFAULT_CONFIG2 = {
|
|
12051
11809
|
enabled: false,
|
|
12052
11810
|
headless: true,
|
|
12053
11811
|
allow: ["^https?://"],
|
|
@@ -12060,7 +11818,7 @@ var DEFAULT_CONFIG3 = {
|
|
|
12060
11818
|
function defaultScreenshotDir(root = process.cwd()) {
|
|
12061
11819
|
return path9.join(runtimeDir(root), "browser", "screenshots");
|
|
12062
11820
|
}
|
|
12063
|
-
function checkUrl(url, cfg =
|
|
11821
|
+
function checkUrl(url, cfg = DEFAULT_CONFIG2) {
|
|
12064
11822
|
if (typeof url !== "string" || url.trim() === "") {
|
|
12065
11823
|
return { ok: false, reason: "empty_url" };
|
|
12066
11824
|
}
|
|
@@ -12107,7 +11865,7 @@ class NoopBrowserController {
|
|
|
12107
11865
|
}
|
|
12108
11866
|
async close() {}
|
|
12109
11867
|
}
|
|
12110
|
-
async function tryCreatePlaywrightController(cfg =
|
|
11868
|
+
async function tryCreatePlaywrightController(cfg = DEFAULT_CONFIG2, resolver = defaultPlaywrightResolver) {
|
|
12111
11869
|
if (!cfg.enabled)
|
|
12112
11870
|
return null;
|
|
12113
11871
|
let mod;
|
|
@@ -12253,7 +12011,7 @@ function describe3(err) {
|
|
|
12253
12011
|
}
|
|
12254
12012
|
}
|
|
12255
12013
|
async function createBrowserController(opts = {}) {
|
|
12256
|
-
const cfg = { ...
|
|
12014
|
+
const cfg = { ...DEFAULT_CONFIG2, ...opts.cfg };
|
|
12257
12015
|
if (!cfg.enabled) {
|
|
12258
12016
|
return new NoopBrowserController("browser disabled in config");
|
|
12259
12017
|
}
|
|
@@ -13018,7 +12776,7 @@ import * as path13 from "node:path";
|
|
|
13018
12776
|
|
|
13019
12777
|
// lib/file-lock.ts
|
|
13020
12778
|
import { promises as fs9 } from "node:fs";
|
|
13021
|
-
import * as
|
|
12779
|
+
import * as crypto3 from "node:crypto";
|
|
13022
12780
|
import * as os4 from "node:os";
|
|
13023
12781
|
import * as path12 from "node:path";
|
|
13024
12782
|
async function withFileLock(lockPath, fn, opts = {}) {
|
|
@@ -13028,7 +12786,7 @@ async function withFileLock(lockPath, fn, opts = {}) {
|
|
|
13028
12786
|
await fs9.mkdir(path12.dirname(lockPath), { recursive: true });
|
|
13029
12787
|
const deadline = Date.now() + timeoutMs;
|
|
13030
12788
|
const myHost = os4.hostname();
|
|
13031
|
-
const myToken =
|
|
12789
|
+
const myToken = crypto3.randomBytes(16).toString("hex");
|
|
13032
12790
|
let acquired = false;
|
|
13033
12791
|
while (!acquired) {
|
|
13034
12792
|
try {
|
|
@@ -14786,8 +14544,10 @@ var description29 = [
|
|
|
14786
14544
|
"**何时调用**:",
|
|
14787
14545
|
"- 用户说「给这个项目加 ADR / 装一下 ADR 检查 / 同步一下 ADR 模板」",
|
|
14788
14546
|
"- 新项目 init 流程中希望把决策记录体系一起带上",
|
|
14789
|
-
"
|
|
14790
|
-
"
|
|
14547
|
+
"- 已有 ADR 但文件内容不正确时,传 force=true 覆盖修正",
|
|
14548
|
+
"**特点**:零 npm 依赖(用 git config core.hooksPath 而非 husky);跟仓库走(提交 .githooks/ 进 git);幂等(默认 skip 已存在文件,force=true 自动 .bak.<ts> 备份)",
|
|
14549
|
+
"**何时不需要**:当前项目已有完整的 ADR + hooks(默认 skip 行为会保护)",
|
|
14550
|
+
"**\uD83D\uDEAB 失败时严禁绕过**:如果返回 ok=false reason=assets_not_found,必须停止并告知用户重装 codeforge(bash install.sh --global),**绝对不允许手动创建文件替代**——手动创建会产生格式错误的文件(如错误的数字编号前缀),破坏整个项目的 ADR 体系"
|
|
14791
14551
|
].join(`
|
|
14792
14552
|
`);
|
|
14793
14553
|
var ArgsSchema29 = z30.object({
|
|
@@ -14871,7 +14631,7 @@ function makeOpencodeRunner(opts) {
|
|
|
14871
14631
|
const created = await opts.client.session.create({
|
|
14872
14632
|
body: {
|
|
14873
14633
|
parentID: opts.parentSessionID,
|
|
14874
|
-
title:
|
|
14634
|
+
title: clip2(`subtask:${spec.id}`, 80)
|
|
14875
14635
|
},
|
|
14876
14636
|
query: opts.directory ? { directory: opts.directory } : undefined
|
|
14877
14637
|
});
|
|
@@ -15071,7 +14831,7 @@ function safeStringify(v) {
|
|
|
15071
14831
|
return String(v);
|
|
15072
14832
|
}
|
|
15073
14833
|
}
|
|
15074
|
-
function
|
|
14834
|
+
function clip2(s, max) {
|
|
15075
14835
|
if (!s)
|
|
15076
14836
|
return "";
|
|
15077
14837
|
return s.length <= max ? s : s.slice(0, max - 1) + "…";
|
|
@@ -15391,7 +15151,7 @@ ${r.text.slice(0, 800)}`
|
|
|
15391
15151
|
let childId;
|
|
15392
15152
|
try {
|
|
15393
15153
|
const created = await this.opts.client.session.create({
|
|
15394
|
-
body: { title:
|
|
15154
|
+
body: { title: clip3(opts.title, 80) },
|
|
15395
15155
|
query: this.opts.directory ? { directory: this.opts.directory } : undefined
|
|
15396
15156
|
});
|
|
15397
15157
|
if (created.error || !created.data?.id) {
|
|
@@ -15534,7 +15294,7 @@ function describe5(err) {
|
|
|
15534
15294
|
return String(err);
|
|
15535
15295
|
}
|
|
15536
15296
|
}
|
|
15537
|
-
function
|
|
15297
|
+
function clip3(s, max) {
|
|
15538
15298
|
if (!s)
|
|
15539
15299
|
return "";
|
|
15540
15300
|
return s.length <= max ? s : s.slice(0, max - 1) + "…";
|
|
@@ -15747,8 +15507,8 @@ function parseRuntime(raw, abs) {
|
|
|
15747
15507
|
}
|
|
15748
15508
|
|
|
15749
15509
|
// plugins/tool-heartbeat.ts
|
|
15750
|
-
var
|
|
15751
|
-
logLifecycle(
|
|
15510
|
+
var PLUGIN_NAME6 = "tool-heartbeat";
|
|
15511
|
+
logLifecycle(PLUGIN_NAME6, "import", {});
|
|
15752
15512
|
var HEARTBEAT_INTERVAL_MS = 15000;
|
|
15753
15513
|
var ALERT_30S_MS = 30000;
|
|
15754
15514
|
var ALERT_60S_MS = 60000;
|
|
@@ -15830,15 +15590,15 @@ async function showToast(client, payload, log5) {
|
|
|
15830
15590
|
return false;
|
|
15831
15591
|
}
|
|
15832
15592
|
}
|
|
15833
|
-
var log5 = makePluginLogger(
|
|
15593
|
+
var log5 = makePluginLogger(PLUGIN_NAME6);
|
|
15834
15594
|
var toolHeartbeatServer = async (ctx) => {
|
|
15835
|
-
logLifecycle(
|
|
15595
|
+
logLifecycle(PLUGIN_NAME6, "activate", {
|
|
15836
15596
|
directory: ctx.directory,
|
|
15837
15597
|
intervalMs: HEARTBEAT_INTERVAL_MS
|
|
15838
15598
|
});
|
|
15839
15599
|
const client = ctx.client;
|
|
15840
15600
|
const interval = setInterval(() => {
|
|
15841
|
-
safeAsync(
|
|
15601
|
+
safeAsync(PLUGIN_NAME6, "interval", async () => {
|
|
15842
15602
|
if (inflight.size === 0)
|
|
15843
15603
|
return;
|
|
15844
15604
|
const records = [...inflight.values()];
|
|
@@ -15848,7 +15608,7 @@ var toolHeartbeatServer = async (ctx) => {
|
|
|
15848
15608
|
continue;
|
|
15849
15609
|
const sent = await showToast(client, { message: alert.message, variant: alert.variant }, log5);
|
|
15850
15610
|
r.alertsSent.add(alert.threshold);
|
|
15851
|
-
safeWriteLog(
|
|
15611
|
+
safeWriteLog(PLUGIN_NAME6, {
|
|
15852
15612
|
hook: "interval",
|
|
15853
15613
|
tool: r.toolName,
|
|
15854
15614
|
callID: r.callID,
|
|
@@ -15866,12 +15626,12 @@ var toolHeartbeatServer = async (ctx) => {
|
|
|
15866
15626
|
event: async () => {}
|
|
15867
15627
|
};
|
|
15868
15628
|
};
|
|
15869
|
-
var
|
|
15629
|
+
var handler6 = toolHeartbeatServer;
|
|
15870
15630
|
|
|
15871
15631
|
// plugins/codeforge-tools.ts
|
|
15872
15632
|
var z31 = tool.schema;
|
|
15873
|
-
var
|
|
15874
|
-
logLifecycle(
|
|
15633
|
+
var PLUGIN_NAME7 = "codeforge-tools";
|
|
15634
|
+
logLifecycle(PLUGIN_NAME7, "import");
|
|
15875
15635
|
function wrap(output, metadata) {
|
|
15876
15636
|
const text = typeof output === "string" ? output : JSON.stringify(output, null, 2);
|
|
15877
15637
|
return metadata && Object.keys(metadata).length > 0 ? { output: text, metadata } : { output: text };
|
|
@@ -15881,7 +15641,7 @@ async function runSafe(toolName, fn) {
|
|
|
15881
15641
|
return await fn();
|
|
15882
15642
|
} catch (err) {
|
|
15883
15643
|
const msg = err instanceof Error ? err.message : String(err);
|
|
15884
|
-
safeWriteLog(
|
|
15644
|
+
safeWriteLog(PLUGIN_NAME7, {
|
|
15885
15645
|
level: "error",
|
|
15886
15646
|
tool: toolName,
|
|
15887
15647
|
error: msg
|
|
@@ -16279,7 +16039,7 @@ var codeforgeToolsServer = async (ctx) => {
|
|
|
16279
16039
|
const rt = loadRuntimeSync({ root: ctx.directory });
|
|
16280
16040
|
const browserEnabled = rt.runtime.tools.browser.enabled;
|
|
16281
16041
|
const activeTools = browserEnabled ? [...CORE_TOOL_NAMES, ...BROWSER_TOOL_NAMES] : [...CORE_TOOL_NAMES];
|
|
16282
|
-
logLifecycle(
|
|
16042
|
+
logLifecycle(PLUGIN_NAME7, "activate", {
|
|
16283
16043
|
directory: ctx.directory,
|
|
16284
16044
|
tools: activeTools,
|
|
16285
16045
|
count: activeTools.length,
|
|
@@ -16295,7 +16055,7 @@ var codeforgeToolsServer = async (ctx) => {
|
|
|
16295
16055
|
client: ctx.client,
|
|
16296
16056
|
directory: ctx.directory ?? process.cwd(),
|
|
16297
16057
|
mainRoot: ctx.directory ?? process.cwd(),
|
|
16298
|
-
log: (level, msg, data) => safeWriteLog(
|
|
16058
|
+
log: (level, msg, data) => safeWriteLog(PLUGIN_NAME7, { level, msg, data })
|
|
16299
16059
|
});
|
|
16300
16060
|
__setContext({
|
|
16301
16061
|
mainRoot: ctx.directory ?? process.cwd(),
|
|
@@ -16599,7 +16359,8 @@ var codeforgeToolsServer = async (ctx) => {
|
|
|
16599
16359
|
const v = projectValidate("adr_init", ArgsSchema29, args);
|
|
16600
16360
|
if (!v.ok)
|
|
16601
16361
|
return wrap(JSON.parse(v.output));
|
|
16602
|
-
const
|
|
16362
|
+
const dataWithCwd = { ...v.data, cwd: v.data.cwd ?? ctx.directory ?? process.cwd() };
|
|
16363
|
+
const result = await execute29(dataWithCwd);
|
|
16603
16364
|
return wrap(result, { title: "adr_init" });
|
|
16604
16365
|
});
|
|
16605
16366
|
}
|
|
@@ -16607,7 +16368,7 @@ var codeforgeToolsServer = async (ctx) => {
|
|
|
16607
16368
|
}
|
|
16608
16369
|
};
|
|
16609
16370
|
};
|
|
16610
|
-
var
|
|
16371
|
+
var handler7 = codeforgeToolsServer;
|
|
16611
16372
|
|
|
16612
16373
|
// plugins/discover-spec-suggest.ts
|
|
16613
16374
|
import { readFileSync as readFileSync3, readdirSync, statSync as statSync3 } from "node:fs";
|
|
@@ -16789,8 +16550,8 @@ function isValidSlug(slug) {
|
|
|
16789
16550
|
}
|
|
16790
16551
|
|
|
16791
16552
|
// plugins/discover-spec-suggest.ts
|
|
16792
|
-
var
|
|
16793
|
-
logLifecycle(
|
|
16553
|
+
var PLUGIN_NAME8 = "discover-spec-suggest";
|
|
16554
|
+
logLifecycle(PLUGIN_NAME8, "import", {});
|
|
16794
16555
|
var TARGET_AGENT = "codeforge";
|
|
16795
16556
|
var SESSION_CAP2 = 200;
|
|
16796
16557
|
var SESSION_TTL_MS2 = 24 * 60 * 60 * 1000;
|
|
@@ -16903,7 +16664,7 @@ function loadSpecs(rootDir, opts = {}) {
|
|
|
16903
16664
|
const dirReader = opts.dirReader ?? defaultDirReader;
|
|
16904
16665
|
const dirExists = opts.dirExists ?? defaultDirExists;
|
|
16905
16666
|
const statReader = opts.statReader ?? defaultStatReader;
|
|
16906
|
-
const log6 = makePluginLogger(
|
|
16667
|
+
const log6 = makePluginLogger(PLUGIN_NAME8);
|
|
16907
16668
|
const specsRoot = join15(rootDir, SPECS_REL_DIR);
|
|
16908
16669
|
const records = [];
|
|
16909
16670
|
if (!dirExists(specsRoot)) {
|
|
@@ -17031,7 +16792,7 @@ function renderCandidatesNudge(matched) {
|
|
|
17031
16792
|
return body.slice(0, NUDGE_MAX_LEN - 4) + `
|
|
17032
16793
|
…`;
|
|
17033
16794
|
}
|
|
17034
|
-
var log6 = makePluginLogger(
|
|
16795
|
+
var log6 = makePluginLogger(PLUGIN_NAME8);
|
|
17035
16796
|
var discoverSpecSuggestServer = async (ctx) => {
|
|
17036
16797
|
try {
|
|
17037
16798
|
const loaded = loadSpecs(ctx.directory ?? process.cwd());
|
|
@@ -17042,7 +16803,7 @@ var discoverSpecSuggestServer = async (ctx) => {
|
|
|
17042
16803
|
error: err instanceof Error ? err.message : String(err)
|
|
17043
16804
|
});
|
|
17044
16805
|
}
|
|
17045
|
-
logLifecycle(
|
|
16806
|
+
logLifecycle(PLUGIN_NAME8, "activate", {
|
|
17046
16807
|
directory: ctx.directory,
|
|
17047
16808
|
specs_loaded: specIndex.length,
|
|
17048
16809
|
target_agent: TARGET_AGENT,
|
|
@@ -17054,7 +16815,7 @@ var discoverSpecSuggestServer = async (ctx) => {
|
|
|
17054
16815
|
});
|
|
17055
16816
|
return {
|
|
17056
16817
|
"chat.message": async (input, output) => {
|
|
17057
|
-
await safeAsync(
|
|
16818
|
+
await safeAsync(PLUGIN_NAME8, "chat.message", async () => {
|
|
17058
16819
|
if (specIndex.length === 0)
|
|
17059
16820
|
return;
|
|
17060
16821
|
const text = extractUserText(output);
|
|
@@ -17069,7 +16830,7 @@ var discoverSpecSuggestServer = async (ctx) => {
|
|
|
17069
16830
|
});
|
|
17070
16831
|
},
|
|
17071
16832
|
"experimental.chat.system.transform": async (input, output) => {
|
|
17072
|
-
await safeAsync(
|
|
16833
|
+
await safeAsync(PLUGIN_NAME8, "experimental.chat.system.transform", async () => {
|
|
17073
16834
|
if (specIndex.length === 0)
|
|
17074
16835
|
return;
|
|
17075
16836
|
if (!output || !Array.isArray(output.system))
|
|
@@ -17095,7 +16856,7 @@ var discoverSpecSuggestServer = async (ctx) => {
|
|
|
17095
16856
|
if (!nudge)
|
|
17096
16857
|
return;
|
|
17097
16858
|
output.system.push(nudge);
|
|
17098
|
-
safeWriteLog(
|
|
16859
|
+
safeWriteLog(PLUGIN_NAME8, {
|
|
17099
16860
|
hook: "experimental.chat.system.transform",
|
|
17100
16861
|
sessionID: sid,
|
|
17101
16862
|
agent: entry.agent,
|
|
@@ -17107,927 +16868,93 @@ var discoverSpecSuggestServer = async (ctx) => {
|
|
|
17107
16868
|
}
|
|
17108
16869
|
};
|
|
17109
16870
|
};
|
|
17110
|
-
var
|
|
16871
|
+
var handler8 = discoverSpecSuggestServer;
|
|
17111
16872
|
|
|
17112
|
-
//
|
|
17113
|
-
|
|
17114
|
-
|
|
17115
|
-
|
|
17116
|
-
|
|
17117
|
-
|
|
17118
|
-
|
|
17119
|
-
|
|
17120
|
-
|
|
17121
|
-
|
|
17122
|
-
|
|
17123
|
-
|
|
17124
|
-
|
|
17125
|
-
|
|
17126
|
-
|
|
17127
|
-
|
|
17128
|
-
|
|
17129
|
-
|
|
17130
|
-
}
|
|
17131
|
-
async searchOrGet(args) {
|
|
17132
|
-
const { client, sessionId, query, limit, timeoutMs } = args;
|
|
17133
|
-
const queryHash = this.hashQuery(sessionId, query);
|
|
17134
|
-
const inflightKey = `${sessionId}::${queryHash}`;
|
|
17135
|
-
const sessionMap2 = this.cache.get(sessionId);
|
|
17136
|
-
if (sessionMap2) {
|
|
17137
|
-
const entry = sessionMap2.get(queryHash);
|
|
17138
|
-
if (entry && this.now() - entry.cachedAt < this.ttlMs) {
|
|
17139
|
-
sessionMap2.delete(queryHash);
|
|
17140
|
-
sessionMap2.set(queryHash, entry);
|
|
17141
|
-
return entry.result;
|
|
17142
|
-
}
|
|
17143
|
-
if (entry)
|
|
17144
|
-
sessionMap2.delete(queryHash);
|
|
17145
|
-
}
|
|
17146
|
-
const pending = this.inflight.get(inflightKey);
|
|
17147
|
-
if (pending)
|
|
17148
|
-
return pending;
|
|
17149
|
-
const startGen = this.generation.get(sessionId) ?? 0;
|
|
17150
|
-
const promise = this.doSearchAndMaybeCache(client, sessionId, queryHash, query, limit, timeoutMs, startGen).finally(() => {
|
|
17151
|
-
this.inflight.delete(inflightKey);
|
|
17152
|
-
});
|
|
17153
|
-
this.inflight.set(inflightKey, promise);
|
|
17154
|
-
return promise;
|
|
17155
|
-
}
|
|
17156
|
-
onSessionEnd(sessionId) {
|
|
17157
|
-
this.generation.set(sessionId, (this.generation.get(sessionId) ?? 0) + 1);
|
|
17158
|
-
this.cache.delete(sessionId);
|
|
17159
|
-
const prefix = `${sessionId}::`;
|
|
17160
|
-
for (const key of this.inflight.keys()) {
|
|
17161
|
-
if (key.startsWith(prefix)) {
|
|
17162
|
-
this.inflight.delete(key);
|
|
17163
|
-
}
|
|
17164
|
-
}
|
|
17165
|
-
}
|
|
17166
|
-
_snapshot() {
|
|
17167
|
-
const perSession = {};
|
|
17168
|
-
let total = 0;
|
|
17169
|
-
for (const [sid, map] of this.cache.entries()) {
|
|
17170
|
-
perSession[sid] = map.size;
|
|
17171
|
-
total += map.size;
|
|
17172
|
-
}
|
|
17173
|
-
const generations = {};
|
|
17174
|
-
for (const [sid, g] of this.generation.entries()) {
|
|
17175
|
-
generations[sid] = g;
|
|
17176
|
-
}
|
|
17177
|
-
return {
|
|
17178
|
-
cacheSize: total,
|
|
17179
|
-
inflightSize: this.inflight.size,
|
|
17180
|
-
sessions: Array.from(this.cache.keys()),
|
|
17181
|
-
perSession,
|
|
17182
|
-
generations
|
|
17183
|
-
};
|
|
17184
|
-
}
|
|
17185
|
-
_reset() {
|
|
17186
|
-
this.cache.clear();
|
|
17187
|
-
this.inflight.clear();
|
|
17188
|
-
this.generation.clear();
|
|
16873
|
+
// lib/memories.ts
|
|
16874
|
+
import { promises as fs15 } from "node:fs";
|
|
16875
|
+
import * as path19 from "node:path";
|
|
16876
|
+
import * as os5 from "node:os";
|
|
16877
|
+
function resolveConfig(c) {
|
|
16878
|
+
return {
|
|
16879
|
+
projectRoot: c.projectRoot,
|
|
16880
|
+
homeDir: c.homeDir ?? os5.homedir(),
|
|
16881
|
+
projectName: c.projectName ?? path19.basename(c.projectRoot),
|
|
16882
|
+
kh: c.kh,
|
|
16883
|
+
now: c.now ?? Date.now,
|
|
16884
|
+
log: c.log ?? (() => {}),
|
|
16885
|
+
maxPerScope: c.maxPerScope ?? 1000
|
|
16886
|
+
};
|
|
16887
|
+
}
|
|
16888
|
+
function fileFor(scope, cfg) {
|
|
16889
|
+
if (scope === "project") {
|
|
16890
|
+
return path19.join(cfg.projectRoot, ".codeforge", "memories.json");
|
|
17189
16891
|
}
|
|
17190
|
-
|
|
17191
|
-
|
|
16892
|
+
return path19.join(cfg.homeDir, ".codeforge", "memories.json");
|
|
16893
|
+
}
|
|
16894
|
+
async function readBank(p) {
|
|
16895
|
+
try {
|
|
16896
|
+
const raw = await fs15.readFile(p, "utf8");
|
|
16897
|
+
const arr = JSON.parse(raw);
|
|
16898
|
+
if (!Array.isArray(arr))
|
|
16899
|
+
return [];
|
|
16900
|
+
return arr.filter((x) => isMemory(x));
|
|
16901
|
+
} catch {
|
|
16902
|
+
return [];
|
|
17192
16903
|
}
|
|
17193
|
-
|
|
17194
|
-
|
|
17195
|
-
|
|
17196
|
-
|
|
17197
|
-
|
|
17198
|
-
|
|
17199
|
-
|
|
16904
|
+
}
|
|
16905
|
+
async function writeBank(p, items) {
|
|
16906
|
+
await fs15.mkdir(path19.dirname(p), { recursive: true });
|
|
16907
|
+
const tmp = `${p}.tmp`;
|
|
16908
|
+
await fs15.writeFile(tmp, JSON.stringify(items, null, 2), "utf8");
|
|
16909
|
+
await fs15.rename(tmp, p);
|
|
16910
|
+
}
|
|
16911
|
+
function isMemory(x) {
|
|
16912
|
+
if (!x || typeof x !== "object")
|
|
16913
|
+
return false;
|
|
16914
|
+
const m = x;
|
|
16915
|
+
return typeof m.id === "string" && (m.scope === "project" || m.scope === "user") && typeof m.content === "string" && typeof m.created_at === "number" && typeof m.updated_at === "number";
|
|
16916
|
+
}
|
|
16917
|
+
var _seq = 0;
|
|
16918
|
+
function localId(now) {
|
|
16919
|
+
_seq = (_seq + 1) % 1e5;
|
|
16920
|
+
return `mem-${now}-${_seq.toString(36)}`;
|
|
16921
|
+
}
|
|
16922
|
+
async function addMemory(params, cfgRaw) {
|
|
16923
|
+
const cfg = resolveConfig(cfgRaw);
|
|
16924
|
+
if (!params.content || params.content.trim().length === 0) {
|
|
16925
|
+
return { ok: false, id: "", written_to: "local", error: "content 不能为空" };
|
|
17200
16926
|
}
|
|
17201
|
-
|
|
17202
|
-
|
|
17203
|
-
|
|
17204
|
-
|
|
17205
|
-
|
|
17206
|
-
|
|
17207
|
-
|
|
17208
|
-
|
|
17209
|
-
|
|
17210
|
-
|
|
17211
|
-
|
|
17212
|
-
})();
|
|
17213
|
-
if (timeoutMs === undefined || timeoutMs <= 0) {
|
|
17214
|
-
return callPromise;
|
|
17215
|
-
}
|
|
17216
|
-
let timer = null;
|
|
16927
|
+
const now = cfg.now();
|
|
16928
|
+
const base = {
|
|
16929
|
+
scope: params.scope,
|
|
16930
|
+
content: params.content.trim(),
|
|
16931
|
+
tags: params.tags?.filter((t) => typeof t === "string") ?? [],
|
|
16932
|
+
project: params.scope === "project" ? cfg.projectName : undefined,
|
|
16933
|
+
created_at: now,
|
|
16934
|
+
updated_at: now,
|
|
16935
|
+
source: params.source ?? "manual"
|
|
16936
|
+
};
|
|
16937
|
+
if (cfg.kh) {
|
|
17217
16938
|
try {
|
|
17218
|
-
const
|
|
17219
|
-
|
|
17220
|
-
|
|
17221
|
-
|
|
17222
|
-
|
|
17223
|
-
|
|
16939
|
+
const r = await cfg.kh.add(base);
|
|
16940
|
+
cfg.log("info", `[memories] add → KH ${r.id}`);
|
|
16941
|
+
return { ok: true, id: r.id, written_to: "kh" };
|
|
16942
|
+
} catch (err) {
|
|
16943
|
+
cfg.log("warn", `[memories] KH 写入失败,降级本地`, {
|
|
16944
|
+
error: err instanceof Error ? err.message : String(err)
|
|
17224
16945
|
});
|
|
17225
|
-
return await Promise.race([callPromise, racer]);
|
|
17226
|
-
} finally {
|
|
17227
|
-
if (timer)
|
|
17228
|
-
clearTimeout(timer);
|
|
17229
16946
|
}
|
|
17230
16947
|
}
|
|
17231
|
-
|
|
17232
|
-
|
|
17233
|
-
|
|
17234
|
-
|
|
17235
|
-
|
|
17236
|
-
|
|
17237
|
-
|
|
17238
|
-
sessionMap2.set(queryHash, { query, result, cachedAt: this.now() });
|
|
17239
|
-
while (sessionMap2.size > this.maxPerSession) {
|
|
17240
|
-
const oldest = sessionMap2.keys().next().value;
|
|
17241
|
-
if (oldest === undefined)
|
|
17242
|
-
break;
|
|
17243
|
-
sessionMap2.delete(oldest);
|
|
17244
|
-
}
|
|
16948
|
+
const file = fileFor(params.scope, cfg);
|
|
16949
|
+
const id = localId(now);
|
|
16950
|
+
const m = { id, ...base };
|
|
16951
|
+
const items = await readBank(file);
|
|
16952
|
+
items.push(m);
|
|
16953
|
+
if (items.length > cfg.maxPerScope) {
|
|
16954
|
+
items.splice(0, items.length - cfg.maxPerScope);
|
|
17245
16955
|
}
|
|
17246
|
-
|
|
17247
|
-
|
|
17248
|
-
|
|
17249
|
-
// plugins/kh-auto-context.ts
|
|
17250
|
-
var PLUGIN_NAME10 = "kh-auto-context";
|
|
17251
|
-
var INJECTION_MODE = "observe-only";
|
|
17252
|
-
function resolveInjectionMode(client) {
|
|
17253
|
-
return client.hasTransport() ? "system-injected" : "observe-only";
|
|
17254
|
-
}
|
|
17255
|
-
var DEFAULT_CONFIG4 = {
|
|
17256
|
-
minConfidence: 0.55,
|
|
17257
|
-
limit: 3,
|
|
17258
|
-
minTextLength: 8,
|
|
17259
|
-
cacheTtlMs: 5 * 60 * 1000,
|
|
17260
|
-
timeoutMs: 1000,
|
|
17261
|
-
skipPrefixes: ["/", "!", "@"],
|
|
17262
|
-
skipKeywords: ["你好", "hello", "hi", "thanks", "谢谢"]
|
|
17263
|
-
};
|
|
17264
|
-
|
|
17265
|
-
class QueryCache {
|
|
17266
|
-
ttlMs;
|
|
17267
|
-
now;
|
|
17268
|
-
map = new Map;
|
|
17269
|
-
constructor(ttlMs, now = () => Date.now()) {
|
|
17270
|
-
this.ttlMs = ttlMs;
|
|
17271
|
-
this.now = now;
|
|
17272
|
-
}
|
|
17273
|
-
shouldSkip(query) {
|
|
17274
|
-
const entry = this.map.get(query);
|
|
17275
|
-
if (!entry)
|
|
17276
|
-
return false;
|
|
17277
|
-
return this.now() - entry.injectedAt < this.ttlMs;
|
|
17278
|
-
}
|
|
17279
|
-
record(query, insights) {
|
|
17280
|
-
this.map.set(query, { query, injectedAt: this.now(), insights });
|
|
17281
|
-
}
|
|
17282
|
-
size() {
|
|
17283
|
-
return this.map.size;
|
|
17284
|
-
}
|
|
17285
|
-
clear() {
|
|
17286
|
-
this.map.clear();
|
|
17287
|
-
}
|
|
17288
|
-
}
|
|
17289
|
-
function extractQuery(text) {
|
|
17290
|
-
if (!text)
|
|
17291
|
-
return "";
|
|
17292
|
-
const norm = text.trim().replace(/\s+/g, " ");
|
|
17293
|
-
const m = /^[^.。?!?!;;\n]+/.exec(norm);
|
|
17294
|
-
let head = (m ? m[0] : norm).trim();
|
|
17295
|
-
const fillerRe = /^(请|帮我|麻烦|我想|你能|你可以|看一?下|查一?下|how (do|can) (i|you)|can you|could you|please|help me)\s*/i;
|
|
17296
|
-
let prev = "";
|
|
17297
|
-
while (prev !== head) {
|
|
17298
|
-
prev = head;
|
|
17299
|
-
head = head.replace(fillerRe, "").trim();
|
|
17300
|
-
}
|
|
17301
|
-
return head.slice(0, 80);
|
|
17302
|
-
}
|
|
17303
|
-
function shouldInject(text, cfg = DEFAULT_CONFIG4) {
|
|
17304
|
-
if (!text)
|
|
17305
|
-
return false;
|
|
17306
|
-
const t = text.trim();
|
|
17307
|
-
if (t.length < cfg.minTextLength)
|
|
17308
|
-
return false;
|
|
17309
|
-
for (const p of cfg.skipPrefixes) {
|
|
17310
|
-
if (t.startsWith(p))
|
|
17311
|
-
return false;
|
|
17312
|
-
}
|
|
17313
|
-
const lower = t.toLowerCase();
|
|
17314
|
-
for (const kw of cfg.skipKeywords) {
|
|
17315
|
-
if (lower.includes(kw.toLowerCase()) && t.length <= kw.length + 5)
|
|
17316
|
-
return false;
|
|
17317
|
-
}
|
|
17318
|
-
return true;
|
|
17319
|
-
}
|
|
17320
|
-
function formatInjection(query, insights, mode = INJECTION_MODE) {
|
|
17321
|
-
const lines = [];
|
|
17322
|
-
lines.push(`> \uD83E\uDDE0 [${mode}] 候选 ${insights.length} 条团队经验(query: "${query}")`);
|
|
17323
|
-
lines.push("");
|
|
17324
|
-
for (const ins of insights) {
|
|
17325
|
-
lines.push(`**${ins.title}** \`${ins.category}\` _(${(ins.confidence * 100).toFixed(0)}%)_`);
|
|
17326
|
-
const content = ins.content.length > 240 ? ins.content.slice(0, 240) + "…" : ins.content;
|
|
17327
|
-
lines.push(content.replace(/\n+/g, " "));
|
|
17328
|
-
lines.push("");
|
|
17329
|
-
}
|
|
17330
|
-
return { query, insights, markdown: lines.join(`
|
|
17331
|
-
`), mode };
|
|
17332
|
-
}
|
|
17333
|
-
var CODEFORGE_CONSTRAINTS = [
|
|
17334
|
-
{
|
|
17335
|
-
content: "本会话使用 CodeForge:触发词命中(部署/怎么/为什么/之前/历史等)必须先 smart_search;写代码直接在 session worktree 内 edit/write/ast_edit(worktree 由 session-worktree-guard 隔离主仓),完成后由用户 /merge 拍板",
|
|
17336
|
-
priority: 9
|
|
17337
|
-
},
|
|
17338
|
-
{
|
|
17339
|
-
content: "API key 仅读环境变量 KNOWLEDGE_API_KEY,禁止写入任何配置文件",
|
|
17340
|
-
priority: 9
|
|
17341
|
-
},
|
|
17342
|
-
{
|
|
17343
|
-
content: "沉淀经验优先用 save_chat_insight(自动分类、去重、合并),不要直接 add_knowledge(结构化录入)",
|
|
17344
|
-
priority: 8
|
|
17345
|
-
}
|
|
17346
|
-
];
|
|
17347
|
-
async function seedConstraints(client, log7) {
|
|
17348
|
-
if (!client.hasTransport()) {
|
|
17349
|
-
log7?.debug?.(`[${PLUGIN_NAME10}] seedConstraints: transport 不可用,跳过`, {});
|
|
17350
|
-
return { ok: false, reason: "transport_unavailable" };
|
|
17351
|
-
}
|
|
17352
|
-
try {
|
|
17353
|
-
const result = await client.updateWorkingMemory({
|
|
17354
|
-
section: "constraints",
|
|
17355
|
-
replace: true,
|
|
17356
|
-
items: CODEFORGE_CONSTRAINTS.map((c) => ({
|
|
17357
|
-
content: c.content,
|
|
17358
|
-
priority: c.priority
|
|
17359
|
-
}))
|
|
17360
|
-
});
|
|
17361
|
-
if (result && typeof result === "object" && "ok" in result && result.ok === false) {
|
|
17362
|
-
const r = result;
|
|
17363
|
-
log7?.warn(`[${PLUGIN_NAME10}] seedConstraints 降级`, {
|
|
17364
|
-
reason: r.reason,
|
|
17365
|
-
message: r.message
|
|
17366
|
-
});
|
|
17367
|
-
return {
|
|
17368
|
-
ok: false,
|
|
17369
|
-
reason: r.reason === "transport_unavailable" ? "transport_unavailable" : "kh_call_failed",
|
|
17370
|
-
message: r.message
|
|
17371
|
-
};
|
|
17372
|
-
}
|
|
17373
|
-
log7?.info(`[${PLUGIN_NAME10}] seedConstraints: 已写入 ${CODEFORGE_CONSTRAINTS.length} 条 constraints`, { count: CODEFORGE_CONSTRAINTS.length });
|
|
17374
|
-
return { ok: true, itemsWritten: CODEFORGE_CONSTRAINTS.length };
|
|
17375
|
-
} catch (err) {
|
|
17376
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
17377
|
-
log7?.warn(`[${PLUGIN_NAME10}] seedConstraints 失败(已静默)`, { error: message });
|
|
17378
|
-
return { ok: false, reason: "exception", message };
|
|
17379
|
-
}
|
|
17380
|
-
}
|
|
17381
|
-
function createSystemInjectedHook(client, sessionId, log7) {
|
|
17382
|
-
const section = `topic:auto-context-${sessionId ?? "global"}`;
|
|
17383
|
-
return async (markdown) => {
|
|
17384
|
-
try {
|
|
17385
|
-
const result = await client.updateWorkingMemory({
|
|
17386
|
-
section,
|
|
17387
|
-
replace: true,
|
|
17388
|
-
items: [{ content: markdown, priority: 7 }]
|
|
17389
|
-
});
|
|
17390
|
-
if (result && typeof result === "object" && "ok" in result && result.ok === false) {
|
|
17391
|
-
const r = result;
|
|
17392
|
-
log7?.warn(`[${PLUGIN_NAME10}] system-injected 降级到 observe-only:${r.reason ?? "unknown"}`, {
|
|
17393
|
-
sessionId,
|
|
17394
|
-
section,
|
|
17395
|
-
preview: markdown.slice(0, 200),
|
|
17396
|
-
reason: r.reason,
|
|
17397
|
-
message: r.message
|
|
17398
|
-
});
|
|
17399
|
-
return;
|
|
17400
|
-
}
|
|
17401
|
-
log7?.info(`[${PLUGIN_NAME10}] system-injected: 写入 KH working_memory 成功 (${markdown.length} chars)`, {
|
|
17402
|
-
sessionId,
|
|
17403
|
-
section
|
|
17404
|
-
});
|
|
17405
|
-
} catch (err) {
|
|
17406
|
-
log7?.warn(`[${PLUGIN_NAME10}] system-injected 抛异常,降级到 observe-only`, {
|
|
17407
|
-
sessionId,
|
|
17408
|
-
section,
|
|
17409
|
-
preview: markdown.slice(0, 200),
|
|
17410
|
-
error: err instanceof Error ? err.message : String(err)
|
|
17411
|
-
});
|
|
17412
|
-
}
|
|
17413
|
-
};
|
|
17414
|
-
}
|
|
17415
|
-
var inflight2 = new Set;
|
|
17416
|
-
var INFLIGHT_CAP = 5;
|
|
17417
|
-
function inflightKey(sessionId, query) {
|
|
17418
|
-
return `${sessionId ?? "global"}:${Date.now()}:${Math.random().toString(36).slice(2, 10)}`;
|
|
17419
|
-
}
|
|
17420
|
-
async function runKhSearchAndInject(args) {
|
|
17421
|
-
const { query, ctx, opts, mode } = args;
|
|
17422
|
-
const cfg = opts.config ?? DEFAULT_CONFIG4;
|
|
17423
|
-
const log7 = ctx.log;
|
|
17424
|
-
const startedAt = Date.now();
|
|
17425
|
-
const racer = opts.scheduler?.raceTimeout ?? (async (p, ms) => {
|
|
17426
|
-
let timer = null;
|
|
17427
|
-
try {
|
|
17428
|
-
return await Promise.race([
|
|
17429
|
-
p,
|
|
17430
|
-
new Promise((res) => {
|
|
17431
|
-
timer = setTimeout(() => res("__timeout__"), ms);
|
|
17432
|
-
})
|
|
17433
|
-
]);
|
|
17434
|
-
} finally {
|
|
17435
|
-
if (timer)
|
|
17436
|
-
clearTimeout(timer);
|
|
17437
|
-
}
|
|
17438
|
-
});
|
|
17439
|
-
let result;
|
|
17440
|
-
try {
|
|
17441
|
-
const searchPromise = sharedKhCache.searchOrGet({
|
|
17442
|
-
client: opts.client,
|
|
17443
|
-
sessionId: ctx.sessionId ?? "global",
|
|
17444
|
-
query,
|
|
17445
|
-
limit: cfg.limit
|
|
17446
|
-
});
|
|
17447
|
-
result = await racer(searchPromise, cfg.timeoutMs);
|
|
17448
|
-
} catch (err) {
|
|
17449
|
-
log7?.warn(`[${PLUGIN_NAME10}] client.search threw (sync or async), return null`, {
|
|
17450
|
-
query,
|
|
17451
|
-
elapsedMs: Date.now() - startedAt,
|
|
17452
|
-
sessionId: ctx.sessionId,
|
|
17453
|
-
error: err instanceof Error ? err.message : String(err)
|
|
17454
|
-
});
|
|
17455
|
-
opts.cache.record(query, []);
|
|
17456
|
-
return null;
|
|
17457
|
-
}
|
|
17458
|
-
if (result === "__timeout__") {
|
|
17459
|
-
log7?.warn(`[${PLUGIN_NAME10}] timeout`, {
|
|
17460
|
-
query,
|
|
17461
|
-
ms: cfg.timeoutMs,
|
|
17462
|
-
elapsedMs: Date.now() - startedAt,
|
|
17463
|
-
sessionId: ctx.sessionId
|
|
17464
|
-
});
|
|
17465
|
-
opts.cache.record(query, []);
|
|
17466
|
-
return null;
|
|
17467
|
-
}
|
|
17468
|
-
if (!result.ok) {
|
|
17469
|
-
log7?.warn(`[${PLUGIN_NAME10}] kh degraded`, {
|
|
17470
|
-
reason: result.reason,
|
|
17471
|
-
query,
|
|
17472
|
-
elapsedMs: Date.now() - startedAt,
|
|
17473
|
-
sessionId: ctx.sessionId
|
|
17474
|
-
});
|
|
17475
|
-
opts.cache.record(query, []);
|
|
17476
|
-
return null;
|
|
17477
|
-
}
|
|
17478
|
-
const filtered = result.insights.filter((i) => (i.confidence ?? 0) >= cfg.minConfidence);
|
|
17479
|
-
if (filtered.length === 0) {
|
|
17480
|
-
opts.cache.record(query, []);
|
|
17481
|
-
log7?.debug?.(`[${PLUGIN_NAME10}] no candidate above threshold`, {
|
|
17482
|
-
query,
|
|
17483
|
-
rawCount: result.insights.length,
|
|
17484
|
-
elapsedMs: Date.now() - startedAt,
|
|
17485
|
-
sessionId: ctx.sessionId
|
|
17486
|
-
});
|
|
17487
|
-
return null;
|
|
17488
|
-
}
|
|
17489
|
-
const payload = formatInjection(query, filtered, mode);
|
|
17490
|
-
if (typeof ctx.injectContext === "function") {
|
|
17491
|
-
try {
|
|
17492
|
-
await ctx.injectContext(payload.markdown);
|
|
17493
|
-
} catch (err) {
|
|
17494
|
-
log7?.warn(`[${PLUGIN_NAME10}] injectContext threw`, {
|
|
17495
|
-
error: err instanceof Error ? err.message : String(err),
|
|
17496
|
-
query,
|
|
17497
|
-
sessionId: ctx.sessionId
|
|
17498
|
-
});
|
|
17499
|
-
}
|
|
17500
|
-
}
|
|
17501
|
-
opts.cache.record(query, filtered);
|
|
17502
|
-
log7?.info(`[${PLUGIN_NAME10}] inject complete (${mode})`, {
|
|
17503
|
-
query,
|
|
17504
|
-
mode,
|
|
17505
|
-
candidateCount: filtered.length,
|
|
17506
|
-
elapsedMs: Date.now() - startedAt,
|
|
17507
|
-
sessionId: ctx.sessionId
|
|
17508
|
-
});
|
|
17509
|
-
return payload;
|
|
17510
|
-
}
|
|
17511
|
-
async function handleMessage2(raw, opts) {
|
|
17512
|
-
const cfg = opts.config ?? DEFAULT_CONFIG4;
|
|
17513
|
-
const mode = opts.mode ?? INJECTION_MODE;
|
|
17514
|
-
const ctx = raw ?? {};
|
|
17515
|
-
const log7 = ctx.log;
|
|
17516
|
-
const text = (ctx.content ?? "").trim();
|
|
17517
|
-
if (!shouldInject(text, cfg)) {
|
|
17518
|
-
log7?.debug?.(`[${PLUGIN_NAME10}] skip (filter)`, { textLen: text.length });
|
|
17519
|
-
return;
|
|
17520
|
-
}
|
|
17521
|
-
const query = extractQuery(text);
|
|
17522
|
-
if (!query)
|
|
17523
|
-
return;
|
|
17524
|
-
if (opts.cache.shouldSkip(query)) {
|
|
17525
|
-
log7?.debug?.(`[${PLUGIN_NAME10}] cache hit, skip`, { query });
|
|
17526
|
-
return;
|
|
17527
|
-
}
|
|
17528
|
-
if (inflight2.size >= INFLIGHT_CAP) {
|
|
17529
|
-
log7?.warn(`[${PLUGIN_NAME10}] inflight cap reached, skip`, {
|
|
17530
|
-
query,
|
|
17531
|
-
inflightSize: inflight2.size,
|
|
17532
|
-
cap: INFLIGHT_CAP,
|
|
17533
|
-
sessionId: ctx.sessionId
|
|
17534
|
-
});
|
|
17535
|
-
return;
|
|
17536
|
-
}
|
|
17537
|
-
const key = inflightKey(ctx.sessionId, query);
|
|
17538
|
-
inflight2.add(key);
|
|
17539
|
-
runKhSearchAndInject({ query, ctx, opts, mode }).catch((err) => {
|
|
17540
|
-
log7?.warn(`[${PLUGIN_NAME10}] runKhSearchAndInject 顶层兜底捕获`, {
|
|
17541
|
-
error: err instanceof Error ? err.message : String(err),
|
|
17542
|
-
query,
|
|
17543
|
-
sessionId: ctx.sessionId
|
|
17544
|
-
});
|
|
17545
|
-
}).finally(() => {
|
|
17546
|
-
inflight2.delete(key);
|
|
17547
|
-
});
|
|
17548
|
-
}
|
|
17549
|
-
logLifecycle(PLUGIN_NAME10, "import");
|
|
17550
|
-
var sharedClient2 = new KhClient;
|
|
17551
|
-
var sharedCache = new QueryCache(DEFAULT_CONFIG4.cacheTtlMs);
|
|
17552
|
-
var khAutoContextServer = async (ctx) => {
|
|
17553
|
-
const log7 = makePluginLogger(PLUGIN_NAME10);
|
|
17554
|
-
const runtimeMode = resolveInjectionMode(sharedClient2);
|
|
17555
|
-
logLifecycle(PLUGIN_NAME10, "activate", {
|
|
17556
|
-
directory: ctx.directory,
|
|
17557
|
-
minConfidence: DEFAULT_CONFIG4.minConfidence,
|
|
17558
|
-
timeoutMs: DEFAULT_CONFIG4.timeoutMs,
|
|
17559
|
-
mode: runtimeMode,
|
|
17560
|
-
transport_available: sharedClient2.hasTransport()
|
|
17561
|
-
});
|
|
17562
|
-
seedConstraints(sharedClient2, log7).catch((err) => {
|
|
17563
|
-
log7.warn("seedConstraints 顶层兜底捕获", {
|
|
17564
|
-
error: err instanceof Error ? err.message : String(err)
|
|
17565
|
-
});
|
|
17566
|
-
});
|
|
17567
|
-
return {
|
|
17568
|
-
"chat.message": async (input, output) => {
|
|
17569
|
-
await safeAsync(PLUGIN_NAME10, "chat.message", async () => {
|
|
17570
|
-
const text = extractUserText(output);
|
|
17571
|
-
if (!text)
|
|
17572
|
-
return;
|
|
17573
|
-
const injectContext = runtimeMode === "system-injected" ? createSystemInjectedHook(sharedClient2, input.sessionID, log7) : async (markdown) => {
|
|
17574
|
-
log7.info(`KH context candidate (${markdown.length} chars)`, {
|
|
17575
|
-
sessionID: input.sessionID,
|
|
17576
|
-
mode: runtimeMode,
|
|
17577
|
-
preview: markdown.slice(0, 200)
|
|
17578
|
-
});
|
|
17579
|
-
};
|
|
17580
|
-
await handleMessage2({
|
|
17581
|
-
content: text,
|
|
17582
|
-
sessionId: input.sessionID,
|
|
17583
|
-
injectContext,
|
|
17584
|
-
log: log7
|
|
17585
|
-
}, { client: sharedClient2, cache: sharedCache, mode: runtimeMode });
|
|
17586
|
-
});
|
|
17587
|
-
},
|
|
17588
|
-
event: async ({ event }) => {
|
|
17589
|
-
await safeAsync(PLUGIN_NAME10, "event", async () => {
|
|
17590
|
-
const e = event;
|
|
17591
|
-
if (e.type !== "session.idle")
|
|
17592
|
-
return;
|
|
17593
|
-
const props = e.properties;
|
|
17594
|
-
const sid = props?.sessionID;
|
|
17595
|
-
if (typeof sid !== "string" || !sid)
|
|
17596
|
-
return;
|
|
17597
|
-
sharedKhCache.onSessionEnd(sid);
|
|
17598
|
-
log7.debug?.(`[${PLUGIN_NAME10}] session.idle: cleared shared cache`, {
|
|
17599
|
-
sessionID: sid
|
|
17600
|
-
});
|
|
17601
|
-
});
|
|
17602
|
-
}
|
|
17603
|
-
};
|
|
17604
|
-
};
|
|
17605
|
-
var handler10 = khAutoContextServer;
|
|
17606
|
-
|
|
17607
|
-
// lib/condenser.ts
|
|
17608
|
-
var DEFAULT_CONDENSE = {
|
|
17609
|
-
budget: 128000,
|
|
17610
|
-
threshold: 0.7,
|
|
17611
|
-
target: 0.4,
|
|
17612
|
-
keepRecent: 6,
|
|
17613
|
-
keepSystem: true,
|
|
17614
|
-
charsPerToken: 4
|
|
17615
|
-
};
|
|
17616
|
-
function estimateTokens(text, charsPerToken = 4) {
|
|
17617
|
-
if (!text)
|
|
17618
|
-
return 0;
|
|
17619
|
-
return Math.max(1, Math.ceil(text.length / charsPerToken));
|
|
17620
|
-
}
|
|
17621
|
-
function tokenizeMessages(msgs, charsPerToken = 4) {
|
|
17622
|
-
let total = 0;
|
|
17623
|
-
for (const m of msgs) {
|
|
17624
|
-
total += m.tokens ?? estimateTokens(m.content, charsPerToken);
|
|
17625
|
-
}
|
|
17626
|
-
return total;
|
|
17627
|
-
}
|
|
17628
|
-
function fallbackSummarize(msgs) {
|
|
17629
|
-
if (msgs.length === 0)
|
|
17630
|
-
return "";
|
|
17631
|
-
const head = msgs.slice(0, 2).map((m) => `- [${m.role}] ${truncate(m.content, 120)}`);
|
|
17632
|
-
const tail = msgs.slice(-2).map((m) => `- [${m.role}] ${truncate(m.content, 120)}`);
|
|
17633
|
-
const tagCounts = new Map;
|
|
17634
|
-
let userCount = 0;
|
|
17635
|
-
let assistantCount = 0;
|
|
17636
|
-
let toolCount = 0;
|
|
17637
|
-
for (const m of msgs) {
|
|
17638
|
-
if (m.role === "user")
|
|
17639
|
-
userCount++;
|
|
17640
|
-
else if (m.role === "assistant")
|
|
17641
|
-
assistantCount++;
|
|
17642
|
-
else if (m.role === "tool")
|
|
17643
|
-
toolCount++;
|
|
17644
|
-
if (m.tag)
|
|
17645
|
-
tagCounts.set(m.tag, (tagCounts.get(m.tag) ?? 0) + 1);
|
|
17646
|
-
}
|
|
17647
|
-
const tagsLine = [...tagCounts.entries()].sort((a, b) => b[1] - a[1]).slice(0, 5).map(([t, c]) => `${t}×${c}`).join(", ");
|
|
17648
|
-
return [
|
|
17649
|
-
`### 历史摘要(共 ${msgs.length} 条 · user=${userCount} assistant=${assistantCount} tool=${toolCount})`,
|
|
17650
|
-
tagsLine ? `- 关键事件:${tagsLine}` : "",
|
|
17651
|
-
"",
|
|
17652
|
-
"**开头:**",
|
|
17653
|
-
...head,
|
|
17654
|
-
"",
|
|
17655
|
-
"**结尾:**",
|
|
17656
|
-
...tail
|
|
17657
|
-
].filter(Boolean).join(`
|
|
17658
|
-
`);
|
|
17659
|
-
}
|
|
17660
|
-
function truncate(s, max) {
|
|
17661
|
-
if (!s)
|
|
17662
|
-
return "";
|
|
17663
|
-
return s.length <= max ? s : s.slice(0, max - 1) + "…";
|
|
17664
|
-
}
|
|
17665
|
-
async function condense(input, opts = {}) {
|
|
17666
|
-
const cfg = { ...DEFAULT_CONDENSE, ...opts.config ?? {} };
|
|
17667
|
-
const msgs = [...input];
|
|
17668
|
-
const before = {
|
|
17669
|
-
count: msgs.length,
|
|
17670
|
-
tokens: tokenizeMessages(msgs, cfg.charsPerToken)
|
|
17671
|
-
};
|
|
17672
|
-
if (cfg.budget <= 0 || before.tokens / cfg.budget < cfg.threshold) {
|
|
17673
|
-
return {
|
|
17674
|
-
messages: msgs,
|
|
17675
|
-
before,
|
|
17676
|
-
after: { ...before },
|
|
17677
|
-
compressed: false,
|
|
17678
|
-
reason: `usage ${(before.tokens / Math.max(cfg.budget, 1)).toFixed(2)} < threshold ${cfg.threshold}`
|
|
17679
|
-
};
|
|
17680
|
-
}
|
|
17681
|
-
const systems = cfg.keepSystem ? msgs.filter((m) => m.role === "system") : [];
|
|
17682
|
-
const nonSystem = cfg.keepSystem ? msgs.filter((m) => m.role !== "system") : msgs;
|
|
17683
|
-
const recents = nonSystem.slice(-cfg.keepRecent);
|
|
17684
|
-
const middles = nonSystem.slice(0, Math.max(0, nonSystem.length - cfg.keepRecent));
|
|
17685
|
-
if (middles.length === 0) {
|
|
17686
|
-
return {
|
|
17687
|
-
messages: msgs,
|
|
17688
|
-
before,
|
|
17689
|
-
after: { ...before },
|
|
17690
|
-
compressed: false,
|
|
17691
|
-
reason: "nothing in middle to compress"
|
|
17692
|
-
};
|
|
17693
|
-
}
|
|
17694
|
-
let summary = "";
|
|
17695
|
-
try {
|
|
17696
|
-
summary = await (opts.summarize ?? fallbackSummarize)(middles) ?? "";
|
|
17697
|
-
} catch {
|
|
17698
|
-
summary = fallbackSummarize(middles);
|
|
17699
|
-
}
|
|
17700
|
-
if (!summary)
|
|
17701
|
-
summary = fallbackSummarize(middles);
|
|
17702
|
-
const summaryMsg = {
|
|
17703
|
-
role: "assistant",
|
|
17704
|
-
content: summary,
|
|
17705
|
-
tag: "condensed-summary"
|
|
17706
|
-
};
|
|
17707
|
-
const out = [...systems, summaryMsg, ...recents];
|
|
17708
|
-
const after = {
|
|
17709
|
-
count: out.length,
|
|
17710
|
-
tokens: tokenizeMessages(out, cfg.charsPerToken)
|
|
17711
|
-
};
|
|
17712
|
-
return {
|
|
17713
|
-
messages: out,
|
|
17714
|
-
before,
|
|
17715
|
-
after,
|
|
17716
|
-
compressed: true,
|
|
17717
|
-
summary,
|
|
17718
|
-
reason: `compressed ${middles.length} → 1 summary (target ${cfg.target})`
|
|
17719
|
-
};
|
|
17720
|
-
}
|
|
17721
|
-
|
|
17722
|
-
// plugins/kh-reminder.ts
|
|
17723
|
-
var PLUGIN_NAME11 = "kh-reminder";
|
|
17724
|
-
logLifecycle(PLUGIN_NAME11, "import", {});
|
|
17725
|
-
var TRIGGER_WORDS_ZH = [
|
|
17726
|
-
"怎么",
|
|
17727
|
-
"怎样",
|
|
17728
|
-
"如何",
|
|
17729
|
-
"为什么",
|
|
17730
|
-
"之前",
|
|
17731
|
-
"历史",
|
|
17732
|
-
"以前",
|
|
17733
|
-
"部署",
|
|
17734
|
-
"上线",
|
|
17735
|
-
"发布",
|
|
17736
|
-
"配置",
|
|
17737
|
-
"地址",
|
|
17738
|
-
"端口",
|
|
17739
|
-
"凭证",
|
|
17740
|
-
"接入",
|
|
17741
|
-
"集成",
|
|
17742
|
-
"谁负责",
|
|
17743
|
-
"谁写的",
|
|
17744
|
-
"报错",
|
|
17745
|
-
"报什么错",
|
|
17746
|
-
"跑不起来",
|
|
17747
|
-
"起不来",
|
|
17748
|
-
"挂了",
|
|
17749
|
-
"崩了",
|
|
17750
|
-
"规范",
|
|
17751
|
-
"约定",
|
|
17752
|
-
"风格"
|
|
17753
|
-
];
|
|
17754
|
-
var TRIGGER_WORDS_EN = [
|
|
17755
|
-
"how to",
|
|
17756
|
-
"why",
|
|
17757
|
-
"previously",
|
|
17758
|
-
"deploy",
|
|
17759
|
-
"release",
|
|
17760
|
-
"config",
|
|
17761
|
-
"URL",
|
|
17762
|
-
"credential",
|
|
17763
|
-
"token",
|
|
17764
|
-
"API key",
|
|
17765
|
-
"api[_-]?key",
|
|
17766
|
-
"integrate",
|
|
17767
|
-
"who owns",
|
|
17768
|
-
"error",
|
|
17769
|
-
"failed",
|
|
17770
|
-
"convention"
|
|
17771
|
-
];
|
|
17772
|
-
function buildTriggerRegex() {
|
|
17773
|
-
const escapedZh = TRIGGER_WORDS_ZH.map(escapeRegex).join("|");
|
|
17774
|
-
const enParts = [];
|
|
17775
|
-
for (const w of TRIGGER_WORDS_EN) {
|
|
17776
|
-
if (w === "api[_-]?key") {
|
|
17777
|
-
enParts.push(w);
|
|
17778
|
-
} else {
|
|
17779
|
-
enParts.push(escapeRegex(w));
|
|
17780
|
-
}
|
|
17781
|
-
}
|
|
17782
|
-
const escapedEn = enParts.join("|");
|
|
17783
|
-
const pattern = `(${escapedZh})|\\b(${escapedEn})\\b`;
|
|
17784
|
-
return new RegExp(pattern, "i");
|
|
17785
|
-
}
|
|
17786
|
-
function escapeRegex(s) {
|
|
17787
|
-
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
17788
|
-
}
|
|
17789
|
-
var TRIGGER_REGEX = buildTriggerRegex();
|
|
17790
|
-
var COOLDOWN_MS = 5 * 60 * 1000;
|
|
17791
|
-
var lastTriggerAt = new Map;
|
|
17792
|
-
var DEFAULT_THRESHOLD = 0.5;
|
|
17793
|
-
var DEFAULT_RECENT_ROUNDS = 5;
|
|
17794
|
-
function computeUsageRatio(messages) {
|
|
17795
|
-
const cpt = DEFAULT_CONDENSE.charsPerToken;
|
|
17796
|
-
const budget = DEFAULT_CONDENSE.budget;
|
|
17797
|
-
if (budget <= 0)
|
|
17798
|
-
return 0;
|
|
17799
|
-
let total = 0;
|
|
17800
|
-
for (const m of messages) {
|
|
17801
|
-
total += m.tokens ?? estimateTokens(m.content, cpt);
|
|
17802
|
-
}
|
|
17803
|
-
return total / budget;
|
|
17804
|
-
}
|
|
17805
|
-
function findTriggerWord(messages) {
|
|
17806
|
-
for (let i = messages.length - 1;i >= 0; i--) {
|
|
17807
|
-
const m = messages[i];
|
|
17808
|
-
if (!m || m.role !== "user")
|
|
17809
|
-
continue;
|
|
17810
|
-
const text = m.content ?? "";
|
|
17811
|
-
const match = TRIGGER_REGEX.exec(text);
|
|
17812
|
-
if (match) {
|
|
17813
|
-
return match[0];
|
|
17814
|
-
}
|
|
17815
|
-
return null;
|
|
17816
|
-
}
|
|
17817
|
-
return null;
|
|
17818
|
-
}
|
|
17819
|
-
function hasRecentSmartSearch(messages, recentRounds) {
|
|
17820
|
-
const slice = messages.slice(-recentRounds);
|
|
17821
|
-
for (const m of slice) {
|
|
17822
|
-
const c = m.content ?? "";
|
|
17823
|
-
if (/smart_search/i.test(c))
|
|
17824
|
-
return true;
|
|
17825
|
-
if (m.tag && /smart_search/i.test(m.tag))
|
|
17826
|
-
return true;
|
|
17827
|
-
}
|
|
17828
|
-
return false;
|
|
17829
|
-
}
|
|
17830
|
-
function evaluate(input) {
|
|
17831
|
-
const messages = Array.isArray(input.messages) ? input.messages : [];
|
|
17832
|
-
const sessionId = input.sessionId ?? "unknown";
|
|
17833
|
-
const threshold = input.threshold ?? DEFAULT_THRESHOLD;
|
|
17834
|
-
const recentRounds = input.recentRounds ?? DEFAULT_RECENT_ROUNDS;
|
|
17835
|
-
const now = (input.nowFn ?? Date.now)();
|
|
17836
|
-
if (messages.length === 0) {
|
|
17837
|
-
return { triggered: false, sessionId };
|
|
17838
|
-
}
|
|
17839
|
-
let reason = null;
|
|
17840
|
-
const ratio = computeUsageRatio(messages);
|
|
17841
|
-
if (ratio >= threshold) {
|
|
17842
|
-
reason = { kind: "token-usage", ratio, threshold };
|
|
17843
|
-
}
|
|
17844
|
-
if (!reason) {
|
|
17845
|
-
const word = findTriggerWord(messages);
|
|
17846
|
-
if (word) {
|
|
17847
|
-
reason = { kind: "trigger-word", word };
|
|
17848
|
-
}
|
|
17849
|
-
}
|
|
17850
|
-
if (!reason) {
|
|
17851
|
-
if (messages.length >= 6 && !hasRecentSmartSearch(messages, recentRounds)) {
|
|
17852
|
-
reason = { kind: "no-search-in-recent-rounds", rounds: recentRounds };
|
|
17853
|
-
}
|
|
17854
|
-
}
|
|
17855
|
-
if (!reason) {
|
|
17856
|
-
return { triggered: false, sessionId };
|
|
17857
|
-
}
|
|
17858
|
-
const last = lastTriggerAt.get(sessionId);
|
|
17859
|
-
if (last !== undefined && now - last < COOLDOWN_MS) {
|
|
17860
|
-
return {
|
|
17861
|
-
triggered: false,
|
|
17862
|
-
cooldown_skipped: true,
|
|
17863
|
-
reason,
|
|
17864
|
-
sessionId
|
|
17865
|
-
};
|
|
17866
|
-
}
|
|
17867
|
-
lastTriggerAt.set(sessionId, now);
|
|
17868
|
-
return { triggered: true, reason, sessionId };
|
|
17869
|
-
}
|
|
17870
|
-
function handleObserve(raw, log7) {
|
|
17871
|
-
const ctx = raw ?? {};
|
|
17872
|
-
if (!Array.isArray(ctx.messages))
|
|
17873
|
-
return null;
|
|
17874
|
-
try {
|
|
17875
|
-
const result = evaluate(ctx);
|
|
17876
|
-
if (result.triggered && result.reason) {
|
|
17877
|
-
const reasonStr = formatReason(result.reason);
|
|
17878
|
-
log7?.info(`[${PLUGIN_NAME11}] ⚡ KH 提醒(observe-only) · session=${result.sessionId} · ${reasonStr}`, {
|
|
17879
|
-
sessionId: result.sessionId,
|
|
17880
|
-
reason: result.reason,
|
|
17881
|
-
suggestion: "调用 smart_search 查项目历史;完成后用 save_chat_insight 沉淀"
|
|
17882
|
-
});
|
|
17883
|
-
}
|
|
17884
|
-
return result;
|
|
17885
|
-
} catch (err) {
|
|
17886
|
-
log7?.warn(`[${PLUGIN_NAME11}] evaluate 异常(已隔离)`, {
|
|
17887
|
-
error: err instanceof Error ? err.message : String(err)
|
|
17888
|
-
});
|
|
17889
|
-
return null;
|
|
17890
|
-
}
|
|
17891
|
-
}
|
|
17892
|
-
function formatReason(r) {
|
|
17893
|
-
switch (r.kind) {
|
|
17894
|
-
case "token-usage":
|
|
17895
|
-
return `token-usage ${(r.ratio * 100).toFixed(1)}% ≥ ${(r.threshold * 100).toFixed(0)}%`;
|
|
17896
|
-
case "trigger-word":
|
|
17897
|
-
return `trigger-word: "${r.word}"`;
|
|
17898
|
-
case "no-search-in-recent-rounds":
|
|
17899
|
-
return `no-search-in-recent-rounds (last ${r.rounds})`;
|
|
17900
|
-
}
|
|
17901
|
-
}
|
|
17902
|
-
var log7 = makePluginLogger(PLUGIN_NAME11);
|
|
17903
|
-
var khReminderServer = async (ctx) => {
|
|
17904
|
-
logLifecycle(PLUGIN_NAME11, "activate", {
|
|
17905
|
-
directory: ctx.directory,
|
|
17906
|
-
threshold: DEFAULT_THRESHOLD,
|
|
17907
|
-
cooldown_ms: COOLDOWN_MS,
|
|
17908
|
-
trigger_words_total: TRIGGER_WORDS_ZH.length + TRIGGER_WORDS_EN.length
|
|
17909
|
-
});
|
|
17910
|
-
return {
|
|
17911
|
-
"experimental.chat.messages.transform": async (_input, output) => {
|
|
17912
|
-
await safeAsync(PLUGIN_NAME11, "experimental.chat.messages.transform", async () => {
|
|
17913
|
-
const list = output.messages;
|
|
17914
|
-
if (!Array.isArray(list) || list.length === 0)
|
|
17915
|
-
return;
|
|
17916
|
-
let sessionId = "unknown";
|
|
17917
|
-
const flat = list.map((m) => {
|
|
17918
|
-
const info = m.info;
|
|
17919
|
-
if (info && typeof info.sessionID === "string" && sessionId === "unknown") {
|
|
17920
|
-
sessionId = info.sessionID;
|
|
17921
|
-
}
|
|
17922
|
-
const role = info?.role ?? "user";
|
|
17923
|
-
const parts = m.parts ?? [];
|
|
17924
|
-
const content = parts.filter((p) => p.type === "text").map((p) => p.text ?? "").join(`
|
|
17925
|
-
`);
|
|
17926
|
-
return { role, content };
|
|
17927
|
-
});
|
|
17928
|
-
const result = handleObserve({ messages: flat, sessionId }, log7);
|
|
17929
|
-
if (!result)
|
|
17930
|
-
return;
|
|
17931
|
-
safeWriteLog(PLUGIN_NAME11, {
|
|
17932
|
-
hook: "experimental.chat.messages.transform",
|
|
17933
|
-
mode: "observe-only",
|
|
17934
|
-
sessionId: result.sessionId,
|
|
17935
|
-
triggered: result.triggered,
|
|
17936
|
-
cooldown_skipped: result.cooldown_skipped ?? false,
|
|
17937
|
-
reason: result.reason ?? null,
|
|
17938
|
-
msgs: flat.length
|
|
17939
|
-
});
|
|
17940
|
-
});
|
|
17941
|
-
}
|
|
17942
|
-
};
|
|
17943
|
-
};
|
|
17944
|
-
var handler11 = khReminderServer;
|
|
17945
|
-
|
|
17946
|
-
// lib/memories.ts
|
|
17947
|
-
import { promises as fs15 } from "node:fs";
|
|
17948
|
-
import * as path19 from "node:path";
|
|
17949
|
-
import * as os5 from "node:os";
|
|
17950
|
-
function resolveConfig(c) {
|
|
17951
|
-
return {
|
|
17952
|
-
projectRoot: c.projectRoot,
|
|
17953
|
-
homeDir: c.homeDir ?? os5.homedir(),
|
|
17954
|
-
projectName: c.projectName ?? path19.basename(c.projectRoot),
|
|
17955
|
-
kh: c.kh,
|
|
17956
|
-
now: c.now ?? Date.now,
|
|
17957
|
-
log: c.log ?? (() => {}),
|
|
17958
|
-
maxPerScope: c.maxPerScope ?? 1000
|
|
17959
|
-
};
|
|
17960
|
-
}
|
|
17961
|
-
function fileFor(scope, cfg) {
|
|
17962
|
-
if (scope === "project") {
|
|
17963
|
-
return path19.join(cfg.projectRoot, ".codeforge", "memories.json");
|
|
17964
|
-
}
|
|
17965
|
-
return path19.join(cfg.homeDir, ".codeforge", "memories.json");
|
|
17966
|
-
}
|
|
17967
|
-
async function readBank(p) {
|
|
17968
|
-
try {
|
|
17969
|
-
const raw = await fs15.readFile(p, "utf8");
|
|
17970
|
-
const arr = JSON.parse(raw);
|
|
17971
|
-
if (!Array.isArray(arr))
|
|
17972
|
-
return [];
|
|
17973
|
-
return arr.filter((x) => isMemory(x));
|
|
17974
|
-
} catch {
|
|
17975
|
-
return [];
|
|
17976
|
-
}
|
|
17977
|
-
}
|
|
17978
|
-
async function writeBank(p, items) {
|
|
17979
|
-
await fs15.mkdir(path19.dirname(p), { recursive: true });
|
|
17980
|
-
const tmp = `${p}.tmp`;
|
|
17981
|
-
await fs15.writeFile(tmp, JSON.stringify(items, null, 2), "utf8");
|
|
17982
|
-
await fs15.rename(tmp, p);
|
|
17983
|
-
}
|
|
17984
|
-
function isMemory(x) {
|
|
17985
|
-
if (!x || typeof x !== "object")
|
|
17986
|
-
return false;
|
|
17987
|
-
const m = x;
|
|
17988
|
-
return typeof m.id === "string" && (m.scope === "project" || m.scope === "user") && typeof m.content === "string" && typeof m.created_at === "number" && typeof m.updated_at === "number";
|
|
17989
|
-
}
|
|
17990
|
-
var _seq = 0;
|
|
17991
|
-
function localId(now) {
|
|
17992
|
-
_seq = (_seq + 1) % 1e5;
|
|
17993
|
-
return `mem-${now}-${_seq.toString(36)}`;
|
|
17994
|
-
}
|
|
17995
|
-
async function addMemory(params, cfgRaw) {
|
|
17996
|
-
const cfg = resolveConfig(cfgRaw);
|
|
17997
|
-
if (!params.content || params.content.trim().length === 0) {
|
|
17998
|
-
return { ok: false, id: "", written_to: "local", error: "content 不能为空" };
|
|
17999
|
-
}
|
|
18000
|
-
const now = cfg.now();
|
|
18001
|
-
const base = {
|
|
18002
|
-
scope: params.scope,
|
|
18003
|
-
content: params.content.trim(),
|
|
18004
|
-
tags: params.tags?.filter((t) => typeof t === "string") ?? [],
|
|
18005
|
-
project: params.scope === "project" ? cfg.projectName : undefined,
|
|
18006
|
-
created_at: now,
|
|
18007
|
-
updated_at: now,
|
|
18008
|
-
source: params.source ?? "manual"
|
|
18009
|
-
};
|
|
18010
|
-
if (cfg.kh) {
|
|
18011
|
-
try {
|
|
18012
|
-
const r = await cfg.kh.add(base);
|
|
18013
|
-
cfg.log("info", `[memories] add → KH ${r.id}`);
|
|
18014
|
-
return { ok: true, id: r.id, written_to: "kh" };
|
|
18015
|
-
} catch (err) {
|
|
18016
|
-
cfg.log("warn", `[memories] KH 写入失败,降级本地`, {
|
|
18017
|
-
error: err instanceof Error ? err.message : String(err)
|
|
18018
|
-
});
|
|
18019
|
-
}
|
|
18020
|
-
}
|
|
18021
|
-
const file = fileFor(params.scope, cfg);
|
|
18022
|
-
const id = localId(now);
|
|
18023
|
-
const m = { id, ...base };
|
|
18024
|
-
const items = await readBank(file);
|
|
18025
|
-
items.push(m);
|
|
18026
|
-
if (items.length > cfg.maxPerScope) {
|
|
18027
|
-
items.splice(0, items.length - cfg.maxPerScope);
|
|
18028
|
-
}
|
|
18029
|
-
await writeBank(file, items);
|
|
18030
|
-
return { ok: true, id, written_to: "local" };
|
|
16956
|
+
await writeBank(file, items);
|
|
16957
|
+
return { ok: true, id, written_to: "local" };
|
|
18031
16958
|
}
|
|
18032
16959
|
async function listMemories(opts, cfgRaw) {
|
|
18033
16960
|
const cfg = resolveConfig(cfgRaw);
|
|
@@ -18142,9 +17069,9 @@ function bagOfWordsScore(query, doc) {
|
|
|
18142
17069
|
}
|
|
18143
17070
|
|
|
18144
17071
|
// plugins/memories-context.ts
|
|
18145
|
-
var
|
|
18146
|
-
var
|
|
18147
|
-
var
|
|
17072
|
+
var PLUGIN_NAME9 = "memories-context";
|
|
17073
|
+
var INJECTION_MODE = "observe-only";
|
|
17074
|
+
var DEFAULT_CONFIG3 = {
|
|
18148
17075
|
minTextLength: 8,
|
|
18149
17076
|
limit: 5,
|
|
18150
17077
|
perItemLimit: 240,
|
|
@@ -18155,7 +17082,7 @@ var DEFAULT_CONFIG5 = {
|
|
|
18155
17082
|
skipKeywords: ["你好", "hello", "hi", "thanks", "谢谢"]
|
|
18156
17083
|
};
|
|
18157
17084
|
|
|
18158
|
-
class
|
|
17085
|
+
class QueryCache {
|
|
18159
17086
|
ttlMs;
|
|
18160
17087
|
now;
|
|
18161
17088
|
map = new Map;
|
|
@@ -18180,7 +17107,7 @@ class QueryCache2 {
|
|
|
18180
17107
|
}
|
|
18181
17108
|
}
|
|
18182
17109
|
var FILLER_RE = /^(请|帮我|麻烦|我想|你能|你可以|看一?下|查一?下|how (do|can) (i|you)|can you|could you|please|help me)\s*/i;
|
|
18183
|
-
function
|
|
17110
|
+
function extractQuery(text) {
|
|
18184
17111
|
if (!text)
|
|
18185
17112
|
return "";
|
|
18186
17113
|
const norm = text.trim().replace(/\s+/g, " ");
|
|
@@ -18193,7 +17120,7 @@ function extractQuery2(text) {
|
|
|
18193
17120
|
}
|
|
18194
17121
|
return head.slice(0, 100);
|
|
18195
17122
|
}
|
|
18196
|
-
function shouldRecall(text, cfg =
|
|
17123
|
+
function shouldRecall(text, cfg = DEFAULT_CONFIG3) {
|
|
18197
17124
|
if (!text)
|
|
18198
17125
|
return false;
|
|
18199
17126
|
const t = text.trim();
|
|
@@ -18227,28 +17154,28 @@ function parseDirective(text) {
|
|
|
18227
17154
|
return { kind: "list" };
|
|
18228
17155
|
return { kind: "none" };
|
|
18229
17156
|
}
|
|
18230
|
-
async function
|
|
18231
|
-
const cfg = opts.cfg ??
|
|
18232
|
-
const cache2 = opts.cache ?? new
|
|
17157
|
+
async function handleMessage2(raw, opts) {
|
|
17158
|
+
const cfg = opts.cfg ?? DEFAULT_CONFIG3;
|
|
17159
|
+
const cache2 = opts.cache ?? new QueryCache(cfg.cacheTtlMs);
|
|
18233
17160
|
const ctx = raw ?? {};
|
|
18234
|
-
const
|
|
17161
|
+
const log7 = ctx.log;
|
|
18235
17162
|
const text = (ctx.content ?? "").trim();
|
|
18236
17163
|
if (!text)
|
|
18237
|
-
return { kind: "noop", reason: "empty", mode:
|
|
17164
|
+
return { kind: "noop", reason: "empty", mode: INJECTION_MODE };
|
|
18238
17165
|
const dir = parseDirective(text);
|
|
18239
17166
|
if (dir.kind !== "none") {
|
|
18240
|
-
return await handleDirective(dir, ctx, opts.memCfg,
|
|
17167
|
+
return await handleDirective(dir, ctx, opts.memCfg, log7);
|
|
18241
17168
|
}
|
|
18242
17169
|
if (!shouldRecall(text, cfg)) {
|
|
18243
|
-
|
|
18244
|
-
return { kind: "noop", reason: "filtered", mode:
|
|
17170
|
+
log7?.debug?.(`[${PLUGIN_NAME9}] skip (filter)`, { textLen: text.length });
|
|
17171
|
+
return { kind: "noop", reason: "filtered", mode: INJECTION_MODE };
|
|
18245
17172
|
}
|
|
18246
|
-
const query =
|
|
17173
|
+
const query = extractQuery(text);
|
|
18247
17174
|
if (!query)
|
|
18248
|
-
return { kind: "noop", reason: "empty_query", mode:
|
|
17175
|
+
return { kind: "noop", reason: "empty_query", mode: INJECTION_MODE };
|
|
18249
17176
|
if (cache2.shouldSkip(query)) {
|
|
18250
|
-
|
|
18251
|
-
return { kind: "noop", reason: "cache", mode:
|
|
17177
|
+
log7?.debug?.(`[${PLUGIN_NAME9}] cache hit`, { query });
|
|
17178
|
+
return { kind: "noop", reason: "cache", mode: INJECTION_MODE };
|
|
18252
17179
|
}
|
|
18253
17180
|
const racer = opts.scheduler?.raceTimeout ?? (async (p, ms) => {
|
|
18254
17181
|
let timer = null;
|
|
@@ -18272,43 +17199,43 @@ async function handleMessage3(raw, opts) {
|
|
|
18272
17199
|
}, opts.memCfg);
|
|
18273
17200
|
const result = await racer(injectPromise, cfg.timeoutMs);
|
|
18274
17201
|
if (result === "__timeout__") {
|
|
18275
|
-
|
|
17202
|
+
log7?.warn(`[${PLUGIN_NAME9}] timeout`, { query, ms: cfg.timeoutMs });
|
|
18276
17203
|
cache2.record(query, 0);
|
|
18277
|
-
return { kind: "noop", reason: "timeout", mode:
|
|
17204
|
+
return { kind: "noop", reason: "timeout", mode: INJECTION_MODE };
|
|
18278
17205
|
}
|
|
18279
17206
|
if (result.recalled === 0 || result.used === 0) {
|
|
18280
17207
|
cache2.record(query, 0);
|
|
18281
|
-
return { kind: "noop", reason: "no_match", mode:
|
|
17208
|
+
return { kind: "noop", reason: "no_match", mode: INJECTION_MODE };
|
|
18282
17209
|
}
|
|
18283
17210
|
if (typeof ctx.injectContext === "function") {
|
|
18284
17211
|
try {
|
|
18285
17212
|
await ctx.injectContext(result.text);
|
|
18286
17213
|
} catch (err) {
|
|
18287
|
-
|
|
17214
|
+
log7?.warn(`[${PLUGIN_NAME9}] injectContext threw`, {
|
|
18288
17215
|
error: err instanceof Error ? err.message : String(err)
|
|
18289
17216
|
});
|
|
18290
17217
|
}
|
|
18291
17218
|
}
|
|
18292
17219
|
cache2.record(query, result.recalled);
|
|
18293
|
-
|
|
18294
|
-
return { kind: "injected", payload: result, mode:
|
|
17220
|
+
log7?.info(`[${PLUGIN_NAME9}] observe-only: ${result.used}/${result.recalled} memories ready`, { query, mode: INJECTION_MODE });
|
|
17221
|
+
return { kind: "injected", payload: result, mode: INJECTION_MODE };
|
|
18295
17222
|
}
|
|
18296
|
-
async function handleDirective(dir, ctx, memCfg,
|
|
17223
|
+
async function handleDirective(dir, ctx, memCfg, log7) {
|
|
18297
17224
|
if (dir.kind === "remember") {
|
|
18298
17225
|
const r = await addMemory({ scope: dir.scope, content: dir.content, source: "directive" }, memCfg);
|
|
18299
17226
|
if (r.ok) {
|
|
18300
17227
|
const target = r.written_to === "kh" ? "KH" : "本地";
|
|
18301
17228
|
await safeReply(ctx, `\uD83E\uDDE0 已记住(${dir.scope} / ${target},id=${r.id})`);
|
|
18302
|
-
|
|
17229
|
+
log7?.info(`[${PLUGIN_NAME9}] /remember ok`, {
|
|
18303
17230
|
scope: dir.scope,
|
|
18304
17231
|
id: r.id,
|
|
18305
|
-
mode:
|
|
17232
|
+
mode: INJECTION_MODE
|
|
18306
17233
|
});
|
|
18307
17234
|
} else {
|
|
18308
17235
|
await safeReply(ctx, `❌ 记忆失败:${r.error ?? "unknown"}`);
|
|
18309
|
-
|
|
17236
|
+
log7?.warn(`[${PLUGIN_NAME9}] /remember failed`, { error: r.error });
|
|
18310
17237
|
}
|
|
18311
|
-
return { kind: "remembered", result: r, mode:
|
|
17238
|
+
return { kind: "remembered", result: r, mode: INJECTION_MODE };
|
|
18312
17239
|
}
|
|
18313
17240
|
if (dir.kind === "forget") {
|
|
18314
17241
|
const r = await removeMemory({ scope: "project", id: dir.id }, memCfg);
|
|
@@ -18318,17 +17245,17 @@ async function handleDirective(dir, ctx, memCfg, log8) {
|
|
|
18318
17245
|
const r2 = await removeMemory({ scope: "user", id: dir.id }, memCfg);
|
|
18319
17246
|
if (r2.removed) {
|
|
18320
17247
|
await safeReply(ctx, `\uD83D\uDDD1 已遗忘记忆 ${dir.id}(user scope)`);
|
|
18321
|
-
return { kind: "forgotten", result: r2, mode:
|
|
17248
|
+
return { kind: "forgotten", result: r2, mode: INJECTION_MODE };
|
|
18322
17249
|
}
|
|
18323
17250
|
await safeReply(ctx, `⚠ 找不到记忆 ${dir.id}`);
|
|
18324
17251
|
}
|
|
18325
|
-
return { kind: "forgotten", result: r, mode:
|
|
17252
|
+
return { kind: "forgotten", result: r, mode: INJECTION_MODE };
|
|
18326
17253
|
}
|
|
18327
17254
|
const proj = await listMemories({ scope: "project" }, memCfg);
|
|
18328
17255
|
const user = await listMemories({ scope: "user" }, memCfg);
|
|
18329
17256
|
const total = proj.length + user.length;
|
|
18330
17257
|
await safeReply(ctx, `\uD83D\uDCDA 共 ${total} 条记忆(project=${proj.length}, user=${user.length})`);
|
|
18331
|
-
return { kind: "listed", total, mode:
|
|
17258
|
+
return { kind: "listed", total, mode: INJECTION_MODE };
|
|
18332
17259
|
}
|
|
18333
17260
|
async function safeReply(ctx, text) {
|
|
18334
17261
|
if (typeof ctx.reply !== "function")
|
|
@@ -18337,58 +17264,58 @@ async function safeReply(ctx, text) {
|
|
|
18337
17264
|
await ctx.reply(text);
|
|
18338
17265
|
} catch {}
|
|
18339
17266
|
}
|
|
18340
|
-
logLifecycle(
|
|
18341
|
-
var
|
|
17267
|
+
logLifecycle(PLUGIN_NAME9, "import");
|
|
17268
|
+
var sharedCache = new QueryCache(DEFAULT_CONFIG3.cacheTtlMs);
|
|
18342
17269
|
function buildMemCfg(directory) {
|
|
18343
17270
|
return { projectRoot: directory };
|
|
18344
17271
|
}
|
|
18345
17272
|
var memoriesContextServer = async (ctx) => {
|
|
18346
|
-
const
|
|
17273
|
+
const log7 = makePluginLogger(PLUGIN_NAME9);
|
|
18347
17274
|
const memCfg = buildMemCfg(ctx.directory);
|
|
18348
|
-
logLifecycle(
|
|
17275
|
+
logLifecycle(PLUGIN_NAME9, "activate", {
|
|
18349
17276
|
directory: ctx.directory,
|
|
18350
17277
|
projectRoot: memCfg.projectRoot,
|
|
18351
|
-
mode:
|
|
17278
|
+
mode: INJECTION_MODE
|
|
18352
17279
|
});
|
|
18353
17280
|
return {
|
|
18354
17281
|
"chat.message": async (input, output) => {
|
|
18355
|
-
await safeAsync(
|
|
17282
|
+
await safeAsync(PLUGIN_NAME9, "chat.message", async () => {
|
|
18356
17283
|
const text = extractUserText(output);
|
|
18357
17284
|
if (!text)
|
|
18358
17285
|
return;
|
|
18359
|
-
await
|
|
17286
|
+
await handleMessage2({
|
|
18360
17287
|
content: text,
|
|
18361
17288
|
sessionId: input.sessionID,
|
|
18362
17289
|
injectContext: async (markdown) => {
|
|
18363
|
-
|
|
17290
|
+
log7.info(`memories context candidate (${markdown.length} chars)`, {
|
|
18364
17291
|
sessionID: input.sessionID,
|
|
18365
|
-
mode:
|
|
17292
|
+
mode: INJECTION_MODE,
|
|
18366
17293
|
preview: markdown.slice(0, 200)
|
|
18367
17294
|
});
|
|
18368
17295
|
},
|
|
18369
17296
|
reply: async (msg) => {
|
|
18370
|
-
|
|
17297
|
+
log7.info(`directive reply (observe-only): ${msg}`, {
|
|
18371
17298
|
sessionID: input.sessionID,
|
|
18372
|
-
mode:
|
|
17299
|
+
mode: INJECTION_MODE
|
|
18373
17300
|
});
|
|
18374
17301
|
},
|
|
18375
|
-
log:
|
|
18376
|
-
}, { cache:
|
|
17302
|
+
log: log7
|
|
17303
|
+
}, { cache: sharedCache, memCfg });
|
|
18377
17304
|
});
|
|
18378
17305
|
}
|
|
18379
17306
|
};
|
|
18380
17307
|
};
|
|
18381
|
-
var
|
|
17308
|
+
var handler9 = memoriesContextServer;
|
|
18382
17309
|
|
|
18383
17310
|
// plugins/model-fallback.ts
|
|
18384
|
-
var
|
|
17311
|
+
var PLUGIN_NAME10 = "model-fallback";
|
|
18385
17312
|
var state = {
|
|
18386
17313
|
config: null,
|
|
18387
17314
|
configPath: null,
|
|
18388
17315
|
warnings: [],
|
|
18389
17316
|
error: null
|
|
18390
17317
|
};
|
|
18391
|
-
logLifecycle(
|
|
17318
|
+
logLifecycle(PLUGIN_NAME10, "import");
|
|
18392
17319
|
function loadOnce(root) {
|
|
18393
17320
|
if (state.config !== null || state.error !== null)
|
|
18394
17321
|
return;
|
|
@@ -18398,11 +17325,11 @@ function loadOnce(root) {
|
|
|
18398
17325
|
state.configPath = r.path ?? null;
|
|
18399
17326
|
state.warnings = r.warnings;
|
|
18400
17327
|
if (r.warnings.length > 0) {
|
|
18401
|
-
safeWriteLog(
|
|
17328
|
+
safeWriteLog(PLUGIN_NAME10, { phase: "load.warnings", warnings: r.warnings });
|
|
18402
17329
|
}
|
|
18403
17330
|
} else {
|
|
18404
17331
|
state.error = r.error ?? "unknown_load_error";
|
|
18405
|
-
safeWriteLog(
|
|
17332
|
+
safeWriteLog(PLUGIN_NAME10, { phase: "load.failed", error: state.error, path: r.path });
|
|
18406
17333
|
}
|
|
18407
17334
|
}
|
|
18408
17335
|
var MODEL_ERR_PATTERNS = [
|
|
@@ -18447,9 +17374,9 @@ fallback 链已用尽:${meta.chain.join(" → ")}`;
|
|
|
18447
17374
|
完整链:${meta.chain.join(" → ")}`;
|
|
18448
17375
|
}
|
|
18449
17376
|
var modelFallbackServer = async (ctx) => {
|
|
18450
|
-
const
|
|
17377
|
+
const log7 = makePluginLogger(PLUGIN_NAME10);
|
|
18451
17378
|
loadOnce(ctx.directory ?? process.cwd());
|
|
18452
|
-
logLifecycle(
|
|
17379
|
+
logLifecycle(PLUGIN_NAME10, "activate", {
|
|
18453
17380
|
directory: ctx.directory,
|
|
18454
17381
|
config_path: state.configPath,
|
|
18455
17382
|
config_loaded: state.config !== null,
|
|
@@ -18459,7 +17386,7 @@ var modelFallbackServer = async (ctx) => {
|
|
|
18459
17386
|
});
|
|
18460
17387
|
return {
|
|
18461
17388
|
"chat.params": async (input, output) => {
|
|
18462
|
-
await safeAsync(
|
|
17389
|
+
await safeAsync(PLUGIN_NAME10, "chat.params", async () => {
|
|
18463
17390
|
if (!state.config)
|
|
18464
17391
|
return;
|
|
18465
17392
|
const agent = input.agent;
|
|
@@ -18480,7 +17407,7 @@ var modelFallbackServer = async (ctx) => {
|
|
|
18480
17407
|
next: meta.next_fallback,
|
|
18481
17408
|
source: meta.source
|
|
18482
17409
|
};
|
|
18483
|
-
safeWriteLog(
|
|
17410
|
+
safeWriteLog(PLUGIN_NAME10, {
|
|
18484
17411
|
hook: "chat.params",
|
|
18485
17412
|
agent,
|
|
18486
17413
|
model: currentModel,
|
|
@@ -18490,7 +17417,7 @@ var modelFallbackServer = async (ctx) => {
|
|
|
18490
17417
|
});
|
|
18491
17418
|
},
|
|
18492
17419
|
event: async ({ event }) => {
|
|
18493
|
-
await safeAsync(
|
|
17420
|
+
await safeAsync(PLUGIN_NAME10, "event", async () => {
|
|
18494
17421
|
if (!state.config)
|
|
18495
17422
|
return;
|
|
18496
17423
|
const e = event;
|
|
@@ -18506,8 +17433,8 @@ var modelFallbackServer = async (ctx) => {
|
|
|
18506
17433
|
const model = props.model ?? "unknown/unknown";
|
|
18507
17434
|
const meta = buildFallbackMeta(state.config, agent, model);
|
|
18508
17435
|
const suggestion = meta ? buildSuggestion(meta, String(message)) : `⚠️ ${agent}/${model} 失败:${message}`;
|
|
18509
|
-
|
|
18510
|
-
safeWriteLog(
|
|
17436
|
+
log7.warn(`[${PLUGIN_NAME10}] ${suggestion}`);
|
|
17437
|
+
safeWriteLog(PLUGIN_NAME10, {
|
|
18511
17438
|
hook: "event.error",
|
|
18512
17439
|
eventType: e.type,
|
|
18513
17440
|
agent,
|
|
@@ -18519,7 +17446,7 @@ var modelFallbackServer = async (ctx) => {
|
|
|
18519
17446
|
}
|
|
18520
17447
|
};
|
|
18521
17448
|
};
|
|
18522
|
-
var
|
|
17449
|
+
var handler10 = modelFallbackServer;
|
|
18523
17450
|
|
|
18524
17451
|
// plugins/subtask-heartbeat.ts
|
|
18525
17452
|
import { promises as fsPromises } from "node:fs";
|
|
@@ -18533,8 +17460,8 @@ var sweepExpiredSessionParents2 = sweepExpiredSessionParents;
|
|
|
18533
17460
|
var _bulkInjectSessionParentMap2 = _bulkInjectSessionParentMap;
|
|
18534
17461
|
var _capSessionParentMap2 = _capSessionParentMap;
|
|
18535
17462
|
var _setPersistRootForTests2 = _setPersistRootForTests;
|
|
18536
|
-
var
|
|
18537
|
-
logLifecycle(
|
|
17463
|
+
var PLUGIN_NAME11 = "subtask-heartbeat";
|
|
17464
|
+
logLifecycle(PLUGIN_NAME11, "import", {});
|
|
18538
17465
|
var HEARTBEAT_INTERVAL_MS2 = 30000;
|
|
18539
17466
|
var HEARTBEAT_DEBOUNCE_MS = 25000;
|
|
18540
17467
|
var TOAST_DURATION_MS2 = 5000;
|
|
@@ -18544,11 +17471,11 @@ var PENDING_TASK_MAX_PARENTS = 64;
|
|
|
18544
17471
|
var PENDING_TASK_MAX_PER_PARENT = 16;
|
|
18545
17472
|
var DESCRIPTION_MAX_LEN = 60;
|
|
18546
17473
|
var PARENT_PARSE_FAIL_MAX_LOG = 10;
|
|
18547
|
-
var
|
|
17474
|
+
var inflight2 = new Map;
|
|
18548
17475
|
var pendingTask = new Map;
|
|
18549
17476
|
var _parentParseFailLogged = 0;
|
|
18550
17477
|
function _snapshotInflight() {
|
|
18551
|
-
return [...
|
|
17478
|
+
return [...inflight2.values()].map((r) => ({ ...r }));
|
|
18552
17479
|
}
|
|
18553
17480
|
function detectUnparsedParentID(event) {
|
|
18554
17481
|
if (!event || typeof event !== "object")
|
|
@@ -18684,11 +17611,11 @@ function registerInflight(payload, now = Date.now()) {
|
|
|
18684
17611
|
lastTool: null,
|
|
18685
17612
|
pendingCalls: new Map
|
|
18686
17613
|
};
|
|
18687
|
-
|
|
17614
|
+
inflight2.set(payload.childID, r);
|
|
18688
17615
|
return r;
|
|
18689
17616
|
}
|
|
18690
17617
|
function recordToolBeat(sessionID, tool2, now = Date.now()) {
|
|
18691
|
-
const r =
|
|
17618
|
+
const r = inflight2.get(sessionID);
|
|
18692
17619
|
if (!r)
|
|
18693
17620
|
return null;
|
|
18694
17621
|
r.lastBeatAt = now;
|
|
@@ -18696,15 +17623,15 @@ function recordToolBeat(sessionID, tool2, now = Date.now()) {
|
|
|
18696
17623
|
return r;
|
|
18697
17624
|
}
|
|
18698
17625
|
function clearInflight2(sessionID) {
|
|
18699
|
-
const r =
|
|
17626
|
+
const r = inflight2.get(sessionID);
|
|
18700
17627
|
if (!r)
|
|
18701
17628
|
return null;
|
|
18702
|
-
|
|
17629
|
+
inflight2.delete(sessionID);
|
|
18703
17630
|
return r;
|
|
18704
17631
|
}
|
|
18705
17632
|
function pickHeartbeats(now = Date.now()) {
|
|
18706
17633
|
const out = [];
|
|
18707
|
-
for (const r of
|
|
17634
|
+
for (const r of inflight2.values()) {
|
|
18708
17635
|
if (now - r.lastBeatAt >= HEARTBEAT_DEBOUNCE_MS)
|
|
18709
17636
|
out.push(r);
|
|
18710
17637
|
}
|
|
@@ -18823,13 +17750,13 @@ function buildAfterLogLine(toolName, ok, durationMs, now = Date.now()) {
|
|
|
18823
17750
|
duration_ms: durationMs
|
|
18824
17751
|
});
|
|
18825
17752
|
}
|
|
18826
|
-
async function appendSubagentLog(filePath, line,
|
|
17753
|
+
async function appendSubagentLog(filePath, line, log7) {
|
|
18827
17754
|
try {
|
|
18828
17755
|
await fsPromises.mkdir(path20.dirname(filePath), { recursive: true });
|
|
18829
17756
|
await fsPromises.appendFile(filePath, line + `
|
|
18830
17757
|
`, "utf8");
|
|
18831
17758
|
} catch (err) {
|
|
18832
|
-
|
|
17759
|
+
log7?.debug?.("appendSubagentLog 失败(已隔离)", {
|
|
18833
17760
|
error: err instanceof Error ? err.message : String(err),
|
|
18834
17761
|
file: filePath
|
|
18835
17762
|
});
|
|
@@ -18854,9 +17781,9 @@ function normalizeVariant2(raw) {
|
|
|
18854
17781
|
return "default";
|
|
18855
17782
|
return raw;
|
|
18856
17783
|
}
|
|
18857
|
-
async function showToast2(client, payload,
|
|
17784
|
+
async function showToast2(client, payload, log7) {
|
|
18858
17785
|
if (typeof client?.tui?.showToast !== "function") {
|
|
18859
|
-
|
|
17786
|
+
log7?.debug?.("tui.showToast 不可用,noop");
|
|
18860
17787
|
return false;
|
|
18861
17788
|
}
|
|
18862
17789
|
try {
|
|
@@ -18870,15 +17797,15 @@ async function showToast2(client, payload, log8) {
|
|
|
18870
17797
|
});
|
|
18871
17798
|
return true;
|
|
18872
17799
|
} catch (err) {
|
|
18873
|
-
|
|
17800
|
+
log7?.warn("tui.showToast 抛错(已隔离)", {
|
|
18874
17801
|
error: err instanceof Error ? err.message : String(err)
|
|
18875
17802
|
});
|
|
18876
17803
|
return false;
|
|
18877
17804
|
}
|
|
18878
17805
|
}
|
|
18879
|
-
var
|
|
17806
|
+
var log7 = makePluginLogger(PLUGIN_NAME11);
|
|
18880
17807
|
var subtaskHeartbeatServer = async (ctx) => {
|
|
18881
|
-
logLifecycle(
|
|
17808
|
+
logLifecycle(PLUGIN_NAME11, "activate", {
|
|
18882
17809
|
directory: ctx.directory,
|
|
18883
17810
|
intervalMs: HEARTBEAT_INTERVAL_MS2
|
|
18884
17811
|
});
|
|
@@ -18895,7 +17822,7 @@ var subtaskHeartbeatServer = async (ctx) => {
|
|
|
18895
17822
|
}));
|
|
18896
17823
|
_bulkInjectSessionParentMap2(entries);
|
|
18897
17824
|
const cappedOut = _capSessionParentMap2();
|
|
18898
|
-
safeWriteLog(
|
|
17825
|
+
safeWriteLog(PLUGIN_NAME11, {
|
|
18899
17826
|
hook: "activate",
|
|
18900
17827
|
type: "parent-map.restore",
|
|
18901
17828
|
restored: restored.size,
|
|
@@ -18903,27 +17830,27 @@ var subtaskHeartbeatServer = async (ctx) => {
|
|
|
18903
17830
|
});
|
|
18904
17831
|
}
|
|
18905
17832
|
} catch (err) {
|
|
18906
|
-
|
|
17833
|
+
log7.warn("loadParentMap 失败(已隔离),降级为空表", {
|
|
18907
17834
|
error: err instanceof Error ? err.message : String(err)
|
|
18908
17835
|
});
|
|
18909
17836
|
}
|
|
18910
17837
|
const interval = setInterval(() => {
|
|
18911
|
-
safeAsync(
|
|
17838
|
+
safeAsync(PLUGIN_NAME11, "interval", async () => {
|
|
18912
17839
|
const swept = sweepExpiredPendingTasks();
|
|
18913
17840
|
if (swept > 0) {
|
|
18914
|
-
safeWriteLog(
|
|
17841
|
+
safeWriteLog(PLUGIN_NAME11, { hook: "interval", pending_task_swept: swept });
|
|
18915
17842
|
}
|
|
18916
17843
|
const sweptParents = sweepExpiredSessionParents2();
|
|
18917
17844
|
if (sweptParents > 0) {
|
|
18918
|
-
safeWriteLog(
|
|
17845
|
+
safeWriteLog(PLUGIN_NAME11, { hook: "interval", session_parent_swept: sweptParents });
|
|
18919
17846
|
}
|
|
18920
17847
|
const beats = pickHeartbeats();
|
|
18921
17848
|
if (beats.length === 0)
|
|
18922
17849
|
return;
|
|
18923
17850
|
for (const r of beats) {
|
|
18924
17851
|
const t = buildHeartbeatToast(r);
|
|
18925
|
-
const sent = await showToast2(client, t,
|
|
18926
|
-
safeWriteLog(
|
|
17852
|
+
const sent = await showToast2(client, t, log7);
|
|
17853
|
+
safeWriteLog(PLUGIN_NAME11, {
|
|
18927
17854
|
hook: "interval",
|
|
18928
17855
|
child: r.childID,
|
|
18929
17856
|
parent: r.parentID,
|
|
@@ -18940,15 +17867,15 @@ var subtaskHeartbeatServer = async (ctx) => {
|
|
|
18940
17867
|
}
|
|
18941
17868
|
return {
|
|
18942
17869
|
event: async ({ event }) => {
|
|
18943
|
-
await safeAsync(
|
|
17870
|
+
await safeAsync(PLUGIN_NAME11, "event", async () => {
|
|
18944
17871
|
try {
|
|
18945
17872
|
if (detectUnparsedParentID(event) && _parentParseFailLogged < PARENT_PARSE_FAIL_MAX_LOG) {
|
|
18946
17873
|
_parentParseFailLogged++;
|
|
18947
|
-
|
|
17874
|
+
log7.warn("session.created 含 parentID 关键字但 extractCreatedChild 解析失败,可能 SDK schema 变更", {
|
|
18948
17875
|
sample_count: _parentParseFailLogged,
|
|
18949
17876
|
hint: "如频繁出现请检查 plugins/subtask-heartbeat.ts::extractCreatedChild 是否适配新 SDK"
|
|
18950
17877
|
});
|
|
18951
|
-
safeWriteLog(
|
|
17878
|
+
safeWriteLog(PLUGIN_NAME11, {
|
|
18952
17879
|
hook: "event",
|
|
18953
17880
|
type: "session.created.unparsed-parent-id",
|
|
18954
17881
|
sample_count: _parentParseFailLogged
|
|
@@ -18960,7 +17887,7 @@ var subtaskHeartbeatServer = async (ctx) => {
|
|
|
18960
17887
|
try {
|
|
18961
17888
|
recordSessionParent2(created.childID, created.parentID);
|
|
18962
17889
|
} catch (err) {
|
|
18963
|
-
|
|
17890
|
+
log7.warn("recordSessionParent 抛错(已隔离)", {
|
|
18964
17891
|
error: err instanceof Error ? err.message : String(err)
|
|
18965
17892
|
});
|
|
18966
17893
|
}
|
|
@@ -18971,7 +17898,7 @@ var subtaskHeartbeatServer = async (ctx) => {
|
|
|
18971
17898
|
agent: pending?.agent ?? created.agent,
|
|
18972
17899
|
description: pending?.description ?? null
|
|
18973
17900
|
});
|
|
18974
|
-
safeWriteLog(
|
|
17901
|
+
safeWriteLog(PLUGIN_NAME11, {
|
|
18975
17902
|
hook: "event",
|
|
18976
17903
|
type: "session.created",
|
|
18977
17904
|
child: created.childID,
|
|
@@ -18981,8 +17908,8 @@ var subtaskHeartbeatServer = async (ctx) => {
|
|
|
18981
17908
|
description_len: record.description?.length ?? 0
|
|
18982
17909
|
});
|
|
18983
17910
|
const startToast = buildStartToast(record);
|
|
18984
|
-
const sent = await showToast2(client, { ...startToast, duration: START_TOAST_DURATION_MS },
|
|
18985
|
-
safeWriteLog(
|
|
17911
|
+
const sent = await showToast2(client, { ...startToast, duration: START_TOAST_DURATION_MS }, log7);
|
|
17912
|
+
safeWriteLog(PLUGIN_NAME11, {
|
|
18986
17913
|
hook: "event",
|
|
18987
17914
|
type: "session.created.toast",
|
|
18988
17915
|
child: created.childID,
|
|
@@ -19001,8 +17928,8 @@ var subtaskHeartbeatServer = async (ctx) => {
|
|
|
19001
17928
|
const r = clearInflight2(ended.sessionID);
|
|
19002
17929
|
if (r) {
|
|
19003
17930
|
const t = buildEndToast(r, ended.type);
|
|
19004
|
-
const sent = await showToast2(client, t,
|
|
19005
|
-
safeWriteLog(
|
|
17931
|
+
const sent = await showToast2(client, t, log7);
|
|
17932
|
+
safeWriteLog(PLUGIN_NAME11, {
|
|
19006
17933
|
hook: "event",
|
|
19007
17934
|
type: ended.type,
|
|
19008
17935
|
child: r.childID,
|
|
@@ -19020,14 +17947,14 @@ var subtaskHeartbeatServer = async (ctx) => {
|
|
|
19020
17947
|
},
|
|
19021
17948
|
"tool.execute.before": async (input, output) => {
|
|
19022
17949
|
const isTaskTool = input?.tool === "task";
|
|
19023
|
-
if (
|
|
17950
|
+
if (inflight2.size === 0 && !isTaskTool)
|
|
19024
17951
|
return;
|
|
19025
|
-
await safeAsync(
|
|
17952
|
+
await safeAsync(PLUGIN_NAME11, "tool.execute.before", async () => {
|
|
19026
17953
|
if (!input || typeof input.sessionID !== "string" || typeof input.tool !== "string")
|
|
19027
17954
|
return;
|
|
19028
17955
|
if (isTaskTool) {
|
|
19029
17956
|
const args = output?.args ?? null;
|
|
19030
|
-
safeWriteLog(
|
|
17957
|
+
safeWriteLog(PLUGIN_NAME11, {
|
|
19031
17958
|
hook: "tool.execute.before.task",
|
|
19032
17959
|
sessionID: input.sessionID,
|
|
19033
17960
|
args_keys: args && typeof args === "object" ? Object.keys(args) : null,
|
|
@@ -19039,7 +17966,7 @@ var subtaskHeartbeatServer = async (ctx) => {
|
|
|
19039
17966
|
agent: extracted.subagentType,
|
|
19040
17967
|
description: extracted.description
|
|
19041
17968
|
});
|
|
19042
|
-
safeWriteLog(
|
|
17969
|
+
safeWriteLog(PLUGIN_NAME11, {
|
|
19043
17970
|
hook: "tool.execute.before.task.enqueued",
|
|
19044
17971
|
parent: input.sessionID,
|
|
19045
17972
|
agent: extracted.subagentType,
|
|
@@ -19047,7 +17974,7 @@ var subtaskHeartbeatServer = async (ctx) => {
|
|
|
19047
17974
|
});
|
|
19048
17975
|
}
|
|
19049
17976
|
}
|
|
19050
|
-
const rec =
|
|
17977
|
+
const rec = inflight2.get(input.sessionID);
|
|
19051
17978
|
if (rec) {
|
|
19052
17979
|
recordToolBeat(input.sessionID, input.tool);
|
|
19053
17980
|
if (!rec.pendingCalls)
|
|
@@ -19059,18 +17986,18 @@ var subtaskHeartbeatServer = async (ctx) => {
|
|
|
19059
17986
|
const args = output?.args ?? null;
|
|
19060
17987
|
const line = buildBeforeLogLine(input.tool, args);
|
|
19061
17988
|
const file = subagentLogPath(cwd, rec.parentID, rec.childID);
|
|
19062
|
-
appendSubagentLog(file, line,
|
|
17989
|
+
appendSubagentLog(file, line, log7);
|
|
19063
17990
|
}
|
|
19064
17991
|
}
|
|
19065
17992
|
});
|
|
19066
17993
|
},
|
|
19067
17994
|
"tool.execute.after": async (input, _output) => {
|
|
19068
|
-
if (
|
|
17995
|
+
if (inflight2.size === 0)
|
|
19069
17996
|
return;
|
|
19070
|
-
await safeAsync(
|
|
17997
|
+
await safeAsync(PLUGIN_NAME11, "tool.execute.after", async () => {
|
|
19071
17998
|
if (!input || typeof input.sessionID !== "string" || typeof input.tool !== "string")
|
|
19072
17999
|
return;
|
|
19073
|
-
const rec =
|
|
18000
|
+
const rec = inflight2.get(input.sessionID);
|
|
19074
18001
|
if (!rec)
|
|
19075
18002
|
return;
|
|
19076
18003
|
let durationMs = 0;
|
|
@@ -19084,17 +18011,17 @@ var subtaskHeartbeatServer = async (ctx) => {
|
|
|
19084
18011
|
if (isLogPersistenceEnabled(cwd)) {
|
|
19085
18012
|
const line = buildAfterLogLine(input.tool, true, durationMs);
|
|
19086
18013
|
const file = subagentLogPath(cwd, rec.parentID, rec.childID);
|
|
19087
|
-
appendSubagentLog(file, line,
|
|
18014
|
+
appendSubagentLog(file, line, log7);
|
|
19088
18015
|
}
|
|
19089
18016
|
});
|
|
19090
18017
|
}
|
|
19091
18018
|
};
|
|
19092
18019
|
};
|
|
19093
|
-
var
|
|
18020
|
+
var handler11 = subtaskHeartbeatServer;
|
|
19094
18021
|
|
|
19095
18022
|
// plugins/parallel-status.ts
|
|
19096
|
-
var
|
|
19097
|
-
logLifecycle(
|
|
18023
|
+
var PLUGIN_NAME12 = "parallel-status";
|
|
18024
|
+
logLifecycle(PLUGIN_NAME12, "import");
|
|
19098
18025
|
var ID_MAX_LEN = 16;
|
|
19099
18026
|
var ID_KEEP_LEN = 13;
|
|
19100
18027
|
function shortId(s) {
|
|
@@ -19126,8 +18053,8 @@ function formatInflightMarkdown(snapshot, now = Date.now()) {
|
|
|
19126
18053
|
`);
|
|
19127
18054
|
}
|
|
19128
18055
|
var parallelStatusServer = async (ctx) => {
|
|
19129
|
-
const
|
|
19130
|
-
logLifecycle(
|
|
18056
|
+
const log8 = makePluginLogger(PLUGIN_NAME12);
|
|
18057
|
+
logLifecycle(PLUGIN_NAME12, "activate", { directory: ctx.directory });
|
|
19131
18058
|
return {
|
|
19132
18059
|
"command.execute.before": async (input, output) => {
|
|
19133
18060
|
try {
|
|
@@ -19146,23 +18073,23 @@ var parallelStatusServer = async (ctx) => {
|
|
|
19146
18073
|
synthetic: false
|
|
19147
18074
|
});
|
|
19148
18075
|
}
|
|
19149
|
-
|
|
18076
|
+
log8.info(`[${PLUGIN_NAME12}] 已回写 ${snapshot.length} 条 inflight`);
|
|
19150
18077
|
} catch (err) {
|
|
19151
|
-
|
|
18078
|
+
log8.error(`[${PLUGIN_NAME12}] command.execute.before 异常(已隔离)`, {
|
|
19152
18079
|
error: err instanceof Error ? err.message : String(err)
|
|
19153
18080
|
});
|
|
19154
18081
|
}
|
|
19155
18082
|
}
|
|
19156
18083
|
};
|
|
19157
18084
|
};
|
|
19158
|
-
var
|
|
18085
|
+
var handler12 = parallelStatusServer;
|
|
19159
18086
|
|
|
19160
18087
|
// plugins/parallel-tool-nudge.ts
|
|
19161
18088
|
import { readFileSync as readFileSync4, readdirSync as readdirSync2, statSync as statSync4 } from "node:fs";
|
|
19162
18089
|
import { join as join17 } from "node:path";
|
|
19163
18090
|
import { homedir as homedir6 } from "node:os";
|
|
19164
|
-
var
|
|
19165
|
-
logLifecycle(
|
|
18091
|
+
var PLUGIN_NAME13 = "parallel-tool-nudge";
|
|
18092
|
+
logLifecycle(PLUGIN_NAME13, "import", {});
|
|
19166
18093
|
var PARALLEL_SAFE_TOOLS = [
|
|
19167
18094
|
"read",
|
|
19168
18095
|
"smart_search",
|
|
@@ -19224,7 +18151,7 @@ function loadAgentToolsMap(rootDir, opts = {}) {
|
|
|
19224
18151
|
const result = new Map;
|
|
19225
18152
|
const safeSet = new Set(PARALLEL_SAFE_TOOLS);
|
|
19226
18153
|
const unionTools = new Set;
|
|
19227
|
-
const
|
|
18154
|
+
const log8 = makePluginLogger(PLUGIN_NAME13);
|
|
19228
18155
|
for (const dir of candidateDirs) {
|
|
19229
18156
|
if (!dirExists(dir))
|
|
19230
18157
|
continue;
|
|
@@ -19232,7 +18159,7 @@ function loadAgentToolsMap(rootDir, opts = {}) {
|
|
|
19232
18159
|
try {
|
|
19233
18160
|
entries = dirReader(dir);
|
|
19234
18161
|
} catch (err) {
|
|
19235
|
-
|
|
18162
|
+
log8.warn(`agents 目录读取失败(已跳过)`, {
|
|
19236
18163
|
dir,
|
|
19237
18164
|
error: err instanceof Error ? err.message : String(err)
|
|
19238
18165
|
});
|
|
@@ -19246,7 +18173,7 @@ function loadAgentToolsMap(rootDir, opts = {}) {
|
|
|
19246
18173
|
try {
|
|
19247
18174
|
content = reader(path21);
|
|
19248
18175
|
} catch (err) {
|
|
19249
|
-
|
|
18176
|
+
log8.warn(`agent.md 读取失败(已跳过)`, {
|
|
19250
18177
|
path: path21,
|
|
19251
18178
|
error: err instanceof Error ? err.message : String(err)
|
|
19252
18179
|
});
|
|
@@ -19254,7 +18181,7 @@ function loadAgentToolsMap(rootDir, opts = {}) {
|
|
|
19254
18181
|
}
|
|
19255
18182
|
const parsed = parseAgentFrontmatter(content);
|
|
19256
18183
|
if (!parsed) {
|
|
19257
|
-
|
|
18184
|
+
log8.warn(`agent frontmatter 解析失败(已跳过)`, { path: path21 });
|
|
19258
18185
|
continue;
|
|
19259
18186
|
}
|
|
19260
18187
|
if (result.has(parsed.name))
|
|
@@ -19332,7 +18259,7 @@ This directive is re-injected every turn — it is not optional advice.
|
|
|
19332
18259
|
return body.slice(0, NUDGE_MAX_LEN2 - 4) + `
|
|
19333
18260
|
…`;
|
|
19334
18261
|
}
|
|
19335
|
-
var
|
|
18262
|
+
var log8 = makePluginLogger(PLUGIN_NAME13);
|
|
19336
18263
|
var parallelToolNudgeServer = async (ctx) => {
|
|
19337
18264
|
try {
|
|
19338
18265
|
const loaded = loadAgentToolsMap(ctx.directory ?? process.cwd());
|
|
@@ -19340,19 +18267,19 @@ var parallelToolNudgeServer = async (ctx) => {
|
|
|
19340
18267
|
for (const [k, v] of loaded.entries())
|
|
19341
18268
|
agentToolsMap.set(k, v);
|
|
19342
18269
|
} catch (err) {
|
|
19343
|
-
|
|
18270
|
+
log8.warn(`loadAgentToolsMap 失败(plugin 将 no-op)`, {
|
|
19344
18271
|
error: err instanceof Error ? err.message : String(err)
|
|
19345
18272
|
});
|
|
19346
18273
|
}
|
|
19347
18274
|
const realAgentCount = Math.max(0, agentToolsMap.size - (agentToolsMap.has(DEFAULT_AGENT_KEY) ? 1 : 0));
|
|
19348
18275
|
if (realAgentCount === 0) {
|
|
19349
18276
|
agentToolsMap.clear();
|
|
19350
|
-
|
|
18277
|
+
log8.warn(`0 real agents loaded; plugin will be no-op for this session`, {
|
|
19351
18278
|
directory: ctx.directory
|
|
19352
18279
|
});
|
|
19353
18280
|
}
|
|
19354
18281
|
const defaultTools = agentToolsMap.get(DEFAULT_AGENT_KEY) ?? [];
|
|
19355
|
-
logLifecycle(
|
|
18282
|
+
logLifecycle(PLUGIN_NAME13, "activate", {
|
|
19356
18283
|
directory: ctx.directory,
|
|
19357
18284
|
real_agents_loaded: realAgentCount,
|
|
19358
18285
|
default_tools_union: defaultTools,
|
|
@@ -19363,7 +18290,7 @@ var parallelToolNudgeServer = async (ctx) => {
|
|
|
19363
18290
|
});
|
|
19364
18291
|
return {
|
|
19365
18292
|
"chat.params": async (input, _output) => {
|
|
19366
|
-
await safeAsync(
|
|
18293
|
+
await safeAsync(PLUGIN_NAME13, "chat.params", async () => {
|
|
19367
18294
|
if (agentToolsMap.size === 0)
|
|
19368
18295
|
return;
|
|
19369
18296
|
const sid = input?.sessionID;
|
|
@@ -19375,7 +18302,7 @@ var parallelToolNudgeServer = async (ctx) => {
|
|
|
19375
18302
|
});
|
|
19376
18303
|
},
|
|
19377
18304
|
"experimental.chat.system.transform": async (input, output) => {
|
|
19378
|
-
await safeAsync(
|
|
18305
|
+
await safeAsync(PLUGIN_NAME13, "experimental.chat.system.transform", async () => {
|
|
19379
18306
|
if (agentToolsMap.size === 0)
|
|
19380
18307
|
return;
|
|
19381
18308
|
if (!output || !Array.isArray(output.system))
|
|
@@ -19385,7 +18312,7 @@ var parallelToolNudgeServer = async (ctx) => {
|
|
|
19385
18312
|
const tools = agent !== "unknown" ? agentToolsMap.get(agent) ?? agentToolsMap.get(DEFAULT_AGENT_KEY) ?? [] : agentToolsMap.get(DEFAULT_AGENT_KEY) ?? [];
|
|
19386
18313
|
const nudge = renderNudge(agent, tools);
|
|
19387
18314
|
output.system.push(nudge);
|
|
19388
|
-
safeWriteLog(
|
|
18315
|
+
safeWriteLog(PLUGIN_NAME13, {
|
|
19389
18316
|
hook: "experimental.chat.system.transform",
|
|
19390
18317
|
sessionID: sid,
|
|
19391
18318
|
agent,
|
|
@@ -19397,11 +18324,11 @@ var parallelToolNudgeServer = async (ctx) => {
|
|
|
19397
18324
|
}
|
|
19398
18325
|
};
|
|
19399
18326
|
};
|
|
19400
|
-
var
|
|
18327
|
+
var handler13 = parallelToolNudgeServer;
|
|
19401
18328
|
|
|
19402
18329
|
// plugins/pwsh-utf8.ts
|
|
19403
|
-
var
|
|
19404
|
-
logLifecycle(
|
|
18330
|
+
var PLUGIN_NAME14 = "pwsh-utf8";
|
|
18331
|
+
logLifecycle(PLUGIN_NAME14, "import", {});
|
|
19405
18332
|
var PRELUDE = "chcp 65001 *> $null; " + "[Console]::OutputEncoding = [System.Text.UTF8Encoding]::new(); " + "$OutputEncoding = [System.Text.UTF8Encoding]::new(); ";
|
|
19406
18333
|
function prependUtf8Prelude(command) {
|
|
19407
18334
|
if (typeof command !== "string")
|
|
@@ -19414,15 +18341,15 @@ function prependUtf8Prelude(command) {
|
|
|
19414
18341
|
return command;
|
|
19415
18342
|
return PRELUDE + command;
|
|
19416
18343
|
}
|
|
19417
|
-
var
|
|
18344
|
+
var handler14 = async (_ctx3) => {
|
|
19418
18345
|
const enabled = process.platform === "win32" && process.env.CODEFORGE_DISABLE_PWSH_UTF8 !== "1";
|
|
19419
18346
|
const reason = enabled ? "win32" : process.platform !== "win32" ? "non-win32" : "disabled-by-env";
|
|
19420
|
-
logLifecycle(
|
|
18347
|
+
logLifecycle(PLUGIN_NAME14, "activate", { enabled, platform: process.platform, reason });
|
|
19421
18348
|
if (!enabled)
|
|
19422
18349
|
return {};
|
|
19423
18350
|
return {
|
|
19424
18351
|
"tool.execute.before": async (input, output) => {
|
|
19425
|
-
await safeAsync(
|
|
18352
|
+
await safeAsync(PLUGIN_NAME14, "tool.execute.before", async () => {
|
|
19426
18353
|
if (input.tool !== "bash")
|
|
19427
18354
|
return;
|
|
19428
18355
|
const args = output.args ?? {};
|
|
@@ -19431,7 +18358,7 @@ var handler17 = async (_ctx3) => {
|
|
|
19431
18358
|
if (next !== undefined && next !== original) {
|
|
19432
18359
|
args["command"] = next;
|
|
19433
18360
|
output.args = args;
|
|
19434
|
-
safeWriteLog(
|
|
18361
|
+
safeWriteLog(PLUGIN_NAME14, {
|
|
19435
18362
|
hook: "tool.execute.before",
|
|
19436
18363
|
tool: input.tool,
|
|
19437
18364
|
callID: input.callID,
|
|
@@ -19587,7 +18514,7 @@ function buildRecoveryPlan(events, meta = { id: null }, opts = {}) {
|
|
|
19587
18514
|
for (let i = events.length - 1;i >= 0; i--) {
|
|
19588
18515
|
const e = events[i];
|
|
19589
18516
|
if (e.type === "message" && e.role === "user") {
|
|
19590
|
-
lastUser =
|
|
18517
|
+
lastUser = clip4(e.content, o.excerptLimit);
|
|
19591
18518
|
break;
|
|
19592
18519
|
}
|
|
19593
18520
|
}
|
|
@@ -19605,7 +18532,7 @@ function buildRecoveryPlan(events, meta = { id: null }, opts = {}) {
|
|
|
19605
18532
|
const toolMap = new Map;
|
|
19606
18533
|
for (const t of window) {
|
|
19607
18534
|
const cur = toolMap.get(t.tool);
|
|
19608
|
-
const argsExcerpt =
|
|
18535
|
+
const argsExcerpt = clip4(safeJson(t.args), o.excerptLimit);
|
|
19609
18536
|
if (cur) {
|
|
19610
18537
|
cur.count++;
|
|
19611
18538
|
cur.last_ok = t.ok;
|
|
@@ -19621,7 +18548,7 @@ function buildRecoveryPlan(events, meta = { id: null }, opts = {}) {
|
|
|
19621
18548
|
});
|
|
19622
18549
|
}
|
|
19623
18550
|
}
|
|
19624
|
-
const
|
|
18551
|
+
const inflight3 = [...toolMap.values()].sort((a, b) => b.last_ts - a.last_ts);
|
|
19625
18552
|
const proposed = window.some((t) => PENDING_CHANGES_TOOLS.has(t.tool));
|
|
19626
18553
|
const applied = window.some((t) => APPLY_TOOLS.has(t.tool) && t.ok);
|
|
19627
18554
|
const pending_changes_likely = proposed && !applied;
|
|
@@ -19645,7 +18572,7 @@ function buildRecoveryPlan(events, meta = { id: null }, opts = {}) {
|
|
|
19645
18572
|
idleMs,
|
|
19646
18573
|
lastUser,
|
|
19647
18574
|
lastAgent,
|
|
19648
|
-
inflight:
|
|
18575
|
+
inflight: inflight3,
|
|
19649
18576
|
pending_changes_likely,
|
|
19650
18577
|
open_subtasks_likely,
|
|
19651
18578
|
reason
|
|
@@ -19656,7 +18583,7 @@ function buildRecoveryPlan(events, meta = { id: null }, opts = {}) {
|
|
|
19656
18583
|
idle_ms: idleMs,
|
|
19657
18584
|
last_user_intent: lastUser,
|
|
19658
18585
|
last_agent: lastAgent,
|
|
19659
|
-
inflight_tools:
|
|
18586
|
+
inflight_tools: inflight3,
|
|
19660
18587
|
pending_changes_likely,
|
|
19661
18588
|
open_subtasks_likely,
|
|
19662
18589
|
summary
|
|
@@ -19694,7 +18621,7 @@ function formatIdle(ms) {
|
|
|
19694
18621
|
return `${Math.round(ms / 3600000)}h`;
|
|
19695
18622
|
return `${Math.round(ms / 86400000)}d`;
|
|
19696
18623
|
}
|
|
19697
|
-
function
|
|
18624
|
+
function clip4(s, max) {
|
|
19698
18625
|
if (!s)
|
|
19699
18626
|
return "";
|
|
19700
18627
|
if (s.length <= max)
|
|
@@ -19847,8 +18774,8 @@ async function markBlocksConsumed(absRoot, entries) {
|
|
|
19847
18774
|
}
|
|
19848
18775
|
|
|
19849
18776
|
// plugins/session-recovery.ts
|
|
19850
|
-
var
|
|
19851
|
-
logLifecycle(
|
|
18777
|
+
var PLUGIN_NAME15 = "session-recovery";
|
|
18778
|
+
logLifecycle(PLUGIN_NAME15, "import", {});
|
|
19852
18779
|
async function processSessionStart(currentSessionId, opts = {}) {
|
|
19853
18780
|
if (opts.disabled) {
|
|
19854
18781
|
return { ok: true, injected: false, reason: "disabled" };
|
|
@@ -19858,7 +18785,7 @@ async function processSessionStart(currentSessionId, opts = {}) {
|
|
|
19858
18785
|
excludeIds.add(currentSessionId);
|
|
19859
18786
|
const r = await scanLastSession({ ...opts, excludeIds: [...excludeIds] });
|
|
19860
18787
|
if (!r.ok) {
|
|
19861
|
-
opts.log?.warn?.(`[${
|
|
18788
|
+
opts.log?.warn?.(`[${PLUGIN_NAME15}] 扫描失败:${r.error}`);
|
|
19862
18789
|
return { ok: false, injected: false, reason: "scan_error", error: r.error };
|
|
19863
18790
|
}
|
|
19864
18791
|
const plan = r.plan;
|
|
@@ -19867,7 +18794,7 @@ async function processSessionStart(currentSessionId, opts = {}) {
|
|
|
19867
18794
|
try {
|
|
19868
18795
|
pendingBlocks = await scanBlockPending(opts.root);
|
|
19869
18796
|
} catch (err) {
|
|
19870
|
-
opts.log?.warn?.(`[${
|
|
18797
|
+
opts.log?.warn?.(`[${PLUGIN_NAME15}] scanBlockPending 异常:${err instanceof Error ? err.message : String(err)}`);
|
|
19871
18798
|
}
|
|
19872
18799
|
}
|
|
19873
18800
|
const hasPendingBlocks = pendingBlocks.length > 0;
|
|
@@ -19885,7 +18812,7 @@ async function processSessionStart(currentSessionId, opts = {}) {
|
|
|
19885
18812
|
await opts.injectRecovery(injection);
|
|
19886
18813
|
} catch (err) {
|
|
19887
18814
|
const msg = err instanceof Error ? err.message : String(err);
|
|
19888
|
-
opts.log?.warn?.(`[${
|
|
18815
|
+
opts.log?.warn?.(`[${PLUGIN_NAME15}] injectRecovery 异常:${msg}`);
|
|
19889
18816
|
return { ok: false, injected: false, plan, reason: "inject_error", error: msg, pendingBlocks };
|
|
19890
18817
|
}
|
|
19891
18818
|
}
|
|
@@ -19893,7 +18820,7 @@ async function processSessionStart(currentSessionId, opts = {}) {
|
|
|
19893
18820
|
try {
|
|
19894
18821
|
await markBlocksConsumed(opts.root, pendingBlocks);
|
|
19895
18822
|
} catch (err) {
|
|
19896
|
-
opts.log?.warn?.(`[${
|
|
18823
|
+
opts.log?.warn?.(`[${PLUGIN_NAME15}] markBlocksConsumed 异常:${err instanceof Error ? err.message : String(err)}`);
|
|
19897
18824
|
}
|
|
19898
18825
|
}
|
|
19899
18826
|
return { ok: true, injected: true, plan, reason: "ok", pendingBlocks };
|
|
@@ -19940,13 +18867,13 @@ function renderPrompt(plan) {
|
|
|
19940
18867
|
return lines.join(`
|
|
19941
18868
|
`);
|
|
19942
18869
|
}
|
|
19943
|
-
var
|
|
18870
|
+
var log9 = makePluginLogger(PLUGIN_NAME15);
|
|
19944
18871
|
var _lastInjection = null;
|
|
19945
18872
|
var sessionRecoveryServer = async (ctx) => {
|
|
19946
|
-
logLifecycle(
|
|
18873
|
+
logLifecycle(PLUGIN_NAME15, "activate", { directory: ctx.directory });
|
|
19947
18874
|
return {
|
|
19948
18875
|
event: async ({ event }) => {
|
|
19949
|
-
await safeAsync(
|
|
18876
|
+
await safeAsync(PLUGIN_NAME15, "event", async () => {
|
|
19950
18877
|
const e = event;
|
|
19951
18878
|
if (!e || typeof e.type !== "string")
|
|
19952
18879
|
return;
|
|
@@ -19956,12 +18883,12 @@ var sessionRecoveryServer = async (ctx) => {
|
|
|
19956
18883
|
const root = typeof e.properties?.["root"] === "string" ? e.properties["root"] : ctx.directory ?? process.cwd();
|
|
19957
18884
|
const r = await processSessionStart(sid, {
|
|
19958
18885
|
root,
|
|
19959
|
-
log:
|
|
18886
|
+
log: log9,
|
|
19960
18887
|
injectRecovery: (inj) => {
|
|
19961
18888
|
_lastInjection = inj;
|
|
19962
18889
|
}
|
|
19963
18890
|
});
|
|
19964
|
-
safeWriteLog(
|
|
18891
|
+
safeWriteLog(PLUGIN_NAME15, {
|
|
19965
18892
|
hook: "event",
|
|
19966
18893
|
type: "session.start",
|
|
19967
18894
|
ok: r.ok,
|
|
@@ -19971,13 +18898,13 @@ var sessionRecoveryServer = async (ctx) => {
|
|
|
19971
18898
|
pending_blocks_count: r.pendingBlocks?.length ?? 0
|
|
19972
18899
|
});
|
|
19973
18900
|
if (r.injected && r.plan) {
|
|
19974
|
-
|
|
18901
|
+
log9.info(`[${PLUGIN_NAME15}] 注入恢复提示(last=${r.plan.last_session_id?.slice(0, 8) ?? "?"}, reason=${r.plan.reason}, pending_blocks=${r.pendingBlocks?.length ?? 0})`);
|
|
19975
18902
|
}
|
|
19976
18903
|
});
|
|
19977
18904
|
}
|
|
19978
18905
|
};
|
|
19979
18906
|
};
|
|
19980
|
-
var
|
|
18907
|
+
var handler15 = sessionRecoveryServer;
|
|
19981
18908
|
|
|
19982
18909
|
// plugins/subtasks.ts
|
|
19983
18910
|
import { promises as fs18 } from "node:fs";
|
|
@@ -19995,7 +18922,7 @@ function resolveMergeFns(deps) {
|
|
|
19995
18922
|
worktreeHasChangesFn: deps.worktreeHasChanges ?? worktreeHasChanges
|
|
19996
18923
|
};
|
|
19997
18924
|
}
|
|
19998
|
-
async function mergeOneAttempt(r, opts, mergeFns,
|
|
18925
|
+
async function mergeOneAttempt(r, opts, mergeFns, log10) {
|
|
19999
18926
|
const t0 = Date.now();
|
|
20000
18927
|
const root = opts.mergeRoot;
|
|
20001
18928
|
const {
|
|
@@ -20077,7 +19004,7 @@ async function mergeOneAttempt(r, opts, mergeFns, log11) {
|
|
|
20077
19004
|
await mergeAbortFn({ root });
|
|
20078
19005
|
} catch (abortErr) {
|
|
20079
19006
|
abortFailed = true;
|
|
20080
|
-
|
|
19007
|
+
log10("error", `[parallel-merge] mergeAbort 失败(仓库可能锁死)`, {
|
|
20081
19008
|
id: r.id,
|
|
20082
19009
|
error: describe6(abortErr)
|
|
20083
19010
|
});
|
|
@@ -20093,13 +19020,13 @@ async function mergeOneAttempt(r, opts, mergeFns, log11) {
|
|
|
20093
19020
|
}
|
|
20094
19021
|
attempt.ok = true;
|
|
20095
19022
|
attempt.durationMs = Date.now() - t0;
|
|
20096
|
-
await safeRemoveWorktree(removeWorktreeFn, root, wt,
|
|
19023
|
+
await safeRemoveWorktree(removeWorktreeFn, root, wt, log10, r.id);
|
|
20097
19024
|
return { attempt };
|
|
20098
19025
|
} else {
|
|
20099
19026
|
attempt.ok = true;
|
|
20100
19027
|
attempt.skippedReason = "no_changes";
|
|
20101
19028
|
attempt.durationMs = Date.now() - t0;
|
|
20102
|
-
await safeRemoveWorktree(removeWorktreeFn, root, wt,
|
|
19029
|
+
await safeRemoveWorktree(removeWorktreeFn, root, wt, log10, r.id);
|
|
20103
19030
|
return { attempt };
|
|
20104
19031
|
}
|
|
20105
19032
|
} catch (err) {
|
|
@@ -20113,7 +19040,7 @@ async function mergeOneAttempt(r, opts, mergeFns, log11) {
|
|
|
20113
19040
|
}
|
|
20114
19041
|
}
|
|
20115
19042
|
async function mergeWorktrees(results, opts, deps) {
|
|
20116
|
-
const
|
|
19043
|
+
const log10 = deps.log ?? (() => {});
|
|
20117
19044
|
const root = opts.mergeRoot;
|
|
20118
19045
|
const mergeFns = resolveMergeFns(deps);
|
|
20119
19046
|
const report = {
|
|
@@ -20155,10 +19082,10 @@ async function mergeWorktrees(results, opts, deps) {
|
|
|
20155
19082
|
};
|
|
20156
19083
|
report.attempts.push(attempt);
|
|
20157
19084
|
report.skipped++;
|
|
20158
|
-
await fireMergeAttempt(opts.onMergeAttempt, attempt, idx++,
|
|
19085
|
+
await fireMergeAttempt(opts.onMergeAttempt, attempt, idx++, log10);
|
|
20159
19086
|
}
|
|
20160
19087
|
for (const r of mergeable) {
|
|
20161
|
-
const { attempt, pendingItem } = await mergeOneAttempt(r, { mergeRoot: root }, mergeFns,
|
|
19088
|
+
const { attempt, pendingItem } = await mergeOneAttempt(r, { mergeRoot: root }, mergeFns, log10);
|
|
20162
19089
|
report.attempts.push(attempt);
|
|
20163
19090
|
if (attempt.ok && !attempt.skippedReason)
|
|
20164
19091
|
report.merged++;
|
|
@@ -20168,12 +19095,12 @@ async function mergeWorktrees(results, opts, deps) {
|
|
|
20168
19095
|
report.conflicted++;
|
|
20169
19096
|
if (pendingItem)
|
|
20170
19097
|
report.pendingWorktrees.push(pendingItem);
|
|
20171
|
-
await fireMergeAttempt(opts.onMergeAttempt, attempt, idx++,
|
|
19098
|
+
await fireMergeAttempt(opts.onMergeAttempt, attempt, idx++, log10);
|
|
20172
19099
|
}
|
|
20173
19100
|
return report;
|
|
20174
19101
|
}
|
|
20175
19102
|
function createMergeQueue(opts, deps = {}) {
|
|
20176
|
-
const
|
|
19103
|
+
const log10 = deps.log ?? (() => {});
|
|
20177
19104
|
const mergeFns = {
|
|
20178
19105
|
commitWorktreeIfDirtyFn: deps.commitWorktreeIfDirtyFn ?? commitWorktreeIfDirty,
|
|
20179
19106
|
tryMergeFn: deps.tryMergeFn ?? tryMerge,
|
|
@@ -20214,10 +19141,10 @@ function createMergeQueue(opts, deps = {}) {
|
|
|
20214
19141
|
conflicts: []
|
|
20215
19142
|
});
|
|
20216
19143
|
}
|
|
20217
|
-
await fireMergeAttempt(opts.onMergeAttempt, attempt2, idx++,
|
|
19144
|
+
await fireMergeAttempt(opts.onMergeAttempt, attempt2, idx++, log10);
|
|
20218
19145
|
return;
|
|
20219
19146
|
}
|
|
20220
|
-
const { attempt, abortFailed, pendingItem } = await mergeOneAttempt(result, { mergeRoot: opts.mergeRoot, summaryCharLimit: opts.summaryCharLimit }, mergeFns,
|
|
19147
|
+
const { attempt, abortFailed, pendingItem } = await mergeOneAttempt(result, { mergeRoot: opts.mergeRoot, summaryCharLimit: opts.summaryCharLimit }, mergeFns, log10);
|
|
20221
19148
|
report.attempts.push(attempt);
|
|
20222
19149
|
if (attempt.ok && !attempt.skippedReason)
|
|
20223
19150
|
report.merged++;
|
|
@@ -20229,11 +19156,11 @@ function createMergeQueue(opts, deps = {}) {
|
|
|
20229
19156
|
report.pendingWorktrees.push(pendingItem);
|
|
20230
19157
|
if (abortFailed) {
|
|
20231
19158
|
queueAborted = true;
|
|
20232
|
-
|
|
19159
|
+
log10("error", `[parallel-merge] queue aborted (mergeAbort failed)`, {
|
|
20233
19160
|
id: result.id
|
|
20234
19161
|
});
|
|
20235
19162
|
}
|
|
20236
|
-
await fireMergeAttempt(opts.onMergeAttempt, attempt, idx++,
|
|
19163
|
+
await fireMergeAttempt(opts.onMergeAttempt, attempt, idx++, log10);
|
|
20237
19164
|
});
|
|
20238
19165
|
},
|
|
20239
19166
|
async flushAndReport() {
|
|
@@ -20242,23 +19169,23 @@ function createMergeQueue(opts, deps = {}) {
|
|
|
20242
19169
|
}
|
|
20243
19170
|
};
|
|
20244
19171
|
}
|
|
20245
|
-
async function safeRemoveWorktree(fn, root, wt,
|
|
19172
|
+
async function safeRemoveWorktree(fn, root, wt, log10, subtaskId) {
|
|
20246
19173
|
try {
|
|
20247
19174
|
await fn({ root, worktree_path: wt, force: true });
|
|
20248
19175
|
} catch (err) {
|
|
20249
|
-
|
|
19176
|
+
log10("warn", `[parallel] removeWorktree 失败 ${subtaskId}`, {
|
|
20250
19177
|
error: describe6(err),
|
|
20251
19178
|
worktree: wt
|
|
20252
19179
|
});
|
|
20253
19180
|
}
|
|
20254
19181
|
}
|
|
20255
|
-
async function fireMergeAttempt(cb, attempt, idx,
|
|
19182
|
+
async function fireMergeAttempt(cb, attempt, idx, log10) {
|
|
20256
19183
|
if (!cb)
|
|
20257
19184
|
return;
|
|
20258
19185
|
try {
|
|
20259
19186
|
await cb(attempt, idx);
|
|
20260
19187
|
} catch (err) {
|
|
20261
|
-
|
|
19188
|
+
log10("warn", `[parallel] onMergeAttempt 抛错(已隔离)`, {
|
|
20262
19189
|
id: attempt.subtaskId,
|
|
20263
19190
|
error: describe6(err)
|
|
20264
19191
|
});
|
|
@@ -20284,7 +19211,7 @@ async function schedule(opts) {
|
|
|
20284
19211
|
}
|
|
20285
19212
|
const now = opts.deps.now ?? Date.now;
|
|
20286
19213
|
const start = now();
|
|
20287
|
-
const
|
|
19214
|
+
const log10 = opts.deps.log ?? (() => {});
|
|
20288
19215
|
const concurrency = Math.max(1, opts.maxConcurrency ?? 4);
|
|
20289
19216
|
const totalTimeout = opts.totalTimeout_ms ?? 30 * 60000;
|
|
20290
19217
|
const limit = Math.max(1, opts.summaryCharLimit ?? 500);
|
|
@@ -20317,7 +19244,7 @@ async function schedule(opts) {
|
|
|
20317
19244
|
mergeAbortFn: opts.deps.mergeAbort,
|
|
20318
19245
|
removeWorktreeFn: opts.deps.removeWorktree,
|
|
20319
19246
|
worktreeHasChangesFn: opts.deps.worktreeHasChanges,
|
|
20320
|
-
log:
|
|
19247
|
+
log: log10
|
|
20321
19248
|
});
|
|
20322
19249
|
}
|
|
20323
19250
|
const fireFinish = async (i, res) => {
|
|
@@ -20325,7 +19252,7 @@ async function schedule(opts) {
|
|
|
20325
19252
|
try {
|
|
20326
19253
|
queue.enqueue(res);
|
|
20327
19254
|
} catch (err) {
|
|
20328
|
-
|
|
19255
|
+
log10("warn", `[parallel] queue.enqueue 抛错(已隔离)`, {
|
|
20329
19256
|
id: res.id,
|
|
20330
19257
|
error: describe7(err)
|
|
20331
19258
|
});
|
|
@@ -20336,7 +19263,7 @@ async function schedule(opts) {
|
|
|
20336
19263
|
try {
|
|
20337
19264
|
await opts.onSubtaskFinish(res, i);
|
|
20338
19265
|
} catch (err) {
|
|
20339
|
-
|
|
19266
|
+
log10("warn", `[parallel] onSubtaskFinish 抛错(已隔离)`, {
|
|
20340
19267
|
id: res.id,
|
|
20341
19268
|
error: describe7(err)
|
|
20342
19269
|
});
|
|
@@ -20366,7 +19293,7 @@ async function schedule(opts) {
|
|
|
20366
19293
|
try {
|
|
20367
19294
|
await opts.onSubtaskStart(spec, i);
|
|
20368
19295
|
} catch (err) {
|
|
20369
|
-
|
|
19296
|
+
log10("warn", `[parallel] onSubtaskStart 抛错(已隔离)`, {
|
|
20370
19297
|
id: spec.id,
|
|
20371
19298
|
error: describe7(err)
|
|
20372
19299
|
});
|
|
@@ -20417,7 +19344,7 @@ async function schedule(opts) {
|
|
|
20417
19344
|
try {
|
|
20418
19345
|
await alloc.cleanup();
|
|
20419
19346
|
} catch (err) {
|
|
20420
|
-
|
|
19347
|
+
log10("warn", `[parallel] worktree 清理失败 ${spec.id}`, {
|
|
20421
19348
|
error: describe7(err)
|
|
20422
19349
|
});
|
|
20423
19350
|
}
|
|
@@ -20597,20 +19524,20 @@ function buildSystemPrompt(maxSubtasks) {
|
|
|
20597
19524
|
`);
|
|
20598
19525
|
}
|
|
20599
19526
|
async function decomposeTask(description30, opts) {
|
|
20600
|
-
const
|
|
19527
|
+
const log10 = opts.log ?? (() => {});
|
|
20601
19528
|
if (opts.mockResponse) {
|
|
20602
|
-
return validateAndFinalize(opts.mockResponse, undefined,
|
|
19529
|
+
return validateAndFinalize(opts.mockResponse, undefined, log10, opts.maxSubtasks ?? DEFAULT_MAX_SUBTASKS);
|
|
20603
19530
|
}
|
|
20604
19531
|
const maxSubtasks = opts.maxSubtasks ?? DEFAULT_MAX_SUBTASKS;
|
|
20605
19532
|
const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS2;
|
|
20606
19533
|
let childSessionId;
|
|
20607
19534
|
try {
|
|
20608
19535
|
const created = await opts.client.session.create({
|
|
20609
|
-
body: { title: `decompose:${
|
|
19536
|
+
body: { title: `decompose:${clip5(description30, 60)}` },
|
|
20610
19537
|
query: opts.directory ? { directory: opts.directory } : undefined
|
|
20611
19538
|
});
|
|
20612
19539
|
if (created.error || !created.data?.id) {
|
|
20613
|
-
|
|
19540
|
+
log10("warn", "[decompose] session.create 失败", { error: describe8(created.error) });
|
|
20614
19541
|
return {
|
|
20615
19542
|
ok: false,
|
|
20616
19543
|
subtasks: [],
|
|
@@ -20632,22 +19559,22 @@ async function decomposeTask(description30, opts) {
|
|
|
20632
19559
|
sleep2(timeoutMs).then(() => ({ kind: "timeout" }))
|
|
20633
19560
|
]);
|
|
20634
19561
|
if (raced.kind === "timeout") {
|
|
20635
|
-
|
|
19562
|
+
log10("warn", "[decompose] LLM 调用超时", { timeoutMs });
|
|
20636
19563
|
return { ok: false, subtasks: [], reason: "llm_unavailable" };
|
|
20637
19564
|
}
|
|
20638
19565
|
const r = raced.value;
|
|
20639
19566
|
if (r.error || !r.data) {
|
|
20640
|
-
|
|
19567
|
+
log10("warn", "[decompose] session.prompt 返回错误", { error: describe8(r.error) });
|
|
20641
19568
|
return { ok: false, subtasks: [], reason: "llm_unavailable" };
|
|
20642
19569
|
}
|
|
20643
19570
|
const rawText = pickLastText2(r.data.parts ?? []);
|
|
20644
19571
|
if (!rawText) {
|
|
20645
|
-
|
|
19572
|
+
log10("warn", "[decompose] LLM 输出为空");
|
|
20646
19573
|
return { ok: false, subtasks: [], reason: "parse_failed", raw: "" };
|
|
20647
19574
|
}
|
|
20648
19575
|
const parsed = extractJson(rawText);
|
|
20649
19576
|
if (!parsed) {
|
|
20650
|
-
|
|
19577
|
+
log10("warn", "[decompose] JSON 解析失败", { raw: clip5(rawText, 200) });
|
|
20651
19578
|
return { ok: false, subtasks: [], reason: "parse_failed", raw: rawText };
|
|
20652
19579
|
}
|
|
20653
19580
|
if (parsed && typeof parsed === "object" && parsed.single_task === true) {
|
|
@@ -20655,7 +19582,7 @@ async function decomposeTask(description30, opts) {
|
|
|
20655
19582
|
}
|
|
20656
19583
|
const subtasksRaw = parsed.subtasks;
|
|
20657
19584
|
if (!Array.isArray(subtasksRaw)) {
|
|
20658
|
-
|
|
19585
|
+
log10("warn", "[decompose] JSON 缺 subtasks 数组");
|
|
20659
19586
|
return { ok: false, subtasks: [], reason: "parse_failed", raw: rawText };
|
|
20660
19587
|
}
|
|
20661
19588
|
const normalized = [];
|
|
@@ -20671,12 +19598,12 @@ async function decomposeTask(description30, opts) {
|
|
|
20671
19598
|
normalized.push({ description: desc, hintFiles });
|
|
20672
19599
|
}
|
|
20673
19600
|
if (normalized.length > maxSubtasks) {
|
|
20674
|
-
|
|
19601
|
+
log10("warn", "[decompose] LLM 返回子任务超过上限", { count: normalized.length, maxSubtasks });
|
|
20675
19602
|
return { ok: false, subtasks: [], reason: "parse_failed", raw: rawText };
|
|
20676
19603
|
}
|
|
20677
|
-
return validateAndFinalize(normalized, rawText,
|
|
19604
|
+
return validateAndFinalize(normalized, rawText, log10, maxSubtasks);
|
|
20678
19605
|
} catch (err) {
|
|
20679
|
-
|
|
19606
|
+
log10("warn", "[decompose] 抛错", { error: describe8(err) });
|
|
20680
19607
|
return { ok: false, subtasks: [], reason: "llm_unavailable" };
|
|
20681
19608
|
} finally {
|
|
20682
19609
|
if (childSessionId) {
|
|
@@ -20686,12 +19613,12 @@ async function decomposeTask(description30, opts) {
|
|
|
20686
19613
|
query: opts.directory ? { directory: opts.directory } : undefined
|
|
20687
19614
|
});
|
|
20688
19615
|
} catch (err) {
|
|
20689
|
-
|
|
19616
|
+
log10("warn", "[decompose] session.delete 失败", { error: describe8(err) });
|
|
20690
19617
|
}
|
|
20691
19618
|
}
|
|
20692
19619
|
}
|
|
20693
19620
|
}
|
|
20694
|
-
function validateAndFinalize(subtasks, raw,
|
|
19621
|
+
function validateAndFinalize(subtasks, raw, log10, maxSubtasks) {
|
|
20695
19622
|
if (subtasks.length < 2) {
|
|
20696
19623
|
return { ok: false, subtasks: [], reason: "single_task", raw };
|
|
20697
19624
|
}
|
|
@@ -20701,7 +19628,7 @@ function validateAndFinalize(subtasks, raw, log11, maxSubtasks) {
|
|
|
20701
19628
|
}
|
|
20702
19629
|
}
|
|
20703
19630
|
if (subtasks.length > maxSubtasks) {
|
|
20704
|
-
|
|
19631
|
+
log10("warn", "[decompose] LLM 返回子任务超过上限", { count: subtasks.length, maxSubtasks });
|
|
20705
19632
|
return { ok: false, subtasks: [], reason: "parse_failed", raw };
|
|
20706
19633
|
}
|
|
20707
19634
|
for (let i = 0;i < subtasks.length; i++) {
|
|
@@ -20715,7 +19642,7 @@ function validateAndFinalize(subtasks, raw, log11, maxSubtasks) {
|
|
|
20715
19642
|
continue;
|
|
20716
19643
|
for (const f of b) {
|
|
20717
19644
|
if (setA.has(f.trim())) {
|
|
20718
|
-
|
|
19645
|
+
log10("info", "[decompose] hintFiles 有交集,降级 single_task", {
|
|
20719
19646
|
a: subtasks[i].description,
|
|
20720
19647
|
b: subtasks[j].description,
|
|
20721
19648
|
file: f
|
|
@@ -20780,7 +19707,7 @@ function tryParse(s) {
|
|
|
20780
19707
|
return null;
|
|
20781
19708
|
}
|
|
20782
19709
|
}
|
|
20783
|
-
function
|
|
19710
|
+
function clip5(s, n) {
|
|
20784
19711
|
if (!s)
|
|
20785
19712
|
return "";
|
|
20786
19713
|
return s.length <= n ? s : s.slice(0, n - 1) + "…";
|
|
@@ -20804,7 +19731,7 @@ function sleep2(ms) {
|
|
|
20804
19731
|
|
|
20805
19732
|
// plugins/subtasks.ts
|
|
20806
19733
|
init_runtime_paths();
|
|
20807
|
-
var
|
|
19734
|
+
var PLUGIN_NAME16 = "subtasks";
|
|
20808
19735
|
function getLogFile(root = process.cwd()) {
|
|
20809
19736
|
return path23.join(runtimeDir(root), "logs", "subtasks.log");
|
|
20810
19737
|
}
|
|
@@ -20870,7 +19797,7 @@ var mockRunner = async (spec) => {
|
|
|
20870
19797
|
};
|
|
20871
19798
|
async function handleParallelCommand(raw) {
|
|
20872
19799
|
const ctx = raw ?? {};
|
|
20873
|
-
const
|
|
19800
|
+
const log10 = ctx.log;
|
|
20874
19801
|
const args = ctx.args ?? {};
|
|
20875
19802
|
const parentId = (args.parentId ?? `task-${Date.now().toString(36)}`).replace(/[^a-zA-Z0-9._-]/g, "-");
|
|
20876
19803
|
const rawDescription = typeof args.description === "string" ? args.description.trim() : "";
|
|
@@ -20884,7 +19811,7 @@ async function handleParallelCommand(raw) {
|
|
|
20884
19811
|
specs = splitDescriptions(args.description, { parentId });
|
|
20885
19812
|
}
|
|
20886
19813
|
if (specs.length === 0) {
|
|
20887
|
-
|
|
19814
|
+
log10?.warn(`[${PLUGIN_NAME16}] /parallel 缺有效子任务`);
|
|
20888
19815
|
await safeReply2(ctx, "⚠ /parallel 需要至少 1 个子任务(用 ; 或换行分隔)");
|
|
20889
19816
|
return { ok: false, reason: "no_subtasks" };
|
|
20890
19817
|
}
|
|
@@ -20910,7 +19837,7 @@ async function handleParallelCommand(raw) {
|
|
|
20910
19837
|
if (!gitOk) {
|
|
20911
19838
|
autoMerge = false;
|
|
20912
19839
|
downgradedAutoMerge = true;
|
|
20913
|
-
|
|
19840
|
+
log10?.warn("[subtasks] mergeRoot 非 git 仓库,降级 autoMerge=false");
|
|
20914
19841
|
const canNoticeEarly = Boolean(ctx.client && ctx.parentSessionID);
|
|
20915
19842
|
if (canNoticeEarly) {
|
|
20916
19843
|
await sendParentNotice(ctx.client, ctx.parentSessionID, "ℹ 当前目录不是 git 仓库,worktree 隔离已自动关闭,以单目录模式运行", { directory: ctx.directory });
|
|
@@ -20919,17 +19846,17 @@ async function handleParallelCommand(raw) {
|
|
|
20919
19846
|
}
|
|
20920
19847
|
const canNotice = Boolean(ctx.client && ctx.parentSessionID);
|
|
20921
19848
|
if (specs.length === 1 && rawDescription.length >= 30 && ctx.client) {
|
|
20922
|
-
|
|
19849
|
+
log10?.info(`[${PLUGIN_NAME16}] 触发 AI 拆分(描述长度=${rawDescription.length})`);
|
|
20923
19850
|
const dec = await decomposeTask(rawDescription, {
|
|
20924
19851
|
client: ctx.client,
|
|
20925
19852
|
directory: ctx.directory,
|
|
20926
19853
|
log: (lvl, msg, data) => {
|
|
20927
19854
|
if (lvl === "error")
|
|
20928
|
-
|
|
19855
|
+
log10?.error(msg, data);
|
|
20929
19856
|
else if (lvl === "warn")
|
|
20930
|
-
|
|
19857
|
+
log10?.warn(msg, data);
|
|
20931
19858
|
else
|
|
20932
|
-
|
|
19859
|
+
log10?.info(msg, data);
|
|
20933
19860
|
},
|
|
20934
19861
|
mockResponse: ctx.decomposeMockResponse
|
|
20935
19862
|
});
|
|
@@ -20940,7 +19867,7 @@ async function handleParallelCommand(raw) {
|
|
|
20940
19867
|
timeout_ms: undefined,
|
|
20941
19868
|
args: d.hintFiles && d.hintFiles.length > 0 ? { hintFiles: d.hintFiles } : undefined
|
|
20942
19869
|
}));
|
|
20943
|
-
|
|
19870
|
+
log10?.info(`[${PLUGIN_NAME16}] AI 拆分成功 → ${specs.length} 个子任务`);
|
|
20944
19871
|
if (canNotice) {
|
|
20945
19872
|
const lines = [
|
|
20946
19873
|
`\uD83E\uDD16 AI 已自动拆为 ${specs.length} 个并行子任务:`,
|
|
@@ -20952,7 +19879,7 @@ async function handleParallelCommand(raw) {
|
|
|
20952
19879
|
});
|
|
20953
19880
|
}
|
|
20954
19881
|
} else if (dec.reason === "llm_unavailable" || dec.reason === "parse_failed") {
|
|
20955
|
-
|
|
19882
|
+
log10?.warn(`[${PLUGIN_NAME16}] AI 拆分失败(${dec.reason}),按单任务执行`);
|
|
20956
19883
|
}
|
|
20957
19884
|
}
|
|
20958
19885
|
if (canNotice) {
|
|
@@ -20967,11 +19894,11 @@ async function handleParallelCommand(raw) {
|
|
|
20967
19894
|
directory: ctx.directory,
|
|
20968
19895
|
log: (lvl, msg, data) => {
|
|
20969
19896
|
if (lvl === "error")
|
|
20970
|
-
|
|
19897
|
+
log10?.error(msg, data);
|
|
20971
19898
|
else if (lvl === "warn")
|
|
20972
|
-
|
|
19899
|
+
log10?.warn(msg, data);
|
|
20973
19900
|
else
|
|
20974
|
-
|
|
19901
|
+
log10?.info(msg, data);
|
|
20975
19902
|
}
|
|
20976
19903
|
});
|
|
20977
19904
|
}
|
|
@@ -21015,11 +19942,11 @@ async function handleParallelCommand(raw) {
|
|
|
21015
19942
|
allocateWorktree: downgradedAutoMerge ? undefined : ctx.allocateWorktree,
|
|
21016
19943
|
log: (lvl, msg, data) => {
|
|
21017
19944
|
if (lvl === "error")
|
|
21018
|
-
|
|
19945
|
+
log10?.error(msg, data);
|
|
21019
19946
|
else if (lvl === "warn")
|
|
21020
|
-
|
|
19947
|
+
log10?.warn(msg, data);
|
|
21021
19948
|
else
|
|
21022
|
-
|
|
19949
|
+
log10?.info(msg, data);
|
|
21023
19950
|
},
|
|
21024
19951
|
tryMerge: ctx.tryMerge,
|
|
21025
19952
|
mergeCommit: ctx.mergeCommit,
|
|
@@ -21031,7 +19958,7 @@ async function handleParallelCommand(raw) {
|
|
|
21031
19958
|
});
|
|
21032
19959
|
} catch (err) {
|
|
21033
19960
|
const msg = err instanceof Error ? err.message : String(err);
|
|
21034
|
-
|
|
19961
|
+
log10?.error(`[${PLUGIN_NAME16}] schedule 抛错`, { error: msg });
|
|
21035
19962
|
await safeReply2(ctx, `❌ 并发调度失败:${msg}`);
|
|
21036
19963
|
return { ok: false, reason: msg };
|
|
21037
19964
|
}
|
|
@@ -21049,7 +19976,7 @@ async function handleParallelCommand(raw) {
|
|
|
21049
19976
|
}
|
|
21050
19977
|
await safeReply2(ctx, summaryLines.join(`
|
|
21051
19978
|
`));
|
|
21052
|
-
|
|
19979
|
+
log10?.info(`[${PLUGIN_NAME16}] schedule 完成`, {
|
|
21053
19980
|
parentId,
|
|
21054
19981
|
success: result.digest.success,
|
|
21055
19982
|
failed: result.digest.failed,
|
|
@@ -21062,7 +19989,7 @@ async function handleParallelCommand(raw) {
|
|
|
21062
19989
|
try {
|
|
21063
19990
|
await ctx.onCompleted(result);
|
|
21064
19991
|
} catch (err) {
|
|
21065
|
-
|
|
19992
|
+
log10?.warn(`[${PLUGIN_NAME16}] onCompleted hook 抛错(已隔离)`, {
|
|
21066
19993
|
error: err instanceof Error ? err.message : String(err)
|
|
21067
19994
|
});
|
|
21068
19995
|
}
|
|
@@ -21104,7 +20031,7 @@ async function writeLog(level, msg, data) {
|
|
|
21104
20031
|
const line = JSON.stringify({
|
|
21105
20032
|
ts: new Date().toISOString(),
|
|
21106
20033
|
level,
|
|
21107
|
-
plugin:
|
|
20034
|
+
plugin: PLUGIN_NAME16,
|
|
21108
20035
|
msg,
|
|
21109
20036
|
data
|
|
21110
20037
|
}) + `
|
|
@@ -21115,11 +20042,11 @@ async function writeLog(level, msg, data) {
|
|
|
21115
20042
|
await fs18.appendFile(logFile, line, "utf8");
|
|
21116
20043
|
} catch {}
|
|
21117
20044
|
}
|
|
21118
|
-
logLifecycle(
|
|
20045
|
+
logLifecycle(PLUGIN_NAME16, "import");
|
|
21119
20046
|
var subtasksServer = async (ctx) => {
|
|
21120
|
-
const
|
|
20047
|
+
const log10 = makePluginLogger(PLUGIN_NAME16);
|
|
21121
20048
|
const client = ctx?.client ?? undefined;
|
|
21122
|
-
logLifecycle(
|
|
20049
|
+
logLifecycle(PLUGIN_NAME16, "activate", {
|
|
21123
20050
|
directory: ctx.directory,
|
|
21124
20051
|
hasClient: Boolean(client)
|
|
21125
20052
|
});
|
|
@@ -21144,7 +20071,7 @@ var subtasksServer = async (ctx) => {
|
|
|
21144
20071
|
const gitOk = await isGitRepo({ root: repoRoot }).catch(() => false);
|
|
21145
20072
|
if (!gitOk) {
|
|
21146
20073
|
autoMerge = false;
|
|
21147
|
-
|
|
20074
|
+
log10?.warn("[subtasks] worktree_isolation=true 但非 git 仓库,自动降级 autoMerge=false");
|
|
21148
20075
|
const canNotice = Boolean(client);
|
|
21149
20076
|
if (canNotice) {
|
|
21150
20077
|
await sendParentNotice(client, input.sessionID, "ℹ 当前目录不是 git 仓库,worktree 隔离已自动关闭,以单目录模式运行", { directory: ctx.directory });
|
|
@@ -21173,7 +20100,7 @@ var subtasksServer = async (ctx) => {
|
|
|
21173
20100
|
replyLines.push(s);
|
|
21174
20101
|
return Promise.resolve();
|
|
21175
20102
|
},
|
|
21176
|
-
log:
|
|
20103
|
+
log: log10,
|
|
21177
20104
|
client,
|
|
21178
20105
|
parentSessionID: input.sessionID,
|
|
21179
20106
|
directory: ctx.directory,
|
|
@@ -21186,11 +20113,11 @@ var subtasksServer = async (ctx) => {
|
|
|
21186
20113
|
perTaskTimeoutMs: 5 * 60000,
|
|
21187
20114
|
log: (lvl, msg, data) => {
|
|
21188
20115
|
if (lvl === "error")
|
|
21189
|
-
|
|
20116
|
+
log10.error(msg, data);
|
|
21190
20117
|
else if (lvl === "warn")
|
|
21191
|
-
|
|
20118
|
+
log10.warn(msg, data);
|
|
21192
20119
|
else
|
|
21193
|
-
|
|
20120
|
+
log10.info(msg, data);
|
|
21194
20121
|
}
|
|
21195
20122
|
}) : undefined
|
|
21196
20123
|
};
|
|
@@ -21216,20 +20143,20 @@ var subtasksServer = async (ctx) => {
|
|
|
21216
20143
|
});
|
|
21217
20144
|
}
|
|
21218
20145
|
} catch (err) {
|
|
21219
|
-
|
|
20146
|
+
log10.error(`[${PLUGIN_NAME16}] command.execute.before 异常(已隔离)`, {
|
|
21220
20147
|
error: err instanceof Error ? err.message : String(err)
|
|
21221
20148
|
});
|
|
21222
20149
|
}
|
|
21223
20150
|
}
|
|
21224
20151
|
};
|
|
21225
20152
|
};
|
|
21226
|
-
var
|
|
20153
|
+
var handler16 = subtasksServer;
|
|
21227
20154
|
|
|
21228
20155
|
// plugins/terminal-monitor.ts
|
|
21229
|
-
import * as
|
|
21230
|
-
var
|
|
21231
|
-
logLifecycle(
|
|
21232
|
-
var
|
|
20156
|
+
import * as crypto4 from "node:crypto";
|
|
20157
|
+
var PLUGIN_NAME17 = "terminal-monitor";
|
|
20158
|
+
logLifecycle(PLUGIN_NAME17, "import", {});
|
|
20159
|
+
var DEFAULT_CONFIG4 = {
|
|
21233
20160
|
minScore: 0.6,
|
|
21234
20161
|
cooldownMs: 30000,
|
|
21235
20162
|
lruLimit: 256,
|
|
@@ -21344,7 +20271,7 @@ var DEFAULT_RULES = [
|
|
|
21344
20271
|
score: 0.3
|
|
21345
20272
|
}
|
|
21346
20273
|
];
|
|
21347
|
-
function normalizeLines(raw, maxLines =
|
|
20274
|
+
function normalizeLines(raw, maxLines = DEFAULT_CONFIG4.maxLines) {
|
|
21348
20275
|
if (!raw)
|
|
21349
20276
|
return [];
|
|
21350
20277
|
const stripped = raw.replace(/\u001b\[[0-9;?]*[A-Za-z]/g, "");
|
|
@@ -21355,7 +20282,7 @@ function normalizeLines(raw, maxLines = DEFAULT_CONFIG6.maxLines) {
|
|
|
21355
20282
|
return [...lines.slice(0, half), `... [省略 ${lines.length - maxLines} 行] ...`, ...lines.slice(-half)];
|
|
21356
20283
|
}
|
|
21357
20284
|
function parseTerminalOutput(ev, cfg = {}, rules = DEFAULT_RULES) {
|
|
21358
|
-
const c = { ...
|
|
20285
|
+
const c = { ...DEFAULT_CONFIG4, ...cfg };
|
|
21359
20286
|
const stdoutLines = normalizeLines(ev.stdout, c.maxLines);
|
|
21360
20287
|
const stderrLines = normalizeLines(ev.stderr, c.maxLines);
|
|
21361
20288
|
const text = [...stdoutLines, ...stderrLines].join(`
|
|
@@ -21371,7 +20298,7 @@ function parseTerminalOutput(ev, cfg = {}, rules = DEFAULT_RULES) {
|
|
|
21371
20298
|
`).length;
|
|
21372
20299
|
const lineFromStart = text.split(`
|
|
21373
20300
|
`)[lineIdx - 1] ?? m[0];
|
|
21374
|
-
const excerpt =
|
|
20301
|
+
const excerpt = clip6(lineFromStart.trim(), c.maxExcerpt);
|
|
21375
20302
|
if (!out.has(rule.kind)) {
|
|
21376
20303
|
out.set(rule.kind, {
|
|
21377
20304
|
severity: rule.severity,
|
|
@@ -21393,16 +20320,16 @@ function parseTerminalOutput(ev, cfg = {}, rules = DEFAULT_RULES) {
|
|
|
21393
20320
|
}
|
|
21394
20321
|
return [...out.values()].sort((a, b) => b.score - a.score);
|
|
21395
20322
|
}
|
|
21396
|
-
function
|
|
20323
|
+
function clip6(s, max) {
|
|
21397
20324
|
if (s.length <= max)
|
|
21398
20325
|
return s;
|
|
21399
20326
|
return s.slice(0, max - 1) + "…";
|
|
21400
20327
|
}
|
|
21401
20328
|
|
|
21402
|
-
class
|
|
20329
|
+
class FingerprintLRU {
|
|
21403
20330
|
limit;
|
|
21404
20331
|
map = new Map;
|
|
21405
|
-
constructor(limit =
|
|
20332
|
+
constructor(limit = DEFAULT_CONFIG4.lruLimit) {
|
|
21406
20333
|
this.limit = limit;
|
|
21407
20334
|
}
|
|
21408
20335
|
add(fp, ts = Date.now()) {
|
|
@@ -21428,10 +20355,10 @@ class FingerprintLRU2 {
|
|
|
21428
20355
|
}
|
|
21429
20356
|
function fingerprintFinding(cmd, f) {
|
|
21430
20357
|
const raw = `${cmd ?? ""}|${f.kind}|${f.excerpt.slice(0, 60)}`;
|
|
21431
|
-
return
|
|
20358
|
+
return crypto4.createHash("sha1").update(raw).digest("hex").slice(0, 16);
|
|
21432
20359
|
}
|
|
21433
20360
|
function shouldNotify(findings, ev, lru, cfg = {}, now = Date.now()) {
|
|
21434
|
-
const c = { ...
|
|
20361
|
+
const c = { ...DEFAULT_CONFIG4, ...cfg };
|
|
21435
20362
|
const out = [];
|
|
21436
20363
|
let suppressed = 0;
|
|
21437
20364
|
for (const f of findings) {
|
|
@@ -21452,7 +20379,7 @@ function shouldNotify(findings, ev, lru, cfg = {}, now = Date.now()) {
|
|
|
21452
20379
|
function buildSummary(ev, findings) {
|
|
21453
20380
|
const parts = [];
|
|
21454
20381
|
if (ev.cmd)
|
|
21455
|
-
parts.push(`\`${
|
|
20382
|
+
parts.push(`\`${clip6(ev.cmd, 60)}\``);
|
|
21456
20383
|
if (ev.type === "terminal.exit" && typeof ev.exit_code === "number") {
|
|
21457
20384
|
parts.push(`exit=${ev.exit_code}`);
|
|
21458
20385
|
}
|
|
@@ -21464,7 +20391,7 @@ function buildSummary(ev, findings) {
|
|
|
21464
20391
|
return parts.join(" · ");
|
|
21465
20392
|
}
|
|
21466
20393
|
const top = findings[0];
|
|
21467
|
-
parts.push(`${top.severity}/${top.kind}: ${
|
|
20394
|
+
parts.push(`${top.severity}/${top.kind}: ${clip6(top.excerpt, 80)}`);
|
|
21468
20395
|
if (findings.length > 1)
|
|
21469
20396
|
parts.push(`(+${findings.length - 1} 条)`);
|
|
21470
20397
|
return parts.join(" · ");
|
|
@@ -21498,7 +20425,7 @@ async function processTerminalEvent(ev, opts = {}) {
|
|
|
21498
20425
|
return { ok: false, notified: false, suppressed: 0, error: msg };
|
|
21499
20426
|
}
|
|
21500
20427
|
}
|
|
21501
|
-
var sharedLRU = new
|
|
20428
|
+
var sharedLRU = new FingerprintLRU;
|
|
21502
20429
|
function describeError(err) {
|
|
21503
20430
|
if (err instanceof Error)
|
|
21504
20431
|
return err.message;
|
|
@@ -21510,17 +20437,17 @@ function describeError(err) {
|
|
|
21510
20437
|
return String(err);
|
|
21511
20438
|
}
|
|
21512
20439
|
}
|
|
21513
|
-
var
|
|
21514
|
-
var lru = new
|
|
20440
|
+
var log10 = makePluginLogger(PLUGIN_NAME17);
|
|
20441
|
+
var lru = new FingerprintLRU;
|
|
21515
20442
|
var _lastNotification = null;
|
|
21516
20443
|
var terminalMonitorServer = async (ctx) => {
|
|
21517
|
-
logLifecycle(
|
|
20444
|
+
logLifecycle(PLUGIN_NAME17, "activate", {
|
|
21518
20445
|
directory: ctx.directory,
|
|
21519
20446
|
triggerEventTypes: ["terminal.output", "terminal.exit"]
|
|
21520
20447
|
});
|
|
21521
20448
|
return {
|
|
21522
20449
|
event: async ({ event }) => {
|
|
21523
|
-
await safeAsync(
|
|
20450
|
+
await safeAsync(PLUGIN_NAME17, "event", async () => {
|
|
21524
20451
|
const e = event;
|
|
21525
20452
|
if (!e || typeof e.type !== "string")
|
|
21526
20453
|
return;
|
|
@@ -21529,12 +20456,12 @@ var terminalMonitorServer = async (ctx) => {
|
|
|
21529
20456
|
const ev = { type: e.type, ...e.properties ?? {} };
|
|
21530
20457
|
const r = await processTerminalEvent(ev, {
|
|
21531
20458
|
lru,
|
|
21532
|
-
log:
|
|
20459
|
+
log: log10,
|
|
21533
20460
|
notifyAgent: (msg) => {
|
|
21534
20461
|
_lastNotification = msg;
|
|
21535
20462
|
}
|
|
21536
20463
|
});
|
|
21537
|
-
safeWriteLog(
|
|
20464
|
+
safeWriteLog(PLUGIN_NAME17, {
|
|
21538
20465
|
hook: "event",
|
|
21539
20466
|
type: e.type,
|
|
21540
20467
|
notified: r.notified,
|
|
@@ -21542,18 +20469,133 @@ var terminalMonitorServer = async (ctx) => {
|
|
|
21542
20469
|
findings: r.notification?.findings.length ?? 0
|
|
21543
20470
|
});
|
|
21544
20471
|
if (r.notified && r.notification) {
|
|
21545
|
-
|
|
20472
|
+
log10.info(`[${PLUGIN_NAME17}] 反馈 ${r.notification.findings.length} 条 → ${r.notification.summary}`);
|
|
21546
20473
|
}
|
|
21547
20474
|
});
|
|
21548
20475
|
}
|
|
21549
20476
|
};
|
|
21550
20477
|
};
|
|
21551
|
-
var
|
|
20478
|
+
var handler17 = terminalMonitorServer;
|
|
20479
|
+
|
|
20480
|
+
// lib/condenser.ts
|
|
20481
|
+
var DEFAULT_CONDENSE = {
|
|
20482
|
+
budget: 128000,
|
|
20483
|
+
threshold: 0.7,
|
|
20484
|
+
target: 0.4,
|
|
20485
|
+
keepRecent: 6,
|
|
20486
|
+
keepSystem: true,
|
|
20487
|
+
charsPerToken: 4
|
|
20488
|
+
};
|
|
20489
|
+
function estimateTokens(text, charsPerToken = 4) {
|
|
20490
|
+
if (!text)
|
|
20491
|
+
return 0;
|
|
20492
|
+
return Math.max(1, Math.ceil(text.length / charsPerToken));
|
|
20493
|
+
}
|
|
20494
|
+
function tokenizeMessages(msgs, charsPerToken = 4) {
|
|
20495
|
+
let total = 0;
|
|
20496
|
+
for (const m of msgs) {
|
|
20497
|
+
total += m.tokens ?? estimateTokens(m.content, charsPerToken);
|
|
20498
|
+
}
|
|
20499
|
+
return total;
|
|
20500
|
+
}
|
|
20501
|
+
function fallbackSummarize(msgs) {
|
|
20502
|
+
if (msgs.length === 0)
|
|
20503
|
+
return "";
|
|
20504
|
+
const head = msgs.slice(0, 2).map((m) => `- [${m.role}] ${truncate(m.content, 120)}`);
|
|
20505
|
+
const tail = msgs.slice(-2).map((m) => `- [${m.role}] ${truncate(m.content, 120)}`);
|
|
20506
|
+
const tagCounts = new Map;
|
|
20507
|
+
let userCount = 0;
|
|
20508
|
+
let assistantCount = 0;
|
|
20509
|
+
let toolCount = 0;
|
|
20510
|
+
for (const m of msgs) {
|
|
20511
|
+
if (m.role === "user")
|
|
20512
|
+
userCount++;
|
|
20513
|
+
else if (m.role === "assistant")
|
|
20514
|
+
assistantCount++;
|
|
20515
|
+
else if (m.role === "tool")
|
|
20516
|
+
toolCount++;
|
|
20517
|
+
if (m.tag)
|
|
20518
|
+
tagCounts.set(m.tag, (tagCounts.get(m.tag) ?? 0) + 1);
|
|
20519
|
+
}
|
|
20520
|
+
const tagsLine = [...tagCounts.entries()].sort((a, b) => b[1] - a[1]).slice(0, 5).map(([t, c]) => `${t}×${c}`).join(", ");
|
|
20521
|
+
return [
|
|
20522
|
+
`### 历史摘要(共 ${msgs.length} 条 · user=${userCount} assistant=${assistantCount} tool=${toolCount})`,
|
|
20523
|
+
tagsLine ? `- 关键事件:${tagsLine}` : "",
|
|
20524
|
+
"",
|
|
20525
|
+
"**开头:**",
|
|
20526
|
+
...head,
|
|
20527
|
+
"",
|
|
20528
|
+
"**结尾:**",
|
|
20529
|
+
...tail
|
|
20530
|
+
].filter(Boolean).join(`
|
|
20531
|
+
`);
|
|
20532
|
+
}
|
|
20533
|
+
function truncate(s, max) {
|
|
20534
|
+
if (!s)
|
|
20535
|
+
return "";
|
|
20536
|
+
return s.length <= max ? s : s.slice(0, max - 1) + "…";
|
|
20537
|
+
}
|
|
20538
|
+
async function condense(input, opts = {}) {
|
|
20539
|
+
const cfg = { ...DEFAULT_CONDENSE, ...opts.config ?? {} };
|
|
20540
|
+
const msgs = [...input];
|
|
20541
|
+
const before = {
|
|
20542
|
+
count: msgs.length,
|
|
20543
|
+
tokens: tokenizeMessages(msgs, cfg.charsPerToken)
|
|
20544
|
+
};
|
|
20545
|
+
if (cfg.budget <= 0 || before.tokens / cfg.budget < cfg.threshold) {
|
|
20546
|
+
return {
|
|
20547
|
+
messages: msgs,
|
|
20548
|
+
before,
|
|
20549
|
+
after: { ...before },
|
|
20550
|
+
compressed: false,
|
|
20551
|
+
reason: `usage ${(before.tokens / Math.max(cfg.budget, 1)).toFixed(2)} < threshold ${cfg.threshold}`
|
|
20552
|
+
};
|
|
20553
|
+
}
|
|
20554
|
+
const systems = cfg.keepSystem ? msgs.filter((m) => m.role === "system") : [];
|
|
20555
|
+
const nonSystem = cfg.keepSystem ? msgs.filter((m) => m.role !== "system") : msgs;
|
|
20556
|
+
const recents = nonSystem.slice(-cfg.keepRecent);
|
|
20557
|
+
const middles = nonSystem.slice(0, Math.max(0, nonSystem.length - cfg.keepRecent));
|
|
20558
|
+
if (middles.length === 0) {
|
|
20559
|
+
return {
|
|
20560
|
+
messages: msgs,
|
|
20561
|
+
before,
|
|
20562
|
+
after: { ...before },
|
|
20563
|
+
compressed: false,
|
|
20564
|
+
reason: "nothing in middle to compress"
|
|
20565
|
+
};
|
|
20566
|
+
}
|
|
20567
|
+
let summary = "";
|
|
20568
|
+
try {
|
|
20569
|
+
summary = await (opts.summarize ?? fallbackSummarize)(middles) ?? "";
|
|
20570
|
+
} catch {
|
|
20571
|
+
summary = fallbackSummarize(middles);
|
|
20572
|
+
}
|
|
20573
|
+
if (!summary)
|
|
20574
|
+
summary = fallbackSummarize(middles);
|
|
20575
|
+
const summaryMsg = {
|
|
20576
|
+
role: "assistant",
|
|
20577
|
+
content: summary,
|
|
20578
|
+
tag: "condensed-summary"
|
|
20579
|
+
};
|
|
20580
|
+
const out = [...systems, summaryMsg, ...recents];
|
|
20581
|
+
const after = {
|
|
20582
|
+
count: out.length,
|
|
20583
|
+
tokens: tokenizeMessages(out, cfg.charsPerToken)
|
|
20584
|
+
};
|
|
20585
|
+
return {
|
|
20586
|
+
messages: out,
|
|
20587
|
+
before,
|
|
20588
|
+
after,
|
|
20589
|
+
compressed: true,
|
|
20590
|
+
summary,
|
|
20591
|
+
reason: `compressed ${middles.length} → 1 summary (target ${cfg.target})`
|
|
20592
|
+
};
|
|
20593
|
+
}
|
|
21552
20594
|
|
|
21553
20595
|
// plugins/token-manager.ts
|
|
21554
|
-
var
|
|
21555
|
-
logLifecycle(
|
|
21556
|
-
async function handleMessageBefore(raw,
|
|
20596
|
+
var PLUGIN_NAME18 = "token-manager";
|
|
20597
|
+
logLifecycle(PLUGIN_NAME18, "import", {});
|
|
20598
|
+
async function handleMessageBefore(raw, log11, defaults) {
|
|
21557
20599
|
const ctx = raw ?? {};
|
|
21558
20600
|
if (!Array.isArray(ctx.messages) || ctx.messages.length === 0)
|
|
21559
20601
|
return null;
|
|
@@ -21572,21 +20614,21 @@ async function handleMessageBefore(raw, log12, defaults) {
|
|
|
21572
20614
|
};
|
|
21573
20615
|
if (r.compressed) {
|
|
21574
20616
|
ctx.messages = r.messages;
|
|
21575
|
-
|
|
20617
|
+
log11?.info(`[${PLUGIN_NAME18}] 压缩 ${r.before.count}→${r.after.count} 条 / ${r.before.tokens}→${r.after.tokens} tokens`);
|
|
21576
20618
|
}
|
|
21577
20619
|
return r;
|
|
21578
20620
|
} catch (err) {
|
|
21579
|
-
|
|
20621
|
+
log11?.warn(`[${PLUGIN_NAME18}] 压缩异常(已隔离)`, {
|
|
21580
20622
|
error: err instanceof Error ? err.message : String(err)
|
|
21581
20623
|
});
|
|
21582
20624
|
return null;
|
|
21583
20625
|
}
|
|
21584
20626
|
}
|
|
21585
|
-
var
|
|
20627
|
+
var log11 = makePluginLogger(PLUGIN_NAME18);
|
|
21586
20628
|
var tokenManagerServer = async (ctx) => {
|
|
21587
20629
|
const rt = loadRuntimeSync();
|
|
21588
20630
|
const threshold = rt.runtime.context.condenser_threshold_ratio;
|
|
21589
|
-
logLifecycle(
|
|
20631
|
+
logLifecycle(PLUGIN_NAME18, "activate", {
|
|
21590
20632
|
directory: ctx.directory,
|
|
21591
20633
|
threshold,
|
|
21592
20634
|
target: DEFAULT_CONDENSE.target,
|
|
@@ -21595,7 +20637,7 @@ var tokenManagerServer = async (ctx) => {
|
|
|
21595
20637
|
});
|
|
21596
20638
|
return {
|
|
21597
20639
|
"experimental.chat.messages.transform": async (_input, output) => {
|
|
21598
|
-
await safeAsync(
|
|
20640
|
+
await safeAsync(PLUGIN_NAME18, "experimental.chat.messages.transform", async () => {
|
|
21599
20641
|
const list = output.messages;
|
|
21600
20642
|
if (!Array.isArray(list) || list.length === 0)
|
|
21601
20643
|
return;
|
|
@@ -21605,10 +20647,10 @@ var tokenManagerServer = async (ctx) => {
|
|
|
21605
20647
|
`);
|
|
21606
20648
|
return { role, content };
|
|
21607
20649
|
});
|
|
21608
|
-
const r = await handleMessageBefore({ messages: flat },
|
|
20650
|
+
const r = await handleMessageBefore({ messages: flat }, log11, { threshold });
|
|
21609
20651
|
if (!r)
|
|
21610
20652
|
return;
|
|
21611
|
-
safeWriteLog(
|
|
20653
|
+
safeWriteLog(PLUGIN_NAME18, {
|
|
21612
20654
|
hook: "experimental.chat.messages.transform",
|
|
21613
20655
|
mode: "observe-only",
|
|
21614
20656
|
before_msgs: r.before.count,
|
|
@@ -21619,13 +20661,13 @@ var tokenManagerServer = async (ctx) => {
|
|
|
21619
20661
|
reason: r.reason
|
|
21620
20662
|
});
|
|
21621
20663
|
if (r.compressed) {
|
|
21622
|
-
|
|
20664
|
+
log11.warn(`[${PLUGIN_NAME18}] 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)`);
|
|
21623
20665
|
}
|
|
21624
20666
|
});
|
|
21625
20667
|
}
|
|
21626
20668
|
};
|
|
21627
20669
|
};
|
|
21628
|
-
var
|
|
20670
|
+
var handler18 = tokenManagerServer;
|
|
21629
20671
|
|
|
21630
20672
|
// plugins/tool-policy.ts
|
|
21631
20673
|
import { promises as fs19 } from "node:fs";
|
|
@@ -21909,8 +20951,8 @@ function checkFileAccess(acl, file, op) {
|
|
|
21909
20951
|
}
|
|
21910
20952
|
|
|
21911
20953
|
// plugins/tool-policy.ts
|
|
21912
|
-
var
|
|
21913
|
-
logLifecycle(
|
|
20954
|
+
var PLUGIN_NAME19 = "tool-policy";
|
|
20955
|
+
logLifecycle(PLUGIN_NAME19, "import", {});
|
|
21914
20956
|
var EMPTY_ACL = { whitelistMode: false };
|
|
21915
20957
|
function getAgentAcl(cfg, agent) {
|
|
21916
20958
|
if (!agent || !cfg.per_agent)
|
|
@@ -21980,18 +21022,18 @@ async function loadPolicy(root = process.cwd()) {
|
|
|
21980
21022
|
function classifyToolKind(toolName) {
|
|
21981
21023
|
return classifyTool(toolName);
|
|
21982
21024
|
}
|
|
21983
|
-
async function resolveCurrentAgent2(client, sessionID,
|
|
21025
|
+
async function resolveCurrentAgent2(client, sessionID, log12) {
|
|
21984
21026
|
try {
|
|
21985
21027
|
const sessionApi = client?.session;
|
|
21986
21028
|
if (!sessionApi || typeof sessionApi.get !== "function") {
|
|
21987
|
-
|
|
21029
|
+
log12.warn(`client.session.get unavailable`, { sessionID });
|
|
21988
21030
|
return;
|
|
21989
21031
|
}
|
|
21990
21032
|
const res = await sessionApi.get({ path: { id: sessionID } });
|
|
21991
21033
|
const data = res?.data ?? res;
|
|
21992
21034
|
const rawAgent = data?.agent;
|
|
21993
21035
|
if (typeof rawAgent !== "string" || rawAgent === "") {
|
|
21994
|
-
|
|
21036
|
+
log12.warn(`client.session.get returned no string agent (保守放行)`, {
|
|
21995
21037
|
sessionID,
|
|
21996
21038
|
dataKeys: data && typeof data === "object" ? Object.keys(data) : null
|
|
21997
21039
|
});
|
|
@@ -21999,31 +21041,31 @@ async function resolveCurrentAgent2(client, sessionID, log13) {
|
|
|
21999
21041
|
}
|
|
22000
21042
|
return rawAgent;
|
|
22001
21043
|
} catch (err) {
|
|
22002
|
-
|
|
21044
|
+
log12.warn(`client.session.get failed (保守放行)`, {
|
|
22003
21045
|
sessionID,
|
|
22004
21046
|
error: err instanceof Error ? err.message : String(err)
|
|
22005
21047
|
});
|
|
22006
21048
|
return;
|
|
22007
21049
|
}
|
|
22008
21050
|
}
|
|
22009
|
-
var
|
|
21051
|
+
var log12 = makePluginLogger(PLUGIN_NAME19);
|
|
22010
21052
|
var toolPolicyServer = async (ctx) => {
|
|
22011
21053
|
const directory = ctx.directory ?? process.cwd();
|
|
22012
21054
|
const cfg = await loadPolicy(directory);
|
|
22013
|
-
logLifecycle(
|
|
21055
|
+
logLifecycle(PLUGIN_NAME19, "activate", {
|
|
22014
21056
|
directory,
|
|
22015
21057
|
acl_loaded: !!cfg.acl
|
|
22016
21058
|
});
|
|
22017
21059
|
return {
|
|
22018
21060
|
"tool.execute.before": async (input, output) => {
|
|
22019
21061
|
let denied;
|
|
22020
|
-
await safeAsync(
|
|
21062
|
+
await safeAsync(PLUGIN_NAME19, "tool.execute.before", async () => {
|
|
22021
21063
|
const toolName = input.tool;
|
|
22022
21064
|
const argsObj = output.args ?? {};
|
|
22023
21065
|
const needsAgentDetection = toolName === "pending_changes" && (argsObj.action === "apply" || argsObj.action === "apply_all");
|
|
22024
21066
|
let currentAgent = undefined;
|
|
22025
21067
|
if (needsAgentDetection) {
|
|
22026
|
-
currentAgent = await resolveCurrentAgent2(ctx.client, input.sessionID,
|
|
21068
|
+
currentAgent = await resolveCurrentAgent2(ctx.client, input.sessionID, log12);
|
|
22027
21069
|
}
|
|
22028
21070
|
const files = [];
|
|
22029
21071
|
const filePath = argsObj["path"] ?? argsObj["filePath"] ?? argsObj["file"];
|
|
@@ -22037,7 +21079,7 @@ var toolPolicyServer = async (ctx) => {
|
|
|
22037
21079
|
args: argsObj,
|
|
22038
21080
|
files: files.length ? files : undefined
|
|
22039
21081
|
}, cfg, currentAgent);
|
|
22040
|
-
safeWriteLog(
|
|
21082
|
+
safeWriteLog(PLUGIN_NAME19, {
|
|
22041
21083
|
hook: "tool.execute.before",
|
|
22042
21084
|
tool: toolName,
|
|
22043
21085
|
callID: input.callID,
|
|
@@ -22048,7 +21090,7 @@ var toolPolicyServer = async (ctx) => {
|
|
|
22048
21090
|
currentAgent
|
|
22049
21091
|
});
|
|
22050
21092
|
if (decision.action === "deny") {
|
|
22051
|
-
|
|
21093
|
+
log12.warn(`[${PLUGIN_NAME19}] DENY ${toolName}`, {
|
|
22052
21094
|
action: argsObj.action,
|
|
22053
21095
|
currentAgent,
|
|
22054
21096
|
reasons: decision.reasons,
|
|
@@ -22063,7 +21105,7 @@ var toolPolicyServer = async (ctx) => {
|
|
|
22063
21105
|
}
|
|
22064
21106
|
};
|
|
22065
21107
|
};
|
|
22066
|
-
var
|
|
21108
|
+
var handler19 = toolPolicyServer;
|
|
22067
21109
|
|
|
22068
21110
|
// plugins/update-checker.ts
|
|
22069
21111
|
import { existsSync as existsSync6 } from "node:fs";
|
|
@@ -22071,7 +21113,7 @@ import { homedir as homedir8 } from "node:os";
|
|
|
22071
21113
|
import { join as join23 } from "node:path";
|
|
22072
21114
|
|
|
22073
21115
|
// lib/update-checker-impl.ts
|
|
22074
|
-
import { createHash as
|
|
21116
|
+
import { createHash as createHash4 } from "node:crypto";
|
|
22075
21117
|
import {
|
|
22076
21118
|
copyFileSync,
|
|
22077
21119
|
existsSync as existsSync5,
|
|
@@ -22093,7 +21135,7 @@ import * as zlib from "node:zlib";
|
|
|
22093
21135
|
// lib/version-injected.ts
|
|
22094
21136
|
function getInjectedVersion() {
|
|
22095
21137
|
try {
|
|
22096
|
-
const v = "0.5.
|
|
21138
|
+
const v = "0.5.22";
|
|
22097
21139
|
if (typeof v === "string" && /^\d+\.\d+\.\d+/.test(v)) {
|
|
22098
21140
|
return v;
|
|
22099
21141
|
}
|
|
@@ -22372,7 +21414,7 @@ function verifyIntegrity(buf, expected) {
|
|
|
22372
21414
|
if (algo !== "sha512" && algo !== "sha256" && algo !== "sha384") {
|
|
22373
21415
|
throw new Error(`integrity_algo_unsupported: ${algo}`);
|
|
22374
21416
|
}
|
|
22375
|
-
const actualB64 =
|
|
21417
|
+
const actualB64 = createHash4(algo).update(buf).digest("base64");
|
|
22376
21418
|
if (actualB64 !== expectedB64) {
|
|
22377
21419
|
throw new Error(`integrity_mismatch: expected ${algo}=${expectedB64.slice(0, 16)}... got ${actualB64.slice(0, 16)}...`);
|
|
22378
21420
|
}
|
|
@@ -22596,20 +21638,20 @@ function compareOpencodeVersion(opts) {
|
|
|
22596
21638
|
}
|
|
22597
21639
|
|
|
22598
21640
|
// plugins/update-checker.ts
|
|
22599
|
-
var
|
|
21641
|
+
var PLUGIN_NAME20 = "update-checker";
|
|
22600
21642
|
var PLUGIN_VERSION = "2.0.0";
|
|
22601
|
-
logLifecycle(
|
|
21643
|
+
logLifecycle(PLUGIN_NAME20, "import", { version: PLUGIN_VERSION });
|
|
22602
21644
|
var updateCheckerServer = async (ctx) => {
|
|
22603
21645
|
const yieldResult = shouldYieldToLocalPlugin({ directory: ctx.directory });
|
|
22604
21646
|
if (yieldResult.yield) {
|
|
22605
|
-
safeWriteLog(
|
|
21647
|
+
safeWriteLog(PLUGIN_NAME20, {
|
|
22606
21648
|
level: "info",
|
|
22607
21649
|
msg: "dev_mode_yield_skip",
|
|
22608
21650
|
reason: yieldResult.reason,
|
|
22609
21651
|
markerPath: yieldResult.markerPath,
|
|
22610
21652
|
detail: formatYieldLog(yieldResult)
|
|
22611
21653
|
});
|
|
22612
|
-
logLifecycle(
|
|
21654
|
+
logLifecycle(PLUGIN_NAME20, "activate", {
|
|
22613
21655
|
yield_to_local: true,
|
|
22614
21656
|
yield_reason: yieldResult.reason,
|
|
22615
21657
|
skipped: "auto_install + background_check"
|
|
@@ -22618,7 +21660,7 @@ var updateCheckerServer = async (ctx) => {
|
|
|
22618
21660
|
}
|
|
22619
21661
|
const rt = loadRuntimeSync();
|
|
22620
21662
|
const u = rt.runtime.update;
|
|
22621
|
-
logLifecycle(
|
|
21663
|
+
logLifecycle(PLUGIN_NAME20, "activate", {
|
|
22622
21664
|
version: PLUGIN_VERSION,
|
|
22623
21665
|
auto_check_enabled: u.auto_check_enabled,
|
|
22624
21666
|
interval_hours: u.interval_hours,
|
|
@@ -22630,14 +21672,14 @@ var updateCheckerServer = async (ctx) => {
|
|
|
22630
21672
|
repo_fallback: u.repo,
|
|
22631
21673
|
config_source: "codeforge.json"
|
|
22632
21674
|
});
|
|
22633
|
-
await safeAsync(
|
|
21675
|
+
await safeAsync(PLUGIN_NAME20, "opencode_version_check", async () => {
|
|
22634
21676
|
const compat = loadCompatibility();
|
|
22635
21677
|
const opencodeVer = detectOpencodeVersion();
|
|
22636
21678
|
const verdict = compareOpencodeVersion({
|
|
22637
21679
|
currentOpencodeVer: opencodeVer,
|
|
22638
21680
|
compat
|
|
22639
21681
|
});
|
|
22640
|
-
safeWriteLog(
|
|
21682
|
+
safeWriteLog(PLUGIN_NAME20, {
|
|
22641
21683
|
level: "info",
|
|
22642
21684
|
msg: "opencode_version_check",
|
|
22643
21685
|
opencodeVer,
|
|
@@ -22654,14 +21696,14 @@ var updateCheckerServer = async (ctx) => {
|
|
|
22654
21696
|
}
|
|
22655
21697
|
});
|
|
22656
21698
|
if (!u.auto_check_enabled) {
|
|
22657
|
-
safeWriteLog(
|
|
21699
|
+
safeWriteLog(PLUGIN_NAME20, {
|
|
22658
21700
|
level: "info",
|
|
22659
21701
|
msg: "auto-check disabled (codeforge.json update.auto_check_enabled=false)"
|
|
22660
21702
|
});
|
|
22661
21703
|
return {};
|
|
22662
21704
|
}
|
|
22663
21705
|
setImmediate(() => {
|
|
22664
|
-
safeAsync(
|
|
21706
|
+
safeAsync(PLUGIN_NAME20, "checkAndMaybeUpdate", async () => {
|
|
22665
21707
|
const local = readLocalVersion();
|
|
22666
21708
|
let npmResult = null;
|
|
22667
21709
|
try {
|
|
@@ -22672,7 +21714,7 @@ var updateCheckerServer = async (ctx) => {
|
|
|
22672
21714
|
timeoutMs: 5000
|
|
22673
21715
|
});
|
|
22674
21716
|
} catch (e) {
|
|
22675
|
-
safeWriteLog(
|
|
21717
|
+
safeWriteLog(PLUGIN_NAME20, {
|
|
22676
21718
|
level: "warn",
|
|
22677
21719
|
msg: "npm_fetch_failed",
|
|
22678
21720
|
error: e.message
|
|
@@ -22681,7 +21723,7 @@ var updateCheckerServer = async (ctx) => {
|
|
|
22681
21723
|
return;
|
|
22682
21724
|
}
|
|
22683
21725
|
if (!npmResult) {
|
|
22684
|
-
safeWriteLog(
|
|
21726
|
+
safeWriteLog(PLUGIN_NAME20, {
|
|
22685
21727
|
level: "info",
|
|
22686
21728
|
msg: "npm_no_release",
|
|
22687
21729
|
package: u.package,
|
|
@@ -22690,7 +21732,7 @@ var updateCheckerServer = async (ctx) => {
|
|
|
22690
21732
|
return;
|
|
22691
21733
|
}
|
|
22692
21734
|
const hasUpdate = cmpVersion(local, npmResult.version) < 0;
|
|
22693
|
-
safeWriteLog(
|
|
21735
|
+
safeWriteLog(PLUGIN_NAME20, {
|
|
22694
21736
|
level: "info",
|
|
22695
21737
|
msg: "npm_check_result",
|
|
22696
21738
|
local,
|
|
@@ -22705,10 +21747,10 @@ var updateCheckerServer = async (ctx) => {
|
|
|
22705
21747
|
更新命令:npx ${u.package} install --global`);
|
|
22706
21748
|
return;
|
|
22707
21749
|
}
|
|
22708
|
-
await safeAsync(
|
|
21750
|
+
await safeAsync(PLUGIN_NAME20, "auto_install_bundle", async () => {
|
|
22709
21751
|
const target = getOpencodeBundlePath();
|
|
22710
21752
|
if (!target) {
|
|
22711
|
-
safeWriteLog(
|
|
21753
|
+
safeWriteLog(PLUGIN_NAME20, {
|
|
22712
21754
|
level: "warn",
|
|
22713
21755
|
msg: "auto_install_skip",
|
|
22714
21756
|
reason: "无法定位 opencode bundle 路径"
|
|
@@ -22727,7 +21769,7 @@ var updateCheckerServer = async (ctx) => {
|
|
|
22727
21769
|
oldVersion: local,
|
|
22728
21770
|
keepBackups: u.backup_keep
|
|
22729
21771
|
});
|
|
22730
|
-
safeWriteLog(
|
|
21772
|
+
safeWriteLog(PLUGIN_NAME20, {
|
|
22731
21773
|
level: "info",
|
|
22732
21774
|
msg: "auto_install_success",
|
|
22733
21775
|
local,
|
|
@@ -22767,7 +21809,7 @@ function getOpencodeBundlePath() {
|
|
|
22767
21809
|
return candidates[0] ?? null;
|
|
22768
21810
|
}
|
|
22769
21811
|
async function fallbackToGitHubReleases(ctx, u) {
|
|
22770
|
-
await safeAsync(
|
|
21812
|
+
await safeAsync(PLUGIN_NAME20, "github_fallback", async () => {
|
|
22771
21813
|
const result = await checkUpdateOnce({
|
|
22772
21814
|
repo: u.repo,
|
|
22773
21815
|
intervalMs: u.interval_hours * 3600 * 1000
|
|
@@ -22777,7 +21819,7 @@ async function fallbackToGitHubReleases(ctx, u) {
|
|
|
22777
21819
|
}
|
|
22778
21820
|
async function reportLegacyResult(ctx, result, repo) {
|
|
22779
21821
|
if (result.error && !result.fromCache) {
|
|
22780
|
-
safeWriteLog(
|
|
21822
|
+
safeWriteLog(PLUGIN_NAME20, {
|
|
22781
21823
|
level: "warn",
|
|
22782
21824
|
msg: `update check failed: ${result.error}`,
|
|
22783
21825
|
...result
|
|
@@ -22785,7 +21827,7 @@ async function reportLegacyResult(ctx, result, repo) {
|
|
|
22785
21827
|
return;
|
|
22786
21828
|
}
|
|
22787
21829
|
if (!result.hasUpdate) {
|
|
22788
|
-
safeWriteLog(
|
|
21830
|
+
safeWriteLog(PLUGIN_NAME20, {
|
|
22789
21831
|
level: "info",
|
|
22790
21832
|
msg: `up-to-date (local=${result.local}, remote=${result.remote}${result.fromCache ? ", from_cache" : ""})`,
|
|
22791
21833
|
...result
|
|
@@ -22795,7 +21837,7 @@ async function reportLegacyResult(ctx, result, repo) {
|
|
|
22795
21837
|
const updateCmd = `bunx --bun github:${repo} install`;
|
|
22796
21838
|
const toast = `[CodeForge] 有新版本:${result.local} → ${result.remote}
|
|
22797
21839
|
更新命令:${updateCmd}`;
|
|
22798
|
-
safeWriteLog(
|
|
21840
|
+
safeWriteLog(PLUGIN_NAME20, {
|
|
22799
21841
|
level: "info",
|
|
22800
21842
|
msg: "new_version_available_github_fallback",
|
|
22801
21843
|
local: result.local,
|
|
@@ -22806,17 +21848,17 @@ async function reportLegacyResult(ctx, result, repo) {
|
|
|
22806
21848
|
await postToast(ctx, toast);
|
|
22807
21849
|
}
|
|
22808
21850
|
async function postToast(ctx, message) {
|
|
22809
|
-
await safeAsync(
|
|
21851
|
+
await safeAsync(PLUGIN_NAME20, "client.app.log", async () => {
|
|
22810
21852
|
await ctx.client.app.log({
|
|
22811
21853
|
body: {
|
|
22812
|
-
service:
|
|
21854
|
+
service: PLUGIN_NAME20,
|
|
22813
21855
|
level: "info",
|
|
22814
21856
|
message
|
|
22815
21857
|
}
|
|
22816
21858
|
});
|
|
22817
21859
|
});
|
|
22818
21860
|
}
|
|
22819
|
-
var
|
|
21861
|
+
var handler20 = updateCheckerServer;
|
|
22820
21862
|
|
|
22821
21863
|
// plugins/workflow-engine.ts
|
|
22822
21864
|
import * as path27 from "node:path";
|
|
@@ -23271,9 +22313,9 @@ async function runStepAutoFeedback(step, adapter) {
|
|
|
23271
22313
|
}
|
|
23272
22314
|
|
|
23273
22315
|
// plugins/workflow-engine.ts
|
|
23274
|
-
var
|
|
23275
|
-
logLifecycle(
|
|
23276
|
-
var fallbackLog2 = makePluginLogger(
|
|
22316
|
+
var PLUGIN_NAME21 = "workflow-engine";
|
|
22317
|
+
logLifecycle(PLUGIN_NAME21, "import", {});
|
|
22318
|
+
var fallbackLog2 = makePluginLogger(PLUGIN_NAME21);
|
|
23277
22319
|
var _registry = null;
|
|
23278
22320
|
async function loadRegistry(workflowsDir) {
|
|
23279
22321
|
const { loaded, failed } = await loadWorkflowsFromDir(workflowsDir);
|
|
@@ -23290,35 +22332,35 @@ async function ensureRegistry(workflowsDir = "workflows") {
|
|
|
23290
22332
|
}
|
|
23291
22333
|
async function handleCommandInvoked(raw, workflowsDir = "workflows") {
|
|
23292
22334
|
const ctx = raw ?? {};
|
|
23293
|
-
const
|
|
22335
|
+
const log13 = ctx.log ?? fallbackLog2;
|
|
23294
22336
|
const command = typeof ctx.command === "string" ? ctx.command : null;
|
|
23295
22337
|
if (!command) {
|
|
23296
|
-
|
|
22338
|
+
log13.warn(`[${PLUGIN_NAME21}] command.invoked 缺 command 字段`, ctx);
|
|
23297
22339
|
return null;
|
|
23298
22340
|
}
|
|
23299
22341
|
const reg = await ensureRegistry(workflowsDir);
|
|
23300
22342
|
if (reg.errors.length) {
|
|
23301
|
-
|
|
22343
|
+
log13.warn(`[${PLUGIN_NAME21}] 有 ${reg.errors.length} 个 workflow 加载失败`, reg.errors);
|
|
23302
22344
|
}
|
|
23303
22345
|
const wf = reg.workflows.find((w) => matchesTrigger(w, command));
|
|
23304
22346
|
if (!wf) {
|
|
23305
|
-
|
|
22347
|
+
log13.info(`[${PLUGIN_NAME21}] no workflow matches "${command}"`);
|
|
23306
22348
|
return null;
|
|
23307
22349
|
}
|
|
23308
|
-
|
|
22350
|
+
log13.info(`[${PLUGIN_NAME21}] dispatch "${command}" → workflow "${wf.name}"`);
|
|
23309
22351
|
try {
|
|
23310
22352
|
const result = await run(wf, {
|
|
23311
22353
|
mode: ctx.adapter ? "real" : "dry_run",
|
|
23312
22354
|
autonomy: ctx.autonomy ?? "semi",
|
|
23313
22355
|
adapter: ctx.adapter
|
|
23314
22356
|
});
|
|
23315
|
-
|
|
22357
|
+
log13.info(`[${PLUGIN_NAME21}] workflow "${wf.name}" 完成 (${result.plan.mode})`, {
|
|
23316
22358
|
steps: result.plan.steps.length,
|
|
23317
22359
|
results: result.results.length
|
|
23318
22360
|
});
|
|
23319
22361
|
return result;
|
|
23320
22362
|
} catch (err) {
|
|
23321
|
-
|
|
22363
|
+
log13.error(`[${PLUGIN_NAME21}] workflow "${wf.name}" 执行失败`, {
|
|
23322
22364
|
error: err instanceof Error ? err.message : String(err)
|
|
23323
22365
|
});
|
|
23324
22366
|
throw err;
|
|
@@ -23327,15 +22369,15 @@ async function handleCommandInvoked(raw, workflowsDir = "workflows") {
|
|
|
23327
22369
|
var workflowEngineServer = async (ctx) => {
|
|
23328
22370
|
const directory = ctx.directory ?? process.cwd();
|
|
23329
22371
|
const workflowsDir = path27.join(directory, "workflows");
|
|
23330
|
-
ensureRegistry(workflowsDir).catch((err) => fallbackLog2.warn(`[${
|
|
22372
|
+
ensureRegistry(workflowsDir).catch((err) => fallbackLog2.warn(`[${PLUGIN_NAME21}] preload workflows failed`, {
|
|
23331
22373
|
error: err instanceof Error ? err.message : String(err)
|
|
23332
22374
|
}));
|
|
23333
|
-
logLifecycle(
|
|
22375
|
+
logLifecycle(PLUGIN_NAME21, "activate", { directory, workflowsDir });
|
|
23334
22376
|
return {
|
|
23335
22377
|
"command.execute.before": async (input, output) => {
|
|
23336
|
-
await safeAsync(
|
|
22378
|
+
await safeAsync(PLUGIN_NAME21, "command.execute.before", async () => {
|
|
23337
22379
|
const cmd = input.command.startsWith("/") ? input.command : `/${input.command}`;
|
|
23338
|
-
safeWriteLog(
|
|
22380
|
+
safeWriteLog(PLUGIN_NAME21, {
|
|
23339
22381
|
hook: "command.execute.before",
|
|
23340
22382
|
command: cmd,
|
|
23341
22383
|
sessionID: input.sessionID
|
|
@@ -23350,14 +22392,14 @@ var workflowEngineServer = async (ctx) => {
|
|
|
23350
22392
|
});
|
|
23351
22393
|
},
|
|
23352
22394
|
"chat.message": async (input, output) => {
|
|
23353
|
-
await safeAsync(
|
|
22395
|
+
await safeAsync(PLUGIN_NAME21, "chat.message", async () => {
|
|
23354
22396
|
const text = extractUserText(output).trim();
|
|
23355
22397
|
if (!text.startsWith("/"))
|
|
23356
22398
|
return;
|
|
23357
22399
|
const cmd = text.split(/\s+/)[0];
|
|
23358
22400
|
if (!cmd)
|
|
23359
22401
|
return;
|
|
23360
|
-
safeWriteLog(
|
|
22402
|
+
safeWriteLog(PLUGIN_NAME21, {
|
|
23361
22403
|
hook: "chat.message",
|
|
23362
22404
|
command: cmd,
|
|
23363
22405
|
sessionID: input.sessionID
|
|
@@ -23367,12 +22409,12 @@ var workflowEngineServer = async (ctx) => {
|
|
|
23367
22409
|
}
|
|
23368
22410
|
};
|
|
23369
22411
|
};
|
|
23370
|
-
var
|
|
22412
|
+
var handler21 = workflowEngineServer;
|
|
23371
22413
|
|
|
23372
22414
|
// plugins/session-worktree-guard.ts
|
|
23373
22415
|
import path28 from "node:path";
|
|
23374
|
-
var
|
|
23375
|
-
logLifecycle(
|
|
22416
|
+
var PLUGIN_NAME22 = "session-worktree-guard";
|
|
22417
|
+
logLifecycle(PLUGIN_NAME22, "import", {});
|
|
23376
22418
|
var WRITE_INTENT_RE = />(?![=&])(?!\s*\/dev\/(?:null|stdout|stderr|fd\/\d+)\b)|\btee\b|\brm\b|\bmv\b|\bcp\b|\bmkdir\b|\btouch\b|\bchmod\b|\bchown\b|\bln\b/;
|
|
23377
22419
|
var READ_ONLY_COMMANDS = /^\s*(?:ls|cat|head|tail|grep|rg|find|fd|wc|stat|file|which|whereis|echo|pwd|cd|pushd|popd|env|printenv|type|less|more|sort|uniq|awk|tr|cut|jq|date|whoami|id|uname|node|npx|tsc|diff|python3?|git(?:\s+-C\s+\S+)?\s+(?:log|show|diff|status|branch|tag|remote|config\s+--get|rev-parse|rev-list|ls-files|ls-tree|cat-file|describe|reflog|blame|shortlog|name-rev|symbolic-ref|merge-base|worktree\s+list|stash\s+list|stash\s+show))\b/;
|
|
23378
22420
|
var SIDE_EFFECT_TOKEN_RE = />(?![=&])(?!\s*\/dev\/(?:null|stdout|stderr|fd\/\d+)\b)|\|\s*tee\b|\btee\b/;
|
|
@@ -23395,11 +22437,11 @@ var INTERPRETER_WRITE_RES = [
|
|
|
23395
22437
|
function stripCommitMessageArgs(command) {
|
|
23396
22438
|
return command.replace(/(?:^|\s)(?:-m|--message|-F|--file)(?:=|\s+)("[^"]*"|'[^']*')/g, " ");
|
|
23397
22439
|
}
|
|
23398
|
-
function
|
|
22440
|
+
function escapeRegex(s) {
|
|
23399
22441
|
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
23400
22442
|
}
|
|
23401
22443
|
function buildGitVcsWriteRegex(mainRoot) {
|
|
23402
|
-
const esc =
|
|
22444
|
+
const esc = escapeRegex(mainRoot);
|
|
23403
22445
|
return new RegExp(`git\\b[^\\n]*(?:-C\\s+|--work-tree[=\\s])${esc}`);
|
|
23404
22446
|
}
|
|
23405
22447
|
var WRITE_TOOLS = new Set(["write", "edit", "ast_edit"]);
|
|
@@ -23514,7 +22556,7 @@ function commandContainsMainRoot(command, mainRoot) {
|
|
|
23514
22556
|
const prefix = mainRoot.endsWith("/") ? mainRoot : mainRoot + "/";
|
|
23515
22557
|
if (command.includes(prefix))
|
|
23516
22558
|
return true;
|
|
23517
|
-
const re = new RegExp(`${
|
|
22559
|
+
const re = new RegExp(`${escapeRegex(mainRoot)}(?=[\\s'"\`)]|$)`);
|
|
23518
22560
|
return re.test(command);
|
|
23519
22561
|
}
|
|
23520
22562
|
function commandContainsMainRootExcludingWorktree(command, mainRoot, worktreePath) {
|
|
@@ -23530,7 +22572,7 @@ function commandContainsMainRootExcludingWorktree(command, mainRoot, worktreePat
|
|
|
23530
22572
|
}
|
|
23531
22573
|
const wtRoot = worktreesRoot(mainRoot);
|
|
23532
22574
|
const wtRootPrefix = wtRoot + path28.sep;
|
|
23533
|
-
const escapedWtRootPrefix =
|
|
22575
|
+
const escapedWtRootPrefix = escapeRegex(wtRootPrefix);
|
|
23534
22576
|
const wtPathPattern = escapedWtRootPrefix + `[^\\s'"\\x60)]*`;
|
|
23535
22577
|
const allWorktreePathsReForEscape = new RegExp(wtPathPattern, "g");
|
|
23536
22578
|
const allWorktreePathsReForReplace = new RegExp(wtPathPattern, "g");
|
|
@@ -23589,7 +22631,7 @@ function collectWritePaths(toolName, argsObj, worktreeRoot) {
|
|
|
23589
22631
|
out.push(rel);
|
|
23590
22632
|
return out;
|
|
23591
22633
|
}
|
|
23592
|
-
var
|
|
22634
|
+
var log13 = makePluginLogger(PLUGIN_NAME22);
|
|
23593
22635
|
function resolveMainRoot2(rawDir) {
|
|
23594
22636
|
const worktreeMarker = "/.git/codeforge-worktrees/";
|
|
23595
22637
|
const idx = rawDir.indexOf(worktreeMarker);
|
|
@@ -23601,14 +22643,14 @@ function resolveMainRoot2(rawDir) {
|
|
|
23601
22643
|
var sessionWorktreeGuardPlugin = async (ctx) => {
|
|
23602
22644
|
const disableEnv = process.env["CODEFORGE_DISABLE_WORKTREE_GUARD"];
|
|
23603
22645
|
if (disableEnv === "1" || disableEnv === "true" || disableEnv === "yes") {
|
|
23604
|
-
|
|
23605
|
-
safeWriteLog(
|
|
22646
|
+
log13.warn("[guard] CODEFORGE_DISABLE_WORKTREE_GUARD 已启用,session-worktree-guard 全部 hook 跳过;" + "本次 opencode 会话所有写操作将直接落到主工作区(失去隔离保护)", { env: disableEnv });
|
|
22647
|
+
safeWriteLog(PLUGIN_NAME22, {
|
|
23606
22648
|
hook: "activate",
|
|
23607
22649
|
action: "skip",
|
|
23608
22650
|
source: "disable-env",
|
|
23609
22651
|
env_value: disableEnv
|
|
23610
22652
|
});
|
|
23611
|
-
logLifecycle(
|
|
22653
|
+
logLifecycle(PLUGIN_NAME22, "activate", { disabled_by_env: true });
|
|
23612
22654
|
return {};
|
|
23613
22655
|
}
|
|
23614
22656
|
const mainRoot = resolveMainRoot2(ctx.directory ?? process.cwd());
|
|
@@ -23616,13 +22658,13 @@ var sessionWorktreeGuardPlugin = async (ctx) => {
|
|
|
23616
22658
|
try {
|
|
23617
22659
|
policyCfg = await loadPolicy(mainRoot);
|
|
23618
22660
|
} catch (err) {
|
|
23619
|
-
|
|
22661
|
+
log13.warn("loadPolicy failed (class E skipped)", {
|
|
23620
22662
|
mainRoot,
|
|
23621
22663
|
error: err instanceof Error ? err.message : String(err)
|
|
23622
22664
|
});
|
|
23623
22665
|
}
|
|
23624
22666
|
const perAgentEnabled = !!policyCfg.per_agent && Object.keys(policyCfg.per_agent).length > 0;
|
|
23625
|
-
logLifecycle(
|
|
22667
|
+
logLifecycle(PLUGIN_NAME22, "activate", {
|
|
23626
22668
|
mainRoot,
|
|
23627
22669
|
CODEFORGE_SESSION_ID: process.env["CODEFORGE_SESSION_ID"] ?? "(not set)"
|
|
23628
22670
|
});
|
|
@@ -23633,12 +22675,12 @@ var sessionWorktreeGuardPlugin = async (ctx) => {
|
|
|
23633
22675
|
const isWrite = isWriteOperation(input.tool, output.args ?? {}, mainRoot);
|
|
23634
22676
|
if (isWrite && !_sessionIdMissingWarned) {
|
|
23635
22677
|
_sessionIdMissingWarned = true;
|
|
23636
|
-
|
|
22678
|
+
log13.warn("[guard] sessionID 缺失,无法绑定 worktree;本会话所有写操作将落到主工作区(仅本进程内 warn 一次)。" + "排查:grep no-session-id ~/.cache/codeforge/plugins.log", {
|
|
23637
22679
|
tool: input.tool,
|
|
23638
22680
|
opencode_version_hint: "需 opencode >= 0.x 才会在 tool.execute.before 注入 input.sessionID"
|
|
23639
22681
|
});
|
|
23640
22682
|
}
|
|
23641
|
-
safeWriteLog(
|
|
22683
|
+
safeWriteLog(PLUGIN_NAME22, {
|
|
23642
22684
|
hook: "tool.execute.before",
|
|
23643
22685
|
tool: input.tool,
|
|
23644
22686
|
action: "skip",
|
|
@@ -23648,14 +22690,14 @@ var sessionWorktreeGuardPlugin = async (ctx) => {
|
|
|
23648
22690
|
return;
|
|
23649
22691
|
}
|
|
23650
22692
|
let denied;
|
|
23651
|
-
await safeAsync(
|
|
22693
|
+
await safeAsync(PLUGIN_NAME22, "tool.execute.before", async () => {
|
|
23652
22694
|
const toolName = input.tool;
|
|
23653
22695
|
const argsObj = output.args ?? {};
|
|
23654
22696
|
let entry = null;
|
|
23655
22697
|
try {
|
|
23656
22698
|
entry = await getSessionWorktree(sessionId, mainRoot);
|
|
23657
22699
|
} catch (err) {
|
|
23658
|
-
|
|
22700
|
+
log13.warn(`getSessionWorktree failed (跳过本次检查)`, {
|
|
23659
22701
|
sessionId,
|
|
23660
22702
|
mainRoot,
|
|
23661
22703
|
error: err instanceof Error ? err.message : String(err)
|
|
@@ -23672,8 +22714,8 @@ var sessionWorktreeGuardPlugin = async (ctx) => {
|
|
|
23672
22714
|
const parentEntry = await getSessionWorktree(parentId, mainRoot);
|
|
23673
22715
|
if (parentEntry && parentEntry.status === "active") {
|
|
23674
22716
|
entry = parentEntry;
|
|
23675
|
-
|
|
23676
|
-
safeWriteLog(
|
|
22717
|
+
log13.debug?.(`[child-inherit] session ${sessionId} 继承父 ${parentId} 的 worktree`, { parentSessionId: parentId, worktreePath: parentEntry.worktreePath });
|
|
22718
|
+
safeWriteLog(PLUGIN_NAME22, {
|
|
23677
22719
|
hook: "tool.execute.before",
|
|
23678
22720
|
tool: toolName,
|
|
23679
22721
|
sessionID: input.sessionID,
|
|
@@ -23685,14 +22727,14 @@ var sessionWorktreeGuardPlugin = async (ctx) => {
|
|
|
23685
22727
|
}
|
|
23686
22728
|
}
|
|
23687
22729
|
} catch (lookupErr) {
|
|
23688
|
-
|
|
22730
|
+
log13.debug?.("[child-inherit] lookupParentSessionId 抛错(已隔离,退回 lazy-bind)", { error: lookupErr instanceof Error ? lookupErr.message : String(lookupErr) });
|
|
23689
22731
|
}
|
|
23690
22732
|
}
|
|
23691
22733
|
if (!entry) {
|
|
23692
22734
|
try {
|
|
23693
22735
|
entry = await bindSessionWorktree({ sessionId, mainRoot });
|
|
23694
|
-
|
|
23695
|
-
safeWriteLog(
|
|
22736
|
+
log13.info(`[lazy-bind] auto-created worktree for session ${sessionId}`, { branch: entry.branch, path: entry.worktreePath });
|
|
22737
|
+
safeWriteLog(PLUGIN_NAME22, {
|
|
23696
22738
|
hook: "tool.execute.before",
|
|
23697
22739
|
tool: toolName,
|
|
23698
22740
|
sessionID: input.sessionID,
|
|
@@ -23711,13 +22753,13 @@ var sessionWorktreeGuardPlugin = async (ctx) => {
|
|
|
23711
22753
|
alreadyNotified
|
|
23712
22754
|
});
|
|
23713
22755
|
_bindFailNotified.add(sessionId);
|
|
23714
|
-
|
|
22756
|
+
log13.warn(`[lazy-bind] DENY (bind failed)`, {
|
|
23715
22757
|
sessionId,
|
|
23716
22758
|
tool: toolName,
|
|
23717
22759
|
error: errMsg,
|
|
23718
22760
|
throttled: alreadyNotified
|
|
23719
22761
|
});
|
|
23720
|
-
safeWriteLog(
|
|
22762
|
+
safeWriteLog(PLUGIN_NAME22, {
|
|
23721
22763
|
hook: "tool.execute.before",
|
|
23722
22764
|
tool: toolName,
|
|
23723
22765
|
sessionID: input.sessionID,
|
|
@@ -23740,7 +22782,7 @@ var sessionWorktreeGuardPlugin = async (ctx) => {
|
|
|
23740
22782
|
sessionId: entry.sessionId,
|
|
23741
22783
|
mainRoot
|
|
23742
22784
|
}).catch((err) => {
|
|
23743
|
-
|
|
22785
|
+
log13.warn("touchEntryUpdatedAt 失败 (已忽略)", {
|
|
23744
22786
|
sessionId: entry?.sessionId,
|
|
23745
22787
|
error: err instanceof Error ? err.message : String(err)
|
|
23746
22788
|
});
|
|
@@ -23753,13 +22795,13 @@ var sessionWorktreeGuardPlugin = async (ctx) => {
|
|
|
23753
22795
|
const caller = input.agent;
|
|
23754
22796
|
if (caller !== undefined && caller !== "codeforge") {
|
|
23755
22797
|
const reason = `[session-worktree-guard] DENIED: session_merge action=merge 仅 codeforge orchestrator 或用户可调;当前 caller=${caller}`;
|
|
23756
|
-
|
|
22798
|
+
log13.warn(reason, {
|
|
23757
22799
|
sessionId,
|
|
23758
22800
|
tool: toolName,
|
|
23759
22801
|
action,
|
|
23760
22802
|
caller
|
|
23761
22803
|
});
|
|
23762
|
-
safeWriteLog(
|
|
22804
|
+
safeWriteLog(PLUGIN_NAME22, {
|
|
23763
22805
|
hook: "tool.execute.before",
|
|
23764
22806
|
tool: toolName,
|
|
23765
22807
|
sessionID: input.sessionID,
|
|
@@ -23774,15 +22816,15 @@ var sessionWorktreeGuardPlugin = async (ctx) => {
|
|
|
23774
22816
|
}
|
|
23775
22817
|
}
|
|
23776
22818
|
if (perAgentEnabled && isWriteOperation(toolName, argsObj, mainRoot)) {
|
|
23777
|
-
const caller = await resolveAgentForGuard({ sessionID: input.sessionID, agent: input.agent }, ctx.client,
|
|
22819
|
+
const caller = await resolveAgentForGuard({ sessionID: input.sessionID, agent: input.agent }, ctx.client, log13);
|
|
23778
22820
|
if (caller === null) {
|
|
23779
22821
|
const reason = `[session-worktree-guard] DENIED: per-agent ACL 启用但 agent 解析失败 ` + `(L1 input.agent 缺, L2a chat-agent-cache 无, L2 IPC 反查失败) — fail-closed 拒绝写 ${toolName} ` + `(ADR:discover-write-permission-acl)`;
|
|
23780
|
-
|
|
22822
|
+
log13.warn(reason, {
|
|
23781
22823
|
sessionId,
|
|
23782
22824
|
tool: toolName,
|
|
23783
22825
|
source: "per-agent-acl-fail-closed"
|
|
23784
22826
|
});
|
|
23785
|
-
safeWriteLog(
|
|
22827
|
+
safeWriteLog(PLUGIN_NAME22, {
|
|
23786
22828
|
hook: "tool.execute.before",
|
|
23787
22829
|
tool: toolName,
|
|
23788
22830
|
sessionID: input.sessionID,
|
|
@@ -23800,14 +22842,14 @@ var sessionWorktreeGuardPlugin = async (ctx) => {
|
|
|
23800
22842
|
const dec = checkFileAccess(agentAcl, relPath, "write");
|
|
23801
22843
|
if (dec.action === "deny") {
|
|
23802
22844
|
const reason = `[session-worktree-guard] DENIED: agent='${caller}' 写路径不在 ACL 白名单 — ` + `${relPath} (${dec.reason})`;
|
|
23803
|
-
|
|
22845
|
+
log13.warn(reason, {
|
|
23804
22846
|
sessionId,
|
|
23805
22847
|
tool: toolName,
|
|
23806
22848
|
caller,
|
|
23807
22849
|
path: relPath,
|
|
23808
22850
|
acl_decision: dec
|
|
23809
22851
|
});
|
|
23810
|
-
safeWriteLog(
|
|
22852
|
+
safeWriteLog(PLUGIN_NAME22, {
|
|
23811
22853
|
hook: "tool.execute.before",
|
|
23812
22854
|
tool: toolName,
|
|
23813
22855
|
sessionID: input.sessionID,
|
|
@@ -23845,13 +22887,13 @@ var sessionWorktreeGuardPlugin = async (ctx) => {
|
|
|
23845
22887
|
const reasonBase = `[session-worktree-guard] DENIED: 当前 session 要求先调用 plan_read(plan_id="${entry.requiredPlanId}") 再执行写操作`;
|
|
23846
22888
|
const reason = inherited ? `${reasonBase}
|
|
23847
22889
|
[gate-deny] child session=${sessionId} 继承父 entry 但父 session planReadOk=false,父 session=${entry.sessionId} 需先调 plan_read(plan_id="${entry.requiredPlanId}")` : reasonBase;
|
|
23848
|
-
|
|
22890
|
+
log13.warn(reason, {
|
|
23849
22891
|
tool: toolName,
|
|
23850
22892
|
sessionId,
|
|
23851
22893
|
requiredPlanId: entry.requiredPlanId,
|
|
23852
22894
|
inheritedFromParent: inherited ? entry.sessionId : undefined
|
|
23853
22895
|
});
|
|
23854
|
-
safeWriteLog(
|
|
22896
|
+
safeWriteLog(PLUGIN_NAME22, {
|
|
23855
22897
|
hook: "tool.execute.before",
|
|
23856
22898
|
tool: toolName,
|
|
23857
22899
|
sessionID: input.sessionID,
|
|
@@ -23866,10 +22908,10 @@ var sessionWorktreeGuardPlugin = async (ctx) => {
|
|
|
23866
22908
|
if (toolName === "bash") {
|
|
23867
22909
|
const command = argsObj["command"];
|
|
23868
22910
|
if (typeof command === "string" && commandContainsMainRootExcludingWorktree(command, mainRoot, worktreePath) && detectBashWriteIntent(command, mainRoot)) {
|
|
23869
|
-
const caller = await resolveAgentForGuard({ sessionID: input.sessionID, agent: input.agent }, ctx.client,
|
|
22911
|
+
const caller = await resolveAgentForGuard({ sessionID: input.sessionID, agent: input.agent }, ctx.client, log13);
|
|
23870
22912
|
if (caller !== null && CLASS_B_CALLER_WHITELIST.has(caller)) {
|
|
23871
|
-
|
|
23872
|
-
safeWriteLog(
|
|
22913
|
+
log13.debug?.(`[class-b-whitelist] allow caller=${caller}`, { sessionId, tool: toolName, command: command.slice(0, 200) });
|
|
22914
|
+
safeWriteLog(PLUGIN_NAME22, {
|
|
23873
22915
|
hook: "tool.execute.before",
|
|
23874
22916
|
tool: toolName,
|
|
23875
22917
|
sessionID: input.sessionID,
|
|
@@ -23882,8 +22924,8 @@ var sessionWorktreeGuardPlugin = async (ctx) => {
|
|
|
23882
22924
|
const callerTag = caller === null ? "unresolved" : caller;
|
|
23883
22925
|
const snippet = command.length > 60 ? command.slice(0, 60) + "…" : command;
|
|
23884
22926
|
const reason = `[session-worktree-guard] DENIED: bash.command 含主仓绝对路径写操作 (${snippet}) [caller=${callerTag}],请在当前 session worktree (${worktreePath}) 内操作`;
|
|
23885
|
-
|
|
23886
|
-
safeWriteLog(
|
|
22927
|
+
log13.warn(reason, { sessionId, caller: callerTag, command: command.slice(0, 200) });
|
|
22928
|
+
safeWriteLog(PLUGIN_NAME22, {
|
|
23887
22929
|
hook: "tool.execute.before",
|
|
23888
22930
|
tool: toolName,
|
|
23889
22931
|
sessionID: input.sessionID,
|
|
@@ -23902,8 +22944,8 @@ var sessionWorktreeGuardPlugin = async (ctx) => {
|
|
|
23902
22944
|
if (typeof filePath === "string") {
|
|
23903
22945
|
const newPath = rewritePath(filePath, mainRoot, worktreePath);
|
|
23904
22946
|
if (newPath !== null) {
|
|
23905
|
-
|
|
23906
|
-
safeWriteLog(
|
|
22947
|
+
log13.info(`rewrote ${toolName}.filePath: ${filePath} → ${newPath}`);
|
|
22948
|
+
safeWriteLog(PLUGIN_NAME22, {
|
|
23907
22949
|
hook: "tool.execute.before",
|
|
23908
22950
|
tool: toolName,
|
|
23909
22951
|
field: "filePath",
|
|
@@ -23920,8 +22962,8 @@ var sessionWorktreeGuardPlugin = async (ctx) => {
|
|
|
23920
22962
|
if (typeof target === "string") {
|
|
23921
22963
|
const newTarget = rewritePath(target, mainRoot, worktreePath);
|
|
23922
22964
|
if (newTarget !== null) {
|
|
23923
|
-
|
|
23924
|
-
safeWriteLog(
|
|
22965
|
+
log13.info(`rewrote ast_edit.target: ${target} → ${newTarget}`);
|
|
22966
|
+
safeWriteLog(PLUGIN_NAME22, {
|
|
23925
22967
|
hook: "tool.execute.before",
|
|
23926
22968
|
tool: toolName,
|
|
23927
22969
|
field: "target",
|
|
@@ -23936,8 +22978,8 @@ var sessionWorktreeGuardPlugin = async (ctx) => {
|
|
|
23936
22978
|
if (typeof root === "string") {
|
|
23937
22979
|
const newRoot = rewritePath(root, mainRoot, worktreePath);
|
|
23938
22980
|
if (newRoot !== null) {
|
|
23939
|
-
|
|
23940
|
-
safeWriteLog(
|
|
22981
|
+
log13.info(`rewrote ast_edit.root: ${root} → ${newRoot}`);
|
|
22982
|
+
safeWriteLog(PLUGIN_NAME22, {
|
|
23941
22983
|
hook: "tool.execute.before",
|
|
23942
22984
|
tool: toolName,
|
|
23943
22985
|
field: "root",
|
|
@@ -23954,8 +22996,8 @@ var sessionWorktreeGuardPlugin = async (ctx) => {
|
|
|
23954
22996
|
if (typeof workdir === "string") {
|
|
23955
22997
|
const newWorkdir = rewritePath(workdir, mainRoot, worktreePath);
|
|
23956
22998
|
if (newWorkdir !== null) {
|
|
23957
|
-
|
|
23958
|
-
safeWriteLog(
|
|
22999
|
+
log13.info(`rewrote bash.workdir: ${workdir} → ${newWorkdir}`);
|
|
23000
|
+
safeWriteLog(PLUGIN_NAME22, {
|
|
23959
23001
|
hook: "tool.execute.before",
|
|
23960
23002
|
tool: toolName,
|
|
23961
23003
|
field: "workdir",
|
|
@@ -23973,7 +23015,7 @@ var sessionWorktreeGuardPlugin = async (ctx) => {
|
|
|
23973
23015
|
}
|
|
23974
23016
|
};
|
|
23975
23017
|
};
|
|
23976
|
-
var
|
|
23018
|
+
var handler22 = sessionWorktreeGuardPlugin;
|
|
23977
23019
|
|
|
23978
23020
|
// lib/opencode-session-probe.ts
|
|
23979
23021
|
import * as path29 from "node:path";
|
|
@@ -24073,8 +23115,8 @@ function createSessionProbe(opts = {}) {
|
|
|
24073
23115
|
}
|
|
24074
23116
|
|
|
24075
23117
|
// plugins/worktree-lifecycle.ts
|
|
24076
|
-
var
|
|
24077
|
-
logLifecycle(
|
|
23118
|
+
var PLUGIN_NAME23 = "worktree-lifecycle";
|
|
23119
|
+
logLifecycle(PLUGIN_NAME23, "import", {});
|
|
24078
23120
|
var IDLE_TOAST_THROTTLE_MS = 60 * 60000;
|
|
24079
23121
|
var IDLE_TOAST_DURATION_MS = 8000;
|
|
24080
23122
|
var IDLE_TOAST_REMINDER_INTERVAL_MS = 30 * 60000;
|
|
@@ -24083,10 +23125,10 @@ var lastIdleToastAt = new Map;
|
|
|
24083
23125
|
var pruneRunning = false;
|
|
24084
23126
|
var _pruneTimer;
|
|
24085
23127
|
var _probe = null;
|
|
24086
|
-
var
|
|
23128
|
+
var log14 = makePluginLogger(PLUGIN_NAME23);
|
|
24087
23129
|
var worktreeLifecyclePlugin = async (ctx) => {
|
|
24088
23130
|
const mainRoot = ctx.directory;
|
|
24089
|
-
logLifecycle(
|
|
23131
|
+
logLifecycle(PLUGIN_NAME23, "activate", {
|
|
24090
23132
|
mainRoot: mainRoot ?? "(not set)",
|
|
24091
23133
|
idle_threshold_ms: IDLE_TOAST_THROTTLE_MS
|
|
24092
23134
|
});
|
|
@@ -24101,13 +23143,13 @@ var worktreeLifecyclePlugin = async (ctx) => {
|
|
|
24101
23143
|
}
|
|
24102
23144
|
_probe = createSessionProbe();
|
|
24103
23145
|
setImmediate(() => {
|
|
24104
|
-
safeAsync(
|
|
23146
|
+
safeAsync(PLUGIN_NAME23, "activate.pruneOrphan", async () => {
|
|
24105
23147
|
const result = await pruneOrphanWorktrees(mainRoot, {
|
|
24106
23148
|
isSessionAlive: _probe.isSessionAlive
|
|
24107
23149
|
});
|
|
24108
23150
|
if (result.cleaned.length > 0 || result.failed.length > 0) {
|
|
24109
|
-
|
|
24110
|
-
safeWriteLog(
|
|
23151
|
+
log14.info(`[pruneOrphan] cleaned=${result.cleaned.length} failed=${result.failed.length} skipped=${result.skipped}`);
|
|
23152
|
+
safeWriteLog(PLUGIN_NAME23, {
|
|
24111
23153
|
hook: "activate.pruneOrphan",
|
|
24112
23154
|
cleaned: result.cleaned,
|
|
24113
23155
|
failed: result.failed,
|
|
@@ -24120,7 +23162,7 @@ var worktreeLifecyclePlugin = async (ctx) => {
|
|
|
24120
23162
|
clearInterval(_pruneTimer);
|
|
24121
23163
|
_pruneTimer = setInterval(() => {
|
|
24122
23164
|
if (pruneRunning) {
|
|
24123
|
-
safeWriteLog(
|
|
23165
|
+
safeWriteLog(PLUGIN_NAME23, {
|
|
24124
23166
|
hook: "interval.pruneOrphan",
|
|
24125
23167
|
action: "skip",
|
|
24126
23168
|
reason: "previous prune still running"
|
|
@@ -24128,14 +23170,14 @@ var worktreeLifecyclePlugin = async (ctx) => {
|
|
|
24128
23170
|
return;
|
|
24129
23171
|
}
|
|
24130
23172
|
pruneRunning = true;
|
|
24131
|
-
safeAsync(
|
|
23173
|
+
safeAsync(PLUGIN_NAME23, "interval.pruneOrphan", async () => {
|
|
24132
23174
|
try {
|
|
24133
23175
|
const result = await pruneOrphanWorktrees(mainRoot, {
|
|
24134
23176
|
isSessionAlive: _probe.isSessionAlive
|
|
24135
23177
|
});
|
|
24136
23178
|
if (result.cleaned.length > 0 || result.failed.length > 0) {
|
|
24137
|
-
|
|
24138
|
-
safeWriteLog(
|
|
23179
|
+
log14.info(`[pruneOrphan interval] cleaned=${result.cleaned.length} failed=${result.failed.length} skipped=${result.skipped}`);
|
|
23180
|
+
safeWriteLog(PLUGIN_NAME23, {
|
|
24139
23181
|
hook: "interval.pruneOrphan",
|
|
24140
23182
|
cleaned: result.cleaned,
|
|
24141
23183
|
failed: result.failed,
|
|
@@ -24150,14 +23192,14 @@ var worktreeLifecyclePlugin = async (ctx) => {
|
|
|
24150
23192
|
_pruneTimer.unref();
|
|
24151
23193
|
return {
|
|
24152
23194
|
event: async ({ event }) => {
|
|
24153
|
-
await safeAsync(
|
|
23195
|
+
await safeAsync(PLUGIN_NAME23, "event", async () => {
|
|
24154
23196
|
const ended = extractEndedSessionID(event);
|
|
24155
23197
|
if (!ended)
|
|
24156
23198
|
return;
|
|
24157
23199
|
if (ended.type === "session.deleted") {
|
|
24158
23200
|
const entry = await getSessionWorktree(ended.sessionID, mainRoot);
|
|
24159
23201
|
if (!entry || entry.status !== "active") {
|
|
24160
|
-
safeWriteLog(
|
|
23202
|
+
safeWriteLog(PLUGIN_NAME23, {
|
|
24161
23203
|
hook: "event",
|
|
24162
23204
|
type: ended.type,
|
|
24163
23205
|
sessionID: ended.sessionID,
|
|
@@ -24172,7 +23214,7 @@ var worktreeLifecyclePlugin = async (ctx) => {
|
|
|
24172
23214
|
const fastDirty = await isWorktreeDirty(entry.worktreePath);
|
|
24173
23215
|
if (!fastDirty) {
|
|
24174
23216
|
await discardSession({ sessionId: ended.sessionID, mainRoot });
|
|
24175
|
-
safeWriteLog(
|
|
23217
|
+
safeWriteLog(PLUGIN_NAME23, {
|
|
24176
23218
|
hook: "event",
|
|
24177
23219
|
type: ended.type,
|
|
24178
23220
|
sessionID: ended.sessionID,
|
|
@@ -24185,7 +23227,7 @@ var worktreeLifecyclePlugin = async (ctx) => {
|
|
|
24185
23227
|
}
|
|
24186
23228
|
}
|
|
24187
23229
|
} catch (err) {
|
|
24188
|
-
|
|
23230
|
+
log14.warn(`[lifecycle] empty-worktree fast-path 检测失败 (回退到常规路径)`, {
|
|
24189
23231
|
sessionId: ended.sessionID,
|
|
24190
23232
|
error: err instanceof Error ? err.message : String(err)
|
|
24191
23233
|
});
|
|
@@ -24200,14 +23242,14 @@ var worktreeLifecyclePlugin = async (ctx) => {
|
|
|
24200
23242
|
});
|
|
24201
23243
|
}
|
|
24202
23244
|
} catch (err) {
|
|
24203
|
-
|
|
23245
|
+
log14.warn(`[lifecycle] checkpointCommit failed (继续 discard)`, {
|
|
24204
23246
|
sessionId: ended.sessionID,
|
|
24205
23247
|
error: err instanceof Error ? err.message : String(err)
|
|
24206
23248
|
});
|
|
24207
23249
|
}
|
|
24208
23250
|
try {
|
|
24209
23251
|
await discardSession({ sessionId: ended.sessionID, mainRoot });
|
|
24210
|
-
safeWriteLog(
|
|
23252
|
+
safeWriteLog(PLUGIN_NAME23, {
|
|
24211
23253
|
hook: "event",
|
|
24212
23254
|
type: ended.type,
|
|
24213
23255
|
sessionID: ended.sessionID,
|
|
@@ -24217,7 +23259,7 @@ var worktreeLifecyclePlugin = async (ctx) => {
|
|
|
24217
23259
|
worktreePath: entry.worktreePath
|
|
24218
23260
|
});
|
|
24219
23261
|
} catch (err) {
|
|
24220
|
-
|
|
23262
|
+
log14.warn(`[lifecycle] discardSession failed`, {
|
|
24221
23263
|
sessionId: ended.sessionID,
|
|
24222
23264
|
error: err instanceof Error ? err.message : String(err)
|
|
24223
23265
|
});
|
|
@@ -24240,8 +23282,8 @@ var worktreeLifecyclePlugin = async (ctx) => {
|
|
|
24240
23282
|
lastIdleToastAt.set(ended.sessionID, now);
|
|
24241
23283
|
const idleMin = Math.round(idleMs / 60000);
|
|
24242
23284
|
const msg = `\uD83D\uDCA4 Session ${ended.sessionID.slice(0, 8)} worktree 已空闲 ${idleMin}min,` + `用 /merge 收尾或 /discard-session 放弃`;
|
|
24243
|
-
const sent = await showToast2(client, { message: msg, variant: "default", duration: IDLE_TOAST_DURATION_MS, title: "CodeForge" },
|
|
24244
|
-
safeWriteLog(
|
|
23285
|
+
const sent = await showToast2(client, { message: msg, variant: "default", duration: IDLE_TOAST_DURATION_MS, title: "CodeForge" }, log14);
|
|
23286
|
+
safeWriteLog(PLUGIN_NAME23, {
|
|
24245
23287
|
hook: "event",
|
|
24246
23288
|
type: ended.type,
|
|
24247
23289
|
sessionID: ended.sessionID,
|
|
@@ -24254,39 +23296,36 @@ var worktreeLifecyclePlugin = async (ctx) => {
|
|
|
24254
23296
|
}
|
|
24255
23297
|
};
|
|
24256
23298
|
};
|
|
24257
|
-
var
|
|
23299
|
+
var handler23 = worktreeLifecyclePlugin;
|
|
24258
23300
|
|
|
24259
23301
|
// src/index.ts
|
|
24260
23302
|
var PLUGIN_ID = "codeforge";
|
|
24261
|
-
var
|
|
23303
|
+
var log15 = makePluginLogger(PLUGIN_ID);
|
|
24262
23304
|
logLifecycle(PLUGIN_ID, "import", { entry: "src/index.ts" });
|
|
24263
23305
|
var HANDLERS = [
|
|
24264
23306
|
{ name: "agent-router", init: handler },
|
|
24265
23307
|
{ name: "arena-orchestrator", init: handler2 },
|
|
24266
23308
|
{ name: "auto-commit", init: handler3 },
|
|
24267
|
-
{ name: "
|
|
24268
|
-
{ name: "
|
|
24269
|
-
{ name: "
|
|
24270
|
-
{ name: "
|
|
24271
|
-
{ name: "
|
|
24272
|
-
{ name: "
|
|
24273
|
-
{ name: "
|
|
24274
|
-
{ name: "
|
|
24275
|
-
{ name: "
|
|
24276
|
-
{ name: "
|
|
24277
|
-
{ name: "
|
|
24278
|
-
{ name: "
|
|
24279
|
-
{ name: "
|
|
24280
|
-
{ name: "
|
|
24281
|
-
{ name: "
|
|
24282
|
-
{ name: "
|
|
24283
|
-
{ name: "
|
|
24284
|
-
{ name: "
|
|
24285
|
-
{ name: "
|
|
24286
|
-
{ name: "
|
|
24287
|
-
{ name: "workflow-engine", init: handler24 },
|
|
24288
|
-
{ name: "session-worktree-guard", init: handler25 },
|
|
24289
|
-
{ name: "worktree-lifecycle", init: handler26 }
|
|
23309
|
+
{ name: "channels", init: handler4 },
|
|
23310
|
+
{ name: "chat-agent-cache", init: handler5 },
|
|
23311
|
+
{ name: "codeforge-tools", init: handler7 },
|
|
23312
|
+
{ name: "discover-spec-suggest", init: handler8 },
|
|
23313
|
+
{ name: "memories-context", init: handler9 },
|
|
23314
|
+
{ name: "model-fallback", init: handler10 },
|
|
23315
|
+
{ name: "parallel-tool-nudge", init: handler13 },
|
|
23316
|
+
{ name: "pwsh-utf8", init: handler14 },
|
|
23317
|
+
{ name: "session-recovery", init: handler15 },
|
|
23318
|
+
{ name: "subtask-heartbeat", init: handler11 },
|
|
23319
|
+
{ name: "subtasks", init: handler16 },
|
|
23320
|
+
{ name: "parallel-status", init: handler12 },
|
|
23321
|
+
{ name: "terminal-monitor", init: handler17 },
|
|
23322
|
+
{ name: "token-manager", init: handler18 },
|
|
23323
|
+
{ name: "tool-heartbeat", init: handler6 },
|
|
23324
|
+
{ name: "tool-policy", init: handler19 },
|
|
23325
|
+
{ name: "update-checker", init: handler20 },
|
|
23326
|
+
{ name: "workflow-engine", init: handler21 },
|
|
23327
|
+
{ name: "session-worktree-guard", init: handler22 },
|
|
23328
|
+
{ name: "worktree-lifecycle", init: handler23 }
|
|
24290
23329
|
];
|
|
24291
23330
|
function makeSerialHook(hookName, fns) {
|
|
24292
23331
|
return async (input, output) => {
|
|
@@ -24296,7 +23335,7 @@ function makeSerialHook(hookName, fns) {
|
|
|
24296
23335
|
} catch (err) {
|
|
24297
23336
|
if (isDeniedError(err))
|
|
24298
23337
|
throw err;
|
|
24299
|
-
|
|
23338
|
+
log15.warn(`[${PLUGIN_ID}] ${hookName} handler 异常(已隔离)`, {
|
|
24300
23339
|
error: err instanceof Error ? err.message : String(err)
|
|
24301
23340
|
});
|
|
24302
23341
|
}
|
|
@@ -24309,7 +23348,7 @@ function createCodeforgeServer(opts) {
|
|
|
24309
23348
|
const yieldResult = shouldYieldToLocalPlugin({ directory: input.directory });
|
|
24310
23349
|
if (yieldResult.yield) {
|
|
24311
23350
|
const msg = formatYieldLog(yieldResult);
|
|
24312
|
-
|
|
23351
|
+
log15.info(msg, { reason: yieldResult.reason, markerPath: yieldResult.markerPath });
|
|
24313
23352
|
logLifecycle(PLUGIN_ID, "activate", {
|
|
24314
23353
|
yield_to_local: true,
|
|
24315
23354
|
yield_reason: yieldResult.reason,
|
|
@@ -24327,7 +23366,7 @@ function createCodeforgeServer(opts) {
|
|
|
24327
23366
|
if (r.status === "fulfilled" && r.value && typeof r.value === "object") {
|
|
24328
23367
|
hooksList.push(r.value);
|
|
24329
23368
|
} else if (r.status === "rejected") {
|
|
24330
|
-
|
|
23369
|
+
log15.warn(`[${PLUGIN_ID}] handler ${HANDLERS[i].name} init failed (隔离,其他 handler 继续)`, { error: r.reason instanceof Error ? r.reason.message : String(r.reason) });
|
|
24331
23370
|
}
|
|
24332
23371
|
});
|
|
24333
23372
|
logLifecycle(PLUGIN_ID, "activate", {
|
|
@@ -24382,7 +23421,7 @@ function createCodeforgeServer(opts) {
|
|
|
24382
23421
|
} catch (err) {
|
|
24383
23422
|
if (isDeniedError(err))
|
|
24384
23423
|
throw err;
|
|
24385
|
-
|
|
23424
|
+
log15.warn(`[${PLUGIN_ID}] event handler 异常(已隔离)`, {
|
|
24386
23425
|
error: err instanceof Error ? err.message : String(err)
|
|
24387
23426
|
});
|
|
24388
23427
|
}
|