@andyqiu/codeforge 0.3.8 → 0.3.10
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 +78 -39
- package/agents/codeforge.md +106 -0
- package/agents/coder.md +15 -48
- package/agents/planner.md +98 -400
- package/agents/reviewer.md +10 -57
- package/dist/index.js +698 -298
- package/install.sh +119 -14
- package/package.json +1 -1
- package/workflows/parallel-explore.yaml +18 -53
package/dist/index.js
CHANGED
|
@@ -83,6 +83,11 @@ function logLifecycle(plugin, phase, extra) {
|
|
|
83
83
|
...extra
|
|
84
84
|
});
|
|
85
85
|
}
|
|
86
|
+
function makePartId(prefix = "prt_") {
|
|
87
|
+
const rand = Math.random().toString(36).slice(2, 9);
|
|
88
|
+
const ts = Date.now().toString(36).slice(-7);
|
|
89
|
+
return `${prefix}${rand}${ts}`;
|
|
90
|
+
}
|
|
86
91
|
var LOG_DIR, LOG_FILE;
|
|
87
92
|
var init_opencode_plugin_helpers = __esm(() => {
|
|
88
93
|
LOG_DIR = process.env["CODEFORGE_LOG_DIR"] ?? join(homedir(), ".cache", "codeforge");
|
|
@@ -10764,6 +10769,14 @@ function applyAnchor(content, edit, eol, beforeHash) {
|
|
|
10764
10769
|
if (!edit.anchor) {
|
|
10765
10770
|
return { ok: false, reason: "invalid_input", message: "anchor 不能为空" };
|
|
10766
10771
|
}
|
|
10772
|
+
if (edit.anchor.includes(`
|
|
10773
|
+
`)) {
|
|
10774
|
+
return {
|
|
10775
|
+
ok: false,
|
|
10776
|
+
reason: "invalid_input",
|
|
10777
|
+
message: "anchor 不能含换行符(仅支持单行匹配);多行改动请改用 pending_changes.stage 整文件暂存"
|
|
10778
|
+
};
|
|
10779
|
+
}
|
|
10767
10780
|
const lines = splitLines(content);
|
|
10768
10781
|
const hits = findAnchorLines(lines, edit.anchor, edit.regex === true);
|
|
10769
10782
|
if (hits.length === 0) {
|
|
@@ -10966,6 +10979,7 @@ function finish(before, after, beforeHash, affected) {
|
|
|
10966
10979
|
// tools/ast-edit.ts
|
|
10967
10980
|
var description16 = [
|
|
10968
10981
|
"AST 风格的精确文件编辑:anchor + 哈希校验,避免 LLM 「整文件重写」误改无关代码。",
|
|
10982
|
+
"**anchor 仅支持单行**:含 `\\n` 的多行 anchor 会被直接拒绝(reason=invalid_input);多行改动请改用 `pending_changes.stage` 整文件暂存。",
|
|
10969
10983
|
"**何时调用**:",
|
|
10970
10984
|
"- 需要在指定 anchor(行特征)后插入代码(hook 注入、import 添加)",
|
|
10971
10985
|
"- 重命名一个标识符(rename_symbol,全文件词边界匹配)",
|
|
@@ -10973,6 +10987,7 @@ var description16 = [
|
|
|
10973
10987
|
"**何时不需要**:",
|
|
10974
10988
|
"- 整文件重写更合理(直接 pending-changes.stage 整文件)",
|
|
10975
10989
|
"- 只是 1-2 个字符的 typo(用 edit 工具更直接)",
|
|
10990
|
+
"- 多行 anchor / 跨行匹配(请用 pending_changes.stage 整文件)",
|
|
10976
10991
|
"**安全约束**:",
|
|
10977
10992
|
"- 必须传 before_hash(如果文件已存在),不一致会被拒绝",
|
|
10978
10993
|
"- 默认 auto_stage=true:结果直接进 pending-changes 等待人审,不立刻落盘"
|
|
@@ -10990,7 +11005,7 @@ var AnchorAction = Common.extend({
|
|
|
10990
11005
|
"insert_after_anchor",
|
|
10991
11006
|
"insert_before_anchor"
|
|
10992
11007
|
]),
|
|
10993
|
-
anchor: z17.string().min(1).describe("anchor
|
|
11008
|
+
anchor: z17.string().min(1).describe("anchor 文本子串或正则源(必须单行;含 \\n 会被拒绝,多行改动请用 pending_changes.stage)"),
|
|
10994
11009
|
regex: z17.boolean().optional().describe("anchor 是否按 RegExp 解释,默认 false"),
|
|
10995
11010
|
occurrence: z17.number().int().min(1).optional().describe("第几次匹配(1-based),默认 1;多次命中时必须显式指定"),
|
|
10996
11011
|
payload: z17.string().describe("要写入的内容;引擎会按文件原 EOL 处理")
|
|
@@ -13512,76 +13527,146 @@ var codeforgeToolsServer = async (ctx) => {
|
|
|
13512
13527
|
};
|
|
13513
13528
|
var handler8 = codeforgeToolsServer;
|
|
13514
13529
|
|
|
13515
|
-
// plugins/
|
|
13516
|
-
|
|
13517
|
-
|
|
13518
|
-
|
|
13519
|
-
|
|
13520
|
-
|
|
13521
|
-
|
|
13522
|
-
|
|
13523
|
-
|
|
13524
|
-
|
|
13525
|
-
|
|
13526
|
-
|
|
13527
|
-
|
|
13528
|
-
|
|
13529
|
-
|
|
13530
|
-
|
|
13531
|
-
|
|
13532
|
-
|
|
13533
|
-
|
|
13534
|
-
|
|
13535
|
-
|
|
13536
|
-
|
|
13537
|
-
|
|
13538
|
-
|
|
13530
|
+
// plugins/kh-auto-context.ts
|
|
13531
|
+
init_kh_client();
|
|
13532
|
+
|
|
13533
|
+
// lib/kh-shared-context.ts
|
|
13534
|
+
var DEFAULT_MAX_PER_SESSION = 20;
|
|
13535
|
+
var DEFAULT_TTL_MS = 5 * 60 * 1000;
|
|
13536
|
+
|
|
13537
|
+
class SessionScopedCache {
|
|
13538
|
+
cache = new Map;
|
|
13539
|
+
inflight = new Map;
|
|
13540
|
+
generation = new Map;
|
|
13541
|
+
ttlMs;
|
|
13542
|
+
maxPerSession;
|
|
13543
|
+
now;
|
|
13544
|
+
constructor(opts = {}) {
|
|
13545
|
+
this.ttlMs = opts.ttlMs ?? DEFAULT_TTL_MS;
|
|
13546
|
+
this.maxPerSession = opts.maxPerSession ?? DEFAULT_MAX_PER_SESSION;
|
|
13547
|
+
this.now = opts.now ?? (() => Date.now());
|
|
13548
|
+
}
|
|
13549
|
+
async searchOrGet(args) {
|
|
13550
|
+
const { client, sessionId, query, limit, timeoutMs } = args;
|
|
13551
|
+
const queryHash = this.hashQuery(sessionId, query);
|
|
13552
|
+
const inflightKey = `${sessionId}::${queryHash}`;
|
|
13553
|
+
const sessionMap = this.cache.get(sessionId);
|
|
13554
|
+
if (sessionMap) {
|
|
13555
|
+
const entry = sessionMap.get(queryHash);
|
|
13556
|
+
if (entry && this.now() - entry.cachedAt < this.ttlMs) {
|
|
13557
|
+
sessionMap.delete(queryHash);
|
|
13558
|
+
sessionMap.set(queryHash, entry);
|
|
13559
|
+
return entry.result;
|
|
13560
|
+
}
|
|
13561
|
+
if (entry)
|
|
13562
|
+
sessionMap.delete(queryHash);
|
|
13563
|
+
}
|
|
13564
|
+
const pending = this.inflight.get(inflightKey);
|
|
13565
|
+
if (pending)
|
|
13566
|
+
return pending;
|
|
13567
|
+
const startGen = this.generation.get(sessionId) ?? 0;
|
|
13568
|
+
const promise = this.doSearchAndMaybeCache(client, sessionId, queryHash, query, limit, timeoutMs, startGen).finally(() => {
|
|
13569
|
+
this.inflight.delete(inflightKey);
|
|
13539
13570
|
});
|
|
13540
|
-
|
|
13541
|
-
|
|
13542
|
-
|
|
13543
|
-
|
|
13544
|
-
|
|
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
|
-
|
|
13571
|
+
this.inflight.set(inflightKey, promise);
|
|
13572
|
+
return promise;
|
|
13573
|
+
}
|
|
13574
|
+
onSessionEnd(sessionId) {
|
|
13575
|
+
this.generation.set(sessionId, (this.generation.get(sessionId) ?? 0) + 1);
|
|
13576
|
+
this.cache.delete(sessionId);
|
|
13577
|
+
const prefix = `${sessionId}::`;
|
|
13578
|
+
for (const key of this.inflight.keys()) {
|
|
13579
|
+
if (key.startsWith(prefix)) {
|
|
13580
|
+
this.inflight.delete(key);
|
|
13581
|
+
}
|
|
13582
|
+
}
|
|
13583
|
+
}
|
|
13584
|
+
_snapshot() {
|
|
13585
|
+
const perSession = {};
|
|
13586
|
+
let total = 0;
|
|
13587
|
+
for (const [sid, map] of this.cache.entries()) {
|
|
13588
|
+
perSession[sid] = map.size;
|
|
13589
|
+
total += map.size;
|
|
13590
|
+
}
|
|
13591
|
+
const generations = {};
|
|
13592
|
+
for (const [sid, g] of this.generation.entries()) {
|
|
13593
|
+
generations[sid] = g;
|
|
13594
|
+
}
|
|
13595
|
+
return {
|
|
13596
|
+
cacheSize: total,
|
|
13597
|
+
inflightSize: this.inflight.size,
|
|
13598
|
+
sessions: Array.from(this.cache.keys()),
|
|
13599
|
+
perSession,
|
|
13600
|
+
generations
|
|
13601
|
+
};
|
|
13602
|
+
}
|
|
13603
|
+
_reset() {
|
|
13604
|
+
this.cache.clear();
|
|
13605
|
+
this.inflight.clear();
|
|
13606
|
+
this.generation.clear();
|
|
13607
|
+
}
|
|
13608
|
+
hashQuery(sessionId, query) {
|
|
13609
|
+
return `${sessionId}::${query}`;
|
|
13610
|
+
}
|
|
13611
|
+
async doSearchAndMaybeCache(client, sessionId, queryHash, query, limit, timeoutMs, startGen) {
|
|
13612
|
+
const result = await this.doSearch(client, query, limit, timeoutMs);
|
|
13613
|
+
const currentGen = this.generation.get(sessionId) ?? 0;
|
|
13614
|
+
if (currentGen === startGen && result.ok) {
|
|
13615
|
+
this.writeCache(sessionId, queryHash, query, result);
|
|
13616
|
+
}
|
|
13617
|
+
return result;
|
|
13618
|
+
}
|
|
13619
|
+
async doSearch(client, query, limit, timeoutMs) {
|
|
13620
|
+
const callPromise = (async () => {
|
|
13621
|
+
try {
|
|
13622
|
+
return await client.search({ query, limit });
|
|
13623
|
+
} catch (err) {
|
|
13624
|
+
return {
|
|
13625
|
+
ok: false,
|
|
13626
|
+
reason: "kh_returned_error",
|
|
13627
|
+
message: err instanceof Error ? err.message : String(err)
|
|
13628
|
+
};
|
|
13629
|
+
}
|
|
13630
|
+
})();
|
|
13631
|
+
if (timeoutMs === undefined || timeoutMs <= 0) {
|
|
13632
|
+
return callPromise;
|
|
13633
|
+
}
|
|
13634
|
+
let timer = null;
|
|
13635
|
+
try {
|
|
13636
|
+
const racer = new Promise((res) => {
|
|
13637
|
+
timer = setTimeout(() => res({
|
|
13638
|
+
ok: false,
|
|
13639
|
+
reason: "kh_returned_error",
|
|
13640
|
+
message: `kh search timeout after ${timeoutMs}ms`
|
|
13641
|
+
}), timeoutMs);
|
|
13575
13642
|
});
|
|
13643
|
+
return await Promise.race([callPromise, racer]);
|
|
13644
|
+
} finally {
|
|
13645
|
+
if (timer)
|
|
13646
|
+
clearTimeout(timer);
|
|
13576
13647
|
}
|
|
13577
|
-
}
|
|
13578
|
-
|
|
13579
|
-
|
|
13648
|
+
}
|
|
13649
|
+
writeCache(sessionId, queryHash, query, result) {
|
|
13650
|
+
let sessionMap = this.cache.get(sessionId);
|
|
13651
|
+
if (!sessionMap) {
|
|
13652
|
+
sessionMap = new Map;
|
|
13653
|
+
this.cache.set(sessionId, sessionMap);
|
|
13654
|
+
}
|
|
13655
|
+
sessionMap.delete(queryHash);
|
|
13656
|
+
sessionMap.set(queryHash, { query, result, cachedAt: this.now() });
|
|
13657
|
+
while (sessionMap.size > this.maxPerSession) {
|
|
13658
|
+
const oldest = sessionMap.keys().next().value;
|
|
13659
|
+
if (oldest === undefined)
|
|
13660
|
+
break;
|
|
13661
|
+
sessionMap.delete(oldest);
|
|
13662
|
+
}
|
|
13663
|
+
}
|
|
13664
|
+
}
|
|
13665
|
+
var sharedKhCache = new SessionScopedCache;
|
|
13580
13666
|
|
|
13581
13667
|
// plugins/kh-auto-context.ts
|
|
13582
|
-
init_kh_client();
|
|
13583
13668
|
init_opencode_plugin_helpers();
|
|
13584
|
-
var
|
|
13669
|
+
var PLUGIN_NAME9 = "kh-auto-context";
|
|
13585
13670
|
var INJECTION_MODE = "observe-only";
|
|
13586
13671
|
function resolveInjectionMode(client) {
|
|
13587
13672
|
return client.hasTransport() ? "system-injected" : "observe-only";
|
|
@@ -13680,7 +13765,7 @@ var CODEFORGE_CONSTRAINTS = [
|
|
|
13680
13765
|
];
|
|
13681
13766
|
async function seedConstraints(client, log6) {
|
|
13682
13767
|
if (!client.hasTransport()) {
|
|
13683
|
-
log6?.debug?.(`[${
|
|
13768
|
+
log6?.debug?.(`[${PLUGIN_NAME9}] seedConstraints: transport 不可用,跳过`, {});
|
|
13684
13769
|
return { ok: false, reason: "transport_unavailable" };
|
|
13685
13770
|
}
|
|
13686
13771
|
try {
|
|
@@ -13694,7 +13779,7 @@ async function seedConstraints(client, log6) {
|
|
|
13694
13779
|
});
|
|
13695
13780
|
if (result && typeof result === "object" && "ok" in result && result.ok === false) {
|
|
13696
13781
|
const r = result;
|
|
13697
|
-
log6?.warn(`[${
|
|
13782
|
+
log6?.warn(`[${PLUGIN_NAME9}] seedConstraints 降级`, {
|
|
13698
13783
|
reason: r.reason,
|
|
13699
13784
|
message: r.message
|
|
13700
13785
|
});
|
|
@@ -13704,11 +13789,11 @@ async function seedConstraints(client, log6) {
|
|
|
13704
13789
|
message: r.message
|
|
13705
13790
|
};
|
|
13706
13791
|
}
|
|
13707
|
-
log6?.info(`[${
|
|
13792
|
+
log6?.info(`[${PLUGIN_NAME9}] seedConstraints: 已写入 ${CODEFORGE_CONSTRAINTS.length} 条 constraints`, { count: CODEFORGE_CONSTRAINTS.length });
|
|
13708
13793
|
return { ok: true, itemsWritten: CODEFORGE_CONSTRAINTS.length };
|
|
13709
13794
|
} catch (err) {
|
|
13710
13795
|
const message = err instanceof Error ? err.message : String(err);
|
|
13711
|
-
log6?.warn(`[${
|
|
13796
|
+
log6?.warn(`[${PLUGIN_NAME9}] seedConstraints 失败(已静默)`, { error: message });
|
|
13712
13797
|
return { ok: false, reason: "exception", message };
|
|
13713
13798
|
}
|
|
13714
13799
|
}
|
|
@@ -13723,7 +13808,7 @@ function createSystemInjectedHook(client, sessionId, log6) {
|
|
|
13723
13808
|
});
|
|
13724
13809
|
if (result && typeof result === "object" && "ok" in result && result.ok === false) {
|
|
13725
13810
|
const r = result;
|
|
13726
|
-
log6?.warn(`[${
|
|
13811
|
+
log6?.warn(`[${PLUGIN_NAME9}] system-injected 降级到 observe-only:${r.reason ?? "unknown"}`, {
|
|
13727
13812
|
sessionId,
|
|
13728
13813
|
section,
|
|
13729
13814
|
preview: markdown.slice(0, 200),
|
|
@@ -13732,12 +13817,12 @@ function createSystemInjectedHook(client, sessionId, log6) {
|
|
|
13732
13817
|
});
|
|
13733
13818
|
return;
|
|
13734
13819
|
}
|
|
13735
|
-
log6?.info(`[${
|
|
13820
|
+
log6?.info(`[${PLUGIN_NAME9}] system-injected: 写入 KH working_memory 成功 (${markdown.length} chars)`, {
|
|
13736
13821
|
sessionId,
|
|
13737
13822
|
section
|
|
13738
13823
|
});
|
|
13739
13824
|
} catch (err) {
|
|
13740
|
-
log6?.warn(`[${
|
|
13825
|
+
log6?.warn(`[${PLUGIN_NAME9}] system-injected 抛异常,降级到 observe-only`, {
|
|
13741
13826
|
sessionId,
|
|
13742
13827
|
section,
|
|
13743
13828
|
preview: markdown.slice(0, 200),
|
|
@@ -13746,23 +13831,16 @@ function createSystemInjectedHook(client, sessionId, log6) {
|
|
|
13746
13831
|
}
|
|
13747
13832
|
};
|
|
13748
13833
|
}
|
|
13749
|
-
|
|
13834
|
+
var inflight2 = new Set;
|
|
13835
|
+
var INFLIGHT_CAP = 5;
|
|
13836
|
+
function inflightKey(sessionId, query) {
|
|
13837
|
+
return `${sessionId ?? "global"}:${Date.now()}:${Math.random().toString(36).slice(2, 10)}`;
|
|
13838
|
+
}
|
|
13839
|
+
async function runKhSearchAndInject(args) {
|
|
13840
|
+
const { query, ctx, opts, mode } = args;
|
|
13750
13841
|
const cfg = opts.config ?? DEFAULT_CONFIG5;
|
|
13751
|
-
const mode = opts.mode ?? INJECTION_MODE;
|
|
13752
|
-
const ctx = raw ?? {};
|
|
13753
13842
|
const log6 = ctx.log;
|
|
13754
|
-
const
|
|
13755
|
-
if (!shouldInject(text, cfg)) {
|
|
13756
|
-
log6?.debug?.(`[${PLUGIN_NAME10}] skip (filter)`, { textLen: text.length });
|
|
13757
|
-
return null;
|
|
13758
|
-
}
|
|
13759
|
-
const query = extractQuery(text);
|
|
13760
|
-
if (!query)
|
|
13761
|
-
return null;
|
|
13762
|
-
if (opts.cache.shouldSkip(query)) {
|
|
13763
|
-
log6?.debug?.(`[${PLUGIN_NAME10}] cache hit, skip`, { query });
|
|
13764
|
-
return null;
|
|
13765
|
-
}
|
|
13843
|
+
const startedAt = Date.now();
|
|
13766
13844
|
const racer = opts.scheduler?.raceTimeout ?? (async (p, ms) => {
|
|
13767
13845
|
let timer = null;
|
|
13768
13846
|
try {
|
|
@@ -13777,20 +13855,54 @@ async function handleMessage2(raw, opts) {
|
|
|
13777
13855
|
clearTimeout(timer);
|
|
13778
13856
|
}
|
|
13779
13857
|
});
|
|
13780
|
-
|
|
13858
|
+
let result;
|
|
13859
|
+
try {
|
|
13860
|
+
const searchPromise = sharedKhCache.searchOrGet({
|
|
13861
|
+
client: opts.client,
|
|
13862
|
+
sessionId: ctx.sessionId ?? "global",
|
|
13863
|
+
query,
|
|
13864
|
+
limit: cfg.limit
|
|
13865
|
+
});
|
|
13866
|
+
result = await racer(searchPromise, cfg.timeoutMs);
|
|
13867
|
+
} catch (err) {
|
|
13868
|
+
log6?.warn(`[${PLUGIN_NAME9}] client.search threw (sync or async), return null`, {
|
|
13869
|
+
query,
|
|
13870
|
+
elapsedMs: Date.now() - startedAt,
|
|
13871
|
+
sessionId: ctx.sessionId,
|
|
13872
|
+
error: err instanceof Error ? err.message : String(err)
|
|
13873
|
+
});
|
|
13874
|
+
opts.cache.record(query, []);
|
|
13875
|
+
return null;
|
|
13876
|
+
}
|
|
13781
13877
|
if (result === "__timeout__") {
|
|
13782
|
-
log6?.warn(`[${
|
|
13878
|
+
log6?.warn(`[${PLUGIN_NAME9}] timeout`, {
|
|
13879
|
+
query,
|
|
13880
|
+
ms: cfg.timeoutMs,
|
|
13881
|
+
elapsedMs: Date.now() - startedAt,
|
|
13882
|
+
sessionId: ctx.sessionId
|
|
13883
|
+
});
|
|
13783
13884
|
opts.cache.record(query, []);
|
|
13784
13885
|
return null;
|
|
13785
13886
|
}
|
|
13786
13887
|
if (!result.ok) {
|
|
13787
|
-
log6?.
|
|
13888
|
+
log6?.warn(`[${PLUGIN_NAME9}] kh degraded`, {
|
|
13889
|
+
reason: result.reason,
|
|
13890
|
+
query,
|
|
13891
|
+
elapsedMs: Date.now() - startedAt,
|
|
13892
|
+
sessionId: ctx.sessionId
|
|
13893
|
+
});
|
|
13788
13894
|
opts.cache.record(query, []);
|
|
13789
13895
|
return null;
|
|
13790
13896
|
}
|
|
13791
13897
|
const filtered = result.insights.filter((i) => (i.confidence ?? 0) >= cfg.minConfidence);
|
|
13792
13898
|
if (filtered.length === 0) {
|
|
13793
13899
|
opts.cache.record(query, []);
|
|
13900
|
+
log6?.debug?.(`[${PLUGIN_NAME9}] no candidate above threshold`, {
|
|
13901
|
+
query,
|
|
13902
|
+
rawCount: result.insights.length,
|
|
13903
|
+
elapsedMs: Date.now() - startedAt,
|
|
13904
|
+
sessionId: ctx.sessionId
|
|
13905
|
+
});
|
|
13794
13906
|
return null;
|
|
13795
13907
|
}
|
|
13796
13908
|
const payload = formatInjection(query, filtered, mode);
|
|
@@ -13798,22 +13910,68 @@ async function handleMessage2(raw, opts) {
|
|
|
13798
13910
|
try {
|
|
13799
13911
|
await ctx.injectContext(payload.markdown);
|
|
13800
13912
|
} catch (err) {
|
|
13801
|
-
log6?.warn(`[${
|
|
13802
|
-
error: err instanceof Error ? err.message : String(err)
|
|
13913
|
+
log6?.warn(`[${PLUGIN_NAME9}] injectContext threw`, {
|
|
13914
|
+
error: err instanceof Error ? err.message : String(err),
|
|
13915
|
+
query,
|
|
13916
|
+
sessionId: ctx.sessionId
|
|
13803
13917
|
});
|
|
13804
13918
|
}
|
|
13805
13919
|
}
|
|
13806
13920
|
opts.cache.record(query, filtered);
|
|
13807
|
-
log6?.info(`[${
|
|
13921
|
+
log6?.info(`[${PLUGIN_NAME9}] inject complete (${mode})`, {
|
|
13922
|
+
query,
|
|
13923
|
+
mode,
|
|
13924
|
+
candidateCount: filtered.length,
|
|
13925
|
+
elapsedMs: Date.now() - startedAt,
|
|
13926
|
+
sessionId: ctx.sessionId
|
|
13927
|
+
});
|
|
13808
13928
|
return payload;
|
|
13809
13929
|
}
|
|
13810
|
-
|
|
13930
|
+
async function handleMessage2(raw, opts) {
|
|
13931
|
+
const cfg = opts.config ?? DEFAULT_CONFIG5;
|
|
13932
|
+
const mode = opts.mode ?? INJECTION_MODE;
|
|
13933
|
+
const ctx = raw ?? {};
|
|
13934
|
+
const log6 = ctx.log;
|
|
13935
|
+
const text = (ctx.content ?? "").trim();
|
|
13936
|
+
if (!shouldInject(text, cfg)) {
|
|
13937
|
+
log6?.debug?.(`[${PLUGIN_NAME9}] skip (filter)`, { textLen: text.length });
|
|
13938
|
+
return;
|
|
13939
|
+
}
|
|
13940
|
+
const query = extractQuery(text);
|
|
13941
|
+
if (!query)
|
|
13942
|
+
return;
|
|
13943
|
+
if (opts.cache.shouldSkip(query)) {
|
|
13944
|
+
log6?.debug?.(`[${PLUGIN_NAME9}] cache hit, skip`, { query });
|
|
13945
|
+
return;
|
|
13946
|
+
}
|
|
13947
|
+
if (inflight2.size >= INFLIGHT_CAP) {
|
|
13948
|
+
log6?.warn(`[${PLUGIN_NAME9}] inflight cap reached, skip`, {
|
|
13949
|
+
query,
|
|
13950
|
+
inflightSize: inflight2.size,
|
|
13951
|
+
cap: INFLIGHT_CAP,
|
|
13952
|
+
sessionId: ctx.sessionId
|
|
13953
|
+
});
|
|
13954
|
+
return;
|
|
13955
|
+
}
|
|
13956
|
+
const key = inflightKey(ctx.sessionId, query);
|
|
13957
|
+
inflight2.add(key);
|
|
13958
|
+
runKhSearchAndInject({ query, ctx, opts, mode }).catch((err) => {
|
|
13959
|
+
log6?.warn(`[${PLUGIN_NAME9}] runKhSearchAndInject 顶层兜底捕获`, {
|
|
13960
|
+
error: err instanceof Error ? err.message : String(err),
|
|
13961
|
+
query,
|
|
13962
|
+
sessionId: ctx.sessionId
|
|
13963
|
+
});
|
|
13964
|
+
}).finally(() => {
|
|
13965
|
+
inflight2.delete(key);
|
|
13966
|
+
});
|
|
13967
|
+
}
|
|
13968
|
+
logLifecycle(PLUGIN_NAME9, "import");
|
|
13811
13969
|
var sharedClient2 = new KhClient;
|
|
13812
13970
|
var sharedCache = new QueryCache(DEFAULT_CONFIG5.cacheTtlMs);
|
|
13813
13971
|
var khAutoContextServer = async (ctx) => {
|
|
13814
|
-
const log6 = makePluginLogger(
|
|
13972
|
+
const log6 = makePluginLogger(PLUGIN_NAME9);
|
|
13815
13973
|
const runtimeMode = resolveInjectionMode(sharedClient2);
|
|
13816
|
-
logLifecycle(
|
|
13974
|
+
logLifecycle(PLUGIN_NAME9, "activate", {
|
|
13817
13975
|
directory: ctx.directory,
|
|
13818
13976
|
minConfidence: DEFAULT_CONFIG5.minConfidence,
|
|
13819
13977
|
timeoutMs: DEFAULT_CONFIG5.timeoutMs,
|
|
@@ -13827,7 +13985,7 @@ var khAutoContextServer = async (ctx) => {
|
|
|
13827
13985
|
});
|
|
13828
13986
|
return {
|
|
13829
13987
|
"chat.message": async (input, output) => {
|
|
13830
|
-
await safeAsync(
|
|
13988
|
+
await safeAsync(PLUGIN_NAME9, "chat.message", async () => {
|
|
13831
13989
|
const text = extractUserText(output);
|
|
13832
13990
|
if (!text)
|
|
13833
13991
|
return;
|
|
@@ -13845,10 +14003,25 @@ var khAutoContextServer = async (ctx) => {
|
|
|
13845
14003
|
log: log6
|
|
13846
14004
|
}, { client: sharedClient2, cache: sharedCache, mode: runtimeMode });
|
|
13847
14005
|
});
|
|
14006
|
+
},
|
|
14007
|
+
event: async ({ event }) => {
|
|
14008
|
+
await safeAsync(PLUGIN_NAME9, "event", async () => {
|
|
14009
|
+
const e = event;
|
|
14010
|
+
if (e.type !== "session.idle")
|
|
14011
|
+
return;
|
|
14012
|
+
const props = e.properties;
|
|
14013
|
+
const sid = props?.sessionID;
|
|
14014
|
+
if (typeof sid !== "string" || !sid)
|
|
14015
|
+
return;
|
|
14016
|
+
sharedKhCache.onSessionEnd(sid);
|
|
14017
|
+
log6.debug?.(`[${PLUGIN_NAME9}] session.idle: cleared shared cache`, {
|
|
14018
|
+
sessionID: sid
|
|
14019
|
+
});
|
|
14020
|
+
});
|
|
13848
14021
|
}
|
|
13849
14022
|
};
|
|
13850
14023
|
};
|
|
13851
|
-
var
|
|
14024
|
+
var handler9 = khAutoContextServer;
|
|
13852
14025
|
|
|
13853
14026
|
// plugins/kh-reminder.ts
|
|
13854
14027
|
init_opencode_plugin_helpers();
|
|
@@ -13969,8 +14142,8 @@ async function condense(input, opts = {}) {
|
|
|
13969
14142
|
}
|
|
13970
14143
|
|
|
13971
14144
|
// plugins/kh-reminder.ts
|
|
13972
|
-
var
|
|
13973
|
-
logLifecycle(
|
|
14145
|
+
var PLUGIN_NAME10 = "kh-reminder";
|
|
14146
|
+
logLifecycle(PLUGIN_NAME10, "import", {});
|
|
13974
14147
|
var TRIGGER_WORDS_ZH = [
|
|
13975
14148
|
"怎么",
|
|
13976
14149
|
"怎样",
|
|
@@ -14124,7 +14297,7 @@ function handleObserve(raw, log6) {
|
|
|
14124
14297
|
const result = evaluate(ctx);
|
|
14125
14298
|
if (result.triggered && result.reason) {
|
|
14126
14299
|
const reasonStr = formatReason(result.reason);
|
|
14127
|
-
log6?.info(`[${
|
|
14300
|
+
log6?.info(`[${PLUGIN_NAME10}] ⚡ KH 提醒(observe-only) · session=${result.sessionId} · ${reasonStr}`, {
|
|
14128
14301
|
sessionId: result.sessionId,
|
|
14129
14302
|
reason: result.reason,
|
|
14130
14303
|
suggestion: "调用 smart_search 查项目历史;完成后用 save_chat_insight 沉淀"
|
|
@@ -14132,7 +14305,7 @@ function handleObserve(raw, log6) {
|
|
|
14132
14305
|
}
|
|
14133
14306
|
return result;
|
|
14134
14307
|
} catch (err) {
|
|
14135
|
-
log6?.warn(`[${
|
|
14308
|
+
log6?.warn(`[${PLUGIN_NAME10}] evaluate 异常(已隔离)`, {
|
|
14136
14309
|
error: err instanceof Error ? err.message : String(err)
|
|
14137
14310
|
});
|
|
14138
14311
|
return null;
|
|
@@ -14148,9 +14321,9 @@ function formatReason(r) {
|
|
|
14148
14321
|
return `no-search-in-recent-rounds (last ${r.rounds})`;
|
|
14149
14322
|
}
|
|
14150
14323
|
}
|
|
14151
|
-
var log6 = makePluginLogger(
|
|
14324
|
+
var log6 = makePluginLogger(PLUGIN_NAME10);
|
|
14152
14325
|
var khReminderServer = async (ctx) => {
|
|
14153
|
-
logLifecycle(
|
|
14326
|
+
logLifecycle(PLUGIN_NAME10, "activate", {
|
|
14154
14327
|
directory: ctx.directory,
|
|
14155
14328
|
threshold: DEFAULT_THRESHOLD,
|
|
14156
14329
|
cooldown_ms: COOLDOWN_MS,
|
|
@@ -14158,7 +14331,7 @@ var khReminderServer = async (ctx) => {
|
|
|
14158
14331
|
});
|
|
14159
14332
|
return {
|
|
14160
14333
|
"experimental.chat.messages.transform": async (_input, output) => {
|
|
14161
|
-
await safeAsync(
|
|
14334
|
+
await safeAsync(PLUGIN_NAME10, "experimental.chat.messages.transform", async () => {
|
|
14162
14335
|
const list = output.messages;
|
|
14163
14336
|
if (!Array.isArray(list) || list.length === 0)
|
|
14164
14337
|
return;
|
|
@@ -14177,7 +14350,7 @@ var khReminderServer = async (ctx) => {
|
|
|
14177
14350
|
const result = handleObserve({ messages: flat, sessionId }, log6);
|
|
14178
14351
|
if (!result)
|
|
14179
14352
|
return;
|
|
14180
|
-
safeWriteLog(
|
|
14353
|
+
safeWriteLog(PLUGIN_NAME10, {
|
|
14181
14354
|
hook: "experimental.chat.messages.transform",
|
|
14182
14355
|
mode: "observe-only",
|
|
14183
14356
|
sessionId: result.sessionId,
|
|
@@ -14190,7 +14363,7 @@ var khReminderServer = async (ctx) => {
|
|
|
14190
14363
|
}
|
|
14191
14364
|
};
|
|
14192
14365
|
};
|
|
14193
|
-
var
|
|
14366
|
+
var handler10 = khReminderServer;
|
|
14194
14367
|
|
|
14195
14368
|
// lib/memories.ts
|
|
14196
14369
|
import { promises as fs8 } from "node:fs";
|
|
@@ -14392,7 +14565,7 @@ function bagOfWordsScore(query, doc) {
|
|
|
14392
14565
|
|
|
14393
14566
|
// plugins/memories-context.ts
|
|
14394
14567
|
init_opencode_plugin_helpers();
|
|
14395
|
-
var
|
|
14568
|
+
var PLUGIN_NAME11 = "memories-context";
|
|
14396
14569
|
var INJECTION_MODE2 = "observe-only";
|
|
14397
14570
|
var DEFAULT_CONFIG6 = {
|
|
14398
14571
|
minTextLength: 8,
|
|
@@ -14490,14 +14663,14 @@ async function handleMessage3(raw, opts) {
|
|
|
14490
14663
|
return await handleDirective(dir, ctx, opts.memCfg, log7);
|
|
14491
14664
|
}
|
|
14492
14665
|
if (!shouldRecall(text, cfg)) {
|
|
14493
|
-
log7?.debug?.(`[${
|
|
14666
|
+
log7?.debug?.(`[${PLUGIN_NAME11}] skip (filter)`, { textLen: text.length });
|
|
14494
14667
|
return { kind: "noop", reason: "filtered", mode: INJECTION_MODE2 };
|
|
14495
14668
|
}
|
|
14496
14669
|
const query = extractQuery2(text);
|
|
14497
14670
|
if (!query)
|
|
14498
14671
|
return { kind: "noop", reason: "empty_query", mode: INJECTION_MODE2 };
|
|
14499
14672
|
if (cache2.shouldSkip(query)) {
|
|
14500
|
-
log7?.debug?.(`[${
|
|
14673
|
+
log7?.debug?.(`[${PLUGIN_NAME11}] cache hit`, { query });
|
|
14501
14674
|
return { kind: "noop", reason: "cache", mode: INJECTION_MODE2 };
|
|
14502
14675
|
}
|
|
14503
14676
|
const racer = opts.scheduler?.raceTimeout ?? (async (p, ms) => {
|
|
@@ -14522,7 +14695,7 @@ async function handleMessage3(raw, opts) {
|
|
|
14522
14695
|
}, opts.memCfg);
|
|
14523
14696
|
const result = await racer(injectPromise, cfg.timeoutMs);
|
|
14524
14697
|
if (result === "__timeout__") {
|
|
14525
|
-
log7?.warn(`[${
|
|
14698
|
+
log7?.warn(`[${PLUGIN_NAME11}] timeout`, { query, ms: cfg.timeoutMs });
|
|
14526
14699
|
cache2.record(query, 0);
|
|
14527
14700
|
return { kind: "noop", reason: "timeout", mode: INJECTION_MODE2 };
|
|
14528
14701
|
}
|
|
@@ -14534,13 +14707,13 @@ async function handleMessage3(raw, opts) {
|
|
|
14534
14707
|
try {
|
|
14535
14708
|
await ctx.injectContext(result.text);
|
|
14536
14709
|
} catch (err) {
|
|
14537
|
-
log7?.warn(`[${
|
|
14710
|
+
log7?.warn(`[${PLUGIN_NAME11}] injectContext threw`, {
|
|
14538
14711
|
error: err instanceof Error ? err.message : String(err)
|
|
14539
14712
|
});
|
|
14540
14713
|
}
|
|
14541
14714
|
}
|
|
14542
14715
|
cache2.record(query, result.recalled);
|
|
14543
|
-
log7?.info(`[${
|
|
14716
|
+
log7?.info(`[${PLUGIN_NAME11}] observe-only: ${result.used}/${result.recalled} memories ready`, { query, mode: INJECTION_MODE2 });
|
|
14544
14717
|
return { kind: "injected", payload: result, mode: INJECTION_MODE2 };
|
|
14545
14718
|
}
|
|
14546
14719
|
async function handleDirective(dir, ctx, memCfg, log7) {
|
|
@@ -14549,14 +14722,14 @@ async function handleDirective(dir, ctx, memCfg, log7) {
|
|
|
14549
14722
|
if (r.ok) {
|
|
14550
14723
|
const target = r.written_to === "kh" ? "KH" : "本地";
|
|
14551
14724
|
await safeReply(ctx, `\uD83E\uDDE0 已记住(${dir.scope} / ${target},id=${r.id})`);
|
|
14552
|
-
log7?.info(`[${
|
|
14725
|
+
log7?.info(`[${PLUGIN_NAME11}] /remember ok`, {
|
|
14553
14726
|
scope: dir.scope,
|
|
14554
14727
|
id: r.id,
|
|
14555
14728
|
mode: INJECTION_MODE2
|
|
14556
14729
|
});
|
|
14557
14730
|
} else {
|
|
14558
14731
|
await safeReply(ctx, `❌ 记忆失败:${r.error ?? "unknown"}`);
|
|
14559
|
-
log7?.warn(`[${
|
|
14732
|
+
log7?.warn(`[${PLUGIN_NAME11}] /remember failed`, { error: r.error });
|
|
14560
14733
|
}
|
|
14561
14734
|
return { kind: "remembered", result: r, mode: INJECTION_MODE2 };
|
|
14562
14735
|
}
|
|
@@ -14587,22 +14760,22 @@ async function safeReply(ctx, text) {
|
|
|
14587
14760
|
await ctx.reply(text);
|
|
14588
14761
|
} catch {}
|
|
14589
14762
|
}
|
|
14590
|
-
logLifecycle(
|
|
14763
|
+
logLifecycle(PLUGIN_NAME11, "import");
|
|
14591
14764
|
var sharedCache2 = new QueryCache2(DEFAULT_CONFIG6.cacheTtlMs);
|
|
14592
14765
|
function buildMemCfg(directory) {
|
|
14593
14766
|
return { projectRoot: directory };
|
|
14594
14767
|
}
|
|
14595
14768
|
var memoriesContextServer = async (ctx) => {
|
|
14596
|
-
const log7 = makePluginLogger(
|
|
14769
|
+
const log7 = makePluginLogger(PLUGIN_NAME11);
|
|
14597
14770
|
const memCfg = buildMemCfg(ctx.directory);
|
|
14598
|
-
logLifecycle(
|
|
14771
|
+
logLifecycle(PLUGIN_NAME11, "activate", {
|
|
14599
14772
|
directory: ctx.directory,
|
|
14600
14773
|
projectRoot: memCfg.projectRoot,
|
|
14601
14774
|
mode: INJECTION_MODE2
|
|
14602
14775
|
});
|
|
14603
14776
|
return {
|
|
14604
14777
|
"chat.message": async (input, output) => {
|
|
14605
|
-
await safeAsync(
|
|
14778
|
+
await safeAsync(PLUGIN_NAME11, "chat.message", async () => {
|
|
14606
14779
|
const text = extractUserText(output);
|
|
14607
14780
|
if (!text)
|
|
14608
14781
|
return;
|
|
@@ -14628,18 +14801,18 @@ var memoriesContextServer = async (ctx) => {
|
|
|
14628
14801
|
}
|
|
14629
14802
|
};
|
|
14630
14803
|
};
|
|
14631
|
-
var
|
|
14804
|
+
var handler11 = memoriesContextServer;
|
|
14632
14805
|
|
|
14633
14806
|
// plugins/model-fallback.ts
|
|
14634
14807
|
init_opencode_plugin_helpers();
|
|
14635
|
-
var
|
|
14808
|
+
var PLUGIN_NAME12 = "model-fallback";
|
|
14636
14809
|
var state2 = {
|
|
14637
14810
|
config: null,
|
|
14638
14811
|
configPath: null,
|
|
14639
14812
|
warnings: [],
|
|
14640
14813
|
error: null
|
|
14641
14814
|
};
|
|
14642
|
-
logLifecycle(
|
|
14815
|
+
logLifecycle(PLUGIN_NAME12, "import");
|
|
14643
14816
|
function loadOnce(root) {
|
|
14644
14817
|
if (state2.config !== null || state2.error !== null)
|
|
14645
14818
|
return;
|
|
@@ -14649,11 +14822,11 @@ function loadOnce(root) {
|
|
|
14649
14822
|
state2.configPath = r.path ?? null;
|
|
14650
14823
|
state2.warnings = r.warnings;
|
|
14651
14824
|
if (r.warnings.length > 0) {
|
|
14652
|
-
safeWriteLog(
|
|
14825
|
+
safeWriteLog(PLUGIN_NAME12, { phase: "load.warnings", warnings: r.warnings });
|
|
14653
14826
|
}
|
|
14654
14827
|
} else {
|
|
14655
14828
|
state2.error = r.error ?? "unknown_load_error";
|
|
14656
|
-
safeWriteLog(
|
|
14829
|
+
safeWriteLog(PLUGIN_NAME12, { phase: "load.failed", error: state2.error, path: r.path });
|
|
14657
14830
|
}
|
|
14658
14831
|
}
|
|
14659
14832
|
var MODEL_ERR_PATTERNS = [
|
|
@@ -14698,9 +14871,9 @@ fallback 链已用尽:${meta.chain.join(" → ")}`;
|
|
|
14698
14871
|
完整链:${meta.chain.join(" → ")}`;
|
|
14699
14872
|
}
|
|
14700
14873
|
var modelFallbackServer = async (ctx) => {
|
|
14701
|
-
const log7 = makePluginLogger(
|
|
14874
|
+
const log7 = makePluginLogger(PLUGIN_NAME12);
|
|
14702
14875
|
loadOnce(ctx.directory ?? process.cwd());
|
|
14703
|
-
logLifecycle(
|
|
14876
|
+
logLifecycle(PLUGIN_NAME12, "activate", {
|
|
14704
14877
|
directory: ctx.directory,
|
|
14705
14878
|
config_path: state2.configPath,
|
|
14706
14879
|
config_loaded: state2.config !== null,
|
|
@@ -14710,7 +14883,7 @@ var modelFallbackServer = async (ctx) => {
|
|
|
14710
14883
|
});
|
|
14711
14884
|
return {
|
|
14712
14885
|
"chat.params": async (input, output) => {
|
|
14713
|
-
await safeAsync(
|
|
14886
|
+
await safeAsync(PLUGIN_NAME12, "chat.params", async () => {
|
|
14714
14887
|
if (!state2.config)
|
|
14715
14888
|
return;
|
|
14716
14889
|
const agent = input.agent;
|
|
@@ -14731,7 +14904,7 @@ var modelFallbackServer = async (ctx) => {
|
|
|
14731
14904
|
next: meta.next_fallback,
|
|
14732
14905
|
source: meta.source
|
|
14733
14906
|
};
|
|
14734
|
-
safeWriteLog(
|
|
14907
|
+
safeWriteLog(PLUGIN_NAME12, {
|
|
14735
14908
|
hook: "chat.params",
|
|
14736
14909
|
agent,
|
|
14737
14910
|
model: currentModel,
|
|
@@ -14741,7 +14914,7 @@ var modelFallbackServer = async (ctx) => {
|
|
|
14741
14914
|
});
|
|
14742
14915
|
},
|
|
14743
14916
|
event: async ({ event }) => {
|
|
14744
|
-
await safeAsync(
|
|
14917
|
+
await safeAsync(PLUGIN_NAME12, "event", async () => {
|
|
14745
14918
|
if (!state2.config)
|
|
14746
14919
|
return;
|
|
14747
14920
|
const e = event;
|
|
@@ -14757,8 +14930,8 @@ var modelFallbackServer = async (ctx) => {
|
|
|
14757
14930
|
const model = props.model ?? "unknown/unknown";
|
|
14758
14931
|
const meta = buildFallbackMeta(state2.config, agent, model);
|
|
14759
14932
|
const suggestion = meta ? buildSuggestion(meta, String(message)) : `⚠️ ${agent}/${model} 失败:${message}`;
|
|
14760
|
-
log7.warn(`[${
|
|
14761
|
-
safeWriteLog(
|
|
14933
|
+
log7.warn(`[${PLUGIN_NAME12}] ${suggestion}`);
|
|
14934
|
+
safeWriteLog(PLUGIN_NAME12, {
|
|
14762
14935
|
hook: "event.error",
|
|
14763
14936
|
eventType: e.type,
|
|
14764
14937
|
agent,
|
|
@@ -14770,19 +14943,24 @@ var modelFallbackServer = async (ctx) => {
|
|
|
14770
14943
|
}
|
|
14771
14944
|
};
|
|
14772
14945
|
};
|
|
14773
|
-
var
|
|
14946
|
+
var handler12 = modelFallbackServer;
|
|
14774
14947
|
|
|
14775
14948
|
// plugins/subtask-heartbeat.ts
|
|
14776
14949
|
init_opencode_plugin_helpers();
|
|
14777
|
-
var
|
|
14778
|
-
logLifecycle(
|
|
14950
|
+
var PLUGIN_NAME13 = "subtask-heartbeat";
|
|
14951
|
+
logLifecycle(PLUGIN_NAME13, "import", {});
|
|
14779
14952
|
var HEARTBEAT_INTERVAL_MS2 = 30000;
|
|
14780
14953
|
var HEARTBEAT_DEBOUNCE_MS = 25000;
|
|
14781
14954
|
var TOAST_DURATION_MS3 = 5000;
|
|
14782
14955
|
var START_TOAST_DURATION_MS = 2000;
|
|
14783
|
-
var
|
|
14956
|
+
var PENDING_TASK_TTL_MS = 60000;
|
|
14957
|
+
var PENDING_TASK_MAX_PARENTS = 64;
|
|
14958
|
+
var PENDING_TASK_MAX_PER_PARENT = 16;
|
|
14959
|
+
var DESCRIPTION_MAX_LEN = 60;
|
|
14960
|
+
var inflight3 = new Map;
|
|
14961
|
+
var pendingTask = new Map;
|
|
14784
14962
|
function _snapshotInflight() {
|
|
14785
|
-
return [...
|
|
14963
|
+
return [...inflight3.values()].map((r) => ({ ...r }));
|
|
14786
14964
|
}
|
|
14787
14965
|
function getInflightSnapshot() {
|
|
14788
14966
|
return _snapshotInflight();
|
|
@@ -14824,20 +15002,97 @@ function extractEndedSessionID(event) {
|
|
|
14824
15002
|
}
|
|
14825
15003
|
return null;
|
|
14826
15004
|
}
|
|
15005
|
+
function extractSubtaskPart(event) {
|
|
15006
|
+
if (!event || typeof event !== "object")
|
|
15007
|
+
return null;
|
|
15008
|
+
const e = event;
|
|
15009
|
+
if (e.type !== "message.part.updated")
|
|
15010
|
+
return null;
|
|
15011
|
+
const part = e.properties?.part;
|
|
15012
|
+
if (!part || typeof part !== "object")
|
|
15013
|
+
return null;
|
|
15014
|
+
const p = part;
|
|
15015
|
+
if (p.type !== "subtask")
|
|
15016
|
+
return null;
|
|
15017
|
+
if (typeof p.sessionID !== "string" || p.sessionID === "")
|
|
15018
|
+
return null;
|
|
15019
|
+
if (typeof p.agent !== "string" || p.agent === "")
|
|
15020
|
+
return null;
|
|
15021
|
+
if (typeof p.description !== "string" || p.description === "")
|
|
15022
|
+
return null;
|
|
15023
|
+
return { parentID: p.sessionID, agent: p.agent, description: p.description };
|
|
15024
|
+
}
|
|
15025
|
+
function enqueuePendingTask(parentID, entry, now = Date.now()) {
|
|
15026
|
+
const ts = entry.ts ?? now;
|
|
15027
|
+
let bucket = pendingTask.get(parentID);
|
|
15028
|
+
if (!bucket) {
|
|
15029
|
+
if (pendingTask.size >= PENDING_TASK_MAX_PARENTS) {
|
|
15030
|
+
let oldestKey = null;
|
|
15031
|
+
let oldestTs = Number.POSITIVE_INFINITY;
|
|
15032
|
+
for (const [k, v] of pendingTask.entries()) {
|
|
15033
|
+
const headTs = v[0]?.ts ?? Number.POSITIVE_INFINITY;
|
|
15034
|
+
if (headTs < oldestTs) {
|
|
15035
|
+
oldestTs = headTs;
|
|
15036
|
+
oldestKey = k;
|
|
15037
|
+
}
|
|
15038
|
+
}
|
|
15039
|
+
if (oldestKey !== null)
|
|
15040
|
+
pendingTask.delete(oldestKey);
|
|
15041
|
+
}
|
|
15042
|
+
bucket = [];
|
|
15043
|
+
pendingTask.set(parentID, bucket);
|
|
15044
|
+
}
|
|
15045
|
+
bucket.push({ agent: entry.agent, description: entry.description, ts });
|
|
15046
|
+
while (bucket.length > PENDING_TASK_MAX_PER_PARENT) {
|
|
15047
|
+
bucket.shift();
|
|
15048
|
+
}
|
|
15049
|
+
}
|
|
15050
|
+
function dequeuePendingTask(parentID, now = Date.now()) {
|
|
15051
|
+
const bucket = pendingTask.get(parentID);
|
|
15052
|
+
if (!bucket || bucket.length === 0) {
|
|
15053
|
+
if (bucket)
|
|
15054
|
+
pendingTask.delete(parentID);
|
|
15055
|
+
return null;
|
|
15056
|
+
}
|
|
15057
|
+
while (bucket.length > 0 && now - bucket[0].ts > PENDING_TASK_TTL_MS) {
|
|
15058
|
+
bucket.shift();
|
|
15059
|
+
}
|
|
15060
|
+
if (bucket.length === 0) {
|
|
15061
|
+
pendingTask.delete(parentID);
|
|
15062
|
+
return null;
|
|
15063
|
+
}
|
|
15064
|
+
const entry = bucket.shift();
|
|
15065
|
+
if (bucket.length === 0)
|
|
15066
|
+
pendingTask.delete(parentID);
|
|
15067
|
+
return entry;
|
|
15068
|
+
}
|
|
15069
|
+
function sweepExpiredPendingTasks(now = Date.now()) {
|
|
15070
|
+
let removed = 0;
|
|
15071
|
+
for (const [parentID, bucket] of [...pendingTask.entries()]) {
|
|
15072
|
+
while (bucket.length > 0 && now - bucket[0].ts > PENDING_TASK_TTL_MS) {
|
|
15073
|
+
bucket.shift();
|
|
15074
|
+
removed++;
|
|
15075
|
+
}
|
|
15076
|
+
if (bucket.length === 0)
|
|
15077
|
+
pendingTask.delete(parentID);
|
|
15078
|
+
}
|
|
15079
|
+
return removed;
|
|
15080
|
+
}
|
|
14827
15081
|
function registerInflight(payload, now = Date.now()) {
|
|
14828
15082
|
const r = {
|
|
14829
15083
|
childID: payload.childID,
|
|
14830
15084
|
parentID: payload.parentID,
|
|
14831
15085
|
agent: payload.agent,
|
|
15086
|
+
description: payload.description ?? null,
|
|
14832
15087
|
startedAt: now,
|
|
14833
15088
|
lastBeatAt: now,
|
|
14834
15089
|
lastTool: null
|
|
14835
15090
|
};
|
|
14836
|
-
|
|
15091
|
+
inflight3.set(payload.childID, r);
|
|
14837
15092
|
return r;
|
|
14838
15093
|
}
|
|
14839
15094
|
function recordToolBeat(sessionID, tool2, now = Date.now()) {
|
|
14840
|
-
const r =
|
|
15095
|
+
const r = inflight3.get(sessionID);
|
|
14841
15096
|
if (!r)
|
|
14842
15097
|
return null;
|
|
14843
15098
|
r.lastBeatAt = now;
|
|
@@ -14845,15 +15100,15 @@ function recordToolBeat(sessionID, tool2, now = Date.now()) {
|
|
|
14845
15100
|
return r;
|
|
14846
15101
|
}
|
|
14847
15102
|
function clearInflight2(sessionID) {
|
|
14848
|
-
const r =
|
|
15103
|
+
const r = inflight3.get(sessionID);
|
|
14849
15104
|
if (!r)
|
|
14850
15105
|
return null;
|
|
14851
|
-
|
|
15106
|
+
inflight3.delete(sessionID);
|
|
14852
15107
|
return r;
|
|
14853
15108
|
}
|
|
14854
15109
|
function pickHeartbeats(now = Date.now()) {
|
|
14855
15110
|
const out = [];
|
|
14856
|
-
for (const r of
|
|
15111
|
+
for (const r of inflight3.values()) {
|
|
14857
15112
|
if (now - r.lastBeatAt >= HEARTBEAT_DEBOUNCE_MS)
|
|
14858
15113
|
out.push(r);
|
|
14859
15114
|
}
|
|
@@ -14865,10 +15120,32 @@ function fmtElapsed(ms) {
|
|
|
14865
15120
|
const s = total % 60;
|
|
14866
15121
|
return m > 0 ? `${m}m${s.toString().padStart(2, "0")}s` : `${s}s`;
|
|
14867
15122
|
}
|
|
15123
|
+
function titleCase(s) {
|
|
15124
|
+
if (!s)
|
|
15125
|
+
return s;
|
|
15126
|
+
return s.charAt(0).toUpperCase() + s.slice(1);
|
|
15127
|
+
}
|
|
15128
|
+
function sanitizeDescription(s) {
|
|
15129
|
+
const collapsed = s.replace(/[\r\n\t]+/g, " ").replace(/\s+/g, " ").trim();
|
|
15130
|
+
if (collapsed.length <= DESCRIPTION_MAX_LEN)
|
|
15131
|
+
return collapsed;
|
|
15132
|
+
return collapsed.slice(0, DESCRIPTION_MAX_LEN) + "…";
|
|
15133
|
+
}
|
|
14868
15134
|
function buildStartToast(r) {
|
|
14869
|
-
|
|
15135
|
+
if (r.agent && r.description) {
|
|
15136
|
+
return {
|
|
15137
|
+
message: `\uD83D\uDE80 ${titleCase(r.agent)} 启动 — ${sanitizeDescription(r.description)}`,
|
|
15138
|
+
variant: "info"
|
|
15139
|
+
};
|
|
15140
|
+
}
|
|
15141
|
+
if (r.agent) {
|
|
15142
|
+
return {
|
|
15143
|
+
message: `\uD83D\uDE80 ${titleCase(r.agent)} 启动`,
|
|
15144
|
+
variant: "info"
|
|
15145
|
+
};
|
|
15146
|
+
}
|
|
14870
15147
|
return {
|
|
14871
|
-
message: `\uD83D\uDE80 子 session 启动:
|
|
15148
|
+
message: `\uD83D\uDE80 子 session 启动: subagent`,
|
|
14872
15149
|
variant: "info"
|
|
14873
15150
|
};
|
|
14874
15151
|
}
|
|
@@ -14881,15 +15158,32 @@ function buildHeartbeatToast(r, now = Date.now()) {
|
|
|
14881
15158
|
};
|
|
14882
15159
|
}
|
|
14883
15160
|
function buildEndToast(r, type, now = Date.now()) {
|
|
14884
|
-
const who = r.agent ?? "subagent";
|
|
14885
15161
|
const elapsed = fmtElapsed(now - r.startedAt);
|
|
14886
|
-
|
|
14887
|
-
|
|
15162
|
+
const variant = type === "session.error" || type === "session.deleted" ? "error" : "success";
|
|
15163
|
+
const emoji = type === "session.error" ? "❌" : type === "session.deleted" ? "\uD83D\uDDD1️" : "✅";
|
|
15164
|
+
const verb = type === "session.error" ? "失败" : type === "session.deleted" ? "被取消" : "完成";
|
|
15165
|
+
if (r.agent && r.description) {
|
|
15166
|
+
if (type === "session.idle" || type !== "session.error" && type !== "session.deleted") {
|
|
15167
|
+
return {
|
|
15168
|
+
message: `${emoji} ${titleCase(r.agent)} Task — ${sanitizeDescription(r.description)} (${elapsed})`,
|
|
15169
|
+
variant
|
|
15170
|
+
};
|
|
15171
|
+
}
|
|
15172
|
+
return {
|
|
15173
|
+
message: `${emoji} ${titleCase(r.agent)} ${verb} — ${sanitizeDescription(r.description)} (${elapsed})`,
|
|
15174
|
+
variant
|
|
15175
|
+
};
|
|
14888
15176
|
}
|
|
14889
|
-
if (
|
|
14890
|
-
return {
|
|
15177
|
+
if (r.agent) {
|
|
15178
|
+
return {
|
|
15179
|
+
message: `${emoji} ${titleCase(r.agent)} ${verb} (${elapsed})`,
|
|
15180
|
+
variant
|
|
15181
|
+
};
|
|
14891
15182
|
}
|
|
14892
|
-
return {
|
|
15183
|
+
return {
|
|
15184
|
+
message: `${emoji} subagent ${verb} (${elapsed})`,
|
|
15185
|
+
variant
|
|
15186
|
+
};
|
|
14893
15187
|
}
|
|
14894
15188
|
function normalizeVariant3(raw) {
|
|
14895
15189
|
if (raw === "info" || raw === "warning")
|
|
@@ -14918,22 +15212,26 @@ async function showToast3(client, payload, log7) {
|
|
|
14918
15212
|
return false;
|
|
14919
15213
|
}
|
|
14920
15214
|
}
|
|
14921
|
-
var log7 = makePluginLogger(
|
|
15215
|
+
var log7 = makePluginLogger(PLUGIN_NAME13);
|
|
14922
15216
|
var subtaskHeartbeatServer = async (ctx) => {
|
|
14923
|
-
logLifecycle(
|
|
15217
|
+
logLifecycle(PLUGIN_NAME13, "activate", {
|
|
14924
15218
|
directory: ctx.directory,
|
|
14925
15219
|
intervalMs: HEARTBEAT_INTERVAL_MS2
|
|
14926
15220
|
});
|
|
14927
15221
|
const client = ctx.client;
|
|
14928
15222
|
const interval = setInterval(() => {
|
|
14929
|
-
safeAsync(
|
|
15223
|
+
safeAsync(PLUGIN_NAME13, "interval", async () => {
|
|
15224
|
+
const swept = sweepExpiredPendingTasks();
|
|
15225
|
+
if (swept > 0) {
|
|
15226
|
+
safeWriteLog(PLUGIN_NAME13, { hook: "interval", pending_task_swept: swept });
|
|
15227
|
+
}
|
|
14930
15228
|
const beats = pickHeartbeats();
|
|
14931
15229
|
if (beats.length === 0)
|
|
14932
15230
|
return;
|
|
14933
15231
|
for (const r of beats) {
|
|
14934
15232
|
const t = buildHeartbeatToast(r);
|
|
14935
15233
|
const sent = await showToast3(client, t, log7);
|
|
14936
|
-
safeWriteLog(
|
|
15234
|
+
safeWriteLog(PLUGIN_NAME13, {
|
|
14937
15235
|
hook: "interval",
|
|
14938
15236
|
child: r.childID,
|
|
14939
15237
|
parent: r.parentID,
|
|
@@ -14950,23 +15248,47 @@ var subtaskHeartbeatServer = async (ctx) => {
|
|
|
14950
15248
|
}
|
|
14951
15249
|
return {
|
|
14952
15250
|
event: async ({ event }) => {
|
|
14953
|
-
await safeAsync(
|
|
15251
|
+
await safeAsync(PLUGIN_NAME13, "event", async () => {
|
|
15252
|
+
const subtask = extractSubtaskPart(event);
|
|
15253
|
+
if (subtask) {
|
|
15254
|
+
enqueuePendingTask(subtask.parentID, {
|
|
15255
|
+
agent: subtask.agent,
|
|
15256
|
+
description: subtask.description
|
|
15257
|
+
});
|
|
15258
|
+
safeWriteLog(PLUGIN_NAME13, {
|
|
15259
|
+
hook: "event",
|
|
15260
|
+
type: "message.part.updated.subtask",
|
|
15261
|
+
parent: subtask.parentID,
|
|
15262
|
+
agent: subtask.agent,
|
|
15263
|
+
description_len: subtask.description.length
|
|
15264
|
+
});
|
|
15265
|
+
return;
|
|
15266
|
+
}
|
|
14954
15267
|
const created = extractCreatedChild(event);
|
|
14955
15268
|
if (created) {
|
|
14956
|
-
const
|
|
14957
|
-
|
|
15269
|
+
const pending = dequeuePendingTask(created.parentID);
|
|
15270
|
+
const record = registerInflight({
|
|
15271
|
+
childID: created.childID,
|
|
15272
|
+
parentID: created.parentID,
|
|
15273
|
+
agent: pending?.agent ?? created.agent,
|
|
15274
|
+
description: pending?.description ?? null
|
|
15275
|
+
});
|
|
15276
|
+
safeWriteLog(PLUGIN_NAME13, {
|
|
14958
15277
|
hook: "event",
|
|
14959
15278
|
type: "session.created",
|
|
14960
15279
|
child: created.childID,
|
|
14961
|
-
parent: created.parentID
|
|
15280
|
+
parent: created.parentID,
|
|
15281
|
+
pending_task_matched: pending !== null,
|
|
15282
|
+
agent: record.agent
|
|
14962
15283
|
});
|
|
14963
15284
|
const startToast = buildStartToast(record);
|
|
14964
15285
|
const sent = await showToast3(client, { ...startToast, duration: START_TOAST_DURATION_MS }, log7);
|
|
14965
|
-
safeWriteLog(
|
|
15286
|
+
safeWriteLog(PLUGIN_NAME13, {
|
|
14966
15287
|
hook: "event",
|
|
14967
15288
|
type: "session.created.toast",
|
|
14968
15289
|
child: created.childID,
|
|
14969
|
-
toast_sent: sent
|
|
15290
|
+
toast_sent: sent,
|
|
15291
|
+
start_toast_message: startToast.message
|
|
14970
15292
|
});
|
|
14971
15293
|
return;
|
|
14972
15294
|
}
|
|
@@ -14976,7 +15298,7 @@ var subtaskHeartbeatServer = async (ctx) => {
|
|
|
14976
15298
|
if (r) {
|
|
14977
15299
|
const t = buildEndToast(r, ended.type);
|
|
14978
15300
|
const sent = await showToast3(client, t, log7);
|
|
14979
|
-
safeWriteLog(
|
|
15301
|
+
safeWriteLog(PLUGIN_NAME13, {
|
|
14980
15302
|
hook: "event",
|
|
14981
15303
|
type: ended.type,
|
|
14982
15304
|
child: r.childID,
|
|
@@ -14989,7 +15311,9 @@ var subtaskHeartbeatServer = async (ctx) => {
|
|
|
14989
15311
|
});
|
|
14990
15312
|
},
|
|
14991
15313
|
"tool.execute.before": async (input) => {
|
|
14992
|
-
|
|
15314
|
+
if (inflight3.size === 0)
|
|
15315
|
+
return;
|
|
15316
|
+
await safeAsync(PLUGIN_NAME13, "tool.execute.before", async () => {
|
|
14993
15317
|
if (!input || typeof input.sessionID !== "string" || typeof input.tool !== "string")
|
|
14994
15318
|
return;
|
|
14995
15319
|
recordToolBeat(input.sessionID, input.tool);
|
|
@@ -14997,12 +15321,12 @@ var subtaskHeartbeatServer = async (ctx) => {
|
|
|
14997
15321
|
}
|
|
14998
15322
|
};
|
|
14999
15323
|
};
|
|
15000
|
-
var
|
|
15324
|
+
var handler13 = subtaskHeartbeatServer;
|
|
15001
15325
|
|
|
15002
15326
|
// plugins/parallel-status.ts
|
|
15003
15327
|
init_opencode_plugin_helpers();
|
|
15004
|
-
var
|
|
15005
|
-
logLifecycle(
|
|
15328
|
+
var PLUGIN_NAME14 = "parallel-status";
|
|
15329
|
+
logLifecycle(PLUGIN_NAME14, "import");
|
|
15006
15330
|
var ID_MAX_LEN = 16;
|
|
15007
15331
|
var ID_KEEP_LEN = 13;
|
|
15008
15332
|
function shortId(s) {
|
|
@@ -15034,8 +15358,8 @@ function formatInflightMarkdown(snapshot, now = Date.now()) {
|
|
|
15034
15358
|
`);
|
|
15035
15359
|
}
|
|
15036
15360
|
var parallelStatusServer = async (ctx) => {
|
|
15037
|
-
const log8 = makePluginLogger(
|
|
15038
|
-
logLifecycle(
|
|
15361
|
+
const log8 = makePluginLogger(PLUGIN_NAME14);
|
|
15362
|
+
logLifecycle(PLUGIN_NAME14, "activate", { directory: ctx.directory });
|
|
15039
15363
|
return {
|
|
15040
15364
|
"command.execute.before": async (input, output) => {
|
|
15041
15365
|
try {
|
|
@@ -15046,7 +15370,7 @@ var parallelStatusServer = async (ctx) => {
|
|
|
15046
15370
|
if (Array.isArray(output?.parts)) {
|
|
15047
15371
|
output.parts.length = 0;
|
|
15048
15372
|
output.parts.push({
|
|
15049
|
-
id:
|
|
15373
|
+
id: makePartId(),
|
|
15050
15374
|
sessionID: input.sessionID,
|
|
15051
15375
|
messageID: "",
|
|
15052
15376
|
type: "text",
|
|
@@ -15054,21 +15378,21 @@ var parallelStatusServer = async (ctx) => {
|
|
|
15054
15378
|
synthetic: false
|
|
15055
15379
|
});
|
|
15056
15380
|
}
|
|
15057
|
-
log8.info(`[${
|
|
15381
|
+
log8.info(`[${PLUGIN_NAME14}] 已回写 ${snapshot.length} 条 inflight`);
|
|
15058
15382
|
} catch (err) {
|
|
15059
|
-
log8.error(`[${
|
|
15383
|
+
log8.error(`[${PLUGIN_NAME14}] command.execute.before 异常(已隔离)`, {
|
|
15060
15384
|
error: err instanceof Error ? err.message : String(err)
|
|
15061
15385
|
});
|
|
15062
15386
|
}
|
|
15063
15387
|
}
|
|
15064
15388
|
};
|
|
15065
15389
|
};
|
|
15066
|
-
var
|
|
15390
|
+
var handler14 = parallelStatusServer;
|
|
15067
15391
|
|
|
15068
15392
|
// plugins/pwsh-utf8.ts
|
|
15069
15393
|
init_opencode_plugin_helpers();
|
|
15070
|
-
var
|
|
15071
|
-
logLifecycle(
|
|
15394
|
+
var PLUGIN_NAME15 = "pwsh-utf8";
|
|
15395
|
+
logLifecycle(PLUGIN_NAME15, "import", {});
|
|
15072
15396
|
var PRELUDE = "chcp 65001 *> $null; " + "[Console]::OutputEncoding = [System.Text.UTF8Encoding]::new(); " + "$OutputEncoding = [System.Text.UTF8Encoding]::new(); ";
|
|
15073
15397
|
function prependUtf8Prelude(command) {
|
|
15074
15398
|
if (typeof command !== "string")
|
|
@@ -15081,14 +15405,15 @@ function prependUtf8Prelude(command) {
|
|
|
15081
15405
|
return command;
|
|
15082
15406
|
return PRELUDE + command;
|
|
15083
15407
|
}
|
|
15084
|
-
var
|
|
15408
|
+
var handler15 = async (_ctx) => {
|
|
15085
15409
|
const enabled = process.platform === "win32" && process.env.CODEFORGE_DISABLE_PWSH_UTF8 !== "1";
|
|
15086
|
-
|
|
15410
|
+
const reason = enabled ? "win32" : process.platform !== "win32" ? "non-win32" : "disabled-by-env";
|
|
15411
|
+
logLifecycle(PLUGIN_NAME15, "activate", { enabled, platform: process.platform, reason });
|
|
15087
15412
|
if (!enabled)
|
|
15088
15413
|
return {};
|
|
15089
15414
|
return {
|
|
15090
15415
|
"tool.execute.before": async (input, output) => {
|
|
15091
|
-
await safeAsync(
|
|
15416
|
+
await safeAsync(PLUGIN_NAME15, "tool.execute.before", async () => {
|
|
15092
15417
|
if (input.tool !== "bash")
|
|
15093
15418
|
return;
|
|
15094
15419
|
const args = output.args ?? {};
|
|
@@ -15097,7 +15422,7 @@ var handler16 = async (_ctx) => {
|
|
|
15097
15422
|
if (next !== undefined && next !== original) {
|
|
15098
15423
|
args["command"] = next;
|
|
15099
15424
|
output.args = args;
|
|
15100
|
-
safeWriteLog(
|
|
15425
|
+
safeWriteLog(PLUGIN_NAME15, {
|
|
15101
15426
|
hook: "tool.execute.before",
|
|
15102
15427
|
tool: input.tool,
|
|
15103
15428
|
callID: input.callID,
|
|
@@ -15290,7 +15615,7 @@ function buildRecoveryPlan(events, meta = { id: null }, opts = {}) {
|
|
|
15290
15615
|
});
|
|
15291
15616
|
}
|
|
15292
15617
|
}
|
|
15293
|
-
const
|
|
15618
|
+
const inflight4 = [...toolMap.values()].sort((a, b) => b.last_ts - a.last_ts);
|
|
15294
15619
|
const proposed = window.some((t) => PENDING_CHANGES_TOOLS.has(t.tool));
|
|
15295
15620
|
const applied = window.some((t) => APPLY_TOOLS.has(t.tool) && t.ok);
|
|
15296
15621
|
const pending_changes_likely = proposed && !applied;
|
|
@@ -15314,7 +15639,7 @@ function buildRecoveryPlan(events, meta = { id: null }, opts = {}) {
|
|
|
15314
15639
|
idleMs,
|
|
15315
15640
|
lastUser,
|
|
15316
15641
|
lastAgent,
|
|
15317
|
-
inflight:
|
|
15642
|
+
inflight: inflight4,
|
|
15318
15643
|
pending_changes_likely,
|
|
15319
15644
|
open_subtasks_likely,
|
|
15320
15645
|
reason
|
|
@@ -15325,7 +15650,7 @@ function buildRecoveryPlan(events, meta = { id: null }, opts = {}) {
|
|
|
15325
15650
|
idle_ms: idleMs,
|
|
15326
15651
|
last_user_intent: lastUser,
|
|
15327
15652
|
last_agent: lastAgent,
|
|
15328
|
-
inflight_tools:
|
|
15653
|
+
inflight_tools: inflight4,
|
|
15329
15654
|
pending_changes_likely,
|
|
15330
15655
|
open_subtasks_likely,
|
|
15331
15656
|
summary
|
|
@@ -15430,8 +15755,8 @@ function isRecoveryWorthShowing(plan) {
|
|
|
15430
15755
|
}
|
|
15431
15756
|
|
|
15432
15757
|
// plugins/session-recovery.ts
|
|
15433
|
-
var
|
|
15434
|
-
logLifecycle(
|
|
15758
|
+
var PLUGIN_NAME16 = "session-recovery";
|
|
15759
|
+
logLifecycle(PLUGIN_NAME16, "import", {});
|
|
15435
15760
|
async function processSessionStart(currentSessionId, opts = {}) {
|
|
15436
15761
|
if (opts.disabled) {
|
|
15437
15762
|
return { ok: true, injected: false, reason: "disabled" };
|
|
@@ -15441,7 +15766,7 @@ async function processSessionStart(currentSessionId, opts = {}) {
|
|
|
15441
15766
|
excludeIds.add(currentSessionId);
|
|
15442
15767
|
const r = await scanLastSession({ ...opts, excludeIds: [...excludeIds] });
|
|
15443
15768
|
if (!r.ok) {
|
|
15444
|
-
opts.log?.warn?.(`[${
|
|
15769
|
+
opts.log?.warn?.(`[${PLUGIN_NAME16}] 扫描失败:${r.error}`);
|
|
15445
15770
|
return { ok: false, injected: false, reason: "scan_error", error: r.error };
|
|
15446
15771
|
}
|
|
15447
15772
|
const plan = r.plan;
|
|
@@ -15455,7 +15780,7 @@ async function processSessionStart(currentSessionId, opts = {}) {
|
|
|
15455
15780
|
await opts.injectRecovery(injection);
|
|
15456
15781
|
} catch (err) {
|
|
15457
15782
|
const msg = err instanceof Error ? err.message : String(err);
|
|
15458
|
-
opts.log?.warn?.(`[${
|
|
15783
|
+
opts.log?.warn?.(`[${PLUGIN_NAME16}] injectRecovery 异常:${msg}`);
|
|
15459
15784
|
return { ok: false, injected: false, plan, reason: "inject_error", error: msg };
|
|
15460
15785
|
}
|
|
15461
15786
|
}
|
|
@@ -15484,13 +15809,13 @@ function renderPrompt(plan) {
|
|
|
15484
15809
|
return lines.join(`
|
|
15485
15810
|
`);
|
|
15486
15811
|
}
|
|
15487
|
-
var log8 = makePluginLogger(
|
|
15812
|
+
var log8 = makePluginLogger(PLUGIN_NAME16);
|
|
15488
15813
|
var _lastInjection = null;
|
|
15489
15814
|
var sessionRecoveryServer = async (ctx) => {
|
|
15490
|
-
logLifecycle(
|
|
15815
|
+
logLifecycle(PLUGIN_NAME16, "activate", { directory: ctx.directory });
|
|
15491
15816
|
return {
|
|
15492
15817
|
event: async ({ event }) => {
|
|
15493
|
-
await safeAsync(
|
|
15818
|
+
await safeAsync(PLUGIN_NAME16, "event", async () => {
|
|
15494
15819
|
const e = event;
|
|
15495
15820
|
if (!e || typeof e.type !== "string")
|
|
15496
15821
|
return;
|
|
@@ -15505,7 +15830,7 @@ var sessionRecoveryServer = async (ctx) => {
|
|
|
15505
15830
|
_lastInjection = inj;
|
|
15506
15831
|
}
|
|
15507
15832
|
});
|
|
15508
|
-
safeWriteLog(
|
|
15833
|
+
safeWriteLog(PLUGIN_NAME16, {
|
|
15509
15834
|
hook: "event",
|
|
15510
15835
|
type: "session.start",
|
|
15511
15836
|
ok: r.ok,
|
|
@@ -15514,13 +15839,13 @@ var sessionRecoveryServer = async (ctx) => {
|
|
|
15514
15839
|
last_session_id: r.plan?.last_session_id
|
|
15515
15840
|
});
|
|
15516
15841
|
if (r.injected && r.plan) {
|
|
15517
|
-
log8.info(`[${
|
|
15842
|
+
log8.info(`[${PLUGIN_NAME16}] 注入恢复提示(last=${r.plan.last_session_id?.slice(0, 8) ?? "?"}, reason=${r.plan.reason})`);
|
|
15518
15843
|
}
|
|
15519
15844
|
});
|
|
15520
15845
|
}
|
|
15521
15846
|
};
|
|
15522
15847
|
};
|
|
15523
|
-
var
|
|
15848
|
+
var handler16 = sessionRecoveryServer;
|
|
15524
15849
|
|
|
15525
15850
|
// plugins/subtasks.ts
|
|
15526
15851
|
import { promises as fs10 } from "node:fs";
|
|
@@ -15786,6 +16111,7 @@ function pickStatus(r, taskAborted, globalAborted) {
|
|
|
15786
16111
|
}
|
|
15787
16112
|
|
|
15788
16113
|
// lib/opencode-runner.ts
|
|
16114
|
+
init_opencode_plugin_helpers();
|
|
15789
16115
|
function makeOpencodeRunner(opts) {
|
|
15790
16116
|
const log9 = opts.log ?? (() => {});
|
|
15791
16117
|
return async (spec, runCtx) => {
|
|
@@ -16013,17 +16339,20 @@ async function sendParentNotice(client, sessionID, text, opts = {}) {
|
|
|
16013
16339
|
}
|
|
16014
16340
|
try {
|
|
16015
16341
|
const res = await sessionAny.promptAsync({
|
|
16016
|
-
sessionID,
|
|
16017
|
-
directory: opts.directory,
|
|
16018
|
-
|
|
16019
|
-
|
|
16020
|
-
|
|
16021
|
-
|
|
16022
|
-
|
|
16023
|
-
|
|
16024
|
-
|
|
16025
|
-
|
|
16026
|
-
|
|
16342
|
+
path: { id: sessionID },
|
|
16343
|
+
query: opts.directory ? { directory: opts.directory } : undefined,
|
|
16344
|
+
body: {
|
|
16345
|
+
noReply: true,
|
|
16346
|
+
parts: [
|
|
16347
|
+
{
|
|
16348
|
+
id: makePartId(),
|
|
16349
|
+
type: "text",
|
|
16350
|
+
text,
|
|
16351
|
+
synthetic: false,
|
|
16352
|
+
ignored: false
|
|
16353
|
+
}
|
|
16354
|
+
]
|
|
16355
|
+
}
|
|
16027
16356
|
});
|
|
16028
16357
|
if (res && typeof res === "object" && "error" in res && res.error) {
|
|
16029
16358
|
log9("warn", "[sendParentNotice] promptAsync 返回 error", { error: res.error });
|
|
@@ -16041,7 +16370,7 @@ async function sendParentNotice(client, sessionID, text, opts = {}) {
|
|
|
16041
16370
|
// plugins/subtasks.ts
|
|
16042
16371
|
init_opencode_plugin_helpers();
|
|
16043
16372
|
init_runtime_paths();
|
|
16044
|
-
var
|
|
16373
|
+
var PLUGIN_NAME17 = "subtasks";
|
|
16045
16374
|
function getLogFile(root = process.cwd()) {
|
|
16046
16375
|
return path13.join(runtimeDir(root), "logs", "subtasks.log");
|
|
16047
16376
|
}
|
|
@@ -16120,7 +16449,7 @@ async function handleParallelCommand(raw) {
|
|
|
16120
16449
|
specs = splitDescriptions(args.description, { parentId });
|
|
16121
16450
|
}
|
|
16122
16451
|
if (specs.length === 0) {
|
|
16123
|
-
log9?.warn(`[${
|
|
16452
|
+
log9?.warn(`[${PLUGIN_NAME17}] /parallel 缺有效子任务`);
|
|
16124
16453
|
await safeReply2(ctx, "⚠ /parallel 需要至少 1 个子任务(用 ; 或换行分隔)");
|
|
16125
16454
|
return { ok: false, reason: "no_subtasks" };
|
|
16126
16455
|
}
|
|
@@ -16178,7 +16507,7 @@ async function handleParallelCommand(raw) {
|
|
|
16178
16507
|
});
|
|
16179
16508
|
} catch (err) {
|
|
16180
16509
|
const msg = err instanceof Error ? err.message : String(err);
|
|
16181
|
-
log9?.error(`[${
|
|
16510
|
+
log9?.error(`[${PLUGIN_NAME17}] schedule 抛错`, { error: msg });
|
|
16182
16511
|
await safeReply2(ctx, `❌ 并发调度失败:${msg}`);
|
|
16183
16512
|
return { ok: false, reason: msg };
|
|
16184
16513
|
}
|
|
@@ -16186,7 +16515,7 @@ async function handleParallelCommand(raw) {
|
|
|
16186
16515
|
const summaryLines = [head, "", "```", result.digest.text, "```"];
|
|
16187
16516
|
await safeReply2(ctx, summaryLines.join(`
|
|
16188
16517
|
`));
|
|
16189
|
-
log9?.info(`[${
|
|
16518
|
+
log9?.info(`[${PLUGIN_NAME17}] schedule 完成`, {
|
|
16190
16519
|
parentId,
|
|
16191
16520
|
success: result.digest.success,
|
|
16192
16521
|
failed: result.digest.failed,
|
|
@@ -16196,7 +16525,7 @@ async function handleParallelCommand(raw) {
|
|
|
16196
16525
|
try {
|
|
16197
16526
|
await ctx.onCompleted(result);
|
|
16198
16527
|
} catch (err) {
|
|
16199
|
-
log9?.warn(`[${
|
|
16528
|
+
log9?.warn(`[${PLUGIN_NAME17}] onCompleted hook 抛错(已隔离)`, {
|
|
16200
16529
|
error: err instanceof Error ? err.message : String(err)
|
|
16201
16530
|
});
|
|
16202
16531
|
}
|
|
@@ -16237,7 +16566,7 @@ async function writeLog(level, msg, data) {
|
|
|
16237
16566
|
const line = JSON.stringify({
|
|
16238
16567
|
ts: new Date().toISOString(),
|
|
16239
16568
|
level,
|
|
16240
|
-
plugin:
|
|
16569
|
+
plugin: PLUGIN_NAME17,
|
|
16241
16570
|
msg,
|
|
16242
16571
|
data
|
|
16243
16572
|
}) + `
|
|
@@ -16248,11 +16577,11 @@ async function writeLog(level, msg, data) {
|
|
|
16248
16577
|
await fs10.appendFile(logFile, line, "utf8");
|
|
16249
16578
|
} catch {}
|
|
16250
16579
|
}
|
|
16251
|
-
logLifecycle(
|
|
16580
|
+
logLifecycle(PLUGIN_NAME17, "import");
|
|
16252
16581
|
var subtasksServer = async (ctx) => {
|
|
16253
|
-
const log9 = makePluginLogger(
|
|
16582
|
+
const log9 = makePluginLogger(PLUGIN_NAME17);
|
|
16254
16583
|
const client = ctx?.client ?? undefined;
|
|
16255
|
-
logLifecycle(
|
|
16584
|
+
logLifecycle(PLUGIN_NAME17, "activate", {
|
|
16256
16585
|
directory: ctx.directory,
|
|
16257
16586
|
hasClient: Boolean(client)
|
|
16258
16587
|
});
|
|
@@ -16301,7 +16630,7 @@ var subtasksServer = async (ctx) => {
|
|
|
16301
16630
|
if (replyLines.length > 0 && Array.isArray(output?.parts)) {
|
|
16302
16631
|
output.parts.length = 0;
|
|
16303
16632
|
output.parts.push({
|
|
16304
|
-
id:
|
|
16633
|
+
id: makePartId(),
|
|
16305
16634
|
sessionID: input.sessionID,
|
|
16306
16635
|
messageID: "",
|
|
16307
16636
|
type: "text",
|
|
@@ -16312,20 +16641,20 @@ var subtasksServer = async (ctx) => {
|
|
|
16312
16641
|
});
|
|
16313
16642
|
}
|
|
16314
16643
|
} catch (err) {
|
|
16315
|
-
log9.error(`[${
|
|
16644
|
+
log9.error(`[${PLUGIN_NAME17}] command.execute.before 异常(已隔离)`, {
|
|
16316
16645
|
error: err instanceof Error ? err.message : String(err)
|
|
16317
16646
|
});
|
|
16318
16647
|
}
|
|
16319
16648
|
}
|
|
16320
16649
|
};
|
|
16321
16650
|
};
|
|
16322
|
-
var
|
|
16651
|
+
var handler17 = subtasksServer;
|
|
16323
16652
|
|
|
16324
16653
|
// plugins/terminal-monitor.ts
|
|
16325
16654
|
init_opencode_plugin_helpers();
|
|
16326
16655
|
import * as crypto5 from "node:crypto";
|
|
16327
|
-
var
|
|
16328
|
-
logLifecycle(
|
|
16656
|
+
var PLUGIN_NAME18 = "terminal-monitor";
|
|
16657
|
+
logLifecycle(PLUGIN_NAME18, "import", {});
|
|
16329
16658
|
var DEFAULT_CONFIG7 = {
|
|
16330
16659
|
minScore: 0.6,
|
|
16331
16660
|
cooldownMs: 30000,
|
|
@@ -16607,17 +16936,17 @@ function describeError(err) {
|
|
|
16607
16936
|
return String(err);
|
|
16608
16937
|
}
|
|
16609
16938
|
}
|
|
16610
|
-
var log9 = makePluginLogger(
|
|
16939
|
+
var log9 = makePluginLogger(PLUGIN_NAME18);
|
|
16611
16940
|
var lru = new FingerprintLRU2;
|
|
16612
16941
|
var _lastNotification = null;
|
|
16613
16942
|
var terminalMonitorServer = async (ctx) => {
|
|
16614
|
-
logLifecycle(
|
|
16943
|
+
logLifecycle(PLUGIN_NAME18, "activate", {
|
|
16615
16944
|
directory: ctx.directory,
|
|
16616
16945
|
triggerEventTypes: ["terminal.output", "terminal.exit"]
|
|
16617
16946
|
});
|
|
16618
16947
|
return {
|
|
16619
16948
|
event: async ({ event }) => {
|
|
16620
|
-
await safeAsync(
|
|
16949
|
+
await safeAsync(PLUGIN_NAME18, "event", async () => {
|
|
16621
16950
|
const e = event;
|
|
16622
16951
|
if (!e || typeof e.type !== "string")
|
|
16623
16952
|
return;
|
|
@@ -16631,7 +16960,7 @@ var terminalMonitorServer = async (ctx) => {
|
|
|
16631
16960
|
_lastNotification = msg;
|
|
16632
16961
|
}
|
|
16633
16962
|
});
|
|
16634
|
-
safeWriteLog(
|
|
16963
|
+
safeWriteLog(PLUGIN_NAME18, {
|
|
16635
16964
|
hook: "event",
|
|
16636
16965
|
type: e.type,
|
|
16637
16966
|
notified: r.notified,
|
|
@@ -16639,18 +16968,18 @@ var terminalMonitorServer = async (ctx) => {
|
|
|
16639
16968
|
findings: r.notification?.findings.length ?? 0
|
|
16640
16969
|
});
|
|
16641
16970
|
if (r.notified && r.notification) {
|
|
16642
|
-
log9.info(`[${
|
|
16971
|
+
log9.info(`[${PLUGIN_NAME18}] 反馈 ${r.notification.findings.length} 条 → ${r.notification.summary}`);
|
|
16643
16972
|
}
|
|
16644
16973
|
});
|
|
16645
16974
|
}
|
|
16646
16975
|
};
|
|
16647
16976
|
};
|
|
16648
|
-
var
|
|
16977
|
+
var handler18 = terminalMonitorServer;
|
|
16649
16978
|
|
|
16650
16979
|
// plugins/token-manager.ts
|
|
16651
16980
|
init_opencode_plugin_helpers();
|
|
16652
|
-
var
|
|
16653
|
-
logLifecycle(
|
|
16981
|
+
var PLUGIN_NAME19 = "token-manager";
|
|
16982
|
+
logLifecycle(PLUGIN_NAME19, "import", {});
|
|
16654
16983
|
async function handleMessageBefore(raw, log10, defaults) {
|
|
16655
16984
|
const ctx = raw ?? {};
|
|
16656
16985
|
if (!Array.isArray(ctx.messages) || ctx.messages.length === 0)
|
|
@@ -16670,21 +16999,21 @@ async function handleMessageBefore(raw, log10, defaults) {
|
|
|
16670
16999
|
};
|
|
16671
17000
|
if (r.compressed) {
|
|
16672
17001
|
ctx.messages = r.messages;
|
|
16673
|
-
log10?.info(`[${
|
|
17002
|
+
log10?.info(`[${PLUGIN_NAME19}] 压缩 ${r.before.count}→${r.after.count} 条 / ${r.before.tokens}→${r.after.tokens} tokens`);
|
|
16674
17003
|
}
|
|
16675
17004
|
return r;
|
|
16676
17005
|
} catch (err) {
|
|
16677
|
-
log10?.warn(`[${
|
|
17006
|
+
log10?.warn(`[${PLUGIN_NAME19}] 压缩异常(已隔离)`, {
|
|
16678
17007
|
error: err instanceof Error ? err.message : String(err)
|
|
16679
17008
|
});
|
|
16680
17009
|
return null;
|
|
16681
17010
|
}
|
|
16682
17011
|
}
|
|
16683
|
-
var log10 = makePluginLogger(
|
|
17012
|
+
var log10 = makePluginLogger(PLUGIN_NAME19);
|
|
16684
17013
|
var tokenManagerServer = async (ctx) => {
|
|
16685
17014
|
const rt = loadRuntimeSync();
|
|
16686
17015
|
const threshold = rt.runtime.context.condenser_threshold_ratio;
|
|
16687
|
-
logLifecycle(
|
|
17016
|
+
logLifecycle(PLUGIN_NAME19, "activate", {
|
|
16688
17017
|
directory: ctx.directory,
|
|
16689
17018
|
threshold,
|
|
16690
17019
|
target: DEFAULT_CONDENSE.target,
|
|
@@ -16693,7 +17022,7 @@ var tokenManagerServer = async (ctx) => {
|
|
|
16693
17022
|
});
|
|
16694
17023
|
return {
|
|
16695
17024
|
"experimental.chat.messages.transform": async (_input, output) => {
|
|
16696
|
-
await safeAsync(
|
|
17025
|
+
await safeAsync(PLUGIN_NAME19, "experimental.chat.messages.transform", async () => {
|
|
16697
17026
|
const list = output.messages;
|
|
16698
17027
|
if (!Array.isArray(list) || list.length === 0)
|
|
16699
17028
|
return;
|
|
@@ -16706,7 +17035,7 @@ var tokenManagerServer = async (ctx) => {
|
|
|
16706
17035
|
const r = await handleMessageBefore({ messages: flat }, log10, { threshold });
|
|
16707
17036
|
if (!r)
|
|
16708
17037
|
return;
|
|
16709
|
-
safeWriteLog(
|
|
17038
|
+
safeWriteLog(PLUGIN_NAME19, {
|
|
16710
17039
|
hook: "experimental.chat.messages.transform",
|
|
16711
17040
|
mode: "observe-only",
|
|
16712
17041
|
before_msgs: r.before.count,
|
|
@@ -16717,13 +17046,13 @@ var tokenManagerServer = async (ctx) => {
|
|
|
16717
17046
|
reason: r.reason
|
|
16718
17047
|
});
|
|
16719
17048
|
if (r.compressed) {
|
|
16720
|
-
log10.warn(`[${
|
|
17049
|
+
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)`);
|
|
16721
17050
|
}
|
|
16722
17051
|
});
|
|
16723
17052
|
}
|
|
16724
17053
|
};
|
|
16725
17054
|
};
|
|
16726
|
-
var
|
|
17055
|
+
var handler19 = tokenManagerServer;
|
|
16727
17056
|
|
|
16728
17057
|
// plugins/tool-policy.ts
|
|
16729
17058
|
init_opencode_plugin_helpers();
|
|
@@ -16966,10 +17295,42 @@ function checkFileAccess(acl, file, op) {
|
|
|
16966
17295
|
}
|
|
16967
17296
|
|
|
16968
17297
|
// plugins/tool-policy.ts
|
|
16969
|
-
var
|
|
16970
|
-
logLifecycle(
|
|
17298
|
+
var PLUGIN_NAME20 = "tool-policy";
|
|
17299
|
+
logLifecycle(PLUGIN_NAME20, "import", {});
|
|
16971
17300
|
var EMPTY_ACL = { whitelistMode: false };
|
|
16972
|
-
|
|
17301
|
+
var SUBAGENT_APPLY_DENY_LIST = new Set([
|
|
17302
|
+
"coder",
|
|
17303
|
+
"planner",
|
|
17304
|
+
"reviewer"
|
|
17305
|
+
]);
|
|
17306
|
+
function isSubagentApplyBlocked(tool2, args, currentAgent) {
|
|
17307
|
+
if (tool2 !== "pending_changes")
|
|
17308
|
+
return false;
|
|
17309
|
+
const action = args?.action;
|
|
17310
|
+
if (action !== "apply" && action !== "apply_all")
|
|
17311
|
+
return false;
|
|
17312
|
+
if (!currentAgent)
|
|
17313
|
+
return false;
|
|
17314
|
+
return SUBAGENT_APPLY_DENY_LIST.has(currentAgent);
|
|
17315
|
+
}
|
|
17316
|
+
function decideToolCall(ctx, cfg = {}, currentAgent) {
|
|
17317
|
+
if (isSubagentApplyBlocked(ctx.tool, ctx.args, currentAgent)) {
|
|
17318
|
+
const action2 = String(ctx.args?.action);
|
|
17319
|
+
return {
|
|
17320
|
+
action: "deny",
|
|
17321
|
+
reasons: [
|
|
17322
|
+
`[ADR-0061] subagent '${currentAgent}' 禁止调 pending_changes.${action2}` + ` — apply 必须由 codeforge orchestrator 或用户拍板`,
|
|
17323
|
+
`替代路径:stage 完成后回报 codeforge,由其决定 apply 后通过 task_id 复用启动你跑测试`
|
|
17324
|
+
],
|
|
17325
|
+
autonomy: {
|
|
17326
|
+
effective_mode: ctx.mode ?? cfg.defaultMode ?? DEFAULT_RUNTIME.autonomy.default_mode,
|
|
17327
|
+
action: "confirm",
|
|
17328
|
+
downgraded: false,
|
|
17329
|
+
detected_risks: ["subagent_apply_blocked"],
|
|
17330
|
+
reason: `subagent '${currentAgent}' 自 apply 越权 (ADR-0061)`
|
|
17331
|
+
}
|
|
17332
|
+
};
|
|
17333
|
+
}
|
|
16973
17334
|
const fallbackMode = cfg.defaultMode ?? DEFAULT_RUNTIME.autonomy.default_mode;
|
|
16974
17335
|
const mode = ctx.mode ?? fallbackMode;
|
|
16975
17336
|
const a = evaluate2(mode, {
|
|
@@ -17019,13 +17380,39 @@ function classifyToolKind(toolName) {
|
|
|
17019
17380
|
return "webfetch";
|
|
17020
17381
|
return "other";
|
|
17021
17382
|
}
|
|
17022
|
-
|
|
17383
|
+
async function resolveCurrentAgent(client, sessionID, log11) {
|
|
17384
|
+
try {
|
|
17385
|
+
const sessionApi = client?.session;
|
|
17386
|
+
if (!sessionApi || typeof sessionApi.get !== "function") {
|
|
17387
|
+
log11.warn(`client.session.get unavailable`, { sessionID });
|
|
17388
|
+
return;
|
|
17389
|
+
}
|
|
17390
|
+
const res = await sessionApi.get({ path: { id: sessionID } });
|
|
17391
|
+
const data = res?.data ?? res;
|
|
17392
|
+
const rawAgent = data?.agent;
|
|
17393
|
+
if (typeof rawAgent !== "string" || rawAgent === "") {
|
|
17394
|
+
log11.warn(`client.session.get returned no string agent (保守放行)`, {
|
|
17395
|
+
sessionID,
|
|
17396
|
+
dataKeys: data && typeof data === "object" ? Object.keys(data) : null
|
|
17397
|
+
});
|
|
17398
|
+
return;
|
|
17399
|
+
}
|
|
17400
|
+
return rawAgent;
|
|
17401
|
+
} catch (err) {
|
|
17402
|
+
log11.warn(`client.session.get failed (保守放行)`, {
|
|
17403
|
+
sessionID,
|
|
17404
|
+
error: err instanceof Error ? err.message : String(err)
|
|
17405
|
+
});
|
|
17406
|
+
return;
|
|
17407
|
+
}
|
|
17408
|
+
}
|
|
17409
|
+
var log11 = makePluginLogger(PLUGIN_NAME20);
|
|
17023
17410
|
var toolPolicyServer = async (ctx) => {
|
|
17024
17411
|
const directory = ctx.directory ?? process.cwd();
|
|
17025
17412
|
const cfg = await loadPolicy(directory);
|
|
17026
17413
|
const rt = loadRuntimeSync();
|
|
17027
17414
|
cfg.defaultMode = rt.runtime.autonomy.default_mode;
|
|
17028
|
-
logLifecycle(
|
|
17415
|
+
logLifecycle(PLUGIN_NAME20, "activate", {
|
|
17029
17416
|
directory,
|
|
17030
17417
|
acl_loaded: !!cfg.acl,
|
|
17031
17418
|
default_mode: cfg.defaultMode,
|
|
@@ -17033,9 +17420,15 @@ var toolPolicyServer = async (ctx) => {
|
|
|
17033
17420
|
});
|
|
17034
17421
|
return {
|
|
17035
17422
|
"tool.execute.before": async (input, output) => {
|
|
17036
|
-
|
|
17423
|
+
let denied;
|
|
17424
|
+
await safeAsync(PLUGIN_NAME20, "tool.execute.before", async () => {
|
|
17037
17425
|
const toolName = input.tool;
|
|
17038
17426
|
const argsObj = output.args ?? {};
|
|
17427
|
+
const needsAgentDetection = toolName === "pending_changes" && (argsObj.action === "apply" || argsObj.action === "apply_all");
|
|
17428
|
+
let currentAgent = undefined;
|
|
17429
|
+
if (needsAgentDetection) {
|
|
17430
|
+
currentAgent = await resolveCurrentAgent(ctx.client, input.sessionID, log11);
|
|
17431
|
+
}
|
|
17039
17432
|
const files = [];
|
|
17040
17433
|
const filePath = argsObj["path"] ?? argsObj["filePath"] ?? argsObj["file"];
|
|
17041
17434
|
if (typeof filePath === "string") {
|
|
@@ -17048,25 +17441,33 @@ var toolPolicyServer = async (ctx) => {
|
|
|
17048
17441
|
args: argsObj,
|
|
17049
17442
|
mode: cfg.defaultMode,
|
|
17050
17443
|
files: files.length ? files : undefined
|
|
17051
|
-
}, cfg);
|
|
17052
|
-
safeWriteLog(
|
|
17444
|
+
}, cfg, currentAgent);
|
|
17445
|
+
safeWriteLog(PLUGIN_NAME20, {
|
|
17053
17446
|
hook: "tool.execute.before",
|
|
17054
17447
|
tool: toolName,
|
|
17055
17448
|
callID: input.callID,
|
|
17056
17449
|
sessionID: input.sessionID,
|
|
17057
17450
|
action: decision.action,
|
|
17058
|
-
reasons: decision.reasons
|
|
17451
|
+
reasons: decision.reasons,
|
|
17452
|
+
currentAgent
|
|
17059
17453
|
});
|
|
17060
17454
|
if (decision.action === "deny") {
|
|
17061
|
-
log11.warn(`[${
|
|
17455
|
+
log11.warn(`[${PLUGIN_NAME20}] DENY ${toolName}`, {
|
|
17456
|
+
action: argsObj.action,
|
|
17457
|
+
currentAgent,
|
|
17458
|
+
reasons: decision.reasons
|
|
17459
|
+
});
|
|
17460
|
+
denied = new Error(`[tool-policy] DENIED: ${decision.reasons.join(" / ")}`);
|
|
17062
17461
|
} else if (decision.action === "confirm") {
|
|
17063
|
-
log11.info(`[${
|
|
17462
|
+
log11.info(`[${PLUGIN_NAME20}] CONFIRM ${toolName}: ${decision.reasons.join("; ")}`);
|
|
17064
17463
|
}
|
|
17065
17464
|
});
|
|
17465
|
+
if (denied)
|
|
17466
|
+
throw denied;
|
|
17066
17467
|
}
|
|
17067
17468
|
};
|
|
17068
17469
|
};
|
|
17069
|
-
var
|
|
17470
|
+
var handler20 = toolPolicyServer;
|
|
17070
17471
|
|
|
17071
17472
|
// plugins/update-checker.ts
|
|
17072
17473
|
init_opencode_plugin_helpers();
|
|
@@ -17097,7 +17498,7 @@ import * as zlib from "node:zlib";
|
|
|
17097
17498
|
// lib/version-injected.ts
|
|
17098
17499
|
function getInjectedVersion() {
|
|
17099
17500
|
try {
|
|
17100
|
-
const v = "0.3.
|
|
17501
|
+
const v = "0.3.10";
|
|
17101
17502
|
if (typeof v === "string" && /^\d+\.\d+\.\d+/.test(v)) {
|
|
17102
17503
|
return v;
|
|
17103
17504
|
}
|
|
@@ -17600,20 +18001,20 @@ function compareOpencodeVersion(opts) {
|
|
|
17600
18001
|
}
|
|
17601
18002
|
|
|
17602
18003
|
// plugins/update-checker.ts
|
|
17603
|
-
var
|
|
17604
|
-
var
|
|
17605
|
-
logLifecycle(
|
|
18004
|
+
var PLUGIN_NAME21 = "update-checker";
|
|
18005
|
+
var PLUGIN_VERSION = "2.0.0";
|
|
18006
|
+
logLifecycle(PLUGIN_NAME21, "import", { version: PLUGIN_VERSION });
|
|
17606
18007
|
var updateCheckerServer = async (ctx) => {
|
|
17607
18008
|
const yieldResult = shouldYieldToLocalPlugin({ directory: ctx.directory });
|
|
17608
18009
|
if (yieldResult.yield) {
|
|
17609
|
-
safeWriteLog(
|
|
18010
|
+
safeWriteLog(PLUGIN_NAME21, {
|
|
17610
18011
|
level: "info",
|
|
17611
18012
|
msg: "dev_mode_yield_skip",
|
|
17612
18013
|
reason: yieldResult.reason,
|
|
17613
18014
|
markerPath: yieldResult.markerPath,
|
|
17614
18015
|
detail: formatYieldLog(yieldResult)
|
|
17615
18016
|
});
|
|
17616
|
-
logLifecycle(
|
|
18017
|
+
logLifecycle(PLUGIN_NAME21, "activate", {
|
|
17617
18018
|
yield_to_local: true,
|
|
17618
18019
|
yield_reason: yieldResult.reason,
|
|
17619
18020
|
skipped: "auto_install + background_check"
|
|
@@ -17622,8 +18023,8 @@ var updateCheckerServer = async (ctx) => {
|
|
|
17622
18023
|
}
|
|
17623
18024
|
const rt = loadRuntimeSync();
|
|
17624
18025
|
const u = rt.runtime.update;
|
|
17625
|
-
logLifecycle(
|
|
17626
|
-
version:
|
|
18026
|
+
logLifecycle(PLUGIN_NAME21, "activate", {
|
|
18027
|
+
version: PLUGIN_VERSION,
|
|
17627
18028
|
auto_check_enabled: u.auto_check_enabled,
|
|
17628
18029
|
interval_hours: u.interval_hours,
|
|
17629
18030
|
package: u.package,
|
|
@@ -17634,14 +18035,14 @@ var updateCheckerServer = async (ctx) => {
|
|
|
17634
18035
|
repo_fallback: u.repo,
|
|
17635
18036
|
config_source: "codeforge.json"
|
|
17636
18037
|
});
|
|
17637
|
-
await safeAsync(
|
|
18038
|
+
await safeAsync(PLUGIN_NAME21, "opencode_version_check", async () => {
|
|
17638
18039
|
const compat = loadCompatibility();
|
|
17639
18040
|
const opencodeVer = detectOpencodeVersion();
|
|
17640
18041
|
const verdict = compareOpencodeVersion({
|
|
17641
18042
|
currentOpencodeVer: opencodeVer,
|
|
17642
18043
|
compat
|
|
17643
18044
|
});
|
|
17644
|
-
safeWriteLog(
|
|
18045
|
+
safeWriteLog(PLUGIN_NAME21, {
|
|
17645
18046
|
level: "info",
|
|
17646
18047
|
msg: "opencode_version_check",
|
|
17647
18048
|
opencodeVer,
|
|
@@ -17658,14 +18059,14 @@ var updateCheckerServer = async (ctx) => {
|
|
|
17658
18059
|
}
|
|
17659
18060
|
});
|
|
17660
18061
|
if (!u.auto_check_enabled) {
|
|
17661
|
-
safeWriteLog(
|
|
18062
|
+
safeWriteLog(PLUGIN_NAME21, {
|
|
17662
18063
|
level: "info",
|
|
17663
18064
|
msg: "auto-check disabled (codeforge.json update.auto_check_enabled=false)"
|
|
17664
18065
|
});
|
|
17665
18066
|
return {};
|
|
17666
18067
|
}
|
|
17667
18068
|
setImmediate(() => {
|
|
17668
|
-
safeAsync(
|
|
18069
|
+
safeAsync(PLUGIN_NAME21, "checkAndMaybeUpdate", async () => {
|
|
17669
18070
|
const local = readLocalVersion();
|
|
17670
18071
|
let npmResult = null;
|
|
17671
18072
|
try {
|
|
@@ -17676,7 +18077,7 @@ var updateCheckerServer = async (ctx) => {
|
|
|
17676
18077
|
timeoutMs: 5000
|
|
17677
18078
|
});
|
|
17678
18079
|
} catch (e) {
|
|
17679
|
-
safeWriteLog(
|
|
18080
|
+
safeWriteLog(PLUGIN_NAME21, {
|
|
17680
18081
|
level: "warn",
|
|
17681
18082
|
msg: "npm_fetch_failed",
|
|
17682
18083
|
error: e.message
|
|
@@ -17685,7 +18086,7 @@ var updateCheckerServer = async (ctx) => {
|
|
|
17685
18086
|
return;
|
|
17686
18087
|
}
|
|
17687
18088
|
if (!npmResult) {
|
|
17688
|
-
safeWriteLog(
|
|
18089
|
+
safeWriteLog(PLUGIN_NAME21, {
|
|
17689
18090
|
level: "info",
|
|
17690
18091
|
msg: "npm_no_release",
|
|
17691
18092
|
package: u.package,
|
|
@@ -17694,7 +18095,7 @@ var updateCheckerServer = async (ctx) => {
|
|
|
17694
18095
|
return;
|
|
17695
18096
|
}
|
|
17696
18097
|
const hasUpdate = cmpVersion(local, npmResult.version) < 0;
|
|
17697
|
-
safeWriteLog(
|
|
18098
|
+
safeWriteLog(PLUGIN_NAME21, {
|
|
17698
18099
|
level: "info",
|
|
17699
18100
|
msg: "npm_check_result",
|
|
17700
18101
|
local,
|
|
@@ -17709,10 +18110,10 @@ var updateCheckerServer = async (ctx) => {
|
|
|
17709
18110
|
更新命令:npx ${u.package} install --global`);
|
|
17710
18111
|
return;
|
|
17711
18112
|
}
|
|
17712
|
-
await safeAsync(
|
|
18113
|
+
await safeAsync(PLUGIN_NAME21, "auto_install_bundle", async () => {
|
|
17713
18114
|
const target = getOpencodeBundlePath();
|
|
17714
18115
|
if (!target) {
|
|
17715
|
-
safeWriteLog(
|
|
18116
|
+
safeWriteLog(PLUGIN_NAME21, {
|
|
17716
18117
|
level: "warn",
|
|
17717
18118
|
msg: "auto_install_skip",
|
|
17718
18119
|
reason: "无法定位 opencode bundle 路径"
|
|
@@ -17731,7 +18132,7 @@ var updateCheckerServer = async (ctx) => {
|
|
|
17731
18132
|
oldVersion: local,
|
|
17732
18133
|
keepBackups: u.backup_keep
|
|
17733
18134
|
});
|
|
17734
|
-
safeWriteLog(
|
|
18135
|
+
safeWriteLog(PLUGIN_NAME21, {
|
|
17735
18136
|
level: "info",
|
|
17736
18137
|
msg: "auto_install_success",
|
|
17737
18138
|
local,
|
|
@@ -17771,7 +18172,7 @@ function getOpencodeBundlePath() {
|
|
|
17771
18172
|
return candidates[0] ?? null;
|
|
17772
18173
|
}
|
|
17773
18174
|
async function fallbackToGitHubReleases(ctx, u) {
|
|
17774
|
-
await safeAsync(
|
|
18175
|
+
await safeAsync(PLUGIN_NAME21, "github_fallback", async () => {
|
|
17775
18176
|
const result = await checkUpdateOnce({
|
|
17776
18177
|
repo: u.repo,
|
|
17777
18178
|
intervalMs: u.interval_hours * 3600 * 1000
|
|
@@ -17781,7 +18182,7 @@ async function fallbackToGitHubReleases(ctx, u) {
|
|
|
17781
18182
|
}
|
|
17782
18183
|
async function reportLegacyResult(ctx, result, repo) {
|
|
17783
18184
|
if (result.error && !result.fromCache) {
|
|
17784
|
-
safeWriteLog(
|
|
18185
|
+
safeWriteLog(PLUGIN_NAME21, {
|
|
17785
18186
|
level: "warn",
|
|
17786
18187
|
msg: `update check failed: ${result.error}`,
|
|
17787
18188
|
...result
|
|
@@ -17789,7 +18190,7 @@ async function reportLegacyResult(ctx, result, repo) {
|
|
|
17789
18190
|
return;
|
|
17790
18191
|
}
|
|
17791
18192
|
if (!result.hasUpdate) {
|
|
17792
|
-
safeWriteLog(
|
|
18193
|
+
safeWriteLog(PLUGIN_NAME21, {
|
|
17793
18194
|
level: "info",
|
|
17794
18195
|
msg: `up-to-date (local=${result.local}, remote=${result.remote}${result.fromCache ? ", from_cache" : ""})`,
|
|
17795
18196
|
...result
|
|
@@ -17799,7 +18200,7 @@ async function reportLegacyResult(ctx, result, repo) {
|
|
|
17799
18200
|
const updateCmd = `bunx --bun github:${repo} install`;
|
|
17800
18201
|
const toast = `[CodeForge] 有新版本:${result.local} → ${result.remote}
|
|
17801
18202
|
更新命令:${updateCmd}`;
|
|
17802
|
-
safeWriteLog(
|
|
18203
|
+
safeWriteLog(PLUGIN_NAME21, {
|
|
17803
18204
|
level: "info",
|
|
17804
18205
|
msg: "new_version_available_github_fallback",
|
|
17805
18206
|
local: result.local,
|
|
@@ -17810,17 +18211,17 @@ async function reportLegacyResult(ctx, result, repo) {
|
|
|
17810
18211
|
await postToast(ctx, toast);
|
|
17811
18212
|
}
|
|
17812
18213
|
async function postToast(ctx, message) {
|
|
17813
|
-
await safeAsync(
|
|
18214
|
+
await safeAsync(PLUGIN_NAME21, "client.app.log", async () => {
|
|
17814
18215
|
await ctx.client.app.log({
|
|
17815
18216
|
body: {
|
|
17816
|
-
service:
|
|
18217
|
+
service: PLUGIN_NAME21,
|
|
17817
18218
|
level: "info",
|
|
17818
18219
|
message
|
|
17819
18220
|
}
|
|
17820
18221
|
});
|
|
17821
18222
|
});
|
|
17822
18223
|
}
|
|
17823
|
-
var
|
|
18224
|
+
var handler21 = updateCheckerServer;
|
|
17824
18225
|
|
|
17825
18226
|
// plugins/workflow-engine.ts
|
|
17826
18227
|
init_opencode_plugin_helpers();
|
|
@@ -18324,9 +18725,9 @@ async function runStepAutoFeedback(step, adapter) {
|
|
|
18324
18725
|
}
|
|
18325
18726
|
|
|
18326
18727
|
// plugins/workflow-engine.ts
|
|
18327
|
-
var
|
|
18328
|
-
logLifecycle(
|
|
18329
|
-
var fallbackLog2 = makePluginLogger(
|
|
18728
|
+
var PLUGIN_NAME22 = "workflow-engine";
|
|
18729
|
+
logLifecycle(PLUGIN_NAME22, "import", {});
|
|
18730
|
+
var fallbackLog2 = makePluginLogger(PLUGIN_NAME22);
|
|
18330
18731
|
var _registry = null;
|
|
18331
18732
|
async function loadRegistry(workflowsDir) {
|
|
18332
18733
|
const { loaded, failed } = await loadWorkflowsFromDir(workflowsDir);
|
|
@@ -18346,32 +18747,32 @@ async function handleCommandInvoked(raw, workflowsDir = "workflows") {
|
|
|
18346
18747
|
const log12 = ctx.log ?? fallbackLog2;
|
|
18347
18748
|
const command = typeof ctx.command === "string" ? ctx.command : null;
|
|
18348
18749
|
if (!command) {
|
|
18349
|
-
log12.warn(`[${
|
|
18750
|
+
log12.warn(`[${PLUGIN_NAME22}] command.invoked 缺 command 字段`, ctx);
|
|
18350
18751
|
return null;
|
|
18351
18752
|
}
|
|
18352
18753
|
const reg = await ensureRegistry(workflowsDir);
|
|
18353
18754
|
if (reg.errors.length) {
|
|
18354
|
-
log12.warn(`[${
|
|
18755
|
+
log12.warn(`[${PLUGIN_NAME22}] 有 ${reg.errors.length} 个 workflow 加载失败`, reg.errors);
|
|
18355
18756
|
}
|
|
18356
18757
|
const wf = reg.workflows.find((w) => matchesTrigger(w, command));
|
|
18357
18758
|
if (!wf) {
|
|
18358
|
-
log12.info(`[${
|
|
18759
|
+
log12.info(`[${PLUGIN_NAME22}] no workflow matches "${command}"`);
|
|
18359
18760
|
return null;
|
|
18360
18761
|
}
|
|
18361
|
-
log12.info(`[${
|
|
18762
|
+
log12.info(`[${PLUGIN_NAME22}] dispatch "${command}" → workflow "${wf.name}"`);
|
|
18362
18763
|
try {
|
|
18363
18764
|
const result = await run(wf, {
|
|
18364
18765
|
mode: ctx.adapter ? "real" : "dry_run",
|
|
18365
18766
|
autonomy: ctx.autonomy ?? "semi",
|
|
18366
18767
|
adapter: ctx.adapter
|
|
18367
18768
|
});
|
|
18368
|
-
log12.info(`[${
|
|
18769
|
+
log12.info(`[${PLUGIN_NAME22}] workflow "${wf.name}" 完成 (${result.plan.mode})`, {
|
|
18369
18770
|
steps: result.plan.steps.length,
|
|
18370
18771
|
results: result.results.length
|
|
18371
18772
|
});
|
|
18372
18773
|
return result;
|
|
18373
18774
|
} catch (err) {
|
|
18374
|
-
log12.error(`[${
|
|
18775
|
+
log12.error(`[${PLUGIN_NAME22}] workflow "${wf.name}" 执行失败`, {
|
|
18375
18776
|
error: err instanceof Error ? err.message : String(err)
|
|
18376
18777
|
});
|
|
18377
18778
|
throw err;
|
|
@@ -18380,15 +18781,15 @@ async function handleCommandInvoked(raw, workflowsDir = "workflows") {
|
|
|
18380
18781
|
var workflowEngineServer = async (ctx) => {
|
|
18381
18782
|
const directory = ctx.directory ?? process.cwd();
|
|
18382
18783
|
const workflowsDir = path17.join(directory, "workflows");
|
|
18383
|
-
ensureRegistry(workflowsDir).catch((err) => fallbackLog2.warn(`[${
|
|
18784
|
+
ensureRegistry(workflowsDir).catch((err) => fallbackLog2.warn(`[${PLUGIN_NAME22}] preload workflows failed`, {
|
|
18384
18785
|
error: err instanceof Error ? err.message : String(err)
|
|
18385
18786
|
}));
|
|
18386
|
-
logLifecycle(
|
|
18787
|
+
logLifecycle(PLUGIN_NAME22, "activate", { directory, workflowsDir });
|
|
18387
18788
|
return {
|
|
18388
18789
|
"command.execute.before": async (input, output) => {
|
|
18389
|
-
await safeAsync(
|
|
18790
|
+
await safeAsync(PLUGIN_NAME22, "command.execute.before", async () => {
|
|
18390
18791
|
const cmd = input.command.startsWith("/") ? input.command : `/${input.command}`;
|
|
18391
|
-
safeWriteLog(
|
|
18792
|
+
safeWriteLog(PLUGIN_NAME22, {
|
|
18392
18793
|
hook: "command.execute.before",
|
|
18393
18794
|
command: cmd,
|
|
18394
18795
|
sessionID: input.sessionID
|
|
@@ -18403,14 +18804,14 @@ var workflowEngineServer = async (ctx) => {
|
|
|
18403
18804
|
});
|
|
18404
18805
|
},
|
|
18405
18806
|
"chat.message": async (input, output) => {
|
|
18406
|
-
await safeAsync(
|
|
18807
|
+
await safeAsync(PLUGIN_NAME22, "chat.message", async () => {
|
|
18407
18808
|
const text = extractUserText(output).trim();
|
|
18408
18809
|
if (!text.startsWith("/"))
|
|
18409
18810
|
return;
|
|
18410
18811
|
const cmd = text.split(/\s+/)[0];
|
|
18411
18812
|
if (!cmd)
|
|
18412
18813
|
return;
|
|
18413
|
-
safeWriteLog(
|
|
18814
|
+
safeWriteLog(PLUGIN_NAME22, {
|
|
18414
18815
|
hook: "chat.message",
|
|
18415
18816
|
command: cmd,
|
|
18416
18817
|
sessionID: input.sessionID
|
|
@@ -18420,7 +18821,7 @@ var workflowEngineServer = async (ctx) => {
|
|
|
18420
18821
|
}
|
|
18421
18822
|
};
|
|
18422
18823
|
};
|
|
18423
|
-
var
|
|
18824
|
+
var handler22 = workflowEngineServer;
|
|
18424
18825
|
|
|
18425
18826
|
// src/index.ts
|
|
18426
18827
|
var PLUGIN_ID = "codeforge";
|
|
@@ -18434,22 +18835,21 @@ var HANDLERS = [
|
|
|
18434
18835
|
{ name: "auto-learning", init: handler5 },
|
|
18435
18836
|
{ name: "channels", init: handler6 },
|
|
18436
18837
|
{ name: "codeforge-tools", init: handler8 },
|
|
18437
|
-
{ name: "
|
|
18438
|
-
{ name: "kh-
|
|
18439
|
-
{ name: "
|
|
18440
|
-
{ name: "
|
|
18441
|
-
{ name: "
|
|
18442
|
-
{ name: "
|
|
18443
|
-
{ name: "
|
|
18444
|
-
{ name: "
|
|
18445
|
-
{ name: "
|
|
18446
|
-
{ name: "
|
|
18447
|
-
{ name: "
|
|
18448
|
-
{ name: "token-manager", init: handler20 },
|
|
18838
|
+
{ name: "kh-auto-context", init: handler9 },
|
|
18839
|
+
{ name: "kh-reminder", init: handler10 },
|
|
18840
|
+
{ name: "memories-context", init: handler11 },
|
|
18841
|
+
{ name: "model-fallback", init: handler12 },
|
|
18842
|
+
{ name: "pwsh-utf8", init: handler15 },
|
|
18843
|
+
{ name: "session-recovery", init: handler16 },
|
|
18844
|
+
{ name: "subtask-heartbeat", init: handler13 },
|
|
18845
|
+
{ name: "subtasks", init: handler17 },
|
|
18846
|
+
{ name: "parallel-status", init: handler14 },
|
|
18847
|
+
{ name: "terminal-monitor", init: handler18 },
|
|
18848
|
+
{ name: "token-manager", init: handler19 },
|
|
18449
18849
|
{ name: "tool-heartbeat", init: handler7 },
|
|
18450
|
-
{ name: "tool-policy", init:
|
|
18451
|
-
{ name: "update-checker", init:
|
|
18452
|
-
{ name: "workflow-engine", init:
|
|
18850
|
+
{ name: "tool-policy", init: handler20 },
|
|
18851
|
+
{ name: "update-checker", init: handler21 },
|
|
18852
|
+
{ name: "workflow-engine", init: handler22 }
|
|
18453
18853
|
];
|
|
18454
18854
|
function makeSerialHook(hookName, fns) {
|
|
18455
18855
|
return async (input, output) => {
|