@andyqiu/codeforge 0.5.20 → 0.5.21
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 +649 -1613
- 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 {
|
|
@@ -14871,7 +14629,7 @@ function makeOpencodeRunner(opts) {
|
|
|
14871
14629
|
const created = await opts.client.session.create({
|
|
14872
14630
|
body: {
|
|
14873
14631
|
parentID: opts.parentSessionID,
|
|
14874
|
-
title:
|
|
14632
|
+
title: clip2(`subtask:${spec.id}`, 80)
|
|
14875
14633
|
},
|
|
14876
14634
|
query: opts.directory ? { directory: opts.directory } : undefined
|
|
14877
14635
|
});
|
|
@@ -15071,7 +14829,7 @@ function safeStringify(v) {
|
|
|
15071
14829
|
return String(v);
|
|
15072
14830
|
}
|
|
15073
14831
|
}
|
|
15074
|
-
function
|
|
14832
|
+
function clip2(s, max) {
|
|
15075
14833
|
if (!s)
|
|
15076
14834
|
return "";
|
|
15077
14835
|
return s.length <= max ? s : s.slice(0, max - 1) + "…";
|
|
@@ -15391,7 +15149,7 @@ ${r.text.slice(0, 800)}`
|
|
|
15391
15149
|
let childId;
|
|
15392
15150
|
try {
|
|
15393
15151
|
const created = await this.opts.client.session.create({
|
|
15394
|
-
body: { title:
|
|
15152
|
+
body: { title: clip3(opts.title, 80) },
|
|
15395
15153
|
query: this.opts.directory ? { directory: this.opts.directory } : undefined
|
|
15396
15154
|
});
|
|
15397
15155
|
if (created.error || !created.data?.id) {
|
|
@@ -15534,7 +15292,7 @@ function describe5(err) {
|
|
|
15534
15292
|
return String(err);
|
|
15535
15293
|
}
|
|
15536
15294
|
}
|
|
15537
|
-
function
|
|
15295
|
+
function clip3(s, max) {
|
|
15538
15296
|
if (!s)
|
|
15539
15297
|
return "";
|
|
15540
15298
|
return s.length <= max ? s : s.slice(0, max - 1) + "…";
|
|
@@ -15747,8 +15505,8 @@ function parseRuntime(raw, abs) {
|
|
|
15747
15505
|
}
|
|
15748
15506
|
|
|
15749
15507
|
// plugins/tool-heartbeat.ts
|
|
15750
|
-
var
|
|
15751
|
-
logLifecycle(
|
|
15508
|
+
var PLUGIN_NAME6 = "tool-heartbeat";
|
|
15509
|
+
logLifecycle(PLUGIN_NAME6, "import", {});
|
|
15752
15510
|
var HEARTBEAT_INTERVAL_MS = 15000;
|
|
15753
15511
|
var ALERT_30S_MS = 30000;
|
|
15754
15512
|
var ALERT_60S_MS = 60000;
|
|
@@ -15830,15 +15588,15 @@ async function showToast(client, payload, log5) {
|
|
|
15830
15588
|
return false;
|
|
15831
15589
|
}
|
|
15832
15590
|
}
|
|
15833
|
-
var log5 = makePluginLogger(
|
|
15591
|
+
var log5 = makePluginLogger(PLUGIN_NAME6);
|
|
15834
15592
|
var toolHeartbeatServer = async (ctx) => {
|
|
15835
|
-
logLifecycle(
|
|
15593
|
+
logLifecycle(PLUGIN_NAME6, "activate", {
|
|
15836
15594
|
directory: ctx.directory,
|
|
15837
15595
|
intervalMs: HEARTBEAT_INTERVAL_MS
|
|
15838
15596
|
});
|
|
15839
15597
|
const client = ctx.client;
|
|
15840
15598
|
const interval = setInterval(() => {
|
|
15841
|
-
safeAsync(
|
|
15599
|
+
safeAsync(PLUGIN_NAME6, "interval", async () => {
|
|
15842
15600
|
if (inflight.size === 0)
|
|
15843
15601
|
return;
|
|
15844
15602
|
const records = [...inflight.values()];
|
|
@@ -15848,7 +15606,7 @@ var toolHeartbeatServer = async (ctx) => {
|
|
|
15848
15606
|
continue;
|
|
15849
15607
|
const sent = await showToast(client, { message: alert.message, variant: alert.variant }, log5);
|
|
15850
15608
|
r.alertsSent.add(alert.threshold);
|
|
15851
|
-
safeWriteLog(
|
|
15609
|
+
safeWriteLog(PLUGIN_NAME6, {
|
|
15852
15610
|
hook: "interval",
|
|
15853
15611
|
tool: r.toolName,
|
|
15854
15612
|
callID: r.callID,
|
|
@@ -15866,12 +15624,12 @@ var toolHeartbeatServer = async (ctx) => {
|
|
|
15866
15624
|
event: async () => {}
|
|
15867
15625
|
};
|
|
15868
15626
|
};
|
|
15869
|
-
var
|
|
15627
|
+
var handler6 = toolHeartbeatServer;
|
|
15870
15628
|
|
|
15871
15629
|
// plugins/codeforge-tools.ts
|
|
15872
15630
|
var z31 = tool.schema;
|
|
15873
|
-
var
|
|
15874
|
-
logLifecycle(
|
|
15631
|
+
var PLUGIN_NAME7 = "codeforge-tools";
|
|
15632
|
+
logLifecycle(PLUGIN_NAME7, "import");
|
|
15875
15633
|
function wrap(output, metadata) {
|
|
15876
15634
|
const text = typeof output === "string" ? output : JSON.stringify(output, null, 2);
|
|
15877
15635
|
return metadata && Object.keys(metadata).length > 0 ? { output: text, metadata } : { output: text };
|
|
@@ -15881,7 +15639,7 @@ async function runSafe(toolName, fn) {
|
|
|
15881
15639
|
return await fn();
|
|
15882
15640
|
} catch (err) {
|
|
15883
15641
|
const msg = err instanceof Error ? err.message : String(err);
|
|
15884
|
-
safeWriteLog(
|
|
15642
|
+
safeWriteLog(PLUGIN_NAME7, {
|
|
15885
15643
|
level: "error",
|
|
15886
15644
|
tool: toolName,
|
|
15887
15645
|
error: msg
|
|
@@ -16279,7 +16037,7 @@ var codeforgeToolsServer = async (ctx) => {
|
|
|
16279
16037
|
const rt = loadRuntimeSync({ root: ctx.directory });
|
|
16280
16038
|
const browserEnabled = rt.runtime.tools.browser.enabled;
|
|
16281
16039
|
const activeTools = browserEnabled ? [...CORE_TOOL_NAMES, ...BROWSER_TOOL_NAMES] : [...CORE_TOOL_NAMES];
|
|
16282
|
-
logLifecycle(
|
|
16040
|
+
logLifecycle(PLUGIN_NAME7, "activate", {
|
|
16283
16041
|
directory: ctx.directory,
|
|
16284
16042
|
tools: activeTools,
|
|
16285
16043
|
count: activeTools.length,
|
|
@@ -16295,7 +16053,7 @@ var codeforgeToolsServer = async (ctx) => {
|
|
|
16295
16053
|
client: ctx.client,
|
|
16296
16054
|
directory: ctx.directory ?? process.cwd(),
|
|
16297
16055
|
mainRoot: ctx.directory ?? process.cwd(),
|
|
16298
|
-
log: (level, msg, data) => safeWriteLog(
|
|
16056
|
+
log: (level, msg, data) => safeWriteLog(PLUGIN_NAME7, { level, msg, data })
|
|
16299
16057
|
});
|
|
16300
16058
|
__setContext({
|
|
16301
16059
|
mainRoot: ctx.directory ?? process.cwd(),
|
|
@@ -16607,7 +16365,7 @@ var codeforgeToolsServer = async (ctx) => {
|
|
|
16607
16365
|
}
|
|
16608
16366
|
};
|
|
16609
16367
|
};
|
|
16610
|
-
var
|
|
16368
|
+
var handler7 = codeforgeToolsServer;
|
|
16611
16369
|
|
|
16612
16370
|
// plugins/discover-spec-suggest.ts
|
|
16613
16371
|
import { readFileSync as readFileSync3, readdirSync, statSync as statSync3 } from "node:fs";
|
|
@@ -16789,8 +16547,8 @@ function isValidSlug(slug) {
|
|
|
16789
16547
|
}
|
|
16790
16548
|
|
|
16791
16549
|
// plugins/discover-spec-suggest.ts
|
|
16792
|
-
var
|
|
16793
|
-
logLifecycle(
|
|
16550
|
+
var PLUGIN_NAME8 = "discover-spec-suggest";
|
|
16551
|
+
logLifecycle(PLUGIN_NAME8, "import", {});
|
|
16794
16552
|
var TARGET_AGENT = "codeforge";
|
|
16795
16553
|
var SESSION_CAP2 = 200;
|
|
16796
16554
|
var SESSION_TTL_MS2 = 24 * 60 * 60 * 1000;
|
|
@@ -16903,7 +16661,7 @@ function loadSpecs(rootDir, opts = {}) {
|
|
|
16903
16661
|
const dirReader = opts.dirReader ?? defaultDirReader;
|
|
16904
16662
|
const dirExists = opts.dirExists ?? defaultDirExists;
|
|
16905
16663
|
const statReader = opts.statReader ?? defaultStatReader;
|
|
16906
|
-
const log6 = makePluginLogger(
|
|
16664
|
+
const log6 = makePluginLogger(PLUGIN_NAME8);
|
|
16907
16665
|
const specsRoot = join15(rootDir, SPECS_REL_DIR);
|
|
16908
16666
|
const records = [];
|
|
16909
16667
|
if (!dirExists(specsRoot)) {
|
|
@@ -17031,7 +16789,7 @@ function renderCandidatesNudge(matched) {
|
|
|
17031
16789
|
return body.slice(0, NUDGE_MAX_LEN - 4) + `
|
|
17032
16790
|
…`;
|
|
17033
16791
|
}
|
|
17034
|
-
var log6 = makePluginLogger(
|
|
16792
|
+
var log6 = makePluginLogger(PLUGIN_NAME8);
|
|
17035
16793
|
var discoverSpecSuggestServer = async (ctx) => {
|
|
17036
16794
|
try {
|
|
17037
16795
|
const loaded = loadSpecs(ctx.directory ?? process.cwd());
|
|
@@ -17042,7 +16800,7 @@ var discoverSpecSuggestServer = async (ctx) => {
|
|
|
17042
16800
|
error: err instanceof Error ? err.message : String(err)
|
|
17043
16801
|
});
|
|
17044
16802
|
}
|
|
17045
|
-
logLifecycle(
|
|
16803
|
+
logLifecycle(PLUGIN_NAME8, "activate", {
|
|
17046
16804
|
directory: ctx.directory,
|
|
17047
16805
|
specs_loaded: specIndex.length,
|
|
17048
16806
|
target_agent: TARGET_AGENT,
|
|
@@ -17054,7 +16812,7 @@ var discoverSpecSuggestServer = async (ctx) => {
|
|
|
17054
16812
|
});
|
|
17055
16813
|
return {
|
|
17056
16814
|
"chat.message": async (input, output) => {
|
|
17057
|
-
await safeAsync(
|
|
16815
|
+
await safeAsync(PLUGIN_NAME8, "chat.message", async () => {
|
|
17058
16816
|
if (specIndex.length === 0)
|
|
17059
16817
|
return;
|
|
17060
16818
|
const text = extractUserText(output);
|
|
@@ -17069,7 +16827,7 @@ var discoverSpecSuggestServer = async (ctx) => {
|
|
|
17069
16827
|
});
|
|
17070
16828
|
},
|
|
17071
16829
|
"experimental.chat.system.transform": async (input, output) => {
|
|
17072
|
-
await safeAsync(
|
|
16830
|
+
await safeAsync(PLUGIN_NAME8, "experimental.chat.system.transform", async () => {
|
|
17073
16831
|
if (specIndex.length === 0)
|
|
17074
16832
|
return;
|
|
17075
16833
|
if (!output || !Array.isArray(output.system))
|
|
@@ -17095,7 +16853,7 @@ var discoverSpecSuggestServer = async (ctx) => {
|
|
|
17095
16853
|
if (!nudge)
|
|
17096
16854
|
return;
|
|
17097
16855
|
output.system.push(nudge);
|
|
17098
|
-
safeWriteLog(
|
|
16856
|
+
safeWriteLog(PLUGIN_NAME8, {
|
|
17099
16857
|
hook: "experimental.chat.system.transform",
|
|
17100
16858
|
sessionID: sid,
|
|
17101
16859
|
agent: entry.agent,
|
|
@@ -17107,927 +16865,93 @@ var discoverSpecSuggestServer = async (ctx) => {
|
|
|
17107
16865
|
}
|
|
17108
16866
|
};
|
|
17109
16867
|
};
|
|
17110
|
-
var
|
|
16868
|
+
var handler8 = discoverSpecSuggestServer;
|
|
17111
16869
|
|
|
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();
|
|
16870
|
+
// lib/memories.ts
|
|
16871
|
+
import { promises as fs15 } from "node:fs";
|
|
16872
|
+
import * as path19 from "node:path";
|
|
16873
|
+
import * as os5 from "node:os";
|
|
16874
|
+
function resolveConfig(c) {
|
|
16875
|
+
return {
|
|
16876
|
+
projectRoot: c.projectRoot,
|
|
16877
|
+
homeDir: c.homeDir ?? os5.homedir(),
|
|
16878
|
+
projectName: c.projectName ?? path19.basename(c.projectRoot),
|
|
16879
|
+
kh: c.kh,
|
|
16880
|
+
now: c.now ?? Date.now,
|
|
16881
|
+
log: c.log ?? (() => {}),
|
|
16882
|
+
maxPerScope: c.maxPerScope ?? 1000
|
|
16883
|
+
};
|
|
16884
|
+
}
|
|
16885
|
+
function fileFor(scope, cfg) {
|
|
16886
|
+
if (scope === "project") {
|
|
16887
|
+
return path19.join(cfg.projectRoot, ".codeforge", "memories.json");
|
|
17189
16888
|
}
|
|
17190
|
-
|
|
17191
|
-
|
|
16889
|
+
return path19.join(cfg.homeDir, ".codeforge", "memories.json");
|
|
16890
|
+
}
|
|
16891
|
+
async function readBank(p) {
|
|
16892
|
+
try {
|
|
16893
|
+
const raw = await fs15.readFile(p, "utf8");
|
|
16894
|
+
const arr = JSON.parse(raw);
|
|
16895
|
+
if (!Array.isArray(arr))
|
|
16896
|
+
return [];
|
|
16897
|
+
return arr.filter((x) => isMemory(x));
|
|
16898
|
+
} catch {
|
|
16899
|
+
return [];
|
|
17192
16900
|
}
|
|
17193
|
-
|
|
17194
|
-
|
|
17195
|
-
|
|
17196
|
-
|
|
17197
|
-
|
|
17198
|
-
|
|
17199
|
-
|
|
16901
|
+
}
|
|
16902
|
+
async function writeBank(p, items) {
|
|
16903
|
+
await fs15.mkdir(path19.dirname(p), { recursive: true });
|
|
16904
|
+
const tmp = `${p}.tmp`;
|
|
16905
|
+
await fs15.writeFile(tmp, JSON.stringify(items, null, 2), "utf8");
|
|
16906
|
+
await fs15.rename(tmp, p);
|
|
16907
|
+
}
|
|
16908
|
+
function isMemory(x) {
|
|
16909
|
+
if (!x || typeof x !== "object")
|
|
16910
|
+
return false;
|
|
16911
|
+
const m = x;
|
|
16912
|
+
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";
|
|
16913
|
+
}
|
|
16914
|
+
var _seq = 0;
|
|
16915
|
+
function localId(now) {
|
|
16916
|
+
_seq = (_seq + 1) % 1e5;
|
|
16917
|
+
return `mem-${now}-${_seq.toString(36)}`;
|
|
16918
|
+
}
|
|
16919
|
+
async function addMemory(params, cfgRaw) {
|
|
16920
|
+
const cfg = resolveConfig(cfgRaw);
|
|
16921
|
+
if (!params.content || params.content.trim().length === 0) {
|
|
16922
|
+
return { ok: false, id: "", written_to: "local", error: "content 不能为空" };
|
|
17200
16923
|
}
|
|
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;
|
|
16924
|
+
const now = cfg.now();
|
|
16925
|
+
const base = {
|
|
16926
|
+
scope: params.scope,
|
|
16927
|
+
content: params.content.trim(),
|
|
16928
|
+
tags: params.tags?.filter((t) => typeof t === "string") ?? [],
|
|
16929
|
+
project: params.scope === "project" ? cfg.projectName : undefined,
|
|
16930
|
+
created_at: now,
|
|
16931
|
+
updated_at: now,
|
|
16932
|
+
source: params.source ?? "manual"
|
|
16933
|
+
};
|
|
16934
|
+
if (cfg.kh) {
|
|
17217
16935
|
try {
|
|
17218
|
-
const
|
|
17219
|
-
|
|
17220
|
-
|
|
17221
|
-
|
|
17222
|
-
|
|
17223
|
-
|
|
16936
|
+
const r = await cfg.kh.add(base);
|
|
16937
|
+
cfg.log("info", `[memories] add → KH ${r.id}`);
|
|
16938
|
+
return { ok: true, id: r.id, written_to: "kh" };
|
|
16939
|
+
} catch (err) {
|
|
16940
|
+
cfg.log("warn", `[memories] KH 写入失败,降级本地`, {
|
|
16941
|
+
error: err instanceof Error ? err.message : String(err)
|
|
17224
16942
|
});
|
|
17225
|
-
return await Promise.race([callPromise, racer]);
|
|
17226
|
-
} finally {
|
|
17227
|
-
if (timer)
|
|
17228
|
-
clearTimeout(timer);
|
|
17229
16943
|
}
|
|
17230
16944
|
}
|
|
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
|
-
}
|
|
16945
|
+
const file = fileFor(params.scope, cfg);
|
|
16946
|
+
const id = localId(now);
|
|
16947
|
+
const m = { id, ...base };
|
|
16948
|
+
const items = await readBank(file);
|
|
16949
|
+
items.push(m);
|
|
16950
|
+
if (items.length > cfg.maxPerScope) {
|
|
16951
|
+
items.splice(0, items.length - cfg.maxPerScope);
|
|
17245
16952
|
}
|
|
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" };
|
|
16953
|
+
await writeBank(file, items);
|
|
16954
|
+
return { ok: true, id, written_to: "local" };
|
|
18031
16955
|
}
|
|
18032
16956
|
async function listMemories(opts, cfgRaw) {
|
|
18033
16957
|
const cfg = resolveConfig(cfgRaw);
|
|
@@ -18142,9 +17066,9 @@ function bagOfWordsScore(query, doc) {
|
|
|
18142
17066
|
}
|
|
18143
17067
|
|
|
18144
17068
|
// plugins/memories-context.ts
|
|
18145
|
-
var
|
|
18146
|
-
var
|
|
18147
|
-
var
|
|
17069
|
+
var PLUGIN_NAME9 = "memories-context";
|
|
17070
|
+
var INJECTION_MODE = "observe-only";
|
|
17071
|
+
var DEFAULT_CONFIG3 = {
|
|
18148
17072
|
minTextLength: 8,
|
|
18149
17073
|
limit: 5,
|
|
18150
17074
|
perItemLimit: 240,
|
|
@@ -18155,7 +17079,7 @@ var DEFAULT_CONFIG5 = {
|
|
|
18155
17079
|
skipKeywords: ["你好", "hello", "hi", "thanks", "谢谢"]
|
|
18156
17080
|
};
|
|
18157
17081
|
|
|
18158
|
-
class
|
|
17082
|
+
class QueryCache {
|
|
18159
17083
|
ttlMs;
|
|
18160
17084
|
now;
|
|
18161
17085
|
map = new Map;
|
|
@@ -18180,7 +17104,7 @@ class QueryCache2 {
|
|
|
18180
17104
|
}
|
|
18181
17105
|
}
|
|
18182
17106
|
var FILLER_RE = /^(请|帮我|麻烦|我想|你能|你可以|看一?下|查一?下|how (do|can) (i|you)|can you|could you|please|help me)\s*/i;
|
|
18183
|
-
function
|
|
17107
|
+
function extractQuery(text) {
|
|
18184
17108
|
if (!text)
|
|
18185
17109
|
return "";
|
|
18186
17110
|
const norm = text.trim().replace(/\s+/g, " ");
|
|
@@ -18193,7 +17117,7 @@ function extractQuery2(text) {
|
|
|
18193
17117
|
}
|
|
18194
17118
|
return head.slice(0, 100);
|
|
18195
17119
|
}
|
|
18196
|
-
function shouldRecall(text, cfg =
|
|
17120
|
+
function shouldRecall(text, cfg = DEFAULT_CONFIG3) {
|
|
18197
17121
|
if (!text)
|
|
18198
17122
|
return false;
|
|
18199
17123
|
const t = text.trim();
|
|
@@ -18227,28 +17151,28 @@ function parseDirective(text) {
|
|
|
18227
17151
|
return { kind: "list" };
|
|
18228
17152
|
return { kind: "none" };
|
|
18229
17153
|
}
|
|
18230
|
-
async function
|
|
18231
|
-
const cfg = opts.cfg ??
|
|
18232
|
-
const cache2 = opts.cache ?? new
|
|
17154
|
+
async function handleMessage2(raw, opts) {
|
|
17155
|
+
const cfg = opts.cfg ?? DEFAULT_CONFIG3;
|
|
17156
|
+
const cache2 = opts.cache ?? new QueryCache(cfg.cacheTtlMs);
|
|
18233
17157
|
const ctx = raw ?? {};
|
|
18234
|
-
const
|
|
17158
|
+
const log7 = ctx.log;
|
|
18235
17159
|
const text = (ctx.content ?? "").trim();
|
|
18236
17160
|
if (!text)
|
|
18237
|
-
return { kind: "noop", reason: "empty", mode:
|
|
17161
|
+
return { kind: "noop", reason: "empty", mode: INJECTION_MODE };
|
|
18238
17162
|
const dir = parseDirective(text);
|
|
18239
17163
|
if (dir.kind !== "none") {
|
|
18240
|
-
return await handleDirective(dir, ctx, opts.memCfg,
|
|
17164
|
+
return await handleDirective(dir, ctx, opts.memCfg, log7);
|
|
18241
17165
|
}
|
|
18242
17166
|
if (!shouldRecall(text, cfg)) {
|
|
18243
|
-
|
|
18244
|
-
return { kind: "noop", reason: "filtered", mode:
|
|
17167
|
+
log7?.debug?.(`[${PLUGIN_NAME9}] skip (filter)`, { textLen: text.length });
|
|
17168
|
+
return { kind: "noop", reason: "filtered", mode: INJECTION_MODE };
|
|
18245
17169
|
}
|
|
18246
|
-
const query =
|
|
17170
|
+
const query = extractQuery(text);
|
|
18247
17171
|
if (!query)
|
|
18248
|
-
return { kind: "noop", reason: "empty_query", mode:
|
|
17172
|
+
return { kind: "noop", reason: "empty_query", mode: INJECTION_MODE };
|
|
18249
17173
|
if (cache2.shouldSkip(query)) {
|
|
18250
|
-
|
|
18251
|
-
return { kind: "noop", reason: "cache", mode:
|
|
17174
|
+
log7?.debug?.(`[${PLUGIN_NAME9}] cache hit`, { query });
|
|
17175
|
+
return { kind: "noop", reason: "cache", mode: INJECTION_MODE };
|
|
18252
17176
|
}
|
|
18253
17177
|
const racer = opts.scheduler?.raceTimeout ?? (async (p, ms) => {
|
|
18254
17178
|
let timer = null;
|
|
@@ -18272,43 +17196,43 @@ async function handleMessage3(raw, opts) {
|
|
|
18272
17196
|
}, opts.memCfg);
|
|
18273
17197
|
const result = await racer(injectPromise, cfg.timeoutMs);
|
|
18274
17198
|
if (result === "__timeout__") {
|
|
18275
|
-
|
|
17199
|
+
log7?.warn(`[${PLUGIN_NAME9}] timeout`, { query, ms: cfg.timeoutMs });
|
|
18276
17200
|
cache2.record(query, 0);
|
|
18277
|
-
return { kind: "noop", reason: "timeout", mode:
|
|
17201
|
+
return { kind: "noop", reason: "timeout", mode: INJECTION_MODE };
|
|
18278
17202
|
}
|
|
18279
17203
|
if (result.recalled === 0 || result.used === 0) {
|
|
18280
17204
|
cache2.record(query, 0);
|
|
18281
|
-
return { kind: "noop", reason: "no_match", mode:
|
|
17205
|
+
return { kind: "noop", reason: "no_match", mode: INJECTION_MODE };
|
|
18282
17206
|
}
|
|
18283
17207
|
if (typeof ctx.injectContext === "function") {
|
|
18284
17208
|
try {
|
|
18285
17209
|
await ctx.injectContext(result.text);
|
|
18286
17210
|
} catch (err) {
|
|
18287
|
-
|
|
17211
|
+
log7?.warn(`[${PLUGIN_NAME9}] injectContext threw`, {
|
|
18288
17212
|
error: err instanceof Error ? err.message : String(err)
|
|
18289
17213
|
});
|
|
18290
17214
|
}
|
|
18291
17215
|
}
|
|
18292
17216
|
cache2.record(query, result.recalled);
|
|
18293
|
-
|
|
18294
|
-
return { kind: "injected", payload: result, mode:
|
|
17217
|
+
log7?.info(`[${PLUGIN_NAME9}] observe-only: ${result.used}/${result.recalled} memories ready`, { query, mode: INJECTION_MODE });
|
|
17218
|
+
return { kind: "injected", payload: result, mode: INJECTION_MODE };
|
|
18295
17219
|
}
|
|
18296
|
-
async function handleDirective(dir, ctx, memCfg,
|
|
17220
|
+
async function handleDirective(dir, ctx, memCfg, log7) {
|
|
18297
17221
|
if (dir.kind === "remember") {
|
|
18298
17222
|
const r = await addMemory({ scope: dir.scope, content: dir.content, source: "directive" }, memCfg);
|
|
18299
17223
|
if (r.ok) {
|
|
18300
17224
|
const target = r.written_to === "kh" ? "KH" : "本地";
|
|
18301
17225
|
await safeReply(ctx, `\uD83E\uDDE0 已记住(${dir.scope} / ${target},id=${r.id})`);
|
|
18302
|
-
|
|
17226
|
+
log7?.info(`[${PLUGIN_NAME9}] /remember ok`, {
|
|
18303
17227
|
scope: dir.scope,
|
|
18304
17228
|
id: r.id,
|
|
18305
|
-
mode:
|
|
17229
|
+
mode: INJECTION_MODE
|
|
18306
17230
|
});
|
|
18307
17231
|
} else {
|
|
18308
17232
|
await safeReply(ctx, `❌ 记忆失败:${r.error ?? "unknown"}`);
|
|
18309
|
-
|
|
17233
|
+
log7?.warn(`[${PLUGIN_NAME9}] /remember failed`, { error: r.error });
|
|
18310
17234
|
}
|
|
18311
|
-
return { kind: "remembered", result: r, mode:
|
|
17235
|
+
return { kind: "remembered", result: r, mode: INJECTION_MODE };
|
|
18312
17236
|
}
|
|
18313
17237
|
if (dir.kind === "forget") {
|
|
18314
17238
|
const r = await removeMemory({ scope: "project", id: dir.id }, memCfg);
|
|
@@ -18318,17 +17242,17 @@ async function handleDirective(dir, ctx, memCfg, log8) {
|
|
|
18318
17242
|
const r2 = await removeMemory({ scope: "user", id: dir.id }, memCfg);
|
|
18319
17243
|
if (r2.removed) {
|
|
18320
17244
|
await safeReply(ctx, `\uD83D\uDDD1 已遗忘记忆 ${dir.id}(user scope)`);
|
|
18321
|
-
return { kind: "forgotten", result: r2, mode:
|
|
17245
|
+
return { kind: "forgotten", result: r2, mode: INJECTION_MODE };
|
|
18322
17246
|
}
|
|
18323
17247
|
await safeReply(ctx, `⚠ 找不到记忆 ${dir.id}`);
|
|
18324
17248
|
}
|
|
18325
|
-
return { kind: "forgotten", result: r, mode:
|
|
17249
|
+
return { kind: "forgotten", result: r, mode: INJECTION_MODE };
|
|
18326
17250
|
}
|
|
18327
17251
|
const proj = await listMemories({ scope: "project" }, memCfg);
|
|
18328
17252
|
const user = await listMemories({ scope: "user" }, memCfg);
|
|
18329
17253
|
const total = proj.length + user.length;
|
|
18330
17254
|
await safeReply(ctx, `\uD83D\uDCDA 共 ${total} 条记忆(project=${proj.length}, user=${user.length})`);
|
|
18331
|
-
return { kind: "listed", total, mode:
|
|
17255
|
+
return { kind: "listed", total, mode: INJECTION_MODE };
|
|
18332
17256
|
}
|
|
18333
17257
|
async function safeReply(ctx, text) {
|
|
18334
17258
|
if (typeof ctx.reply !== "function")
|
|
@@ -18337,58 +17261,58 @@ async function safeReply(ctx, text) {
|
|
|
18337
17261
|
await ctx.reply(text);
|
|
18338
17262
|
} catch {}
|
|
18339
17263
|
}
|
|
18340
|
-
logLifecycle(
|
|
18341
|
-
var
|
|
17264
|
+
logLifecycle(PLUGIN_NAME9, "import");
|
|
17265
|
+
var sharedCache = new QueryCache(DEFAULT_CONFIG3.cacheTtlMs);
|
|
18342
17266
|
function buildMemCfg(directory) {
|
|
18343
17267
|
return { projectRoot: directory };
|
|
18344
17268
|
}
|
|
18345
17269
|
var memoriesContextServer = async (ctx) => {
|
|
18346
|
-
const
|
|
17270
|
+
const log7 = makePluginLogger(PLUGIN_NAME9);
|
|
18347
17271
|
const memCfg = buildMemCfg(ctx.directory);
|
|
18348
|
-
logLifecycle(
|
|
17272
|
+
logLifecycle(PLUGIN_NAME9, "activate", {
|
|
18349
17273
|
directory: ctx.directory,
|
|
18350
17274
|
projectRoot: memCfg.projectRoot,
|
|
18351
|
-
mode:
|
|
17275
|
+
mode: INJECTION_MODE
|
|
18352
17276
|
});
|
|
18353
17277
|
return {
|
|
18354
17278
|
"chat.message": async (input, output) => {
|
|
18355
|
-
await safeAsync(
|
|
17279
|
+
await safeAsync(PLUGIN_NAME9, "chat.message", async () => {
|
|
18356
17280
|
const text = extractUserText(output);
|
|
18357
17281
|
if (!text)
|
|
18358
17282
|
return;
|
|
18359
|
-
await
|
|
17283
|
+
await handleMessage2({
|
|
18360
17284
|
content: text,
|
|
18361
17285
|
sessionId: input.sessionID,
|
|
18362
17286
|
injectContext: async (markdown) => {
|
|
18363
|
-
|
|
17287
|
+
log7.info(`memories context candidate (${markdown.length} chars)`, {
|
|
18364
17288
|
sessionID: input.sessionID,
|
|
18365
|
-
mode:
|
|
17289
|
+
mode: INJECTION_MODE,
|
|
18366
17290
|
preview: markdown.slice(0, 200)
|
|
18367
17291
|
});
|
|
18368
17292
|
},
|
|
18369
17293
|
reply: async (msg) => {
|
|
18370
|
-
|
|
17294
|
+
log7.info(`directive reply (observe-only): ${msg}`, {
|
|
18371
17295
|
sessionID: input.sessionID,
|
|
18372
|
-
mode:
|
|
17296
|
+
mode: INJECTION_MODE
|
|
18373
17297
|
});
|
|
18374
17298
|
},
|
|
18375
|
-
log:
|
|
18376
|
-
}, { cache:
|
|
17299
|
+
log: log7
|
|
17300
|
+
}, { cache: sharedCache, memCfg });
|
|
18377
17301
|
});
|
|
18378
17302
|
}
|
|
18379
17303
|
};
|
|
18380
17304
|
};
|
|
18381
|
-
var
|
|
17305
|
+
var handler9 = memoriesContextServer;
|
|
18382
17306
|
|
|
18383
17307
|
// plugins/model-fallback.ts
|
|
18384
|
-
var
|
|
17308
|
+
var PLUGIN_NAME10 = "model-fallback";
|
|
18385
17309
|
var state = {
|
|
18386
17310
|
config: null,
|
|
18387
17311
|
configPath: null,
|
|
18388
17312
|
warnings: [],
|
|
18389
17313
|
error: null
|
|
18390
17314
|
};
|
|
18391
|
-
logLifecycle(
|
|
17315
|
+
logLifecycle(PLUGIN_NAME10, "import");
|
|
18392
17316
|
function loadOnce(root) {
|
|
18393
17317
|
if (state.config !== null || state.error !== null)
|
|
18394
17318
|
return;
|
|
@@ -18398,11 +17322,11 @@ function loadOnce(root) {
|
|
|
18398
17322
|
state.configPath = r.path ?? null;
|
|
18399
17323
|
state.warnings = r.warnings;
|
|
18400
17324
|
if (r.warnings.length > 0) {
|
|
18401
|
-
safeWriteLog(
|
|
17325
|
+
safeWriteLog(PLUGIN_NAME10, { phase: "load.warnings", warnings: r.warnings });
|
|
18402
17326
|
}
|
|
18403
17327
|
} else {
|
|
18404
17328
|
state.error = r.error ?? "unknown_load_error";
|
|
18405
|
-
safeWriteLog(
|
|
17329
|
+
safeWriteLog(PLUGIN_NAME10, { phase: "load.failed", error: state.error, path: r.path });
|
|
18406
17330
|
}
|
|
18407
17331
|
}
|
|
18408
17332
|
var MODEL_ERR_PATTERNS = [
|
|
@@ -18447,9 +17371,9 @@ fallback 链已用尽:${meta.chain.join(" → ")}`;
|
|
|
18447
17371
|
完整链:${meta.chain.join(" → ")}`;
|
|
18448
17372
|
}
|
|
18449
17373
|
var modelFallbackServer = async (ctx) => {
|
|
18450
|
-
const
|
|
17374
|
+
const log7 = makePluginLogger(PLUGIN_NAME10);
|
|
18451
17375
|
loadOnce(ctx.directory ?? process.cwd());
|
|
18452
|
-
logLifecycle(
|
|
17376
|
+
logLifecycle(PLUGIN_NAME10, "activate", {
|
|
18453
17377
|
directory: ctx.directory,
|
|
18454
17378
|
config_path: state.configPath,
|
|
18455
17379
|
config_loaded: state.config !== null,
|
|
@@ -18459,7 +17383,7 @@ var modelFallbackServer = async (ctx) => {
|
|
|
18459
17383
|
});
|
|
18460
17384
|
return {
|
|
18461
17385
|
"chat.params": async (input, output) => {
|
|
18462
|
-
await safeAsync(
|
|
17386
|
+
await safeAsync(PLUGIN_NAME10, "chat.params", async () => {
|
|
18463
17387
|
if (!state.config)
|
|
18464
17388
|
return;
|
|
18465
17389
|
const agent = input.agent;
|
|
@@ -18480,7 +17404,7 @@ var modelFallbackServer = async (ctx) => {
|
|
|
18480
17404
|
next: meta.next_fallback,
|
|
18481
17405
|
source: meta.source
|
|
18482
17406
|
};
|
|
18483
|
-
safeWriteLog(
|
|
17407
|
+
safeWriteLog(PLUGIN_NAME10, {
|
|
18484
17408
|
hook: "chat.params",
|
|
18485
17409
|
agent,
|
|
18486
17410
|
model: currentModel,
|
|
@@ -18490,7 +17414,7 @@ var modelFallbackServer = async (ctx) => {
|
|
|
18490
17414
|
});
|
|
18491
17415
|
},
|
|
18492
17416
|
event: async ({ event }) => {
|
|
18493
|
-
await safeAsync(
|
|
17417
|
+
await safeAsync(PLUGIN_NAME10, "event", async () => {
|
|
18494
17418
|
if (!state.config)
|
|
18495
17419
|
return;
|
|
18496
17420
|
const e = event;
|
|
@@ -18506,8 +17430,8 @@ var modelFallbackServer = async (ctx) => {
|
|
|
18506
17430
|
const model = props.model ?? "unknown/unknown";
|
|
18507
17431
|
const meta = buildFallbackMeta(state.config, agent, model);
|
|
18508
17432
|
const suggestion = meta ? buildSuggestion(meta, String(message)) : `⚠️ ${agent}/${model} 失败:${message}`;
|
|
18509
|
-
|
|
18510
|
-
safeWriteLog(
|
|
17433
|
+
log7.warn(`[${PLUGIN_NAME10}] ${suggestion}`);
|
|
17434
|
+
safeWriteLog(PLUGIN_NAME10, {
|
|
18511
17435
|
hook: "event.error",
|
|
18512
17436
|
eventType: e.type,
|
|
18513
17437
|
agent,
|
|
@@ -18519,7 +17443,7 @@ var modelFallbackServer = async (ctx) => {
|
|
|
18519
17443
|
}
|
|
18520
17444
|
};
|
|
18521
17445
|
};
|
|
18522
|
-
var
|
|
17446
|
+
var handler10 = modelFallbackServer;
|
|
18523
17447
|
|
|
18524
17448
|
// plugins/subtask-heartbeat.ts
|
|
18525
17449
|
import { promises as fsPromises } from "node:fs";
|
|
@@ -18533,8 +17457,8 @@ var sweepExpiredSessionParents2 = sweepExpiredSessionParents;
|
|
|
18533
17457
|
var _bulkInjectSessionParentMap2 = _bulkInjectSessionParentMap;
|
|
18534
17458
|
var _capSessionParentMap2 = _capSessionParentMap;
|
|
18535
17459
|
var _setPersistRootForTests2 = _setPersistRootForTests;
|
|
18536
|
-
var
|
|
18537
|
-
logLifecycle(
|
|
17460
|
+
var PLUGIN_NAME11 = "subtask-heartbeat";
|
|
17461
|
+
logLifecycle(PLUGIN_NAME11, "import", {});
|
|
18538
17462
|
var HEARTBEAT_INTERVAL_MS2 = 30000;
|
|
18539
17463
|
var HEARTBEAT_DEBOUNCE_MS = 25000;
|
|
18540
17464
|
var TOAST_DURATION_MS2 = 5000;
|
|
@@ -18544,11 +17468,11 @@ var PENDING_TASK_MAX_PARENTS = 64;
|
|
|
18544
17468
|
var PENDING_TASK_MAX_PER_PARENT = 16;
|
|
18545
17469
|
var DESCRIPTION_MAX_LEN = 60;
|
|
18546
17470
|
var PARENT_PARSE_FAIL_MAX_LOG = 10;
|
|
18547
|
-
var
|
|
17471
|
+
var inflight2 = new Map;
|
|
18548
17472
|
var pendingTask = new Map;
|
|
18549
17473
|
var _parentParseFailLogged = 0;
|
|
18550
17474
|
function _snapshotInflight() {
|
|
18551
|
-
return [...
|
|
17475
|
+
return [...inflight2.values()].map((r) => ({ ...r }));
|
|
18552
17476
|
}
|
|
18553
17477
|
function detectUnparsedParentID(event) {
|
|
18554
17478
|
if (!event || typeof event !== "object")
|
|
@@ -18684,11 +17608,11 @@ function registerInflight(payload, now = Date.now()) {
|
|
|
18684
17608
|
lastTool: null,
|
|
18685
17609
|
pendingCalls: new Map
|
|
18686
17610
|
};
|
|
18687
|
-
|
|
17611
|
+
inflight2.set(payload.childID, r);
|
|
18688
17612
|
return r;
|
|
18689
17613
|
}
|
|
18690
17614
|
function recordToolBeat(sessionID, tool2, now = Date.now()) {
|
|
18691
|
-
const r =
|
|
17615
|
+
const r = inflight2.get(sessionID);
|
|
18692
17616
|
if (!r)
|
|
18693
17617
|
return null;
|
|
18694
17618
|
r.lastBeatAt = now;
|
|
@@ -18696,15 +17620,15 @@ function recordToolBeat(sessionID, tool2, now = Date.now()) {
|
|
|
18696
17620
|
return r;
|
|
18697
17621
|
}
|
|
18698
17622
|
function clearInflight2(sessionID) {
|
|
18699
|
-
const r =
|
|
17623
|
+
const r = inflight2.get(sessionID);
|
|
18700
17624
|
if (!r)
|
|
18701
17625
|
return null;
|
|
18702
|
-
|
|
17626
|
+
inflight2.delete(sessionID);
|
|
18703
17627
|
return r;
|
|
18704
17628
|
}
|
|
18705
17629
|
function pickHeartbeats(now = Date.now()) {
|
|
18706
17630
|
const out = [];
|
|
18707
|
-
for (const r of
|
|
17631
|
+
for (const r of inflight2.values()) {
|
|
18708
17632
|
if (now - r.lastBeatAt >= HEARTBEAT_DEBOUNCE_MS)
|
|
18709
17633
|
out.push(r);
|
|
18710
17634
|
}
|
|
@@ -18823,13 +17747,13 @@ function buildAfterLogLine(toolName, ok, durationMs, now = Date.now()) {
|
|
|
18823
17747
|
duration_ms: durationMs
|
|
18824
17748
|
});
|
|
18825
17749
|
}
|
|
18826
|
-
async function appendSubagentLog(filePath, line,
|
|
17750
|
+
async function appendSubagentLog(filePath, line, log7) {
|
|
18827
17751
|
try {
|
|
18828
17752
|
await fsPromises.mkdir(path20.dirname(filePath), { recursive: true });
|
|
18829
17753
|
await fsPromises.appendFile(filePath, line + `
|
|
18830
17754
|
`, "utf8");
|
|
18831
17755
|
} catch (err) {
|
|
18832
|
-
|
|
17756
|
+
log7?.debug?.("appendSubagentLog 失败(已隔离)", {
|
|
18833
17757
|
error: err instanceof Error ? err.message : String(err),
|
|
18834
17758
|
file: filePath
|
|
18835
17759
|
});
|
|
@@ -18854,9 +17778,9 @@ function normalizeVariant2(raw) {
|
|
|
18854
17778
|
return "default";
|
|
18855
17779
|
return raw;
|
|
18856
17780
|
}
|
|
18857
|
-
async function showToast2(client, payload,
|
|
17781
|
+
async function showToast2(client, payload, log7) {
|
|
18858
17782
|
if (typeof client?.tui?.showToast !== "function") {
|
|
18859
|
-
|
|
17783
|
+
log7?.debug?.("tui.showToast 不可用,noop");
|
|
18860
17784
|
return false;
|
|
18861
17785
|
}
|
|
18862
17786
|
try {
|
|
@@ -18870,15 +17794,15 @@ async function showToast2(client, payload, log8) {
|
|
|
18870
17794
|
});
|
|
18871
17795
|
return true;
|
|
18872
17796
|
} catch (err) {
|
|
18873
|
-
|
|
17797
|
+
log7?.warn("tui.showToast 抛错(已隔离)", {
|
|
18874
17798
|
error: err instanceof Error ? err.message : String(err)
|
|
18875
17799
|
});
|
|
18876
17800
|
return false;
|
|
18877
17801
|
}
|
|
18878
17802
|
}
|
|
18879
|
-
var
|
|
17803
|
+
var log7 = makePluginLogger(PLUGIN_NAME11);
|
|
18880
17804
|
var subtaskHeartbeatServer = async (ctx) => {
|
|
18881
|
-
logLifecycle(
|
|
17805
|
+
logLifecycle(PLUGIN_NAME11, "activate", {
|
|
18882
17806
|
directory: ctx.directory,
|
|
18883
17807
|
intervalMs: HEARTBEAT_INTERVAL_MS2
|
|
18884
17808
|
});
|
|
@@ -18895,7 +17819,7 @@ var subtaskHeartbeatServer = async (ctx) => {
|
|
|
18895
17819
|
}));
|
|
18896
17820
|
_bulkInjectSessionParentMap2(entries);
|
|
18897
17821
|
const cappedOut = _capSessionParentMap2();
|
|
18898
|
-
safeWriteLog(
|
|
17822
|
+
safeWriteLog(PLUGIN_NAME11, {
|
|
18899
17823
|
hook: "activate",
|
|
18900
17824
|
type: "parent-map.restore",
|
|
18901
17825
|
restored: restored.size,
|
|
@@ -18903,27 +17827,27 @@ var subtaskHeartbeatServer = async (ctx) => {
|
|
|
18903
17827
|
});
|
|
18904
17828
|
}
|
|
18905
17829
|
} catch (err) {
|
|
18906
|
-
|
|
17830
|
+
log7.warn("loadParentMap 失败(已隔离),降级为空表", {
|
|
18907
17831
|
error: err instanceof Error ? err.message : String(err)
|
|
18908
17832
|
});
|
|
18909
17833
|
}
|
|
18910
17834
|
const interval = setInterval(() => {
|
|
18911
|
-
safeAsync(
|
|
17835
|
+
safeAsync(PLUGIN_NAME11, "interval", async () => {
|
|
18912
17836
|
const swept = sweepExpiredPendingTasks();
|
|
18913
17837
|
if (swept > 0) {
|
|
18914
|
-
safeWriteLog(
|
|
17838
|
+
safeWriteLog(PLUGIN_NAME11, { hook: "interval", pending_task_swept: swept });
|
|
18915
17839
|
}
|
|
18916
17840
|
const sweptParents = sweepExpiredSessionParents2();
|
|
18917
17841
|
if (sweptParents > 0) {
|
|
18918
|
-
safeWriteLog(
|
|
17842
|
+
safeWriteLog(PLUGIN_NAME11, { hook: "interval", session_parent_swept: sweptParents });
|
|
18919
17843
|
}
|
|
18920
17844
|
const beats = pickHeartbeats();
|
|
18921
17845
|
if (beats.length === 0)
|
|
18922
17846
|
return;
|
|
18923
17847
|
for (const r of beats) {
|
|
18924
17848
|
const t = buildHeartbeatToast(r);
|
|
18925
|
-
const sent = await showToast2(client, t,
|
|
18926
|
-
safeWriteLog(
|
|
17849
|
+
const sent = await showToast2(client, t, log7);
|
|
17850
|
+
safeWriteLog(PLUGIN_NAME11, {
|
|
18927
17851
|
hook: "interval",
|
|
18928
17852
|
child: r.childID,
|
|
18929
17853
|
parent: r.parentID,
|
|
@@ -18940,15 +17864,15 @@ var subtaskHeartbeatServer = async (ctx) => {
|
|
|
18940
17864
|
}
|
|
18941
17865
|
return {
|
|
18942
17866
|
event: async ({ event }) => {
|
|
18943
|
-
await safeAsync(
|
|
17867
|
+
await safeAsync(PLUGIN_NAME11, "event", async () => {
|
|
18944
17868
|
try {
|
|
18945
17869
|
if (detectUnparsedParentID(event) && _parentParseFailLogged < PARENT_PARSE_FAIL_MAX_LOG) {
|
|
18946
17870
|
_parentParseFailLogged++;
|
|
18947
|
-
|
|
17871
|
+
log7.warn("session.created 含 parentID 关键字但 extractCreatedChild 解析失败,可能 SDK schema 变更", {
|
|
18948
17872
|
sample_count: _parentParseFailLogged,
|
|
18949
17873
|
hint: "如频繁出现请检查 plugins/subtask-heartbeat.ts::extractCreatedChild 是否适配新 SDK"
|
|
18950
17874
|
});
|
|
18951
|
-
safeWriteLog(
|
|
17875
|
+
safeWriteLog(PLUGIN_NAME11, {
|
|
18952
17876
|
hook: "event",
|
|
18953
17877
|
type: "session.created.unparsed-parent-id",
|
|
18954
17878
|
sample_count: _parentParseFailLogged
|
|
@@ -18960,7 +17884,7 @@ var subtaskHeartbeatServer = async (ctx) => {
|
|
|
18960
17884
|
try {
|
|
18961
17885
|
recordSessionParent2(created.childID, created.parentID);
|
|
18962
17886
|
} catch (err) {
|
|
18963
|
-
|
|
17887
|
+
log7.warn("recordSessionParent 抛错(已隔离)", {
|
|
18964
17888
|
error: err instanceof Error ? err.message : String(err)
|
|
18965
17889
|
});
|
|
18966
17890
|
}
|
|
@@ -18971,7 +17895,7 @@ var subtaskHeartbeatServer = async (ctx) => {
|
|
|
18971
17895
|
agent: pending?.agent ?? created.agent,
|
|
18972
17896
|
description: pending?.description ?? null
|
|
18973
17897
|
});
|
|
18974
|
-
safeWriteLog(
|
|
17898
|
+
safeWriteLog(PLUGIN_NAME11, {
|
|
18975
17899
|
hook: "event",
|
|
18976
17900
|
type: "session.created",
|
|
18977
17901
|
child: created.childID,
|
|
@@ -18981,8 +17905,8 @@ var subtaskHeartbeatServer = async (ctx) => {
|
|
|
18981
17905
|
description_len: record.description?.length ?? 0
|
|
18982
17906
|
});
|
|
18983
17907
|
const startToast = buildStartToast(record);
|
|
18984
|
-
const sent = await showToast2(client, { ...startToast, duration: START_TOAST_DURATION_MS },
|
|
18985
|
-
safeWriteLog(
|
|
17908
|
+
const sent = await showToast2(client, { ...startToast, duration: START_TOAST_DURATION_MS }, log7);
|
|
17909
|
+
safeWriteLog(PLUGIN_NAME11, {
|
|
18986
17910
|
hook: "event",
|
|
18987
17911
|
type: "session.created.toast",
|
|
18988
17912
|
child: created.childID,
|
|
@@ -19001,8 +17925,8 @@ var subtaskHeartbeatServer = async (ctx) => {
|
|
|
19001
17925
|
const r = clearInflight2(ended.sessionID);
|
|
19002
17926
|
if (r) {
|
|
19003
17927
|
const t = buildEndToast(r, ended.type);
|
|
19004
|
-
const sent = await showToast2(client, t,
|
|
19005
|
-
safeWriteLog(
|
|
17928
|
+
const sent = await showToast2(client, t, log7);
|
|
17929
|
+
safeWriteLog(PLUGIN_NAME11, {
|
|
19006
17930
|
hook: "event",
|
|
19007
17931
|
type: ended.type,
|
|
19008
17932
|
child: r.childID,
|
|
@@ -19020,14 +17944,14 @@ var subtaskHeartbeatServer = async (ctx) => {
|
|
|
19020
17944
|
},
|
|
19021
17945
|
"tool.execute.before": async (input, output) => {
|
|
19022
17946
|
const isTaskTool = input?.tool === "task";
|
|
19023
|
-
if (
|
|
17947
|
+
if (inflight2.size === 0 && !isTaskTool)
|
|
19024
17948
|
return;
|
|
19025
|
-
await safeAsync(
|
|
17949
|
+
await safeAsync(PLUGIN_NAME11, "tool.execute.before", async () => {
|
|
19026
17950
|
if (!input || typeof input.sessionID !== "string" || typeof input.tool !== "string")
|
|
19027
17951
|
return;
|
|
19028
17952
|
if (isTaskTool) {
|
|
19029
17953
|
const args = output?.args ?? null;
|
|
19030
|
-
safeWriteLog(
|
|
17954
|
+
safeWriteLog(PLUGIN_NAME11, {
|
|
19031
17955
|
hook: "tool.execute.before.task",
|
|
19032
17956
|
sessionID: input.sessionID,
|
|
19033
17957
|
args_keys: args && typeof args === "object" ? Object.keys(args) : null,
|
|
@@ -19039,7 +17963,7 @@ var subtaskHeartbeatServer = async (ctx) => {
|
|
|
19039
17963
|
agent: extracted.subagentType,
|
|
19040
17964
|
description: extracted.description
|
|
19041
17965
|
});
|
|
19042
|
-
safeWriteLog(
|
|
17966
|
+
safeWriteLog(PLUGIN_NAME11, {
|
|
19043
17967
|
hook: "tool.execute.before.task.enqueued",
|
|
19044
17968
|
parent: input.sessionID,
|
|
19045
17969
|
agent: extracted.subagentType,
|
|
@@ -19047,7 +17971,7 @@ var subtaskHeartbeatServer = async (ctx) => {
|
|
|
19047
17971
|
});
|
|
19048
17972
|
}
|
|
19049
17973
|
}
|
|
19050
|
-
const rec =
|
|
17974
|
+
const rec = inflight2.get(input.sessionID);
|
|
19051
17975
|
if (rec) {
|
|
19052
17976
|
recordToolBeat(input.sessionID, input.tool);
|
|
19053
17977
|
if (!rec.pendingCalls)
|
|
@@ -19059,18 +17983,18 @@ var subtaskHeartbeatServer = async (ctx) => {
|
|
|
19059
17983
|
const args = output?.args ?? null;
|
|
19060
17984
|
const line = buildBeforeLogLine(input.tool, args);
|
|
19061
17985
|
const file = subagentLogPath(cwd, rec.parentID, rec.childID);
|
|
19062
|
-
appendSubagentLog(file, line,
|
|
17986
|
+
appendSubagentLog(file, line, log7);
|
|
19063
17987
|
}
|
|
19064
17988
|
}
|
|
19065
17989
|
});
|
|
19066
17990
|
},
|
|
19067
17991
|
"tool.execute.after": async (input, _output) => {
|
|
19068
|
-
if (
|
|
17992
|
+
if (inflight2.size === 0)
|
|
19069
17993
|
return;
|
|
19070
|
-
await safeAsync(
|
|
17994
|
+
await safeAsync(PLUGIN_NAME11, "tool.execute.after", async () => {
|
|
19071
17995
|
if (!input || typeof input.sessionID !== "string" || typeof input.tool !== "string")
|
|
19072
17996
|
return;
|
|
19073
|
-
const rec =
|
|
17997
|
+
const rec = inflight2.get(input.sessionID);
|
|
19074
17998
|
if (!rec)
|
|
19075
17999
|
return;
|
|
19076
18000
|
let durationMs = 0;
|
|
@@ -19084,17 +18008,17 @@ var subtaskHeartbeatServer = async (ctx) => {
|
|
|
19084
18008
|
if (isLogPersistenceEnabled(cwd)) {
|
|
19085
18009
|
const line = buildAfterLogLine(input.tool, true, durationMs);
|
|
19086
18010
|
const file = subagentLogPath(cwd, rec.parentID, rec.childID);
|
|
19087
|
-
appendSubagentLog(file, line,
|
|
18011
|
+
appendSubagentLog(file, line, log7);
|
|
19088
18012
|
}
|
|
19089
18013
|
});
|
|
19090
18014
|
}
|
|
19091
18015
|
};
|
|
19092
18016
|
};
|
|
19093
|
-
var
|
|
18017
|
+
var handler11 = subtaskHeartbeatServer;
|
|
19094
18018
|
|
|
19095
18019
|
// plugins/parallel-status.ts
|
|
19096
|
-
var
|
|
19097
|
-
logLifecycle(
|
|
18020
|
+
var PLUGIN_NAME12 = "parallel-status";
|
|
18021
|
+
logLifecycle(PLUGIN_NAME12, "import");
|
|
19098
18022
|
var ID_MAX_LEN = 16;
|
|
19099
18023
|
var ID_KEEP_LEN = 13;
|
|
19100
18024
|
function shortId(s) {
|
|
@@ -19126,8 +18050,8 @@ function formatInflightMarkdown(snapshot, now = Date.now()) {
|
|
|
19126
18050
|
`);
|
|
19127
18051
|
}
|
|
19128
18052
|
var parallelStatusServer = async (ctx) => {
|
|
19129
|
-
const
|
|
19130
|
-
logLifecycle(
|
|
18053
|
+
const log8 = makePluginLogger(PLUGIN_NAME12);
|
|
18054
|
+
logLifecycle(PLUGIN_NAME12, "activate", { directory: ctx.directory });
|
|
19131
18055
|
return {
|
|
19132
18056
|
"command.execute.before": async (input, output) => {
|
|
19133
18057
|
try {
|
|
@@ -19146,23 +18070,23 @@ var parallelStatusServer = async (ctx) => {
|
|
|
19146
18070
|
synthetic: false
|
|
19147
18071
|
});
|
|
19148
18072
|
}
|
|
19149
|
-
|
|
18073
|
+
log8.info(`[${PLUGIN_NAME12}] 已回写 ${snapshot.length} 条 inflight`);
|
|
19150
18074
|
} catch (err) {
|
|
19151
|
-
|
|
18075
|
+
log8.error(`[${PLUGIN_NAME12}] command.execute.before 异常(已隔离)`, {
|
|
19152
18076
|
error: err instanceof Error ? err.message : String(err)
|
|
19153
18077
|
});
|
|
19154
18078
|
}
|
|
19155
18079
|
}
|
|
19156
18080
|
};
|
|
19157
18081
|
};
|
|
19158
|
-
var
|
|
18082
|
+
var handler12 = parallelStatusServer;
|
|
19159
18083
|
|
|
19160
18084
|
// plugins/parallel-tool-nudge.ts
|
|
19161
18085
|
import { readFileSync as readFileSync4, readdirSync as readdirSync2, statSync as statSync4 } from "node:fs";
|
|
19162
18086
|
import { join as join17 } from "node:path";
|
|
19163
18087
|
import { homedir as homedir6 } from "node:os";
|
|
19164
|
-
var
|
|
19165
|
-
logLifecycle(
|
|
18088
|
+
var PLUGIN_NAME13 = "parallel-tool-nudge";
|
|
18089
|
+
logLifecycle(PLUGIN_NAME13, "import", {});
|
|
19166
18090
|
var PARALLEL_SAFE_TOOLS = [
|
|
19167
18091
|
"read",
|
|
19168
18092
|
"smart_search",
|
|
@@ -19224,7 +18148,7 @@ function loadAgentToolsMap(rootDir, opts = {}) {
|
|
|
19224
18148
|
const result = new Map;
|
|
19225
18149
|
const safeSet = new Set(PARALLEL_SAFE_TOOLS);
|
|
19226
18150
|
const unionTools = new Set;
|
|
19227
|
-
const
|
|
18151
|
+
const log8 = makePluginLogger(PLUGIN_NAME13);
|
|
19228
18152
|
for (const dir of candidateDirs) {
|
|
19229
18153
|
if (!dirExists(dir))
|
|
19230
18154
|
continue;
|
|
@@ -19232,7 +18156,7 @@ function loadAgentToolsMap(rootDir, opts = {}) {
|
|
|
19232
18156
|
try {
|
|
19233
18157
|
entries = dirReader(dir);
|
|
19234
18158
|
} catch (err) {
|
|
19235
|
-
|
|
18159
|
+
log8.warn(`agents 目录读取失败(已跳过)`, {
|
|
19236
18160
|
dir,
|
|
19237
18161
|
error: err instanceof Error ? err.message : String(err)
|
|
19238
18162
|
});
|
|
@@ -19246,7 +18170,7 @@ function loadAgentToolsMap(rootDir, opts = {}) {
|
|
|
19246
18170
|
try {
|
|
19247
18171
|
content = reader(path21);
|
|
19248
18172
|
} catch (err) {
|
|
19249
|
-
|
|
18173
|
+
log8.warn(`agent.md 读取失败(已跳过)`, {
|
|
19250
18174
|
path: path21,
|
|
19251
18175
|
error: err instanceof Error ? err.message : String(err)
|
|
19252
18176
|
});
|
|
@@ -19254,7 +18178,7 @@ function loadAgentToolsMap(rootDir, opts = {}) {
|
|
|
19254
18178
|
}
|
|
19255
18179
|
const parsed = parseAgentFrontmatter(content);
|
|
19256
18180
|
if (!parsed) {
|
|
19257
|
-
|
|
18181
|
+
log8.warn(`agent frontmatter 解析失败(已跳过)`, { path: path21 });
|
|
19258
18182
|
continue;
|
|
19259
18183
|
}
|
|
19260
18184
|
if (result.has(parsed.name))
|
|
@@ -19332,7 +18256,7 @@ This directive is re-injected every turn — it is not optional advice.
|
|
|
19332
18256
|
return body.slice(0, NUDGE_MAX_LEN2 - 4) + `
|
|
19333
18257
|
…`;
|
|
19334
18258
|
}
|
|
19335
|
-
var
|
|
18259
|
+
var log8 = makePluginLogger(PLUGIN_NAME13);
|
|
19336
18260
|
var parallelToolNudgeServer = async (ctx) => {
|
|
19337
18261
|
try {
|
|
19338
18262
|
const loaded = loadAgentToolsMap(ctx.directory ?? process.cwd());
|
|
@@ -19340,19 +18264,19 @@ var parallelToolNudgeServer = async (ctx) => {
|
|
|
19340
18264
|
for (const [k, v] of loaded.entries())
|
|
19341
18265
|
agentToolsMap.set(k, v);
|
|
19342
18266
|
} catch (err) {
|
|
19343
|
-
|
|
18267
|
+
log8.warn(`loadAgentToolsMap 失败(plugin 将 no-op)`, {
|
|
19344
18268
|
error: err instanceof Error ? err.message : String(err)
|
|
19345
18269
|
});
|
|
19346
18270
|
}
|
|
19347
18271
|
const realAgentCount = Math.max(0, agentToolsMap.size - (agentToolsMap.has(DEFAULT_AGENT_KEY) ? 1 : 0));
|
|
19348
18272
|
if (realAgentCount === 0) {
|
|
19349
18273
|
agentToolsMap.clear();
|
|
19350
|
-
|
|
18274
|
+
log8.warn(`0 real agents loaded; plugin will be no-op for this session`, {
|
|
19351
18275
|
directory: ctx.directory
|
|
19352
18276
|
});
|
|
19353
18277
|
}
|
|
19354
18278
|
const defaultTools = agentToolsMap.get(DEFAULT_AGENT_KEY) ?? [];
|
|
19355
|
-
logLifecycle(
|
|
18279
|
+
logLifecycle(PLUGIN_NAME13, "activate", {
|
|
19356
18280
|
directory: ctx.directory,
|
|
19357
18281
|
real_agents_loaded: realAgentCount,
|
|
19358
18282
|
default_tools_union: defaultTools,
|
|
@@ -19363,7 +18287,7 @@ var parallelToolNudgeServer = async (ctx) => {
|
|
|
19363
18287
|
});
|
|
19364
18288
|
return {
|
|
19365
18289
|
"chat.params": async (input, _output) => {
|
|
19366
|
-
await safeAsync(
|
|
18290
|
+
await safeAsync(PLUGIN_NAME13, "chat.params", async () => {
|
|
19367
18291
|
if (agentToolsMap.size === 0)
|
|
19368
18292
|
return;
|
|
19369
18293
|
const sid = input?.sessionID;
|
|
@@ -19375,7 +18299,7 @@ var parallelToolNudgeServer = async (ctx) => {
|
|
|
19375
18299
|
});
|
|
19376
18300
|
},
|
|
19377
18301
|
"experimental.chat.system.transform": async (input, output) => {
|
|
19378
|
-
await safeAsync(
|
|
18302
|
+
await safeAsync(PLUGIN_NAME13, "experimental.chat.system.transform", async () => {
|
|
19379
18303
|
if (agentToolsMap.size === 0)
|
|
19380
18304
|
return;
|
|
19381
18305
|
if (!output || !Array.isArray(output.system))
|
|
@@ -19385,7 +18309,7 @@ var parallelToolNudgeServer = async (ctx) => {
|
|
|
19385
18309
|
const tools = agent !== "unknown" ? agentToolsMap.get(agent) ?? agentToolsMap.get(DEFAULT_AGENT_KEY) ?? [] : agentToolsMap.get(DEFAULT_AGENT_KEY) ?? [];
|
|
19386
18310
|
const nudge = renderNudge(agent, tools);
|
|
19387
18311
|
output.system.push(nudge);
|
|
19388
|
-
safeWriteLog(
|
|
18312
|
+
safeWriteLog(PLUGIN_NAME13, {
|
|
19389
18313
|
hook: "experimental.chat.system.transform",
|
|
19390
18314
|
sessionID: sid,
|
|
19391
18315
|
agent,
|
|
@@ -19397,11 +18321,11 @@ var parallelToolNudgeServer = async (ctx) => {
|
|
|
19397
18321
|
}
|
|
19398
18322
|
};
|
|
19399
18323
|
};
|
|
19400
|
-
var
|
|
18324
|
+
var handler13 = parallelToolNudgeServer;
|
|
19401
18325
|
|
|
19402
18326
|
// plugins/pwsh-utf8.ts
|
|
19403
|
-
var
|
|
19404
|
-
logLifecycle(
|
|
18327
|
+
var PLUGIN_NAME14 = "pwsh-utf8";
|
|
18328
|
+
logLifecycle(PLUGIN_NAME14, "import", {});
|
|
19405
18329
|
var PRELUDE = "chcp 65001 *> $null; " + "[Console]::OutputEncoding = [System.Text.UTF8Encoding]::new(); " + "$OutputEncoding = [System.Text.UTF8Encoding]::new(); ";
|
|
19406
18330
|
function prependUtf8Prelude(command) {
|
|
19407
18331
|
if (typeof command !== "string")
|
|
@@ -19414,15 +18338,15 @@ function prependUtf8Prelude(command) {
|
|
|
19414
18338
|
return command;
|
|
19415
18339
|
return PRELUDE + command;
|
|
19416
18340
|
}
|
|
19417
|
-
var
|
|
18341
|
+
var handler14 = async (_ctx3) => {
|
|
19418
18342
|
const enabled = process.platform === "win32" && process.env.CODEFORGE_DISABLE_PWSH_UTF8 !== "1";
|
|
19419
18343
|
const reason = enabled ? "win32" : process.platform !== "win32" ? "non-win32" : "disabled-by-env";
|
|
19420
|
-
logLifecycle(
|
|
18344
|
+
logLifecycle(PLUGIN_NAME14, "activate", { enabled, platform: process.platform, reason });
|
|
19421
18345
|
if (!enabled)
|
|
19422
18346
|
return {};
|
|
19423
18347
|
return {
|
|
19424
18348
|
"tool.execute.before": async (input, output) => {
|
|
19425
|
-
await safeAsync(
|
|
18349
|
+
await safeAsync(PLUGIN_NAME14, "tool.execute.before", async () => {
|
|
19426
18350
|
if (input.tool !== "bash")
|
|
19427
18351
|
return;
|
|
19428
18352
|
const args = output.args ?? {};
|
|
@@ -19431,7 +18355,7 @@ var handler17 = async (_ctx3) => {
|
|
|
19431
18355
|
if (next !== undefined && next !== original) {
|
|
19432
18356
|
args["command"] = next;
|
|
19433
18357
|
output.args = args;
|
|
19434
|
-
safeWriteLog(
|
|
18358
|
+
safeWriteLog(PLUGIN_NAME14, {
|
|
19435
18359
|
hook: "tool.execute.before",
|
|
19436
18360
|
tool: input.tool,
|
|
19437
18361
|
callID: input.callID,
|
|
@@ -19587,7 +18511,7 @@ function buildRecoveryPlan(events, meta = { id: null }, opts = {}) {
|
|
|
19587
18511
|
for (let i = events.length - 1;i >= 0; i--) {
|
|
19588
18512
|
const e = events[i];
|
|
19589
18513
|
if (e.type === "message" && e.role === "user") {
|
|
19590
|
-
lastUser =
|
|
18514
|
+
lastUser = clip4(e.content, o.excerptLimit);
|
|
19591
18515
|
break;
|
|
19592
18516
|
}
|
|
19593
18517
|
}
|
|
@@ -19605,7 +18529,7 @@ function buildRecoveryPlan(events, meta = { id: null }, opts = {}) {
|
|
|
19605
18529
|
const toolMap = new Map;
|
|
19606
18530
|
for (const t of window) {
|
|
19607
18531
|
const cur = toolMap.get(t.tool);
|
|
19608
|
-
const argsExcerpt =
|
|
18532
|
+
const argsExcerpt = clip4(safeJson(t.args), o.excerptLimit);
|
|
19609
18533
|
if (cur) {
|
|
19610
18534
|
cur.count++;
|
|
19611
18535
|
cur.last_ok = t.ok;
|
|
@@ -19621,7 +18545,7 @@ function buildRecoveryPlan(events, meta = { id: null }, opts = {}) {
|
|
|
19621
18545
|
});
|
|
19622
18546
|
}
|
|
19623
18547
|
}
|
|
19624
|
-
const
|
|
18548
|
+
const inflight3 = [...toolMap.values()].sort((a, b) => b.last_ts - a.last_ts);
|
|
19625
18549
|
const proposed = window.some((t) => PENDING_CHANGES_TOOLS.has(t.tool));
|
|
19626
18550
|
const applied = window.some((t) => APPLY_TOOLS.has(t.tool) && t.ok);
|
|
19627
18551
|
const pending_changes_likely = proposed && !applied;
|
|
@@ -19645,7 +18569,7 @@ function buildRecoveryPlan(events, meta = { id: null }, opts = {}) {
|
|
|
19645
18569
|
idleMs,
|
|
19646
18570
|
lastUser,
|
|
19647
18571
|
lastAgent,
|
|
19648
|
-
inflight:
|
|
18572
|
+
inflight: inflight3,
|
|
19649
18573
|
pending_changes_likely,
|
|
19650
18574
|
open_subtasks_likely,
|
|
19651
18575
|
reason
|
|
@@ -19656,7 +18580,7 @@ function buildRecoveryPlan(events, meta = { id: null }, opts = {}) {
|
|
|
19656
18580
|
idle_ms: idleMs,
|
|
19657
18581
|
last_user_intent: lastUser,
|
|
19658
18582
|
last_agent: lastAgent,
|
|
19659
|
-
inflight_tools:
|
|
18583
|
+
inflight_tools: inflight3,
|
|
19660
18584
|
pending_changes_likely,
|
|
19661
18585
|
open_subtasks_likely,
|
|
19662
18586
|
summary
|
|
@@ -19694,7 +18618,7 @@ function formatIdle(ms) {
|
|
|
19694
18618
|
return `${Math.round(ms / 3600000)}h`;
|
|
19695
18619
|
return `${Math.round(ms / 86400000)}d`;
|
|
19696
18620
|
}
|
|
19697
|
-
function
|
|
18621
|
+
function clip4(s, max) {
|
|
19698
18622
|
if (!s)
|
|
19699
18623
|
return "";
|
|
19700
18624
|
if (s.length <= max)
|
|
@@ -19847,8 +18771,8 @@ async function markBlocksConsumed(absRoot, entries) {
|
|
|
19847
18771
|
}
|
|
19848
18772
|
|
|
19849
18773
|
// plugins/session-recovery.ts
|
|
19850
|
-
var
|
|
19851
|
-
logLifecycle(
|
|
18774
|
+
var PLUGIN_NAME15 = "session-recovery";
|
|
18775
|
+
logLifecycle(PLUGIN_NAME15, "import", {});
|
|
19852
18776
|
async function processSessionStart(currentSessionId, opts = {}) {
|
|
19853
18777
|
if (opts.disabled) {
|
|
19854
18778
|
return { ok: true, injected: false, reason: "disabled" };
|
|
@@ -19858,7 +18782,7 @@ async function processSessionStart(currentSessionId, opts = {}) {
|
|
|
19858
18782
|
excludeIds.add(currentSessionId);
|
|
19859
18783
|
const r = await scanLastSession({ ...opts, excludeIds: [...excludeIds] });
|
|
19860
18784
|
if (!r.ok) {
|
|
19861
|
-
opts.log?.warn?.(`[${
|
|
18785
|
+
opts.log?.warn?.(`[${PLUGIN_NAME15}] 扫描失败:${r.error}`);
|
|
19862
18786
|
return { ok: false, injected: false, reason: "scan_error", error: r.error };
|
|
19863
18787
|
}
|
|
19864
18788
|
const plan = r.plan;
|
|
@@ -19867,7 +18791,7 @@ async function processSessionStart(currentSessionId, opts = {}) {
|
|
|
19867
18791
|
try {
|
|
19868
18792
|
pendingBlocks = await scanBlockPending(opts.root);
|
|
19869
18793
|
} catch (err) {
|
|
19870
|
-
opts.log?.warn?.(`[${
|
|
18794
|
+
opts.log?.warn?.(`[${PLUGIN_NAME15}] scanBlockPending 异常:${err instanceof Error ? err.message : String(err)}`);
|
|
19871
18795
|
}
|
|
19872
18796
|
}
|
|
19873
18797
|
const hasPendingBlocks = pendingBlocks.length > 0;
|
|
@@ -19885,7 +18809,7 @@ async function processSessionStart(currentSessionId, opts = {}) {
|
|
|
19885
18809
|
await opts.injectRecovery(injection);
|
|
19886
18810
|
} catch (err) {
|
|
19887
18811
|
const msg = err instanceof Error ? err.message : String(err);
|
|
19888
|
-
opts.log?.warn?.(`[${
|
|
18812
|
+
opts.log?.warn?.(`[${PLUGIN_NAME15}] injectRecovery 异常:${msg}`);
|
|
19889
18813
|
return { ok: false, injected: false, plan, reason: "inject_error", error: msg, pendingBlocks };
|
|
19890
18814
|
}
|
|
19891
18815
|
}
|
|
@@ -19893,7 +18817,7 @@ async function processSessionStart(currentSessionId, opts = {}) {
|
|
|
19893
18817
|
try {
|
|
19894
18818
|
await markBlocksConsumed(opts.root, pendingBlocks);
|
|
19895
18819
|
} catch (err) {
|
|
19896
|
-
opts.log?.warn?.(`[${
|
|
18820
|
+
opts.log?.warn?.(`[${PLUGIN_NAME15}] markBlocksConsumed 异常:${err instanceof Error ? err.message : String(err)}`);
|
|
19897
18821
|
}
|
|
19898
18822
|
}
|
|
19899
18823
|
return { ok: true, injected: true, plan, reason: "ok", pendingBlocks };
|
|
@@ -19940,13 +18864,13 @@ function renderPrompt(plan) {
|
|
|
19940
18864
|
return lines.join(`
|
|
19941
18865
|
`);
|
|
19942
18866
|
}
|
|
19943
|
-
var
|
|
18867
|
+
var log9 = makePluginLogger(PLUGIN_NAME15);
|
|
19944
18868
|
var _lastInjection = null;
|
|
19945
18869
|
var sessionRecoveryServer = async (ctx) => {
|
|
19946
|
-
logLifecycle(
|
|
18870
|
+
logLifecycle(PLUGIN_NAME15, "activate", { directory: ctx.directory });
|
|
19947
18871
|
return {
|
|
19948
18872
|
event: async ({ event }) => {
|
|
19949
|
-
await safeAsync(
|
|
18873
|
+
await safeAsync(PLUGIN_NAME15, "event", async () => {
|
|
19950
18874
|
const e = event;
|
|
19951
18875
|
if (!e || typeof e.type !== "string")
|
|
19952
18876
|
return;
|
|
@@ -19956,12 +18880,12 @@ var sessionRecoveryServer = async (ctx) => {
|
|
|
19956
18880
|
const root = typeof e.properties?.["root"] === "string" ? e.properties["root"] : ctx.directory ?? process.cwd();
|
|
19957
18881
|
const r = await processSessionStart(sid, {
|
|
19958
18882
|
root,
|
|
19959
|
-
log:
|
|
18883
|
+
log: log9,
|
|
19960
18884
|
injectRecovery: (inj) => {
|
|
19961
18885
|
_lastInjection = inj;
|
|
19962
18886
|
}
|
|
19963
18887
|
});
|
|
19964
|
-
safeWriteLog(
|
|
18888
|
+
safeWriteLog(PLUGIN_NAME15, {
|
|
19965
18889
|
hook: "event",
|
|
19966
18890
|
type: "session.start",
|
|
19967
18891
|
ok: r.ok,
|
|
@@ -19971,13 +18895,13 @@ var sessionRecoveryServer = async (ctx) => {
|
|
|
19971
18895
|
pending_blocks_count: r.pendingBlocks?.length ?? 0
|
|
19972
18896
|
});
|
|
19973
18897
|
if (r.injected && r.plan) {
|
|
19974
|
-
|
|
18898
|
+
log9.info(`[${PLUGIN_NAME15}] 注入恢复提示(last=${r.plan.last_session_id?.slice(0, 8) ?? "?"}, reason=${r.plan.reason}, pending_blocks=${r.pendingBlocks?.length ?? 0})`);
|
|
19975
18899
|
}
|
|
19976
18900
|
});
|
|
19977
18901
|
}
|
|
19978
18902
|
};
|
|
19979
18903
|
};
|
|
19980
|
-
var
|
|
18904
|
+
var handler15 = sessionRecoveryServer;
|
|
19981
18905
|
|
|
19982
18906
|
// plugins/subtasks.ts
|
|
19983
18907
|
import { promises as fs18 } from "node:fs";
|
|
@@ -19995,7 +18919,7 @@ function resolveMergeFns(deps) {
|
|
|
19995
18919
|
worktreeHasChangesFn: deps.worktreeHasChanges ?? worktreeHasChanges
|
|
19996
18920
|
};
|
|
19997
18921
|
}
|
|
19998
|
-
async function mergeOneAttempt(r, opts, mergeFns,
|
|
18922
|
+
async function mergeOneAttempt(r, opts, mergeFns, log10) {
|
|
19999
18923
|
const t0 = Date.now();
|
|
20000
18924
|
const root = opts.mergeRoot;
|
|
20001
18925
|
const {
|
|
@@ -20077,7 +19001,7 @@ async function mergeOneAttempt(r, opts, mergeFns, log11) {
|
|
|
20077
19001
|
await mergeAbortFn({ root });
|
|
20078
19002
|
} catch (abortErr) {
|
|
20079
19003
|
abortFailed = true;
|
|
20080
|
-
|
|
19004
|
+
log10("error", `[parallel-merge] mergeAbort 失败(仓库可能锁死)`, {
|
|
20081
19005
|
id: r.id,
|
|
20082
19006
|
error: describe6(abortErr)
|
|
20083
19007
|
});
|
|
@@ -20093,13 +19017,13 @@ async function mergeOneAttempt(r, opts, mergeFns, log11) {
|
|
|
20093
19017
|
}
|
|
20094
19018
|
attempt.ok = true;
|
|
20095
19019
|
attempt.durationMs = Date.now() - t0;
|
|
20096
|
-
await safeRemoveWorktree(removeWorktreeFn, root, wt,
|
|
19020
|
+
await safeRemoveWorktree(removeWorktreeFn, root, wt, log10, r.id);
|
|
20097
19021
|
return { attempt };
|
|
20098
19022
|
} else {
|
|
20099
19023
|
attempt.ok = true;
|
|
20100
19024
|
attempt.skippedReason = "no_changes";
|
|
20101
19025
|
attempt.durationMs = Date.now() - t0;
|
|
20102
|
-
await safeRemoveWorktree(removeWorktreeFn, root, wt,
|
|
19026
|
+
await safeRemoveWorktree(removeWorktreeFn, root, wt, log10, r.id);
|
|
20103
19027
|
return { attempt };
|
|
20104
19028
|
}
|
|
20105
19029
|
} catch (err) {
|
|
@@ -20113,7 +19037,7 @@ async function mergeOneAttempt(r, opts, mergeFns, log11) {
|
|
|
20113
19037
|
}
|
|
20114
19038
|
}
|
|
20115
19039
|
async function mergeWorktrees(results, opts, deps) {
|
|
20116
|
-
const
|
|
19040
|
+
const log10 = deps.log ?? (() => {});
|
|
20117
19041
|
const root = opts.mergeRoot;
|
|
20118
19042
|
const mergeFns = resolveMergeFns(deps);
|
|
20119
19043
|
const report = {
|
|
@@ -20155,10 +19079,10 @@ async function mergeWorktrees(results, opts, deps) {
|
|
|
20155
19079
|
};
|
|
20156
19080
|
report.attempts.push(attempt);
|
|
20157
19081
|
report.skipped++;
|
|
20158
|
-
await fireMergeAttempt(opts.onMergeAttempt, attempt, idx++,
|
|
19082
|
+
await fireMergeAttempt(opts.onMergeAttempt, attempt, idx++, log10);
|
|
20159
19083
|
}
|
|
20160
19084
|
for (const r of mergeable) {
|
|
20161
|
-
const { attempt, pendingItem } = await mergeOneAttempt(r, { mergeRoot: root }, mergeFns,
|
|
19085
|
+
const { attempt, pendingItem } = await mergeOneAttempt(r, { mergeRoot: root }, mergeFns, log10);
|
|
20162
19086
|
report.attempts.push(attempt);
|
|
20163
19087
|
if (attempt.ok && !attempt.skippedReason)
|
|
20164
19088
|
report.merged++;
|
|
@@ -20168,12 +19092,12 @@ async function mergeWorktrees(results, opts, deps) {
|
|
|
20168
19092
|
report.conflicted++;
|
|
20169
19093
|
if (pendingItem)
|
|
20170
19094
|
report.pendingWorktrees.push(pendingItem);
|
|
20171
|
-
await fireMergeAttempt(opts.onMergeAttempt, attempt, idx++,
|
|
19095
|
+
await fireMergeAttempt(opts.onMergeAttempt, attempt, idx++, log10);
|
|
20172
19096
|
}
|
|
20173
19097
|
return report;
|
|
20174
19098
|
}
|
|
20175
19099
|
function createMergeQueue(opts, deps = {}) {
|
|
20176
|
-
const
|
|
19100
|
+
const log10 = deps.log ?? (() => {});
|
|
20177
19101
|
const mergeFns = {
|
|
20178
19102
|
commitWorktreeIfDirtyFn: deps.commitWorktreeIfDirtyFn ?? commitWorktreeIfDirty,
|
|
20179
19103
|
tryMergeFn: deps.tryMergeFn ?? tryMerge,
|
|
@@ -20214,10 +19138,10 @@ function createMergeQueue(opts, deps = {}) {
|
|
|
20214
19138
|
conflicts: []
|
|
20215
19139
|
});
|
|
20216
19140
|
}
|
|
20217
|
-
await fireMergeAttempt(opts.onMergeAttempt, attempt2, idx++,
|
|
19141
|
+
await fireMergeAttempt(opts.onMergeAttempt, attempt2, idx++, log10);
|
|
20218
19142
|
return;
|
|
20219
19143
|
}
|
|
20220
|
-
const { attempt, abortFailed, pendingItem } = await mergeOneAttempt(result, { mergeRoot: opts.mergeRoot, summaryCharLimit: opts.summaryCharLimit }, mergeFns,
|
|
19144
|
+
const { attempt, abortFailed, pendingItem } = await mergeOneAttempt(result, { mergeRoot: opts.mergeRoot, summaryCharLimit: opts.summaryCharLimit }, mergeFns, log10);
|
|
20221
19145
|
report.attempts.push(attempt);
|
|
20222
19146
|
if (attempt.ok && !attempt.skippedReason)
|
|
20223
19147
|
report.merged++;
|
|
@@ -20229,11 +19153,11 @@ function createMergeQueue(opts, deps = {}) {
|
|
|
20229
19153
|
report.pendingWorktrees.push(pendingItem);
|
|
20230
19154
|
if (abortFailed) {
|
|
20231
19155
|
queueAborted = true;
|
|
20232
|
-
|
|
19156
|
+
log10("error", `[parallel-merge] queue aborted (mergeAbort failed)`, {
|
|
20233
19157
|
id: result.id
|
|
20234
19158
|
});
|
|
20235
19159
|
}
|
|
20236
|
-
await fireMergeAttempt(opts.onMergeAttempt, attempt, idx++,
|
|
19160
|
+
await fireMergeAttempt(opts.onMergeAttempt, attempt, idx++, log10);
|
|
20237
19161
|
});
|
|
20238
19162
|
},
|
|
20239
19163
|
async flushAndReport() {
|
|
@@ -20242,23 +19166,23 @@ function createMergeQueue(opts, deps = {}) {
|
|
|
20242
19166
|
}
|
|
20243
19167
|
};
|
|
20244
19168
|
}
|
|
20245
|
-
async function safeRemoveWorktree(fn, root, wt,
|
|
19169
|
+
async function safeRemoveWorktree(fn, root, wt, log10, subtaskId) {
|
|
20246
19170
|
try {
|
|
20247
19171
|
await fn({ root, worktree_path: wt, force: true });
|
|
20248
19172
|
} catch (err) {
|
|
20249
|
-
|
|
19173
|
+
log10("warn", `[parallel] removeWorktree 失败 ${subtaskId}`, {
|
|
20250
19174
|
error: describe6(err),
|
|
20251
19175
|
worktree: wt
|
|
20252
19176
|
});
|
|
20253
19177
|
}
|
|
20254
19178
|
}
|
|
20255
|
-
async function fireMergeAttempt(cb, attempt, idx,
|
|
19179
|
+
async function fireMergeAttempt(cb, attempt, idx, log10) {
|
|
20256
19180
|
if (!cb)
|
|
20257
19181
|
return;
|
|
20258
19182
|
try {
|
|
20259
19183
|
await cb(attempt, idx);
|
|
20260
19184
|
} catch (err) {
|
|
20261
|
-
|
|
19185
|
+
log10("warn", `[parallel] onMergeAttempt 抛错(已隔离)`, {
|
|
20262
19186
|
id: attempt.subtaskId,
|
|
20263
19187
|
error: describe6(err)
|
|
20264
19188
|
});
|
|
@@ -20284,7 +19208,7 @@ async function schedule(opts) {
|
|
|
20284
19208
|
}
|
|
20285
19209
|
const now = opts.deps.now ?? Date.now;
|
|
20286
19210
|
const start = now();
|
|
20287
|
-
const
|
|
19211
|
+
const log10 = opts.deps.log ?? (() => {});
|
|
20288
19212
|
const concurrency = Math.max(1, opts.maxConcurrency ?? 4);
|
|
20289
19213
|
const totalTimeout = opts.totalTimeout_ms ?? 30 * 60000;
|
|
20290
19214
|
const limit = Math.max(1, opts.summaryCharLimit ?? 500);
|
|
@@ -20317,7 +19241,7 @@ async function schedule(opts) {
|
|
|
20317
19241
|
mergeAbortFn: opts.deps.mergeAbort,
|
|
20318
19242
|
removeWorktreeFn: opts.deps.removeWorktree,
|
|
20319
19243
|
worktreeHasChangesFn: opts.deps.worktreeHasChanges,
|
|
20320
|
-
log:
|
|
19244
|
+
log: log10
|
|
20321
19245
|
});
|
|
20322
19246
|
}
|
|
20323
19247
|
const fireFinish = async (i, res) => {
|
|
@@ -20325,7 +19249,7 @@ async function schedule(opts) {
|
|
|
20325
19249
|
try {
|
|
20326
19250
|
queue.enqueue(res);
|
|
20327
19251
|
} catch (err) {
|
|
20328
|
-
|
|
19252
|
+
log10("warn", `[parallel] queue.enqueue 抛错(已隔离)`, {
|
|
20329
19253
|
id: res.id,
|
|
20330
19254
|
error: describe7(err)
|
|
20331
19255
|
});
|
|
@@ -20336,7 +19260,7 @@ async function schedule(opts) {
|
|
|
20336
19260
|
try {
|
|
20337
19261
|
await opts.onSubtaskFinish(res, i);
|
|
20338
19262
|
} catch (err) {
|
|
20339
|
-
|
|
19263
|
+
log10("warn", `[parallel] onSubtaskFinish 抛错(已隔离)`, {
|
|
20340
19264
|
id: res.id,
|
|
20341
19265
|
error: describe7(err)
|
|
20342
19266
|
});
|
|
@@ -20366,7 +19290,7 @@ async function schedule(opts) {
|
|
|
20366
19290
|
try {
|
|
20367
19291
|
await opts.onSubtaskStart(spec, i);
|
|
20368
19292
|
} catch (err) {
|
|
20369
|
-
|
|
19293
|
+
log10("warn", `[parallel] onSubtaskStart 抛错(已隔离)`, {
|
|
20370
19294
|
id: spec.id,
|
|
20371
19295
|
error: describe7(err)
|
|
20372
19296
|
});
|
|
@@ -20417,7 +19341,7 @@ async function schedule(opts) {
|
|
|
20417
19341
|
try {
|
|
20418
19342
|
await alloc.cleanup();
|
|
20419
19343
|
} catch (err) {
|
|
20420
|
-
|
|
19344
|
+
log10("warn", `[parallel] worktree 清理失败 ${spec.id}`, {
|
|
20421
19345
|
error: describe7(err)
|
|
20422
19346
|
});
|
|
20423
19347
|
}
|
|
@@ -20597,20 +19521,20 @@ function buildSystemPrompt(maxSubtasks) {
|
|
|
20597
19521
|
`);
|
|
20598
19522
|
}
|
|
20599
19523
|
async function decomposeTask(description30, opts) {
|
|
20600
|
-
const
|
|
19524
|
+
const log10 = opts.log ?? (() => {});
|
|
20601
19525
|
if (opts.mockResponse) {
|
|
20602
|
-
return validateAndFinalize(opts.mockResponse, undefined,
|
|
19526
|
+
return validateAndFinalize(opts.mockResponse, undefined, log10, opts.maxSubtasks ?? DEFAULT_MAX_SUBTASKS);
|
|
20603
19527
|
}
|
|
20604
19528
|
const maxSubtasks = opts.maxSubtasks ?? DEFAULT_MAX_SUBTASKS;
|
|
20605
19529
|
const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS2;
|
|
20606
19530
|
let childSessionId;
|
|
20607
19531
|
try {
|
|
20608
19532
|
const created = await opts.client.session.create({
|
|
20609
|
-
body: { title: `decompose:${
|
|
19533
|
+
body: { title: `decompose:${clip5(description30, 60)}` },
|
|
20610
19534
|
query: opts.directory ? { directory: opts.directory } : undefined
|
|
20611
19535
|
});
|
|
20612
19536
|
if (created.error || !created.data?.id) {
|
|
20613
|
-
|
|
19537
|
+
log10("warn", "[decompose] session.create 失败", { error: describe8(created.error) });
|
|
20614
19538
|
return {
|
|
20615
19539
|
ok: false,
|
|
20616
19540
|
subtasks: [],
|
|
@@ -20632,22 +19556,22 @@ async function decomposeTask(description30, opts) {
|
|
|
20632
19556
|
sleep2(timeoutMs).then(() => ({ kind: "timeout" }))
|
|
20633
19557
|
]);
|
|
20634
19558
|
if (raced.kind === "timeout") {
|
|
20635
|
-
|
|
19559
|
+
log10("warn", "[decompose] LLM 调用超时", { timeoutMs });
|
|
20636
19560
|
return { ok: false, subtasks: [], reason: "llm_unavailable" };
|
|
20637
19561
|
}
|
|
20638
19562
|
const r = raced.value;
|
|
20639
19563
|
if (r.error || !r.data) {
|
|
20640
|
-
|
|
19564
|
+
log10("warn", "[decompose] session.prompt 返回错误", { error: describe8(r.error) });
|
|
20641
19565
|
return { ok: false, subtasks: [], reason: "llm_unavailable" };
|
|
20642
19566
|
}
|
|
20643
19567
|
const rawText = pickLastText2(r.data.parts ?? []);
|
|
20644
19568
|
if (!rawText) {
|
|
20645
|
-
|
|
19569
|
+
log10("warn", "[decompose] LLM 输出为空");
|
|
20646
19570
|
return { ok: false, subtasks: [], reason: "parse_failed", raw: "" };
|
|
20647
19571
|
}
|
|
20648
19572
|
const parsed = extractJson(rawText);
|
|
20649
19573
|
if (!parsed) {
|
|
20650
|
-
|
|
19574
|
+
log10("warn", "[decompose] JSON 解析失败", { raw: clip5(rawText, 200) });
|
|
20651
19575
|
return { ok: false, subtasks: [], reason: "parse_failed", raw: rawText };
|
|
20652
19576
|
}
|
|
20653
19577
|
if (parsed && typeof parsed === "object" && parsed.single_task === true) {
|
|
@@ -20655,7 +19579,7 @@ async function decomposeTask(description30, opts) {
|
|
|
20655
19579
|
}
|
|
20656
19580
|
const subtasksRaw = parsed.subtasks;
|
|
20657
19581
|
if (!Array.isArray(subtasksRaw)) {
|
|
20658
|
-
|
|
19582
|
+
log10("warn", "[decompose] JSON 缺 subtasks 数组");
|
|
20659
19583
|
return { ok: false, subtasks: [], reason: "parse_failed", raw: rawText };
|
|
20660
19584
|
}
|
|
20661
19585
|
const normalized = [];
|
|
@@ -20671,12 +19595,12 @@ async function decomposeTask(description30, opts) {
|
|
|
20671
19595
|
normalized.push({ description: desc, hintFiles });
|
|
20672
19596
|
}
|
|
20673
19597
|
if (normalized.length > maxSubtasks) {
|
|
20674
|
-
|
|
19598
|
+
log10("warn", "[decompose] LLM 返回子任务超过上限", { count: normalized.length, maxSubtasks });
|
|
20675
19599
|
return { ok: false, subtasks: [], reason: "parse_failed", raw: rawText };
|
|
20676
19600
|
}
|
|
20677
|
-
return validateAndFinalize(normalized, rawText,
|
|
19601
|
+
return validateAndFinalize(normalized, rawText, log10, maxSubtasks);
|
|
20678
19602
|
} catch (err) {
|
|
20679
|
-
|
|
19603
|
+
log10("warn", "[decompose] 抛错", { error: describe8(err) });
|
|
20680
19604
|
return { ok: false, subtasks: [], reason: "llm_unavailable" };
|
|
20681
19605
|
} finally {
|
|
20682
19606
|
if (childSessionId) {
|
|
@@ -20686,12 +19610,12 @@ async function decomposeTask(description30, opts) {
|
|
|
20686
19610
|
query: opts.directory ? { directory: opts.directory } : undefined
|
|
20687
19611
|
});
|
|
20688
19612
|
} catch (err) {
|
|
20689
|
-
|
|
19613
|
+
log10("warn", "[decompose] session.delete 失败", { error: describe8(err) });
|
|
20690
19614
|
}
|
|
20691
19615
|
}
|
|
20692
19616
|
}
|
|
20693
19617
|
}
|
|
20694
|
-
function validateAndFinalize(subtasks, raw,
|
|
19618
|
+
function validateAndFinalize(subtasks, raw, log10, maxSubtasks) {
|
|
20695
19619
|
if (subtasks.length < 2) {
|
|
20696
19620
|
return { ok: false, subtasks: [], reason: "single_task", raw };
|
|
20697
19621
|
}
|
|
@@ -20701,7 +19625,7 @@ function validateAndFinalize(subtasks, raw, log11, maxSubtasks) {
|
|
|
20701
19625
|
}
|
|
20702
19626
|
}
|
|
20703
19627
|
if (subtasks.length > maxSubtasks) {
|
|
20704
|
-
|
|
19628
|
+
log10("warn", "[decompose] LLM 返回子任务超过上限", { count: subtasks.length, maxSubtasks });
|
|
20705
19629
|
return { ok: false, subtasks: [], reason: "parse_failed", raw };
|
|
20706
19630
|
}
|
|
20707
19631
|
for (let i = 0;i < subtasks.length; i++) {
|
|
@@ -20715,7 +19639,7 @@ function validateAndFinalize(subtasks, raw, log11, maxSubtasks) {
|
|
|
20715
19639
|
continue;
|
|
20716
19640
|
for (const f of b) {
|
|
20717
19641
|
if (setA.has(f.trim())) {
|
|
20718
|
-
|
|
19642
|
+
log10("info", "[decompose] hintFiles 有交集,降级 single_task", {
|
|
20719
19643
|
a: subtasks[i].description,
|
|
20720
19644
|
b: subtasks[j].description,
|
|
20721
19645
|
file: f
|
|
@@ -20780,7 +19704,7 @@ function tryParse(s) {
|
|
|
20780
19704
|
return null;
|
|
20781
19705
|
}
|
|
20782
19706
|
}
|
|
20783
|
-
function
|
|
19707
|
+
function clip5(s, n) {
|
|
20784
19708
|
if (!s)
|
|
20785
19709
|
return "";
|
|
20786
19710
|
return s.length <= n ? s : s.slice(0, n - 1) + "…";
|
|
@@ -20804,7 +19728,7 @@ function sleep2(ms) {
|
|
|
20804
19728
|
|
|
20805
19729
|
// plugins/subtasks.ts
|
|
20806
19730
|
init_runtime_paths();
|
|
20807
|
-
var
|
|
19731
|
+
var PLUGIN_NAME16 = "subtasks";
|
|
20808
19732
|
function getLogFile(root = process.cwd()) {
|
|
20809
19733
|
return path23.join(runtimeDir(root), "logs", "subtasks.log");
|
|
20810
19734
|
}
|
|
@@ -20870,7 +19794,7 @@ var mockRunner = async (spec) => {
|
|
|
20870
19794
|
};
|
|
20871
19795
|
async function handleParallelCommand(raw) {
|
|
20872
19796
|
const ctx = raw ?? {};
|
|
20873
|
-
const
|
|
19797
|
+
const log10 = ctx.log;
|
|
20874
19798
|
const args = ctx.args ?? {};
|
|
20875
19799
|
const parentId = (args.parentId ?? `task-${Date.now().toString(36)}`).replace(/[^a-zA-Z0-9._-]/g, "-");
|
|
20876
19800
|
const rawDescription = typeof args.description === "string" ? args.description.trim() : "";
|
|
@@ -20884,7 +19808,7 @@ async function handleParallelCommand(raw) {
|
|
|
20884
19808
|
specs = splitDescriptions(args.description, { parentId });
|
|
20885
19809
|
}
|
|
20886
19810
|
if (specs.length === 0) {
|
|
20887
|
-
|
|
19811
|
+
log10?.warn(`[${PLUGIN_NAME16}] /parallel 缺有效子任务`);
|
|
20888
19812
|
await safeReply2(ctx, "⚠ /parallel 需要至少 1 个子任务(用 ; 或换行分隔)");
|
|
20889
19813
|
return { ok: false, reason: "no_subtasks" };
|
|
20890
19814
|
}
|
|
@@ -20910,7 +19834,7 @@ async function handleParallelCommand(raw) {
|
|
|
20910
19834
|
if (!gitOk) {
|
|
20911
19835
|
autoMerge = false;
|
|
20912
19836
|
downgradedAutoMerge = true;
|
|
20913
|
-
|
|
19837
|
+
log10?.warn("[subtasks] mergeRoot 非 git 仓库,降级 autoMerge=false");
|
|
20914
19838
|
const canNoticeEarly = Boolean(ctx.client && ctx.parentSessionID);
|
|
20915
19839
|
if (canNoticeEarly) {
|
|
20916
19840
|
await sendParentNotice(ctx.client, ctx.parentSessionID, "ℹ 当前目录不是 git 仓库,worktree 隔离已自动关闭,以单目录模式运行", { directory: ctx.directory });
|
|
@@ -20919,17 +19843,17 @@ async function handleParallelCommand(raw) {
|
|
|
20919
19843
|
}
|
|
20920
19844
|
const canNotice = Boolean(ctx.client && ctx.parentSessionID);
|
|
20921
19845
|
if (specs.length === 1 && rawDescription.length >= 30 && ctx.client) {
|
|
20922
|
-
|
|
19846
|
+
log10?.info(`[${PLUGIN_NAME16}] 触发 AI 拆分(描述长度=${rawDescription.length})`);
|
|
20923
19847
|
const dec = await decomposeTask(rawDescription, {
|
|
20924
19848
|
client: ctx.client,
|
|
20925
19849
|
directory: ctx.directory,
|
|
20926
19850
|
log: (lvl, msg, data) => {
|
|
20927
19851
|
if (lvl === "error")
|
|
20928
|
-
|
|
19852
|
+
log10?.error(msg, data);
|
|
20929
19853
|
else if (lvl === "warn")
|
|
20930
|
-
|
|
19854
|
+
log10?.warn(msg, data);
|
|
20931
19855
|
else
|
|
20932
|
-
|
|
19856
|
+
log10?.info(msg, data);
|
|
20933
19857
|
},
|
|
20934
19858
|
mockResponse: ctx.decomposeMockResponse
|
|
20935
19859
|
});
|
|
@@ -20940,7 +19864,7 @@ async function handleParallelCommand(raw) {
|
|
|
20940
19864
|
timeout_ms: undefined,
|
|
20941
19865
|
args: d.hintFiles && d.hintFiles.length > 0 ? { hintFiles: d.hintFiles } : undefined
|
|
20942
19866
|
}));
|
|
20943
|
-
|
|
19867
|
+
log10?.info(`[${PLUGIN_NAME16}] AI 拆分成功 → ${specs.length} 个子任务`);
|
|
20944
19868
|
if (canNotice) {
|
|
20945
19869
|
const lines = [
|
|
20946
19870
|
`\uD83E\uDD16 AI 已自动拆为 ${specs.length} 个并行子任务:`,
|
|
@@ -20952,7 +19876,7 @@ async function handleParallelCommand(raw) {
|
|
|
20952
19876
|
});
|
|
20953
19877
|
}
|
|
20954
19878
|
} else if (dec.reason === "llm_unavailable" || dec.reason === "parse_failed") {
|
|
20955
|
-
|
|
19879
|
+
log10?.warn(`[${PLUGIN_NAME16}] AI 拆分失败(${dec.reason}),按单任务执行`);
|
|
20956
19880
|
}
|
|
20957
19881
|
}
|
|
20958
19882
|
if (canNotice) {
|
|
@@ -20967,11 +19891,11 @@ async function handleParallelCommand(raw) {
|
|
|
20967
19891
|
directory: ctx.directory,
|
|
20968
19892
|
log: (lvl, msg, data) => {
|
|
20969
19893
|
if (lvl === "error")
|
|
20970
|
-
|
|
19894
|
+
log10?.error(msg, data);
|
|
20971
19895
|
else if (lvl === "warn")
|
|
20972
|
-
|
|
19896
|
+
log10?.warn(msg, data);
|
|
20973
19897
|
else
|
|
20974
|
-
|
|
19898
|
+
log10?.info(msg, data);
|
|
20975
19899
|
}
|
|
20976
19900
|
});
|
|
20977
19901
|
}
|
|
@@ -21015,11 +19939,11 @@ async function handleParallelCommand(raw) {
|
|
|
21015
19939
|
allocateWorktree: downgradedAutoMerge ? undefined : ctx.allocateWorktree,
|
|
21016
19940
|
log: (lvl, msg, data) => {
|
|
21017
19941
|
if (lvl === "error")
|
|
21018
|
-
|
|
19942
|
+
log10?.error(msg, data);
|
|
21019
19943
|
else if (lvl === "warn")
|
|
21020
|
-
|
|
19944
|
+
log10?.warn(msg, data);
|
|
21021
19945
|
else
|
|
21022
|
-
|
|
19946
|
+
log10?.info(msg, data);
|
|
21023
19947
|
},
|
|
21024
19948
|
tryMerge: ctx.tryMerge,
|
|
21025
19949
|
mergeCommit: ctx.mergeCommit,
|
|
@@ -21031,7 +19955,7 @@ async function handleParallelCommand(raw) {
|
|
|
21031
19955
|
});
|
|
21032
19956
|
} catch (err) {
|
|
21033
19957
|
const msg = err instanceof Error ? err.message : String(err);
|
|
21034
|
-
|
|
19958
|
+
log10?.error(`[${PLUGIN_NAME16}] schedule 抛错`, { error: msg });
|
|
21035
19959
|
await safeReply2(ctx, `❌ 并发调度失败:${msg}`);
|
|
21036
19960
|
return { ok: false, reason: msg };
|
|
21037
19961
|
}
|
|
@@ -21049,7 +19973,7 @@ async function handleParallelCommand(raw) {
|
|
|
21049
19973
|
}
|
|
21050
19974
|
await safeReply2(ctx, summaryLines.join(`
|
|
21051
19975
|
`));
|
|
21052
|
-
|
|
19976
|
+
log10?.info(`[${PLUGIN_NAME16}] schedule 完成`, {
|
|
21053
19977
|
parentId,
|
|
21054
19978
|
success: result.digest.success,
|
|
21055
19979
|
failed: result.digest.failed,
|
|
@@ -21062,7 +19986,7 @@ async function handleParallelCommand(raw) {
|
|
|
21062
19986
|
try {
|
|
21063
19987
|
await ctx.onCompleted(result);
|
|
21064
19988
|
} catch (err) {
|
|
21065
|
-
|
|
19989
|
+
log10?.warn(`[${PLUGIN_NAME16}] onCompleted hook 抛错(已隔离)`, {
|
|
21066
19990
|
error: err instanceof Error ? err.message : String(err)
|
|
21067
19991
|
});
|
|
21068
19992
|
}
|
|
@@ -21104,7 +20028,7 @@ async function writeLog(level, msg, data) {
|
|
|
21104
20028
|
const line = JSON.stringify({
|
|
21105
20029
|
ts: new Date().toISOString(),
|
|
21106
20030
|
level,
|
|
21107
|
-
plugin:
|
|
20031
|
+
plugin: PLUGIN_NAME16,
|
|
21108
20032
|
msg,
|
|
21109
20033
|
data
|
|
21110
20034
|
}) + `
|
|
@@ -21115,11 +20039,11 @@ async function writeLog(level, msg, data) {
|
|
|
21115
20039
|
await fs18.appendFile(logFile, line, "utf8");
|
|
21116
20040
|
} catch {}
|
|
21117
20041
|
}
|
|
21118
|
-
logLifecycle(
|
|
20042
|
+
logLifecycle(PLUGIN_NAME16, "import");
|
|
21119
20043
|
var subtasksServer = async (ctx) => {
|
|
21120
|
-
const
|
|
20044
|
+
const log10 = makePluginLogger(PLUGIN_NAME16);
|
|
21121
20045
|
const client = ctx?.client ?? undefined;
|
|
21122
|
-
logLifecycle(
|
|
20046
|
+
logLifecycle(PLUGIN_NAME16, "activate", {
|
|
21123
20047
|
directory: ctx.directory,
|
|
21124
20048
|
hasClient: Boolean(client)
|
|
21125
20049
|
});
|
|
@@ -21144,7 +20068,7 @@ var subtasksServer = async (ctx) => {
|
|
|
21144
20068
|
const gitOk = await isGitRepo({ root: repoRoot }).catch(() => false);
|
|
21145
20069
|
if (!gitOk) {
|
|
21146
20070
|
autoMerge = false;
|
|
21147
|
-
|
|
20071
|
+
log10?.warn("[subtasks] worktree_isolation=true 但非 git 仓库,自动降级 autoMerge=false");
|
|
21148
20072
|
const canNotice = Boolean(client);
|
|
21149
20073
|
if (canNotice) {
|
|
21150
20074
|
await sendParentNotice(client, input.sessionID, "ℹ 当前目录不是 git 仓库,worktree 隔离已自动关闭,以单目录模式运行", { directory: ctx.directory });
|
|
@@ -21173,7 +20097,7 @@ var subtasksServer = async (ctx) => {
|
|
|
21173
20097
|
replyLines.push(s);
|
|
21174
20098
|
return Promise.resolve();
|
|
21175
20099
|
},
|
|
21176
|
-
log:
|
|
20100
|
+
log: log10,
|
|
21177
20101
|
client,
|
|
21178
20102
|
parentSessionID: input.sessionID,
|
|
21179
20103
|
directory: ctx.directory,
|
|
@@ -21186,11 +20110,11 @@ var subtasksServer = async (ctx) => {
|
|
|
21186
20110
|
perTaskTimeoutMs: 5 * 60000,
|
|
21187
20111
|
log: (lvl, msg, data) => {
|
|
21188
20112
|
if (lvl === "error")
|
|
21189
|
-
|
|
20113
|
+
log10.error(msg, data);
|
|
21190
20114
|
else if (lvl === "warn")
|
|
21191
|
-
|
|
20115
|
+
log10.warn(msg, data);
|
|
21192
20116
|
else
|
|
21193
|
-
|
|
20117
|
+
log10.info(msg, data);
|
|
21194
20118
|
}
|
|
21195
20119
|
}) : undefined
|
|
21196
20120
|
};
|
|
@@ -21216,20 +20140,20 @@ var subtasksServer = async (ctx) => {
|
|
|
21216
20140
|
});
|
|
21217
20141
|
}
|
|
21218
20142
|
} catch (err) {
|
|
21219
|
-
|
|
20143
|
+
log10.error(`[${PLUGIN_NAME16}] command.execute.before 异常(已隔离)`, {
|
|
21220
20144
|
error: err instanceof Error ? err.message : String(err)
|
|
21221
20145
|
});
|
|
21222
20146
|
}
|
|
21223
20147
|
}
|
|
21224
20148
|
};
|
|
21225
20149
|
};
|
|
21226
|
-
var
|
|
20150
|
+
var handler16 = subtasksServer;
|
|
21227
20151
|
|
|
21228
20152
|
// plugins/terminal-monitor.ts
|
|
21229
|
-
import * as
|
|
21230
|
-
var
|
|
21231
|
-
logLifecycle(
|
|
21232
|
-
var
|
|
20153
|
+
import * as crypto4 from "node:crypto";
|
|
20154
|
+
var PLUGIN_NAME17 = "terminal-monitor";
|
|
20155
|
+
logLifecycle(PLUGIN_NAME17, "import", {});
|
|
20156
|
+
var DEFAULT_CONFIG4 = {
|
|
21233
20157
|
minScore: 0.6,
|
|
21234
20158
|
cooldownMs: 30000,
|
|
21235
20159
|
lruLimit: 256,
|
|
@@ -21344,7 +20268,7 @@ var DEFAULT_RULES = [
|
|
|
21344
20268
|
score: 0.3
|
|
21345
20269
|
}
|
|
21346
20270
|
];
|
|
21347
|
-
function normalizeLines(raw, maxLines =
|
|
20271
|
+
function normalizeLines(raw, maxLines = DEFAULT_CONFIG4.maxLines) {
|
|
21348
20272
|
if (!raw)
|
|
21349
20273
|
return [];
|
|
21350
20274
|
const stripped = raw.replace(/\u001b\[[0-9;?]*[A-Za-z]/g, "");
|
|
@@ -21355,7 +20279,7 @@ function normalizeLines(raw, maxLines = DEFAULT_CONFIG6.maxLines) {
|
|
|
21355
20279
|
return [...lines.slice(0, half), `... [省略 ${lines.length - maxLines} 行] ...`, ...lines.slice(-half)];
|
|
21356
20280
|
}
|
|
21357
20281
|
function parseTerminalOutput(ev, cfg = {}, rules = DEFAULT_RULES) {
|
|
21358
|
-
const c = { ...
|
|
20282
|
+
const c = { ...DEFAULT_CONFIG4, ...cfg };
|
|
21359
20283
|
const stdoutLines = normalizeLines(ev.stdout, c.maxLines);
|
|
21360
20284
|
const stderrLines = normalizeLines(ev.stderr, c.maxLines);
|
|
21361
20285
|
const text = [...stdoutLines, ...stderrLines].join(`
|
|
@@ -21371,7 +20295,7 @@ function parseTerminalOutput(ev, cfg = {}, rules = DEFAULT_RULES) {
|
|
|
21371
20295
|
`).length;
|
|
21372
20296
|
const lineFromStart = text.split(`
|
|
21373
20297
|
`)[lineIdx - 1] ?? m[0];
|
|
21374
|
-
const excerpt =
|
|
20298
|
+
const excerpt = clip6(lineFromStart.trim(), c.maxExcerpt);
|
|
21375
20299
|
if (!out.has(rule.kind)) {
|
|
21376
20300
|
out.set(rule.kind, {
|
|
21377
20301
|
severity: rule.severity,
|
|
@@ -21393,16 +20317,16 @@ function parseTerminalOutput(ev, cfg = {}, rules = DEFAULT_RULES) {
|
|
|
21393
20317
|
}
|
|
21394
20318
|
return [...out.values()].sort((a, b) => b.score - a.score);
|
|
21395
20319
|
}
|
|
21396
|
-
function
|
|
20320
|
+
function clip6(s, max) {
|
|
21397
20321
|
if (s.length <= max)
|
|
21398
20322
|
return s;
|
|
21399
20323
|
return s.slice(0, max - 1) + "…";
|
|
21400
20324
|
}
|
|
21401
20325
|
|
|
21402
|
-
class
|
|
20326
|
+
class FingerprintLRU {
|
|
21403
20327
|
limit;
|
|
21404
20328
|
map = new Map;
|
|
21405
|
-
constructor(limit =
|
|
20329
|
+
constructor(limit = DEFAULT_CONFIG4.lruLimit) {
|
|
21406
20330
|
this.limit = limit;
|
|
21407
20331
|
}
|
|
21408
20332
|
add(fp, ts = Date.now()) {
|
|
@@ -21428,10 +20352,10 @@ class FingerprintLRU2 {
|
|
|
21428
20352
|
}
|
|
21429
20353
|
function fingerprintFinding(cmd, f) {
|
|
21430
20354
|
const raw = `${cmd ?? ""}|${f.kind}|${f.excerpt.slice(0, 60)}`;
|
|
21431
|
-
return
|
|
20355
|
+
return crypto4.createHash("sha1").update(raw).digest("hex").slice(0, 16);
|
|
21432
20356
|
}
|
|
21433
20357
|
function shouldNotify(findings, ev, lru, cfg = {}, now = Date.now()) {
|
|
21434
|
-
const c = { ...
|
|
20358
|
+
const c = { ...DEFAULT_CONFIG4, ...cfg };
|
|
21435
20359
|
const out = [];
|
|
21436
20360
|
let suppressed = 0;
|
|
21437
20361
|
for (const f of findings) {
|
|
@@ -21452,7 +20376,7 @@ function shouldNotify(findings, ev, lru, cfg = {}, now = Date.now()) {
|
|
|
21452
20376
|
function buildSummary(ev, findings) {
|
|
21453
20377
|
const parts = [];
|
|
21454
20378
|
if (ev.cmd)
|
|
21455
|
-
parts.push(`\`${
|
|
20379
|
+
parts.push(`\`${clip6(ev.cmd, 60)}\``);
|
|
21456
20380
|
if (ev.type === "terminal.exit" && typeof ev.exit_code === "number") {
|
|
21457
20381
|
parts.push(`exit=${ev.exit_code}`);
|
|
21458
20382
|
}
|
|
@@ -21464,7 +20388,7 @@ function buildSummary(ev, findings) {
|
|
|
21464
20388
|
return parts.join(" · ");
|
|
21465
20389
|
}
|
|
21466
20390
|
const top = findings[0];
|
|
21467
|
-
parts.push(`${top.severity}/${top.kind}: ${
|
|
20391
|
+
parts.push(`${top.severity}/${top.kind}: ${clip6(top.excerpt, 80)}`);
|
|
21468
20392
|
if (findings.length > 1)
|
|
21469
20393
|
parts.push(`(+${findings.length - 1} 条)`);
|
|
21470
20394
|
return parts.join(" · ");
|
|
@@ -21498,7 +20422,7 @@ async function processTerminalEvent(ev, opts = {}) {
|
|
|
21498
20422
|
return { ok: false, notified: false, suppressed: 0, error: msg };
|
|
21499
20423
|
}
|
|
21500
20424
|
}
|
|
21501
|
-
var sharedLRU = new
|
|
20425
|
+
var sharedLRU = new FingerprintLRU;
|
|
21502
20426
|
function describeError(err) {
|
|
21503
20427
|
if (err instanceof Error)
|
|
21504
20428
|
return err.message;
|
|
@@ -21510,17 +20434,17 @@ function describeError(err) {
|
|
|
21510
20434
|
return String(err);
|
|
21511
20435
|
}
|
|
21512
20436
|
}
|
|
21513
|
-
var
|
|
21514
|
-
var lru = new
|
|
20437
|
+
var log10 = makePluginLogger(PLUGIN_NAME17);
|
|
20438
|
+
var lru = new FingerprintLRU;
|
|
21515
20439
|
var _lastNotification = null;
|
|
21516
20440
|
var terminalMonitorServer = async (ctx) => {
|
|
21517
|
-
logLifecycle(
|
|
20441
|
+
logLifecycle(PLUGIN_NAME17, "activate", {
|
|
21518
20442
|
directory: ctx.directory,
|
|
21519
20443
|
triggerEventTypes: ["terminal.output", "terminal.exit"]
|
|
21520
20444
|
});
|
|
21521
20445
|
return {
|
|
21522
20446
|
event: async ({ event }) => {
|
|
21523
|
-
await safeAsync(
|
|
20447
|
+
await safeAsync(PLUGIN_NAME17, "event", async () => {
|
|
21524
20448
|
const e = event;
|
|
21525
20449
|
if (!e || typeof e.type !== "string")
|
|
21526
20450
|
return;
|
|
@@ -21529,12 +20453,12 @@ var terminalMonitorServer = async (ctx) => {
|
|
|
21529
20453
|
const ev = { type: e.type, ...e.properties ?? {} };
|
|
21530
20454
|
const r = await processTerminalEvent(ev, {
|
|
21531
20455
|
lru,
|
|
21532
|
-
log:
|
|
20456
|
+
log: log10,
|
|
21533
20457
|
notifyAgent: (msg) => {
|
|
21534
20458
|
_lastNotification = msg;
|
|
21535
20459
|
}
|
|
21536
20460
|
});
|
|
21537
|
-
safeWriteLog(
|
|
20461
|
+
safeWriteLog(PLUGIN_NAME17, {
|
|
21538
20462
|
hook: "event",
|
|
21539
20463
|
type: e.type,
|
|
21540
20464
|
notified: r.notified,
|
|
@@ -21542,18 +20466,133 @@ var terminalMonitorServer = async (ctx) => {
|
|
|
21542
20466
|
findings: r.notification?.findings.length ?? 0
|
|
21543
20467
|
});
|
|
21544
20468
|
if (r.notified && r.notification) {
|
|
21545
|
-
|
|
20469
|
+
log10.info(`[${PLUGIN_NAME17}] 反馈 ${r.notification.findings.length} 条 → ${r.notification.summary}`);
|
|
21546
20470
|
}
|
|
21547
20471
|
});
|
|
21548
20472
|
}
|
|
21549
20473
|
};
|
|
21550
20474
|
};
|
|
21551
|
-
var
|
|
20475
|
+
var handler17 = terminalMonitorServer;
|
|
20476
|
+
|
|
20477
|
+
// lib/condenser.ts
|
|
20478
|
+
var DEFAULT_CONDENSE = {
|
|
20479
|
+
budget: 128000,
|
|
20480
|
+
threshold: 0.7,
|
|
20481
|
+
target: 0.4,
|
|
20482
|
+
keepRecent: 6,
|
|
20483
|
+
keepSystem: true,
|
|
20484
|
+
charsPerToken: 4
|
|
20485
|
+
};
|
|
20486
|
+
function estimateTokens(text, charsPerToken = 4) {
|
|
20487
|
+
if (!text)
|
|
20488
|
+
return 0;
|
|
20489
|
+
return Math.max(1, Math.ceil(text.length / charsPerToken));
|
|
20490
|
+
}
|
|
20491
|
+
function tokenizeMessages(msgs, charsPerToken = 4) {
|
|
20492
|
+
let total = 0;
|
|
20493
|
+
for (const m of msgs) {
|
|
20494
|
+
total += m.tokens ?? estimateTokens(m.content, charsPerToken);
|
|
20495
|
+
}
|
|
20496
|
+
return total;
|
|
20497
|
+
}
|
|
20498
|
+
function fallbackSummarize(msgs) {
|
|
20499
|
+
if (msgs.length === 0)
|
|
20500
|
+
return "";
|
|
20501
|
+
const head = msgs.slice(0, 2).map((m) => `- [${m.role}] ${truncate(m.content, 120)}`);
|
|
20502
|
+
const tail = msgs.slice(-2).map((m) => `- [${m.role}] ${truncate(m.content, 120)}`);
|
|
20503
|
+
const tagCounts = new Map;
|
|
20504
|
+
let userCount = 0;
|
|
20505
|
+
let assistantCount = 0;
|
|
20506
|
+
let toolCount = 0;
|
|
20507
|
+
for (const m of msgs) {
|
|
20508
|
+
if (m.role === "user")
|
|
20509
|
+
userCount++;
|
|
20510
|
+
else if (m.role === "assistant")
|
|
20511
|
+
assistantCount++;
|
|
20512
|
+
else if (m.role === "tool")
|
|
20513
|
+
toolCount++;
|
|
20514
|
+
if (m.tag)
|
|
20515
|
+
tagCounts.set(m.tag, (tagCounts.get(m.tag) ?? 0) + 1);
|
|
20516
|
+
}
|
|
20517
|
+
const tagsLine = [...tagCounts.entries()].sort((a, b) => b[1] - a[1]).slice(0, 5).map(([t, c]) => `${t}×${c}`).join(", ");
|
|
20518
|
+
return [
|
|
20519
|
+
`### 历史摘要(共 ${msgs.length} 条 · user=${userCount} assistant=${assistantCount} tool=${toolCount})`,
|
|
20520
|
+
tagsLine ? `- 关键事件:${tagsLine}` : "",
|
|
20521
|
+
"",
|
|
20522
|
+
"**开头:**",
|
|
20523
|
+
...head,
|
|
20524
|
+
"",
|
|
20525
|
+
"**结尾:**",
|
|
20526
|
+
...tail
|
|
20527
|
+
].filter(Boolean).join(`
|
|
20528
|
+
`);
|
|
20529
|
+
}
|
|
20530
|
+
function truncate(s, max) {
|
|
20531
|
+
if (!s)
|
|
20532
|
+
return "";
|
|
20533
|
+
return s.length <= max ? s : s.slice(0, max - 1) + "…";
|
|
20534
|
+
}
|
|
20535
|
+
async function condense(input, opts = {}) {
|
|
20536
|
+
const cfg = { ...DEFAULT_CONDENSE, ...opts.config ?? {} };
|
|
20537
|
+
const msgs = [...input];
|
|
20538
|
+
const before = {
|
|
20539
|
+
count: msgs.length,
|
|
20540
|
+
tokens: tokenizeMessages(msgs, cfg.charsPerToken)
|
|
20541
|
+
};
|
|
20542
|
+
if (cfg.budget <= 0 || before.tokens / cfg.budget < cfg.threshold) {
|
|
20543
|
+
return {
|
|
20544
|
+
messages: msgs,
|
|
20545
|
+
before,
|
|
20546
|
+
after: { ...before },
|
|
20547
|
+
compressed: false,
|
|
20548
|
+
reason: `usage ${(before.tokens / Math.max(cfg.budget, 1)).toFixed(2)} < threshold ${cfg.threshold}`
|
|
20549
|
+
};
|
|
20550
|
+
}
|
|
20551
|
+
const systems = cfg.keepSystem ? msgs.filter((m) => m.role === "system") : [];
|
|
20552
|
+
const nonSystem = cfg.keepSystem ? msgs.filter((m) => m.role !== "system") : msgs;
|
|
20553
|
+
const recents = nonSystem.slice(-cfg.keepRecent);
|
|
20554
|
+
const middles = nonSystem.slice(0, Math.max(0, nonSystem.length - cfg.keepRecent));
|
|
20555
|
+
if (middles.length === 0) {
|
|
20556
|
+
return {
|
|
20557
|
+
messages: msgs,
|
|
20558
|
+
before,
|
|
20559
|
+
after: { ...before },
|
|
20560
|
+
compressed: false,
|
|
20561
|
+
reason: "nothing in middle to compress"
|
|
20562
|
+
};
|
|
20563
|
+
}
|
|
20564
|
+
let summary = "";
|
|
20565
|
+
try {
|
|
20566
|
+
summary = await (opts.summarize ?? fallbackSummarize)(middles) ?? "";
|
|
20567
|
+
} catch {
|
|
20568
|
+
summary = fallbackSummarize(middles);
|
|
20569
|
+
}
|
|
20570
|
+
if (!summary)
|
|
20571
|
+
summary = fallbackSummarize(middles);
|
|
20572
|
+
const summaryMsg = {
|
|
20573
|
+
role: "assistant",
|
|
20574
|
+
content: summary,
|
|
20575
|
+
tag: "condensed-summary"
|
|
20576
|
+
};
|
|
20577
|
+
const out = [...systems, summaryMsg, ...recents];
|
|
20578
|
+
const after = {
|
|
20579
|
+
count: out.length,
|
|
20580
|
+
tokens: tokenizeMessages(out, cfg.charsPerToken)
|
|
20581
|
+
};
|
|
20582
|
+
return {
|
|
20583
|
+
messages: out,
|
|
20584
|
+
before,
|
|
20585
|
+
after,
|
|
20586
|
+
compressed: true,
|
|
20587
|
+
summary,
|
|
20588
|
+
reason: `compressed ${middles.length} → 1 summary (target ${cfg.target})`
|
|
20589
|
+
};
|
|
20590
|
+
}
|
|
21552
20591
|
|
|
21553
20592
|
// plugins/token-manager.ts
|
|
21554
|
-
var
|
|
21555
|
-
logLifecycle(
|
|
21556
|
-
async function handleMessageBefore(raw,
|
|
20593
|
+
var PLUGIN_NAME18 = "token-manager";
|
|
20594
|
+
logLifecycle(PLUGIN_NAME18, "import", {});
|
|
20595
|
+
async function handleMessageBefore(raw, log11, defaults) {
|
|
21557
20596
|
const ctx = raw ?? {};
|
|
21558
20597
|
if (!Array.isArray(ctx.messages) || ctx.messages.length === 0)
|
|
21559
20598
|
return null;
|
|
@@ -21572,21 +20611,21 @@ async function handleMessageBefore(raw, log12, defaults) {
|
|
|
21572
20611
|
};
|
|
21573
20612
|
if (r.compressed) {
|
|
21574
20613
|
ctx.messages = r.messages;
|
|
21575
|
-
|
|
20614
|
+
log11?.info(`[${PLUGIN_NAME18}] 压缩 ${r.before.count}→${r.after.count} 条 / ${r.before.tokens}→${r.after.tokens} tokens`);
|
|
21576
20615
|
}
|
|
21577
20616
|
return r;
|
|
21578
20617
|
} catch (err) {
|
|
21579
|
-
|
|
20618
|
+
log11?.warn(`[${PLUGIN_NAME18}] 压缩异常(已隔离)`, {
|
|
21580
20619
|
error: err instanceof Error ? err.message : String(err)
|
|
21581
20620
|
});
|
|
21582
20621
|
return null;
|
|
21583
20622
|
}
|
|
21584
20623
|
}
|
|
21585
|
-
var
|
|
20624
|
+
var log11 = makePluginLogger(PLUGIN_NAME18);
|
|
21586
20625
|
var tokenManagerServer = async (ctx) => {
|
|
21587
20626
|
const rt = loadRuntimeSync();
|
|
21588
20627
|
const threshold = rt.runtime.context.condenser_threshold_ratio;
|
|
21589
|
-
logLifecycle(
|
|
20628
|
+
logLifecycle(PLUGIN_NAME18, "activate", {
|
|
21590
20629
|
directory: ctx.directory,
|
|
21591
20630
|
threshold,
|
|
21592
20631
|
target: DEFAULT_CONDENSE.target,
|
|
@@ -21595,7 +20634,7 @@ var tokenManagerServer = async (ctx) => {
|
|
|
21595
20634
|
});
|
|
21596
20635
|
return {
|
|
21597
20636
|
"experimental.chat.messages.transform": async (_input, output) => {
|
|
21598
|
-
await safeAsync(
|
|
20637
|
+
await safeAsync(PLUGIN_NAME18, "experimental.chat.messages.transform", async () => {
|
|
21599
20638
|
const list = output.messages;
|
|
21600
20639
|
if (!Array.isArray(list) || list.length === 0)
|
|
21601
20640
|
return;
|
|
@@ -21605,10 +20644,10 @@ var tokenManagerServer = async (ctx) => {
|
|
|
21605
20644
|
`);
|
|
21606
20645
|
return { role, content };
|
|
21607
20646
|
});
|
|
21608
|
-
const r = await handleMessageBefore({ messages: flat },
|
|
20647
|
+
const r = await handleMessageBefore({ messages: flat }, log11, { threshold });
|
|
21609
20648
|
if (!r)
|
|
21610
20649
|
return;
|
|
21611
|
-
safeWriteLog(
|
|
20650
|
+
safeWriteLog(PLUGIN_NAME18, {
|
|
21612
20651
|
hook: "experimental.chat.messages.transform",
|
|
21613
20652
|
mode: "observe-only",
|
|
21614
20653
|
before_msgs: r.before.count,
|
|
@@ -21619,13 +20658,13 @@ var tokenManagerServer = async (ctx) => {
|
|
|
21619
20658
|
reason: r.reason
|
|
21620
20659
|
});
|
|
21621
20660
|
if (r.compressed) {
|
|
21622
|
-
|
|
20661
|
+
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
20662
|
}
|
|
21624
20663
|
});
|
|
21625
20664
|
}
|
|
21626
20665
|
};
|
|
21627
20666
|
};
|
|
21628
|
-
var
|
|
20667
|
+
var handler18 = tokenManagerServer;
|
|
21629
20668
|
|
|
21630
20669
|
// plugins/tool-policy.ts
|
|
21631
20670
|
import { promises as fs19 } from "node:fs";
|
|
@@ -21909,8 +20948,8 @@ function checkFileAccess(acl, file, op) {
|
|
|
21909
20948
|
}
|
|
21910
20949
|
|
|
21911
20950
|
// plugins/tool-policy.ts
|
|
21912
|
-
var
|
|
21913
|
-
logLifecycle(
|
|
20951
|
+
var PLUGIN_NAME19 = "tool-policy";
|
|
20952
|
+
logLifecycle(PLUGIN_NAME19, "import", {});
|
|
21914
20953
|
var EMPTY_ACL = { whitelistMode: false };
|
|
21915
20954
|
function getAgentAcl(cfg, agent) {
|
|
21916
20955
|
if (!agent || !cfg.per_agent)
|
|
@@ -21980,18 +21019,18 @@ async function loadPolicy(root = process.cwd()) {
|
|
|
21980
21019
|
function classifyToolKind(toolName) {
|
|
21981
21020
|
return classifyTool(toolName);
|
|
21982
21021
|
}
|
|
21983
|
-
async function resolveCurrentAgent2(client, sessionID,
|
|
21022
|
+
async function resolveCurrentAgent2(client, sessionID, log12) {
|
|
21984
21023
|
try {
|
|
21985
21024
|
const sessionApi = client?.session;
|
|
21986
21025
|
if (!sessionApi || typeof sessionApi.get !== "function") {
|
|
21987
|
-
|
|
21026
|
+
log12.warn(`client.session.get unavailable`, { sessionID });
|
|
21988
21027
|
return;
|
|
21989
21028
|
}
|
|
21990
21029
|
const res = await sessionApi.get({ path: { id: sessionID } });
|
|
21991
21030
|
const data = res?.data ?? res;
|
|
21992
21031
|
const rawAgent = data?.agent;
|
|
21993
21032
|
if (typeof rawAgent !== "string" || rawAgent === "") {
|
|
21994
|
-
|
|
21033
|
+
log12.warn(`client.session.get returned no string agent (保守放行)`, {
|
|
21995
21034
|
sessionID,
|
|
21996
21035
|
dataKeys: data && typeof data === "object" ? Object.keys(data) : null
|
|
21997
21036
|
});
|
|
@@ -21999,31 +21038,31 @@ async function resolveCurrentAgent2(client, sessionID, log13) {
|
|
|
21999
21038
|
}
|
|
22000
21039
|
return rawAgent;
|
|
22001
21040
|
} catch (err) {
|
|
22002
|
-
|
|
21041
|
+
log12.warn(`client.session.get failed (保守放行)`, {
|
|
22003
21042
|
sessionID,
|
|
22004
21043
|
error: err instanceof Error ? err.message : String(err)
|
|
22005
21044
|
});
|
|
22006
21045
|
return;
|
|
22007
21046
|
}
|
|
22008
21047
|
}
|
|
22009
|
-
var
|
|
21048
|
+
var log12 = makePluginLogger(PLUGIN_NAME19);
|
|
22010
21049
|
var toolPolicyServer = async (ctx) => {
|
|
22011
21050
|
const directory = ctx.directory ?? process.cwd();
|
|
22012
21051
|
const cfg = await loadPolicy(directory);
|
|
22013
|
-
logLifecycle(
|
|
21052
|
+
logLifecycle(PLUGIN_NAME19, "activate", {
|
|
22014
21053
|
directory,
|
|
22015
21054
|
acl_loaded: !!cfg.acl
|
|
22016
21055
|
});
|
|
22017
21056
|
return {
|
|
22018
21057
|
"tool.execute.before": async (input, output) => {
|
|
22019
21058
|
let denied;
|
|
22020
|
-
await safeAsync(
|
|
21059
|
+
await safeAsync(PLUGIN_NAME19, "tool.execute.before", async () => {
|
|
22021
21060
|
const toolName = input.tool;
|
|
22022
21061
|
const argsObj = output.args ?? {};
|
|
22023
21062
|
const needsAgentDetection = toolName === "pending_changes" && (argsObj.action === "apply" || argsObj.action === "apply_all");
|
|
22024
21063
|
let currentAgent = undefined;
|
|
22025
21064
|
if (needsAgentDetection) {
|
|
22026
|
-
currentAgent = await resolveCurrentAgent2(ctx.client, input.sessionID,
|
|
21065
|
+
currentAgent = await resolveCurrentAgent2(ctx.client, input.sessionID, log12);
|
|
22027
21066
|
}
|
|
22028
21067
|
const files = [];
|
|
22029
21068
|
const filePath = argsObj["path"] ?? argsObj["filePath"] ?? argsObj["file"];
|
|
@@ -22037,7 +21076,7 @@ var toolPolicyServer = async (ctx) => {
|
|
|
22037
21076
|
args: argsObj,
|
|
22038
21077
|
files: files.length ? files : undefined
|
|
22039
21078
|
}, cfg, currentAgent);
|
|
22040
|
-
safeWriteLog(
|
|
21079
|
+
safeWriteLog(PLUGIN_NAME19, {
|
|
22041
21080
|
hook: "tool.execute.before",
|
|
22042
21081
|
tool: toolName,
|
|
22043
21082
|
callID: input.callID,
|
|
@@ -22048,7 +21087,7 @@ var toolPolicyServer = async (ctx) => {
|
|
|
22048
21087
|
currentAgent
|
|
22049
21088
|
});
|
|
22050
21089
|
if (decision.action === "deny") {
|
|
22051
|
-
|
|
21090
|
+
log12.warn(`[${PLUGIN_NAME19}] DENY ${toolName}`, {
|
|
22052
21091
|
action: argsObj.action,
|
|
22053
21092
|
currentAgent,
|
|
22054
21093
|
reasons: decision.reasons,
|
|
@@ -22063,7 +21102,7 @@ var toolPolicyServer = async (ctx) => {
|
|
|
22063
21102
|
}
|
|
22064
21103
|
};
|
|
22065
21104
|
};
|
|
22066
|
-
var
|
|
21105
|
+
var handler19 = toolPolicyServer;
|
|
22067
21106
|
|
|
22068
21107
|
// plugins/update-checker.ts
|
|
22069
21108
|
import { existsSync as existsSync6 } from "node:fs";
|
|
@@ -22071,7 +21110,7 @@ import { homedir as homedir8 } from "node:os";
|
|
|
22071
21110
|
import { join as join23 } from "node:path";
|
|
22072
21111
|
|
|
22073
21112
|
// lib/update-checker-impl.ts
|
|
22074
|
-
import { createHash as
|
|
21113
|
+
import { createHash as createHash4 } from "node:crypto";
|
|
22075
21114
|
import {
|
|
22076
21115
|
copyFileSync,
|
|
22077
21116
|
existsSync as existsSync5,
|
|
@@ -22093,7 +21132,7 @@ import * as zlib from "node:zlib";
|
|
|
22093
21132
|
// lib/version-injected.ts
|
|
22094
21133
|
function getInjectedVersion() {
|
|
22095
21134
|
try {
|
|
22096
|
-
const v = "0.5.
|
|
21135
|
+
const v = "0.5.21";
|
|
22097
21136
|
if (typeof v === "string" && /^\d+\.\d+\.\d+/.test(v)) {
|
|
22098
21137
|
return v;
|
|
22099
21138
|
}
|
|
@@ -22372,7 +21411,7 @@ function verifyIntegrity(buf, expected) {
|
|
|
22372
21411
|
if (algo !== "sha512" && algo !== "sha256" && algo !== "sha384") {
|
|
22373
21412
|
throw new Error(`integrity_algo_unsupported: ${algo}`);
|
|
22374
21413
|
}
|
|
22375
|
-
const actualB64 =
|
|
21414
|
+
const actualB64 = createHash4(algo).update(buf).digest("base64");
|
|
22376
21415
|
if (actualB64 !== expectedB64) {
|
|
22377
21416
|
throw new Error(`integrity_mismatch: expected ${algo}=${expectedB64.slice(0, 16)}... got ${actualB64.slice(0, 16)}...`);
|
|
22378
21417
|
}
|
|
@@ -22596,20 +21635,20 @@ function compareOpencodeVersion(opts) {
|
|
|
22596
21635
|
}
|
|
22597
21636
|
|
|
22598
21637
|
// plugins/update-checker.ts
|
|
22599
|
-
var
|
|
21638
|
+
var PLUGIN_NAME20 = "update-checker";
|
|
22600
21639
|
var PLUGIN_VERSION = "2.0.0";
|
|
22601
|
-
logLifecycle(
|
|
21640
|
+
logLifecycle(PLUGIN_NAME20, "import", { version: PLUGIN_VERSION });
|
|
22602
21641
|
var updateCheckerServer = async (ctx) => {
|
|
22603
21642
|
const yieldResult = shouldYieldToLocalPlugin({ directory: ctx.directory });
|
|
22604
21643
|
if (yieldResult.yield) {
|
|
22605
|
-
safeWriteLog(
|
|
21644
|
+
safeWriteLog(PLUGIN_NAME20, {
|
|
22606
21645
|
level: "info",
|
|
22607
21646
|
msg: "dev_mode_yield_skip",
|
|
22608
21647
|
reason: yieldResult.reason,
|
|
22609
21648
|
markerPath: yieldResult.markerPath,
|
|
22610
21649
|
detail: formatYieldLog(yieldResult)
|
|
22611
21650
|
});
|
|
22612
|
-
logLifecycle(
|
|
21651
|
+
logLifecycle(PLUGIN_NAME20, "activate", {
|
|
22613
21652
|
yield_to_local: true,
|
|
22614
21653
|
yield_reason: yieldResult.reason,
|
|
22615
21654
|
skipped: "auto_install + background_check"
|
|
@@ -22618,7 +21657,7 @@ var updateCheckerServer = async (ctx) => {
|
|
|
22618
21657
|
}
|
|
22619
21658
|
const rt = loadRuntimeSync();
|
|
22620
21659
|
const u = rt.runtime.update;
|
|
22621
|
-
logLifecycle(
|
|
21660
|
+
logLifecycle(PLUGIN_NAME20, "activate", {
|
|
22622
21661
|
version: PLUGIN_VERSION,
|
|
22623
21662
|
auto_check_enabled: u.auto_check_enabled,
|
|
22624
21663
|
interval_hours: u.interval_hours,
|
|
@@ -22630,14 +21669,14 @@ var updateCheckerServer = async (ctx) => {
|
|
|
22630
21669
|
repo_fallback: u.repo,
|
|
22631
21670
|
config_source: "codeforge.json"
|
|
22632
21671
|
});
|
|
22633
|
-
await safeAsync(
|
|
21672
|
+
await safeAsync(PLUGIN_NAME20, "opencode_version_check", async () => {
|
|
22634
21673
|
const compat = loadCompatibility();
|
|
22635
21674
|
const opencodeVer = detectOpencodeVersion();
|
|
22636
21675
|
const verdict = compareOpencodeVersion({
|
|
22637
21676
|
currentOpencodeVer: opencodeVer,
|
|
22638
21677
|
compat
|
|
22639
21678
|
});
|
|
22640
|
-
safeWriteLog(
|
|
21679
|
+
safeWriteLog(PLUGIN_NAME20, {
|
|
22641
21680
|
level: "info",
|
|
22642
21681
|
msg: "opencode_version_check",
|
|
22643
21682
|
opencodeVer,
|
|
@@ -22654,14 +21693,14 @@ var updateCheckerServer = async (ctx) => {
|
|
|
22654
21693
|
}
|
|
22655
21694
|
});
|
|
22656
21695
|
if (!u.auto_check_enabled) {
|
|
22657
|
-
safeWriteLog(
|
|
21696
|
+
safeWriteLog(PLUGIN_NAME20, {
|
|
22658
21697
|
level: "info",
|
|
22659
21698
|
msg: "auto-check disabled (codeforge.json update.auto_check_enabled=false)"
|
|
22660
21699
|
});
|
|
22661
21700
|
return {};
|
|
22662
21701
|
}
|
|
22663
21702
|
setImmediate(() => {
|
|
22664
|
-
safeAsync(
|
|
21703
|
+
safeAsync(PLUGIN_NAME20, "checkAndMaybeUpdate", async () => {
|
|
22665
21704
|
const local = readLocalVersion();
|
|
22666
21705
|
let npmResult = null;
|
|
22667
21706
|
try {
|
|
@@ -22672,7 +21711,7 @@ var updateCheckerServer = async (ctx) => {
|
|
|
22672
21711
|
timeoutMs: 5000
|
|
22673
21712
|
});
|
|
22674
21713
|
} catch (e) {
|
|
22675
|
-
safeWriteLog(
|
|
21714
|
+
safeWriteLog(PLUGIN_NAME20, {
|
|
22676
21715
|
level: "warn",
|
|
22677
21716
|
msg: "npm_fetch_failed",
|
|
22678
21717
|
error: e.message
|
|
@@ -22681,7 +21720,7 @@ var updateCheckerServer = async (ctx) => {
|
|
|
22681
21720
|
return;
|
|
22682
21721
|
}
|
|
22683
21722
|
if (!npmResult) {
|
|
22684
|
-
safeWriteLog(
|
|
21723
|
+
safeWriteLog(PLUGIN_NAME20, {
|
|
22685
21724
|
level: "info",
|
|
22686
21725
|
msg: "npm_no_release",
|
|
22687
21726
|
package: u.package,
|
|
@@ -22690,7 +21729,7 @@ var updateCheckerServer = async (ctx) => {
|
|
|
22690
21729
|
return;
|
|
22691
21730
|
}
|
|
22692
21731
|
const hasUpdate = cmpVersion(local, npmResult.version) < 0;
|
|
22693
|
-
safeWriteLog(
|
|
21732
|
+
safeWriteLog(PLUGIN_NAME20, {
|
|
22694
21733
|
level: "info",
|
|
22695
21734
|
msg: "npm_check_result",
|
|
22696
21735
|
local,
|
|
@@ -22705,10 +21744,10 @@ var updateCheckerServer = async (ctx) => {
|
|
|
22705
21744
|
更新命令:npx ${u.package} install --global`);
|
|
22706
21745
|
return;
|
|
22707
21746
|
}
|
|
22708
|
-
await safeAsync(
|
|
21747
|
+
await safeAsync(PLUGIN_NAME20, "auto_install_bundle", async () => {
|
|
22709
21748
|
const target = getOpencodeBundlePath();
|
|
22710
21749
|
if (!target) {
|
|
22711
|
-
safeWriteLog(
|
|
21750
|
+
safeWriteLog(PLUGIN_NAME20, {
|
|
22712
21751
|
level: "warn",
|
|
22713
21752
|
msg: "auto_install_skip",
|
|
22714
21753
|
reason: "无法定位 opencode bundle 路径"
|
|
@@ -22727,7 +21766,7 @@ var updateCheckerServer = async (ctx) => {
|
|
|
22727
21766
|
oldVersion: local,
|
|
22728
21767
|
keepBackups: u.backup_keep
|
|
22729
21768
|
});
|
|
22730
|
-
safeWriteLog(
|
|
21769
|
+
safeWriteLog(PLUGIN_NAME20, {
|
|
22731
21770
|
level: "info",
|
|
22732
21771
|
msg: "auto_install_success",
|
|
22733
21772
|
local,
|
|
@@ -22767,7 +21806,7 @@ function getOpencodeBundlePath() {
|
|
|
22767
21806
|
return candidates[0] ?? null;
|
|
22768
21807
|
}
|
|
22769
21808
|
async function fallbackToGitHubReleases(ctx, u) {
|
|
22770
|
-
await safeAsync(
|
|
21809
|
+
await safeAsync(PLUGIN_NAME20, "github_fallback", async () => {
|
|
22771
21810
|
const result = await checkUpdateOnce({
|
|
22772
21811
|
repo: u.repo,
|
|
22773
21812
|
intervalMs: u.interval_hours * 3600 * 1000
|
|
@@ -22777,7 +21816,7 @@ async function fallbackToGitHubReleases(ctx, u) {
|
|
|
22777
21816
|
}
|
|
22778
21817
|
async function reportLegacyResult(ctx, result, repo) {
|
|
22779
21818
|
if (result.error && !result.fromCache) {
|
|
22780
|
-
safeWriteLog(
|
|
21819
|
+
safeWriteLog(PLUGIN_NAME20, {
|
|
22781
21820
|
level: "warn",
|
|
22782
21821
|
msg: `update check failed: ${result.error}`,
|
|
22783
21822
|
...result
|
|
@@ -22785,7 +21824,7 @@ async function reportLegacyResult(ctx, result, repo) {
|
|
|
22785
21824
|
return;
|
|
22786
21825
|
}
|
|
22787
21826
|
if (!result.hasUpdate) {
|
|
22788
|
-
safeWriteLog(
|
|
21827
|
+
safeWriteLog(PLUGIN_NAME20, {
|
|
22789
21828
|
level: "info",
|
|
22790
21829
|
msg: `up-to-date (local=${result.local}, remote=${result.remote}${result.fromCache ? ", from_cache" : ""})`,
|
|
22791
21830
|
...result
|
|
@@ -22795,7 +21834,7 @@ async function reportLegacyResult(ctx, result, repo) {
|
|
|
22795
21834
|
const updateCmd = `bunx --bun github:${repo} install`;
|
|
22796
21835
|
const toast = `[CodeForge] 有新版本:${result.local} → ${result.remote}
|
|
22797
21836
|
更新命令:${updateCmd}`;
|
|
22798
|
-
safeWriteLog(
|
|
21837
|
+
safeWriteLog(PLUGIN_NAME20, {
|
|
22799
21838
|
level: "info",
|
|
22800
21839
|
msg: "new_version_available_github_fallback",
|
|
22801
21840
|
local: result.local,
|
|
@@ -22806,17 +21845,17 @@ async function reportLegacyResult(ctx, result, repo) {
|
|
|
22806
21845
|
await postToast(ctx, toast);
|
|
22807
21846
|
}
|
|
22808
21847
|
async function postToast(ctx, message) {
|
|
22809
|
-
await safeAsync(
|
|
21848
|
+
await safeAsync(PLUGIN_NAME20, "client.app.log", async () => {
|
|
22810
21849
|
await ctx.client.app.log({
|
|
22811
21850
|
body: {
|
|
22812
|
-
service:
|
|
21851
|
+
service: PLUGIN_NAME20,
|
|
22813
21852
|
level: "info",
|
|
22814
21853
|
message
|
|
22815
21854
|
}
|
|
22816
21855
|
});
|
|
22817
21856
|
});
|
|
22818
21857
|
}
|
|
22819
|
-
var
|
|
21858
|
+
var handler20 = updateCheckerServer;
|
|
22820
21859
|
|
|
22821
21860
|
// plugins/workflow-engine.ts
|
|
22822
21861
|
import * as path27 from "node:path";
|
|
@@ -23271,9 +22310,9 @@ async function runStepAutoFeedback(step, adapter) {
|
|
|
23271
22310
|
}
|
|
23272
22311
|
|
|
23273
22312
|
// plugins/workflow-engine.ts
|
|
23274
|
-
var
|
|
23275
|
-
logLifecycle(
|
|
23276
|
-
var fallbackLog2 = makePluginLogger(
|
|
22313
|
+
var PLUGIN_NAME21 = "workflow-engine";
|
|
22314
|
+
logLifecycle(PLUGIN_NAME21, "import", {});
|
|
22315
|
+
var fallbackLog2 = makePluginLogger(PLUGIN_NAME21);
|
|
23277
22316
|
var _registry = null;
|
|
23278
22317
|
async function loadRegistry(workflowsDir) {
|
|
23279
22318
|
const { loaded, failed } = await loadWorkflowsFromDir(workflowsDir);
|
|
@@ -23290,35 +22329,35 @@ async function ensureRegistry(workflowsDir = "workflows") {
|
|
|
23290
22329
|
}
|
|
23291
22330
|
async function handleCommandInvoked(raw, workflowsDir = "workflows") {
|
|
23292
22331
|
const ctx = raw ?? {};
|
|
23293
|
-
const
|
|
22332
|
+
const log13 = ctx.log ?? fallbackLog2;
|
|
23294
22333
|
const command = typeof ctx.command === "string" ? ctx.command : null;
|
|
23295
22334
|
if (!command) {
|
|
23296
|
-
|
|
22335
|
+
log13.warn(`[${PLUGIN_NAME21}] command.invoked 缺 command 字段`, ctx);
|
|
23297
22336
|
return null;
|
|
23298
22337
|
}
|
|
23299
22338
|
const reg = await ensureRegistry(workflowsDir);
|
|
23300
22339
|
if (reg.errors.length) {
|
|
23301
|
-
|
|
22340
|
+
log13.warn(`[${PLUGIN_NAME21}] 有 ${reg.errors.length} 个 workflow 加载失败`, reg.errors);
|
|
23302
22341
|
}
|
|
23303
22342
|
const wf = reg.workflows.find((w) => matchesTrigger(w, command));
|
|
23304
22343
|
if (!wf) {
|
|
23305
|
-
|
|
22344
|
+
log13.info(`[${PLUGIN_NAME21}] no workflow matches "${command}"`);
|
|
23306
22345
|
return null;
|
|
23307
22346
|
}
|
|
23308
|
-
|
|
22347
|
+
log13.info(`[${PLUGIN_NAME21}] dispatch "${command}" → workflow "${wf.name}"`);
|
|
23309
22348
|
try {
|
|
23310
22349
|
const result = await run(wf, {
|
|
23311
22350
|
mode: ctx.adapter ? "real" : "dry_run",
|
|
23312
22351
|
autonomy: ctx.autonomy ?? "semi",
|
|
23313
22352
|
adapter: ctx.adapter
|
|
23314
22353
|
});
|
|
23315
|
-
|
|
22354
|
+
log13.info(`[${PLUGIN_NAME21}] workflow "${wf.name}" 完成 (${result.plan.mode})`, {
|
|
23316
22355
|
steps: result.plan.steps.length,
|
|
23317
22356
|
results: result.results.length
|
|
23318
22357
|
});
|
|
23319
22358
|
return result;
|
|
23320
22359
|
} catch (err) {
|
|
23321
|
-
|
|
22360
|
+
log13.error(`[${PLUGIN_NAME21}] workflow "${wf.name}" 执行失败`, {
|
|
23322
22361
|
error: err instanceof Error ? err.message : String(err)
|
|
23323
22362
|
});
|
|
23324
22363
|
throw err;
|
|
@@ -23327,15 +22366,15 @@ async function handleCommandInvoked(raw, workflowsDir = "workflows") {
|
|
|
23327
22366
|
var workflowEngineServer = async (ctx) => {
|
|
23328
22367
|
const directory = ctx.directory ?? process.cwd();
|
|
23329
22368
|
const workflowsDir = path27.join(directory, "workflows");
|
|
23330
|
-
ensureRegistry(workflowsDir).catch((err) => fallbackLog2.warn(`[${
|
|
22369
|
+
ensureRegistry(workflowsDir).catch((err) => fallbackLog2.warn(`[${PLUGIN_NAME21}] preload workflows failed`, {
|
|
23331
22370
|
error: err instanceof Error ? err.message : String(err)
|
|
23332
22371
|
}));
|
|
23333
|
-
logLifecycle(
|
|
22372
|
+
logLifecycle(PLUGIN_NAME21, "activate", { directory, workflowsDir });
|
|
23334
22373
|
return {
|
|
23335
22374
|
"command.execute.before": async (input, output) => {
|
|
23336
|
-
await safeAsync(
|
|
22375
|
+
await safeAsync(PLUGIN_NAME21, "command.execute.before", async () => {
|
|
23337
22376
|
const cmd = input.command.startsWith("/") ? input.command : `/${input.command}`;
|
|
23338
|
-
safeWriteLog(
|
|
22377
|
+
safeWriteLog(PLUGIN_NAME21, {
|
|
23339
22378
|
hook: "command.execute.before",
|
|
23340
22379
|
command: cmd,
|
|
23341
22380
|
sessionID: input.sessionID
|
|
@@ -23350,14 +22389,14 @@ var workflowEngineServer = async (ctx) => {
|
|
|
23350
22389
|
});
|
|
23351
22390
|
},
|
|
23352
22391
|
"chat.message": async (input, output) => {
|
|
23353
|
-
await safeAsync(
|
|
22392
|
+
await safeAsync(PLUGIN_NAME21, "chat.message", async () => {
|
|
23354
22393
|
const text = extractUserText(output).trim();
|
|
23355
22394
|
if (!text.startsWith("/"))
|
|
23356
22395
|
return;
|
|
23357
22396
|
const cmd = text.split(/\s+/)[0];
|
|
23358
22397
|
if (!cmd)
|
|
23359
22398
|
return;
|
|
23360
|
-
safeWriteLog(
|
|
22399
|
+
safeWriteLog(PLUGIN_NAME21, {
|
|
23361
22400
|
hook: "chat.message",
|
|
23362
22401
|
command: cmd,
|
|
23363
22402
|
sessionID: input.sessionID
|
|
@@ -23367,12 +22406,12 @@ var workflowEngineServer = async (ctx) => {
|
|
|
23367
22406
|
}
|
|
23368
22407
|
};
|
|
23369
22408
|
};
|
|
23370
|
-
var
|
|
22409
|
+
var handler21 = workflowEngineServer;
|
|
23371
22410
|
|
|
23372
22411
|
// plugins/session-worktree-guard.ts
|
|
23373
22412
|
import path28 from "node:path";
|
|
23374
|
-
var
|
|
23375
|
-
logLifecycle(
|
|
22413
|
+
var PLUGIN_NAME22 = "session-worktree-guard";
|
|
22414
|
+
logLifecycle(PLUGIN_NAME22, "import", {});
|
|
23376
22415
|
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
22416
|
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
22417
|
var SIDE_EFFECT_TOKEN_RE = />(?![=&])(?!\s*\/dev\/(?:null|stdout|stderr|fd\/\d+)\b)|\|\s*tee\b|\btee\b/;
|
|
@@ -23395,11 +22434,11 @@ var INTERPRETER_WRITE_RES = [
|
|
|
23395
22434
|
function stripCommitMessageArgs(command) {
|
|
23396
22435
|
return command.replace(/(?:^|\s)(?:-m|--message|-F|--file)(?:=|\s+)("[^"]*"|'[^']*')/g, " ");
|
|
23397
22436
|
}
|
|
23398
|
-
function
|
|
22437
|
+
function escapeRegex(s) {
|
|
23399
22438
|
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
23400
22439
|
}
|
|
23401
22440
|
function buildGitVcsWriteRegex(mainRoot) {
|
|
23402
|
-
const esc =
|
|
22441
|
+
const esc = escapeRegex(mainRoot);
|
|
23403
22442
|
return new RegExp(`git\\b[^\\n]*(?:-C\\s+|--work-tree[=\\s])${esc}`);
|
|
23404
22443
|
}
|
|
23405
22444
|
var WRITE_TOOLS = new Set(["write", "edit", "ast_edit"]);
|
|
@@ -23514,7 +22553,7 @@ function commandContainsMainRoot(command, mainRoot) {
|
|
|
23514
22553
|
const prefix = mainRoot.endsWith("/") ? mainRoot : mainRoot + "/";
|
|
23515
22554
|
if (command.includes(prefix))
|
|
23516
22555
|
return true;
|
|
23517
|
-
const re = new RegExp(`${
|
|
22556
|
+
const re = new RegExp(`${escapeRegex(mainRoot)}(?=[\\s'"\`)]|$)`);
|
|
23518
22557
|
return re.test(command);
|
|
23519
22558
|
}
|
|
23520
22559
|
function commandContainsMainRootExcludingWorktree(command, mainRoot, worktreePath) {
|
|
@@ -23530,7 +22569,7 @@ function commandContainsMainRootExcludingWorktree(command, mainRoot, worktreePat
|
|
|
23530
22569
|
}
|
|
23531
22570
|
const wtRoot = worktreesRoot(mainRoot);
|
|
23532
22571
|
const wtRootPrefix = wtRoot + path28.sep;
|
|
23533
|
-
const escapedWtRootPrefix =
|
|
22572
|
+
const escapedWtRootPrefix = escapeRegex(wtRootPrefix);
|
|
23534
22573
|
const wtPathPattern = escapedWtRootPrefix + `[^\\s'"\\x60)]*`;
|
|
23535
22574
|
const allWorktreePathsReForEscape = new RegExp(wtPathPattern, "g");
|
|
23536
22575
|
const allWorktreePathsReForReplace = new RegExp(wtPathPattern, "g");
|
|
@@ -23589,7 +22628,7 @@ function collectWritePaths(toolName, argsObj, worktreeRoot) {
|
|
|
23589
22628
|
out.push(rel);
|
|
23590
22629
|
return out;
|
|
23591
22630
|
}
|
|
23592
|
-
var
|
|
22631
|
+
var log13 = makePluginLogger(PLUGIN_NAME22);
|
|
23593
22632
|
function resolveMainRoot2(rawDir) {
|
|
23594
22633
|
const worktreeMarker = "/.git/codeforge-worktrees/";
|
|
23595
22634
|
const idx = rawDir.indexOf(worktreeMarker);
|
|
@@ -23601,14 +22640,14 @@ function resolveMainRoot2(rawDir) {
|
|
|
23601
22640
|
var sessionWorktreeGuardPlugin = async (ctx) => {
|
|
23602
22641
|
const disableEnv = process.env["CODEFORGE_DISABLE_WORKTREE_GUARD"];
|
|
23603
22642
|
if (disableEnv === "1" || disableEnv === "true" || disableEnv === "yes") {
|
|
23604
|
-
|
|
23605
|
-
safeWriteLog(
|
|
22643
|
+
log13.warn("[guard] CODEFORGE_DISABLE_WORKTREE_GUARD 已启用,session-worktree-guard 全部 hook 跳过;" + "本次 opencode 会话所有写操作将直接落到主工作区(失去隔离保护)", { env: disableEnv });
|
|
22644
|
+
safeWriteLog(PLUGIN_NAME22, {
|
|
23606
22645
|
hook: "activate",
|
|
23607
22646
|
action: "skip",
|
|
23608
22647
|
source: "disable-env",
|
|
23609
22648
|
env_value: disableEnv
|
|
23610
22649
|
});
|
|
23611
|
-
logLifecycle(
|
|
22650
|
+
logLifecycle(PLUGIN_NAME22, "activate", { disabled_by_env: true });
|
|
23612
22651
|
return {};
|
|
23613
22652
|
}
|
|
23614
22653
|
const mainRoot = resolveMainRoot2(ctx.directory ?? process.cwd());
|
|
@@ -23616,13 +22655,13 @@ var sessionWorktreeGuardPlugin = async (ctx) => {
|
|
|
23616
22655
|
try {
|
|
23617
22656
|
policyCfg = await loadPolicy(mainRoot);
|
|
23618
22657
|
} catch (err) {
|
|
23619
|
-
|
|
22658
|
+
log13.warn("loadPolicy failed (class E skipped)", {
|
|
23620
22659
|
mainRoot,
|
|
23621
22660
|
error: err instanceof Error ? err.message : String(err)
|
|
23622
22661
|
});
|
|
23623
22662
|
}
|
|
23624
22663
|
const perAgentEnabled = !!policyCfg.per_agent && Object.keys(policyCfg.per_agent).length > 0;
|
|
23625
|
-
logLifecycle(
|
|
22664
|
+
logLifecycle(PLUGIN_NAME22, "activate", {
|
|
23626
22665
|
mainRoot,
|
|
23627
22666
|
CODEFORGE_SESSION_ID: process.env["CODEFORGE_SESSION_ID"] ?? "(not set)"
|
|
23628
22667
|
});
|
|
@@ -23633,12 +22672,12 @@ var sessionWorktreeGuardPlugin = async (ctx) => {
|
|
|
23633
22672
|
const isWrite = isWriteOperation(input.tool, output.args ?? {}, mainRoot);
|
|
23634
22673
|
if (isWrite && !_sessionIdMissingWarned) {
|
|
23635
22674
|
_sessionIdMissingWarned = true;
|
|
23636
|
-
|
|
22675
|
+
log13.warn("[guard] sessionID 缺失,无法绑定 worktree;本会话所有写操作将落到主工作区(仅本进程内 warn 一次)。" + "排查:grep no-session-id ~/.cache/codeforge/plugins.log", {
|
|
23637
22676
|
tool: input.tool,
|
|
23638
22677
|
opencode_version_hint: "需 opencode >= 0.x 才会在 tool.execute.before 注入 input.sessionID"
|
|
23639
22678
|
});
|
|
23640
22679
|
}
|
|
23641
|
-
safeWriteLog(
|
|
22680
|
+
safeWriteLog(PLUGIN_NAME22, {
|
|
23642
22681
|
hook: "tool.execute.before",
|
|
23643
22682
|
tool: input.tool,
|
|
23644
22683
|
action: "skip",
|
|
@@ -23648,14 +22687,14 @@ var sessionWorktreeGuardPlugin = async (ctx) => {
|
|
|
23648
22687
|
return;
|
|
23649
22688
|
}
|
|
23650
22689
|
let denied;
|
|
23651
|
-
await safeAsync(
|
|
22690
|
+
await safeAsync(PLUGIN_NAME22, "tool.execute.before", async () => {
|
|
23652
22691
|
const toolName = input.tool;
|
|
23653
22692
|
const argsObj = output.args ?? {};
|
|
23654
22693
|
let entry = null;
|
|
23655
22694
|
try {
|
|
23656
22695
|
entry = await getSessionWorktree(sessionId, mainRoot);
|
|
23657
22696
|
} catch (err) {
|
|
23658
|
-
|
|
22697
|
+
log13.warn(`getSessionWorktree failed (跳过本次检查)`, {
|
|
23659
22698
|
sessionId,
|
|
23660
22699
|
mainRoot,
|
|
23661
22700
|
error: err instanceof Error ? err.message : String(err)
|
|
@@ -23672,8 +22711,8 @@ var sessionWorktreeGuardPlugin = async (ctx) => {
|
|
|
23672
22711
|
const parentEntry = await getSessionWorktree(parentId, mainRoot);
|
|
23673
22712
|
if (parentEntry && parentEntry.status === "active") {
|
|
23674
22713
|
entry = parentEntry;
|
|
23675
|
-
|
|
23676
|
-
safeWriteLog(
|
|
22714
|
+
log13.debug?.(`[child-inherit] session ${sessionId} 继承父 ${parentId} 的 worktree`, { parentSessionId: parentId, worktreePath: parentEntry.worktreePath });
|
|
22715
|
+
safeWriteLog(PLUGIN_NAME22, {
|
|
23677
22716
|
hook: "tool.execute.before",
|
|
23678
22717
|
tool: toolName,
|
|
23679
22718
|
sessionID: input.sessionID,
|
|
@@ -23685,14 +22724,14 @@ var sessionWorktreeGuardPlugin = async (ctx) => {
|
|
|
23685
22724
|
}
|
|
23686
22725
|
}
|
|
23687
22726
|
} catch (lookupErr) {
|
|
23688
|
-
|
|
22727
|
+
log13.debug?.("[child-inherit] lookupParentSessionId 抛错(已隔离,退回 lazy-bind)", { error: lookupErr instanceof Error ? lookupErr.message : String(lookupErr) });
|
|
23689
22728
|
}
|
|
23690
22729
|
}
|
|
23691
22730
|
if (!entry) {
|
|
23692
22731
|
try {
|
|
23693
22732
|
entry = await bindSessionWorktree({ sessionId, mainRoot });
|
|
23694
|
-
|
|
23695
|
-
safeWriteLog(
|
|
22733
|
+
log13.info(`[lazy-bind] auto-created worktree for session ${sessionId}`, { branch: entry.branch, path: entry.worktreePath });
|
|
22734
|
+
safeWriteLog(PLUGIN_NAME22, {
|
|
23696
22735
|
hook: "tool.execute.before",
|
|
23697
22736
|
tool: toolName,
|
|
23698
22737
|
sessionID: input.sessionID,
|
|
@@ -23711,13 +22750,13 @@ var sessionWorktreeGuardPlugin = async (ctx) => {
|
|
|
23711
22750
|
alreadyNotified
|
|
23712
22751
|
});
|
|
23713
22752
|
_bindFailNotified.add(sessionId);
|
|
23714
|
-
|
|
22753
|
+
log13.warn(`[lazy-bind] DENY (bind failed)`, {
|
|
23715
22754
|
sessionId,
|
|
23716
22755
|
tool: toolName,
|
|
23717
22756
|
error: errMsg,
|
|
23718
22757
|
throttled: alreadyNotified
|
|
23719
22758
|
});
|
|
23720
|
-
safeWriteLog(
|
|
22759
|
+
safeWriteLog(PLUGIN_NAME22, {
|
|
23721
22760
|
hook: "tool.execute.before",
|
|
23722
22761
|
tool: toolName,
|
|
23723
22762
|
sessionID: input.sessionID,
|
|
@@ -23740,7 +22779,7 @@ var sessionWorktreeGuardPlugin = async (ctx) => {
|
|
|
23740
22779
|
sessionId: entry.sessionId,
|
|
23741
22780
|
mainRoot
|
|
23742
22781
|
}).catch((err) => {
|
|
23743
|
-
|
|
22782
|
+
log13.warn("touchEntryUpdatedAt 失败 (已忽略)", {
|
|
23744
22783
|
sessionId: entry?.sessionId,
|
|
23745
22784
|
error: err instanceof Error ? err.message : String(err)
|
|
23746
22785
|
});
|
|
@@ -23753,13 +22792,13 @@ var sessionWorktreeGuardPlugin = async (ctx) => {
|
|
|
23753
22792
|
const caller = input.agent;
|
|
23754
22793
|
if (caller !== undefined && caller !== "codeforge") {
|
|
23755
22794
|
const reason = `[session-worktree-guard] DENIED: session_merge action=merge 仅 codeforge orchestrator 或用户可调;当前 caller=${caller}`;
|
|
23756
|
-
|
|
22795
|
+
log13.warn(reason, {
|
|
23757
22796
|
sessionId,
|
|
23758
22797
|
tool: toolName,
|
|
23759
22798
|
action,
|
|
23760
22799
|
caller
|
|
23761
22800
|
});
|
|
23762
|
-
safeWriteLog(
|
|
22801
|
+
safeWriteLog(PLUGIN_NAME22, {
|
|
23763
22802
|
hook: "tool.execute.before",
|
|
23764
22803
|
tool: toolName,
|
|
23765
22804
|
sessionID: input.sessionID,
|
|
@@ -23774,15 +22813,15 @@ var sessionWorktreeGuardPlugin = async (ctx) => {
|
|
|
23774
22813
|
}
|
|
23775
22814
|
}
|
|
23776
22815
|
if (perAgentEnabled && isWriteOperation(toolName, argsObj, mainRoot)) {
|
|
23777
|
-
const caller = await resolveAgentForGuard({ sessionID: input.sessionID, agent: input.agent }, ctx.client,
|
|
22816
|
+
const caller = await resolveAgentForGuard({ sessionID: input.sessionID, agent: input.agent }, ctx.client, log13);
|
|
23778
22817
|
if (caller === null) {
|
|
23779
22818
|
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
|
-
|
|
22819
|
+
log13.warn(reason, {
|
|
23781
22820
|
sessionId,
|
|
23782
22821
|
tool: toolName,
|
|
23783
22822
|
source: "per-agent-acl-fail-closed"
|
|
23784
22823
|
});
|
|
23785
|
-
safeWriteLog(
|
|
22824
|
+
safeWriteLog(PLUGIN_NAME22, {
|
|
23786
22825
|
hook: "tool.execute.before",
|
|
23787
22826
|
tool: toolName,
|
|
23788
22827
|
sessionID: input.sessionID,
|
|
@@ -23800,14 +22839,14 @@ var sessionWorktreeGuardPlugin = async (ctx) => {
|
|
|
23800
22839
|
const dec = checkFileAccess(agentAcl, relPath, "write");
|
|
23801
22840
|
if (dec.action === "deny") {
|
|
23802
22841
|
const reason = `[session-worktree-guard] DENIED: agent='${caller}' 写路径不在 ACL 白名单 — ` + `${relPath} (${dec.reason})`;
|
|
23803
|
-
|
|
22842
|
+
log13.warn(reason, {
|
|
23804
22843
|
sessionId,
|
|
23805
22844
|
tool: toolName,
|
|
23806
22845
|
caller,
|
|
23807
22846
|
path: relPath,
|
|
23808
22847
|
acl_decision: dec
|
|
23809
22848
|
});
|
|
23810
|
-
safeWriteLog(
|
|
22849
|
+
safeWriteLog(PLUGIN_NAME22, {
|
|
23811
22850
|
hook: "tool.execute.before",
|
|
23812
22851
|
tool: toolName,
|
|
23813
22852
|
sessionID: input.sessionID,
|
|
@@ -23845,13 +22884,13 @@ var sessionWorktreeGuardPlugin = async (ctx) => {
|
|
|
23845
22884
|
const reasonBase = `[session-worktree-guard] DENIED: 当前 session 要求先调用 plan_read(plan_id="${entry.requiredPlanId}") 再执行写操作`;
|
|
23846
22885
|
const reason = inherited ? `${reasonBase}
|
|
23847
22886
|
[gate-deny] child session=${sessionId} 继承父 entry 但父 session planReadOk=false,父 session=${entry.sessionId} 需先调 plan_read(plan_id="${entry.requiredPlanId}")` : reasonBase;
|
|
23848
|
-
|
|
22887
|
+
log13.warn(reason, {
|
|
23849
22888
|
tool: toolName,
|
|
23850
22889
|
sessionId,
|
|
23851
22890
|
requiredPlanId: entry.requiredPlanId,
|
|
23852
22891
|
inheritedFromParent: inherited ? entry.sessionId : undefined
|
|
23853
22892
|
});
|
|
23854
|
-
safeWriteLog(
|
|
22893
|
+
safeWriteLog(PLUGIN_NAME22, {
|
|
23855
22894
|
hook: "tool.execute.before",
|
|
23856
22895
|
tool: toolName,
|
|
23857
22896
|
sessionID: input.sessionID,
|
|
@@ -23866,10 +22905,10 @@ var sessionWorktreeGuardPlugin = async (ctx) => {
|
|
|
23866
22905
|
if (toolName === "bash") {
|
|
23867
22906
|
const command = argsObj["command"];
|
|
23868
22907
|
if (typeof command === "string" && commandContainsMainRootExcludingWorktree(command, mainRoot, worktreePath) && detectBashWriteIntent(command, mainRoot)) {
|
|
23869
|
-
const caller = await resolveAgentForGuard({ sessionID: input.sessionID, agent: input.agent }, ctx.client,
|
|
22908
|
+
const caller = await resolveAgentForGuard({ sessionID: input.sessionID, agent: input.agent }, ctx.client, log13);
|
|
23870
22909
|
if (caller !== null && CLASS_B_CALLER_WHITELIST.has(caller)) {
|
|
23871
|
-
|
|
23872
|
-
safeWriteLog(
|
|
22910
|
+
log13.debug?.(`[class-b-whitelist] allow caller=${caller}`, { sessionId, tool: toolName, command: command.slice(0, 200) });
|
|
22911
|
+
safeWriteLog(PLUGIN_NAME22, {
|
|
23873
22912
|
hook: "tool.execute.before",
|
|
23874
22913
|
tool: toolName,
|
|
23875
22914
|
sessionID: input.sessionID,
|
|
@@ -23882,8 +22921,8 @@ var sessionWorktreeGuardPlugin = async (ctx) => {
|
|
|
23882
22921
|
const callerTag = caller === null ? "unresolved" : caller;
|
|
23883
22922
|
const snippet = command.length > 60 ? command.slice(0, 60) + "…" : command;
|
|
23884
22923
|
const reason = `[session-worktree-guard] DENIED: bash.command 含主仓绝对路径写操作 (${snippet}) [caller=${callerTag}],请在当前 session worktree (${worktreePath}) 内操作`;
|
|
23885
|
-
|
|
23886
|
-
safeWriteLog(
|
|
22924
|
+
log13.warn(reason, { sessionId, caller: callerTag, command: command.slice(0, 200) });
|
|
22925
|
+
safeWriteLog(PLUGIN_NAME22, {
|
|
23887
22926
|
hook: "tool.execute.before",
|
|
23888
22927
|
tool: toolName,
|
|
23889
22928
|
sessionID: input.sessionID,
|
|
@@ -23902,8 +22941,8 @@ var sessionWorktreeGuardPlugin = async (ctx) => {
|
|
|
23902
22941
|
if (typeof filePath === "string") {
|
|
23903
22942
|
const newPath = rewritePath(filePath, mainRoot, worktreePath);
|
|
23904
22943
|
if (newPath !== null) {
|
|
23905
|
-
|
|
23906
|
-
safeWriteLog(
|
|
22944
|
+
log13.info(`rewrote ${toolName}.filePath: ${filePath} → ${newPath}`);
|
|
22945
|
+
safeWriteLog(PLUGIN_NAME22, {
|
|
23907
22946
|
hook: "tool.execute.before",
|
|
23908
22947
|
tool: toolName,
|
|
23909
22948
|
field: "filePath",
|
|
@@ -23920,8 +22959,8 @@ var sessionWorktreeGuardPlugin = async (ctx) => {
|
|
|
23920
22959
|
if (typeof target === "string") {
|
|
23921
22960
|
const newTarget = rewritePath(target, mainRoot, worktreePath);
|
|
23922
22961
|
if (newTarget !== null) {
|
|
23923
|
-
|
|
23924
|
-
safeWriteLog(
|
|
22962
|
+
log13.info(`rewrote ast_edit.target: ${target} → ${newTarget}`);
|
|
22963
|
+
safeWriteLog(PLUGIN_NAME22, {
|
|
23925
22964
|
hook: "tool.execute.before",
|
|
23926
22965
|
tool: toolName,
|
|
23927
22966
|
field: "target",
|
|
@@ -23936,8 +22975,8 @@ var sessionWorktreeGuardPlugin = async (ctx) => {
|
|
|
23936
22975
|
if (typeof root === "string") {
|
|
23937
22976
|
const newRoot = rewritePath(root, mainRoot, worktreePath);
|
|
23938
22977
|
if (newRoot !== null) {
|
|
23939
|
-
|
|
23940
|
-
safeWriteLog(
|
|
22978
|
+
log13.info(`rewrote ast_edit.root: ${root} → ${newRoot}`);
|
|
22979
|
+
safeWriteLog(PLUGIN_NAME22, {
|
|
23941
22980
|
hook: "tool.execute.before",
|
|
23942
22981
|
tool: toolName,
|
|
23943
22982
|
field: "root",
|
|
@@ -23954,8 +22993,8 @@ var sessionWorktreeGuardPlugin = async (ctx) => {
|
|
|
23954
22993
|
if (typeof workdir === "string") {
|
|
23955
22994
|
const newWorkdir = rewritePath(workdir, mainRoot, worktreePath);
|
|
23956
22995
|
if (newWorkdir !== null) {
|
|
23957
|
-
|
|
23958
|
-
safeWriteLog(
|
|
22996
|
+
log13.info(`rewrote bash.workdir: ${workdir} → ${newWorkdir}`);
|
|
22997
|
+
safeWriteLog(PLUGIN_NAME22, {
|
|
23959
22998
|
hook: "tool.execute.before",
|
|
23960
22999
|
tool: toolName,
|
|
23961
23000
|
field: "workdir",
|
|
@@ -23973,7 +23012,7 @@ var sessionWorktreeGuardPlugin = async (ctx) => {
|
|
|
23973
23012
|
}
|
|
23974
23013
|
};
|
|
23975
23014
|
};
|
|
23976
|
-
var
|
|
23015
|
+
var handler22 = sessionWorktreeGuardPlugin;
|
|
23977
23016
|
|
|
23978
23017
|
// lib/opencode-session-probe.ts
|
|
23979
23018
|
import * as path29 from "node:path";
|
|
@@ -24073,8 +23112,8 @@ function createSessionProbe(opts = {}) {
|
|
|
24073
23112
|
}
|
|
24074
23113
|
|
|
24075
23114
|
// plugins/worktree-lifecycle.ts
|
|
24076
|
-
var
|
|
24077
|
-
logLifecycle(
|
|
23115
|
+
var PLUGIN_NAME23 = "worktree-lifecycle";
|
|
23116
|
+
logLifecycle(PLUGIN_NAME23, "import", {});
|
|
24078
23117
|
var IDLE_TOAST_THROTTLE_MS = 60 * 60000;
|
|
24079
23118
|
var IDLE_TOAST_DURATION_MS = 8000;
|
|
24080
23119
|
var IDLE_TOAST_REMINDER_INTERVAL_MS = 30 * 60000;
|
|
@@ -24083,10 +23122,10 @@ var lastIdleToastAt = new Map;
|
|
|
24083
23122
|
var pruneRunning = false;
|
|
24084
23123
|
var _pruneTimer;
|
|
24085
23124
|
var _probe = null;
|
|
24086
|
-
var
|
|
23125
|
+
var log14 = makePluginLogger(PLUGIN_NAME23);
|
|
24087
23126
|
var worktreeLifecyclePlugin = async (ctx) => {
|
|
24088
23127
|
const mainRoot = ctx.directory;
|
|
24089
|
-
logLifecycle(
|
|
23128
|
+
logLifecycle(PLUGIN_NAME23, "activate", {
|
|
24090
23129
|
mainRoot: mainRoot ?? "(not set)",
|
|
24091
23130
|
idle_threshold_ms: IDLE_TOAST_THROTTLE_MS
|
|
24092
23131
|
});
|
|
@@ -24101,13 +23140,13 @@ var worktreeLifecyclePlugin = async (ctx) => {
|
|
|
24101
23140
|
}
|
|
24102
23141
|
_probe = createSessionProbe();
|
|
24103
23142
|
setImmediate(() => {
|
|
24104
|
-
safeAsync(
|
|
23143
|
+
safeAsync(PLUGIN_NAME23, "activate.pruneOrphan", async () => {
|
|
24105
23144
|
const result = await pruneOrphanWorktrees(mainRoot, {
|
|
24106
23145
|
isSessionAlive: _probe.isSessionAlive
|
|
24107
23146
|
});
|
|
24108
23147
|
if (result.cleaned.length > 0 || result.failed.length > 0) {
|
|
24109
|
-
|
|
24110
|
-
safeWriteLog(
|
|
23148
|
+
log14.info(`[pruneOrphan] cleaned=${result.cleaned.length} failed=${result.failed.length} skipped=${result.skipped}`);
|
|
23149
|
+
safeWriteLog(PLUGIN_NAME23, {
|
|
24111
23150
|
hook: "activate.pruneOrphan",
|
|
24112
23151
|
cleaned: result.cleaned,
|
|
24113
23152
|
failed: result.failed,
|
|
@@ -24120,7 +23159,7 @@ var worktreeLifecyclePlugin = async (ctx) => {
|
|
|
24120
23159
|
clearInterval(_pruneTimer);
|
|
24121
23160
|
_pruneTimer = setInterval(() => {
|
|
24122
23161
|
if (pruneRunning) {
|
|
24123
|
-
safeWriteLog(
|
|
23162
|
+
safeWriteLog(PLUGIN_NAME23, {
|
|
24124
23163
|
hook: "interval.pruneOrphan",
|
|
24125
23164
|
action: "skip",
|
|
24126
23165
|
reason: "previous prune still running"
|
|
@@ -24128,14 +23167,14 @@ var worktreeLifecyclePlugin = async (ctx) => {
|
|
|
24128
23167
|
return;
|
|
24129
23168
|
}
|
|
24130
23169
|
pruneRunning = true;
|
|
24131
|
-
safeAsync(
|
|
23170
|
+
safeAsync(PLUGIN_NAME23, "interval.pruneOrphan", async () => {
|
|
24132
23171
|
try {
|
|
24133
23172
|
const result = await pruneOrphanWorktrees(mainRoot, {
|
|
24134
23173
|
isSessionAlive: _probe.isSessionAlive
|
|
24135
23174
|
});
|
|
24136
23175
|
if (result.cleaned.length > 0 || result.failed.length > 0) {
|
|
24137
|
-
|
|
24138
|
-
safeWriteLog(
|
|
23176
|
+
log14.info(`[pruneOrphan interval] cleaned=${result.cleaned.length} failed=${result.failed.length} skipped=${result.skipped}`);
|
|
23177
|
+
safeWriteLog(PLUGIN_NAME23, {
|
|
24139
23178
|
hook: "interval.pruneOrphan",
|
|
24140
23179
|
cleaned: result.cleaned,
|
|
24141
23180
|
failed: result.failed,
|
|
@@ -24150,14 +23189,14 @@ var worktreeLifecyclePlugin = async (ctx) => {
|
|
|
24150
23189
|
_pruneTimer.unref();
|
|
24151
23190
|
return {
|
|
24152
23191
|
event: async ({ event }) => {
|
|
24153
|
-
await safeAsync(
|
|
23192
|
+
await safeAsync(PLUGIN_NAME23, "event", async () => {
|
|
24154
23193
|
const ended = extractEndedSessionID(event);
|
|
24155
23194
|
if (!ended)
|
|
24156
23195
|
return;
|
|
24157
23196
|
if (ended.type === "session.deleted") {
|
|
24158
23197
|
const entry = await getSessionWorktree(ended.sessionID, mainRoot);
|
|
24159
23198
|
if (!entry || entry.status !== "active") {
|
|
24160
|
-
safeWriteLog(
|
|
23199
|
+
safeWriteLog(PLUGIN_NAME23, {
|
|
24161
23200
|
hook: "event",
|
|
24162
23201
|
type: ended.type,
|
|
24163
23202
|
sessionID: ended.sessionID,
|
|
@@ -24172,7 +23211,7 @@ var worktreeLifecyclePlugin = async (ctx) => {
|
|
|
24172
23211
|
const fastDirty = await isWorktreeDirty(entry.worktreePath);
|
|
24173
23212
|
if (!fastDirty) {
|
|
24174
23213
|
await discardSession({ sessionId: ended.sessionID, mainRoot });
|
|
24175
|
-
safeWriteLog(
|
|
23214
|
+
safeWriteLog(PLUGIN_NAME23, {
|
|
24176
23215
|
hook: "event",
|
|
24177
23216
|
type: ended.type,
|
|
24178
23217
|
sessionID: ended.sessionID,
|
|
@@ -24185,7 +23224,7 @@ var worktreeLifecyclePlugin = async (ctx) => {
|
|
|
24185
23224
|
}
|
|
24186
23225
|
}
|
|
24187
23226
|
} catch (err) {
|
|
24188
|
-
|
|
23227
|
+
log14.warn(`[lifecycle] empty-worktree fast-path 检测失败 (回退到常规路径)`, {
|
|
24189
23228
|
sessionId: ended.sessionID,
|
|
24190
23229
|
error: err instanceof Error ? err.message : String(err)
|
|
24191
23230
|
});
|
|
@@ -24200,14 +23239,14 @@ var worktreeLifecyclePlugin = async (ctx) => {
|
|
|
24200
23239
|
});
|
|
24201
23240
|
}
|
|
24202
23241
|
} catch (err) {
|
|
24203
|
-
|
|
23242
|
+
log14.warn(`[lifecycle] checkpointCommit failed (继续 discard)`, {
|
|
24204
23243
|
sessionId: ended.sessionID,
|
|
24205
23244
|
error: err instanceof Error ? err.message : String(err)
|
|
24206
23245
|
});
|
|
24207
23246
|
}
|
|
24208
23247
|
try {
|
|
24209
23248
|
await discardSession({ sessionId: ended.sessionID, mainRoot });
|
|
24210
|
-
safeWriteLog(
|
|
23249
|
+
safeWriteLog(PLUGIN_NAME23, {
|
|
24211
23250
|
hook: "event",
|
|
24212
23251
|
type: ended.type,
|
|
24213
23252
|
sessionID: ended.sessionID,
|
|
@@ -24217,7 +23256,7 @@ var worktreeLifecyclePlugin = async (ctx) => {
|
|
|
24217
23256
|
worktreePath: entry.worktreePath
|
|
24218
23257
|
});
|
|
24219
23258
|
} catch (err) {
|
|
24220
|
-
|
|
23259
|
+
log14.warn(`[lifecycle] discardSession failed`, {
|
|
24221
23260
|
sessionId: ended.sessionID,
|
|
24222
23261
|
error: err instanceof Error ? err.message : String(err)
|
|
24223
23262
|
});
|
|
@@ -24240,8 +23279,8 @@ var worktreeLifecyclePlugin = async (ctx) => {
|
|
|
24240
23279
|
lastIdleToastAt.set(ended.sessionID, now);
|
|
24241
23280
|
const idleMin = Math.round(idleMs / 60000);
|
|
24242
23281
|
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(
|
|
23282
|
+
const sent = await showToast2(client, { message: msg, variant: "default", duration: IDLE_TOAST_DURATION_MS, title: "CodeForge" }, log14);
|
|
23283
|
+
safeWriteLog(PLUGIN_NAME23, {
|
|
24245
23284
|
hook: "event",
|
|
24246
23285
|
type: ended.type,
|
|
24247
23286
|
sessionID: ended.sessionID,
|
|
@@ -24254,39 +23293,36 @@ var worktreeLifecyclePlugin = async (ctx) => {
|
|
|
24254
23293
|
}
|
|
24255
23294
|
};
|
|
24256
23295
|
};
|
|
24257
|
-
var
|
|
23296
|
+
var handler23 = worktreeLifecyclePlugin;
|
|
24258
23297
|
|
|
24259
23298
|
// src/index.ts
|
|
24260
23299
|
var PLUGIN_ID = "codeforge";
|
|
24261
|
-
var
|
|
23300
|
+
var log15 = makePluginLogger(PLUGIN_ID);
|
|
24262
23301
|
logLifecycle(PLUGIN_ID, "import", { entry: "src/index.ts" });
|
|
24263
23302
|
var HANDLERS = [
|
|
24264
23303
|
{ name: "agent-router", init: handler },
|
|
24265
23304
|
{ name: "arena-orchestrator", init: handler2 },
|
|
24266
23305
|
{ 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 }
|
|
23306
|
+
{ name: "channels", init: handler4 },
|
|
23307
|
+
{ name: "chat-agent-cache", init: handler5 },
|
|
23308
|
+
{ name: "codeforge-tools", init: handler7 },
|
|
23309
|
+
{ name: "discover-spec-suggest", init: handler8 },
|
|
23310
|
+
{ name: "memories-context", init: handler9 },
|
|
23311
|
+
{ name: "model-fallback", init: handler10 },
|
|
23312
|
+
{ name: "parallel-tool-nudge", init: handler13 },
|
|
23313
|
+
{ name: "pwsh-utf8", init: handler14 },
|
|
23314
|
+
{ name: "session-recovery", init: handler15 },
|
|
23315
|
+
{ name: "subtask-heartbeat", init: handler11 },
|
|
23316
|
+
{ name: "subtasks", init: handler16 },
|
|
23317
|
+
{ name: "parallel-status", init: handler12 },
|
|
23318
|
+
{ name: "terminal-monitor", init: handler17 },
|
|
23319
|
+
{ name: "token-manager", init: handler18 },
|
|
23320
|
+
{ name: "tool-heartbeat", init: handler6 },
|
|
23321
|
+
{ name: "tool-policy", init: handler19 },
|
|
23322
|
+
{ name: "update-checker", init: handler20 },
|
|
23323
|
+
{ name: "workflow-engine", init: handler21 },
|
|
23324
|
+
{ name: "session-worktree-guard", init: handler22 },
|
|
23325
|
+
{ name: "worktree-lifecycle", init: handler23 }
|
|
24290
23326
|
];
|
|
24291
23327
|
function makeSerialHook(hookName, fns) {
|
|
24292
23328
|
return async (input, output) => {
|
|
@@ -24296,7 +23332,7 @@ function makeSerialHook(hookName, fns) {
|
|
|
24296
23332
|
} catch (err) {
|
|
24297
23333
|
if (isDeniedError(err))
|
|
24298
23334
|
throw err;
|
|
24299
|
-
|
|
23335
|
+
log15.warn(`[${PLUGIN_ID}] ${hookName} handler 异常(已隔离)`, {
|
|
24300
23336
|
error: err instanceof Error ? err.message : String(err)
|
|
24301
23337
|
});
|
|
24302
23338
|
}
|
|
@@ -24309,7 +23345,7 @@ function createCodeforgeServer(opts) {
|
|
|
24309
23345
|
const yieldResult = shouldYieldToLocalPlugin({ directory: input.directory });
|
|
24310
23346
|
if (yieldResult.yield) {
|
|
24311
23347
|
const msg = formatYieldLog(yieldResult);
|
|
24312
|
-
|
|
23348
|
+
log15.info(msg, { reason: yieldResult.reason, markerPath: yieldResult.markerPath });
|
|
24313
23349
|
logLifecycle(PLUGIN_ID, "activate", {
|
|
24314
23350
|
yield_to_local: true,
|
|
24315
23351
|
yield_reason: yieldResult.reason,
|
|
@@ -24327,7 +23363,7 @@ function createCodeforgeServer(opts) {
|
|
|
24327
23363
|
if (r.status === "fulfilled" && r.value && typeof r.value === "object") {
|
|
24328
23364
|
hooksList.push(r.value);
|
|
24329
23365
|
} else if (r.status === "rejected") {
|
|
24330
|
-
|
|
23366
|
+
log15.warn(`[${PLUGIN_ID}] handler ${HANDLERS[i].name} init failed (隔离,其他 handler 继续)`, { error: r.reason instanceof Error ? r.reason.message : String(r.reason) });
|
|
24331
23367
|
}
|
|
24332
23368
|
});
|
|
24333
23369
|
logLifecycle(PLUGIN_ID, "activate", {
|
|
@@ -24382,7 +23418,7 @@ function createCodeforgeServer(opts) {
|
|
|
24382
23418
|
} catch (err) {
|
|
24383
23419
|
if (isDeniedError(err))
|
|
24384
23420
|
throw err;
|
|
24385
|
-
|
|
23421
|
+
log15.warn(`[${PLUGIN_ID}] event handler 异常(已隔离)`, {
|
|
24386
23422
|
error: err instanceof Error ? err.message : String(err)
|
|
24387
23423
|
});
|
|
24388
23424
|
}
|