@andyqiu/codeforge 0.3.9 → 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 +4 -83
- package/agents/coder.md +13 -50
- package/agents/planner.md +12 -47
- package/agents/reviewer.md +8 -59
- package/dist/index.js +676 -285
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -10769,6 +10769,14 @@ function applyAnchor(content, edit, eol, beforeHash) {
|
|
|
10769
10769
|
if (!edit.anchor) {
|
|
10770
10770
|
return { ok: false, reason: "invalid_input", message: "anchor 不能为空" };
|
|
10771
10771
|
}
|
|
10772
|
+
if (edit.anchor.includes(`
|
|
10773
|
+
`)) {
|
|
10774
|
+
return {
|
|
10775
|
+
ok: false,
|
|
10776
|
+
reason: "invalid_input",
|
|
10777
|
+
message: "anchor 不能含换行符(仅支持单行匹配);多行改动请改用 pending_changes.stage 整文件暂存"
|
|
10778
|
+
};
|
|
10779
|
+
}
|
|
10772
10780
|
const lines = splitLines(content);
|
|
10773
10781
|
const hits = findAnchorLines(lines, edit.anchor, edit.regex === true);
|
|
10774
10782
|
if (hits.length === 0) {
|
|
@@ -10971,6 +10979,7 @@ function finish(before, after, beforeHash, affected) {
|
|
|
10971
10979
|
// tools/ast-edit.ts
|
|
10972
10980
|
var description16 = [
|
|
10973
10981
|
"AST 风格的精确文件编辑:anchor + 哈希校验,避免 LLM 「整文件重写」误改无关代码。",
|
|
10982
|
+
"**anchor 仅支持单行**:含 `\\n` 的多行 anchor 会被直接拒绝(reason=invalid_input);多行改动请改用 `pending_changes.stage` 整文件暂存。",
|
|
10974
10983
|
"**何时调用**:",
|
|
10975
10984
|
"- 需要在指定 anchor(行特征)后插入代码(hook 注入、import 添加)",
|
|
10976
10985
|
"- 重命名一个标识符(rename_symbol,全文件词边界匹配)",
|
|
@@ -10978,6 +10987,7 @@ var description16 = [
|
|
|
10978
10987
|
"**何时不需要**:",
|
|
10979
10988
|
"- 整文件重写更合理(直接 pending-changes.stage 整文件)",
|
|
10980
10989
|
"- 只是 1-2 个字符的 typo(用 edit 工具更直接)",
|
|
10990
|
+
"- 多行 anchor / 跨行匹配(请用 pending_changes.stage 整文件)",
|
|
10981
10991
|
"**安全约束**:",
|
|
10982
10992
|
"- 必须传 before_hash(如果文件已存在),不一致会被拒绝",
|
|
10983
10993
|
"- 默认 auto_stage=true:结果直接进 pending-changes 等待人审,不立刻落盘"
|
|
@@ -10995,7 +11005,7 @@ var AnchorAction = Common.extend({
|
|
|
10995
11005
|
"insert_after_anchor",
|
|
10996
11006
|
"insert_before_anchor"
|
|
10997
11007
|
]),
|
|
10998
|
-
anchor: z17.string().min(1).describe("anchor
|
|
11008
|
+
anchor: z17.string().min(1).describe("anchor 文本子串或正则源(必须单行;含 \\n 会被拒绝,多行改动请用 pending_changes.stage)"),
|
|
10999
11009
|
regex: z17.boolean().optional().describe("anchor 是否按 RegExp 解释,默认 false"),
|
|
11000
11010
|
occurrence: z17.number().int().min(1).optional().describe("第几次匹配(1-based),默认 1;多次命中时必须显式指定"),
|
|
11001
11011
|
payload: z17.string().describe("要写入的内容;引擎会按文件原 EOL 处理")
|
|
@@ -13517,76 +13527,146 @@ var codeforgeToolsServer = async (ctx) => {
|
|
|
13517
13527
|
};
|
|
13518
13528
|
var handler8 = codeforgeToolsServer;
|
|
13519
13529
|
|
|
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
|
-
|
|
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);
|
|
13544
13570
|
});
|
|
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
|
-
|
|
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);
|
|
13580
13642
|
});
|
|
13643
|
+
return await Promise.race([callPromise, racer]);
|
|
13644
|
+
} finally {
|
|
13645
|
+
if (timer)
|
|
13646
|
+
clearTimeout(timer);
|
|
13581
13647
|
}
|
|
13582
|
-
}
|
|
13583
|
-
|
|
13584
|
-
|
|
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;
|
|
13585
13666
|
|
|
13586
13667
|
// plugins/kh-auto-context.ts
|
|
13587
|
-
init_kh_client();
|
|
13588
13668
|
init_opencode_plugin_helpers();
|
|
13589
|
-
var
|
|
13669
|
+
var PLUGIN_NAME9 = "kh-auto-context";
|
|
13590
13670
|
var INJECTION_MODE = "observe-only";
|
|
13591
13671
|
function resolveInjectionMode(client) {
|
|
13592
13672
|
return client.hasTransport() ? "system-injected" : "observe-only";
|
|
@@ -13685,7 +13765,7 @@ var CODEFORGE_CONSTRAINTS = [
|
|
|
13685
13765
|
];
|
|
13686
13766
|
async function seedConstraints(client, log6) {
|
|
13687
13767
|
if (!client.hasTransport()) {
|
|
13688
|
-
log6?.debug?.(`[${
|
|
13768
|
+
log6?.debug?.(`[${PLUGIN_NAME9}] seedConstraints: transport 不可用,跳过`, {});
|
|
13689
13769
|
return { ok: false, reason: "transport_unavailable" };
|
|
13690
13770
|
}
|
|
13691
13771
|
try {
|
|
@@ -13699,7 +13779,7 @@ async function seedConstraints(client, log6) {
|
|
|
13699
13779
|
});
|
|
13700
13780
|
if (result && typeof result === "object" && "ok" in result && result.ok === false) {
|
|
13701
13781
|
const r = result;
|
|
13702
|
-
log6?.warn(`[${
|
|
13782
|
+
log6?.warn(`[${PLUGIN_NAME9}] seedConstraints 降级`, {
|
|
13703
13783
|
reason: r.reason,
|
|
13704
13784
|
message: r.message
|
|
13705
13785
|
});
|
|
@@ -13709,11 +13789,11 @@ async function seedConstraints(client, log6) {
|
|
|
13709
13789
|
message: r.message
|
|
13710
13790
|
};
|
|
13711
13791
|
}
|
|
13712
|
-
log6?.info(`[${
|
|
13792
|
+
log6?.info(`[${PLUGIN_NAME9}] seedConstraints: 已写入 ${CODEFORGE_CONSTRAINTS.length} 条 constraints`, { count: CODEFORGE_CONSTRAINTS.length });
|
|
13713
13793
|
return { ok: true, itemsWritten: CODEFORGE_CONSTRAINTS.length };
|
|
13714
13794
|
} catch (err) {
|
|
13715
13795
|
const message = err instanceof Error ? err.message : String(err);
|
|
13716
|
-
log6?.warn(`[${
|
|
13796
|
+
log6?.warn(`[${PLUGIN_NAME9}] seedConstraints 失败(已静默)`, { error: message });
|
|
13717
13797
|
return { ok: false, reason: "exception", message };
|
|
13718
13798
|
}
|
|
13719
13799
|
}
|
|
@@ -13728,7 +13808,7 @@ function createSystemInjectedHook(client, sessionId, log6) {
|
|
|
13728
13808
|
});
|
|
13729
13809
|
if (result && typeof result === "object" && "ok" in result && result.ok === false) {
|
|
13730
13810
|
const r = result;
|
|
13731
|
-
log6?.warn(`[${
|
|
13811
|
+
log6?.warn(`[${PLUGIN_NAME9}] system-injected 降级到 observe-only:${r.reason ?? "unknown"}`, {
|
|
13732
13812
|
sessionId,
|
|
13733
13813
|
section,
|
|
13734
13814
|
preview: markdown.slice(0, 200),
|
|
@@ -13737,12 +13817,12 @@ function createSystemInjectedHook(client, sessionId, log6) {
|
|
|
13737
13817
|
});
|
|
13738
13818
|
return;
|
|
13739
13819
|
}
|
|
13740
|
-
log6?.info(`[${
|
|
13820
|
+
log6?.info(`[${PLUGIN_NAME9}] system-injected: 写入 KH working_memory 成功 (${markdown.length} chars)`, {
|
|
13741
13821
|
sessionId,
|
|
13742
13822
|
section
|
|
13743
13823
|
});
|
|
13744
13824
|
} catch (err) {
|
|
13745
|
-
log6?.warn(`[${
|
|
13825
|
+
log6?.warn(`[${PLUGIN_NAME9}] system-injected 抛异常,降级到 observe-only`, {
|
|
13746
13826
|
sessionId,
|
|
13747
13827
|
section,
|
|
13748
13828
|
preview: markdown.slice(0, 200),
|
|
@@ -13751,23 +13831,16 @@ function createSystemInjectedHook(client, sessionId, log6) {
|
|
|
13751
13831
|
}
|
|
13752
13832
|
};
|
|
13753
13833
|
}
|
|
13754
|
-
|
|
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;
|
|
13755
13841
|
const cfg = opts.config ?? DEFAULT_CONFIG5;
|
|
13756
|
-
const mode = opts.mode ?? INJECTION_MODE;
|
|
13757
|
-
const ctx = raw ?? {};
|
|
13758
13842
|
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
|
-
}
|
|
13843
|
+
const startedAt = Date.now();
|
|
13771
13844
|
const racer = opts.scheduler?.raceTimeout ?? (async (p, ms) => {
|
|
13772
13845
|
let timer = null;
|
|
13773
13846
|
try {
|
|
@@ -13782,20 +13855,54 @@ async function handleMessage2(raw, opts) {
|
|
|
13782
13855
|
clearTimeout(timer);
|
|
13783
13856
|
}
|
|
13784
13857
|
});
|
|
13785
|
-
|
|
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
|
+
}
|
|
13786
13877
|
if (result === "__timeout__") {
|
|
13787
|
-
log6?.warn(`[${
|
|
13878
|
+
log6?.warn(`[${PLUGIN_NAME9}] timeout`, {
|
|
13879
|
+
query,
|
|
13880
|
+
ms: cfg.timeoutMs,
|
|
13881
|
+
elapsedMs: Date.now() - startedAt,
|
|
13882
|
+
sessionId: ctx.sessionId
|
|
13883
|
+
});
|
|
13788
13884
|
opts.cache.record(query, []);
|
|
13789
13885
|
return null;
|
|
13790
13886
|
}
|
|
13791
13887
|
if (!result.ok) {
|
|
13792
|
-
log6?.
|
|
13888
|
+
log6?.warn(`[${PLUGIN_NAME9}] kh degraded`, {
|
|
13889
|
+
reason: result.reason,
|
|
13890
|
+
query,
|
|
13891
|
+
elapsedMs: Date.now() - startedAt,
|
|
13892
|
+
sessionId: ctx.sessionId
|
|
13893
|
+
});
|
|
13793
13894
|
opts.cache.record(query, []);
|
|
13794
13895
|
return null;
|
|
13795
13896
|
}
|
|
13796
13897
|
const filtered = result.insights.filter((i) => (i.confidence ?? 0) >= cfg.minConfidence);
|
|
13797
13898
|
if (filtered.length === 0) {
|
|
13798
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
|
+
});
|
|
13799
13906
|
return null;
|
|
13800
13907
|
}
|
|
13801
13908
|
const payload = formatInjection(query, filtered, mode);
|
|
@@ -13803,22 +13910,68 @@ async function handleMessage2(raw, opts) {
|
|
|
13803
13910
|
try {
|
|
13804
13911
|
await ctx.injectContext(payload.markdown);
|
|
13805
13912
|
} catch (err) {
|
|
13806
|
-
log6?.warn(`[${
|
|
13807
|
-
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
|
|
13808
13917
|
});
|
|
13809
13918
|
}
|
|
13810
13919
|
}
|
|
13811
13920
|
opts.cache.record(query, filtered);
|
|
13812
|
-
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
|
+
});
|
|
13813
13928
|
return payload;
|
|
13814
13929
|
}
|
|
13815
|
-
|
|
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");
|
|
13816
13969
|
var sharedClient2 = new KhClient;
|
|
13817
13970
|
var sharedCache = new QueryCache(DEFAULT_CONFIG5.cacheTtlMs);
|
|
13818
13971
|
var khAutoContextServer = async (ctx) => {
|
|
13819
|
-
const log6 = makePluginLogger(
|
|
13972
|
+
const log6 = makePluginLogger(PLUGIN_NAME9);
|
|
13820
13973
|
const runtimeMode = resolveInjectionMode(sharedClient2);
|
|
13821
|
-
logLifecycle(
|
|
13974
|
+
logLifecycle(PLUGIN_NAME9, "activate", {
|
|
13822
13975
|
directory: ctx.directory,
|
|
13823
13976
|
minConfidence: DEFAULT_CONFIG5.minConfidence,
|
|
13824
13977
|
timeoutMs: DEFAULT_CONFIG5.timeoutMs,
|
|
@@ -13832,7 +13985,7 @@ var khAutoContextServer = async (ctx) => {
|
|
|
13832
13985
|
});
|
|
13833
13986
|
return {
|
|
13834
13987
|
"chat.message": async (input, output) => {
|
|
13835
|
-
await safeAsync(
|
|
13988
|
+
await safeAsync(PLUGIN_NAME9, "chat.message", async () => {
|
|
13836
13989
|
const text = extractUserText(output);
|
|
13837
13990
|
if (!text)
|
|
13838
13991
|
return;
|
|
@@ -13850,10 +14003,25 @@ var khAutoContextServer = async (ctx) => {
|
|
|
13850
14003
|
log: log6
|
|
13851
14004
|
}, { client: sharedClient2, cache: sharedCache, mode: runtimeMode });
|
|
13852
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
|
+
});
|
|
13853
14021
|
}
|
|
13854
14022
|
};
|
|
13855
14023
|
};
|
|
13856
|
-
var
|
|
14024
|
+
var handler9 = khAutoContextServer;
|
|
13857
14025
|
|
|
13858
14026
|
// plugins/kh-reminder.ts
|
|
13859
14027
|
init_opencode_plugin_helpers();
|
|
@@ -13974,8 +14142,8 @@ async function condense(input, opts = {}) {
|
|
|
13974
14142
|
}
|
|
13975
14143
|
|
|
13976
14144
|
// plugins/kh-reminder.ts
|
|
13977
|
-
var
|
|
13978
|
-
logLifecycle(
|
|
14145
|
+
var PLUGIN_NAME10 = "kh-reminder";
|
|
14146
|
+
logLifecycle(PLUGIN_NAME10, "import", {});
|
|
13979
14147
|
var TRIGGER_WORDS_ZH = [
|
|
13980
14148
|
"怎么",
|
|
13981
14149
|
"怎样",
|
|
@@ -14129,7 +14297,7 @@ function handleObserve(raw, log6) {
|
|
|
14129
14297
|
const result = evaluate(ctx);
|
|
14130
14298
|
if (result.triggered && result.reason) {
|
|
14131
14299
|
const reasonStr = formatReason(result.reason);
|
|
14132
|
-
log6?.info(`[${
|
|
14300
|
+
log6?.info(`[${PLUGIN_NAME10}] ⚡ KH 提醒(observe-only) · session=${result.sessionId} · ${reasonStr}`, {
|
|
14133
14301
|
sessionId: result.sessionId,
|
|
14134
14302
|
reason: result.reason,
|
|
14135
14303
|
suggestion: "调用 smart_search 查项目历史;完成后用 save_chat_insight 沉淀"
|
|
@@ -14137,7 +14305,7 @@ function handleObserve(raw, log6) {
|
|
|
14137
14305
|
}
|
|
14138
14306
|
return result;
|
|
14139
14307
|
} catch (err) {
|
|
14140
|
-
log6?.warn(`[${
|
|
14308
|
+
log6?.warn(`[${PLUGIN_NAME10}] evaluate 异常(已隔离)`, {
|
|
14141
14309
|
error: err instanceof Error ? err.message : String(err)
|
|
14142
14310
|
});
|
|
14143
14311
|
return null;
|
|
@@ -14153,9 +14321,9 @@ function formatReason(r) {
|
|
|
14153
14321
|
return `no-search-in-recent-rounds (last ${r.rounds})`;
|
|
14154
14322
|
}
|
|
14155
14323
|
}
|
|
14156
|
-
var log6 = makePluginLogger(
|
|
14324
|
+
var log6 = makePluginLogger(PLUGIN_NAME10);
|
|
14157
14325
|
var khReminderServer = async (ctx) => {
|
|
14158
|
-
logLifecycle(
|
|
14326
|
+
logLifecycle(PLUGIN_NAME10, "activate", {
|
|
14159
14327
|
directory: ctx.directory,
|
|
14160
14328
|
threshold: DEFAULT_THRESHOLD,
|
|
14161
14329
|
cooldown_ms: COOLDOWN_MS,
|
|
@@ -14163,7 +14331,7 @@ var khReminderServer = async (ctx) => {
|
|
|
14163
14331
|
});
|
|
14164
14332
|
return {
|
|
14165
14333
|
"experimental.chat.messages.transform": async (_input, output) => {
|
|
14166
|
-
await safeAsync(
|
|
14334
|
+
await safeAsync(PLUGIN_NAME10, "experimental.chat.messages.transform", async () => {
|
|
14167
14335
|
const list = output.messages;
|
|
14168
14336
|
if (!Array.isArray(list) || list.length === 0)
|
|
14169
14337
|
return;
|
|
@@ -14182,7 +14350,7 @@ var khReminderServer = async (ctx) => {
|
|
|
14182
14350
|
const result = handleObserve({ messages: flat, sessionId }, log6);
|
|
14183
14351
|
if (!result)
|
|
14184
14352
|
return;
|
|
14185
|
-
safeWriteLog(
|
|
14353
|
+
safeWriteLog(PLUGIN_NAME10, {
|
|
14186
14354
|
hook: "experimental.chat.messages.transform",
|
|
14187
14355
|
mode: "observe-only",
|
|
14188
14356
|
sessionId: result.sessionId,
|
|
@@ -14195,7 +14363,7 @@ var khReminderServer = async (ctx) => {
|
|
|
14195
14363
|
}
|
|
14196
14364
|
};
|
|
14197
14365
|
};
|
|
14198
|
-
var
|
|
14366
|
+
var handler10 = khReminderServer;
|
|
14199
14367
|
|
|
14200
14368
|
// lib/memories.ts
|
|
14201
14369
|
import { promises as fs8 } from "node:fs";
|
|
@@ -14397,7 +14565,7 @@ function bagOfWordsScore(query, doc) {
|
|
|
14397
14565
|
|
|
14398
14566
|
// plugins/memories-context.ts
|
|
14399
14567
|
init_opencode_plugin_helpers();
|
|
14400
|
-
var
|
|
14568
|
+
var PLUGIN_NAME11 = "memories-context";
|
|
14401
14569
|
var INJECTION_MODE2 = "observe-only";
|
|
14402
14570
|
var DEFAULT_CONFIG6 = {
|
|
14403
14571
|
minTextLength: 8,
|
|
@@ -14495,14 +14663,14 @@ async function handleMessage3(raw, opts) {
|
|
|
14495
14663
|
return await handleDirective(dir, ctx, opts.memCfg, log7);
|
|
14496
14664
|
}
|
|
14497
14665
|
if (!shouldRecall(text, cfg)) {
|
|
14498
|
-
log7?.debug?.(`[${
|
|
14666
|
+
log7?.debug?.(`[${PLUGIN_NAME11}] skip (filter)`, { textLen: text.length });
|
|
14499
14667
|
return { kind: "noop", reason: "filtered", mode: INJECTION_MODE2 };
|
|
14500
14668
|
}
|
|
14501
14669
|
const query = extractQuery2(text);
|
|
14502
14670
|
if (!query)
|
|
14503
14671
|
return { kind: "noop", reason: "empty_query", mode: INJECTION_MODE2 };
|
|
14504
14672
|
if (cache2.shouldSkip(query)) {
|
|
14505
|
-
log7?.debug?.(`[${
|
|
14673
|
+
log7?.debug?.(`[${PLUGIN_NAME11}] cache hit`, { query });
|
|
14506
14674
|
return { kind: "noop", reason: "cache", mode: INJECTION_MODE2 };
|
|
14507
14675
|
}
|
|
14508
14676
|
const racer = opts.scheduler?.raceTimeout ?? (async (p, ms) => {
|
|
@@ -14527,7 +14695,7 @@ async function handleMessage3(raw, opts) {
|
|
|
14527
14695
|
}, opts.memCfg);
|
|
14528
14696
|
const result = await racer(injectPromise, cfg.timeoutMs);
|
|
14529
14697
|
if (result === "__timeout__") {
|
|
14530
|
-
log7?.warn(`[${
|
|
14698
|
+
log7?.warn(`[${PLUGIN_NAME11}] timeout`, { query, ms: cfg.timeoutMs });
|
|
14531
14699
|
cache2.record(query, 0);
|
|
14532
14700
|
return { kind: "noop", reason: "timeout", mode: INJECTION_MODE2 };
|
|
14533
14701
|
}
|
|
@@ -14539,13 +14707,13 @@ async function handleMessage3(raw, opts) {
|
|
|
14539
14707
|
try {
|
|
14540
14708
|
await ctx.injectContext(result.text);
|
|
14541
14709
|
} catch (err) {
|
|
14542
|
-
log7?.warn(`[${
|
|
14710
|
+
log7?.warn(`[${PLUGIN_NAME11}] injectContext threw`, {
|
|
14543
14711
|
error: err instanceof Error ? err.message : String(err)
|
|
14544
14712
|
});
|
|
14545
14713
|
}
|
|
14546
14714
|
}
|
|
14547
14715
|
cache2.record(query, result.recalled);
|
|
14548
|
-
log7?.info(`[${
|
|
14716
|
+
log7?.info(`[${PLUGIN_NAME11}] observe-only: ${result.used}/${result.recalled} memories ready`, { query, mode: INJECTION_MODE2 });
|
|
14549
14717
|
return { kind: "injected", payload: result, mode: INJECTION_MODE2 };
|
|
14550
14718
|
}
|
|
14551
14719
|
async function handleDirective(dir, ctx, memCfg, log7) {
|
|
@@ -14554,14 +14722,14 @@ async function handleDirective(dir, ctx, memCfg, log7) {
|
|
|
14554
14722
|
if (r.ok) {
|
|
14555
14723
|
const target = r.written_to === "kh" ? "KH" : "本地";
|
|
14556
14724
|
await safeReply(ctx, `\uD83E\uDDE0 已记住(${dir.scope} / ${target},id=${r.id})`);
|
|
14557
|
-
log7?.info(`[${
|
|
14725
|
+
log7?.info(`[${PLUGIN_NAME11}] /remember ok`, {
|
|
14558
14726
|
scope: dir.scope,
|
|
14559
14727
|
id: r.id,
|
|
14560
14728
|
mode: INJECTION_MODE2
|
|
14561
14729
|
});
|
|
14562
14730
|
} else {
|
|
14563
14731
|
await safeReply(ctx, `❌ 记忆失败:${r.error ?? "unknown"}`);
|
|
14564
|
-
log7?.warn(`[${
|
|
14732
|
+
log7?.warn(`[${PLUGIN_NAME11}] /remember failed`, { error: r.error });
|
|
14565
14733
|
}
|
|
14566
14734
|
return { kind: "remembered", result: r, mode: INJECTION_MODE2 };
|
|
14567
14735
|
}
|
|
@@ -14592,22 +14760,22 @@ async function safeReply(ctx, text) {
|
|
|
14592
14760
|
await ctx.reply(text);
|
|
14593
14761
|
} catch {}
|
|
14594
14762
|
}
|
|
14595
|
-
logLifecycle(
|
|
14763
|
+
logLifecycle(PLUGIN_NAME11, "import");
|
|
14596
14764
|
var sharedCache2 = new QueryCache2(DEFAULT_CONFIG6.cacheTtlMs);
|
|
14597
14765
|
function buildMemCfg(directory) {
|
|
14598
14766
|
return { projectRoot: directory };
|
|
14599
14767
|
}
|
|
14600
14768
|
var memoriesContextServer = async (ctx) => {
|
|
14601
|
-
const log7 = makePluginLogger(
|
|
14769
|
+
const log7 = makePluginLogger(PLUGIN_NAME11);
|
|
14602
14770
|
const memCfg = buildMemCfg(ctx.directory);
|
|
14603
|
-
logLifecycle(
|
|
14771
|
+
logLifecycle(PLUGIN_NAME11, "activate", {
|
|
14604
14772
|
directory: ctx.directory,
|
|
14605
14773
|
projectRoot: memCfg.projectRoot,
|
|
14606
14774
|
mode: INJECTION_MODE2
|
|
14607
14775
|
});
|
|
14608
14776
|
return {
|
|
14609
14777
|
"chat.message": async (input, output) => {
|
|
14610
|
-
await safeAsync(
|
|
14778
|
+
await safeAsync(PLUGIN_NAME11, "chat.message", async () => {
|
|
14611
14779
|
const text = extractUserText(output);
|
|
14612
14780
|
if (!text)
|
|
14613
14781
|
return;
|
|
@@ -14633,18 +14801,18 @@ var memoriesContextServer = async (ctx) => {
|
|
|
14633
14801
|
}
|
|
14634
14802
|
};
|
|
14635
14803
|
};
|
|
14636
|
-
var
|
|
14804
|
+
var handler11 = memoriesContextServer;
|
|
14637
14805
|
|
|
14638
14806
|
// plugins/model-fallback.ts
|
|
14639
14807
|
init_opencode_plugin_helpers();
|
|
14640
|
-
var
|
|
14808
|
+
var PLUGIN_NAME12 = "model-fallback";
|
|
14641
14809
|
var state2 = {
|
|
14642
14810
|
config: null,
|
|
14643
14811
|
configPath: null,
|
|
14644
14812
|
warnings: [],
|
|
14645
14813
|
error: null
|
|
14646
14814
|
};
|
|
14647
|
-
logLifecycle(
|
|
14815
|
+
logLifecycle(PLUGIN_NAME12, "import");
|
|
14648
14816
|
function loadOnce(root) {
|
|
14649
14817
|
if (state2.config !== null || state2.error !== null)
|
|
14650
14818
|
return;
|
|
@@ -14654,11 +14822,11 @@ function loadOnce(root) {
|
|
|
14654
14822
|
state2.configPath = r.path ?? null;
|
|
14655
14823
|
state2.warnings = r.warnings;
|
|
14656
14824
|
if (r.warnings.length > 0) {
|
|
14657
|
-
safeWriteLog(
|
|
14825
|
+
safeWriteLog(PLUGIN_NAME12, { phase: "load.warnings", warnings: r.warnings });
|
|
14658
14826
|
}
|
|
14659
14827
|
} else {
|
|
14660
14828
|
state2.error = r.error ?? "unknown_load_error";
|
|
14661
|
-
safeWriteLog(
|
|
14829
|
+
safeWriteLog(PLUGIN_NAME12, { phase: "load.failed", error: state2.error, path: r.path });
|
|
14662
14830
|
}
|
|
14663
14831
|
}
|
|
14664
14832
|
var MODEL_ERR_PATTERNS = [
|
|
@@ -14703,9 +14871,9 @@ fallback 链已用尽:${meta.chain.join(" → ")}`;
|
|
|
14703
14871
|
完整链:${meta.chain.join(" → ")}`;
|
|
14704
14872
|
}
|
|
14705
14873
|
var modelFallbackServer = async (ctx) => {
|
|
14706
|
-
const log7 = makePluginLogger(
|
|
14874
|
+
const log7 = makePluginLogger(PLUGIN_NAME12);
|
|
14707
14875
|
loadOnce(ctx.directory ?? process.cwd());
|
|
14708
|
-
logLifecycle(
|
|
14876
|
+
logLifecycle(PLUGIN_NAME12, "activate", {
|
|
14709
14877
|
directory: ctx.directory,
|
|
14710
14878
|
config_path: state2.configPath,
|
|
14711
14879
|
config_loaded: state2.config !== null,
|
|
@@ -14715,7 +14883,7 @@ var modelFallbackServer = async (ctx) => {
|
|
|
14715
14883
|
});
|
|
14716
14884
|
return {
|
|
14717
14885
|
"chat.params": async (input, output) => {
|
|
14718
|
-
await safeAsync(
|
|
14886
|
+
await safeAsync(PLUGIN_NAME12, "chat.params", async () => {
|
|
14719
14887
|
if (!state2.config)
|
|
14720
14888
|
return;
|
|
14721
14889
|
const agent = input.agent;
|
|
@@ -14736,7 +14904,7 @@ var modelFallbackServer = async (ctx) => {
|
|
|
14736
14904
|
next: meta.next_fallback,
|
|
14737
14905
|
source: meta.source
|
|
14738
14906
|
};
|
|
14739
|
-
safeWriteLog(
|
|
14907
|
+
safeWriteLog(PLUGIN_NAME12, {
|
|
14740
14908
|
hook: "chat.params",
|
|
14741
14909
|
agent,
|
|
14742
14910
|
model: currentModel,
|
|
@@ -14746,7 +14914,7 @@ var modelFallbackServer = async (ctx) => {
|
|
|
14746
14914
|
});
|
|
14747
14915
|
},
|
|
14748
14916
|
event: async ({ event }) => {
|
|
14749
|
-
await safeAsync(
|
|
14917
|
+
await safeAsync(PLUGIN_NAME12, "event", async () => {
|
|
14750
14918
|
if (!state2.config)
|
|
14751
14919
|
return;
|
|
14752
14920
|
const e = event;
|
|
@@ -14762,8 +14930,8 @@ var modelFallbackServer = async (ctx) => {
|
|
|
14762
14930
|
const model = props.model ?? "unknown/unknown";
|
|
14763
14931
|
const meta = buildFallbackMeta(state2.config, agent, model);
|
|
14764
14932
|
const suggestion = meta ? buildSuggestion(meta, String(message)) : `⚠️ ${agent}/${model} 失败:${message}`;
|
|
14765
|
-
log7.warn(`[${
|
|
14766
|
-
safeWriteLog(
|
|
14933
|
+
log7.warn(`[${PLUGIN_NAME12}] ${suggestion}`);
|
|
14934
|
+
safeWriteLog(PLUGIN_NAME12, {
|
|
14767
14935
|
hook: "event.error",
|
|
14768
14936
|
eventType: e.type,
|
|
14769
14937
|
agent,
|
|
@@ -14775,19 +14943,24 @@ var modelFallbackServer = async (ctx) => {
|
|
|
14775
14943
|
}
|
|
14776
14944
|
};
|
|
14777
14945
|
};
|
|
14778
|
-
var
|
|
14946
|
+
var handler12 = modelFallbackServer;
|
|
14779
14947
|
|
|
14780
14948
|
// plugins/subtask-heartbeat.ts
|
|
14781
14949
|
init_opencode_plugin_helpers();
|
|
14782
|
-
var
|
|
14783
|
-
logLifecycle(
|
|
14950
|
+
var PLUGIN_NAME13 = "subtask-heartbeat";
|
|
14951
|
+
logLifecycle(PLUGIN_NAME13, "import", {});
|
|
14784
14952
|
var HEARTBEAT_INTERVAL_MS2 = 30000;
|
|
14785
14953
|
var HEARTBEAT_DEBOUNCE_MS = 25000;
|
|
14786
14954
|
var TOAST_DURATION_MS3 = 5000;
|
|
14787
14955
|
var START_TOAST_DURATION_MS = 2000;
|
|
14788
|
-
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;
|
|
14789
14962
|
function _snapshotInflight() {
|
|
14790
|
-
return [...
|
|
14963
|
+
return [...inflight3.values()].map((r) => ({ ...r }));
|
|
14791
14964
|
}
|
|
14792
14965
|
function getInflightSnapshot() {
|
|
14793
14966
|
return _snapshotInflight();
|
|
@@ -14829,20 +15002,97 @@ function extractEndedSessionID(event) {
|
|
|
14829
15002
|
}
|
|
14830
15003
|
return null;
|
|
14831
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
|
+
}
|
|
14832
15081
|
function registerInflight(payload, now = Date.now()) {
|
|
14833
15082
|
const r = {
|
|
14834
15083
|
childID: payload.childID,
|
|
14835
15084
|
parentID: payload.parentID,
|
|
14836
15085
|
agent: payload.agent,
|
|
15086
|
+
description: payload.description ?? null,
|
|
14837
15087
|
startedAt: now,
|
|
14838
15088
|
lastBeatAt: now,
|
|
14839
15089
|
lastTool: null
|
|
14840
15090
|
};
|
|
14841
|
-
|
|
15091
|
+
inflight3.set(payload.childID, r);
|
|
14842
15092
|
return r;
|
|
14843
15093
|
}
|
|
14844
15094
|
function recordToolBeat(sessionID, tool2, now = Date.now()) {
|
|
14845
|
-
const r =
|
|
15095
|
+
const r = inflight3.get(sessionID);
|
|
14846
15096
|
if (!r)
|
|
14847
15097
|
return null;
|
|
14848
15098
|
r.lastBeatAt = now;
|
|
@@ -14850,15 +15100,15 @@ function recordToolBeat(sessionID, tool2, now = Date.now()) {
|
|
|
14850
15100
|
return r;
|
|
14851
15101
|
}
|
|
14852
15102
|
function clearInflight2(sessionID) {
|
|
14853
|
-
const r =
|
|
15103
|
+
const r = inflight3.get(sessionID);
|
|
14854
15104
|
if (!r)
|
|
14855
15105
|
return null;
|
|
14856
|
-
|
|
15106
|
+
inflight3.delete(sessionID);
|
|
14857
15107
|
return r;
|
|
14858
15108
|
}
|
|
14859
15109
|
function pickHeartbeats(now = Date.now()) {
|
|
14860
15110
|
const out = [];
|
|
14861
|
-
for (const r of
|
|
15111
|
+
for (const r of inflight3.values()) {
|
|
14862
15112
|
if (now - r.lastBeatAt >= HEARTBEAT_DEBOUNCE_MS)
|
|
14863
15113
|
out.push(r);
|
|
14864
15114
|
}
|
|
@@ -14870,10 +15120,32 @@ function fmtElapsed(ms) {
|
|
|
14870
15120
|
const s = total % 60;
|
|
14871
15121
|
return m > 0 ? `${m}m${s.toString().padStart(2, "0")}s` : `${s}s`;
|
|
14872
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
|
+
}
|
|
14873
15134
|
function buildStartToast(r) {
|
|
14874
|
-
|
|
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
|
+
}
|
|
14875
15147
|
return {
|
|
14876
|
-
message: `\uD83D\uDE80 子 session 启动:
|
|
15148
|
+
message: `\uD83D\uDE80 子 session 启动: subagent`,
|
|
14877
15149
|
variant: "info"
|
|
14878
15150
|
};
|
|
14879
15151
|
}
|
|
@@ -14886,15 +15158,32 @@ function buildHeartbeatToast(r, now = Date.now()) {
|
|
|
14886
15158
|
};
|
|
14887
15159
|
}
|
|
14888
15160
|
function buildEndToast(r, type, now = Date.now()) {
|
|
14889
|
-
const who = r.agent ?? "subagent";
|
|
14890
15161
|
const elapsed = fmtElapsed(now - r.startedAt);
|
|
14891
|
-
|
|
14892
|
-
|
|
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
|
+
};
|
|
14893
15176
|
}
|
|
14894
|
-
if (
|
|
14895
|
-
return {
|
|
15177
|
+
if (r.agent) {
|
|
15178
|
+
return {
|
|
15179
|
+
message: `${emoji} ${titleCase(r.agent)} ${verb} (${elapsed})`,
|
|
15180
|
+
variant
|
|
15181
|
+
};
|
|
14896
15182
|
}
|
|
14897
|
-
return {
|
|
15183
|
+
return {
|
|
15184
|
+
message: `${emoji} subagent ${verb} (${elapsed})`,
|
|
15185
|
+
variant
|
|
15186
|
+
};
|
|
14898
15187
|
}
|
|
14899
15188
|
function normalizeVariant3(raw) {
|
|
14900
15189
|
if (raw === "info" || raw === "warning")
|
|
@@ -14923,22 +15212,26 @@ async function showToast3(client, payload, log7) {
|
|
|
14923
15212
|
return false;
|
|
14924
15213
|
}
|
|
14925
15214
|
}
|
|
14926
|
-
var log7 = makePluginLogger(
|
|
15215
|
+
var log7 = makePluginLogger(PLUGIN_NAME13);
|
|
14927
15216
|
var subtaskHeartbeatServer = async (ctx) => {
|
|
14928
|
-
logLifecycle(
|
|
15217
|
+
logLifecycle(PLUGIN_NAME13, "activate", {
|
|
14929
15218
|
directory: ctx.directory,
|
|
14930
15219
|
intervalMs: HEARTBEAT_INTERVAL_MS2
|
|
14931
15220
|
});
|
|
14932
15221
|
const client = ctx.client;
|
|
14933
15222
|
const interval = setInterval(() => {
|
|
14934
|
-
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
|
+
}
|
|
14935
15228
|
const beats = pickHeartbeats();
|
|
14936
15229
|
if (beats.length === 0)
|
|
14937
15230
|
return;
|
|
14938
15231
|
for (const r of beats) {
|
|
14939
15232
|
const t = buildHeartbeatToast(r);
|
|
14940
15233
|
const sent = await showToast3(client, t, log7);
|
|
14941
|
-
safeWriteLog(
|
|
15234
|
+
safeWriteLog(PLUGIN_NAME13, {
|
|
14942
15235
|
hook: "interval",
|
|
14943
15236
|
child: r.childID,
|
|
14944
15237
|
parent: r.parentID,
|
|
@@ -14955,23 +15248,47 @@ var subtaskHeartbeatServer = async (ctx) => {
|
|
|
14955
15248
|
}
|
|
14956
15249
|
return {
|
|
14957
15250
|
event: async ({ event }) => {
|
|
14958
|
-
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
|
+
}
|
|
14959
15267
|
const created = extractCreatedChild(event);
|
|
14960
15268
|
if (created) {
|
|
14961
|
-
const
|
|
14962
|
-
|
|
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, {
|
|
14963
15277
|
hook: "event",
|
|
14964
15278
|
type: "session.created",
|
|
14965
15279
|
child: created.childID,
|
|
14966
|
-
parent: created.parentID
|
|
15280
|
+
parent: created.parentID,
|
|
15281
|
+
pending_task_matched: pending !== null,
|
|
15282
|
+
agent: record.agent
|
|
14967
15283
|
});
|
|
14968
15284
|
const startToast = buildStartToast(record);
|
|
14969
15285
|
const sent = await showToast3(client, { ...startToast, duration: START_TOAST_DURATION_MS }, log7);
|
|
14970
|
-
safeWriteLog(
|
|
15286
|
+
safeWriteLog(PLUGIN_NAME13, {
|
|
14971
15287
|
hook: "event",
|
|
14972
15288
|
type: "session.created.toast",
|
|
14973
15289
|
child: created.childID,
|
|
14974
|
-
toast_sent: sent
|
|
15290
|
+
toast_sent: sent,
|
|
15291
|
+
start_toast_message: startToast.message
|
|
14975
15292
|
});
|
|
14976
15293
|
return;
|
|
14977
15294
|
}
|
|
@@ -14981,7 +15298,7 @@ var subtaskHeartbeatServer = async (ctx) => {
|
|
|
14981
15298
|
if (r) {
|
|
14982
15299
|
const t = buildEndToast(r, ended.type);
|
|
14983
15300
|
const sent = await showToast3(client, t, log7);
|
|
14984
|
-
safeWriteLog(
|
|
15301
|
+
safeWriteLog(PLUGIN_NAME13, {
|
|
14985
15302
|
hook: "event",
|
|
14986
15303
|
type: ended.type,
|
|
14987
15304
|
child: r.childID,
|
|
@@ -14994,7 +15311,9 @@ var subtaskHeartbeatServer = async (ctx) => {
|
|
|
14994
15311
|
});
|
|
14995
15312
|
},
|
|
14996
15313
|
"tool.execute.before": async (input) => {
|
|
14997
|
-
|
|
15314
|
+
if (inflight3.size === 0)
|
|
15315
|
+
return;
|
|
15316
|
+
await safeAsync(PLUGIN_NAME13, "tool.execute.before", async () => {
|
|
14998
15317
|
if (!input || typeof input.sessionID !== "string" || typeof input.tool !== "string")
|
|
14999
15318
|
return;
|
|
15000
15319
|
recordToolBeat(input.sessionID, input.tool);
|
|
@@ -15002,12 +15321,12 @@ var subtaskHeartbeatServer = async (ctx) => {
|
|
|
15002
15321
|
}
|
|
15003
15322
|
};
|
|
15004
15323
|
};
|
|
15005
|
-
var
|
|
15324
|
+
var handler13 = subtaskHeartbeatServer;
|
|
15006
15325
|
|
|
15007
15326
|
// plugins/parallel-status.ts
|
|
15008
15327
|
init_opencode_plugin_helpers();
|
|
15009
|
-
var
|
|
15010
|
-
logLifecycle(
|
|
15328
|
+
var PLUGIN_NAME14 = "parallel-status";
|
|
15329
|
+
logLifecycle(PLUGIN_NAME14, "import");
|
|
15011
15330
|
var ID_MAX_LEN = 16;
|
|
15012
15331
|
var ID_KEEP_LEN = 13;
|
|
15013
15332
|
function shortId(s) {
|
|
@@ -15039,8 +15358,8 @@ function formatInflightMarkdown(snapshot, now = Date.now()) {
|
|
|
15039
15358
|
`);
|
|
15040
15359
|
}
|
|
15041
15360
|
var parallelStatusServer = async (ctx) => {
|
|
15042
|
-
const log8 = makePluginLogger(
|
|
15043
|
-
logLifecycle(
|
|
15361
|
+
const log8 = makePluginLogger(PLUGIN_NAME14);
|
|
15362
|
+
logLifecycle(PLUGIN_NAME14, "activate", { directory: ctx.directory });
|
|
15044
15363
|
return {
|
|
15045
15364
|
"command.execute.before": async (input, output) => {
|
|
15046
15365
|
try {
|
|
@@ -15059,21 +15378,21 @@ var parallelStatusServer = async (ctx) => {
|
|
|
15059
15378
|
synthetic: false
|
|
15060
15379
|
});
|
|
15061
15380
|
}
|
|
15062
|
-
log8.info(`[${
|
|
15381
|
+
log8.info(`[${PLUGIN_NAME14}] 已回写 ${snapshot.length} 条 inflight`);
|
|
15063
15382
|
} catch (err) {
|
|
15064
|
-
log8.error(`[${
|
|
15383
|
+
log8.error(`[${PLUGIN_NAME14}] command.execute.before 异常(已隔离)`, {
|
|
15065
15384
|
error: err instanceof Error ? err.message : String(err)
|
|
15066
15385
|
});
|
|
15067
15386
|
}
|
|
15068
15387
|
}
|
|
15069
15388
|
};
|
|
15070
15389
|
};
|
|
15071
|
-
var
|
|
15390
|
+
var handler14 = parallelStatusServer;
|
|
15072
15391
|
|
|
15073
15392
|
// plugins/pwsh-utf8.ts
|
|
15074
15393
|
init_opencode_plugin_helpers();
|
|
15075
|
-
var
|
|
15076
|
-
logLifecycle(
|
|
15394
|
+
var PLUGIN_NAME15 = "pwsh-utf8";
|
|
15395
|
+
logLifecycle(PLUGIN_NAME15, "import", {});
|
|
15077
15396
|
var PRELUDE = "chcp 65001 *> $null; " + "[Console]::OutputEncoding = [System.Text.UTF8Encoding]::new(); " + "$OutputEncoding = [System.Text.UTF8Encoding]::new(); ";
|
|
15078
15397
|
function prependUtf8Prelude(command) {
|
|
15079
15398
|
if (typeof command !== "string")
|
|
@@ -15086,14 +15405,15 @@ function prependUtf8Prelude(command) {
|
|
|
15086
15405
|
return command;
|
|
15087
15406
|
return PRELUDE + command;
|
|
15088
15407
|
}
|
|
15089
|
-
var
|
|
15408
|
+
var handler15 = async (_ctx) => {
|
|
15090
15409
|
const enabled = process.platform === "win32" && process.env.CODEFORGE_DISABLE_PWSH_UTF8 !== "1";
|
|
15091
|
-
|
|
15410
|
+
const reason = enabled ? "win32" : process.platform !== "win32" ? "non-win32" : "disabled-by-env";
|
|
15411
|
+
logLifecycle(PLUGIN_NAME15, "activate", { enabled, platform: process.platform, reason });
|
|
15092
15412
|
if (!enabled)
|
|
15093
15413
|
return {};
|
|
15094
15414
|
return {
|
|
15095
15415
|
"tool.execute.before": async (input, output) => {
|
|
15096
|
-
await safeAsync(
|
|
15416
|
+
await safeAsync(PLUGIN_NAME15, "tool.execute.before", async () => {
|
|
15097
15417
|
if (input.tool !== "bash")
|
|
15098
15418
|
return;
|
|
15099
15419
|
const args = output.args ?? {};
|
|
@@ -15102,7 +15422,7 @@ var handler16 = async (_ctx) => {
|
|
|
15102
15422
|
if (next !== undefined && next !== original) {
|
|
15103
15423
|
args["command"] = next;
|
|
15104
15424
|
output.args = args;
|
|
15105
|
-
safeWriteLog(
|
|
15425
|
+
safeWriteLog(PLUGIN_NAME15, {
|
|
15106
15426
|
hook: "tool.execute.before",
|
|
15107
15427
|
tool: input.tool,
|
|
15108
15428
|
callID: input.callID,
|
|
@@ -15295,7 +15615,7 @@ function buildRecoveryPlan(events, meta = { id: null }, opts = {}) {
|
|
|
15295
15615
|
});
|
|
15296
15616
|
}
|
|
15297
15617
|
}
|
|
15298
|
-
const
|
|
15618
|
+
const inflight4 = [...toolMap.values()].sort((a, b) => b.last_ts - a.last_ts);
|
|
15299
15619
|
const proposed = window.some((t) => PENDING_CHANGES_TOOLS.has(t.tool));
|
|
15300
15620
|
const applied = window.some((t) => APPLY_TOOLS.has(t.tool) && t.ok);
|
|
15301
15621
|
const pending_changes_likely = proposed && !applied;
|
|
@@ -15319,7 +15639,7 @@ function buildRecoveryPlan(events, meta = { id: null }, opts = {}) {
|
|
|
15319
15639
|
idleMs,
|
|
15320
15640
|
lastUser,
|
|
15321
15641
|
lastAgent,
|
|
15322
|
-
inflight:
|
|
15642
|
+
inflight: inflight4,
|
|
15323
15643
|
pending_changes_likely,
|
|
15324
15644
|
open_subtasks_likely,
|
|
15325
15645
|
reason
|
|
@@ -15330,7 +15650,7 @@ function buildRecoveryPlan(events, meta = { id: null }, opts = {}) {
|
|
|
15330
15650
|
idle_ms: idleMs,
|
|
15331
15651
|
last_user_intent: lastUser,
|
|
15332
15652
|
last_agent: lastAgent,
|
|
15333
|
-
inflight_tools:
|
|
15653
|
+
inflight_tools: inflight4,
|
|
15334
15654
|
pending_changes_likely,
|
|
15335
15655
|
open_subtasks_likely,
|
|
15336
15656
|
summary
|
|
@@ -15435,8 +15755,8 @@ function isRecoveryWorthShowing(plan) {
|
|
|
15435
15755
|
}
|
|
15436
15756
|
|
|
15437
15757
|
// plugins/session-recovery.ts
|
|
15438
|
-
var
|
|
15439
|
-
logLifecycle(
|
|
15758
|
+
var PLUGIN_NAME16 = "session-recovery";
|
|
15759
|
+
logLifecycle(PLUGIN_NAME16, "import", {});
|
|
15440
15760
|
async function processSessionStart(currentSessionId, opts = {}) {
|
|
15441
15761
|
if (opts.disabled) {
|
|
15442
15762
|
return { ok: true, injected: false, reason: "disabled" };
|
|
@@ -15446,7 +15766,7 @@ async function processSessionStart(currentSessionId, opts = {}) {
|
|
|
15446
15766
|
excludeIds.add(currentSessionId);
|
|
15447
15767
|
const r = await scanLastSession({ ...opts, excludeIds: [...excludeIds] });
|
|
15448
15768
|
if (!r.ok) {
|
|
15449
|
-
opts.log?.warn?.(`[${
|
|
15769
|
+
opts.log?.warn?.(`[${PLUGIN_NAME16}] 扫描失败:${r.error}`);
|
|
15450
15770
|
return { ok: false, injected: false, reason: "scan_error", error: r.error };
|
|
15451
15771
|
}
|
|
15452
15772
|
const plan = r.plan;
|
|
@@ -15460,7 +15780,7 @@ async function processSessionStart(currentSessionId, opts = {}) {
|
|
|
15460
15780
|
await opts.injectRecovery(injection);
|
|
15461
15781
|
} catch (err) {
|
|
15462
15782
|
const msg = err instanceof Error ? err.message : String(err);
|
|
15463
|
-
opts.log?.warn?.(`[${
|
|
15783
|
+
opts.log?.warn?.(`[${PLUGIN_NAME16}] injectRecovery 异常:${msg}`);
|
|
15464
15784
|
return { ok: false, injected: false, plan, reason: "inject_error", error: msg };
|
|
15465
15785
|
}
|
|
15466
15786
|
}
|
|
@@ -15489,13 +15809,13 @@ function renderPrompt(plan) {
|
|
|
15489
15809
|
return lines.join(`
|
|
15490
15810
|
`);
|
|
15491
15811
|
}
|
|
15492
|
-
var log8 = makePluginLogger(
|
|
15812
|
+
var log8 = makePluginLogger(PLUGIN_NAME16);
|
|
15493
15813
|
var _lastInjection = null;
|
|
15494
15814
|
var sessionRecoveryServer = async (ctx) => {
|
|
15495
|
-
logLifecycle(
|
|
15815
|
+
logLifecycle(PLUGIN_NAME16, "activate", { directory: ctx.directory });
|
|
15496
15816
|
return {
|
|
15497
15817
|
event: async ({ event }) => {
|
|
15498
|
-
await safeAsync(
|
|
15818
|
+
await safeAsync(PLUGIN_NAME16, "event", async () => {
|
|
15499
15819
|
const e = event;
|
|
15500
15820
|
if (!e || typeof e.type !== "string")
|
|
15501
15821
|
return;
|
|
@@ -15510,7 +15830,7 @@ var sessionRecoveryServer = async (ctx) => {
|
|
|
15510
15830
|
_lastInjection = inj;
|
|
15511
15831
|
}
|
|
15512
15832
|
});
|
|
15513
|
-
safeWriteLog(
|
|
15833
|
+
safeWriteLog(PLUGIN_NAME16, {
|
|
15514
15834
|
hook: "event",
|
|
15515
15835
|
type: "session.start",
|
|
15516
15836
|
ok: r.ok,
|
|
@@ -15519,13 +15839,13 @@ var sessionRecoveryServer = async (ctx) => {
|
|
|
15519
15839
|
last_session_id: r.plan?.last_session_id
|
|
15520
15840
|
});
|
|
15521
15841
|
if (r.injected && r.plan) {
|
|
15522
|
-
log8.info(`[${
|
|
15842
|
+
log8.info(`[${PLUGIN_NAME16}] 注入恢复提示(last=${r.plan.last_session_id?.slice(0, 8) ?? "?"}, reason=${r.plan.reason})`);
|
|
15523
15843
|
}
|
|
15524
15844
|
});
|
|
15525
15845
|
}
|
|
15526
15846
|
};
|
|
15527
15847
|
};
|
|
15528
|
-
var
|
|
15848
|
+
var handler16 = sessionRecoveryServer;
|
|
15529
15849
|
|
|
15530
15850
|
// plugins/subtasks.ts
|
|
15531
15851
|
import { promises as fs10 } from "node:fs";
|
|
@@ -16050,7 +16370,7 @@ async function sendParentNotice(client, sessionID, text, opts = {}) {
|
|
|
16050
16370
|
// plugins/subtasks.ts
|
|
16051
16371
|
init_opencode_plugin_helpers();
|
|
16052
16372
|
init_runtime_paths();
|
|
16053
|
-
var
|
|
16373
|
+
var PLUGIN_NAME17 = "subtasks";
|
|
16054
16374
|
function getLogFile(root = process.cwd()) {
|
|
16055
16375
|
return path13.join(runtimeDir(root), "logs", "subtasks.log");
|
|
16056
16376
|
}
|
|
@@ -16129,7 +16449,7 @@ async function handleParallelCommand(raw) {
|
|
|
16129
16449
|
specs = splitDescriptions(args.description, { parentId });
|
|
16130
16450
|
}
|
|
16131
16451
|
if (specs.length === 0) {
|
|
16132
|
-
log9?.warn(`[${
|
|
16452
|
+
log9?.warn(`[${PLUGIN_NAME17}] /parallel 缺有效子任务`);
|
|
16133
16453
|
await safeReply2(ctx, "⚠ /parallel 需要至少 1 个子任务(用 ; 或换行分隔)");
|
|
16134
16454
|
return { ok: false, reason: "no_subtasks" };
|
|
16135
16455
|
}
|
|
@@ -16187,7 +16507,7 @@ async function handleParallelCommand(raw) {
|
|
|
16187
16507
|
});
|
|
16188
16508
|
} catch (err) {
|
|
16189
16509
|
const msg = err instanceof Error ? err.message : String(err);
|
|
16190
|
-
log9?.error(`[${
|
|
16510
|
+
log9?.error(`[${PLUGIN_NAME17}] schedule 抛错`, { error: msg });
|
|
16191
16511
|
await safeReply2(ctx, `❌ 并发调度失败:${msg}`);
|
|
16192
16512
|
return { ok: false, reason: msg };
|
|
16193
16513
|
}
|
|
@@ -16195,7 +16515,7 @@ async function handleParallelCommand(raw) {
|
|
|
16195
16515
|
const summaryLines = [head, "", "```", result.digest.text, "```"];
|
|
16196
16516
|
await safeReply2(ctx, summaryLines.join(`
|
|
16197
16517
|
`));
|
|
16198
|
-
log9?.info(`[${
|
|
16518
|
+
log9?.info(`[${PLUGIN_NAME17}] schedule 完成`, {
|
|
16199
16519
|
parentId,
|
|
16200
16520
|
success: result.digest.success,
|
|
16201
16521
|
failed: result.digest.failed,
|
|
@@ -16205,7 +16525,7 @@ async function handleParallelCommand(raw) {
|
|
|
16205
16525
|
try {
|
|
16206
16526
|
await ctx.onCompleted(result);
|
|
16207
16527
|
} catch (err) {
|
|
16208
|
-
log9?.warn(`[${
|
|
16528
|
+
log9?.warn(`[${PLUGIN_NAME17}] onCompleted hook 抛错(已隔离)`, {
|
|
16209
16529
|
error: err instanceof Error ? err.message : String(err)
|
|
16210
16530
|
});
|
|
16211
16531
|
}
|
|
@@ -16246,7 +16566,7 @@ async function writeLog(level, msg, data) {
|
|
|
16246
16566
|
const line = JSON.stringify({
|
|
16247
16567
|
ts: new Date().toISOString(),
|
|
16248
16568
|
level,
|
|
16249
|
-
plugin:
|
|
16569
|
+
plugin: PLUGIN_NAME17,
|
|
16250
16570
|
msg,
|
|
16251
16571
|
data
|
|
16252
16572
|
}) + `
|
|
@@ -16257,11 +16577,11 @@ async function writeLog(level, msg, data) {
|
|
|
16257
16577
|
await fs10.appendFile(logFile, line, "utf8");
|
|
16258
16578
|
} catch {}
|
|
16259
16579
|
}
|
|
16260
|
-
logLifecycle(
|
|
16580
|
+
logLifecycle(PLUGIN_NAME17, "import");
|
|
16261
16581
|
var subtasksServer = async (ctx) => {
|
|
16262
|
-
const log9 = makePluginLogger(
|
|
16582
|
+
const log9 = makePluginLogger(PLUGIN_NAME17);
|
|
16263
16583
|
const client = ctx?.client ?? undefined;
|
|
16264
|
-
logLifecycle(
|
|
16584
|
+
logLifecycle(PLUGIN_NAME17, "activate", {
|
|
16265
16585
|
directory: ctx.directory,
|
|
16266
16586
|
hasClient: Boolean(client)
|
|
16267
16587
|
});
|
|
@@ -16321,20 +16641,20 @@ var subtasksServer = async (ctx) => {
|
|
|
16321
16641
|
});
|
|
16322
16642
|
}
|
|
16323
16643
|
} catch (err) {
|
|
16324
|
-
log9.error(`[${
|
|
16644
|
+
log9.error(`[${PLUGIN_NAME17}] command.execute.before 异常(已隔离)`, {
|
|
16325
16645
|
error: err instanceof Error ? err.message : String(err)
|
|
16326
16646
|
});
|
|
16327
16647
|
}
|
|
16328
16648
|
}
|
|
16329
16649
|
};
|
|
16330
16650
|
};
|
|
16331
|
-
var
|
|
16651
|
+
var handler17 = subtasksServer;
|
|
16332
16652
|
|
|
16333
16653
|
// plugins/terminal-monitor.ts
|
|
16334
16654
|
init_opencode_plugin_helpers();
|
|
16335
16655
|
import * as crypto5 from "node:crypto";
|
|
16336
|
-
var
|
|
16337
|
-
logLifecycle(
|
|
16656
|
+
var PLUGIN_NAME18 = "terminal-monitor";
|
|
16657
|
+
logLifecycle(PLUGIN_NAME18, "import", {});
|
|
16338
16658
|
var DEFAULT_CONFIG7 = {
|
|
16339
16659
|
minScore: 0.6,
|
|
16340
16660
|
cooldownMs: 30000,
|
|
@@ -16616,17 +16936,17 @@ function describeError(err) {
|
|
|
16616
16936
|
return String(err);
|
|
16617
16937
|
}
|
|
16618
16938
|
}
|
|
16619
|
-
var log9 = makePluginLogger(
|
|
16939
|
+
var log9 = makePluginLogger(PLUGIN_NAME18);
|
|
16620
16940
|
var lru = new FingerprintLRU2;
|
|
16621
16941
|
var _lastNotification = null;
|
|
16622
16942
|
var terminalMonitorServer = async (ctx) => {
|
|
16623
|
-
logLifecycle(
|
|
16943
|
+
logLifecycle(PLUGIN_NAME18, "activate", {
|
|
16624
16944
|
directory: ctx.directory,
|
|
16625
16945
|
triggerEventTypes: ["terminal.output", "terminal.exit"]
|
|
16626
16946
|
});
|
|
16627
16947
|
return {
|
|
16628
16948
|
event: async ({ event }) => {
|
|
16629
|
-
await safeAsync(
|
|
16949
|
+
await safeAsync(PLUGIN_NAME18, "event", async () => {
|
|
16630
16950
|
const e = event;
|
|
16631
16951
|
if (!e || typeof e.type !== "string")
|
|
16632
16952
|
return;
|
|
@@ -16640,7 +16960,7 @@ var terminalMonitorServer = async (ctx) => {
|
|
|
16640
16960
|
_lastNotification = msg;
|
|
16641
16961
|
}
|
|
16642
16962
|
});
|
|
16643
|
-
safeWriteLog(
|
|
16963
|
+
safeWriteLog(PLUGIN_NAME18, {
|
|
16644
16964
|
hook: "event",
|
|
16645
16965
|
type: e.type,
|
|
16646
16966
|
notified: r.notified,
|
|
@@ -16648,18 +16968,18 @@ var terminalMonitorServer = async (ctx) => {
|
|
|
16648
16968
|
findings: r.notification?.findings.length ?? 0
|
|
16649
16969
|
});
|
|
16650
16970
|
if (r.notified && r.notification) {
|
|
16651
|
-
log9.info(`[${
|
|
16971
|
+
log9.info(`[${PLUGIN_NAME18}] 反馈 ${r.notification.findings.length} 条 → ${r.notification.summary}`);
|
|
16652
16972
|
}
|
|
16653
16973
|
});
|
|
16654
16974
|
}
|
|
16655
16975
|
};
|
|
16656
16976
|
};
|
|
16657
|
-
var
|
|
16977
|
+
var handler18 = terminalMonitorServer;
|
|
16658
16978
|
|
|
16659
16979
|
// plugins/token-manager.ts
|
|
16660
16980
|
init_opencode_plugin_helpers();
|
|
16661
|
-
var
|
|
16662
|
-
logLifecycle(
|
|
16981
|
+
var PLUGIN_NAME19 = "token-manager";
|
|
16982
|
+
logLifecycle(PLUGIN_NAME19, "import", {});
|
|
16663
16983
|
async function handleMessageBefore(raw, log10, defaults) {
|
|
16664
16984
|
const ctx = raw ?? {};
|
|
16665
16985
|
if (!Array.isArray(ctx.messages) || ctx.messages.length === 0)
|
|
@@ -16679,21 +16999,21 @@ async function handleMessageBefore(raw, log10, defaults) {
|
|
|
16679
16999
|
};
|
|
16680
17000
|
if (r.compressed) {
|
|
16681
17001
|
ctx.messages = r.messages;
|
|
16682
|
-
log10?.info(`[${
|
|
17002
|
+
log10?.info(`[${PLUGIN_NAME19}] 压缩 ${r.before.count}→${r.after.count} 条 / ${r.before.tokens}→${r.after.tokens} tokens`);
|
|
16683
17003
|
}
|
|
16684
17004
|
return r;
|
|
16685
17005
|
} catch (err) {
|
|
16686
|
-
log10?.warn(`[${
|
|
17006
|
+
log10?.warn(`[${PLUGIN_NAME19}] 压缩异常(已隔离)`, {
|
|
16687
17007
|
error: err instanceof Error ? err.message : String(err)
|
|
16688
17008
|
});
|
|
16689
17009
|
return null;
|
|
16690
17010
|
}
|
|
16691
17011
|
}
|
|
16692
|
-
var log10 = makePluginLogger(
|
|
17012
|
+
var log10 = makePluginLogger(PLUGIN_NAME19);
|
|
16693
17013
|
var tokenManagerServer = async (ctx) => {
|
|
16694
17014
|
const rt = loadRuntimeSync();
|
|
16695
17015
|
const threshold = rt.runtime.context.condenser_threshold_ratio;
|
|
16696
|
-
logLifecycle(
|
|
17016
|
+
logLifecycle(PLUGIN_NAME19, "activate", {
|
|
16697
17017
|
directory: ctx.directory,
|
|
16698
17018
|
threshold,
|
|
16699
17019
|
target: DEFAULT_CONDENSE.target,
|
|
@@ -16702,7 +17022,7 @@ var tokenManagerServer = async (ctx) => {
|
|
|
16702
17022
|
});
|
|
16703
17023
|
return {
|
|
16704
17024
|
"experimental.chat.messages.transform": async (_input, output) => {
|
|
16705
|
-
await safeAsync(
|
|
17025
|
+
await safeAsync(PLUGIN_NAME19, "experimental.chat.messages.transform", async () => {
|
|
16706
17026
|
const list = output.messages;
|
|
16707
17027
|
if (!Array.isArray(list) || list.length === 0)
|
|
16708
17028
|
return;
|
|
@@ -16715,7 +17035,7 @@ var tokenManagerServer = async (ctx) => {
|
|
|
16715
17035
|
const r = await handleMessageBefore({ messages: flat }, log10, { threshold });
|
|
16716
17036
|
if (!r)
|
|
16717
17037
|
return;
|
|
16718
|
-
safeWriteLog(
|
|
17038
|
+
safeWriteLog(PLUGIN_NAME19, {
|
|
16719
17039
|
hook: "experimental.chat.messages.transform",
|
|
16720
17040
|
mode: "observe-only",
|
|
16721
17041
|
before_msgs: r.before.count,
|
|
@@ -16726,13 +17046,13 @@ var tokenManagerServer = async (ctx) => {
|
|
|
16726
17046
|
reason: r.reason
|
|
16727
17047
|
});
|
|
16728
17048
|
if (r.compressed) {
|
|
16729
|
-
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)`);
|
|
16730
17050
|
}
|
|
16731
17051
|
});
|
|
16732
17052
|
}
|
|
16733
17053
|
};
|
|
16734
17054
|
};
|
|
16735
|
-
var
|
|
17055
|
+
var handler19 = tokenManagerServer;
|
|
16736
17056
|
|
|
16737
17057
|
// plugins/tool-policy.ts
|
|
16738
17058
|
init_opencode_plugin_helpers();
|
|
@@ -16975,10 +17295,42 @@ function checkFileAccess(acl, file, op) {
|
|
|
16975
17295
|
}
|
|
16976
17296
|
|
|
16977
17297
|
// plugins/tool-policy.ts
|
|
16978
|
-
var
|
|
16979
|
-
logLifecycle(
|
|
17298
|
+
var PLUGIN_NAME20 = "tool-policy";
|
|
17299
|
+
logLifecycle(PLUGIN_NAME20, "import", {});
|
|
16980
17300
|
var EMPTY_ACL = { whitelistMode: false };
|
|
16981
|
-
|
|
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
|
+
}
|
|
16982
17334
|
const fallbackMode = cfg.defaultMode ?? DEFAULT_RUNTIME.autonomy.default_mode;
|
|
16983
17335
|
const mode = ctx.mode ?? fallbackMode;
|
|
16984
17336
|
const a = evaluate2(mode, {
|
|
@@ -17028,13 +17380,39 @@ function classifyToolKind(toolName) {
|
|
|
17028
17380
|
return "webfetch";
|
|
17029
17381
|
return "other";
|
|
17030
17382
|
}
|
|
17031
|
-
|
|
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);
|
|
17032
17410
|
var toolPolicyServer = async (ctx) => {
|
|
17033
17411
|
const directory = ctx.directory ?? process.cwd();
|
|
17034
17412
|
const cfg = await loadPolicy(directory);
|
|
17035
17413
|
const rt = loadRuntimeSync();
|
|
17036
17414
|
cfg.defaultMode = rt.runtime.autonomy.default_mode;
|
|
17037
|
-
logLifecycle(
|
|
17415
|
+
logLifecycle(PLUGIN_NAME20, "activate", {
|
|
17038
17416
|
directory,
|
|
17039
17417
|
acl_loaded: !!cfg.acl,
|
|
17040
17418
|
default_mode: cfg.defaultMode,
|
|
@@ -17042,9 +17420,15 @@ var toolPolicyServer = async (ctx) => {
|
|
|
17042
17420
|
});
|
|
17043
17421
|
return {
|
|
17044
17422
|
"tool.execute.before": async (input, output) => {
|
|
17045
|
-
|
|
17423
|
+
let denied;
|
|
17424
|
+
await safeAsync(PLUGIN_NAME20, "tool.execute.before", async () => {
|
|
17046
17425
|
const toolName = input.tool;
|
|
17047
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
|
+
}
|
|
17048
17432
|
const files = [];
|
|
17049
17433
|
const filePath = argsObj["path"] ?? argsObj["filePath"] ?? argsObj["file"];
|
|
17050
17434
|
if (typeof filePath === "string") {
|
|
@@ -17057,25 +17441,33 @@ var toolPolicyServer = async (ctx) => {
|
|
|
17057
17441
|
args: argsObj,
|
|
17058
17442
|
mode: cfg.defaultMode,
|
|
17059
17443
|
files: files.length ? files : undefined
|
|
17060
|
-
}, cfg);
|
|
17061
|
-
safeWriteLog(
|
|
17444
|
+
}, cfg, currentAgent);
|
|
17445
|
+
safeWriteLog(PLUGIN_NAME20, {
|
|
17062
17446
|
hook: "tool.execute.before",
|
|
17063
17447
|
tool: toolName,
|
|
17064
17448
|
callID: input.callID,
|
|
17065
17449
|
sessionID: input.sessionID,
|
|
17066
17450
|
action: decision.action,
|
|
17067
|
-
reasons: decision.reasons
|
|
17451
|
+
reasons: decision.reasons,
|
|
17452
|
+
currentAgent
|
|
17068
17453
|
});
|
|
17069
17454
|
if (decision.action === "deny") {
|
|
17070
|
-
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(" / ")}`);
|
|
17071
17461
|
} else if (decision.action === "confirm") {
|
|
17072
|
-
log11.info(`[${
|
|
17462
|
+
log11.info(`[${PLUGIN_NAME20}] CONFIRM ${toolName}: ${decision.reasons.join("; ")}`);
|
|
17073
17463
|
}
|
|
17074
17464
|
});
|
|
17465
|
+
if (denied)
|
|
17466
|
+
throw denied;
|
|
17075
17467
|
}
|
|
17076
17468
|
};
|
|
17077
17469
|
};
|
|
17078
|
-
var
|
|
17470
|
+
var handler20 = toolPolicyServer;
|
|
17079
17471
|
|
|
17080
17472
|
// plugins/update-checker.ts
|
|
17081
17473
|
init_opencode_plugin_helpers();
|
|
@@ -17106,7 +17498,7 @@ import * as zlib from "node:zlib";
|
|
|
17106
17498
|
// lib/version-injected.ts
|
|
17107
17499
|
function getInjectedVersion() {
|
|
17108
17500
|
try {
|
|
17109
|
-
const v = "0.3.
|
|
17501
|
+
const v = "0.3.10";
|
|
17110
17502
|
if (typeof v === "string" && /^\d+\.\d+\.\d+/.test(v)) {
|
|
17111
17503
|
return v;
|
|
17112
17504
|
}
|
|
@@ -17609,20 +18001,20 @@ function compareOpencodeVersion(opts) {
|
|
|
17609
18001
|
}
|
|
17610
18002
|
|
|
17611
18003
|
// plugins/update-checker.ts
|
|
17612
|
-
var
|
|
17613
|
-
var
|
|
17614
|
-
logLifecycle(
|
|
18004
|
+
var PLUGIN_NAME21 = "update-checker";
|
|
18005
|
+
var PLUGIN_VERSION = "2.0.0";
|
|
18006
|
+
logLifecycle(PLUGIN_NAME21, "import", { version: PLUGIN_VERSION });
|
|
17615
18007
|
var updateCheckerServer = async (ctx) => {
|
|
17616
18008
|
const yieldResult = shouldYieldToLocalPlugin({ directory: ctx.directory });
|
|
17617
18009
|
if (yieldResult.yield) {
|
|
17618
|
-
safeWriteLog(
|
|
18010
|
+
safeWriteLog(PLUGIN_NAME21, {
|
|
17619
18011
|
level: "info",
|
|
17620
18012
|
msg: "dev_mode_yield_skip",
|
|
17621
18013
|
reason: yieldResult.reason,
|
|
17622
18014
|
markerPath: yieldResult.markerPath,
|
|
17623
18015
|
detail: formatYieldLog(yieldResult)
|
|
17624
18016
|
});
|
|
17625
|
-
logLifecycle(
|
|
18017
|
+
logLifecycle(PLUGIN_NAME21, "activate", {
|
|
17626
18018
|
yield_to_local: true,
|
|
17627
18019
|
yield_reason: yieldResult.reason,
|
|
17628
18020
|
skipped: "auto_install + background_check"
|
|
@@ -17631,8 +18023,8 @@ var updateCheckerServer = async (ctx) => {
|
|
|
17631
18023
|
}
|
|
17632
18024
|
const rt = loadRuntimeSync();
|
|
17633
18025
|
const u = rt.runtime.update;
|
|
17634
|
-
logLifecycle(
|
|
17635
|
-
version:
|
|
18026
|
+
logLifecycle(PLUGIN_NAME21, "activate", {
|
|
18027
|
+
version: PLUGIN_VERSION,
|
|
17636
18028
|
auto_check_enabled: u.auto_check_enabled,
|
|
17637
18029
|
interval_hours: u.interval_hours,
|
|
17638
18030
|
package: u.package,
|
|
@@ -17643,14 +18035,14 @@ var updateCheckerServer = async (ctx) => {
|
|
|
17643
18035
|
repo_fallback: u.repo,
|
|
17644
18036
|
config_source: "codeforge.json"
|
|
17645
18037
|
});
|
|
17646
|
-
await safeAsync(
|
|
18038
|
+
await safeAsync(PLUGIN_NAME21, "opencode_version_check", async () => {
|
|
17647
18039
|
const compat = loadCompatibility();
|
|
17648
18040
|
const opencodeVer = detectOpencodeVersion();
|
|
17649
18041
|
const verdict = compareOpencodeVersion({
|
|
17650
18042
|
currentOpencodeVer: opencodeVer,
|
|
17651
18043
|
compat
|
|
17652
18044
|
});
|
|
17653
|
-
safeWriteLog(
|
|
18045
|
+
safeWriteLog(PLUGIN_NAME21, {
|
|
17654
18046
|
level: "info",
|
|
17655
18047
|
msg: "opencode_version_check",
|
|
17656
18048
|
opencodeVer,
|
|
@@ -17667,14 +18059,14 @@ var updateCheckerServer = async (ctx) => {
|
|
|
17667
18059
|
}
|
|
17668
18060
|
});
|
|
17669
18061
|
if (!u.auto_check_enabled) {
|
|
17670
|
-
safeWriteLog(
|
|
18062
|
+
safeWriteLog(PLUGIN_NAME21, {
|
|
17671
18063
|
level: "info",
|
|
17672
18064
|
msg: "auto-check disabled (codeforge.json update.auto_check_enabled=false)"
|
|
17673
18065
|
});
|
|
17674
18066
|
return {};
|
|
17675
18067
|
}
|
|
17676
18068
|
setImmediate(() => {
|
|
17677
|
-
safeAsync(
|
|
18069
|
+
safeAsync(PLUGIN_NAME21, "checkAndMaybeUpdate", async () => {
|
|
17678
18070
|
const local = readLocalVersion();
|
|
17679
18071
|
let npmResult = null;
|
|
17680
18072
|
try {
|
|
@@ -17685,7 +18077,7 @@ var updateCheckerServer = async (ctx) => {
|
|
|
17685
18077
|
timeoutMs: 5000
|
|
17686
18078
|
});
|
|
17687
18079
|
} catch (e) {
|
|
17688
|
-
safeWriteLog(
|
|
18080
|
+
safeWriteLog(PLUGIN_NAME21, {
|
|
17689
18081
|
level: "warn",
|
|
17690
18082
|
msg: "npm_fetch_failed",
|
|
17691
18083
|
error: e.message
|
|
@@ -17694,7 +18086,7 @@ var updateCheckerServer = async (ctx) => {
|
|
|
17694
18086
|
return;
|
|
17695
18087
|
}
|
|
17696
18088
|
if (!npmResult) {
|
|
17697
|
-
safeWriteLog(
|
|
18089
|
+
safeWriteLog(PLUGIN_NAME21, {
|
|
17698
18090
|
level: "info",
|
|
17699
18091
|
msg: "npm_no_release",
|
|
17700
18092
|
package: u.package,
|
|
@@ -17703,7 +18095,7 @@ var updateCheckerServer = async (ctx) => {
|
|
|
17703
18095
|
return;
|
|
17704
18096
|
}
|
|
17705
18097
|
const hasUpdate = cmpVersion(local, npmResult.version) < 0;
|
|
17706
|
-
safeWriteLog(
|
|
18098
|
+
safeWriteLog(PLUGIN_NAME21, {
|
|
17707
18099
|
level: "info",
|
|
17708
18100
|
msg: "npm_check_result",
|
|
17709
18101
|
local,
|
|
@@ -17718,10 +18110,10 @@ var updateCheckerServer = async (ctx) => {
|
|
|
17718
18110
|
更新命令:npx ${u.package} install --global`);
|
|
17719
18111
|
return;
|
|
17720
18112
|
}
|
|
17721
|
-
await safeAsync(
|
|
18113
|
+
await safeAsync(PLUGIN_NAME21, "auto_install_bundle", async () => {
|
|
17722
18114
|
const target = getOpencodeBundlePath();
|
|
17723
18115
|
if (!target) {
|
|
17724
|
-
safeWriteLog(
|
|
18116
|
+
safeWriteLog(PLUGIN_NAME21, {
|
|
17725
18117
|
level: "warn",
|
|
17726
18118
|
msg: "auto_install_skip",
|
|
17727
18119
|
reason: "无法定位 opencode bundle 路径"
|
|
@@ -17740,7 +18132,7 @@ var updateCheckerServer = async (ctx) => {
|
|
|
17740
18132
|
oldVersion: local,
|
|
17741
18133
|
keepBackups: u.backup_keep
|
|
17742
18134
|
});
|
|
17743
|
-
safeWriteLog(
|
|
18135
|
+
safeWriteLog(PLUGIN_NAME21, {
|
|
17744
18136
|
level: "info",
|
|
17745
18137
|
msg: "auto_install_success",
|
|
17746
18138
|
local,
|
|
@@ -17780,7 +18172,7 @@ function getOpencodeBundlePath() {
|
|
|
17780
18172
|
return candidates[0] ?? null;
|
|
17781
18173
|
}
|
|
17782
18174
|
async function fallbackToGitHubReleases(ctx, u) {
|
|
17783
|
-
await safeAsync(
|
|
18175
|
+
await safeAsync(PLUGIN_NAME21, "github_fallback", async () => {
|
|
17784
18176
|
const result = await checkUpdateOnce({
|
|
17785
18177
|
repo: u.repo,
|
|
17786
18178
|
intervalMs: u.interval_hours * 3600 * 1000
|
|
@@ -17790,7 +18182,7 @@ async function fallbackToGitHubReleases(ctx, u) {
|
|
|
17790
18182
|
}
|
|
17791
18183
|
async function reportLegacyResult(ctx, result, repo) {
|
|
17792
18184
|
if (result.error && !result.fromCache) {
|
|
17793
|
-
safeWriteLog(
|
|
18185
|
+
safeWriteLog(PLUGIN_NAME21, {
|
|
17794
18186
|
level: "warn",
|
|
17795
18187
|
msg: `update check failed: ${result.error}`,
|
|
17796
18188
|
...result
|
|
@@ -17798,7 +18190,7 @@ async function reportLegacyResult(ctx, result, repo) {
|
|
|
17798
18190
|
return;
|
|
17799
18191
|
}
|
|
17800
18192
|
if (!result.hasUpdate) {
|
|
17801
|
-
safeWriteLog(
|
|
18193
|
+
safeWriteLog(PLUGIN_NAME21, {
|
|
17802
18194
|
level: "info",
|
|
17803
18195
|
msg: `up-to-date (local=${result.local}, remote=${result.remote}${result.fromCache ? ", from_cache" : ""})`,
|
|
17804
18196
|
...result
|
|
@@ -17808,7 +18200,7 @@ async function reportLegacyResult(ctx, result, repo) {
|
|
|
17808
18200
|
const updateCmd = `bunx --bun github:${repo} install`;
|
|
17809
18201
|
const toast = `[CodeForge] 有新版本:${result.local} → ${result.remote}
|
|
17810
18202
|
更新命令:${updateCmd}`;
|
|
17811
|
-
safeWriteLog(
|
|
18203
|
+
safeWriteLog(PLUGIN_NAME21, {
|
|
17812
18204
|
level: "info",
|
|
17813
18205
|
msg: "new_version_available_github_fallback",
|
|
17814
18206
|
local: result.local,
|
|
@@ -17819,17 +18211,17 @@ async function reportLegacyResult(ctx, result, repo) {
|
|
|
17819
18211
|
await postToast(ctx, toast);
|
|
17820
18212
|
}
|
|
17821
18213
|
async function postToast(ctx, message) {
|
|
17822
|
-
await safeAsync(
|
|
18214
|
+
await safeAsync(PLUGIN_NAME21, "client.app.log", async () => {
|
|
17823
18215
|
await ctx.client.app.log({
|
|
17824
18216
|
body: {
|
|
17825
|
-
service:
|
|
18217
|
+
service: PLUGIN_NAME21,
|
|
17826
18218
|
level: "info",
|
|
17827
18219
|
message
|
|
17828
18220
|
}
|
|
17829
18221
|
});
|
|
17830
18222
|
});
|
|
17831
18223
|
}
|
|
17832
|
-
var
|
|
18224
|
+
var handler21 = updateCheckerServer;
|
|
17833
18225
|
|
|
17834
18226
|
// plugins/workflow-engine.ts
|
|
17835
18227
|
init_opencode_plugin_helpers();
|
|
@@ -18333,9 +18725,9 @@ async function runStepAutoFeedback(step, adapter) {
|
|
|
18333
18725
|
}
|
|
18334
18726
|
|
|
18335
18727
|
// plugins/workflow-engine.ts
|
|
18336
|
-
var
|
|
18337
|
-
logLifecycle(
|
|
18338
|
-
var fallbackLog2 = makePluginLogger(
|
|
18728
|
+
var PLUGIN_NAME22 = "workflow-engine";
|
|
18729
|
+
logLifecycle(PLUGIN_NAME22, "import", {});
|
|
18730
|
+
var fallbackLog2 = makePluginLogger(PLUGIN_NAME22);
|
|
18339
18731
|
var _registry = null;
|
|
18340
18732
|
async function loadRegistry(workflowsDir) {
|
|
18341
18733
|
const { loaded, failed } = await loadWorkflowsFromDir(workflowsDir);
|
|
@@ -18355,32 +18747,32 @@ async function handleCommandInvoked(raw, workflowsDir = "workflows") {
|
|
|
18355
18747
|
const log12 = ctx.log ?? fallbackLog2;
|
|
18356
18748
|
const command = typeof ctx.command === "string" ? ctx.command : null;
|
|
18357
18749
|
if (!command) {
|
|
18358
|
-
log12.warn(`[${
|
|
18750
|
+
log12.warn(`[${PLUGIN_NAME22}] command.invoked 缺 command 字段`, ctx);
|
|
18359
18751
|
return null;
|
|
18360
18752
|
}
|
|
18361
18753
|
const reg = await ensureRegistry(workflowsDir);
|
|
18362
18754
|
if (reg.errors.length) {
|
|
18363
|
-
log12.warn(`[${
|
|
18755
|
+
log12.warn(`[${PLUGIN_NAME22}] 有 ${reg.errors.length} 个 workflow 加载失败`, reg.errors);
|
|
18364
18756
|
}
|
|
18365
18757
|
const wf = reg.workflows.find((w) => matchesTrigger(w, command));
|
|
18366
18758
|
if (!wf) {
|
|
18367
|
-
log12.info(`[${
|
|
18759
|
+
log12.info(`[${PLUGIN_NAME22}] no workflow matches "${command}"`);
|
|
18368
18760
|
return null;
|
|
18369
18761
|
}
|
|
18370
|
-
log12.info(`[${
|
|
18762
|
+
log12.info(`[${PLUGIN_NAME22}] dispatch "${command}" → workflow "${wf.name}"`);
|
|
18371
18763
|
try {
|
|
18372
18764
|
const result = await run(wf, {
|
|
18373
18765
|
mode: ctx.adapter ? "real" : "dry_run",
|
|
18374
18766
|
autonomy: ctx.autonomy ?? "semi",
|
|
18375
18767
|
adapter: ctx.adapter
|
|
18376
18768
|
});
|
|
18377
|
-
log12.info(`[${
|
|
18769
|
+
log12.info(`[${PLUGIN_NAME22}] workflow "${wf.name}" 完成 (${result.plan.mode})`, {
|
|
18378
18770
|
steps: result.plan.steps.length,
|
|
18379
18771
|
results: result.results.length
|
|
18380
18772
|
});
|
|
18381
18773
|
return result;
|
|
18382
18774
|
} catch (err) {
|
|
18383
|
-
log12.error(`[${
|
|
18775
|
+
log12.error(`[${PLUGIN_NAME22}] workflow "${wf.name}" 执行失败`, {
|
|
18384
18776
|
error: err instanceof Error ? err.message : String(err)
|
|
18385
18777
|
});
|
|
18386
18778
|
throw err;
|
|
@@ -18389,15 +18781,15 @@ async function handleCommandInvoked(raw, workflowsDir = "workflows") {
|
|
|
18389
18781
|
var workflowEngineServer = async (ctx) => {
|
|
18390
18782
|
const directory = ctx.directory ?? process.cwd();
|
|
18391
18783
|
const workflowsDir = path17.join(directory, "workflows");
|
|
18392
|
-
ensureRegistry(workflowsDir).catch((err) => fallbackLog2.warn(`[${
|
|
18784
|
+
ensureRegistry(workflowsDir).catch((err) => fallbackLog2.warn(`[${PLUGIN_NAME22}] preload workflows failed`, {
|
|
18393
18785
|
error: err instanceof Error ? err.message : String(err)
|
|
18394
18786
|
}));
|
|
18395
|
-
logLifecycle(
|
|
18787
|
+
logLifecycle(PLUGIN_NAME22, "activate", { directory, workflowsDir });
|
|
18396
18788
|
return {
|
|
18397
18789
|
"command.execute.before": async (input, output) => {
|
|
18398
|
-
await safeAsync(
|
|
18790
|
+
await safeAsync(PLUGIN_NAME22, "command.execute.before", async () => {
|
|
18399
18791
|
const cmd = input.command.startsWith("/") ? input.command : `/${input.command}`;
|
|
18400
|
-
safeWriteLog(
|
|
18792
|
+
safeWriteLog(PLUGIN_NAME22, {
|
|
18401
18793
|
hook: "command.execute.before",
|
|
18402
18794
|
command: cmd,
|
|
18403
18795
|
sessionID: input.sessionID
|
|
@@ -18412,14 +18804,14 @@ var workflowEngineServer = async (ctx) => {
|
|
|
18412
18804
|
});
|
|
18413
18805
|
},
|
|
18414
18806
|
"chat.message": async (input, output) => {
|
|
18415
|
-
await safeAsync(
|
|
18807
|
+
await safeAsync(PLUGIN_NAME22, "chat.message", async () => {
|
|
18416
18808
|
const text = extractUserText(output).trim();
|
|
18417
18809
|
if (!text.startsWith("/"))
|
|
18418
18810
|
return;
|
|
18419
18811
|
const cmd = text.split(/\s+/)[0];
|
|
18420
18812
|
if (!cmd)
|
|
18421
18813
|
return;
|
|
18422
|
-
safeWriteLog(
|
|
18814
|
+
safeWriteLog(PLUGIN_NAME22, {
|
|
18423
18815
|
hook: "chat.message",
|
|
18424
18816
|
command: cmd,
|
|
18425
18817
|
sessionID: input.sessionID
|
|
@@ -18429,7 +18821,7 @@ var workflowEngineServer = async (ctx) => {
|
|
|
18429
18821
|
}
|
|
18430
18822
|
};
|
|
18431
18823
|
};
|
|
18432
|
-
var
|
|
18824
|
+
var handler22 = workflowEngineServer;
|
|
18433
18825
|
|
|
18434
18826
|
// src/index.ts
|
|
18435
18827
|
var PLUGIN_ID = "codeforge";
|
|
@@ -18443,22 +18835,21 @@ var HANDLERS = [
|
|
|
18443
18835
|
{ name: "auto-learning", init: handler5 },
|
|
18444
18836
|
{ name: "channels", init: handler6 },
|
|
18445
18837
|
{ 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 },
|
|
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 },
|
|
18458
18849
|
{ name: "tool-heartbeat", init: handler7 },
|
|
18459
|
-
{ name: "tool-policy", init:
|
|
18460
|
-
{ name: "update-checker", init:
|
|
18461
|
-
{ name: "workflow-engine", init:
|
|
18850
|
+
{ name: "tool-policy", init: handler20 },
|
|
18851
|
+
{ name: "update-checker", init: handler21 },
|
|
18852
|
+
{ name: "workflow-engine", init: handler22 }
|
|
18462
18853
|
];
|
|
18463
18854
|
function makeSerialHook(hookName, fns) {
|
|
18464
18855
|
return async (input, output) => {
|