@andyqiu/codeforge 0.3.9 → 0.3.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +176 -34
- package/agents/codeforge.md +39 -84
- package/agents/coder-deep.md +93 -0
- package/agents/coder-quick.md +92 -0
- package/agents/coder.md +18 -54
- package/agents/planner.md +13 -47
- package/agents/reviewer.md +9 -59
- package/codeforge.json +28 -2
- package/commands/deep.md +87 -0
- package/commands/quick.md +92 -0
- package/dist/index.js +1173 -315
- package/install.sh +8 -0
- package/package.json +1 -1
- package/schemas/codeforge.schema.json +230 -224
- package/scripts/sync-agent-models.mjs +22 -3
package/dist/index.js
CHANGED
|
@@ -9007,7 +9007,7 @@ var handler5 = autoLearningServer;
|
|
|
9007
9007
|
|
|
9008
9008
|
// plugins/channels.ts
|
|
9009
9009
|
init_opencode_plugin_helpers();
|
|
9010
|
-
import { promises as fs2 } from "node:fs";
|
|
9010
|
+
import { promises as fs2, statSync as statSync2 } from "node:fs";
|
|
9011
9011
|
import * as path4 from "node:path";
|
|
9012
9012
|
|
|
9013
9013
|
// lib/channels.ts
|
|
@@ -9285,6 +9285,13 @@ function transformSlackToWebhook(ch, ev) {
|
|
|
9285
9285
|
text: { type: "plain_text", text: titleText.slice(0, 150), emoji: true }
|
|
9286
9286
|
}
|
|
9287
9287
|
];
|
|
9288
|
+
const sevLabel = ch.show_severity_label !== false ? severityToLabel(ev.severity) : "";
|
|
9289
|
+
if (sevLabel) {
|
|
9290
|
+
blocks.push({
|
|
9291
|
+
type: "section",
|
|
9292
|
+
text: { type: "mrkdwn", text: sevLabel }
|
|
9293
|
+
});
|
|
9294
|
+
}
|
|
9288
9295
|
if (message.rendered.trim()) {
|
|
9289
9296
|
blocks.push({
|
|
9290
9297
|
type: "section",
|
|
@@ -9297,6 +9304,56 @@ function transformSlackToWebhook(ch, ev) {
|
|
|
9297
9304
|
elements: [{ type: "mrkdwn", text: mentionText }]
|
|
9298
9305
|
});
|
|
9299
9306
|
}
|
|
9307
|
+
const showFields = ch.show_fields ?? [...DEFAULT_SHOW_FIELDS];
|
|
9308
|
+
const labels = { ...DEFAULT_FIELD_LABELS, ...ch.field_labels ?? {} };
|
|
9309
|
+
const fieldLines = pickFieldLines(ev.data, showFields, labels);
|
|
9310
|
+
if (fieldLines.length > 0) {
|
|
9311
|
+
blocks.push({ type: "divider" });
|
|
9312
|
+
blocks.push({
|
|
9313
|
+
type: "section",
|
|
9314
|
+
text: { type: "mrkdwn", text: fieldLines.join(`
|
|
9315
|
+
`) }
|
|
9316
|
+
});
|
|
9317
|
+
}
|
|
9318
|
+
if (ev.severity !== undefined && ev.severity >= 20) {
|
|
9319
|
+
const errMsg = ev.data?.["error"];
|
|
9320
|
+
const stack = ev.data?.["stack"];
|
|
9321
|
+
if (typeof errMsg === "string" && errMsg) {
|
|
9322
|
+
let errContent = `**❌ Error**: ${errMsg.slice(0, 500)}`;
|
|
9323
|
+
if (typeof stack === "string" && stack) {
|
|
9324
|
+
const stackHead = stack.split(`
|
|
9325
|
+
`).slice(0, 5).join(`
|
|
9326
|
+
`);
|
|
9327
|
+
errContent += "\n```\n" + stackHead.slice(0, 1000) + "\n```";
|
|
9328
|
+
}
|
|
9329
|
+
blocks.push({ type: "divider" });
|
|
9330
|
+
blocks.push({
|
|
9331
|
+
type: "section",
|
|
9332
|
+
text: { type: "mrkdwn", text: errContent }
|
|
9333
|
+
});
|
|
9334
|
+
}
|
|
9335
|
+
}
|
|
9336
|
+
const sessionUrl = ev.data?.["session_url"];
|
|
9337
|
+
const logsUrl = ev.data?.["logs_url"];
|
|
9338
|
+
const slackActions = [];
|
|
9339
|
+
if (isValidCtaUrl(sessionUrl)) {
|
|
9340
|
+
slackActions.push({
|
|
9341
|
+
type: "button",
|
|
9342
|
+
text: { type: "plain_text", text: "查看会话", emoji: true },
|
|
9343
|
+
url: sessionUrl,
|
|
9344
|
+
style: "primary"
|
|
9345
|
+
});
|
|
9346
|
+
}
|
|
9347
|
+
if (isValidCtaUrl(logsUrl)) {
|
|
9348
|
+
slackActions.push({
|
|
9349
|
+
type: "button",
|
|
9350
|
+
text: { type: "plain_text", text: "查看日志", emoji: true },
|
|
9351
|
+
url: logsUrl
|
|
9352
|
+
});
|
|
9353
|
+
}
|
|
9354
|
+
if (slackActions.length > 0) {
|
|
9355
|
+
blocks.push({ type: "actions", elements: slackActions });
|
|
9356
|
+
}
|
|
9300
9357
|
const footerParts = [`event=\`${ev.event}\``];
|
|
9301
9358
|
if (ev.session_id)
|
|
9302
9359
|
footerParts.push(`session=\`${ev.session_id.slice(0, 8)}\``);
|
|
@@ -9387,6 +9444,13 @@ function transformLarkToWebhook(ch, ev) {
|
|
|
9387
9444
|
}).join(" ");
|
|
9388
9445
|
const headerTemplate = severityToLarkHeader(ev.severity);
|
|
9389
9446
|
const elements = [];
|
|
9447
|
+
const sevLabel = ch.show_severity_label !== false ? severityToLabel(ev.severity) : "";
|
|
9448
|
+
if (sevLabel) {
|
|
9449
|
+
elements.push({
|
|
9450
|
+
tag: "div",
|
|
9451
|
+
text: { tag: "lark_md", content: sevLabel }
|
|
9452
|
+
});
|
|
9453
|
+
}
|
|
9390
9454
|
if (messageText || mentionMarkdown) {
|
|
9391
9455
|
const fullMsg = [messageText, mentionMarkdown].filter(Boolean).join(`
|
|
9392
9456
|
|
|
@@ -9396,6 +9460,57 @@ function transformLarkToWebhook(ch, ev) {
|
|
|
9396
9460
|
text: { tag: "lark_md", content: fullMsg.slice(0, 3000) }
|
|
9397
9461
|
});
|
|
9398
9462
|
}
|
|
9463
|
+
const showFields = ch.show_fields ?? [...DEFAULT_SHOW_FIELDS];
|
|
9464
|
+
const labels = { ...DEFAULT_FIELD_LABELS, ...ch.field_labels ?? {} };
|
|
9465
|
+
const fieldLines = pickFieldLines(ev.data, showFields, labels);
|
|
9466
|
+
if (fieldLines.length > 0) {
|
|
9467
|
+
elements.push({ tag: "hr" });
|
|
9468
|
+
elements.push({
|
|
9469
|
+
tag: "div",
|
|
9470
|
+
text: { tag: "lark_md", content: fieldLines.join(`
|
|
9471
|
+
`) }
|
|
9472
|
+
});
|
|
9473
|
+
}
|
|
9474
|
+
if (ev.severity !== undefined && ev.severity >= 20) {
|
|
9475
|
+
const errMsg = ev.data?.["error"];
|
|
9476
|
+
const stack = ev.data?.["stack"];
|
|
9477
|
+
if (typeof errMsg === "string" && errMsg) {
|
|
9478
|
+
let errContent = `**❌ Error**: ${errMsg.slice(0, 500)}`;
|
|
9479
|
+
if (typeof stack === "string" && stack) {
|
|
9480
|
+
const stackHead = stack.split(`
|
|
9481
|
+
`).slice(0, 5).join(`
|
|
9482
|
+
`);
|
|
9483
|
+
errContent += "\n```\n" + stackHead.slice(0, 1000) + "\n```";
|
|
9484
|
+
}
|
|
9485
|
+
elements.push({ tag: "hr" });
|
|
9486
|
+
elements.push({
|
|
9487
|
+
tag: "div",
|
|
9488
|
+
text: { tag: "lark_md", content: errContent }
|
|
9489
|
+
});
|
|
9490
|
+
}
|
|
9491
|
+
}
|
|
9492
|
+
const sessionUrl = ev.data?.["session_url"];
|
|
9493
|
+
const logsUrl = ev.data?.["logs_url"];
|
|
9494
|
+
const larkActions = [];
|
|
9495
|
+
if (isValidCtaUrl(sessionUrl)) {
|
|
9496
|
+
larkActions.push({
|
|
9497
|
+
tag: "button",
|
|
9498
|
+
text: { tag: "plain_text", content: "查看会话" },
|
|
9499
|
+
type: "primary",
|
|
9500
|
+
url: sessionUrl
|
|
9501
|
+
});
|
|
9502
|
+
}
|
|
9503
|
+
if (isValidCtaUrl(logsUrl)) {
|
|
9504
|
+
larkActions.push({
|
|
9505
|
+
tag: "button",
|
|
9506
|
+
text: { tag: "plain_text", content: "查看日志" },
|
|
9507
|
+
type: "default",
|
|
9508
|
+
url: logsUrl
|
|
9509
|
+
});
|
|
9510
|
+
}
|
|
9511
|
+
if (larkActions.length > 0) {
|
|
9512
|
+
elements.push({ tag: "action", actions: larkActions });
|
|
9513
|
+
}
|
|
9399
9514
|
const footer = [
|
|
9400
9515
|
`**event**: \`${ev.event}\``,
|
|
9401
9516
|
ev.session_id ? `**session**: \`${ev.session_id.slice(0, 8)}\`` : null,
|
|
@@ -9431,6 +9546,60 @@ function severityToLarkHeader(sev) {
|
|
|
9431
9546
|
return "blue";
|
|
9432
9547
|
return "grey";
|
|
9433
9548
|
}
|
|
9549
|
+
function severityToLabel(sev) {
|
|
9550
|
+
if (sev === undefined)
|
|
9551
|
+
return "";
|
|
9552
|
+
if (sev >= 40)
|
|
9553
|
+
return "\uD83D\uDEA8 CRITICAL";
|
|
9554
|
+
if (sev >= 30)
|
|
9555
|
+
return "\uD83D\uDD34 ERROR";
|
|
9556
|
+
if (sev >= 20)
|
|
9557
|
+
return "\uD83D\uDFE0 WARN";
|
|
9558
|
+
if (sev >= 10)
|
|
9559
|
+
return "\uD83D\uDD35 INFO";
|
|
9560
|
+
return "";
|
|
9561
|
+
}
|
|
9562
|
+
var DEFAULT_SHOW_FIELDS = [
|
|
9563
|
+
"agent",
|
|
9564
|
+
"model",
|
|
9565
|
+
"duration_ms",
|
|
9566
|
+
"cost",
|
|
9567
|
+
"files_changed",
|
|
9568
|
+
"status"
|
|
9569
|
+
];
|
|
9570
|
+
var DEFAULT_FIELD_LABELS = {
|
|
9571
|
+
agent: "Agent",
|
|
9572
|
+
model: "Model",
|
|
9573
|
+
duration_ms: "耗时",
|
|
9574
|
+
cost: "费用",
|
|
9575
|
+
files_changed: "改动文件数",
|
|
9576
|
+
status: "状态",
|
|
9577
|
+
error: "错误"
|
|
9578
|
+
};
|
|
9579
|
+
function pickFieldLines(data, showFields, labels) {
|
|
9580
|
+
if (!data)
|
|
9581
|
+
return [];
|
|
9582
|
+
const out = [];
|
|
9583
|
+
for (const key of showFields) {
|
|
9584
|
+
const v = data[key];
|
|
9585
|
+
if (v === undefined || v === null || v === "")
|
|
9586
|
+
continue;
|
|
9587
|
+
const label = labels[key] ?? key;
|
|
9588
|
+
let valueStr;
|
|
9589
|
+
if (key === "duration_ms" && typeof v === "number") {
|
|
9590
|
+
valueStr = v >= 60000 ? `${(v / 60000).toFixed(1)}m` : `${(v / 1000).toFixed(1)}s`;
|
|
9591
|
+
} else if (key === "cost" && typeof v === "number") {
|
|
9592
|
+
valueStr = `$${v.toFixed(4)}`;
|
|
9593
|
+
} else {
|
|
9594
|
+
valueStr = String(v);
|
|
9595
|
+
}
|
|
9596
|
+
out.push(`**${label}**: ${valueStr}`);
|
|
9597
|
+
}
|
|
9598
|
+
return out;
|
|
9599
|
+
}
|
|
9600
|
+
function isValidCtaUrl(url) {
|
|
9601
|
+
return typeof url === "string" && url.startsWith("https://");
|
|
9602
|
+
}
|
|
9434
9603
|
function computeLarkSign(secret, timestampSec) {
|
|
9435
9604
|
const stringToSign = `${timestampSec}
|
|
9436
9605
|
${secret}`;
|
|
@@ -9524,11 +9693,88 @@ function makeChannelEvent(event, data = {}) {
|
|
|
9524
9693
|
}
|
|
9525
9694
|
|
|
9526
9695
|
// plugins/channels.ts
|
|
9696
|
+
init_global_config();
|
|
9527
9697
|
var PLUGIN_NAME6 = "channels";
|
|
9528
9698
|
logLifecycle(PLUGIN_NAME6, "import", {});
|
|
9529
9699
|
var fallbackLog = makePluginLogger(PLUGIN_NAME6);
|
|
9530
9700
|
var _channelsCache = null;
|
|
9531
|
-
|
|
9701
|
+
var _activatedDirectory;
|
|
9702
|
+
var KNOWN_TYPES = new Set([
|
|
9703
|
+
"webhook",
|
|
9704
|
+
"file",
|
|
9705
|
+
"exec",
|
|
9706
|
+
"kh",
|
|
9707
|
+
"mcp",
|
|
9708
|
+
"slack",
|
|
9709
|
+
"lark"
|
|
9710
|
+
]);
|
|
9711
|
+
var REQUIRED_FIELDS = {
|
|
9712
|
+
webhook: ["url"],
|
|
9713
|
+
file: ["path"],
|
|
9714
|
+
exec: [],
|
|
9715
|
+
kh: [],
|
|
9716
|
+
mcp: ["server", "tool"],
|
|
9717
|
+
slack: ["webhook_url"],
|
|
9718
|
+
lark: ["webhook_url"]
|
|
9719
|
+
};
|
|
9720
|
+
function safePeek(o) {
|
|
9721
|
+
const out = {};
|
|
9722
|
+
for (const k of ["type", "name"]) {
|
|
9723
|
+
if (k in o)
|
|
9724
|
+
out[k] = o[k];
|
|
9725
|
+
}
|
|
9726
|
+
return out;
|
|
9727
|
+
}
|
|
9728
|
+
function isChannel(x, log4) {
|
|
9729
|
+
if (!x || typeof x !== "object")
|
|
9730
|
+
return false;
|
|
9731
|
+
const o = x;
|
|
9732
|
+
const t = o["type"];
|
|
9733
|
+
const n = o["name"];
|
|
9734
|
+
if (typeof n !== "string" || n.length === 0) {
|
|
9735
|
+
log4?.warn(`[channels] 配置被忽略:缺少 name 字段`, { sample: safePeek(o) });
|
|
9736
|
+
return false;
|
|
9737
|
+
}
|
|
9738
|
+
if (typeof t !== "string" || !KNOWN_TYPES.has(t)) {
|
|
9739
|
+
log4?.warn(`[channels] 配置 '${n}' 被忽略:未知 type='${String(t)}',合法值=${[...KNOWN_TYPES].join(",")}`);
|
|
9740
|
+
return false;
|
|
9741
|
+
}
|
|
9742
|
+
const required = REQUIRED_FIELDS[t] ?? [];
|
|
9743
|
+
for (const f of required) {
|
|
9744
|
+
const v = o[f];
|
|
9745
|
+
if (typeof v !== "string" || v.length === 0) {
|
|
9746
|
+
log4?.warn(`[channels] 配置 '${n}' (type=${t}) 被忽略:缺少必填字段 '${f}'`);
|
|
9747
|
+
return false;
|
|
9748
|
+
}
|
|
9749
|
+
}
|
|
9750
|
+
if (t === "exec") {
|
|
9751
|
+
const hasArgv = Array.isArray(o["argv"]) && o["argv"].length > 0;
|
|
9752
|
+
const hasCmd = typeof o["command"] === "string" && o["command"].length > 0;
|
|
9753
|
+
if (!hasArgv && !hasCmd) {
|
|
9754
|
+
log4?.warn(`[channels] 配置 '${n}' (type=exec) 被忽略:argv 与 command 均为空`);
|
|
9755
|
+
return false;
|
|
9756
|
+
}
|
|
9757
|
+
}
|
|
9758
|
+
return true;
|
|
9759
|
+
}
|
|
9760
|
+
function resolveGlobalConfigPath() {
|
|
9761
|
+
return path4.join(globalConfigDir(), "channels.json");
|
|
9762
|
+
}
|
|
9763
|
+
function resolveProjectConfigPaths(root) {
|
|
9764
|
+
const r = root ?? _activatedDirectory ?? process.cwd();
|
|
9765
|
+
return {
|
|
9766
|
+
recommended: path4.join(projectConfigDir(r), "channels.json"),
|
|
9767
|
+
legacy: path4.join(projectConfigDir(r), "config", "channels.json")
|
|
9768
|
+
};
|
|
9769
|
+
}
|
|
9770
|
+
var _warnedKeys2 = new Set;
|
|
9771
|
+
function warnOnce3(key, msg, log4) {
|
|
9772
|
+
if (_warnedKeys2.has(key))
|
|
9773
|
+
return;
|
|
9774
|
+
_warnedKeys2.add(key);
|
|
9775
|
+
log4.warn(msg);
|
|
9776
|
+
}
|
|
9777
|
+
function loadChannelsFromEnv(log4 = fallbackLog) {
|
|
9532
9778
|
const raw = process.env.CODEFORGE_CHANNELS_JSON;
|
|
9533
9779
|
if (!raw)
|
|
9534
9780
|
return [];
|
|
@@ -9536,22 +9782,103 @@ function loadChannelsFromEnv() {
|
|
|
9536
9782
|
const parsed = JSON.parse(raw);
|
|
9537
9783
|
if (!Array.isArray(parsed))
|
|
9538
9784
|
return [];
|
|
9539
|
-
return parsed.filter((c) => isChannel(c));
|
|
9785
|
+
return parsed.filter((c) => isChannel(c, log4));
|
|
9540
9786
|
} catch {
|
|
9787
|
+
log4.warn(`[channels] CODEFORGE_CHANNELS_JSON JSON 解析失败,忽略整段 env 配置`);
|
|
9541
9788
|
return [];
|
|
9542
9789
|
}
|
|
9543
9790
|
}
|
|
9544
|
-
function
|
|
9545
|
-
|
|
9546
|
-
|
|
9547
|
-
|
|
9548
|
-
|
|
9549
|
-
|
|
9791
|
+
function loadChannelsFromGlobal(log4 = fallbackLog) {
|
|
9792
|
+
const filePath = resolveGlobalConfigPath();
|
|
9793
|
+
const raw = loadJsonIfExists(filePath);
|
|
9794
|
+
if (!raw)
|
|
9795
|
+
return [];
|
|
9796
|
+
try {
|
|
9797
|
+
const st = statSync2(filePath);
|
|
9798
|
+
const perm = st.mode & 511;
|
|
9799
|
+
if ((perm & 63) !== 0) {
|
|
9800
|
+
warnOnce3(`global-perm:${filePath}`, `[channels] 全局 channels.json 权限 0${perm.toString(8)} 含 group/other 可读位,` + `含 webhook 时建议 chmod 600 ${filePath}`, log4);
|
|
9801
|
+
}
|
|
9802
|
+
} catch {}
|
|
9803
|
+
const arr = Array.isArray(raw) ? raw : raw.channels;
|
|
9804
|
+
if (!Array.isArray(arr))
|
|
9805
|
+
return [];
|
|
9806
|
+
return arr.filter((c) => isChannel(c, log4));
|
|
9807
|
+
}
|
|
9808
|
+
function loadChannelsFromFile(root, log4 = fallbackLog) {
|
|
9809
|
+
const { recommended, legacy } = resolveProjectConfigPaths(root);
|
|
9810
|
+
let raw = loadJsonIfExists(recommended);
|
|
9811
|
+
if (raw === null) {
|
|
9812
|
+
const legacyRaw = loadJsonIfExists(legacy);
|
|
9813
|
+
if (legacyRaw !== null) {
|
|
9814
|
+
log4.warn(`[channels] 检测到 0.3.10 兼容路径 ${legacy},建议迁移到 0.3.11 推荐路径 ${recommended}` + `(与 KH 配置惯例统一)。两条路径同时存在时只读推荐路径。`);
|
|
9815
|
+
raw = legacyRaw;
|
|
9816
|
+
}
|
|
9817
|
+
}
|
|
9818
|
+
if (!raw)
|
|
9819
|
+
return [];
|
|
9820
|
+
const arr = Array.isArray(raw) ? raw : raw.channels;
|
|
9821
|
+
if (!Array.isArray(arr))
|
|
9822
|
+
return [];
|
|
9823
|
+
return arr.filter((c) => isChannel(c, log4));
|
|
9550
9824
|
}
|
|
9551
|
-
function
|
|
9825
|
+
function parseRawEnvLength(raw) {
|
|
9826
|
+
if (raw === undefined || raw === "")
|
|
9827
|
+
return "not-set";
|
|
9828
|
+
try {
|
|
9829
|
+
const p = JSON.parse(raw);
|
|
9830
|
+
return Array.isArray(p) ? p.length : "invalid";
|
|
9831
|
+
} catch {
|
|
9832
|
+
return "invalid";
|
|
9833
|
+
}
|
|
9834
|
+
}
|
|
9835
|
+
function mergeByName(layers, log4) {
|
|
9836
|
+
const map = new Map;
|
|
9837
|
+
for (const { layer, items } of layers) {
|
|
9838
|
+
for (const c of items) {
|
|
9839
|
+
const prev = map.get(c.name);
|
|
9840
|
+
if (prev) {
|
|
9841
|
+
const typeNote = prev.channel.type !== c.type ? `(type: ${prev.channel.type} → ${c.type})` : "";
|
|
9842
|
+
log4.warn(`[channels] channel '${c.name}' (${layer} 层) 覆盖 ${prev.layer} 层同名配置${typeNote}`);
|
|
9843
|
+
}
|
|
9844
|
+
map.set(c.name, { layer, channel: c });
|
|
9845
|
+
}
|
|
9846
|
+
}
|
|
9847
|
+
return [...map.values()].map((e) => e.channel);
|
|
9848
|
+
}
|
|
9849
|
+
function ensureChannels(log4 = fallbackLog) {
|
|
9552
9850
|
if (_channelsCache)
|
|
9553
9851
|
return _channelsCache;
|
|
9554
|
-
|
|
9852
|
+
const rawEnv = process.env.CODEFORGE_CHANNELS_JSON;
|
|
9853
|
+
const rawLen = parseRawEnvLength(rawEnv);
|
|
9854
|
+
if (rawLen === 0) {
|
|
9855
|
+
log4.warn(`[channels] CODEFORGE_CHANNELS_JSON 显式为空数组,跳过 global + project 两层(全部通知已禁用)`);
|
|
9856
|
+
_channelsCache = [];
|
|
9857
|
+
return _channelsCache;
|
|
9858
|
+
}
|
|
9859
|
+
const fromGlobal = loadChannelsFromGlobal(log4);
|
|
9860
|
+
const fromFile = loadChannelsFromFile(undefined, log4);
|
|
9861
|
+
const fromEnv = rawLen === "not-set" ? [] : loadChannelsFromEnv(log4);
|
|
9862
|
+
if (rawLen === "not-set") {
|
|
9863
|
+
_channelsCache = mergeByName([
|
|
9864
|
+
{ layer: "global", items: fromGlobal },
|
|
9865
|
+
{ layer: "project", items: fromFile }
|
|
9866
|
+
], log4);
|
|
9867
|
+
return _channelsCache;
|
|
9868
|
+
}
|
|
9869
|
+
if (fromEnv.length === 0) {
|
|
9870
|
+
log4.warn(`[channels] CODEFORGE_CHANNELS_JSON 含 ${rawLen === "invalid" ? "非数组" : rawLen} 项但全部被过滤,` + `退回 global + project 两层(global ${fromGlobal.length} 项 + project ${fromFile.length} 项)`);
|
|
9871
|
+
_channelsCache = mergeByName([
|
|
9872
|
+
{ layer: "global", items: fromGlobal },
|
|
9873
|
+
{ layer: "project", items: fromFile }
|
|
9874
|
+
], log4);
|
|
9875
|
+
return _channelsCache;
|
|
9876
|
+
}
|
|
9877
|
+
_channelsCache = mergeByName([
|
|
9878
|
+
{ layer: "global", items: fromGlobal },
|
|
9879
|
+
{ layer: "project", items: fromFile },
|
|
9880
|
+
{ layer: "env", items: fromEnv }
|
|
9881
|
+
], log4);
|
|
9555
9882
|
return _channelsCache;
|
|
9556
9883
|
}
|
|
9557
9884
|
var _limiter = null;
|
|
@@ -9698,6 +10025,7 @@ var TRIGGER_EVENT_TYPES3 = new Set([
|
|
|
9698
10025
|
"subtasks.completed"
|
|
9699
10026
|
]);
|
|
9700
10027
|
var channelsServer = async (ctx) => {
|
|
10028
|
+
_activatedDirectory = ctx.directory;
|
|
9701
10029
|
logLifecycle(PLUGIN_NAME6, "activate", {
|
|
9702
10030
|
directory: ctx.directory,
|
|
9703
10031
|
triggerEventTypes: [...TRIGGER_EVENT_TYPES3],
|
|
@@ -10769,6 +11097,14 @@ function applyAnchor(content, edit, eol, beforeHash) {
|
|
|
10769
11097
|
if (!edit.anchor) {
|
|
10770
11098
|
return { ok: false, reason: "invalid_input", message: "anchor 不能为空" };
|
|
10771
11099
|
}
|
|
11100
|
+
if (edit.anchor.includes(`
|
|
11101
|
+
`)) {
|
|
11102
|
+
return {
|
|
11103
|
+
ok: false,
|
|
11104
|
+
reason: "invalid_input",
|
|
11105
|
+
message: "anchor 不能含换行符(仅支持单行匹配);多行改动请改用 pending_changes.stage 整文件暂存"
|
|
11106
|
+
};
|
|
11107
|
+
}
|
|
10772
11108
|
const lines = splitLines(content);
|
|
10773
11109
|
const hits = findAnchorLines(lines, edit.anchor, edit.regex === true);
|
|
10774
11110
|
if (hits.length === 0) {
|
|
@@ -10971,6 +11307,7 @@ function finish(before, after, beforeHash, affected) {
|
|
|
10971
11307
|
// tools/ast-edit.ts
|
|
10972
11308
|
var description16 = [
|
|
10973
11309
|
"AST 风格的精确文件编辑:anchor + 哈希校验,避免 LLM 「整文件重写」误改无关代码。",
|
|
11310
|
+
"**anchor 仅支持单行**:含 `\\n` 的多行 anchor 会被直接拒绝(reason=invalid_input);多行改动请改用 `pending_changes.stage` 整文件暂存。",
|
|
10974
11311
|
"**何时调用**:",
|
|
10975
11312
|
"- 需要在指定 anchor(行特征)后插入代码(hook 注入、import 添加)",
|
|
10976
11313
|
"- 重命名一个标识符(rename_symbol,全文件词边界匹配)",
|
|
@@ -10978,6 +11315,7 @@ var description16 = [
|
|
|
10978
11315
|
"**何时不需要**:",
|
|
10979
11316
|
"- 整文件重写更合理(直接 pending-changes.stage 整文件)",
|
|
10980
11317
|
"- 只是 1-2 个字符的 typo(用 edit 工具更直接)",
|
|
11318
|
+
"- 多行 anchor / 跨行匹配(请用 pending_changes.stage 整文件)",
|
|
10981
11319
|
"**安全约束**:",
|
|
10982
11320
|
"- 必须传 before_hash(如果文件已存在),不一致会被拒绝",
|
|
10983
11321
|
"- 默认 auto_stage=true:结果直接进 pending-changes 等待人审,不立刻落盘"
|
|
@@ -10995,7 +11333,7 @@ var AnchorAction = Common.extend({
|
|
|
10995
11333
|
"insert_after_anchor",
|
|
10996
11334
|
"insert_before_anchor"
|
|
10997
11335
|
]),
|
|
10998
|
-
anchor: z17.string().min(1).describe("anchor
|
|
11336
|
+
anchor: z17.string().min(1).describe("anchor 文本子串或正则源(必须单行;含 \\n 会被拒绝,多行改动请用 pending_changes.stage)"),
|
|
10999
11337
|
regex: z17.boolean().optional().describe("anchor 是否按 RegExp 解释,默认 false"),
|
|
11000
11338
|
occurrence: z17.number().int().min(1).optional().describe("第几次匹配(1-based),默认 1;多次命中时必须显式指定"),
|
|
11001
11339
|
payload: z17.string().describe("要写入的内容;引擎会按文件原 EOL 处理")
|
|
@@ -12201,6 +12539,11 @@ import { z as z26 } from "zod";
|
|
|
12201
12539
|
// lib/model-config.ts
|
|
12202
12540
|
import { promises as fs7 } from "node:fs";
|
|
12203
12541
|
import * as path10 from "node:path";
|
|
12542
|
+
|
|
12543
|
+
// lib/model-tier.ts
|
|
12544
|
+
var TIER_ORDER = ["quick", "balanced", "deep", "ultra"];
|
|
12545
|
+
|
|
12546
|
+
// lib/model-config.ts
|
|
12204
12547
|
var CONFIG_FILE = "codeforge.json";
|
|
12205
12548
|
var DEFAULT_RUNTIME_FALLBACK = {
|
|
12206
12549
|
enabled: true,
|
|
@@ -12305,11 +12648,29 @@ function validateConfig(input) {
|
|
|
12305
12648
|
return { ok: false, warnings, error: r.error };
|
|
12306
12649
|
runtime = r.cfg;
|
|
12307
12650
|
}
|
|
12651
|
+
let tiers;
|
|
12652
|
+
if (obj.tiers !== undefined) {
|
|
12653
|
+
const r = normalizeTiers(obj.tiers);
|
|
12654
|
+
if (!r.ok)
|
|
12655
|
+
return { ok: false, warnings, error: r.error };
|
|
12656
|
+
tiers = r.cfg;
|
|
12657
|
+
}
|
|
12308
12658
|
for (const [name, agent] of Object.entries(agents)) {
|
|
12309
12659
|
if (agent.category && !categories?.[agent.category]) {
|
|
12310
12660
|
warnings.push(`agent[${name}].category="${agent.category}" 未在 categories 定义,将忽略 category 链`);
|
|
12311
12661
|
}
|
|
12312
12662
|
}
|
|
12663
|
+
for (const [name, agent] of Object.entries(agents)) {
|
|
12664
|
+
if (!agent.tier)
|
|
12665
|
+
continue;
|
|
12666
|
+
const mappedCat = tiers?.category_map?.[agent.tier];
|
|
12667
|
+
const hasOverride = agent.tier_overrides?.[agent.tier] !== undefined;
|
|
12668
|
+
if (!mappedCat && !hasOverride) {
|
|
12669
|
+
warnings.push(`agent[${name}].tier="${agent.tier}" 既无 models.tiers.category_map["${agent.tier}"] 映射,` + `也无 tier_overrides["${agent.tier}"];该 agent 将不参与 tier 体系(adapter 返 null)。`);
|
|
12670
|
+
} else if (mappedCat && !categories?.[mappedCat] && !hasOverride) {
|
|
12671
|
+
warnings.push(`agent[${name}].tier="${agent.tier}" 通过 category_map 映射到 "${mappedCat}",` + `但 categories.${mappedCat} 不存在;该 agent 将不参与 tier 体系。`);
|
|
12672
|
+
}
|
|
12673
|
+
}
|
|
12313
12674
|
return {
|
|
12314
12675
|
ok: true,
|
|
12315
12676
|
warnings,
|
|
@@ -12318,7 +12679,8 @@ function validateConfig(input) {
|
|
|
12318
12679
|
_doc: typeof obj._doc === "string" ? obj._doc : undefined,
|
|
12319
12680
|
agents,
|
|
12320
12681
|
categories,
|
|
12321
|
-
runtime_fallback: runtime
|
|
12682
|
+
runtime_fallback: runtime,
|
|
12683
|
+
tiers
|
|
12322
12684
|
}
|
|
12323
12685
|
};
|
|
12324
12686
|
}
|
|
@@ -12339,6 +12701,23 @@ function normalizeAgent(name, raw) {
|
|
|
12339
12701
|
const thinking = o.thinking !== undefined ? normalizeThinking(`agent[${name}]`, o.thinking) : undefined;
|
|
12340
12702
|
if (thinking && !thinking.ok)
|
|
12341
12703
|
return { ok: false, error: thinking.error };
|
|
12704
|
+
let tier;
|
|
12705
|
+
if (o.tier !== undefined) {
|
|
12706
|
+
if (typeof o.tier !== "string" || !isValidTierLevel(o.tier)) {
|
|
12707
|
+
return {
|
|
12708
|
+
ok: false,
|
|
12709
|
+
error: `agent[${name}].tier="${String(o.tier)}" 不是合法 TierLevel (期望 ${TIER_ORDER.join("/")})`
|
|
12710
|
+
};
|
|
12711
|
+
}
|
|
12712
|
+
tier = o.tier;
|
|
12713
|
+
}
|
|
12714
|
+
let tierOverrides;
|
|
12715
|
+
if (o.tier_overrides !== undefined) {
|
|
12716
|
+
const r = normalizeTierOverrides(name, o.tier_overrides);
|
|
12717
|
+
if (!r.ok)
|
|
12718
|
+
return { ok: false, error: r.error };
|
|
12719
|
+
tierOverrides = r.value;
|
|
12720
|
+
}
|
|
12342
12721
|
return {
|
|
12343
12722
|
ok: true,
|
|
12344
12723
|
binding: {
|
|
@@ -12347,6 +12726,8 @@ function normalizeAgent(name, raw) {
|
|
|
12347
12726
|
category: typeof o.category === "string" ? o.category : undefined,
|
|
12348
12727
|
thinking: thinking?.value,
|
|
12349
12728
|
fallback_models: fallbacks.value,
|
|
12729
|
+
tier,
|
|
12730
|
+
tier_overrides: tierOverrides,
|
|
12350
12731
|
_doc: typeof o._doc === "string" ? o._doc : undefined
|
|
12351
12732
|
}
|
|
12352
12733
|
};
|
|
@@ -12443,6 +12824,99 @@ function normalizeRuntime(raw) {
|
|
|
12443
12824
|
cfg._doc = o._doc;
|
|
12444
12825
|
return { ok: true, cfg };
|
|
12445
12826
|
}
|
|
12827
|
+
function isValidTierLevel(x) {
|
|
12828
|
+
return typeof x === "string" && TIER_ORDER.includes(x);
|
|
12829
|
+
}
|
|
12830
|
+
function normalizeTierOverrides(agentName, raw) {
|
|
12831
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
12832
|
+
return {
|
|
12833
|
+
ok: false,
|
|
12834
|
+
error: `agent[${agentName}].tier_overrides 必须是 object(不是 array / null / 其他类型)`
|
|
12835
|
+
};
|
|
12836
|
+
}
|
|
12837
|
+
const o = raw;
|
|
12838
|
+
const result = {};
|
|
12839
|
+
for (const [key, value] of Object.entries(o)) {
|
|
12840
|
+
if (!isValidTierLevel(key)) {
|
|
12841
|
+
return {
|
|
12842
|
+
ok: false,
|
|
12843
|
+
error: `agent[${agentName}].tier_overrides.${key}: 非法 TierLevel key (期望 ${TIER_ORDER.join("/")})`
|
|
12844
|
+
};
|
|
12845
|
+
}
|
|
12846
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
12847
|
+
return {
|
|
12848
|
+
ok: false,
|
|
12849
|
+
error: `agent[${agentName}].tier_overrides.${key}: 必须是 object`
|
|
12850
|
+
};
|
|
12851
|
+
}
|
|
12852
|
+
const ov = value;
|
|
12853
|
+
const partial = {};
|
|
12854
|
+
if (ov.level !== undefined) {
|
|
12855
|
+
if (ov.level !== key) {
|
|
12856
|
+
return {
|
|
12857
|
+
ok: false,
|
|
12858
|
+
error: `agent[${agentName}].tier_overrides.${key}.level="${String(ov.level)}" 与 key "${key}" 错配`
|
|
12859
|
+
};
|
|
12860
|
+
}
|
|
12861
|
+
partial.level = key;
|
|
12862
|
+
}
|
|
12863
|
+
if (ov.model !== undefined) {
|
|
12864
|
+
if (typeof ov.model !== "string" || !PROVIDER_MODEL_RE.test(ov.model)) {
|
|
12865
|
+
return {
|
|
12866
|
+
ok: false,
|
|
12867
|
+
error: `agent[${agentName}].tier_overrides.${key}.model="${String(ov.model)}" 格式非法 (期望 <provider>/<id>)`
|
|
12868
|
+
};
|
|
12869
|
+
}
|
|
12870
|
+
partial.model = ov.model;
|
|
12871
|
+
}
|
|
12872
|
+
if (ov.thinking !== undefined) {
|
|
12873
|
+
const t = normalizeThinking(`agent[${agentName}].tier_overrides.${key}`, ov.thinking);
|
|
12874
|
+
if (!t.ok)
|
|
12875
|
+
return { ok: false, error: t.error };
|
|
12876
|
+
partial.thinking = t.value;
|
|
12877
|
+
}
|
|
12878
|
+
if (ov.fallback_models !== undefined) {
|
|
12879
|
+
const f = normalizeFallbackList(`agent[${agentName}].tier_overrides.${key}.fallback_models`, ov.fallback_models);
|
|
12880
|
+
if (!f.ok)
|
|
12881
|
+
return { ok: false, error: f.error };
|
|
12882
|
+
partial.fallback_models = f.value;
|
|
12883
|
+
}
|
|
12884
|
+
result[key] = partial;
|
|
12885
|
+
}
|
|
12886
|
+
return { ok: true, value: result };
|
|
12887
|
+
}
|
|
12888
|
+
function normalizeTiers(raw) {
|
|
12889
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
12890
|
+
return { ok: false, error: "models.tiers 必须是 object(不是 array / null)" };
|
|
12891
|
+
}
|
|
12892
|
+
const o = raw;
|
|
12893
|
+
const cfg = {};
|
|
12894
|
+
if (o.category_map !== undefined) {
|
|
12895
|
+
if (!o.category_map || typeof o.category_map !== "object" || Array.isArray(o.category_map)) {
|
|
12896
|
+
return { ok: false, error: "models.tiers.category_map 必须是 object" };
|
|
12897
|
+
}
|
|
12898
|
+
const map = {};
|
|
12899
|
+
for (const [key, value] of Object.entries(o.category_map)) {
|
|
12900
|
+
if (!isValidTierLevel(key)) {
|
|
12901
|
+
return {
|
|
12902
|
+
ok: false,
|
|
12903
|
+
error: `models.tiers.category_map.${key}: 非法 TierLevel key (期望 ${TIER_ORDER.join("/")})`
|
|
12904
|
+
};
|
|
12905
|
+
}
|
|
12906
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
12907
|
+
return {
|
|
12908
|
+
ok: false,
|
|
12909
|
+
error: `models.tiers.category_map.${key}: 必须是非空字符串(category 名)`
|
|
12910
|
+
};
|
|
12911
|
+
}
|
|
12912
|
+
map[key] = value;
|
|
12913
|
+
}
|
|
12914
|
+
cfg.category_map = map;
|
|
12915
|
+
}
|
|
12916
|
+
if (typeof o._doc === "string")
|
|
12917
|
+
cfg._doc = o._doc;
|
|
12918
|
+
return { ok: true, cfg };
|
|
12919
|
+
}
|
|
12446
12920
|
function resolveAgentModel(config, agent) {
|
|
12447
12921
|
const a = config.agents[agent];
|
|
12448
12922
|
if (!a)
|
|
@@ -13517,76 +13991,146 @@ var codeforgeToolsServer = async (ctx) => {
|
|
|
13517
13991
|
};
|
|
13518
13992
|
var handler8 = codeforgeToolsServer;
|
|
13519
13993
|
|
|
13520
|
-
// plugins/
|
|
13521
|
-
|
|
13522
|
-
|
|
13523
|
-
|
|
13524
|
-
|
|
13525
|
-
|
|
13526
|
-
|
|
13527
|
-
|
|
13528
|
-
|
|
13529
|
-
|
|
13530
|
-
|
|
13531
|
-
|
|
13532
|
-
|
|
13533
|
-
|
|
13534
|
-
|
|
13535
|
-
|
|
13536
|
-
|
|
13537
|
-
|
|
13538
|
-
|
|
13539
|
-
|
|
13540
|
-
|
|
13541
|
-
|
|
13542
|
-
|
|
13543
|
-
|
|
13994
|
+
// plugins/kh-auto-context.ts
|
|
13995
|
+
init_kh_client();
|
|
13996
|
+
|
|
13997
|
+
// lib/kh-shared-context.ts
|
|
13998
|
+
var DEFAULT_MAX_PER_SESSION = 20;
|
|
13999
|
+
var DEFAULT_TTL_MS = 5 * 60 * 1000;
|
|
14000
|
+
|
|
14001
|
+
class SessionScopedCache {
|
|
14002
|
+
cache = new Map;
|
|
14003
|
+
inflight = new Map;
|
|
14004
|
+
generation = new Map;
|
|
14005
|
+
ttlMs;
|
|
14006
|
+
maxPerSession;
|
|
14007
|
+
now;
|
|
14008
|
+
constructor(opts = {}) {
|
|
14009
|
+
this.ttlMs = opts.ttlMs ?? DEFAULT_TTL_MS;
|
|
14010
|
+
this.maxPerSession = opts.maxPerSession ?? DEFAULT_MAX_PER_SESSION;
|
|
14011
|
+
this.now = opts.now ?? (() => Date.now());
|
|
14012
|
+
}
|
|
14013
|
+
async searchOrGet(args) {
|
|
14014
|
+
const { client, sessionId, query, limit, timeoutMs } = args;
|
|
14015
|
+
const queryHash = this.hashQuery(sessionId, query);
|
|
14016
|
+
const inflightKey = `${sessionId}::${queryHash}`;
|
|
14017
|
+
const sessionMap = this.cache.get(sessionId);
|
|
14018
|
+
if (sessionMap) {
|
|
14019
|
+
const entry = sessionMap.get(queryHash);
|
|
14020
|
+
if (entry && this.now() - entry.cachedAt < this.ttlMs) {
|
|
14021
|
+
sessionMap.delete(queryHash);
|
|
14022
|
+
sessionMap.set(queryHash, entry);
|
|
14023
|
+
return entry.result;
|
|
14024
|
+
}
|
|
14025
|
+
if (entry)
|
|
14026
|
+
sessionMap.delete(queryHash);
|
|
14027
|
+
}
|
|
14028
|
+
const pending = this.inflight.get(inflightKey);
|
|
14029
|
+
if (pending)
|
|
14030
|
+
return pending;
|
|
14031
|
+
const startGen = this.generation.get(sessionId) ?? 0;
|
|
14032
|
+
const promise = this.doSearchAndMaybeCache(client, sessionId, queryHash, query, limit, timeoutMs, startGen).finally(() => {
|
|
14033
|
+
this.inflight.delete(inflightKey);
|
|
13544
14034
|
});
|
|
13545
|
-
|
|
13546
|
-
|
|
13547
|
-
|
|
13548
|
-
|
|
13549
|
-
|
|
13550
|
-
|
|
13551
|
-
|
|
13552
|
-
|
|
13553
|
-
|
|
13554
|
-
|
|
13555
|
-
|
|
13556
|
-
|
|
13557
|
-
|
|
13558
|
-
|
|
13559
|
-
|
|
13560
|
-
|
|
13561
|
-
|
|
13562
|
-
|
|
13563
|
-
|
|
13564
|
-
|
|
13565
|
-
|
|
13566
|
-
|
|
13567
|
-
|
|
13568
|
-
|
|
13569
|
-
|
|
13570
|
-
|
|
13571
|
-
|
|
13572
|
-
|
|
13573
|
-
|
|
13574
|
-
|
|
13575
|
-
|
|
13576
|
-
|
|
13577
|
-
|
|
13578
|
-
|
|
13579
|
-
|
|
14035
|
+
this.inflight.set(inflightKey, promise);
|
|
14036
|
+
return promise;
|
|
14037
|
+
}
|
|
14038
|
+
onSessionEnd(sessionId) {
|
|
14039
|
+
this.generation.set(sessionId, (this.generation.get(sessionId) ?? 0) + 1);
|
|
14040
|
+
this.cache.delete(sessionId);
|
|
14041
|
+
const prefix = `${sessionId}::`;
|
|
14042
|
+
for (const key of this.inflight.keys()) {
|
|
14043
|
+
if (key.startsWith(prefix)) {
|
|
14044
|
+
this.inflight.delete(key);
|
|
14045
|
+
}
|
|
14046
|
+
}
|
|
14047
|
+
}
|
|
14048
|
+
_snapshot() {
|
|
14049
|
+
const perSession = {};
|
|
14050
|
+
let total = 0;
|
|
14051
|
+
for (const [sid, map] of this.cache.entries()) {
|
|
14052
|
+
perSession[sid] = map.size;
|
|
14053
|
+
total += map.size;
|
|
14054
|
+
}
|
|
14055
|
+
const generations = {};
|
|
14056
|
+
for (const [sid, g] of this.generation.entries()) {
|
|
14057
|
+
generations[sid] = g;
|
|
14058
|
+
}
|
|
14059
|
+
return {
|
|
14060
|
+
cacheSize: total,
|
|
14061
|
+
inflightSize: this.inflight.size,
|
|
14062
|
+
sessions: Array.from(this.cache.keys()),
|
|
14063
|
+
perSession,
|
|
14064
|
+
generations
|
|
14065
|
+
};
|
|
14066
|
+
}
|
|
14067
|
+
_reset() {
|
|
14068
|
+
this.cache.clear();
|
|
14069
|
+
this.inflight.clear();
|
|
14070
|
+
this.generation.clear();
|
|
14071
|
+
}
|
|
14072
|
+
hashQuery(sessionId, query) {
|
|
14073
|
+
return `${sessionId}::${query}`;
|
|
14074
|
+
}
|
|
14075
|
+
async doSearchAndMaybeCache(client, sessionId, queryHash, query, limit, timeoutMs, startGen) {
|
|
14076
|
+
const result = await this.doSearch(client, query, limit, timeoutMs);
|
|
14077
|
+
const currentGen = this.generation.get(sessionId) ?? 0;
|
|
14078
|
+
if (currentGen === startGen && result.ok) {
|
|
14079
|
+
this.writeCache(sessionId, queryHash, query, result);
|
|
14080
|
+
}
|
|
14081
|
+
return result;
|
|
14082
|
+
}
|
|
14083
|
+
async doSearch(client, query, limit, timeoutMs) {
|
|
14084
|
+
const callPromise = (async () => {
|
|
14085
|
+
try {
|
|
14086
|
+
return await client.search({ query, limit });
|
|
14087
|
+
} catch (err) {
|
|
14088
|
+
return {
|
|
14089
|
+
ok: false,
|
|
14090
|
+
reason: "kh_returned_error",
|
|
14091
|
+
message: err instanceof Error ? err.message : String(err)
|
|
14092
|
+
};
|
|
14093
|
+
}
|
|
14094
|
+
})();
|
|
14095
|
+
if (timeoutMs === undefined || timeoutMs <= 0) {
|
|
14096
|
+
return callPromise;
|
|
14097
|
+
}
|
|
14098
|
+
let timer = null;
|
|
14099
|
+
try {
|
|
14100
|
+
const racer = new Promise((res) => {
|
|
14101
|
+
timer = setTimeout(() => res({
|
|
14102
|
+
ok: false,
|
|
14103
|
+
reason: "kh_returned_error",
|
|
14104
|
+
message: `kh search timeout after ${timeoutMs}ms`
|
|
14105
|
+
}), timeoutMs);
|
|
13580
14106
|
});
|
|
14107
|
+
return await Promise.race([callPromise, racer]);
|
|
14108
|
+
} finally {
|
|
14109
|
+
if (timer)
|
|
14110
|
+
clearTimeout(timer);
|
|
13581
14111
|
}
|
|
13582
|
-
}
|
|
13583
|
-
|
|
13584
|
-
|
|
14112
|
+
}
|
|
14113
|
+
writeCache(sessionId, queryHash, query, result) {
|
|
14114
|
+
let sessionMap = this.cache.get(sessionId);
|
|
14115
|
+
if (!sessionMap) {
|
|
14116
|
+
sessionMap = new Map;
|
|
14117
|
+
this.cache.set(sessionId, sessionMap);
|
|
14118
|
+
}
|
|
14119
|
+
sessionMap.delete(queryHash);
|
|
14120
|
+
sessionMap.set(queryHash, { query, result, cachedAt: this.now() });
|
|
14121
|
+
while (sessionMap.size > this.maxPerSession) {
|
|
14122
|
+
const oldest = sessionMap.keys().next().value;
|
|
14123
|
+
if (oldest === undefined)
|
|
14124
|
+
break;
|
|
14125
|
+
sessionMap.delete(oldest);
|
|
14126
|
+
}
|
|
14127
|
+
}
|
|
14128
|
+
}
|
|
14129
|
+
var sharedKhCache = new SessionScopedCache;
|
|
13585
14130
|
|
|
13586
14131
|
// plugins/kh-auto-context.ts
|
|
13587
|
-
init_kh_client();
|
|
13588
14132
|
init_opencode_plugin_helpers();
|
|
13589
|
-
var
|
|
14133
|
+
var PLUGIN_NAME9 = "kh-auto-context";
|
|
13590
14134
|
var INJECTION_MODE = "observe-only";
|
|
13591
14135
|
function resolveInjectionMode(client) {
|
|
13592
14136
|
return client.hasTransport() ? "system-injected" : "observe-only";
|
|
@@ -13685,7 +14229,7 @@ var CODEFORGE_CONSTRAINTS = [
|
|
|
13685
14229
|
];
|
|
13686
14230
|
async function seedConstraints(client, log6) {
|
|
13687
14231
|
if (!client.hasTransport()) {
|
|
13688
|
-
log6?.debug?.(`[${
|
|
14232
|
+
log6?.debug?.(`[${PLUGIN_NAME9}] seedConstraints: transport 不可用,跳过`, {});
|
|
13689
14233
|
return { ok: false, reason: "transport_unavailable" };
|
|
13690
14234
|
}
|
|
13691
14235
|
try {
|
|
@@ -13699,7 +14243,7 @@ async function seedConstraints(client, log6) {
|
|
|
13699
14243
|
});
|
|
13700
14244
|
if (result && typeof result === "object" && "ok" in result && result.ok === false) {
|
|
13701
14245
|
const r = result;
|
|
13702
|
-
log6?.warn(`[${
|
|
14246
|
+
log6?.warn(`[${PLUGIN_NAME9}] seedConstraints 降级`, {
|
|
13703
14247
|
reason: r.reason,
|
|
13704
14248
|
message: r.message
|
|
13705
14249
|
});
|
|
@@ -13709,11 +14253,11 @@ async function seedConstraints(client, log6) {
|
|
|
13709
14253
|
message: r.message
|
|
13710
14254
|
};
|
|
13711
14255
|
}
|
|
13712
|
-
log6?.info(`[${
|
|
14256
|
+
log6?.info(`[${PLUGIN_NAME9}] seedConstraints: 已写入 ${CODEFORGE_CONSTRAINTS.length} 条 constraints`, { count: CODEFORGE_CONSTRAINTS.length });
|
|
13713
14257
|
return { ok: true, itemsWritten: CODEFORGE_CONSTRAINTS.length };
|
|
13714
14258
|
} catch (err) {
|
|
13715
14259
|
const message = err instanceof Error ? err.message : String(err);
|
|
13716
|
-
log6?.warn(`[${
|
|
14260
|
+
log6?.warn(`[${PLUGIN_NAME9}] seedConstraints 失败(已静默)`, { error: message });
|
|
13717
14261
|
return { ok: false, reason: "exception", message };
|
|
13718
14262
|
}
|
|
13719
14263
|
}
|
|
@@ -13728,7 +14272,7 @@ function createSystemInjectedHook(client, sessionId, log6) {
|
|
|
13728
14272
|
});
|
|
13729
14273
|
if (result && typeof result === "object" && "ok" in result && result.ok === false) {
|
|
13730
14274
|
const r = result;
|
|
13731
|
-
log6?.warn(`[${
|
|
14275
|
+
log6?.warn(`[${PLUGIN_NAME9}] system-injected 降级到 observe-only:${r.reason ?? "unknown"}`, {
|
|
13732
14276
|
sessionId,
|
|
13733
14277
|
section,
|
|
13734
14278
|
preview: markdown.slice(0, 200),
|
|
@@ -13737,12 +14281,12 @@ function createSystemInjectedHook(client, sessionId, log6) {
|
|
|
13737
14281
|
});
|
|
13738
14282
|
return;
|
|
13739
14283
|
}
|
|
13740
|
-
log6?.info(`[${
|
|
14284
|
+
log6?.info(`[${PLUGIN_NAME9}] system-injected: 写入 KH working_memory 成功 (${markdown.length} chars)`, {
|
|
13741
14285
|
sessionId,
|
|
13742
14286
|
section
|
|
13743
14287
|
});
|
|
13744
14288
|
} catch (err) {
|
|
13745
|
-
log6?.warn(`[${
|
|
14289
|
+
log6?.warn(`[${PLUGIN_NAME9}] system-injected 抛异常,降级到 observe-only`, {
|
|
13746
14290
|
sessionId,
|
|
13747
14291
|
section,
|
|
13748
14292
|
preview: markdown.slice(0, 200),
|
|
@@ -13751,23 +14295,16 @@ function createSystemInjectedHook(client, sessionId, log6) {
|
|
|
13751
14295
|
}
|
|
13752
14296
|
};
|
|
13753
14297
|
}
|
|
13754
|
-
|
|
14298
|
+
var inflight2 = new Set;
|
|
14299
|
+
var INFLIGHT_CAP = 5;
|
|
14300
|
+
function inflightKey(sessionId, query) {
|
|
14301
|
+
return `${sessionId ?? "global"}:${Date.now()}:${Math.random().toString(36).slice(2, 10)}`;
|
|
14302
|
+
}
|
|
14303
|
+
async function runKhSearchAndInject(args) {
|
|
14304
|
+
const { query, ctx, opts, mode } = args;
|
|
13755
14305
|
const cfg = opts.config ?? DEFAULT_CONFIG5;
|
|
13756
|
-
const mode = opts.mode ?? INJECTION_MODE;
|
|
13757
|
-
const ctx = raw ?? {};
|
|
13758
14306
|
const log6 = ctx.log;
|
|
13759
|
-
const
|
|
13760
|
-
if (!shouldInject(text, cfg)) {
|
|
13761
|
-
log6?.debug?.(`[${PLUGIN_NAME10}] skip (filter)`, { textLen: text.length });
|
|
13762
|
-
return null;
|
|
13763
|
-
}
|
|
13764
|
-
const query = extractQuery(text);
|
|
13765
|
-
if (!query)
|
|
13766
|
-
return null;
|
|
13767
|
-
if (opts.cache.shouldSkip(query)) {
|
|
13768
|
-
log6?.debug?.(`[${PLUGIN_NAME10}] cache hit, skip`, { query });
|
|
13769
|
-
return null;
|
|
13770
|
-
}
|
|
14307
|
+
const startedAt = Date.now();
|
|
13771
14308
|
const racer = opts.scheduler?.raceTimeout ?? (async (p, ms) => {
|
|
13772
14309
|
let timer = null;
|
|
13773
14310
|
try {
|
|
@@ -13782,20 +14319,54 @@ async function handleMessage2(raw, opts) {
|
|
|
13782
14319
|
clearTimeout(timer);
|
|
13783
14320
|
}
|
|
13784
14321
|
});
|
|
13785
|
-
|
|
14322
|
+
let result;
|
|
14323
|
+
try {
|
|
14324
|
+
const searchPromise = sharedKhCache.searchOrGet({
|
|
14325
|
+
client: opts.client,
|
|
14326
|
+
sessionId: ctx.sessionId ?? "global",
|
|
14327
|
+
query,
|
|
14328
|
+
limit: cfg.limit
|
|
14329
|
+
});
|
|
14330
|
+
result = await racer(searchPromise, cfg.timeoutMs);
|
|
14331
|
+
} catch (err) {
|
|
14332
|
+
log6?.warn(`[${PLUGIN_NAME9}] client.search threw (sync or async), return null`, {
|
|
14333
|
+
query,
|
|
14334
|
+
elapsedMs: Date.now() - startedAt,
|
|
14335
|
+
sessionId: ctx.sessionId,
|
|
14336
|
+
error: err instanceof Error ? err.message : String(err)
|
|
14337
|
+
});
|
|
14338
|
+
opts.cache.record(query, []);
|
|
14339
|
+
return null;
|
|
14340
|
+
}
|
|
13786
14341
|
if (result === "__timeout__") {
|
|
13787
|
-
log6?.warn(`[${
|
|
14342
|
+
log6?.warn(`[${PLUGIN_NAME9}] timeout`, {
|
|
14343
|
+
query,
|
|
14344
|
+
ms: cfg.timeoutMs,
|
|
14345
|
+
elapsedMs: Date.now() - startedAt,
|
|
14346
|
+
sessionId: ctx.sessionId
|
|
14347
|
+
});
|
|
13788
14348
|
opts.cache.record(query, []);
|
|
13789
14349
|
return null;
|
|
13790
14350
|
}
|
|
13791
14351
|
if (!result.ok) {
|
|
13792
|
-
log6?.
|
|
14352
|
+
log6?.warn(`[${PLUGIN_NAME9}] kh degraded`, {
|
|
14353
|
+
reason: result.reason,
|
|
14354
|
+
query,
|
|
14355
|
+
elapsedMs: Date.now() - startedAt,
|
|
14356
|
+
sessionId: ctx.sessionId
|
|
14357
|
+
});
|
|
13793
14358
|
opts.cache.record(query, []);
|
|
13794
14359
|
return null;
|
|
13795
14360
|
}
|
|
13796
14361
|
const filtered = result.insights.filter((i) => (i.confidence ?? 0) >= cfg.minConfidence);
|
|
13797
14362
|
if (filtered.length === 0) {
|
|
13798
14363
|
opts.cache.record(query, []);
|
|
14364
|
+
log6?.debug?.(`[${PLUGIN_NAME9}] no candidate above threshold`, {
|
|
14365
|
+
query,
|
|
14366
|
+
rawCount: result.insights.length,
|
|
14367
|
+
elapsedMs: Date.now() - startedAt,
|
|
14368
|
+
sessionId: ctx.sessionId
|
|
14369
|
+
});
|
|
13799
14370
|
return null;
|
|
13800
14371
|
}
|
|
13801
14372
|
const payload = formatInjection(query, filtered, mode);
|
|
@@ -13803,22 +14374,68 @@ async function handleMessage2(raw, opts) {
|
|
|
13803
14374
|
try {
|
|
13804
14375
|
await ctx.injectContext(payload.markdown);
|
|
13805
14376
|
} catch (err) {
|
|
13806
|
-
log6?.warn(`[${
|
|
13807
|
-
error: err instanceof Error ? err.message : String(err)
|
|
14377
|
+
log6?.warn(`[${PLUGIN_NAME9}] injectContext threw`, {
|
|
14378
|
+
error: err instanceof Error ? err.message : String(err),
|
|
14379
|
+
query,
|
|
14380
|
+
sessionId: ctx.sessionId
|
|
13808
14381
|
});
|
|
13809
14382
|
}
|
|
13810
14383
|
}
|
|
13811
14384
|
opts.cache.record(query, filtered);
|
|
13812
|
-
log6?.info(`[${
|
|
14385
|
+
log6?.info(`[${PLUGIN_NAME9}] inject complete (${mode})`, {
|
|
14386
|
+
query,
|
|
14387
|
+
mode,
|
|
14388
|
+
candidateCount: filtered.length,
|
|
14389
|
+
elapsedMs: Date.now() - startedAt,
|
|
14390
|
+
sessionId: ctx.sessionId
|
|
14391
|
+
});
|
|
13813
14392
|
return payload;
|
|
13814
14393
|
}
|
|
13815
|
-
|
|
14394
|
+
async function handleMessage2(raw, opts) {
|
|
14395
|
+
const cfg = opts.config ?? DEFAULT_CONFIG5;
|
|
14396
|
+
const mode = opts.mode ?? INJECTION_MODE;
|
|
14397
|
+
const ctx = raw ?? {};
|
|
14398
|
+
const log6 = ctx.log;
|
|
14399
|
+
const text = (ctx.content ?? "").trim();
|
|
14400
|
+
if (!shouldInject(text, cfg)) {
|
|
14401
|
+
log6?.debug?.(`[${PLUGIN_NAME9}] skip (filter)`, { textLen: text.length });
|
|
14402
|
+
return;
|
|
14403
|
+
}
|
|
14404
|
+
const query = extractQuery(text);
|
|
14405
|
+
if (!query)
|
|
14406
|
+
return;
|
|
14407
|
+
if (opts.cache.shouldSkip(query)) {
|
|
14408
|
+
log6?.debug?.(`[${PLUGIN_NAME9}] cache hit, skip`, { query });
|
|
14409
|
+
return;
|
|
14410
|
+
}
|
|
14411
|
+
if (inflight2.size >= INFLIGHT_CAP) {
|
|
14412
|
+
log6?.warn(`[${PLUGIN_NAME9}] inflight cap reached, skip`, {
|
|
14413
|
+
query,
|
|
14414
|
+
inflightSize: inflight2.size,
|
|
14415
|
+
cap: INFLIGHT_CAP,
|
|
14416
|
+
sessionId: ctx.sessionId
|
|
14417
|
+
});
|
|
14418
|
+
return;
|
|
14419
|
+
}
|
|
14420
|
+
const key = inflightKey(ctx.sessionId, query);
|
|
14421
|
+
inflight2.add(key);
|
|
14422
|
+
runKhSearchAndInject({ query, ctx, opts, mode }).catch((err) => {
|
|
14423
|
+
log6?.warn(`[${PLUGIN_NAME9}] runKhSearchAndInject 顶层兜底捕获`, {
|
|
14424
|
+
error: err instanceof Error ? err.message : String(err),
|
|
14425
|
+
query,
|
|
14426
|
+
sessionId: ctx.sessionId
|
|
14427
|
+
});
|
|
14428
|
+
}).finally(() => {
|
|
14429
|
+
inflight2.delete(key);
|
|
14430
|
+
});
|
|
14431
|
+
}
|
|
14432
|
+
logLifecycle(PLUGIN_NAME9, "import");
|
|
13816
14433
|
var sharedClient2 = new KhClient;
|
|
13817
14434
|
var sharedCache = new QueryCache(DEFAULT_CONFIG5.cacheTtlMs);
|
|
13818
14435
|
var khAutoContextServer = async (ctx) => {
|
|
13819
|
-
const log6 = makePluginLogger(
|
|
14436
|
+
const log6 = makePluginLogger(PLUGIN_NAME9);
|
|
13820
14437
|
const runtimeMode = resolveInjectionMode(sharedClient2);
|
|
13821
|
-
logLifecycle(
|
|
14438
|
+
logLifecycle(PLUGIN_NAME9, "activate", {
|
|
13822
14439
|
directory: ctx.directory,
|
|
13823
14440
|
minConfidence: DEFAULT_CONFIG5.minConfidence,
|
|
13824
14441
|
timeoutMs: DEFAULT_CONFIG5.timeoutMs,
|
|
@@ -13832,7 +14449,7 @@ var khAutoContextServer = async (ctx) => {
|
|
|
13832
14449
|
});
|
|
13833
14450
|
return {
|
|
13834
14451
|
"chat.message": async (input, output) => {
|
|
13835
|
-
await safeAsync(
|
|
14452
|
+
await safeAsync(PLUGIN_NAME9, "chat.message", async () => {
|
|
13836
14453
|
const text = extractUserText(output);
|
|
13837
14454
|
if (!text)
|
|
13838
14455
|
return;
|
|
@@ -13850,10 +14467,25 @@ var khAutoContextServer = async (ctx) => {
|
|
|
13850
14467
|
log: log6
|
|
13851
14468
|
}, { client: sharedClient2, cache: sharedCache, mode: runtimeMode });
|
|
13852
14469
|
});
|
|
14470
|
+
},
|
|
14471
|
+
event: async ({ event }) => {
|
|
14472
|
+
await safeAsync(PLUGIN_NAME9, "event", async () => {
|
|
14473
|
+
const e = event;
|
|
14474
|
+
if (e.type !== "session.idle")
|
|
14475
|
+
return;
|
|
14476
|
+
const props = e.properties;
|
|
14477
|
+
const sid = props?.sessionID;
|
|
14478
|
+
if (typeof sid !== "string" || !sid)
|
|
14479
|
+
return;
|
|
14480
|
+
sharedKhCache.onSessionEnd(sid);
|
|
14481
|
+
log6.debug?.(`[${PLUGIN_NAME9}] session.idle: cleared shared cache`, {
|
|
14482
|
+
sessionID: sid
|
|
14483
|
+
});
|
|
14484
|
+
});
|
|
13853
14485
|
}
|
|
13854
14486
|
};
|
|
13855
14487
|
};
|
|
13856
|
-
var
|
|
14488
|
+
var handler9 = khAutoContextServer;
|
|
13857
14489
|
|
|
13858
14490
|
// plugins/kh-reminder.ts
|
|
13859
14491
|
init_opencode_plugin_helpers();
|
|
@@ -13974,8 +14606,8 @@ async function condense(input, opts = {}) {
|
|
|
13974
14606
|
}
|
|
13975
14607
|
|
|
13976
14608
|
// plugins/kh-reminder.ts
|
|
13977
|
-
var
|
|
13978
|
-
logLifecycle(
|
|
14609
|
+
var PLUGIN_NAME10 = "kh-reminder";
|
|
14610
|
+
logLifecycle(PLUGIN_NAME10, "import", {});
|
|
13979
14611
|
var TRIGGER_WORDS_ZH = [
|
|
13980
14612
|
"怎么",
|
|
13981
14613
|
"怎样",
|
|
@@ -14129,7 +14761,7 @@ function handleObserve(raw, log6) {
|
|
|
14129
14761
|
const result = evaluate(ctx);
|
|
14130
14762
|
if (result.triggered && result.reason) {
|
|
14131
14763
|
const reasonStr = formatReason(result.reason);
|
|
14132
|
-
log6?.info(`[${
|
|
14764
|
+
log6?.info(`[${PLUGIN_NAME10}] ⚡ KH 提醒(observe-only) · session=${result.sessionId} · ${reasonStr}`, {
|
|
14133
14765
|
sessionId: result.sessionId,
|
|
14134
14766
|
reason: result.reason,
|
|
14135
14767
|
suggestion: "调用 smart_search 查项目历史;完成后用 save_chat_insight 沉淀"
|
|
@@ -14137,7 +14769,7 @@ function handleObserve(raw, log6) {
|
|
|
14137
14769
|
}
|
|
14138
14770
|
return result;
|
|
14139
14771
|
} catch (err) {
|
|
14140
|
-
log6?.warn(`[${
|
|
14772
|
+
log6?.warn(`[${PLUGIN_NAME10}] evaluate 异常(已隔离)`, {
|
|
14141
14773
|
error: err instanceof Error ? err.message : String(err)
|
|
14142
14774
|
});
|
|
14143
14775
|
return null;
|
|
@@ -14153,9 +14785,9 @@ function formatReason(r) {
|
|
|
14153
14785
|
return `no-search-in-recent-rounds (last ${r.rounds})`;
|
|
14154
14786
|
}
|
|
14155
14787
|
}
|
|
14156
|
-
var log6 = makePluginLogger(
|
|
14788
|
+
var log6 = makePluginLogger(PLUGIN_NAME10);
|
|
14157
14789
|
var khReminderServer = async (ctx) => {
|
|
14158
|
-
logLifecycle(
|
|
14790
|
+
logLifecycle(PLUGIN_NAME10, "activate", {
|
|
14159
14791
|
directory: ctx.directory,
|
|
14160
14792
|
threshold: DEFAULT_THRESHOLD,
|
|
14161
14793
|
cooldown_ms: COOLDOWN_MS,
|
|
@@ -14163,7 +14795,7 @@ var khReminderServer = async (ctx) => {
|
|
|
14163
14795
|
});
|
|
14164
14796
|
return {
|
|
14165
14797
|
"experimental.chat.messages.transform": async (_input, output) => {
|
|
14166
|
-
await safeAsync(
|
|
14798
|
+
await safeAsync(PLUGIN_NAME10, "experimental.chat.messages.transform", async () => {
|
|
14167
14799
|
const list = output.messages;
|
|
14168
14800
|
if (!Array.isArray(list) || list.length === 0)
|
|
14169
14801
|
return;
|
|
@@ -14182,7 +14814,7 @@ var khReminderServer = async (ctx) => {
|
|
|
14182
14814
|
const result = handleObserve({ messages: flat, sessionId }, log6);
|
|
14183
14815
|
if (!result)
|
|
14184
14816
|
return;
|
|
14185
|
-
safeWriteLog(
|
|
14817
|
+
safeWriteLog(PLUGIN_NAME10, {
|
|
14186
14818
|
hook: "experimental.chat.messages.transform",
|
|
14187
14819
|
mode: "observe-only",
|
|
14188
14820
|
sessionId: result.sessionId,
|
|
@@ -14195,7 +14827,7 @@ var khReminderServer = async (ctx) => {
|
|
|
14195
14827
|
}
|
|
14196
14828
|
};
|
|
14197
14829
|
};
|
|
14198
|
-
var
|
|
14830
|
+
var handler10 = khReminderServer;
|
|
14199
14831
|
|
|
14200
14832
|
// lib/memories.ts
|
|
14201
14833
|
import { promises as fs8 } from "node:fs";
|
|
@@ -14397,7 +15029,7 @@ function bagOfWordsScore(query, doc) {
|
|
|
14397
15029
|
|
|
14398
15030
|
// plugins/memories-context.ts
|
|
14399
15031
|
init_opencode_plugin_helpers();
|
|
14400
|
-
var
|
|
15032
|
+
var PLUGIN_NAME11 = "memories-context";
|
|
14401
15033
|
var INJECTION_MODE2 = "observe-only";
|
|
14402
15034
|
var DEFAULT_CONFIG6 = {
|
|
14403
15035
|
minTextLength: 8,
|
|
@@ -14495,14 +15127,14 @@ async function handleMessage3(raw, opts) {
|
|
|
14495
15127
|
return await handleDirective(dir, ctx, opts.memCfg, log7);
|
|
14496
15128
|
}
|
|
14497
15129
|
if (!shouldRecall(text, cfg)) {
|
|
14498
|
-
log7?.debug?.(`[${
|
|
15130
|
+
log7?.debug?.(`[${PLUGIN_NAME11}] skip (filter)`, { textLen: text.length });
|
|
14499
15131
|
return { kind: "noop", reason: "filtered", mode: INJECTION_MODE2 };
|
|
14500
15132
|
}
|
|
14501
15133
|
const query = extractQuery2(text);
|
|
14502
15134
|
if (!query)
|
|
14503
15135
|
return { kind: "noop", reason: "empty_query", mode: INJECTION_MODE2 };
|
|
14504
15136
|
if (cache2.shouldSkip(query)) {
|
|
14505
|
-
log7?.debug?.(`[${
|
|
15137
|
+
log7?.debug?.(`[${PLUGIN_NAME11}] cache hit`, { query });
|
|
14506
15138
|
return { kind: "noop", reason: "cache", mode: INJECTION_MODE2 };
|
|
14507
15139
|
}
|
|
14508
15140
|
const racer = opts.scheduler?.raceTimeout ?? (async (p, ms) => {
|
|
@@ -14527,7 +15159,7 @@ async function handleMessage3(raw, opts) {
|
|
|
14527
15159
|
}, opts.memCfg);
|
|
14528
15160
|
const result = await racer(injectPromise, cfg.timeoutMs);
|
|
14529
15161
|
if (result === "__timeout__") {
|
|
14530
|
-
log7?.warn(`[${
|
|
15162
|
+
log7?.warn(`[${PLUGIN_NAME11}] timeout`, { query, ms: cfg.timeoutMs });
|
|
14531
15163
|
cache2.record(query, 0);
|
|
14532
15164
|
return { kind: "noop", reason: "timeout", mode: INJECTION_MODE2 };
|
|
14533
15165
|
}
|
|
@@ -14539,13 +15171,13 @@ async function handleMessage3(raw, opts) {
|
|
|
14539
15171
|
try {
|
|
14540
15172
|
await ctx.injectContext(result.text);
|
|
14541
15173
|
} catch (err) {
|
|
14542
|
-
log7?.warn(`[${
|
|
15174
|
+
log7?.warn(`[${PLUGIN_NAME11}] injectContext threw`, {
|
|
14543
15175
|
error: err instanceof Error ? err.message : String(err)
|
|
14544
15176
|
});
|
|
14545
15177
|
}
|
|
14546
15178
|
}
|
|
14547
15179
|
cache2.record(query, result.recalled);
|
|
14548
|
-
log7?.info(`[${
|
|
15180
|
+
log7?.info(`[${PLUGIN_NAME11}] observe-only: ${result.used}/${result.recalled} memories ready`, { query, mode: INJECTION_MODE2 });
|
|
14549
15181
|
return { kind: "injected", payload: result, mode: INJECTION_MODE2 };
|
|
14550
15182
|
}
|
|
14551
15183
|
async function handleDirective(dir, ctx, memCfg, log7) {
|
|
@@ -14554,14 +15186,14 @@ async function handleDirective(dir, ctx, memCfg, log7) {
|
|
|
14554
15186
|
if (r.ok) {
|
|
14555
15187
|
const target = r.written_to === "kh" ? "KH" : "本地";
|
|
14556
15188
|
await safeReply(ctx, `\uD83E\uDDE0 已记住(${dir.scope} / ${target},id=${r.id})`);
|
|
14557
|
-
log7?.info(`[${
|
|
15189
|
+
log7?.info(`[${PLUGIN_NAME11}] /remember ok`, {
|
|
14558
15190
|
scope: dir.scope,
|
|
14559
15191
|
id: r.id,
|
|
14560
15192
|
mode: INJECTION_MODE2
|
|
14561
15193
|
});
|
|
14562
15194
|
} else {
|
|
14563
15195
|
await safeReply(ctx, `❌ 记忆失败:${r.error ?? "unknown"}`);
|
|
14564
|
-
log7?.warn(`[${
|
|
15196
|
+
log7?.warn(`[${PLUGIN_NAME11}] /remember failed`, { error: r.error });
|
|
14565
15197
|
}
|
|
14566
15198
|
return { kind: "remembered", result: r, mode: INJECTION_MODE2 };
|
|
14567
15199
|
}
|
|
@@ -14592,22 +15224,22 @@ async function safeReply(ctx, text) {
|
|
|
14592
15224
|
await ctx.reply(text);
|
|
14593
15225
|
} catch {}
|
|
14594
15226
|
}
|
|
14595
|
-
logLifecycle(
|
|
15227
|
+
logLifecycle(PLUGIN_NAME11, "import");
|
|
14596
15228
|
var sharedCache2 = new QueryCache2(DEFAULT_CONFIG6.cacheTtlMs);
|
|
14597
15229
|
function buildMemCfg(directory) {
|
|
14598
15230
|
return { projectRoot: directory };
|
|
14599
15231
|
}
|
|
14600
15232
|
var memoriesContextServer = async (ctx) => {
|
|
14601
|
-
const log7 = makePluginLogger(
|
|
15233
|
+
const log7 = makePluginLogger(PLUGIN_NAME11);
|
|
14602
15234
|
const memCfg = buildMemCfg(ctx.directory);
|
|
14603
|
-
logLifecycle(
|
|
15235
|
+
logLifecycle(PLUGIN_NAME11, "activate", {
|
|
14604
15236
|
directory: ctx.directory,
|
|
14605
15237
|
projectRoot: memCfg.projectRoot,
|
|
14606
15238
|
mode: INJECTION_MODE2
|
|
14607
15239
|
});
|
|
14608
15240
|
return {
|
|
14609
15241
|
"chat.message": async (input, output) => {
|
|
14610
|
-
await safeAsync(
|
|
15242
|
+
await safeAsync(PLUGIN_NAME11, "chat.message", async () => {
|
|
14611
15243
|
const text = extractUserText(output);
|
|
14612
15244
|
if (!text)
|
|
14613
15245
|
return;
|
|
@@ -14633,18 +15265,18 @@ var memoriesContextServer = async (ctx) => {
|
|
|
14633
15265
|
}
|
|
14634
15266
|
};
|
|
14635
15267
|
};
|
|
14636
|
-
var
|
|
15268
|
+
var handler11 = memoriesContextServer;
|
|
14637
15269
|
|
|
14638
15270
|
// plugins/model-fallback.ts
|
|
14639
15271
|
init_opencode_plugin_helpers();
|
|
14640
|
-
var
|
|
15272
|
+
var PLUGIN_NAME12 = "model-fallback";
|
|
14641
15273
|
var state2 = {
|
|
14642
15274
|
config: null,
|
|
14643
15275
|
configPath: null,
|
|
14644
15276
|
warnings: [],
|
|
14645
15277
|
error: null
|
|
14646
15278
|
};
|
|
14647
|
-
logLifecycle(
|
|
15279
|
+
logLifecycle(PLUGIN_NAME12, "import");
|
|
14648
15280
|
function loadOnce(root) {
|
|
14649
15281
|
if (state2.config !== null || state2.error !== null)
|
|
14650
15282
|
return;
|
|
@@ -14654,11 +15286,11 @@ function loadOnce(root) {
|
|
|
14654
15286
|
state2.configPath = r.path ?? null;
|
|
14655
15287
|
state2.warnings = r.warnings;
|
|
14656
15288
|
if (r.warnings.length > 0) {
|
|
14657
|
-
safeWriteLog(
|
|
15289
|
+
safeWriteLog(PLUGIN_NAME12, { phase: "load.warnings", warnings: r.warnings });
|
|
14658
15290
|
}
|
|
14659
15291
|
} else {
|
|
14660
15292
|
state2.error = r.error ?? "unknown_load_error";
|
|
14661
|
-
safeWriteLog(
|
|
15293
|
+
safeWriteLog(PLUGIN_NAME12, { phase: "load.failed", error: state2.error, path: r.path });
|
|
14662
15294
|
}
|
|
14663
15295
|
}
|
|
14664
15296
|
var MODEL_ERR_PATTERNS = [
|
|
@@ -14703,9 +15335,9 @@ fallback 链已用尽:${meta.chain.join(" → ")}`;
|
|
|
14703
15335
|
完整链:${meta.chain.join(" → ")}`;
|
|
14704
15336
|
}
|
|
14705
15337
|
var modelFallbackServer = async (ctx) => {
|
|
14706
|
-
const log7 = makePluginLogger(
|
|
15338
|
+
const log7 = makePluginLogger(PLUGIN_NAME12);
|
|
14707
15339
|
loadOnce(ctx.directory ?? process.cwd());
|
|
14708
|
-
logLifecycle(
|
|
15340
|
+
logLifecycle(PLUGIN_NAME12, "activate", {
|
|
14709
15341
|
directory: ctx.directory,
|
|
14710
15342
|
config_path: state2.configPath,
|
|
14711
15343
|
config_loaded: state2.config !== null,
|
|
@@ -14715,7 +15347,7 @@ var modelFallbackServer = async (ctx) => {
|
|
|
14715
15347
|
});
|
|
14716
15348
|
return {
|
|
14717
15349
|
"chat.params": async (input, output) => {
|
|
14718
|
-
await safeAsync(
|
|
15350
|
+
await safeAsync(PLUGIN_NAME12, "chat.params", async () => {
|
|
14719
15351
|
if (!state2.config)
|
|
14720
15352
|
return;
|
|
14721
15353
|
const agent = input.agent;
|
|
@@ -14736,7 +15368,7 @@ var modelFallbackServer = async (ctx) => {
|
|
|
14736
15368
|
next: meta.next_fallback,
|
|
14737
15369
|
source: meta.source
|
|
14738
15370
|
};
|
|
14739
|
-
safeWriteLog(
|
|
15371
|
+
safeWriteLog(PLUGIN_NAME12, {
|
|
14740
15372
|
hook: "chat.params",
|
|
14741
15373
|
agent,
|
|
14742
15374
|
model: currentModel,
|
|
@@ -14746,7 +15378,7 @@ var modelFallbackServer = async (ctx) => {
|
|
|
14746
15378
|
});
|
|
14747
15379
|
},
|
|
14748
15380
|
event: async ({ event }) => {
|
|
14749
|
-
await safeAsync(
|
|
15381
|
+
await safeAsync(PLUGIN_NAME12, "event", async () => {
|
|
14750
15382
|
if (!state2.config)
|
|
14751
15383
|
return;
|
|
14752
15384
|
const e = event;
|
|
@@ -14762,8 +15394,8 @@ var modelFallbackServer = async (ctx) => {
|
|
|
14762
15394
|
const model = props.model ?? "unknown/unknown";
|
|
14763
15395
|
const meta = buildFallbackMeta(state2.config, agent, model);
|
|
14764
15396
|
const suggestion = meta ? buildSuggestion(meta, String(message)) : `⚠️ ${agent}/${model} 失败:${message}`;
|
|
14765
|
-
log7.warn(`[${
|
|
14766
|
-
safeWriteLog(
|
|
15397
|
+
log7.warn(`[${PLUGIN_NAME12}] ${suggestion}`);
|
|
15398
|
+
safeWriteLog(PLUGIN_NAME12, {
|
|
14767
15399
|
hook: "event.error",
|
|
14768
15400
|
eventType: e.type,
|
|
14769
15401
|
agent,
|
|
@@ -14775,19 +15407,24 @@ var modelFallbackServer = async (ctx) => {
|
|
|
14775
15407
|
}
|
|
14776
15408
|
};
|
|
14777
15409
|
};
|
|
14778
|
-
var
|
|
15410
|
+
var handler12 = modelFallbackServer;
|
|
14779
15411
|
|
|
14780
15412
|
// plugins/subtask-heartbeat.ts
|
|
14781
15413
|
init_opencode_plugin_helpers();
|
|
14782
|
-
var
|
|
14783
|
-
logLifecycle(
|
|
15414
|
+
var PLUGIN_NAME13 = "subtask-heartbeat";
|
|
15415
|
+
logLifecycle(PLUGIN_NAME13, "import", {});
|
|
14784
15416
|
var HEARTBEAT_INTERVAL_MS2 = 30000;
|
|
14785
15417
|
var HEARTBEAT_DEBOUNCE_MS = 25000;
|
|
14786
15418
|
var TOAST_DURATION_MS3 = 5000;
|
|
14787
15419
|
var START_TOAST_DURATION_MS = 2000;
|
|
14788
|
-
var
|
|
15420
|
+
var PENDING_TASK_TTL_MS = 60000;
|
|
15421
|
+
var PENDING_TASK_MAX_PARENTS = 64;
|
|
15422
|
+
var PENDING_TASK_MAX_PER_PARENT = 16;
|
|
15423
|
+
var DESCRIPTION_MAX_LEN = 60;
|
|
15424
|
+
var inflight3 = new Map;
|
|
15425
|
+
var pendingTask = new Map;
|
|
14789
15426
|
function _snapshotInflight() {
|
|
14790
|
-
return [...
|
|
15427
|
+
return [...inflight3.values()].map((r) => ({ ...r }));
|
|
14791
15428
|
}
|
|
14792
15429
|
function getInflightSnapshot() {
|
|
14793
15430
|
return _snapshotInflight();
|
|
@@ -14829,20 +15466,89 @@ function extractEndedSessionID(event) {
|
|
|
14829
15466
|
}
|
|
14830
15467
|
return null;
|
|
14831
15468
|
}
|
|
15469
|
+
function extractTaskArgs(args) {
|
|
15470
|
+
if (!args || typeof args !== "object")
|
|
15471
|
+
return null;
|
|
15472
|
+
const a = args;
|
|
15473
|
+
const rawDesc = typeof a["description"] === "string" ? a["description"] : null;
|
|
15474
|
+
const rawPrompt = typeof a["prompt"] === "string" ? a["prompt"] : null;
|
|
15475
|
+
const description26 = rawDesc ?? (rawPrompt ? rawPrompt.slice(0, 60) : null);
|
|
15476
|
+
const subagentType = typeof a["subagent_type"] === "string" && a["subagent_type"] || typeof a["agent"] === "string" && a["agent"] || typeof a["agentType"] === "string" && a["agentType"] || typeof a["agent_type"] === "string" && a["agent_type"] || null;
|
|
15477
|
+
if (!description26 && !subagentType)
|
|
15478
|
+
return null;
|
|
15479
|
+
return { description: description26, subagentType };
|
|
15480
|
+
}
|
|
15481
|
+
function enqueuePendingTask(parentID, entry, now = Date.now()) {
|
|
15482
|
+
const ts = entry.ts ?? now;
|
|
15483
|
+
let bucket = pendingTask.get(parentID);
|
|
15484
|
+
if (!bucket) {
|
|
15485
|
+
if (pendingTask.size >= PENDING_TASK_MAX_PARENTS) {
|
|
15486
|
+
let oldestKey = null;
|
|
15487
|
+
let oldestTs = Number.POSITIVE_INFINITY;
|
|
15488
|
+
for (const [k, v] of pendingTask.entries()) {
|
|
15489
|
+
const headTs = v[0]?.ts ?? Number.POSITIVE_INFINITY;
|
|
15490
|
+
if (headTs < oldestTs) {
|
|
15491
|
+
oldestTs = headTs;
|
|
15492
|
+
oldestKey = k;
|
|
15493
|
+
}
|
|
15494
|
+
}
|
|
15495
|
+
if (oldestKey !== null)
|
|
15496
|
+
pendingTask.delete(oldestKey);
|
|
15497
|
+
}
|
|
15498
|
+
bucket = [];
|
|
15499
|
+
pendingTask.set(parentID, bucket);
|
|
15500
|
+
}
|
|
15501
|
+
bucket.push({ agent: entry.agent, description: entry.description, ts });
|
|
15502
|
+
while (bucket.length > PENDING_TASK_MAX_PER_PARENT) {
|
|
15503
|
+
bucket.shift();
|
|
15504
|
+
}
|
|
15505
|
+
}
|
|
15506
|
+
function dequeuePendingTask(parentID, now = Date.now()) {
|
|
15507
|
+
const bucket = pendingTask.get(parentID);
|
|
15508
|
+
if (!bucket || bucket.length === 0) {
|
|
15509
|
+
if (bucket)
|
|
15510
|
+
pendingTask.delete(parentID);
|
|
15511
|
+
return null;
|
|
15512
|
+
}
|
|
15513
|
+
while (bucket.length > 0 && now - bucket[0].ts > PENDING_TASK_TTL_MS) {
|
|
15514
|
+
bucket.shift();
|
|
15515
|
+
}
|
|
15516
|
+
if (bucket.length === 0) {
|
|
15517
|
+
pendingTask.delete(parentID);
|
|
15518
|
+
return null;
|
|
15519
|
+
}
|
|
15520
|
+
const entry = bucket.shift();
|
|
15521
|
+
if (bucket.length === 0)
|
|
15522
|
+
pendingTask.delete(parentID);
|
|
15523
|
+
return entry;
|
|
15524
|
+
}
|
|
15525
|
+
function sweepExpiredPendingTasks(now = Date.now()) {
|
|
15526
|
+
let removed = 0;
|
|
15527
|
+
for (const [parentID, bucket] of [...pendingTask.entries()]) {
|
|
15528
|
+
while (bucket.length > 0 && now - bucket[0].ts > PENDING_TASK_TTL_MS) {
|
|
15529
|
+
bucket.shift();
|
|
15530
|
+
removed++;
|
|
15531
|
+
}
|
|
15532
|
+
if (bucket.length === 0)
|
|
15533
|
+
pendingTask.delete(parentID);
|
|
15534
|
+
}
|
|
15535
|
+
return removed;
|
|
15536
|
+
}
|
|
14832
15537
|
function registerInflight(payload, now = Date.now()) {
|
|
14833
15538
|
const r = {
|
|
14834
15539
|
childID: payload.childID,
|
|
14835
15540
|
parentID: payload.parentID,
|
|
14836
15541
|
agent: payload.agent,
|
|
15542
|
+
description: payload.description ?? null,
|
|
14837
15543
|
startedAt: now,
|
|
14838
15544
|
lastBeatAt: now,
|
|
14839
15545
|
lastTool: null
|
|
14840
15546
|
};
|
|
14841
|
-
|
|
15547
|
+
inflight3.set(payload.childID, r);
|
|
14842
15548
|
return r;
|
|
14843
15549
|
}
|
|
14844
15550
|
function recordToolBeat(sessionID, tool2, now = Date.now()) {
|
|
14845
|
-
const r =
|
|
15551
|
+
const r = inflight3.get(sessionID);
|
|
14846
15552
|
if (!r)
|
|
14847
15553
|
return null;
|
|
14848
15554
|
r.lastBeatAt = now;
|
|
@@ -14850,15 +15556,15 @@ function recordToolBeat(sessionID, tool2, now = Date.now()) {
|
|
|
14850
15556
|
return r;
|
|
14851
15557
|
}
|
|
14852
15558
|
function clearInflight2(sessionID) {
|
|
14853
|
-
const r =
|
|
15559
|
+
const r = inflight3.get(sessionID);
|
|
14854
15560
|
if (!r)
|
|
14855
15561
|
return null;
|
|
14856
|
-
|
|
15562
|
+
inflight3.delete(sessionID);
|
|
14857
15563
|
return r;
|
|
14858
15564
|
}
|
|
14859
15565
|
function pickHeartbeats(now = Date.now()) {
|
|
14860
15566
|
const out = [];
|
|
14861
|
-
for (const r of
|
|
15567
|
+
for (const r of inflight3.values()) {
|
|
14862
15568
|
if (now - r.lastBeatAt >= HEARTBEAT_DEBOUNCE_MS)
|
|
14863
15569
|
out.push(r);
|
|
14864
15570
|
}
|
|
@@ -14870,10 +15576,32 @@ function fmtElapsed(ms) {
|
|
|
14870
15576
|
const s = total % 60;
|
|
14871
15577
|
return m > 0 ? `${m}m${s.toString().padStart(2, "0")}s` : `${s}s`;
|
|
14872
15578
|
}
|
|
15579
|
+
function titleCase(s) {
|
|
15580
|
+
if (!s)
|
|
15581
|
+
return s;
|
|
15582
|
+
return s.charAt(0).toUpperCase() + s.slice(1);
|
|
15583
|
+
}
|
|
15584
|
+
function sanitizeDescription(s) {
|
|
15585
|
+
const collapsed = s.replace(/[\r\n\t]+/g, " ").replace(/\s+/g, " ").trim();
|
|
15586
|
+
if (collapsed.length <= DESCRIPTION_MAX_LEN)
|
|
15587
|
+
return collapsed;
|
|
15588
|
+
return collapsed.slice(0, DESCRIPTION_MAX_LEN) + "…";
|
|
15589
|
+
}
|
|
14873
15590
|
function buildStartToast(r) {
|
|
14874
|
-
|
|
15591
|
+
if (r.agent && r.description) {
|
|
15592
|
+
return {
|
|
15593
|
+
message: `\uD83D\uDE80 ${titleCase(r.agent)} 启动 — ${sanitizeDescription(r.description)}`,
|
|
15594
|
+
variant: "info"
|
|
15595
|
+
};
|
|
15596
|
+
}
|
|
15597
|
+
if (r.agent) {
|
|
15598
|
+
return {
|
|
15599
|
+
message: `\uD83D\uDE80 ${titleCase(r.agent)} 启动`,
|
|
15600
|
+
variant: "info"
|
|
15601
|
+
};
|
|
15602
|
+
}
|
|
14875
15603
|
return {
|
|
14876
|
-
message: `\uD83D\uDE80 子 session 启动:
|
|
15604
|
+
message: `\uD83D\uDE80 子 session 启动: subagent`,
|
|
14877
15605
|
variant: "info"
|
|
14878
15606
|
};
|
|
14879
15607
|
}
|
|
@@ -14886,15 +15614,32 @@ function buildHeartbeatToast(r, now = Date.now()) {
|
|
|
14886
15614
|
};
|
|
14887
15615
|
}
|
|
14888
15616
|
function buildEndToast(r, type, now = Date.now()) {
|
|
14889
|
-
const who = r.agent ?? "subagent";
|
|
14890
15617
|
const elapsed = fmtElapsed(now - r.startedAt);
|
|
14891
|
-
|
|
14892
|
-
|
|
15618
|
+
const variant = type === "session.error" || type === "session.deleted" ? "error" : "success";
|
|
15619
|
+
const emoji = type === "session.error" ? "❌" : type === "session.deleted" ? "\uD83D\uDDD1️" : "✅";
|
|
15620
|
+
const verb = type === "session.error" ? "失败" : type === "session.deleted" ? "被取消" : "完成";
|
|
15621
|
+
if (r.agent && r.description) {
|
|
15622
|
+
if (type === "session.idle" || type !== "session.error" && type !== "session.deleted") {
|
|
15623
|
+
return {
|
|
15624
|
+
message: `${emoji} ${titleCase(r.agent)} Task — ${sanitizeDescription(r.description)} (${elapsed})`,
|
|
15625
|
+
variant
|
|
15626
|
+
};
|
|
15627
|
+
}
|
|
15628
|
+
return {
|
|
15629
|
+
message: `${emoji} ${titleCase(r.agent)} ${verb} — ${sanitizeDescription(r.description)} (${elapsed})`,
|
|
15630
|
+
variant
|
|
15631
|
+
};
|
|
14893
15632
|
}
|
|
14894
|
-
if (
|
|
14895
|
-
return {
|
|
15633
|
+
if (r.agent) {
|
|
15634
|
+
return {
|
|
15635
|
+
message: `${emoji} ${titleCase(r.agent)} ${verb} (${elapsed})`,
|
|
15636
|
+
variant
|
|
15637
|
+
};
|
|
14896
15638
|
}
|
|
14897
|
-
return {
|
|
15639
|
+
return {
|
|
15640
|
+
message: `${emoji} subagent ${verb} (${elapsed})`,
|
|
15641
|
+
variant
|
|
15642
|
+
};
|
|
14898
15643
|
}
|
|
14899
15644
|
function normalizeVariant3(raw) {
|
|
14900
15645
|
if (raw === "info" || raw === "warning")
|
|
@@ -14923,22 +15668,26 @@ async function showToast3(client, payload, log7) {
|
|
|
14923
15668
|
return false;
|
|
14924
15669
|
}
|
|
14925
15670
|
}
|
|
14926
|
-
var log7 = makePluginLogger(
|
|
15671
|
+
var log7 = makePluginLogger(PLUGIN_NAME13);
|
|
14927
15672
|
var subtaskHeartbeatServer = async (ctx) => {
|
|
14928
|
-
logLifecycle(
|
|
15673
|
+
logLifecycle(PLUGIN_NAME13, "activate", {
|
|
14929
15674
|
directory: ctx.directory,
|
|
14930
15675
|
intervalMs: HEARTBEAT_INTERVAL_MS2
|
|
14931
15676
|
});
|
|
14932
15677
|
const client = ctx.client;
|
|
14933
15678
|
const interval = setInterval(() => {
|
|
14934
|
-
safeAsync(
|
|
15679
|
+
safeAsync(PLUGIN_NAME13, "interval", async () => {
|
|
15680
|
+
const swept = sweepExpiredPendingTasks();
|
|
15681
|
+
if (swept > 0) {
|
|
15682
|
+
safeWriteLog(PLUGIN_NAME13, { hook: "interval", pending_task_swept: swept });
|
|
15683
|
+
}
|
|
14935
15684
|
const beats = pickHeartbeats();
|
|
14936
15685
|
if (beats.length === 0)
|
|
14937
15686
|
return;
|
|
14938
15687
|
for (const r of beats) {
|
|
14939
15688
|
const t = buildHeartbeatToast(r);
|
|
14940
15689
|
const sent = await showToast3(client, t, log7);
|
|
14941
|
-
safeWriteLog(
|
|
15690
|
+
safeWriteLog(PLUGIN_NAME13, {
|
|
14942
15691
|
hook: "interval",
|
|
14943
15692
|
child: r.childID,
|
|
14944
15693
|
parent: r.parentID,
|
|
@@ -14955,23 +15704,33 @@ var subtaskHeartbeatServer = async (ctx) => {
|
|
|
14955
15704
|
}
|
|
14956
15705
|
return {
|
|
14957
15706
|
event: async ({ event }) => {
|
|
14958
|
-
await safeAsync(
|
|
15707
|
+
await safeAsync(PLUGIN_NAME13, "event", async () => {
|
|
14959
15708
|
const created = extractCreatedChild(event);
|
|
14960
15709
|
if (created) {
|
|
14961
|
-
const
|
|
14962
|
-
|
|
15710
|
+
const pending = dequeuePendingTask(created.parentID);
|
|
15711
|
+
const record = registerInflight({
|
|
15712
|
+
childID: created.childID,
|
|
15713
|
+
parentID: created.parentID,
|
|
15714
|
+
agent: pending?.agent ?? created.agent,
|
|
15715
|
+
description: pending?.description ?? null
|
|
15716
|
+
});
|
|
15717
|
+
safeWriteLog(PLUGIN_NAME13, {
|
|
14963
15718
|
hook: "event",
|
|
14964
15719
|
type: "session.created",
|
|
14965
15720
|
child: created.childID,
|
|
14966
|
-
parent: created.parentID
|
|
15721
|
+
parent: created.parentID,
|
|
15722
|
+
pending_task_matched: pending !== null,
|
|
15723
|
+
agent: record.agent,
|
|
15724
|
+
description_len: record.description?.length ?? 0
|
|
14967
15725
|
});
|
|
14968
15726
|
const startToast = buildStartToast(record);
|
|
14969
15727
|
const sent = await showToast3(client, { ...startToast, duration: START_TOAST_DURATION_MS }, log7);
|
|
14970
|
-
safeWriteLog(
|
|
15728
|
+
safeWriteLog(PLUGIN_NAME13, {
|
|
14971
15729
|
hook: "event",
|
|
14972
15730
|
type: "session.created.toast",
|
|
14973
15731
|
child: created.childID,
|
|
14974
|
-
toast_sent: sent
|
|
15732
|
+
toast_sent: sent,
|
|
15733
|
+
start_toast_message: startToast.message
|
|
14975
15734
|
});
|
|
14976
15735
|
return;
|
|
14977
15736
|
}
|
|
@@ -14981,7 +15740,7 @@ var subtaskHeartbeatServer = async (ctx) => {
|
|
|
14981
15740
|
if (r) {
|
|
14982
15741
|
const t = buildEndToast(r, ended.type);
|
|
14983
15742
|
const sent = await showToast3(client, t, log7);
|
|
14984
|
-
safeWriteLog(
|
|
15743
|
+
safeWriteLog(PLUGIN_NAME13, {
|
|
14985
15744
|
hook: "event",
|
|
14986
15745
|
type: ended.type,
|
|
14987
15746
|
child: r.childID,
|
|
@@ -14993,21 +15752,48 @@ var subtaskHeartbeatServer = async (ctx) => {
|
|
|
14993
15752
|
}
|
|
14994
15753
|
});
|
|
14995
15754
|
},
|
|
14996
|
-
"tool.execute.before": async (input) => {
|
|
14997
|
-
|
|
15755
|
+
"tool.execute.before": async (input, output) => {
|
|
15756
|
+
const isTaskTool = input?.tool === "task";
|
|
15757
|
+
if (inflight3.size === 0 && !isTaskTool)
|
|
15758
|
+
return;
|
|
15759
|
+
await safeAsync(PLUGIN_NAME13, "tool.execute.before", async () => {
|
|
14998
15760
|
if (!input || typeof input.sessionID !== "string" || typeof input.tool !== "string")
|
|
14999
15761
|
return;
|
|
15000
|
-
|
|
15762
|
+
if (isTaskTool) {
|
|
15763
|
+
const args = output?.args ?? null;
|
|
15764
|
+
safeWriteLog(PLUGIN_NAME13, {
|
|
15765
|
+
hook: "tool.execute.before.task",
|
|
15766
|
+
sessionID: input.sessionID,
|
|
15767
|
+
args_keys: args && typeof args === "object" ? Object.keys(args) : null,
|
|
15768
|
+
args_raw: args
|
|
15769
|
+
});
|
|
15770
|
+
const extracted = extractTaskArgs(args);
|
|
15771
|
+
if (extracted) {
|
|
15772
|
+
enqueuePendingTask(input.sessionID, {
|
|
15773
|
+
agent: extracted.subagentType,
|
|
15774
|
+
description: extracted.description
|
|
15775
|
+
});
|
|
15776
|
+
safeWriteLog(PLUGIN_NAME13, {
|
|
15777
|
+
hook: "tool.execute.before.task.enqueued",
|
|
15778
|
+
parent: input.sessionID,
|
|
15779
|
+
agent: extracted.subagentType,
|
|
15780
|
+
description_len: extracted.description?.length ?? 0
|
|
15781
|
+
});
|
|
15782
|
+
}
|
|
15783
|
+
}
|
|
15784
|
+
if (inflight3.has(input.sessionID)) {
|
|
15785
|
+
recordToolBeat(input.sessionID, input.tool);
|
|
15786
|
+
}
|
|
15001
15787
|
});
|
|
15002
15788
|
}
|
|
15003
15789
|
};
|
|
15004
15790
|
};
|
|
15005
|
-
var
|
|
15791
|
+
var handler13 = subtaskHeartbeatServer;
|
|
15006
15792
|
|
|
15007
15793
|
// plugins/parallel-status.ts
|
|
15008
15794
|
init_opencode_plugin_helpers();
|
|
15009
|
-
var
|
|
15010
|
-
logLifecycle(
|
|
15795
|
+
var PLUGIN_NAME14 = "parallel-status";
|
|
15796
|
+
logLifecycle(PLUGIN_NAME14, "import");
|
|
15011
15797
|
var ID_MAX_LEN = 16;
|
|
15012
15798
|
var ID_KEEP_LEN = 13;
|
|
15013
15799
|
function shortId(s) {
|
|
@@ -15039,8 +15825,8 @@ function formatInflightMarkdown(snapshot, now = Date.now()) {
|
|
|
15039
15825
|
`);
|
|
15040
15826
|
}
|
|
15041
15827
|
var parallelStatusServer = async (ctx) => {
|
|
15042
|
-
const log8 = makePluginLogger(
|
|
15043
|
-
logLifecycle(
|
|
15828
|
+
const log8 = makePluginLogger(PLUGIN_NAME14);
|
|
15829
|
+
logLifecycle(PLUGIN_NAME14, "activate", { directory: ctx.directory });
|
|
15044
15830
|
return {
|
|
15045
15831
|
"command.execute.before": async (input, output) => {
|
|
15046
15832
|
try {
|
|
@@ -15059,21 +15845,21 @@ var parallelStatusServer = async (ctx) => {
|
|
|
15059
15845
|
synthetic: false
|
|
15060
15846
|
});
|
|
15061
15847
|
}
|
|
15062
|
-
log8.info(`[${
|
|
15848
|
+
log8.info(`[${PLUGIN_NAME14}] 已回写 ${snapshot.length} 条 inflight`);
|
|
15063
15849
|
} catch (err) {
|
|
15064
|
-
log8.error(`[${
|
|
15850
|
+
log8.error(`[${PLUGIN_NAME14}] command.execute.before 异常(已隔离)`, {
|
|
15065
15851
|
error: err instanceof Error ? err.message : String(err)
|
|
15066
15852
|
});
|
|
15067
15853
|
}
|
|
15068
15854
|
}
|
|
15069
15855
|
};
|
|
15070
15856
|
};
|
|
15071
|
-
var
|
|
15857
|
+
var handler14 = parallelStatusServer;
|
|
15072
15858
|
|
|
15073
15859
|
// plugins/pwsh-utf8.ts
|
|
15074
15860
|
init_opencode_plugin_helpers();
|
|
15075
|
-
var
|
|
15076
|
-
logLifecycle(
|
|
15861
|
+
var PLUGIN_NAME15 = "pwsh-utf8";
|
|
15862
|
+
logLifecycle(PLUGIN_NAME15, "import", {});
|
|
15077
15863
|
var PRELUDE = "chcp 65001 *> $null; " + "[Console]::OutputEncoding = [System.Text.UTF8Encoding]::new(); " + "$OutputEncoding = [System.Text.UTF8Encoding]::new(); ";
|
|
15078
15864
|
function prependUtf8Prelude(command) {
|
|
15079
15865
|
if (typeof command !== "string")
|
|
@@ -15086,14 +15872,15 @@ function prependUtf8Prelude(command) {
|
|
|
15086
15872
|
return command;
|
|
15087
15873
|
return PRELUDE + command;
|
|
15088
15874
|
}
|
|
15089
|
-
var
|
|
15875
|
+
var handler15 = async (_ctx) => {
|
|
15090
15876
|
const enabled = process.platform === "win32" && process.env.CODEFORGE_DISABLE_PWSH_UTF8 !== "1";
|
|
15091
|
-
|
|
15877
|
+
const reason = enabled ? "win32" : process.platform !== "win32" ? "non-win32" : "disabled-by-env";
|
|
15878
|
+
logLifecycle(PLUGIN_NAME15, "activate", { enabled, platform: process.platform, reason });
|
|
15092
15879
|
if (!enabled)
|
|
15093
15880
|
return {};
|
|
15094
15881
|
return {
|
|
15095
15882
|
"tool.execute.before": async (input, output) => {
|
|
15096
|
-
await safeAsync(
|
|
15883
|
+
await safeAsync(PLUGIN_NAME15, "tool.execute.before", async () => {
|
|
15097
15884
|
if (input.tool !== "bash")
|
|
15098
15885
|
return;
|
|
15099
15886
|
const args = output.args ?? {};
|
|
@@ -15102,7 +15889,7 @@ var handler16 = async (_ctx) => {
|
|
|
15102
15889
|
if (next !== undefined && next !== original) {
|
|
15103
15890
|
args["command"] = next;
|
|
15104
15891
|
output.args = args;
|
|
15105
|
-
safeWriteLog(
|
|
15892
|
+
safeWriteLog(PLUGIN_NAME15, {
|
|
15106
15893
|
hook: "tool.execute.before",
|
|
15107
15894
|
tool: input.tool,
|
|
15108
15895
|
callID: input.callID,
|
|
@@ -15295,7 +16082,7 @@ function buildRecoveryPlan(events, meta = { id: null }, opts = {}) {
|
|
|
15295
16082
|
});
|
|
15296
16083
|
}
|
|
15297
16084
|
}
|
|
15298
|
-
const
|
|
16085
|
+
const inflight4 = [...toolMap.values()].sort((a, b) => b.last_ts - a.last_ts);
|
|
15299
16086
|
const proposed = window.some((t) => PENDING_CHANGES_TOOLS.has(t.tool));
|
|
15300
16087
|
const applied = window.some((t) => APPLY_TOOLS.has(t.tool) && t.ok);
|
|
15301
16088
|
const pending_changes_likely = proposed && !applied;
|
|
@@ -15319,7 +16106,7 @@ function buildRecoveryPlan(events, meta = { id: null }, opts = {}) {
|
|
|
15319
16106
|
idleMs,
|
|
15320
16107
|
lastUser,
|
|
15321
16108
|
lastAgent,
|
|
15322
|
-
inflight:
|
|
16109
|
+
inflight: inflight4,
|
|
15323
16110
|
pending_changes_likely,
|
|
15324
16111
|
open_subtasks_likely,
|
|
15325
16112
|
reason
|
|
@@ -15330,7 +16117,7 @@ function buildRecoveryPlan(events, meta = { id: null }, opts = {}) {
|
|
|
15330
16117
|
idle_ms: idleMs,
|
|
15331
16118
|
last_user_intent: lastUser,
|
|
15332
16119
|
last_agent: lastAgent,
|
|
15333
|
-
inflight_tools:
|
|
16120
|
+
inflight_tools: inflight4,
|
|
15334
16121
|
pending_changes_likely,
|
|
15335
16122
|
open_subtasks_likely,
|
|
15336
16123
|
summary
|
|
@@ -15435,8 +16222,8 @@ function isRecoveryWorthShowing(plan) {
|
|
|
15435
16222
|
}
|
|
15436
16223
|
|
|
15437
16224
|
// plugins/session-recovery.ts
|
|
15438
|
-
var
|
|
15439
|
-
logLifecycle(
|
|
16225
|
+
var PLUGIN_NAME16 = "session-recovery";
|
|
16226
|
+
logLifecycle(PLUGIN_NAME16, "import", {});
|
|
15440
16227
|
async function processSessionStart(currentSessionId, opts = {}) {
|
|
15441
16228
|
if (opts.disabled) {
|
|
15442
16229
|
return { ok: true, injected: false, reason: "disabled" };
|
|
@@ -15446,7 +16233,7 @@ async function processSessionStart(currentSessionId, opts = {}) {
|
|
|
15446
16233
|
excludeIds.add(currentSessionId);
|
|
15447
16234
|
const r = await scanLastSession({ ...opts, excludeIds: [...excludeIds] });
|
|
15448
16235
|
if (!r.ok) {
|
|
15449
|
-
opts.log?.warn?.(`[${
|
|
16236
|
+
opts.log?.warn?.(`[${PLUGIN_NAME16}] 扫描失败:${r.error}`);
|
|
15450
16237
|
return { ok: false, injected: false, reason: "scan_error", error: r.error };
|
|
15451
16238
|
}
|
|
15452
16239
|
const plan = r.plan;
|
|
@@ -15460,7 +16247,7 @@ async function processSessionStart(currentSessionId, opts = {}) {
|
|
|
15460
16247
|
await opts.injectRecovery(injection);
|
|
15461
16248
|
} catch (err) {
|
|
15462
16249
|
const msg = err instanceof Error ? err.message : String(err);
|
|
15463
|
-
opts.log?.warn?.(`[${
|
|
16250
|
+
opts.log?.warn?.(`[${PLUGIN_NAME16}] injectRecovery 异常:${msg}`);
|
|
15464
16251
|
return { ok: false, injected: false, plan, reason: "inject_error", error: msg };
|
|
15465
16252
|
}
|
|
15466
16253
|
}
|
|
@@ -15489,13 +16276,13 @@ function renderPrompt(plan) {
|
|
|
15489
16276
|
return lines.join(`
|
|
15490
16277
|
`);
|
|
15491
16278
|
}
|
|
15492
|
-
var log8 = makePluginLogger(
|
|
16279
|
+
var log8 = makePluginLogger(PLUGIN_NAME16);
|
|
15493
16280
|
var _lastInjection = null;
|
|
15494
16281
|
var sessionRecoveryServer = async (ctx) => {
|
|
15495
|
-
logLifecycle(
|
|
16282
|
+
logLifecycle(PLUGIN_NAME16, "activate", { directory: ctx.directory });
|
|
15496
16283
|
return {
|
|
15497
16284
|
event: async ({ event }) => {
|
|
15498
|
-
await safeAsync(
|
|
16285
|
+
await safeAsync(PLUGIN_NAME16, "event", async () => {
|
|
15499
16286
|
const e = event;
|
|
15500
16287
|
if (!e || typeof e.type !== "string")
|
|
15501
16288
|
return;
|
|
@@ -15510,7 +16297,7 @@ var sessionRecoveryServer = async (ctx) => {
|
|
|
15510
16297
|
_lastInjection = inj;
|
|
15511
16298
|
}
|
|
15512
16299
|
});
|
|
15513
|
-
safeWriteLog(
|
|
16300
|
+
safeWriteLog(PLUGIN_NAME16, {
|
|
15514
16301
|
hook: "event",
|
|
15515
16302
|
type: "session.start",
|
|
15516
16303
|
ok: r.ok,
|
|
@@ -15519,13 +16306,13 @@ var sessionRecoveryServer = async (ctx) => {
|
|
|
15519
16306
|
last_session_id: r.plan?.last_session_id
|
|
15520
16307
|
});
|
|
15521
16308
|
if (r.injected && r.plan) {
|
|
15522
|
-
log8.info(`[${
|
|
16309
|
+
log8.info(`[${PLUGIN_NAME16}] 注入恢复提示(last=${r.plan.last_session_id?.slice(0, 8) ?? "?"}, reason=${r.plan.reason})`);
|
|
15523
16310
|
}
|
|
15524
16311
|
});
|
|
15525
16312
|
}
|
|
15526
16313
|
};
|
|
15527
16314
|
};
|
|
15528
|
-
var
|
|
16315
|
+
var handler16 = sessionRecoveryServer;
|
|
15529
16316
|
|
|
15530
16317
|
// plugins/subtasks.ts
|
|
15531
16318
|
import { promises as fs10 } from "node:fs";
|
|
@@ -16050,7 +16837,7 @@ async function sendParentNotice(client, sessionID, text, opts = {}) {
|
|
|
16050
16837
|
// plugins/subtasks.ts
|
|
16051
16838
|
init_opencode_plugin_helpers();
|
|
16052
16839
|
init_runtime_paths();
|
|
16053
|
-
var
|
|
16840
|
+
var PLUGIN_NAME17 = "subtasks";
|
|
16054
16841
|
function getLogFile(root = process.cwd()) {
|
|
16055
16842
|
return path13.join(runtimeDir(root), "logs", "subtasks.log");
|
|
16056
16843
|
}
|
|
@@ -16129,7 +16916,7 @@ async function handleParallelCommand(raw) {
|
|
|
16129
16916
|
specs = splitDescriptions(args.description, { parentId });
|
|
16130
16917
|
}
|
|
16131
16918
|
if (specs.length === 0) {
|
|
16132
|
-
log9?.warn(`[${
|
|
16919
|
+
log9?.warn(`[${PLUGIN_NAME17}] /parallel 缺有效子任务`);
|
|
16133
16920
|
await safeReply2(ctx, "⚠ /parallel 需要至少 1 个子任务(用 ; 或换行分隔)");
|
|
16134
16921
|
return { ok: false, reason: "no_subtasks" };
|
|
16135
16922
|
}
|
|
@@ -16187,7 +16974,7 @@ async function handleParallelCommand(raw) {
|
|
|
16187
16974
|
});
|
|
16188
16975
|
} catch (err) {
|
|
16189
16976
|
const msg = err instanceof Error ? err.message : String(err);
|
|
16190
|
-
log9?.error(`[${
|
|
16977
|
+
log9?.error(`[${PLUGIN_NAME17}] schedule 抛错`, { error: msg });
|
|
16191
16978
|
await safeReply2(ctx, `❌ 并发调度失败:${msg}`);
|
|
16192
16979
|
return { ok: false, reason: msg };
|
|
16193
16980
|
}
|
|
@@ -16195,7 +16982,7 @@ async function handleParallelCommand(raw) {
|
|
|
16195
16982
|
const summaryLines = [head, "", "```", result.digest.text, "```"];
|
|
16196
16983
|
await safeReply2(ctx, summaryLines.join(`
|
|
16197
16984
|
`));
|
|
16198
|
-
log9?.info(`[${
|
|
16985
|
+
log9?.info(`[${PLUGIN_NAME17}] schedule 完成`, {
|
|
16199
16986
|
parentId,
|
|
16200
16987
|
success: result.digest.success,
|
|
16201
16988
|
failed: result.digest.failed,
|
|
@@ -16205,7 +16992,7 @@ async function handleParallelCommand(raw) {
|
|
|
16205
16992
|
try {
|
|
16206
16993
|
await ctx.onCompleted(result);
|
|
16207
16994
|
} catch (err) {
|
|
16208
|
-
log9?.warn(`[${
|
|
16995
|
+
log9?.warn(`[${PLUGIN_NAME17}] onCompleted hook 抛错(已隔离)`, {
|
|
16209
16996
|
error: err instanceof Error ? err.message : String(err)
|
|
16210
16997
|
});
|
|
16211
16998
|
}
|
|
@@ -16246,7 +17033,7 @@ async function writeLog(level, msg, data) {
|
|
|
16246
17033
|
const line = JSON.stringify({
|
|
16247
17034
|
ts: new Date().toISOString(),
|
|
16248
17035
|
level,
|
|
16249
|
-
plugin:
|
|
17036
|
+
plugin: PLUGIN_NAME17,
|
|
16250
17037
|
msg,
|
|
16251
17038
|
data
|
|
16252
17039
|
}) + `
|
|
@@ -16257,11 +17044,11 @@ async function writeLog(level, msg, data) {
|
|
|
16257
17044
|
await fs10.appendFile(logFile, line, "utf8");
|
|
16258
17045
|
} catch {}
|
|
16259
17046
|
}
|
|
16260
|
-
logLifecycle(
|
|
17047
|
+
logLifecycle(PLUGIN_NAME17, "import");
|
|
16261
17048
|
var subtasksServer = async (ctx) => {
|
|
16262
|
-
const log9 = makePluginLogger(
|
|
17049
|
+
const log9 = makePluginLogger(PLUGIN_NAME17);
|
|
16263
17050
|
const client = ctx?.client ?? undefined;
|
|
16264
|
-
logLifecycle(
|
|
17051
|
+
logLifecycle(PLUGIN_NAME17, "activate", {
|
|
16265
17052
|
directory: ctx.directory,
|
|
16266
17053
|
hasClient: Boolean(client)
|
|
16267
17054
|
});
|
|
@@ -16321,20 +17108,20 @@ var subtasksServer = async (ctx) => {
|
|
|
16321
17108
|
});
|
|
16322
17109
|
}
|
|
16323
17110
|
} catch (err) {
|
|
16324
|
-
log9.error(`[${
|
|
17111
|
+
log9.error(`[${PLUGIN_NAME17}] command.execute.before 异常(已隔离)`, {
|
|
16325
17112
|
error: err instanceof Error ? err.message : String(err)
|
|
16326
17113
|
});
|
|
16327
17114
|
}
|
|
16328
17115
|
}
|
|
16329
17116
|
};
|
|
16330
17117
|
};
|
|
16331
|
-
var
|
|
17118
|
+
var handler17 = subtasksServer;
|
|
16332
17119
|
|
|
16333
17120
|
// plugins/terminal-monitor.ts
|
|
16334
17121
|
init_opencode_plugin_helpers();
|
|
16335
17122
|
import * as crypto5 from "node:crypto";
|
|
16336
|
-
var
|
|
16337
|
-
logLifecycle(
|
|
17123
|
+
var PLUGIN_NAME18 = "terminal-monitor";
|
|
17124
|
+
logLifecycle(PLUGIN_NAME18, "import", {});
|
|
16338
17125
|
var DEFAULT_CONFIG7 = {
|
|
16339
17126
|
minScore: 0.6,
|
|
16340
17127
|
cooldownMs: 30000,
|
|
@@ -16616,17 +17403,17 @@ function describeError(err) {
|
|
|
16616
17403
|
return String(err);
|
|
16617
17404
|
}
|
|
16618
17405
|
}
|
|
16619
|
-
var log9 = makePluginLogger(
|
|
17406
|
+
var log9 = makePluginLogger(PLUGIN_NAME18);
|
|
16620
17407
|
var lru = new FingerprintLRU2;
|
|
16621
17408
|
var _lastNotification = null;
|
|
16622
17409
|
var terminalMonitorServer = async (ctx) => {
|
|
16623
|
-
logLifecycle(
|
|
17410
|
+
logLifecycle(PLUGIN_NAME18, "activate", {
|
|
16624
17411
|
directory: ctx.directory,
|
|
16625
17412
|
triggerEventTypes: ["terminal.output", "terminal.exit"]
|
|
16626
17413
|
});
|
|
16627
17414
|
return {
|
|
16628
17415
|
event: async ({ event }) => {
|
|
16629
|
-
await safeAsync(
|
|
17416
|
+
await safeAsync(PLUGIN_NAME18, "event", async () => {
|
|
16630
17417
|
const e = event;
|
|
16631
17418
|
if (!e || typeof e.type !== "string")
|
|
16632
17419
|
return;
|
|
@@ -16640,7 +17427,7 @@ var terminalMonitorServer = async (ctx) => {
|
|
|
16640
17427
|
_lastNotification = msg;
|
|
16641
17428
|
}
|
|
16642
17429
|
});
|
|
16643
|
-
safeWriteLog(
|
|
17430
|
+
safeWriteLog(PLUGIN_NAME18, {
|
|
16644
17431
|
hook: "event",
|
|
16645
17432
|
type: e.type,
|
|
16646
17433
|
notified: r.notified,
|
|
@@ -16648,18 +17435,18 @@ var terminalMonitorServer = async (ctx) => {
|
|
|
16648
17435
|
findings: r.notification?.findings.length ?? 0
|
|
16649
17436
|
});
|
|
16650
17437
|
if (r.notified && r.notification) {
|
|
16651
|
-
log9.info(`[${
|
|
17438
|
+
log9.info(`[${PLUGIN_NAME18}] 反馈 ${r.notification.findings.length} 条 → ${r.notification.summary}`);
|
|
16652
17439
|
}
|
|
16653
17440
|
});
|
|
16654
17441
|
}
|
|
16655
17442
|
};
|
|
16656
17443
|
};
|
|
16657
|
-
var
|
|
17444
|
+
var handler18 = terminalMonitorServer;
|
|
16658
17445
|
|
|
16659
17446
|
// plugins/token-manager.ts
|
|
16660
17447
|
init_opencode_plugin_helpers();
|
|
16661
|
-
var
|
|
16662
|
-
logLifecycle(
|
|
17448
|
+
var PLUGIN_NAME19 = "token-manager";
|
|
17449
|
+
logLifecycle(PLUGIN_NAME19, "import", {});
|
|
16663
17450
|
async function handleMessageBefore(raw, log10, defaults) {
|
|
16664
17451
|
const ctx = raw ?? {};
|
|
16665
17452
|
if (!Array.isArray(ctx.messages) || ctx.messages.length === 0)
|
|
@@ -16679,21 +17466,21 @@ async function handleMessageBefore(raw, log10, defaults) {
|
|
|
16679
17466
|
};
|
|
16680
17467
|
if (r.compressed) {
|
|
16681
17468
|
ctx.messages = r.messages;
|
|
16682
|
-
log10?.info(`[${
|
|
17469
|
+
log10?.info(`[${PLUGIN_NAME19}] 压缩 ${r.before.count}→${r.after.count} 条 / ${r.before.tokens}→${r.after.tokens} tokens`);
|
|
16683
17470
|
}
|
|
16684
17471
|
return r;
|
|
16685
17472
|
} catch (err) {
|
|
16686
|
-
log10?.warn(`[${
|
|
17473
|
+
log10?.warn(`[${PLUGIN_NAME19}] 压缩异常(已隔离)`, {
|
|
16687
17474
|
error: err instanceof Error ? err.message : String(err)
|
|
16688
17475
|
});
|
|
16689
17476
|
return null;
|
|
16690
17477
|
}
|
|
16691
17478
|
}
|
|
16692
|
-
var log10 = makePluginLogger(
|
|
17479
|
+
var log10 = makePluginLogger(PLUGIN_NAME19);
|
|
16693
17480
|
var tokenManagerServer = async (ctx) => {
|
|
16694
17481
|
const rt = loadRuntimeSync();
|
|
16695
17482
|
const threshold = rt.runtime.context.condenser_threshold_ratio;
|
|
16696
|
-
logLifecycle(
|
|
17483
|
+
logLifecycle(PLUGIN_NAME19, "activate", {
|
|
16697
17484
|
directory: ctx.directory,
|
|
16698
17485
|
threshold,
|
|
16699
17486
|
target: DEFAULT_CONDENSE.target,
|
|
@@ -16702,7 +17489,7 @@ var tokenManagerServer = async (ctx) => {
|
|
|
16702
17489
|
});
|
|
16703
17490
|
return {
|
|
16704
17491
|
"experimental.chat.messages.transform": async (_input, output) => {
|
|
16705
|
-
await safeAsync(
|
|
17492
|
+
await safeAsync(PLUGIN_NAME19, "experimental.chat.messages.transform", async () => {
|
|
16706
17493
|
const list = output.messages;
|
|
16707
17494
|
if (!Array.isArray(list) || list.length === 0)
|
|
16708
17495
|
return;
|
|
@@ -16715,7 +17502,7 @@ var tokenManagerServer = async (ctx) => {
|
|
|
16715
17502
|
const r = await handleMessageBefore({ messages: flat }, log10, { threshold });
|
|
16716
17503
|
if (!r)
|
|
16717
17504
|
return;
|
|
16718
|
-
safeWriteLog(
|
|
17505
|
+
safeWriteLog(PLUGIN_NAME19, {
|
|
16719
17506
|
hook: "experimental.chat.messages.transform",
|
|
16720
17507
|
mode: "observe-only",
|
|
16721
17508
|
before_msgs: r.before.count,
|
|
@@ -16726,13 +17513,13 @@ var tokenManagerServer = async (ctx) => {
|
|
|
16726
17513
|
reason: r.reason
|
|
16727
17514
|
});
|
|
16728
17515
|
if (r.compressed) {
|
|
16729
|
-
log10.warn(`[${
|
|
17516
|
+
log10.warn(`[${PLUGIN_NAME19}] 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)`);
|
|
16730
17517
|
}
|
|
16731
17518
|
});
|
|
16732
17519
|
}
|
|
16733
17520
|
};
|
|
16734
17521
|
};
|
|
16735
|
-
var
|
|
17522
|
+
var handler19 = tokenManagerServer;
|
|
16736
17523
|
|
|
16737
17524
|
// plugins/tool-policy.ts
|
|
16738
17525
|
init_opencode_plugin_helpers();
|
|
@@ -16975,10 +17762,42 @@ function checkFileAccess(acl, file, op) {
|
|
|
16975
17762
|
}
|
|
16976
17763
|
|
|
16977
17764
|
// plugins/tool-policy.ts
|
|
16978
|
-
var
|
|
16979
|
-
logLifecycle(
|
|
17765
|
+
var PLUGIN_NAME20 = "tool-policy";
|
|
17766
|
+
logLifecycle(PLUGIN_NAME20, "import", {});
|
|
16980
17767
|
var EMPTY_ACL = { whitelistMode: false };
|
|
16981
|
-
|
|
17768
|
+
var SUBAGENT_APPLY_DENY_LIST = new Set([
|
|
17769
|
+
"coder",
|
|
17770
|
+
"planner",
|
|
17771
|
+
"reviewer"
|
|
17772
|
+
]);
|
|
17773
|
+
function isSubagentApplyBlocked(tool2, args, currentAgent) {
|
|
17774
|
+
if (tool2 !== "pending_changes")
|
|
17775
|
+
return false;
|
|
17776
|
+
const action = args?.action;
|
|
17777
|
+
if (action !== "apply" && action !== "apply_all")
|
|
17778
|
+
return false;
|
|
17779
|
+
if (!currentAgent)
|
|
17780
|
+
return false;
|
|
17781
|
+
return SUBAGENT_APPLY_DENY_LIST.has(currentAgent);
|
|
17782
|
+
}
|
|
17783
|
+
function decideToolCall(ctx, cfg = {}, currentAgent) {
|
|
17784
|
+
if (isSubagentApplyBlocked(ctx.tool, ctx.args, currentAgent)) {
|
|
17785
|
+
const action2 = String(ctx.args?.action);
|
|
17786
|
+
return {
|
|
17787
|
+
action: "deny",
|
|
17788
|
+
reasons: [
|
|
17789
|
+
`[ADR-0061] subagent '${currentAgent}' 禁止调 pending_changes.${action2}` + ` — apply 必须由 codeforge orchestrator 或用户拍板`,
|
|
17790
|
+
`替代路径:stage 完成后回报 codeforge,由其决定 apply 后通过 task_id 复用启动你跑测试`
|
|
17791
|
+
],
|
|
17792
|
+
autonomy: {
|
|
17793
|
+
effective_mode: ctx.mode ?? cfg.defaultMode ?? DEFAULT_RUNTIME.autonomy.default_mode,
|
|
17794
|
+
action: "confirm",
|
|
17795
|
+
downgraded: false,
|
|
17796
|
+
detected_risks: ["subagent_apply_blocked"],
|
|
17797
|
+
reason: `subagent '${currentAgent}' 自 apply 越权 (ADR-0061)`
|
|
17798
|
+
}
|
|
17799
|
+
};
|
|
17800
|
+
}
|
|
16982
17801
|
const fallbackMode = cfg.defaultMode ?? DEFAULT_RUNTIME.autonomy.default_mode;
|
|
16983
17802
|
const mode = ctx.mode ?? fallbackMode;
|
|
16984
17803
|
const a = evaluate2(mode, {
|
|
@@ -17028,13 +17847,39 @@ function classifyToolKind(toolName) {
|
|
|
17028
17847
|
return "webfetch";
|
|
17029
17848
|
return "other";
|
|
17030
17849
|
}
|
|
17031
|
-
|
|
17850
|
+
async function resolveCurrentAgent(client, sessionID, log11) {
|
|
17851
|
+
try {
|
|
17852
|
+
const sessionApi = client?.session;
|
|
17853
|
+
if (!sessionApi || typeof sessionApi.get !== "function") {
|
|
17854
|
+
log11.warn(`client.session.get unavailable`, { sessionID });
|
|
17855
|
+
return;
|
|
17856
|
+
}
|
|
17857
|
+
const res = await sessionApi.get({ path: { id: sessionID } });
|
|
17858
|
+
const data = res?.data ?? res;
|
|
17859
|
+
const rawAgent = data?.agent;
|
|
17860
|
+
if (typeof rawAgent !== "string" || rawAgent === "") {
|
|
17861
|
+
log11.warn(`client.session.get returned no string agent (保守放行)`, {
|
|
17862
|
+
sessionID,
|
|
17863
|
+
dataKeys: data && typeof data === "object" ? Object.keys(data) : null
|
|
17864
|
+
});
|
|
17865
|
+
return;
|
|
17866
|
+
}
|
|
17867
|
+
return rawAgent;
|
|
17868
|
+
} catch (err) {
|
|
17869
|
+
log11.warn(`client.session.get failed (保守放行)`, {
|
|
17870
|
+
sessionID,
|
|
17871
|
+
error: err instanceof Error ? err.message : String(err)
|
|
17872
|
+
});
|
|
17873
|
+
return;
|
|
17874
|
+
}
|
|
17875
|
+
}
|
|
17876
|
+
var log11 = makePluginLogger(PLUGIN_NAME20);
|
|
17032
17877
|
var toolPolicyServer = async (ctx) => {
|
|
17033
17878
|
const directory = ctx.directory ?? process.cwd();
|
|
17034
17879
|
const cfg = await loadPolicy(directory);
|
|
17035
17880
|
const rt = loadRuntimeSync();
|
|
17036
17881
|
cfg.defaultMode = rt.runtime.autonomy.default_mode;
|
|
17037
|
-
logLifecycle(
|
|
17882
|
+
logLifecycle(PLUGIN_NAME20, "activate", {
|
|
17038
17883
|
directory,
|
|
17039
17884
|
acl_loaded: !!cfg.acl,
|
|
17040
17885
|
default_mode: cfg.defaultMode,
|
|
@@ -17042,9 +17887,15 @@ var toolPolicyServer = async (ctx) => {
|
|
|
17042
17887
|
});
|
|
17043
17888
|
return {
|
|
17044
17889
|
"tool.execute.before": async (input, output) => {
|
|
17045
|
-
|
|
17890
|
+
let denied;
|
|
17891
|
+
await safeAsync(PLUGIN_NAME20, "tool.execute.before", async () => {
|
|
17046
17892
|
const toolName = input.tool;
|
|
17047
17893
|
const argsObj = output.args ?? {};
|
|
17894
|
+
const needsAgentDetection = toolName === "pending_changes" && (argsObj.action === "apply" || argsObj.action === "apply_all");
|
|
17895
|
+
let currentAgent = undefined;
|
|
17896
|
+
if (needsAgentDetection) {
|
|
17897
|
+
currentAgent = await resolveCurrentAgent(ctx.client, input.sessionID, log11);
|
|
17898
|
+
}
|
|
17048
17899
|
const files = [];
|
|
17049
17900
|
const filePath = argsObj["path"] ?? argsObj["filePath"] ?? argsObj["file"];
|
|
17050
17901
|
if (typeof filePath === "string") {
|
|
@@ -17057,31 +17908,39 @@ var toolPolicyServer = async (ctx) => {
|
|
|
17057
17908
|
args: argsObj,
|
|
17058
17909
|
mode: cfg.defaultMode,
|
|
17059
17910
|
files: files.length ? files : undefined
|
|
17060
|
-
}, cfg);
|
|
17061
|
-
safeWriteLog(
|
|
17911
|
+
}, cfg, currentAgent);
|
|
17912
|
+
safeWriteLog(PLUGIN_NAME20, {
|
|
17062
17913
|
hook: "tool.execute.before",
|
|
17063
17914
|
tool: toolName,
|
|
17064
17915
|
callID: input.callID,
|
|
17065
17916
|
sessionID: input.sessionID,
|
|
17066
17917
|
action: decision.action,
|
|
17067
|
-
reasons: decision.reasons
|
|
17918
|
+
reasons: decision.reasons,
|
|
17919
|
+
currentAgent
|
|
17068
17920
|
});
|
|
17069
17921
|
if (decision.action === "deny") {
|
|
17070
|
-
log11.warn(`[${
|
|
17922
|
+
log11.warn(`[${PLUGIN_NAME20}] DENY ${toolName}`, {
|
|
17923
|
+
action: argsObj.action,
|
|
17924
|
+
currentAgent,
|
|
17925
|
+
reasons: decision.reasons
|
|
17926
|
+
});
|
|
17927
|
+
denied = new Error(`[tool-policy] DENIED: ${decision.reasons.join(" / ")}`);
|
|
17071
17928
|
} else if (decision.action === "confirm") {
|
|
17072
|
-
log11.info(`[${
|
|
17929
|
+
log11.info(`[${PLUGIN_NAME20}] CONFIRM ${toolName}: ${decision.reasons.join("; ")}`);
|
|
17073
17930
|
}
|
|
17074
17931
|
});
|
|
17932
|
+
if (denied)
|
|
17933
|
+
throw denied;
|
|
17075
17934
|
}
|
|
17076
17935
|
};
|
|
17077
17936
|
};
|
|
17078
|
-
var
|
|
17937
|
+
var handler20 = toolPolicyServer;
|
|
17079
17938
|
|
|
17080
17939
|
// plugins/update-checker.ts
|
|
17081
17940
|
init_opencode_plugin_helpers();
|
|
17082
17941
|
import { existsSync as existsSync5 } from "node:fs";
|
|
17083
17942
|
import { homedir as homedir7 } from "node:os";
|
|
17084
|
-
import { join as
|
|
17943
|
+
import { join as join16 } from "node:path";
|
|
17085
17944
|
|
|
17086
17945
|
// lib/update-checker-impl.ts
|
|
17087
17946
|
import { createHash as createHash6 } from "node:crypto";
|
|
@@ -17093,12 +17952,12 @@ import {
|
|
|
17093
17952
|
readFileSync as readFileSync4,
|
|
17094
17953
|
readdirSync,
|
|
17095
17954
|
renameSync,
|
|
17096
|
-
statSync as
|
|
17955
|
+
statSync as statSync3,
|
|
17097
17956
|
unlinkSync,
|
|
17098
17957
|
writeFileSync as writeFileSync2
|
|
17099
17958
|
} from "node:fs";
|
|
17100
17959
|
import { homedir as homedir6, tmpdir } from "node:os";
|
|
17101
|
-
import { dirname as dirname7, join as
|
|
17960
|
+
import { dirname as dirname7, join as join15 } from "node:path";
|
|
17102
17961
|
import { fileURLToPath } from "node:url";
|
|
17103
17962
|
import * as https from "node:https";
|
|
17104
17963
|
import * as zlib from "node:zlib";
|
|
@@ -17106,7 +17965,7 @@ import * as zlib from "node:zlib";
|
|
|
17106
17965
|
// lib/version-injected.ts
|
|
17107
17966
|
function getInjectedVersion() {
|
|
17108
17967
|
try {
|
|
17109
|
-
const v = "0.3.
|
|
17968
|
+
const v = "0.3.11";
|
|
17110
17969
|
if (typeof v === "string" && /^\d+\.\d+\.\d+/.test(v)) {
|
|
17111
17970
|
return v;
|
|
17112
17971
|
}
|
|
@@ -17196,17 +18055,17 @@ function readLocalVersion() {
|
|
|
17196
18055
|
try {
|
|
17197
18056
|
const here = fileURLToPath(import.meta.url);
|
|
17198
18057
|
const root = dirname7(dirname7(here));
|
|
17199
|
-
const pkg = JSON.parse(readFileSync4(
|
|
18058
|
+
const pkg = JSON.parse(readFileSync4(join15(root, "package.json"), "utf8"));
|
|
17200
18059
|
return typeof pkg.version === "string" ? pkg.version : "0.0.0";
|
|
17201
18060
|
} catch {
|
|
17202
18061
|
return "0.0.0";
|
|
17203
18062
|
}
|
|
17204
18063
|
}
|
|
17205
18064
|
function defaultCacheDir() {
|
|
17206
|
-
return process.env["CODEFORGE_CACHE_DIR"] ??
|
|
18065
|
+
return process.env["CODEFORGE_CACHE_DIR"] ?? join15(homedir6(), ".cache", "codeforge");
|
|
17207
18066
|
}
|
|
17208
18067
|
function defaultCacheFile() {
|
|
17209
|
-
return
|
|
18068
|
+
return join15(defaultCacheDir(), "update-check.json");
|
|
17210
18069
|
}
|
|
17211
18070
|
function readCache(file) {
|
|
17212
18071
|
try {
|
|
@@ -17362,14 +18221,14 @@ function defaultHttpFetcher(url, timeoutMs) {
|
|
|
17362
18221
|
});
|
|
17363
18222
|
}
|
|
17364
18223
|
async function downloadAndExtractBundle(opts) {
|
|
17365
|
-
const tmpRoot = opts.tmpDir ?? mkdtempSync(
|
|
18224
|
+
const tmpRoot = opts.tmpDir ?? mkdtempSync(join15(tmpdir(), "codeforge-update-"));
|
|
17366
18225
|
mkdirSync3(tmpRoot, { recursive: true });
|
|
17367
18226
|
const fetcher = opts.tarballFetcher ?? defaultBinaryFetcher;
|
|
17368
18227
|
const tarballBuf = await fetcher(opts.tarballUrl);
|
|
17369
18228
|
verifyIntegrity(tarballBuf, opts.expectedIntegrity);
|
|
17370
18229
|
const tarBuf = zlib.gunzipSync(tarballBuf);
|
|
17371
18230
|
extractTarToDir(tarBuf, tmpRoot);
|
|
17372
|
-
const bundlePath =
|
|
18231
|
+
const bundlePath = join15(tmpRoot, "package", "dist", "index.js");
|
|
17373
18232
|
if (!existsSync4(bundlePath)) {
|
|
17374
18233
|
throw new Error(`bundle_not_found: ${bundlePath}`);
|
|
17375
18234
|
}
|
|
@@ -17409,11 +18268,11 @@ function extractTarToDir(tarBuf, destRoot) {
|
|
|
17409
18268
|
offset += 512;
|
|
17410
18269
|
if (typeFlag === "0" || typeFlag === "" || typeFlag === "\x00") {
|
|
17411
18270
|
const fileBuf = tarBuf.subarray(offset, offset + size);
|
|
17412
|
-
const dest =
|
|
18271
|
+
const dest = join15(destRoot, fullName);
|
|
17413
18272
|
mkdirSync3(dirname7(dest), { recursive: true });
|
|
17414
18273
|
writeFileSync2(dest, fileBuf);
|
|
17415
18274
|
} else if (typeFlag === "5") {
|
|
17416
|
-
mkdirSync3(
|
|
18275
|
+
mkdirSync3(join15(destRoot, fullName), { recursive: true });
|
|
17417
18276
|
}
|
|
17418
18277
|
offset += Math.ceil(size / 512) * 512;
|
|
17419
18278
|
}
|
|
@@ -17522,10 +18381,10 @@ function cleanupOldBackups(target, keep) {
|
|
|
17522
18381
|
const base = target.substring(dir.length + 1);
|
|
17523
18382
|
const prefix = `${base}.bak.`;
|
|
17524
18383
|
const all = readdirSync(dir).filter((f) => f.startsWith(prefix)).map((f) => {
|
|
17525
|
-
const full =
|
|
18384
|
+
const full = join15(dir, f);
|
|
17526
18385
|
let mtimeMs = 0;
|
|
17527
18386
|
try {
|
|
17528
|
-
mtimeMs =
|
|
18387
|
+
mtimeMs = statSync3(full).mtimeMs;
|
|
17529
18388
|
} catch {}
|
|
17530
18389
|
return { full, mtimeMs };
|
|
17531
18390
|
}).sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
@@ -17544,7 +18403,7 @@ function loadCompatibility(opts) {
|
|
|
17544
18403
|
const root = opts?.cwd ?? inferPluginRoot();
|
|
17545
18404
|
if (!root)
|
|
17546
18405
|
return null;
|
|
17547
|
-
file =
|
|
18406
|
+
file = join15(root, "compatibility.json");
|
|
17548
18407
|
}
|
|
17549
18408
|
if (!existsSync4(file))
|
|
17550
18409
|
return null;
|
|
@@ -17609,20 +18468,20 @@ function compareOpencodeVersion(opts) {
|
|
|
17609
18468
|
}
|
|
17610
18469
|
|
|
17611
18470
|
// plugins/update-checker.ts
|
|
17612
|
-
var
|
|
17613
|
-
var
|
|
17614
|
-
logLifecycle(
|
|
18471
|
+
var PLUGIN_NAME21 = "update-checker";
|
|
18472
|
+
var PLUGIN_VERSION = "2.0.0";
|
|
18473
|
+
logLifecycle(PLUGIN_NAME21, "import", { version: PLUGIN_VERSION });
|
|
17615
18474
|
var updateCheckerServer = async (ctx) => {
|
|
17616
18475
|
const yieldResult = shouldYieldToLocalPlugin({ directory: ctx.directory });
|
|
17617
18476
|
if (yieldResult.yield) {
|
|
17618
|
-
safeWriteLog(
|
|
18477
|
+
safeWriteLog(PLUGIN_NAME21, {
|
|
17619
18478
|
level: "info",
|
|
17620
18479
|
msg: "dev_mode_yield_skip",
|
|
17621
18480
|
reason: yieldResult.reason,
|
|
17622
18481
|
markerPath: yieldResult.markerPath,
|
|
17623
18482
|
detail: formatYieldLog(yieldResult)
|
|
17624
18483
|
});
|
|
17625
|
-
logLifecycle(
|
|
18484
|
+
logLifecycle(PLUGIN_NAME21, "activate", {
|
|
17626
18485
|
yield_to_local: true,
|
|
17627
18486
|
yield_reason: yieldResult.reason,
|
|
17628
18487
|
skipped: "auto_install + background_check"
|
|
@@ -17631,8 +18490,8 @@ var updateCheckerServer = async (ctx) => {
|
|
|
17631
18490
|
}
|
|
17632
18491
|
const rt = loadRuntimeSync();
|
|
17633
18492
|
const u = rt.runtime.update;
|
|
17634
|
-
logLifecycle(
|
|
17635
|
-
version:
|
|
18493
|
+
logLifecycle(PLUGIN_NAME21, "activate", {
|
|
18494
|
+
version: PLUGIN_VERSION,
|
|
17636
18495
|
auto_check_enabled: u.auto_check_enabled,
|
|
17637
18496
|
interval_hours: u.interval_hours,
|
|
17638
18497
|
package: u.package,
|
|
@@ -17643,14 +18502,14 @@ var updateCheckerServer = async (ctx) => {
|
|
|
17643
18502
|
repo_fallback: u.repo,
|
|
17644
18503
|
config_source: "codeforge.json"
|
|
17645
18504
|
});
|
|
17646
|
-
await safeAsync(
|
|
18505
|
+
await safeAsync(PLUGIN_NAME21, "opencode_version_check", async () => {
|
|
17647
18506
|
const compat = loadCompatibility();
|
|
17648
18507
|
const opencodeVer = detectOpencodeVersion();
|
|
17649
18508
|
const verdict = compareOpencodeVersion({
|
|
17650
18509
|
currentOpencodeVer: opencodeVer,
|
|
17651
18510
|
compat
|
|
17652
18511
|
});
|
|
17653
|
-
safeWriteLog(
|
|
18512
|
+
safeWriteLog(PLUGIN_NAME21, {
|
|
17654
18513
|
level: "info",
|
|
17655
18514
|
msg: "opencode_version_check",
|
|
17656
18515
|
opencodeVer,
|
|
@@ -17667,14 +18526,14 @@ var updateCheckerServer = async (ctx) => {
|
|
|
17667
18526
|
}
|
|
17668
18527
|
});
|
|
17669
18528
|
if (!u.auto_check_enabled) {
|
|
17670
|
-
safeWriteLog(
|
|
18529
|
+
safeWriteLog(PLUGIN_NAME21, {
|
|
17671
18530
|
level: "info",
|
|
17672
18531
|
msg: "auto-check disabled (codeforge.json update.auto_check_enabled=false)"
|
|
17673
18532
|
});
|
|
17674
18533
|
return {};
|
|
17675
18534
|
}
|
|
17676
18535
|
setImmediate(() => {
|
|
17677
|
-
safeAsync(
|
|
18536
|
+
safeAsync(PLUGIN_NAME21, "checkAndMaybeUpdate", async () => {
|
|
17678
18537
|
const local = readLocalVersion();
|
|
17679
18538
|
let npmResult = null;
|
|
17680
18539
|
try {
|
|
@@ -17685,7 +18544,7 @@ var updateCheckerServer = async (ctx) => {
|
|
|
17685
18544
|
timeoutMs: 5000
|
|
17686
18545
|
});
|
|
17687
18546
|
} catch (e) {
|
|
17688
|
-
safeWriteLog(
|
|
18547
|
+
safeWriteLog(PLUGIN_NAME21, {
|
|
17689
18548
|
level: "warn",
|
|
17690
18549
|
msg: "npm_fetch_failed",
|
|
17691
18550
|
error: e.message
|
|
@@ -17694,7 +18553,7 @@ var updateCheckerServer = async (ctx) => {
|
|
|
17694
18553
|
return;
|
|
17695
18554
|
}
|
|
17696
18555
|
if (!npmResult) {
|
|
17697
|
-
safeWriteLog(
|
|
18556
|
+
safeWriteLog(PLUGIN_NAME21, {
|
|
17698
18557
|
level: "info",
|
|
17699
18558
|
msg: "npm_no_release",
|
|
17700
18559
|
package: u.package,
|
|
@@ -17703,7 +18562,7 @@ var updateCheckerServer = async (ctx) => {
|
|
|
17703
18562
|
return;
|
|
17704
18563
|
}
|
|
17705
18564
|
const hasUpdate = cmpVersion(local, npmResult.version) < 0;
|
|
17706
|
-
safeWriteLog(
|
|
18565
|
+
safeWriteLog(PLUGIN_NAME21, {
|
|
17707
18566
|
level: "info",
|
|
17708
18567
|
msg: "npm_check_result",
|
|
17709
18568
|
local,
|
|
@@ -17718,10 +18577,10 @@ var updateCheckerServer = async (ctx) => {
|
|
|
17718
18577
|
更新命令:npx ${u.package} install --global`);
|
|
17719
18578
|
return;
|
|
17720
18579
|
}
|
|
17721
|
-
await safeAsync(
|
|
18580
|
+
await safeAsync(PLUGIN_NAME21, "auto_install_bundle", async () => {
|
|
17722
18581
|
const target = getOpencodeBundlePath();
|
|
17723
18582
|
if (!target) {
|
|
17724
|
-
safeWriteLog(
|
|
18583
|
+
safeWriteLog(PLUGIN_NAME21, {
|
|
17725
18584
|
level: "warn",
|
|
17726
18585
|
msg: "auto_install_skip",
|
|
17727
18586
|
reason: "无法定位 opencode bundle 路径"
|
|
@@ -17740,7 +18599,7 @@ var updateCheckerServer = async (ctx) => {
|
|
|
17740
18599
|
oldVersion: local,
|
|
17741
18600
|
keepBackups: u.backup_keep
|
|
17742
18601
|
});
|
|
17743
|
-
safeWriteLog(
|
|
18602
|
+
safeWriteLog(PLUGIN_NAME21, {
|
|
17744
18603
|
level: "info",
|
|
17745
18604
|
msg: "auto_install_success",
|
|
17746
18605
|
local,
|
|
@@ -17764,14 +18623,14 @@ function detectOpencodeVersion() {
|
|
|
17764
18623
|
}
|
|
17765
18624
|
function getOpencodeBundlePath() {
|
|
17766
18625
|
const candidates = [];
|
|
17767
|
-
candidates.push(
|
|
18626
|
+
candidates.push(join16(homedir7(), ".config", "opencode", "codeforge", "index.js"));
|
|
17768
18627
|
if (process.platform === "win32") {
|
|
17769
18628
|
const appData = process.env["APPDATA"];
|
|
17770
18629
|
if (appData)
|
|
17771
|
-
candidates.push(
|
|
18630
|
+
candidates.push(join16(appData, "opencode", "codeforge", "index.js"));
|
|
17772
18631
|
const localAppData = process.env["LOCALAPPDATA"];
|
|
17773
18632
|
if (localAppData)
|
|
17774
|
-
candidates.push(
|
|
18633
|
+
candidates.push(join16(localAppData, "opencode", "codeforge", "index.js"));
|
|
17775
18634
|
}
|
|
17776
18635
|
for (const c of candidates) {
|
|
17777
18636
|
if (existsSync5(c))
|
|
@@ -17780,7 +18639,7 @@ function getOpencodeBundlePath() {
|
|
|
17780
18639
|
return candidates[0] ?? null;
|
|
17781
18640
|
}
|
|
17782
18641
|
async function fallbackToGitHubReleases(ctx, u) {
|
|
17783
|
-
await safeAsync(
|
|
18642
|
+
await safeAsync(PLUGIN_NAME21, "github_fallback", async () => {
|
|
17784
18643
|
const result = await checkUpdateOnce({
|
|
17785
18644
|
repo: u.repo,
|
|
17786
18645
|
intervalMs: u.interval_hours * 3600 * 1000
|
|
@@ -17790,7 +18649,7 @@ async function fallbackToGitHubReleases(ctx, u) {
|
|
|
17790
18649
|
}
|
|
17791
18650
|
async function reportLegacyResult(ctx, result, repo) {
|
|
17792
18651
|
if (result.error && !result.fromCache) {
|
|
17793
|
-
safeWriteLog(
|
|
18652
|
+
safeWriteLog(PLUGIN_NAME21, {
|
|
17794
18653
|
level: "warn",
|
|
17795
18654
|
msg: `update check failed: ${result.error}`,
|
|
17796
18655
|
...result
|
|
@@ -17798,7 +18657,7 @@ async function reportLegacyResult(ctx, result, repo) {
|
|
|
17798
18657
|
return;
|
|
17799
18658
|
}
|
|
17800
18659
|
if (!result.hasUpdate) {
|
|
17801
|
-
safeWriteLog(
|
|
18660
|
+
safeWriteLog(PLUGIN_NAME21, {
|
|
17802
18661
|
level: "info",
|
|
17803
18662
|
msg: `up-to-date (local=${result.local}, remote=${result.remote}${result.fromCache ? ", from_cache" : ""})`,
|
|
17804
18663
|
...result
|
|
@@ -17808,7 +18667,7 @@ async function reportLegacyResult(ctx, result, repo) {
|
|
|
17808
18667
|
const updateCmd = `bunx --bun github:${repo} install`;
|
|
17809
18668
|
const toast = `[CodeForge] 有新版本:${result.local} → ${result.remote}
|
|
17810
18669
|
更新命令:${updateCmd}`;
|
|
17811
|
-
safeWriteLog(
|
|
18670
|
+
safeWriteLog(PLUGIN_NAME21, {
|
|
17812
18671
|
level: "info",
|
|
17813
18672
|
msg: "new_version_available_github_fallback",
|
|
17814
18673
|
local: result.local,
|
|
@@ -17819,17 +18678,17 @@ async function reportLegacyResult(ctx, result, repo) {
|
|
|
17819
18678
|
await postToast(ctx, toast);
|
|
17820
18679
|
}
|
|
17821
18680
|
async function postToast(ctx, message) {
|
|
17822
|
-
await safeAsync(
|
|
18681
|
+
await safeAsync(PLUGIN_NAME21, "client.app.log", async () => {
|
|
17823
18682
|
await ctx.client.app.log({
|
|
17824
18683
|
body: {
|
|
17825
|
-
service:
|
|
18684
|
+
service: PLUGIN_NAME21,
|
|
17826
18685
|
level: "info",
|
|
17827
18686
|
message
|
|
17828
18687
|
}
|
|
17829
18688
|
});
|
|
17830
18689
|
});
|
|
17831
18690
|
}
|
|
17832
|
-
var
|
|
18691
|
+
var handler21 = updateCheckerServer;
|
|
17833
18692
|
|
|
17834
18693
|
// plugins/workflow-engine.ts
|
|
17835
18694
|
init_opencode_plugin_helpers();
|
|
@@ -18333,9 +19192,9 @@ async function runStepAutoFeedback(step, adapter) {
|
|
|
18333
19192
|
}
|
|
18334
19193
|
|
|
18335
19194
|
// plugins/workflow-engine.ts
|
|
18336
|
-
var
|
|
18337
|
-
logLifecycle(
|
|
18338
|
-
var fallbackLog2 = makePluginLogger(
|
|
19195
|
+
var PLUGIN_NAME22 = "workflow-engine";
|
|
19196
|
+
logLifecycle(PLUGIN_NAME22, "import", {});
|
|
19197
|
+
var fallbackLog2 = makePluginLogger(PLUGIN_NAME22);
|
|
18339
19198
|
var _registry = null;
|
|
18340
19199
|
async function loadRegistry(workflowsDir) {
|
|
18341
19200
|
const { loaded, failed } = await loadWorkflowsFromDir(workflowsDir);
|
|
@@ -18355,32 +19214,32 @@ async function handleCommandInvoked(raw, workflowsDir = "workflows") {
|
|
|
18355
19214
|
const log12 = ctx.log ?? fallbackLog2;
|
|
18356
19215
|
const command = typeof ctx.command === "string" ? ctx.command : null;
|
|
18357
19216
|
if (!command) {
|
|
18358
|
-
log12.warn(`[${
|
|
19217
|
+
log12.warn(`[${PLUGIN_NAME22}] command.invoked 缺 command 字段`, ctx);
|
|
18359
19218
|
return null;
|
|
18360
19219
|
}
|
|
18361
19220
|
const reg = await ensureRegistry(workflowsDir);
|
|
18362
19221
|
if (reg.errors.length) {
|
|
18363
|
-
log12.warn(`[${
|
|
19222
|
+
log12.warn(`[${PLUGIN_NAME22}] 有 ${reg.errors.length} 个 workflow 加载失败`, reg.errors);
|
|
18364
19223
|
}
|
|
18365
19224
|
const wf = reg.workflows.find((w) => matchesTrigger(w, command));
|
|
18366
19225
|
if (!wf) {
|
|
18367
|
-
log12.info(`[${
|
|
19226
|
+
log12.info(`[${PLUGIN_NAME22}] no workflow matches "${command}"`);
|
|
18368
19227
|
return null;
|
|
18369
19228
|
}
|
|
18370
|
-
log12.info(`[${
|
|
19229
|
+
log12.info(`[${PLUGIN_NAME22}] dispatch "${command}" → workflow "${wf.name}"`);
|
|
18371
19230
|
try {
|
|
18372
19231
|
const result = await run(wf, {
|
|
18373
19232
|
mode: ctx.adapter ? "real" : "dry_run",
|
|
18374
19233
|
autonomy: ctx.autonomy ?? "semi",
|
|
18375
19234
|
adapter: ctx.adapter
|
|
18376
19235
|
});
|
|
18377
|
-
log12.info(`[${
|
|
19236
|
+
log12.info(`[${PLUGIN_NAME22}] workflow "${wf.name}" 完成 (${result.plan.mode})`, {
|
|
18378
19237
|
steps: result.plan.steps.length,
|
|
18379
19238
|
results: result.results.length
|
|
18380
19239
|
});
|
|
18381
19240
|
return result;
|
|
18382
19241
|
} catch (err) {
|
|
18383
|
-
log12.error(`[${
|
|
19242
|
+
log12.error(`[${PLUGIN_NAME22}] workflow "${wf.name}" 执行失败`, {
|
|
18384
19243
|
error: err instanceof Error ? err.message : String(err)
|
|
18385
19244
|
});
|
|
18386
19245
|
throw err;
|
|
@@ -18389,15 +19248,15 @@ async function handleCommandInvoked(raw, workflowsDir = "workflows") {
|
|
|
18389
19248
|
var workflowEngineServer = async (ctx) => {
|
|
18390
19249
|
const directory = ctx.directory ?? process.cwd();
|
|
18391
19250
|
const workflowsDir = path17.join(directory, "workflows");
|
|
18392
|
-
ensureRegistry(workflowsDir).catch((err) => fallbackLog2.warn(`[${
|
|
19251
|
+
ensureRegistry(workflowsDir).catch((err) => fallbackLog2.warn(`[${PLUGIN_NAME22}] preload workflows failed`, {
|
|
18393
19252
|
error: err instanceof Error ? err.message : String(err)
|
|
18394
19253
|
}));
|
|
18395
|
-
logLifecycle(
|
|
19254
|
+
logLifecycle(PLUGIN_NAME22, "activate", { directory, workflowsDir });
|
|
18396
19255
|
return {
|
|
18397
19256
|
"command.execute.before": async (input, output) => {
|
|
18398
|
-
await safeAsync(
|
|
19257
|
+
await safeAsync(PLUGIN_NAME22, "command.execute.before", async () => {
|
|
18399
19258
|
const cmd = input.command.startsWith("/") ? input.command : `/${input.command}`;
|
|
18400
|
-
safeWriteLog(
|
|
19259
|
+
safeWriteLog(PLUGIN_NAME22, {
|
|
18401
19260
|
hook: "command.execute.before",
|
|
18402
19261
|
command: cmd,
|
|
18403
19262
|
sessionID: input.sessionID
|
|
@@ -18412,14 +19271,14 @@ var workflowEngineServer = async (ctx) => {
|
|
|
18412
19271
|
});
|
|
18413
19272
|
},
|
|
18414
19273
|
"chat.message": async (input, output) => {
|
|
18415
|
-
await safeAsync(
|
|
19274
|
+
await safeAsync(PLUGIN_NAME22, "chat.message", async () => {
|
|
18416
19275
|
const text = extractUserText(output).trim();
|
|
18417
19276
|
if (!text.startsWith("/"))
|
|
18418
19277
|
return;
|
|
18419
19278
|
const cmd = text.split(/\s+/)[0];
|
|
18420
19279
|
if (!cmd)
|
|
18421
19280
|
return;
|
|
18422
|
-
safeWriteLog(
|
|
19281
|
+
safeWriteLog(PLUGIN_NAME22, {
|
|
18423
19282
|
hook: "chat.message",
|
|
18424
19283
|
command: cmd,
|
|
18425
19284
|
sessionID: input.sessionID
|
|
@@ -18429,7 +19288,7 @@ var workflowEngineServer = async (ctx) => {
|
|
|
18429
19288
|
}
|
|
18430
19289
|
};
|
|
18431
19290
|
};
|
|
18432
|
-
var
|
|
19291
|
+
var handler22 = workflowEngineServer;
|
|
18433
19292
|
|
|
18434
19293
|
// src/index.ts
|
|
18435
19294
|
var PLUGIN_ID = "codeforge";
|
|
@@ -18443,22 +19302,21 @@ var HANDLERS = [
|
|
|
18443
19302
|
{ name: "auto-learning", init: handler5 },
|
|
18444
19303
|
{ name: "channels", init: handler6 },
|
|
18445
19304
|
{ name: "codeforge-tools", init: handler8 },
|
|
18446
|
-
{ name: "
|
|
18447
|
-
{ name: "kh-
|
|
18448
|
-
{ name: "
|
|
18449
|
-
{ name: "
|
|
18450
|
-
{ name: "
|
|
18451
|
-
{ name: "
|
|
18452
|
-
{ name: "
|
|
18453
|
-
{ name: "
|
|
18454
|
-
{ name: "
|
|
18455
|
-
{ name: "
|
|
18456
|
-
{ name: "
|
|
18457
|
-
{ name: "token-manager", init: handler20 },
|
|
19305
|
+
{ name: "kh-auto-context", init: handler9 },
|
|
19306
|
+
{ name: "kh-reminder", init: handler10 },
|
|
19307
|
+
{ name: "memories-context", init: handler11 },
|
|
19308
|
+
{ name: "model-fallback", init: handler12 },
|
|
19309
|
+
{ name: "pwsh-utf8", init: handler15 },
|
|
19310
|
+
{ name: "session-recovery", init: handler16 },
|
|
19311
|
+
{ name: "subtask-heartbeat", init: handler13 },
|
|
19312
|
+
{ name: "subtasks", init: handler17 },
|
|
19313
|
+
{ name: "parallel-status", init: handler14 },
|
|
19314
|
+
{ name: "terminal-monitor", init: handler18 },
|
|
19315
|
+
{ name: "token-manager", init: handler19 },
|
|
18458
19316
|
{ name: "tool-heartbeat", init: handler7 },
|
|
18459
|
-
{ name: "tool-policy", init:
|
|
18460
|
-
{ name: "update-checker", init:
|
|
18461
|
-
{ name: "workflow-engine", init:
|
|
19317
|
+
{ name: "tool-policy", init: handler20 },
|
|
19318
|
+
{ name: "update-checker", init: handler21 },
|
|
19319
|
+
{ name: "workflow-engine", init: handler22 }
|
|
18462
19320
|
];
|
|
18463
19321
|
function makeSerialHook(hookName, fns) {
|
|
18464
19322
|
return async (input, output) => {
|