@andyqiu/codeforge 0.3.7 → 0.3.8
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/commands/parallel-status.md +56 -0
- package/commands/parallel.md +38 -1
- package/dist/index.js +685 -432
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -262,7 +262,7 @@ var init_auto_review_trigger = __esm(() => {
|
|
|
262
262
|
});
|
|
263
263
|
|
|
264
264
|
// lib/runtime-paths.ts
|
|
265
|
-
import { mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync, existsSync } from "node:fs";
|
|
265
|
+
import { mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync, existsSync as existsSync2 } from "node:fs";
|
|
266
266
|
import * as path2 from "node:path";
|
|
267
267
|
import * as os from "node:os";
|
|
268
268
|
import * as crypto from "node:crypto";
|
|
@@ -296,7 +296,7 @@ function runtimeDir(absRoot, opts = {}) {
|
|
|
296
296
|
return dir;
|
|
297
297
|
mkdirSync2(dir, { recursive: true });
|
|
298
298
|
const metaFile = path2.join(dir, ".meta.json");
|
|
299
|
-
if (
|
|
299
|
+
if (existsSync2(metaFile)) {
|
|
300
300
|
const existing = readMetaSafe(metaFile);
|
|
301
301
|
if (existing && existing.absPath && existing.absPath !== resolvedRoot) {
|
|
302
302
|
throw new Error(`runtime dir hash collision: ${projectKey(resolvedRoot)} already used by ${existing.absPath}, current is ${resolvedRoot}`);
|
|
@@ -335,7 +335,7 @@ function readMetaSafe(file) {
|
|
|
335
335
|
var init_runtime_paths = () => {};
|
|
336
336
|
|
|
337
337
|
// lib/global-config.ts
|
|
338
|
-
import { readFileSync as readFileSync3, existsSync as
|
|
338
|
+
import { readFileSync as readFileSync3, existsSync as existsSync3, statSync } from "node:fs";
|
|
339
339
|
import * as path3 from "node:path";
|
|
340
340
|
import * as os2 from "node:os";
|
|
341
341
|
function __resetGlobalConfigCache() {
|
|
@@ -360,7 +360,7 @@ function loadJsonIfExists(filePath) {
|
|
|
360
360
|
const cached = cacheGet(cacheKey);
|
|
361
361
|
if (cached !== undefined)
|
|
362
362
|
return cached;
|
|
363
|
-
if (!
|
|
363
|
+
if (!existsSync3(filePath)) {
|
|
364
364
|
cacheSet(cacheKey, null);
|
|
365
365
|
return null;
|
|
366
366
|
}
|
|
@@ -641,7 +641,7 @@ function isAbortError(err) {
|
|
|
641
641
|
return name === "AbortError" || name === "TimeoutError";
|
|
642
642
|
}
|
|
643
643
|
function defaultSleep(ms) {
|
|
644
|
-
return new Promise((
|
|
644
|
+
return new Promise((resolve5) => setTimeout(resolve5, ms));
|
|
645
645
|
}
|
|
646
646
|
function errorMessage2(err) {
|
|
647
647
|
return err instanceof Error ? err.message : String(err);
|
|
@@ -8025,11 +8025,11 @@ function shouldStopByStuck(history, cfg) {
|
|
|
8025
8025
|
async function withTimeout3(p, timeoutMs) {
|
|
8026
8026
|
if (timeoutMs <= 0)
|
|
8027
8027
|
return await p;
|
|
8028
|
-
return await new Promise((
|
|
8028
|
+
return await new Promise((resolve11, reject) => {
|
|
8029
8029
|
const timer = setTimeout(() => reject(new Error(`timeout after ${timeoutMs}ms`)), timeoutMs);
|
|
8030
8030
|
Promise.resolve(p).then((v) => {
|
|
8031
8031
|
clearTimeout(timer);
|
|
8032
|
-
|
|
8032
|
+
resolve11(v);
|
|
8033
8033
|
}, (err) => {
|
|
8034
8034
|
clearTimeout(timer);
|
|
8035
8035
|
reject(err);
|
|
@@ -8151,6 +8151,47 @@ var init_auto_feedback = __esm(() => {
|
|
|
8151
8151
|
// src/index.ts
|
|
8152
8152
|
init_opencode_plugin_helpers();
|
|
8153
8153
|
|
|
8154
|
+
// lib/dev-isolation.ts
|
|
8155
|
+
import { existsSync } from "node:fs";
|
|
8156
|
+
import { resolve, dirname } from "node:path";
|
|
8157
|
+
var MARKER_REL = ".codeforge/.dev-marker";
|
|
8158
|
+
var ENV_KEY = "CODEFORGE_DEV";
|
|
8159
|
+
function shouldYieldToLocalPlugin(opts = {}) {
|
|
8160
|
+
const env = opts.env ?? process.env;
|
|
8161
|
+
const fileExists = opts.fileExists ?? existsSync;
|
|
8162
|
+
const envVal = env[ENV_KEY];
|
|
8163
|
+
if (envVal === "1" || envVal === "true" || envVal === "yes") {
|
|
8164
|
+
return { yield: true, reason: "env" };
|
|
8165
|
+
}
|
|
8166
|
+
const startDir = opts.directory ? resolve(opts.directory) : process.cwd();
|
|
8167
|
+
const maxDepth = Math.max(1, opts.maxDepth ?? 20);
|
|
8168
|
+
let cur = startDir;
|
|
8169
|
+
for (let i = 0;i < maxDepth; i++) {
|
|
8170
|
+
const markerPath = resolve(cur, MARKER_REL);
|
|
8171
|
+
try {
|
|
8172
|
+
if (fileExists(markerPath)) {
|
|
8173
|
+
return { yield: true, reason: "marker", markerPath };
|
|
8174
|
+
}
|
|
8175
|
+
} catch {}
|
|
8176
|
+
const parent = dirname(cur);
|
|
8177
|
+
if (parent === cur)
|
|
8178
|
+
break;
|
|
8179
|
+
cur = parent;
|
|
8180
|
+
}
|
|
8181
|
+
return { yield: false, reason: null };
|
|
8182
|
+
}
|
|
8183
|
+
function formatYieldLog(result) {
|
|
8184
|
+
if (!result.yield)
|
|
8185
|
+
return "[codeforge] stable plugin 正常加载";
|
|
8186
|
+
if (result.reason === "env") {
|
|
8187
|
+
return "[codeforge] 检测到 CODEFORGE_DEV env,stable plugin 让位";
|
|
8188
|
+
}
|
|
8189
|
+
if (result.reason === "marker") {
|
|
8190
|
+
return `[codeforge] 检测到 dev marker (${result.markerPath}),stable plugin 让位`;
|
|
8191
|
+
}
|
|
8192
|
+
return "[codeforge] stable plugin 让位(未知原因)";
|
|
8193
|
+
}
|
|
8194
|
+
|
|
8154
8195
|
// plugins/agent-router.ts
|
|
8155
8196
|
init_opencode_plugin_helpers();
|
|
8156
8197
|
var PLUGIN_NAME = "agent-router";
|
|
@@ -8317,7 +8358,7 @@ init_opencode_plugin_helpers();
|
|
|
8317
8358
|
// lib/arena.ts
|
|
8318
8359
|
var DEFAULT_TIMEOUT = 90000;
|
|
8319
8360
|
async function withTimeout(p, timeoutMs, signal) {
|
|
8320
|
-
return new Promise((
|
|
8361
|
+
return new Promise((resolve2, reject) => {
|
|
8321
8362
|
if (signal?.aborted)
|
|
8322
8363
|
return reject(new Error("aborted"));
|
|
8323
8364
|
const onAbort = () => {
|
|
@@ -8335,7 +8376,7 @@ async function withTimeout(p, timeoutMs, signal) {
|
|
|
8335
8376
|
}
|
|
8336
8377
|
Promise.resolve().then(() => p).then((v) => {
|
|
8337
8378
|
cleanup();
|
|
8338
|
-
|
|
8379
|
+
resolve2(v);
|
|
8339
8380
|
}, (err) => {
|
|
8340
8381
|
cleanup();
|
|
8341
8382
|
reject(err);
|
|
@@ -8561,14 +8602,14 @@ async function git(opts, args) {
|
|
|
8561
8602
|
};
|
|
8562
8603
|
}
|
|
8563
8604
|
}
|
|
8564
|
-
function
|
|
8605
|
+
function resolve3(o) {
|
|
8565
8606
|
return {
|
|
8566
8607
|
root: path.resolve(o?.root ?? process.cwd()),
|
|
8567
8608
|
timeoutMs: o?.timeoutMs ?? DEFAULTS.timeoutMs
|
|
8568
8609
|
};
|
|
8569
8610
|
}
|
|
8570
8611
|
async function isGitRepo(o) {
|
|
8571
|
-
const opts =
|
|
8612
|
+
const opts = resolve3(o);
|
|
8572
8613
|
try {
|
|
8573
8614
|
await fs.access(path.join(opts.root, ".git"));
|
|
8574
8615
|
return true;
|
|
@@ -8577,7 +8618,7 @@ async function isGitRepo(o) {
|
|
|
8577
8618
|
}
|
|
8578
8619
|
}
|
|
8579
8620
|
async function listChanged(o) {
|
|
8580
|
-
const opts =
|
|
8621
|
+
const opts = resolve3(o);
|
|
8581
8622
|
if (!await isGitRepo(opts))
|
|
8582
8623
|
return [];
|
|
8583
8624
|
const r = await git(opts, ["status", "--porcelain"]);
|
|
@@ -8604,7 +8645,7 @@ ${input.body.trim()}
|
|
|
8604
8645
|
`;
|
|
8605
8646
|
}
|
|
8606
8647
|
async function commit(input, o) {
|
|
8607
|
-
const opts =
|
|
8648
|
+
const opts = resolve3(o);
|
|
8608
8649
|
if (!await isGitRepo(opts)) {
|
|
8609
8650
|
return { ok: false, reason: "not-a-git-repo" };
|
|
8610
8651
|
}
|
|
@@ -14731,10 +14772,303 @@ var modelFallbackServer = async (ctx) => {
|
|
|
14731
14772
|
};
|
|
14732
14773
|
var handler13 = modelFallbackServer;
|
|
14733
14774
|
|
|
14734
|
-
// plugins/
|
|
14775
|
+
// plugins/subtask-heartbeat.ts
|
|
14735
14776
|
init_opencode_plugin_helpers();
|
|
14736
|
-
var PLUGIN_NAME14 = "
|
|
14777
|
+
var PLUGIN_NAME14 = "subtask-heartbeat";
|
|
14737
14778
|
logLifecycle(PLUGIN_NAME14, "import", {});
|
|
14779
|
+
var HEARTBEAT_INTERVAL_MS2 = 30000;
|
|
14780
|
+
var HEARTBEAT_DEBOUNCE_MS = 25000;
|
|
14781
|
+
var TOAST_DURATION_MS3 = 5000;
|
|
14782
|
+
var START_TOAST_DURATION_MS = 2000;
|
|
14783
|
+
var inflight2 = new Map;
|
|
14784
|
+
function _snapshotInflight() {
|
|
14785
|
+
return [...inflight2.values()].map((r) => ({ ...r }));
|
|
14786
|
+
}
|
|
14787
|
+
function getInflightSnapshot() {
|
|
14788
|
+
return _snapshotInflight();
|
|
14789
|
+
}
|
|
14790
|
+
function extractCreatedChild(event) {
|
|
14791
|
+
if (!event || typeof event !== "object")
|
|
14792
|
+
return null;
|
|
14793
|
+
const e = event;
|
|
14794
|
+
if (e.type !== "session.created")
|
|
14795
|
+
return null;
|
|
14796
|
+
const info = e.properties?.info;
|
|
14797
|
+
if (!info || typeof info !== "object")
|
|
14798
|
+
return null;
|
|
14799
|
+
const session = info;
|
|
14800
|
+
if (typeof session.id !== "string")
|
|
14801
|
+
return null;
|
|
14802
|
+
if (typeof session.parentID !== "string" || session.parentID === "")
|
|
14803
|
+
return null;
|
|
14804
|
+
return { childID: session.id, parentID: session.parentID, agent: null };
|
|
14805
|
+
}
|
|
14806
|
+
function extractEndedSessionID(event) {
|
|
14807
|
+
if (!event || typeof event !== "object")
|
|
14808
|
+
return null;
|
|
14809
|
+
const e = event;
|
|
14810
|
+
if (typeof e.type !== "string")
|
|
14811
|
+
return null;
|
|
14812
|
+
if (e.type !== "session.idle" && e.type !== "session.deleted" && e.type !== "session.error") {
|
|
14813
|
+
return null;
|
|
14814
|
+
}
|
|
14815
|
+
const props = e.properties ?? {};
|
|
14816
|
+
const direct = props["sessionID"];
|
|
14817
|
+
if (typeof direct === "string" && direct)
|
|
14818
|
+
return { type: e.type, sessionID: direct };
|
|
14819
|
+
const info = props["info"];
|
|
14820
|
+
if (info && typeof info === "object") {
|
|
14821
|
+
const sid = info.id;
|
|
14822
|
+
if (typeof sid === "string" && sid)
|
|
14823
|
+
return { type: e.type, sessionID: sid };
|
|
14824
|
+
}
|
|
14825
|
+
return null;
|
|
14826
|
+
}
|
|
14827
|
+
function registerInflight(payload, now = Date.now()) {
|
|
14828
|
+
const r = {
|
|
14829
|
+
childID: payload.childID,
|
|
14830
|
+
parentID: payload.parentID,
|
|
14831
|
+
agent: payload.agent,
|
|
14832
|
+
startedAt: now,
|
|
14833
|
+
lastBeatAt: now,
|
|
14834
|
+
lastTool: null
|
|
14835
|
+
};
|
|
14836
|
+
inflight2.set(payload.childID, r);
|
|
14837
|
+
return r;
|
|
14838
|
+
}
|
|
14839
|
+
function recordToolBeat(sessionID, tool2, now = Date.now()) {
|
|
14840
|
+
const r = inflight2.get(sessionID);
|
|
14841
|
+
if (!r)
|
|
14842
|
+
return null;
|
|
14843
|
+
r.lastBeatAt = now;
|
|
14844
|
+
r.lastTool = tool2;
|
|
14845
|
+
return r;
|
|
14846
|
+
}
|
|
14847
|
+
function clearInflight2(sessionID) {
|
|
14848
|
+
const r = inflight2.get(sessionID);
|
|
14849
|
+
if (!r)
|
|
14850
|
+
return null;
|
|
14851
|
+
inflight2.delete(sessionID);
|
|
14852
|
+
return r;
|
|
14853
|
+
}
|
|
14854
|
+
function pickHeartbeats(now = Date.now()) {
|
|
14855
|
+
const out = [];
|
|
14856
|
+
for (const r of inflight2.values()) {
|
|
14857
|
+
if (now - r.lastBeatAt >= HEARTBEAT_DEBOUNCE_MS)
|
|
14858
|
+
out.push(r);
|
|
14859
|
+
}
|
|
14860
|
+
return out;
|
|
14861
|
+
}
|
|
14862
|
+
function fmtElapsed(ms) {
|
|
14863
|
+
const total = Math.max(0, Math.floor(ms / 1000));
|
|
14864
|
+
const m = Math.floor(total / 60);
|
|
14865
|
+
const s = total % 60;
|
|
14866
|
+
return m > 0 ? `${m}m${s.toString().padStart(2, "0")}s` : `${s}s`;
|
|
14867
|
+
}
|
|
14868
|
+
function buildStartToast(r) {
|
|
14869
|
+
const who = r.agent ?? "subagent";
|
|
14870
|
+
return {
|
|
14871
|
+
message: `\uD83D\uDE80 子 session 启动: ${who}`,
|
|
14872
|
+
variant: "info"
|
|
14873
|
+
};
|
|
14874
|
+
}
|
|
14875
|
+
function buildHeartbeatToast(r, now = Date.now()) {
|
|
14876
|
+
const who = r.agent ?? "subagent";
|
|
14877
|
+
const tool2 = r.lastTool ?? "thinking";
|
|
14878
|
+
return {
|
|
14879
|
+
message: `⏳ ${who} 仍在运行 ${fmtElapsed(now - r.startedAt)} | 当前: ${tool2}`,
|
|
14880
|
+
variant: "info"
|
|
14881
|
+
};
|
|
14882
|
+
}
|
|
14883
|
+
function buildEndToast(r, type, now = Date.now()) {
|
|
14884
|
+
const who = r.agent ?? "subagent";
|
|
14885
|
+
const elapsed = fmtElapsed(now - r.startedAt);
|
|
14886
|
+
if (type === "session.error") {
|
|
14887
|
+
return { message: `❌ ${who} 失败 (${elapsed})`, variant: "error" };
|
|
14888
|
+
}
|
|
14889
|
+
if (type === "session.deleted") {
|
|
14890
|
+
return { message: `\uD83D\uDDD1️ ${who} 被取消 (${elapsed})`, variant: "error" };
|
|
14891
|
+
}
|
|
14892
|
+
return { message: `✅ ${who} 完成 (${elapsed})`, variant: "success" };
|
|
14893
|
+
}
|
|
14894
|
+
function normalizeVariant3(raw) {
|
|
14895
|
+
if (raw === "info" || raw === "warning")
|
|
14896
|
+
return "default";
|
|
14897
|
+
return raw;
|
|
14898
|
+
}
|
|
14899
|
+
async function showToast3(client, payload, log7) {
|
|
14900
|
+
if (typeof client?.tui?.showToast !== "function") {
|
|
14901
|
+
log7?.debug?.("tui.showToast 不可用,noop");
|
|
14902
|
+
return false;
|
|
14903
|
+
}
|
|
14904
|
+
try {
|
|
14905
|
+
await client.tui.showToast({
|
|
14906
|
+
body: {
|
|
14907
|
+
message: payload.message,
|
|
14908
|
+
variant: normalizeVariant3(payload.variant),
|
|
14909
|
+
duration: payload.duration ?? TOAST_DURATION_MS3,
|
|
14910
|
+
title: payload.title ?? "CodeForge"
|
|
14911
|
+
}
|
|
14912
|
+
});
|
|
14913
|
+
return true;
|
|
14914
|
+
} catch (err) {
|
|
14915
|
+
log7?.warn("tui.showToast 抛错(已隔离)", {
|
|
14916
|
+
error: err instanceof Error ? err.message : String(err)
|
|
14917
|
+
});
|
|
14918
|
+
return false;
|
|
14919
|
+
}
|
|
14920
|
+
}
|
|
14921
|
+
var log7 = makePluginLogger(PLUGIN_NAME14);
|
|
14922
|
+
var subtaskHeartbeatServer = async (ctx) => {
|
|
14923
|
+
logLifecycle(PLUGIN_NAME14, "activate", {
|
|
14924
|
+
directory: ctx.directory,
|
|
14925
|
+
intervalMs: HEARTBEAT_INTERVAL_MS2
|
|
14926
|
+
});
|
|
14927
|
+
const client = ctx.client;
|
|
14928
|
+
const interval = setInterval(() => {
|
|
14929
|
+
safeAsync(PLUGIN_NAME14, "interval", async () => {
|
|
14930
|
+
const beats = pickHeartbeats();
|
|
14931
|
+
if (beats.length === 0)
|
|
14932
|
+
return;
|
|
14933
|
+
for (const r of beats) {
|
|
14934
|
+
const t = buildHeartbeatToast(r);
|
|
14935
|
+
const sent = await showToast3(client, t, log7);
|
|
14936
|
+
safeWriteLog(PLUGIN_NAME14, {
|
|
14937
|
+
hook: "interval",
|
|
14938
|
+
child: r.childID,
|
|
14939
|
+
parent: r.parentID,
|
|
14940
|
+
tool: r.lastTool,
|
|
14941
|
+
elapsed_ms: Date.now() - r.startedAt,
|
|
14942
|
+
toast_sent: sent
|
|
14943
|
+
});
|
|
14944
|
+
r.lastBeatAt = Date.now();
|
|
14945
|
+
}
|
|
14946
|
+
});
|
|
14947
|
+
}, HEARTBEAT_INTERVAL_MS2);
|
|
14948
|
+
if (typeof interval.unref === "function") {
|
|
14949
|
+
interval.unref();
|
|
14950
|
+
}
|
|
14951
|
+
return {
|
|
14952
|
+
event: async ({ event }) => {
|
|
14953
|
+
await safeAsync(PLUGIN_NAME14, "event", async () => {
|
|
14954
|
+
const created = extractCreatedChild(event);
|
|
14955
|
+
if (created) {
|
|
14956
|
+
const record = registerInflight(created);
|
|
14957
|
+
safeWriteLog(PLUGIN_NAME14, {
|
|
14958
|
+
hook: "event",
|
|
14959
|
+
type: "session.created",
|
|
14960
|
+
child: created.childID,
|
|
14961
|
+
parent: created.parentID
|
|
14962
|
+
});
|
|
14963
|
+
const startToast = buildStartToast(record);
|
|
14964
|
+
const sent = await showToast3(client, { ...startToast, duration: START_TOAST_DURATION_MS }, log7);
|
|
14965
|
+
safeWriteLog(PLUGIN_NAME14, {
|
|
14966
|
+
hook: "event",
|
|
14967
|
+
type: "session.created.toast",
|
|
14968
|
+
child: created.childID,
|
|
14969
|
+
toast_sent: sent
|
|
14970
|
+
});
|
|
14971
|
+
return;
|
|
14972
|
+
}
|
|
14973
|
+
const ended = extractEndedSessionID(event);
|
|
14974
|
+
if (ended) {
|
|
14975
|
+
const r = clearInflight2(ended.sessionID);
|
|
14976
|
+
if (r) {
|
|
14977
|
+
const t = buildEndToast(r, ended.type);
|
|
14978
|
+
const sent = await showToast3(client, t, log7);
|
|
14979
|
+
safeWriteLog(PLUGIN_NAME14, {
|
|
14980
|
+
hook: "event",
|
|
14981
|
+
type: ended.type,
|
|
14982
|
+
child: r.childID,
|
|
14983
|
+
elapsed_ms: Date.now() - r.startedAt,
|
|
14984
|
+
toast_sent: sent,
|
|
14985
|
+
end_toast_message: t.message
|
|
14986
|
+
});
|
|
14987
|
+
}
|
|
14988
|
+
}
|
|
14989
|
+
});
|
|
14990
|
+
},
|
|
14991
|
+
"tool.execute.before": async (input) => {
|
|
14992
|
+
await safeAsync(PLUGIN_NAME14, "tool.execute.before", async () => {
|
|
14993
|
+
if (!input || typeof input.sessionID !== "string" || typeof input.tool !== "string")
|
|
14994
|
+
return;
|
|
14995
|
+
recordToolBeat(input.sessionID, input.tool);
|
|
14996
|
+
});
|
|
14997
|
+
}
|
|
14998
|
+
};
|
|
14999
|
+
};
|
|
15000
|
+
var handler14 = subtaskHeartbeatServer;
|
|
15001
|
+
|
|
15002
|
+
// plugins/parallel-status.ts
|
|
15003
|
+
init_opencode_plugin_helpers();
|
|
15004
|
+
var PLUGIN_NAME15 = "parallel-status";
|
|
15005
|
+
logLifecycle(PLUGIN_NAME15, "import");
|
|
15006
|
+
var ID_MAX_LEN = 16;
|
|
15007
|
+
var ID_KEEP_LEN = 13;
|
|
15008
|
+
function shortId(s) {
|
|
15009
|
+
return s.length > ID_MAX_LEN ? s.slice(0, ID_KEEP_LEN) + "..." : s;
|
|
15010
|
+
}
|
|
15011
|
+
function formatElapsed(ms) {
|
|
15012
|
+
const total = Math.max(0, Math.floor(ms / 1000));
|
|
15013
|
+
const m = Math.floor(total / 60);
|
|
15014
|
+
const s = total % 60;
|
|
15015
|
+
return m > 0 ? `${m}m${s.toString().padStart(2, "0")}s` : `${s}s`;
|
|
15016
|
+
}
|
|
15017
|
+
function formatInflightMarkdown(snapshot, now = Date.now()) {
|
|
15018
|
+
if (snapshot.length === 0) {
|
|
15019
|
+
return "✅ 当前无 inflight subagent";
|
|
15020
|
+
}
|
|
15021
|
+
const lines = [];
|
|
15022
|
+
lines.push(`\uD83D\uDCCA 当前 ${snapshot.length} 个 subagent 在跑:`, "");
|
|
15023
|
+
lines.push("| # | child id | parent id | agent | 已跑 | 最近工具 |");
|
|
15024
|
+
lines.push("|---|----------|-----------|-------|------|----------|");
|
|
15025
|
+
snapshot.forEach((r, i) => {
|
|
15026
|
+
const elapsed = formatElapsed(now - r.startedAt);
|
|
15027
|
+
const agent = r.agent ?? "(unknown)";
|
|
15028
|
+
const tool2 = r.lastTool ?? "(thinking)";
|
|
15029
|
+
const child = shortId(r.childID);
|
|
15030
|
+
const parent = shortId(r.parentID);
|
|
15031
|
+
lines.push(`| ${i + 1} | \`${child}\` | \`${parent}\` | ${agent} | ${elapsed} | ${tool2} |`);
|
|
15032
|
+
});
|
|
15033
|
+
return lines.join(`
|
|
15034
|
+
`);
|
|
15035
|
+
}
|
|
15036
|
+
var parallelStatusServer = async (ctx) => {
|
|
15037
|
+
const log8 = makePluginLogger(PLUGIN_NAME15);
|
|
15038
|
+
logLifecycle(PLUGIN_NAME15, "activate", { directory: ctx.directory });
|
|
15039
|
+
return {
|
|
15040
|
+
"command.execute.before": async (input, output) => {
|
|
15041
|
+
try {
|
|
15042
|
+
if (!input || input.command !== "parallel-status")
|
|
15043
|
+
return;
|
|
15044
|
+
const snapshot = getInflightSnapshot();
|
|
15045
|
+
const text = formatInflightMarkdown(snapshot);
|
|
15046
|
+
if (Array.isArray(output?.parts)) {
|
|
15047
|
+
output.parts.length = 0;
|
|
15048
|
+
output.parts.push({
|
|
15049
|
+
id: `parallel-status-${Date.now()}`,
|
|
15050
|
+
sessionID: input.sessionID,
|
|
15051
|
+
messageID: "",
|
|
15052
|
+
type: "text",
|
|
15053
|
+
text,
|
|
15054
|
+
synthetic: false
|
|
15055
|
+
});
|
|
15056
|
+
}
|
|
15057
|
+
log8.info(`[${PLUGIN_NAME15}] 已回写 ${snapshot.length} 条 inflight`);
|
|
15058
|
+
} catch (err) {
|
|
15059
|
+
log8.error(`[${PLUGIN_NAME15}] command.execute.before 异常(已隔离)`, {
|
|
15060
|
+
error: err instanceof Error ? err.message : String(err)
|
|
15061
|
+
});
|
|
15062
|
+
}
|
|
15063
|
+
}
|
|
15064
|
+
};
|
|
15065
|
+
};
|
|
15066
|
+
var handler15 = parallelStatusServer;
|
|
15067
|
+
|
|
15068
|
+
// plugins/pwsh-utf8.ts
|
|
15069
|
+
init_opencode_plugin_helpers();
|
|
15070
|
+
var PLUGIN_NAME16 = "pwsh-utf8";
|
|
15071
|
+
logLifecycle(PLUGIN_NAME16, "import", {});
|
|
14738
15072
|
var PRELUDE = "chcp 65001 *> $null; " + "[Console]::OutputEncoding = [System.Text.UTF8Encoding]::new(); " + "$OutputEncoding = [System.Text.UTF8Encoding]::new(); ";
|
|
14739
15073
|
function prependUtf8Prelude(command) {
|
|
14740
15074
|
if (typeof command !== "string")
|
|
@@ -14747,14 +15081,14 @@ function prependUtf8Prelude(command) {
|
|
|
14747
15081
|
return command;
|
|
14748
15082
|
return PRELUDE + command;
|
|
14749
15083
|
}
|
|
14750
|
-
var
|
|
15084
|
+
var handler16 = async (_ctx) => {
|
|
14751
15085
|
const enabled = process.platform === "win32" && process.env.CODEFORGE_DISABLE_PWSH_UTF8 !== "1";
|
|
14752
|
-
logLifecycle(
|
|
15086
|
+
logLifecycle(PLUGIN_NAME16, "activate", { enabled, platform: process.platform });
|
|
14753
15087
|
if (!enabled)
|
|
14754
15088
|
return {};
|
|
14755
15089
|
return {
|
|
14756
15090
|
"tool.execute.before": async (input, output) => {
|
|
14757
|
-
await safeAsync(
|
|
15091
|
+
await safeAsync(PLUGIN_NAME16, "tool.execute.before", async () => {
|
|
14758
15092
|
if (input.tool !== "bash")
|
|
14759
15093
|
return;
|
|
14760
15094
|
const args = output.args ?? {};
|
|
@@ -14763,7 +15097,7 @@ var handler14 = async (_ctx) => {
|
|
|
14763
15097
|
if (next !== undefined && next !== original) {
|
|
14764
15098
|
args["command"] = next;
|
|
14765
15099
|
output.args = args;
|
|
14766
|
-
safeWriteLog(
|
|
15100
|
+
safeWriteLog(PLUGIN_NAME16, {
|
|
14767
15101
|
hook: "tool.execute.before",
|
|
14768
15102
|
tool: input.tool,
|
|
14769
15103
|
callID: input.callID,
|
|
@@ -14956,7 +15290,7 @@ function buildRecoveryPlan(events, meta = { id: null }, opts = {}) {
|
|
|
14956
15290
|
});
|
|
14957
15291
|
}
|
|
14958
15292
|
}
|
|
14959
|
-
const
|
|
15293
|
+
const inflight3 = [...toolMap.values()].sort((a, b) => b.last_ts - a.last_ts);
|
|
14960
15294
|
const proposed = window.some((t) => PENDING_CHANGES_TOOLS.has(t.tool));
|
|
14961
15295
|
const applied = window.some((t) => APPLY_TOOLS.has(t.tool) && t.ok);
|
|
14962
15296
|
const pending_changes_likely = proposed && !applied;
|
|
@@ -14980,7 +15314,7 @@ function buildRecoveryPlan(events, meta = { id: null }, opts = {}) {
|
|
|
14980
15314
|
idleMs,
|
|
14981
15315
|
lastUser,
|
|
14982
15316
|
lastAgent,
|
|
14983
|
-
inflight:
|
|
15317
|
+
inflight: inflight3,
|
|
14984
15318
|
pending_changes_likely,
|
|
14985
15319
|
open_subtasks_likely,
|
|
14986
15320
|
reason
|
|
@@ -14991,7 +15325,7 @@ function buildRecoveryPlan(events, meta = { id: null }, opts = {}) {
|
|
|
14991
15325
|
idle_ms: idleMs,
|
|
14992
15326
|
last_user_intent: lastUser,
|
|
14993
15327
|
last_agent: lastAgent,
|
|
14994
|
-
inflight_tools:
|
|
15328
|
+
inflight_tools: inflight3,
|
|
14995
15329
|
pending_changes_likely,
|
|
14996
15330
|
open_subtasks_likely,
|
|
14997
15331
|
summary
|
|
@@ -15096,8 +15430,8 @@ function isRecoveryWorthShowing(plan) {
|
|
|
15096
15430
|
}
|
|
15097
15431
|
|
|
15098
15432
|
// plugins/session-recovery.ts
|
|
15099
|
-
var
|
|
15100
|
-
logLifecycle(
|
|
15433
|
+
var PLUGIN_NAME17 = "session-recovery";
|
|
15434
|
+
logLifecycle(PLUGIN_NAME17, "import", {});
|
|
15101
15435
|
async function processSessionStart(currentSessionId, opts = {}) {
|
|
15102
15436
|
if (opts.disabled) {
|
|
15103
15437
|
return { ok: true, injected: false, reason: "disabled" };
|
|
@@ -15107,7 +15441,7 @@ async function processSessionStart(currentSessionId, opts = {}) {
|
|
|
15107
15441
|
excludeIds.add(currentSessionId);
|
|
15108
15442
|
const r = await scanLastSession({ ...opts, excludeIds: [...excludeIds] });
|
|
15109
15443
|
if (!r.ok) {
|
|
15110
|
-
opts.log?.warn?.(`[${
|
|
15444
|
+
opts.log?.warn?.(`[${PLUGIN_NAME17}] 扫描失败:${r.error}`);
|
|
15111
15445
|
return { ok: false, injected: false, reason: "scan_error", error: r.error };
|
|
15112
15446
|
}
|
|
15113
15447
|
const plan = r.plan;
|
|
@@ -15121,7 +15455,7 @@ async function processSessionStart(currentSessionId, opts = {}) {
|
|
|
15121
15455
|
await opts.injectRecovery(injection);
|
|
15122
15456
|
} catch (err) {
|
|
15123
15457
|
const msg = err instanceof Error ? err.message : String(err);
|
|
15124
|
-
opts.log?.warn?.(`[${
|
|
15458
|
+
opts.log?.warn?.(`[${PLUGIN_NAME17}] injectRecovery 异常:${msg}`);
|
|
15125
15459
|
return { ok: false, injected: false, plan, reason: "inject_error", error: msg };
|
|
15126
15460
|
}
|
|
15127
15461
|
}
|
|
@@ -15150,13 +15484,13 @@ function renderPrompt(plan) {
|
|
|
15150
15484
|
return lines.join(`
|
|
15151
15485
|
`);
|
|
15152
15486
|
}
|
|
15153
|
-
var
|
|
15487
|
+
var log8 = makePluginLogger(PLUGIN_NAME17);
|
|
15154
15488
|
var _lastInjection = null;
|
|
15155
15489
|
var sessionRecoveryServer = async (ctx) => {
|
|
15156
|
-
logLifecycle(
|
|
15490
|
+
logLifecycle(PLUGIN_NAME17, "activate", { directory: ctx.directory });
|
|
15157
15491
|
return {
|
|
15158
15492
|
event: async ({ event }) => {
|
|
15159
|
-
await safeAsync(
|
|
15493
|
+
await safeAsync(PLUGIN_NAME17, "event", async () => {
|
|
15160
15494
|
const e = event;
|
|
15161
15495
|
if (!e || typeof e.type !== "string")
|
|
15162
15496
|
return;
|
|
@@ -15166,12 +15500,12 @@ var sessionRecoveryServer = async (ctx) => {
|
|
|
15166
15500
|
const root = typeof e.properties?.["root"] === "string" ? e.properties["root"] : ctx.directory ?? process.cwd();
|
|
15167
15501
|
const r = await processSessionStart(sid, {
|
|
15168
15502
|
root,
|
|
15169
|
-
log:
|
|
15503
|
+
log: log8,
|
|
15170
15504
|
injectRecovery: (inj) => {
|
|
15171
15505
|
_lastInjection = inj;
|
|
15172
15506
|
}
|
|
15173
15507
|
});
|
|
15174
|
-
safeWriteLog(
|
|
15508
|
+
safeWriteLog(PLUGIN_NAME17, {
|
|
15175
15509
|
hook: "event",
|
|
15176
15510
|
type: "session.start",
|
|
15177
15511
|
ok: r.ok,
|
|
@@ -15180,234 +15514,13 @@ var sessionRecoveryServer = async (ctx) => {
|
|
|
15180
15514
|
last_session_id: r.plan?.last_session_id
|
|
15181
15515
|
});
|
|
15182
15516
|
if (r.injected && r.plan) {
|
|
15183
|
-
|
|
15184
|
-
}
|
|
15185
|
-
});
|
|
15186
|
-
}
|
|
15187
|
-
};
|
|
15188
|
-
};
|
|
15189
|
-
var handler15 = sessionRecoveryServer;
|
|
15190
|
-
|
|
15191
|
-
// plugins/subtask-heartbeat.ts
|
|
15192
|
-
init_opencode_plugin_helpers();
|
|
15193
|
-
var PLUGIN_NAME16 = "subtask-heartbeat";
|
|
15194
|
-
logLifecycle(PLUGIN_NAME16, "import", {});
|
|
15195
|
-
var HEARTBEAT_INTERVAL_MS2 = 30000;
|
|
15196
|
-
var HEARTBEAT_DEBOUNCE_MS = 25000;
|
|
15197
|
-
var TOAST_DURATION_MS3 = 5000;
|
|
15198
|
-
var START_TOAST_DURATION_MS = 2000;
|
|
15199
|
-
var inflight2 = new Map;
|
|
15200
|
-
function extractCreatedChild(event) {
|
|
15201
|
-
if (!event || typeof event !== "object")
|
|
15202
|
-
return null;
|
|
15203
|
-
const e = event;
|
|
15204
|
-
if (e.type !== "session.created")
|
|
15205
|
-
return null;
|
|
15206
|
-
const info = e.properties?.info;
|
|
15207
|
-
if (!info || typeof info !== "object")
|
|
15208
|
-
return null;
|
|
15209
|
-
const session = info;
|
|
15210
|
-
if (typeof session.id !== "string")
|
|
15211
|
-
return null;
|
|
15212
|
-
if (typeof session.parentID !== "string" || session.parentID === "")
|
|
15213
|
-
return null;
|
|
15214
|
-
return { childID: session.id, parentID: session.parentID, agent: null };
|
|
15215
|
-
}
|
|
15216
|
-
function extractEndedSessionID(event) {
|
|
15217
|
-
if (!event || typeof event !== "object")
|
|
15218
|
-
return null;
|
|
15219
|
-
const e = event;
|
|
15220
|
-
if (typeof e.type !== "string")
|
|
15221
|
-
return null;
|
|
15222
|
-
if (e.type !== "session.idle" && e.type !== "session.deleted" && e.type !== "session.error") {
|
|
15223
|
-
return null;
|
|
15224
|
-
}
|
|
15225
|
-
const props = e.properties ?? {};
|
|
15226
|
-
const direct = props["sessionID"];
|
|
15227
|
-
if (typeof direct === "string" && direct)
|
|
15228
|
-
return { type: e.type, sessionID: direct };
|
|
15229
|
-
const info = props["info"];
|
|
15230
|
-
if (info && typeof info === "object") {
|
|
15231
|
-
const sid = info.id;
|
|
15232
|
-
if (typeof sid === "string" && sid)
|
|
15233
|
-
return { type: e.type, sessionID: sid };
|
|
15234
|
-
}
|
|
15235
|
-
return null;
|
|
15236
|
-
}
|
|
15237
|
-
function registerInflight(payload, now = Date.now()) {
|
|
15238
|
-
const r = {
|
|
15239
|
-
childID: payload.childID,
|
|
15240
|
-
parentID: payload.parentID,
|
|
15241
|
-
agent: payload.agent,
|
|
15242
|
-
startedAt: now,
|
|
15243
|
-
lastBeatAt: now,
|
|
15244
|
-
lastTool: null
|
|
15245
|
-
};
|
|
15246
|
-
inflight2.set(payload.childID, r);
|
|
15247
|
-
return r;
|
|
15248
|
-
}
|
|
15249
|
-
function recordToolBeat(sessionID, tool2, now = Date.now()) {
|
|
15250
|
-
const r = inflight2.get(sessionID);
|
|
15251
|
-
if (!r)
|
|
15252
|
-
return null;
|
|
15253
|
-
r.lastBeatAt = now;
|
|
15254
|
-
r.lastTool = tool2;
|
|
15255
|
-
return r;
|
|
15256
|
-
}
|
|
15257
|
-
function clearInflight2(sessionID) {
|
|
15258
|
-
const r = inflight2.get(sessionID);
|
|
15259
|
-
if (!r)
|
|
15260
|
-
return null;
|
|
15261
|
-
inflight2.delete(sessionID);
|
|
15262
|
-
return r;
|
|
15263
|
-
}
|
|
15264
|
-
function pickHeartbeats(now = Date.now()) {
|
|
15265
|
-
const out = [];
|
|
15266
|
-
for (const r of inflight2.values()) {
|
|
15267
|
-
if (now - r.lastBeatAt >= HEARTBEAT_DEBOUNCE_MS)
|
|
15268
|
-
out.push(r);
|
|
15269
|
-
}
|
|
15270
|
-
return out;
|
|
15271
|
-
}
|
|
15272
|
-
function fmtElapsed(ms) {
|
|
15273
|
-
const total = Math.max(0, Math.floor(ms / 1000));
|
|
15274
|
-
const m = Math.floor(total / 60);
|
|
15275
|
-
const s = total % 60;
|
|
15276
|
-
return m > 0 ? `${m}m${s.toString().padStart(2, "0")}s` : `${s}s`;
|
|
15277
|
-
}
|
|
15278
|
-
function buildStartToast(r) {
|
|
15279
|
-
const who = r.agent ?? "subagent";
|
|
15280
|
-
return {
|
|
15281
|
-
message: `\uD83D\uDE80 子 session 启动: ${who}`,
|
|
15282
|
-
variant: "info"
|
|
15283
|
-
};
|
|
15284
|
-
}
|
|
15285
|
-
function buildHeartbeatToast(r, now = Date.now()) {
|
|
15286
|
-
const who = r.agent ?? "subagent";
|
|
15287
|
-
const tool2 = r.lastTool ?? "thinking";
|
|
15288
|
-
return {
|
|
15289
|
-
message: `⏳ ${who} 仍在运行 ${fmtElapsed(now - r.startedAt)} | 当前: ${tool2}`,
|
|
15290
|
-
variant: "info"
|
|
15291
|
-
};
|
|
15292
|
-
}
|
|
15293
|
-
function buildEndToast(r, type, now = Date.now()) {
|
|
15294
|
-
const who = r.agent ?? "subagent";
|
|
15295
|
-
const elapsed = fmtElapsed(now - r.startedAt);
|
|
15296
|
-
if (type === "session.error") {
|
|
15297
|
-
return { message: `❌ ${who} 失败 (${elapsed})`, variant: "error" };
|
|
15298
|
-
}
|
|
15299
|
-
if (type === "session.deleted") {
|
|
15300
|
-
return { message: `\uD83D\uDDD1️ ${who} 被取消 (${elapsed})`, variant: "error" };
|
|
15301
|
-
}
|
|
15302
|
-
return { message: `✅ ${who} 完成 (${elapsed})`, variant: "success" };
|
|
15303
|
-
}
|
|
15304
|
-
function normalizeVariant3(raw) {
|
|
15305
|
-
if (raw === "info" || raw === "warning")
|
|
15306
|
-
return "default";
|
|
15307
|
-
return raw;
|
|
15308
|
-
}
|
|
15309
|
-
async function showToast3(client, payload, log8) {
|
|
15310
|
-
if (typeof client?.tui?.showToast !== "function") {
|
|
15311
|
-
log8?.debug?.("tui.showToast 不可用,noop");
|
|
15312
|
-
return false;
|
|
15313
|
-
}
|
|
15314
|
-
try {
|
|
15315
|
-
await client.tui.showToast({
|
|
15316
|
-
body: {
|
|
15317
|
-
message: payload.message,
|
|
15318
|
-
variant: normalizeVariant3(payload.variant),
|
|
15319
|
-
duration: payload.duration ?? TOAST_DURATION_MS3,
|
|
15320
|
-
title: payload.title ?? "CodeForge"
|
|
15321
|
-
}
|
|
15322
|
-
});
|
|
15323
|
-
return true;
|
|
15324
|
-
} catch (err) {
|
|
15325
|
-
log8?.warn("tui.showToast 抛错(已隔离)", {
|
|
15326
|
-
error: err instanceof Error ? err.message : String(err)
|
|
15327
|
-
});
|
|
15328
|
-
return false;
|
|
15329
|
-
}
|
|
15330
|
-
}
|
|
15331
|
-
var log8 = makePluginLogger(PLUGIN_NAME16);
|
|
15332
|
-
var subtaskHeartbeatServer = async (ctx) => {
|
|
15333
|
-
logLifecycle(PLUGIN_NAME16, "activate", {
|
|
15334
|
-
directory: ctx.directory,
|
|
15335
|
-
intervalMs: HEARTBEAT_INTERVAL_MS2
|
|
15336
|
-
});
|
|
15337
|
-
const client = ctx.client;
|
|
15338
|
-
const interval = setInterval(() => {
|
|
15339
|
-
safeAsync(PLUGIN_NAME16, "interval", async () => {
|
|
15340
|
-
const beats = pickHeartbeats();
|
|
15341
|
-
if (beats.length === 0)
|
|
15342
|
-
return;
|
|
15343
|
-
for (const r of beats) {
|
|
15344
|
-
const t = buildHeartbeatToast(r);
|
|
15345
|
-
const sent = await showToast3(client, t, log8);
|
|
15346
|
-
safeWriteLog(PLUGIN_NAME16, {
|
|
15347
|
-
hook: "interval",
|
|
15348
|
-
child: r.childID,
|
|
15349
|
-
parent: r.parentID,
|
|
15350
|
-
tool: r.lastTool,
|
|
15351
|
-
elapsed_ms: Date.now() - r.startedAt,
|
|
15352
|
-
toast_sent: sent
|
|
15353
|
-
});
|
|
15354
|
-
r.lastBeatAt = Date.now();
|
|
15355
|
-
}
|
|
15356
|
-
});
|
|
15357
|
-
}, HEARTBEAT_INTERVAL_MS2);
|
|
15358
|
-
if (typeof interval.unref === "function") {
|
|
15359
|
-
interval.unref();
|
|
15360
|
-
}
|
|
15361
|
-
return {
|
|
15362
|
-
event: async ({ event }) => {
|
|
15363
|
-
await safeAsync(PLUGIN_NAME16, "event", async () => {
|
|
15364
|
-
const created = extractCreatedChild(event);
|
|
15365
|
-
if (created) {
|
|
15366
|
-
const record = registerInflight(created);
|
|
15367
|
-
safeWriteLog(PLUGIN_NAME16, {
|
|
15368
|
-
hook: "event",
|
|
15369
|
-
type: "session.created",
|
|
15370
|
-
child: created.childID,
|
|
15371
|
-
parent: created.parentID
|
|
15372
|
-
});
|
|
15373
|
-
const startToast = buildStartToast(record);
|
|
15374
|
-
const sent = await showToast3(client, { ...startToast, duration: START_TOAST_DURATION_MS }, log8);
|
|
15375
|
-
safeWriteLog(PLUGIN_NAME16, {
|
|
15376
|
-
hook: "event",
|
|
15377
|
-
type: "session.created.toast",
|
|
15378
|
-
child: created.childID,
|
|
15379
|
-
toast_sent: sent
|
|
15380
|
-
});
|
|
15381
|
-
return;
|
|
15517
|
+
log8.info(`[${PLUGIN_NAME17}] 注入恢复提示(last=${r.plan.last_session_id?.slice(0, 8) ?? "?"}, reason=${r.plan.reason})`);
|
|
15382
15518
|
}
|
|
15383
|
-
const ended = extractEndedSessionID(event);
|
|
15384
|
-
if (ended) {
|
|
15385
|
-
const r = clearInflight2(ended.sessionID);
|
|
15386
|
-
if (r) {
|
|
15387
|
-
const t = buildEndToast(r, ended.type);
|
|
15388
|
-
const sent = await showToast3(client, t, log8);
|
|
15389
|
-
safeWriteLog(PLUGIN_NAME16, {
|
|
15390
|
-
hook: "event",
|
|
15391
|
-
type: ended.type,
|
|
15392
|
-
child: r.childID,
|
|
15393
|
-
elapsed_ms: Date.now() - r.startedAt,
|
|
15394
|
-
toast_sent: sent,
|
|
15395
|
-
end_toast_message: t.message
|
|
15396
|
-
});
|
|
15397
|
-
}
|
|
15398
|
-
}
|
|
15399
|
-
});
|
|
15400
|
-
},
|
|
15401
|
-
"tool.execute.before": async (input) => {
|
|
15402
|
-
await safeAsync(PLUGIN_NAME16, "tool.execute.before", async () => {
|
|
15403
|
-
if (!input || typeof input.sessionID !== "string" || typeof input.tool !== "string")
|
|
15404
|
-
return;
|
|
15405
|
-
recordToolBeat(input.sessionID, input.tool);
|
|
15406
15519
|
});
|
|
15407
15520
|
}
|
|
15408
15521
|
};
|
|
15409
15522
|
};
|
|
15410
|
-
var
|
|
15523
|
+
var handler17 = sessionRecoveryServer;
|
|
15411
15524
|
|
|
15412
15525
|
// plugins/subtasks.ts
|
|
15413
15526
|
import { promises as fs10 } from "node:fs";
|
|
@@ -15437,6 +15550,18 @@ async function schedule(opts) {
|
|
|
15437
15550
|
const results = new Array(opts.subtasks.length);
|
|
15438
15551
|
let nextIdx = 0;
|
|
15439
15552
|
const workers = [];
|
|
15553
|
+
const fireFinish = async (i, res) => {
|
|
15554
|
+
if (!opts.onSubtaskFinish)
|
|
15555
|
+
return;
|
|
15556
|
+
try {
|
|
15557
|
+
await opts.onSubtaskFinish(res, i);
|
|
15558
|
+
} catch (err) {
|
|
15559
|
+
log9("warn", `[parallel] onSubtaskFinish 抛错(已隔离)`, {
|
|
15560
|
+
id: res.id,
|
|
15561
|
+
error: describe4(err)
|
|
15562
|
+
});
|
|
15563
|
+
}
|
|
15564
|
+
};
|
|
15440
15565
|
const runOne = async (i, spec) => {
|
|
15441
15566
|
const subStart = now();
|
|
15442
15567
|
let alloc;
|
|
@@ -15444,7 +15569,7 @@ async function schedule(opts) {
|
|
|
15444
15569
|
try {
|
|
15445
15570
|
alloc = await opts.deps.allocateWorktree(spec.id);
|
|
15446
15571
|
} catch (err) {
|
|
15447
|
-
|
|
15572
|
+
const res2 = {
|
|
15448
15573
|
id: spec.id,
|
|
15449
15574
|
ok: false,
|
|
15450
15575
|
summary: clamp(`worktree 分配失败:${describe4(err)}`, limit),
|
|
@@ -15452,9 +15577,21 @@ async function schedule(opts) {
|
|
|
15452
15577
|
duration_ms: now() - subStart,
|
|
15453
15578
|
error: describe4(err)
|
|
15454
15579
|
};
|
|
15580
|
+
results[i] = res2;
|
|
15581
|
+
await fireFinish(i, res2);
|
|
15455
15582
|
return;
|
|
15456
15583
|
}
|
|
15457
15584
|
}
|
|
15585
|
+
if (opts.onSubtaskStart) {
|
|
15586
|
+
try {
|
|
15587
|
+
await opts.onSubtaskStart(spec, i);
|
|
15588
|
+
} catch (err) {
|
|
15589
|
+
log9("warn", `[parallel] onSubtaskStart 抛错(已隔离)`, {
|
|
15590
|
+
id: spec.id,
|
|
15591
|
+
error: describe4(err)
|
|
15592
|
+
});
|
|
15593
|
+
}
|
|
15594
|
+
}
|
|
15458
15595
|
const ctl = new AbortController;
|
|
15459
15596
|
const cascade = () => ctl.abort();
|
|
15460
15597
|
if (globalCtl.signal.aborted)
|
|
@@ -15507,6 +15644,7 @@ async function schedule(opts) {
|
|
|
15507
15644
|
}
|
|
15508
15645
|
}
|
|
15509
15646
|
results[i] = res;
|
|
15647
|
+
await fireFinish(i, res);
|
|
15510
15648
|
};
|
|
15511
15649
|
const launch = async () => {
|
|
15512
15650
|
while (true) {
|
|
@@ -15515,13 +15653,15 @@ async function schedule(opts) {
|
|
|
15515
15653
|
return;
|
|
15516
15654
|
if (globalCtl.signal.aborted) {
|
|
15517
15655
|
const spec = opts.subtasks[i];
|
|
15518
|
-
|
|
15656
|
+
const res = {
|
|
15519
15657
|
id: spec.id,
|
|
15520
15658
|
ok: false,
|
|
15521
15659
|
summary: clamp("调度器已取消,未启动", limit),
|
|
15522
15660
|
status: "cancelled",
|
|
15523
15661
|
duration_ms: 0
|
|
15524
15662
|
};
|
|
15663
|
+
results[i] = res;
|
|
15664
|
+
await fireFinish(i, res);
|
|
15525
15665
|
continue;
|
|
15526
15666
|
}
|
|
15527
15667
|
await runOne(i, opts.subtasks[i]);
|
|
@@ -15802,20 +15942,20 @@ async function withTimeout2(p, ms, signal) {
|
|
|
15802
15942
|
throw err;
|
|
15803
15943
|
}
|
|
15804
15944
|
}
|
|
15805
|
-
return await new Promise((
|
|
15945
|
+
return await new Promise((resolve11, reject) => {
|
|
15806
15946
|
let settled = false;
|
|
15807
15947
|
const timer = setTimeout(() => {
|
|
15808
15948
|
if (settled)
|
|
15809
15949
|
return;
|
|
15810
15950
|
settled = true;
|
|
15811
|
-
|
|
15951
|
+
resolve11({ kind: "timeout" });
|
|
15812
15952
|
}, ms);
|
|
15813
15953
|
const onAbort = () => {
|
|
15814
15954
|
if (settled)
|
|
15815
15955
|
return;
|
|
15816
15956
|
settled = true;
|
|
15817
15957
|
clearTimeout(timer);
|
|
15818
|
-
|
|
15958
|
+
resolve11({ kind: "aborted" });
|
|
15819
15959
|
};
|
|
15820
15960
|
signal?.addEventListener("abort", onAbort, { once: true });
|
|
15821
15961
|
p.then((value) => {
|
|
@@ -15824,7 +15964,7 @@ async function withTimeout2(p, ms, signal) {
|
|
|
15824
15964
|
settled = true;
|
|
15825
15965
|
clearTimeout(timer);
|
|
15826
15966
|
signal?.removeEventListener("abort", onAbort);
|
|
15827
|
-
|
|
15967
|
+
resolve11({ kind: "ok", value });
|
|
15828
15968
|
}, (err) => {
|
|
15829
15969
|
if (settled)
|
|
15830
15970
|
return;
|
|
@@ -15860,11 +16000,48 @@ function clip4(s, max) {
|
|
|
15860
16000
|
return "";
|
|
15861
16001
|
return s.length <= max ? s : s.slice(0, max - 1) + "…";
|
|
15862
16002
|
}
|
|
16003
|
+
async function sendParentNotice(client, sessionID, text, opts = {}) {
|
|
16004
|
+
const log9 = opts.log ?? (() => {});
|
|
16005
|
+
if (!client?.session) {
|
|
16006
|
+
log9("warn", "[sendParentNotice] client.session 不可用,noop");
|
|
16007
|
+
return false;
|
|
16008
|
+
}
|
|
16009
|
+
const sessionAny = client.session;
|
|
16010
|
+
if (typeof sessionAny.promptAsync !== "function") {
|
|
16011
|
+
log9("warn", "[sendParentNotice] promptAsync 不可用(SDK 太老?),noop");
|
|
16012
|
+
return false;
|
|
16013
|
+
}
|
|
16014
|
+
try {
|
|
16015
|
+
const res = await sessionAny.promptAsync({
|
|
16016
|
+
sessionID,
|
|
16017
|
+
directory: opts.directory,
|
|
16018
|
+
noReply: true,
|
|
16019
|
+
parts: [
|
|
16020
|
+
{
|
|
16021
|
+
type: "text",
|
|
16022
|
+
text,
|
|
16023
|
+
synthetic: true,
|
|
16024
|
+
ignored: true
|
|
16025
|
+
}
|
|
16026
|
+
]
|
|
16027
|
+
});
|
|
16028
|
+
if (res && typeof res === "object" && "error" in res && res.error) {
|
|
16029
|
+
log9("warn", "[sendParentNotice] promptAsync 返回 error", { error: res.error });
|
|
16030
|
+
return false;
|
|
16031
|
+
}
|
|
16032
|
+
return true;
|
|
16033
|
+
} catch (err) {
|
|
16034
|
+
log9("warn", "[sendParentNotice] 抛错(已隔离)", {
|
|
16035
|
+
error: err instanceof Error ? err.message : String(err)
|
|
16036
|
+
});
|
|
16037
|
+
return false;
|
|
16038
|
+
}
|
|
16039
|
+
}
|
|
15863
16040
|
|
|
15864
16041
|
// plugins/subtasks.ts
|
|
15865
16042
|
init_opencode_plugin_helpers();
|
|
15866
16043
|
init_runtime_paths();
|
|
15867
|
-
var
|
|
16044
|
+
var PLUGIN_NAME18 = "subtasks";
|
|
15868
16045
|
function getLogFile(root = process.cwd()) {
|
|
15869
16046
|
return path13.join(runtimeDir(root), "logs", "subtasks.log");
|
|
15870
16047
|
}
|
|
@@ -15943,18 +16120,49 @@ async function handleParallelCommand(raw) {
|
|
|
15943
16120
|
specs = splitDescriptions(args.description, { parentId });
|
|
15944
16121
|
}
|
|
15945
16122
|
if (specs.length === 0) {
|
|
15946
|
-
log9?.warn(`[${
|
|
16123
|
+
log9?.warn(`[${PLUGIN_NAME18}] /parallel 缺有效子任务`);
|
|
15947
16124
|
await safeReply2(ctx, "⚠ /parallel 需要至少 1 个子任务(用 ; 或换行分隔)");
|
|
15948
16125
|
return { ok: false, reason: "no_subtasks" };
|
|
15949
16126
|
}
|
|
16127
|
+
const maxConcurrency = clampInt(args.maxConcurrency, 1, 16, 4);
|
|
16128
|
+
const totalTimeoutMs = clampInt(args.totalTimeout_ms, 1000, 60 * 60000, 30 * 60000);
|
|
16129
|
+
const canNotice = Boolean(ctx.client && ctx.parentSessionID);
|
|
16130
|
+
if (canNotice) {
|
|
16131
|
+
const lines = [
|
|
16132
|
+
`\uD83D\uDE80 /parallel 已派出 ${specs.length} 个子任务(并发=${maxConcurrency}):`,
|
|
16133
|
+
...specs.map((s, i) => ` ${i + 1}. \`${s.id}\` — ${s.description}`),
|
|
16134
|
+
"",
|
|
16135
|
+
"\uD83D\uDCA1 用 /parallel-status 随时查进度;TUI 按 Ctrl+→ 进子 session 看实时。"
|
|
16136
|
+
];
|
|
16137
|
+
await sendParentNotice(ctx.client, ctx.parentSessionID, lines.join(`
|
|
16138
|
+
`), {
|
|
16139
|
+
directory: ctx.directory,
|
|
16140
|
+
log: (lvl, msg, data) => {
|
|
16141
|
+
if (lvl === "error")
|
|
16142
|
+
log9?.error(msg, data);
|
|
16143
|
+
else if (lvl === "warn")
|
|
16144
|
+
log9?.warn(msg, data);
|
|
16145
|
+
else
|
|
16146
|
+
log9?.info(msg, data);
|
|
16147
|
+
}
|
|
16148
|
+
});
|
|
16149
|
+
}
|
|
15950
16150
|
const runner = ctx.runner ?? mockRunner;
|
|
15951
16151
|
let result;
|
|
15952
16152
|
try {
|
|
15953
16153
|
result = await schedule({
|
|
15954
16154
|
parentId,
|
|
15955
16155
|
subtasks: specs,
|
|
15956
|
-
maxConcurrency
|
|
15957
|
-
totalTimeout_ms:
|
|
16156
|
+
maxConcurrency,
|
|
16157
|
+
totalTimeout_ms: totalTimeoutMs,
|
|
16158
|
+
onSubtaskStart: canNotice ? async (spec, idx) => {
|
|
16159
|
+
await sendParentNotice(ctx.client, ctx.parentSessionID, `▶ 子任务 ${idx + 1}/${specs.length} 启动: \`${spec.id}\` — ${spec.description}`, { directory: ctx.directory });
|
|
16160
|
+
} : undefined,
|
|
16161
|
+
onSubtaskFinish: canNotice ? async (res, idx) => {
|
|
16162
|
+
const emoji = res.status === "success" ? "✅" : res.status === "need_review" ? "⚠" : res.status === "timeout" ? "⏱" : res.status === "cancelled" ? "\uD83D\uDDD1" : "❌";
|
|
16163
|
+
const fileCount = res.changedFiles?.length ?? 0;
|
|
16164
|
+
await sendParentNotice(ctx.client, ctx.parentSessionID, `${emoji} 子任务 ${idx + 1}/${specs.length} 完成 [${res.status}] \`${res.id}\` (${res.duration_ms}ms, files=${fileCount})`, { directory: ctx.directory });
|
|
16165
|
+
} : undefined,
|
|
15958
16166
|
deps: {
|
|
15959
16167
|
runSubtask: runner,
|
|
15960
16168
|
allocateWorktree: ctx.allocateWorktree,
|
|
@@ -15970,7 +16178,7 @@ async function handleParallelCommand(raw) {
|
|
|
15970
16178
|
});
|
|
15971
16179
|
} catch (err) {
|
|
15972
16180
|
const msg = err instanceof Error ? err.message : String(err);
|
|
15973
|
-
log9?.error(`[${
|
|
16181
|
+
log9?.error(`[${PLUGIN_NAME18}] schedule 抛错`, { error: msg });
|
|
15974
16182
|
await safeReply2(ctx, `❌ 并发调度失败:${msg}`);
|
|
15975
16183
|
return { ok: false, reason: msg };
|
|
15976
16184
|
}
|
|
@@ -15978,7 +16186,7 @@ async function handleParallelCommand(raw) {
|
|
|
15978
16186
|
const summaryLines = [head, "", "```", result.digest.text, "```"];
|
|
15979
16187
|
await safeReply2(ctx, summaryLines.join(`
|
|
15980
16188
|
`));
|
|
15981
|
-
log9?.info(`[${
|
|
16189
|
+
log9?.info(`[${PLUGIN_NAME18}] schedule 完成`, {
|
|
15982
16190
|
parentId,
|
|
15983
16191
|
success: result.digest.success,
|
|
15984
16192
|
failed: result.digest.failed,
|
|
@@ -15988,7 +16196,7 @@ async function handleParallelCommand(raw) {
|
|
|
15988
16196
|
try {
|
|
15989
16197
|
await ctx.onCompleted(result);
|
|
15990
16198
|
} catch (err) {
|
|
15991
|
-
log9?.warn(`[${
|
|
16199
|
+
log9?.warn(`[${PLUGIN_NAME18}] onCompleted hook 抛错(已隔离)`, {
|
|
15992
16200
|
error: err instanceof Error ? err.message : String(err)
|
|
15993
16201
|
});
|
|
15994
16202
|
}
|
|
@@ -16019,14 +16227,17 @@ async function maybeHandleMessage(raw) {
|
|
|
16019
16227
|
reply: ctx.reply,
|
|
16020
16228
|
runner: ctx.runner,
|
|
16021
16229
|
allocateWorktree: ctx.allocateWorktree,
|
|
16022
|
-
log: ctx.log
|
|
16230
|
+
log: ctx.log,
|
|
16231
|
+
client: ctx.client,
|
|
16232
|
+
parentSessionID: ctx.parentSessionID,
|
|
16233
|
+
directory: ctx.directory
|
|
16023
16234
|
});
|
|
16024
16235
|
}
|
|
16025
16236
|
async function writeLog(level, msg, data) {
|
|
16026
16237
|
const line = JSON.stringify({
|
|
16027
16238
|
ts: new Date().toISOString(),
|
|
16028
16239
|
level,
|
|
16029
|
-
plugin:
|
|
16240
|
+
plugin: PLUGIN_NAME18,
|
|
16030
16241
|
msg,
|
|
16031
16242
|
data
|
|
16032
16243
|
}) + `
|
|
@@ -16037,11 +16248,11 @@ async function writeLog(level, msg, data) {
|
|
|
16037
16248
|
await fs10.appendFile(logFile, line, "utf8");
|
|
16038
16249
|
} catch {}
|
|
16039
16250
|
}
|
|
16040
|
-
logLifecycle(
|
|
16251
|
+
logLifecycle(PLUGIN_NAME18, "import");
|
|
16041
16252
|
var subtasksServer = async (ctx) => {
|
|
16042
|
-
const log9 = makePluginLogger(
|
|
16253
|
+
const log9 = makePluginLogger(PLUGIN_NAME18);
|
|
16043
16254
|
const client = ctx?.client ?? undefined;
|
|
16044
|
-
logLifecycle(
|
|
16255
|
+
logLifecycle(PLUGIN_NAME18, "activate", {
|
|
16045
16256
|
directory: ctx.directory,
|
|
16046
16257
|
hasClient: Boolean(client)
|
|
16047
16258
|
});
|
|
@@ -16062,6 +16273,9 @@ var subtasksServer = async (ctx) => {
|
|
|
16062
16273
|
return Promise.resolve();
|
|
16063
16274
|
},
|
|
16064
16275
|
log: log9,
|
|
16276
|
+
client,
|
|
16277
|
+
parentSessionID: input.sessionID,
|
|
16278
|
+
directory: ctx.directory,
|
|
16065
16279
|
runner: client ? makeOpencodeRunner({
|
|
16066
16280
|
client,
|
|
16067
16281
|
parentSessionID: input.sessionID,
|
|
@@ -16098,20 +16312,20 @@ var subtasksServer = async (ctx) => {
|
|
|
16098
16312
|
});
|
|
16099
16313
|
}
|
|
16100
16314
|
} catch (err) {
|
|
16101
|
-
log9.error(`[${
|
|
16315
|
+
log9.error(`[${PLUGIN_NAME18}] command.execute.before 异常(已隔离)`, {
|
|
16102
16316
|
error: err instanceof Error ? err.message : String(err)
|
|
16103
16317
|
});
|
|
16104
16318
|
}
|
|
16105
16319
|
}
|
|
16106
16320
|
};
|
|
16107
16321
|
};
|
|
16108
|
-
var
|
|
16322
|
+
var handler18 = subtasksServer;
|
|
16109
16323
|
|
|
16110
16324
|
// plugins/terminal-monitor.ts
|
|
16111
16325
|
init_opencode_plugin_helpers();
|
|
16112
16326
|
import * as crypto5 from "node:crypto";
|
|
16113
|
-
var
|
|
16114
|
-
logLifecycle(
|
|
16327
|
+
var PLUGIN_NAME19 = "terminal-monitor";
|
|
16328
|
+
logLifecycle(PLUGIN_NAME19, "import", {});
|
|
16115
16329
|
var DEFAULT_CONFIG7 = {
|
|
16116
16330
|
minScore: 0.6,
|
|
16117
16331
|
cooldownMs: 30000,
|
|
@@ -16393,17 +16607,17 @@ function describeError(err) {
|
|
|
16393
16607
|
return String(err);
|
|
16394
16608
|
}
|
|
16395
16609
|
}
|
|
16396
|
-
var log9 = makePluginLogger(
|
|
16610
|
+
var log9 = makePluginLogger(PLUGIN_NAME19);
|
|
16397
16611
|
var lru = new FingerprintLRU2;
|
|
16398
16612
|
var _lastNotification = null;
|
|
16399
16613
|
var terminalMonitorServer = async (ctx) => {
|
|
16400
|
-
logLifecycle(
|
|
16614
|
+
logLifecycle(PLUGIN_NAME19, "activate", {
|
|
16401
16615
|
directory: ctx.directory,
|
|
16402
16616
|
triggerEventTypes: ["terminal.output", "terminal.exit"]
|
|
16403
16617
|
});
|
|
16404
16618
|
return {
|
|
16405
16619
|
event: async ({ event }) => {
|
|
16406
|
-
await safeAsync(
|
|
16620
|
+
await safeAsync(PLUGIN_NAME19, "event", async () => {
|
|
16407
16621
|
const e = event;
|
|
16408
16622
|
if (!e || typeof e.type !== "string")
|
|
16409
16623
|
return;
|
|
@@ -16417,7 +16631,7 @@ var terminalMonitorServer = async (ctx) => {
|
|
|
16417
16631
|
_lastNotification = msg;
|
|
16418
16632
|
}
|
|
16419
16633
|
});
|
|
16420
|
-
safeWriteLog(
|
|
16634
|
+
safeWriteLog(PLUGIN_NAME19, {
|
|
16421
16635
|
hook: "event",
|
|
16422
16636
|
type: e.type,
|
|
16423
16637
|
notified: r.notified,
|
|
@@ -16425,18 +16639,18 @@ var terminalMonitorServer = async (ctx) => {
|
|
|
16425
16639
|
findings: r.notification?.findings.length ?? 0
|
|
16426
16640
|
});
|
|
16427
16641
|
if (r.notified && r.notification) {
|
|
16428
|
-
log9.info(`[${
|
|
16642
|
+
log9.info(`[${PLUGIN_NAME19}] 反馈 ${r.notification.findings.length} 条 → ${r.notification.summary}`);
|
|
16429
16643
|
}
|
|
16430
16644
|
});
|
|
16431
16645
|
}
|
|
16432
16646
|
};
|
|
16433
16647
|
};
|
|
16434
|
-
var
|
|
16648
|
+
var handler19 = terminalMonitorServer;
|
|
16435
16649
|
|
|
16436
16650
|
// plugins/token-manager.ts
|
|
16437
16651
|
init_opencode_plugin_helpers();
|
|
16438
|
-
var
|
|
16439
|
-
logLifecycle(
|
|
16652
|
+
var PLUGIN_NAME20 = "token-manager";
|
|
16653
|
+
logLifecycle(PLUGIN_NAME20, "import", {});
|
|
16440
16654
|
async function handleMessageBefore(raw, log10, defaults) {
|
|
16441
16655
|
const ctx = raw ?? {};
|
|
16442
16656
|
if (!Array.isArray(ctx.messages) || ctx.messages.length === 0)
|
|
@@ -16456,21 +16670,21 @@ async function handleMessageBefore(raw, log10, defaults) {
|
|
|
16456
16670
|
};
|
|
16457
16671
|
if (r.compressed) {
|
|
16458
16672
|
ctx.messages = r.messages;
|
|
16459
|
-
log10?.info(`[${
|
|
16673
|
+
log10?.info(`[${PLUGIN_NAME20}] 压缩 ${r.before.count}→${r.after.count} 条 / ${r.before.tokens}→${r.after.tokens} tokens`);
|
|
16460
16674
|
}
|
|
16461
16675
|
return r;
|
|
16462
16676
|
} catch (err) {
|
|
16463
|
-
log10?.warn(`[${
|
|
16677
|
+
log10?.warn(`[${PLUGIN_NAME20}] 压缩异常(已隔离)`, {
|
|
16464
16678
|
error: err instanceof Error ? err.message : String(err)
|
|
16465
16679
|
});
|
|
16466
16680
|
return null;
|
|
16467
16681
|
}
|
|
16468
16682
|
}
|
|
16469
|
-
var log10 = makePluginLogger(
|
|
16683
|
+
var log10 = makePluginLogger(PLUGIN_NAME20);
|
|
16470
16684
|
var tokenManagerServer = async (ctx) => {
|
|
16471
16685
|
const rt = loadRuntimeSync();
|
|
16472
16686
|
const threshold = rt.runtime.context.condenser_threshold_ratio;
|
|
16473
|
-
logLifecycle(
|
|
16687
|
+
logLifecycle(PLUGIN_NAME20, "activate", {
|
|
16474
16688
|
directory: ctx.directory,
|
|
16475
16689
|
threshold,
|
|
16476
16690
|
target: DEFAULT_CONDENSE.target,
|
|
@@ -16479,7 +16693,7 @@ var tokenManagerServer = async (ctx) => {
|
|
|
16479
16693
|
});
|
|
16480
16694
|
return {
|
|
16481
16695
|
"experimental.chat.messages.transform": async (_input, output) => {
|
|
16482
|
-
await safeAsync(
|
|
16696
|
+
await safeAsync(PLUGIN_NAME20, "experimental.chat.messages.transform", async () => {
|
|
16483
16697
|
const list = output.messages;
|
|
16484
16698
|
if (!Array.isArray(list) || list.length === 0)
|
|
16485
16699
|
return;
|
|
@@ -16492,7 +16706,7 @@ var tokenManagerServer = async (ctx) => {
|
|
|
16492
16706
|
const r = await handleMessageBefore({ messages: flat }, log10, { threshold });
|
|
16493
16707
|
if (!r)
|
|
16494
16708
|
return;
|
|
16495
|
-
safeWriteLog(
|
|
16709
|
+
safeWriteLog(PLUGIN_NAME20, {
|
|
16496
16710
|
hook: "experimental.chat.messages.transform",
|
|
16497
16711
|
mode: "observe-only",
|
|
16498
16712
|
before_msgs: r.before.count,
|
|
@@ -16503,13 +16717,13 @@ var tokenManagerServer = async (ctx) => {
|
|
|
16503
16717
|
reason: r.reason
|
|
16504
16718
|
});
|
|
16505
16719
|
if (r.compressed) {
|
|
16506
|
-
log10.warn(`[${
|
|
16720
|
+
log10.warn(`[${PLUGIN_NAME20}] 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)`);
|
|
16507
16721
|
}
|
|
16508
16722
|
});
|
|
16509
16723
|
}
|
|
16510
16724
|
};
|
|
16511
16725
|
};
|
|
16512
|
-
var
|
|
16726
|
+
var handler20 = tokenManagerServer;
|
|
16513
16727
|
|
|
16514
16728
|
// plugins/tool-policy.ts
|
|
16515
16729
|
init_opencode_plugin_helpers();
|
|
@@ -16752,8 +16966,8 @@ function checkFileAccess(acl, file, op) {
|
|
|
16752
16966
|
}
|
|
16753
16967
|
|
|
16754
16968
|
// plugins/tool-policy.ts
|
|
16755
|
-
var
|
|
16756
|
-
logLifecycle(
|
|
16969
|
+
var PLUGIN_NAME21 = "tool-policy";
|
|
16970
|
+
logLifecycle(PLUGIN_NAME21, "import", {});
|
|
16757
16971
|
var EMPTY_ACL = { whitelistMode: false };
|
|
16758
16972
|
function decideToolCall(ctx, cfg = {}) {
|
|
16759
16973
|
const fallbackMode = cfg.defaultMode ?? DEFAULT_RUNTIME.autonomy.default_mode;
|
|
@@ -16805,13 +17019,13 @@ function classifyToolKind(toolName) {
|
|
|
16805
17019
|
return "webfetch";
|
|
16806
17020
|
return "other";
|
|
16807
17021
|
}
|
|
16808
|
-
var log11 = makePluginLogger(
|
|
17022
|
+
var log11 = makePluginLogger(PLUGIN_NAME21);
|
|
16809
17023
|
var toolPolicyServer = async (ctx) => {
|
|
16810
17024
|
const directory = ctx.directory ?? process.cwd();
|
|
16811
17025
|
const cfg = await loadPolicy(directory);
|
|
16812
17026
|
const rt = loadRuntimeSync();
|
|
16813
17027
|
cfg.defaultMode = rt.runtime.autonomy.default_mode;
|
|
16814
|
-
logLifecycle(
|
|
17028
|
+
logLifecycle(PLUGIN_NAME21, "activate", {
|
|
16815
17029
|
directory,
|
|
16816
17030
|
acl_loaded: !!cfg.acl,
|
|
16817
17031
|
default_mode: cfg.defaultMode,
|
|
@@ -16819,7 +17033,7 @@ var toolPolicyServer = async (ctx) => {
|
|
|
16819
17033
|
});
|
|
16820
17034
|
return {
|
|
16821
17035
|
"tool.execute.before": async (input, output) => {
|
|
16822
|
-
await safeAsync(
|
|
17036
|
+
await safeAsync(PLUGIN_NAME21, "tool.execute.before", async () => {
|
|
16823
17037
|
const toolName = input.tool;
|
|
16824
17038
|
const argsObj = output.args ?? {};
|
|
16825
17039
|
const files = [];
|
|
@@ -16835,7 +17049,7 @@ var toolPolicyServer = async (ctx) => {
|
|
|
16835
17049
|
mode: cfg.defaultMode,
|
|
16836
17050
|
files: files.length ? files : undefined
|
|
16837
17051
|
}, cfg);
|
|
16838
|
-
safeWriteLog(
|
|
17052
|
+
safeWriteLog(PLUGIN_NAME21, {
|
|
16839
17053
|
hook: "tool.execute.before",
|
|
16840
17054
|
tool: toolName,
|
|
16841
17055
|
callID: input.callID,
|
|
@@ -16844,19 +17058,19 @@ var toolPolicyServer = async (ctx) => {
|
|
|
16844
17058
|
reasons: decision.reasons
|
|
16845
17059
|
});
|
|
16846
17060
|
if (decision.action === "deny") {
|
|
16847
|
-
log11.warn(`[${
|
|
17061
|
+
log11.warn(`[${PLUGIN_NAME21}] DENY ${toolName}: ${decision.reasons.join("; ")}`);
|
|
16848
17062
|
} else if (decision.action === "confirm") {
|
|
16849
|
-
log11.info(`[${
|
|
17063
|
+
log11.info(`[${PLUGIN_NAME21}] CONFIRM ${toolName}: ${decision.reasons.join("; ")}`);
|
|
16850
17064
|
}
|
|
16851
17065
|
});
|
|
16852
17066
|
}
|
|
16853
17067
|
};
|
|
16854
17068
|
};
|
|
16855
|
-
var
|
|
17069
|
+
var handler21 = toolPolicyServer;
|
|
16856
17070
|
|
|
16857
17071
|
// plugins/update-checker.ts
|
|
16858
17072
|
init_opencode_plugin_helpers();
|
|
16859
|
-
import { existsSync as
|
|
17073
|
+
import { existsSync as existsSync5 } from "node:fs";
|
|
16860
17074
|
import { homedir as homedir7 } from "node:os";
|
|
16861
17075
|
import { join as join15 } from "node:path";
|
|
16862
17076
|
|
|
@@ -16864,7 +17078,7 @@ import { join as join15 } from "node:path";
|
|
|
16864
17078
|
import { createHash as createHash6 } from "node:crypto";
|
|
16865
17079
|
import {
|
|
16866
17080
|
copyFileSync,
|
|
16867
|
-
existsSync as
|
|
17081
|
+
existsSync as existsSync4,
|
|
16868
17082
|
mkdirSync as mkdirSync3,
|
|
16869
17083
|
mkdtempSync,
|
|
16870
17084
|
readFileSync as readFileSync4,
|
|
@@ -16875,7 +17089,7 @@ import {
|
|
|
16875
17089
|
writeFileSync as writeFileSync2
|
|
16876
17090
|
} from "node:fs";
|
|
16877
17091
|
import { homedir as homedir6, tmpdir } from "node:os";
|
|
16878
|
-
import { dirname as
|
|
17092
|
+
import { dirname as dirname7, join as join14 } from "node:path";
|
|
16879
17093
|
import { fileURLToPath } from "node:url";
|
|
16880
17094
|
import * as https from "node:https";
|
|
16881
17095
|
import * as zlib from "node:zlib";
|
|
@@ -16883,7 +17097,7 @@ import * as zlib from "node:zlib";
|
|
|
16883
17097
|
// lib/version-injected.ts
|
|
16884
17098
|
function getInjectedVersion() {
|
|
16885
17099
|
try {
|
|
16886
|
-
const v = "0.3.
|
|
17100
|
+
const v = "0.3.8";
|
|
16887
17101
|
if (typeof v === "string" && /^\d+\.\d+\.\d+/.test(v)) {
|
|
16888
17102
|
return v;
|
|
16889
17103
|
}
|
|
@@ -16972,7 +17186,7 @@ function readLocalVersion() {
|
|
|
16972
17186
|
return injected;
|
|
16973
17187
|
try {
|
|
16974
17188
|
const here = fileURLToPath(import.meta.url);
|
|
16975
|
-
const root =
|
|
17189
|
+
const root = dirname7(dirname7(here));
|
|
16976
17190
|
const pkg = JSON.parse(readFileSync4(join14(root, "package.json"), "utf8"));
|
|
16977
17191
|
return typeof pkg.version === "string" ? pkg.version : "0.0.0";
|
|
16978
17192
|
} catch {
|
|
@@ -16987,7 +17201,7 @@ function defaultCacheFile() {
|
|
|
16987
17201
|
}
|
|
16988
17202
|
function readCache(file) {
|
|
16989
17203
|
try {
|
|
16990
|
-
if (!
|
|
17204
|
+
if (!existsSync4(file))
|
|
16991
17205
|
return null;
|
|
16992
17206
|
const raw = readFileSync4(file, "utf8");
|
|
16993
17207
|
const obj = JSON.parse(raw);
|
|
@@ -17001,7 +17215,7 @@ function readCache(file) {
|
|
|
17001
17215
|
}
|
|
17002
17216
|
function writeCache(file, entry) {
|
|
17003
17217
|
try {
|
|
17004
|
-
mkdirSync3(
|
|
17218
|
+
mkdirSync3(dirname7(file), { recursive: true });
|
|
17005
17219
|
writeFileSync2(file, JSON.stringify(entry, null, 2), "utf8");
|
|
17006
17220
|
} catch {}
|
|
17007
17221
|
}
|
|
@@ -17020,7 +17234,7 @@ function fetchLatestTagFromGitHub(repo) {
|
|
|
17020
17234
|
});
|
|
17021
17235
|
}
|
|
17022
17236
|
function getJsonWithRedirect(url, hopsLeft) {
|
|
17023
|
-
return new Promise((
|
|
17237
|
+
return new Promise((resolve11, reject) => {
|
|
17024
17238
|
const u = new URL(url);
|
|
17025
17239
|
const headers = {
|
|
17026
17240
|
"User-Agent": "codeforge-update-checker",
|
|
@@ -17044,12 +17258,12 @@ function getJsonWithRedirect(url, hopsLeft) {
|
|
|
17044
17258
|
return;
|
|
17045
17259
|
}
|
|
17046
17260
|
const next = new URL(res.headers.location, url).toString();
|
|
17047
|
-
getJsonWithRedirect(next, hopsLeft - 1).then(
|
|
17261
|
+
getJsonWithRedirect(next, hopsLeft - 1).then(resolve11, reject);
|
|
17048
17262
|
return;
|
|
17049
17263
|
}
|
|
17050
17264
|
if (status === 404) {
|
|
17051
17265
|
res.resume();
|
|
17052
|
-
|
|
17266
|
+
resolve11(null);
|
|
17053
17267
|
return;
|
|
17054
17268
|
}
|
|
17055
17269
|
if (status >= 400) {
|
|
@@ -17060,7 +17274,7 @@ function getJsonWithRedirect(url, hopsLeft) {
|
|
|
17060
17274
|
let body = "";
|
|
17061
17275
|
res.setEncoding("utf8");
|
|
17062
17276
|
res.on("data", (chunk) => body += chunk);
|
|
17063
|
-
res.on("end", () =>
|
|
17277
|
+
res.on("end", () => resolve11(body));
|
|
17064
17278
|
});
|
|
17065
17279
|
req.on("timeout", () => {
|
|
17066
17280
|
req.destroy();
|
|
@@ -17100,7 +17314,7 @@ async function fetchLatestFromNpm(opts) {
|
|
|
17100
17314
|
return { version, tarballUrl, integrity };
|
|
17101
17315
|
}
|
|
17102
17316
|
function defaultHttpFetcher(url, timeoutMs) {
|
|
17103
|
-
return new Promise((
|
|
17317
|
+
return new Promise((resolve11, reject) => {
|
|
17104
17318
|
const u = new URL(url);
|
|
17105
17319
|
const headers = {
|
|
17106
17320
|
"User-Agent": "codeforge-update-checker",
|
|
@@ -17117,7 +17331,7 @@ function defaultHttpFetcher(url, timeoutMs) {
|
|
|
17117
17331
|
const status = res.statusCode ?? 0;
|
|
17118
17332
|
if (status === 404) {
|
|
17119
17333
|
res.resume();
|
|
17120
|
-
|
|
17334
|
+
resolve11(null);
|
|
17121
17335
|
return;
|
|
17122
17336
|
}
|
|
17123
17337
|
if (status >= 400) {
|
|
@@ -17128,7 +17342,7 @@ function defaultHttpFetcher(url, timeoutMs) {
|
|
|
17128
17342
|
let body = "";
|
|
17129
17343
|
res.setEncoding("utf8");
|
|
17130
17344
|
res.on("data", (chunk) => body += chunk);
|
|
17131
|
-
res.on("end", () =>
|
|
17345
|
+
res.on("end", () => resolve11(body));
|
|
17132
17346
|
});
|
|
17133
17347
|
req.on("timeout", () => {
|
|
17134
17348
|
req.destroy();
|
|
@@ -17147,7 +17361,7 @@ async function downloadAndExtractBundle(opts) {
|
|
|
17147
17361
|
const tarBuf = zlib.gunzipSync(tarballBuf);
|
|
17148
17362
|
extractTarToDir(tarBuf, tmpRoot);
|
|
17149
17363
|
const bundlePath = join14(tmpRoot, "package", "dist", "index.js");
|
|
17150
|
-
if (!
|
|
17364
|
+
if (!existsSync4(bundlePath)) {
|
|
17151
17365
|
throw new Error(`bundle_not_found: ${bundlePath}`);
|
|
17152
17366
|
}
|
|
17153
17367
|
return { bundlePath, extractDir: tmpRoot };
|
|
@@ -17187,7 +17401,7 @@ function extractTarToDir(tarBuf, destRoot) {
|
|
|
17187
17401
|
if (typeFlag === "0" || typeFlag === "" || typeFlag === "\x00") {
|
|
17188
17402
|
const fileBuf = tarBuf.subarray(offset, offset + size);
|
|
17189
17403
|
const dest = join14(destRoot, fullName);
|
|
17190
|
-
mkdirSync3(
|
|
17404
|
+
mkdirSync3(dirname7(dest), { recursive: true });
|
|
17191
17405
|
writeFileSync2(dest, fileBuf);
|
|
17192
17406
|
} else if (typeFlag === "5") {
|
|
17193
17407
|
mkdirSync3(join14(destRoot, fullName), { recursive: true });
|
|
@@ -17199,7 +17413,7 @@ function defaultBinaryFetcher(url) {
|
|
|
17199
17413
|
return downloadBinary(url, 3);
|
|
17200
17414
|
}
|
|
17201
17415
|
function downloadBinary(url, hopsLeft) {
|
|
17202
|
-
return new Promise((
|
|
17416
|
+
return new Promise((resolve11, reject) => {
|
|
17203
17417
|
const u = new URL(url);
|
|
17204
17418
|
const req = https.request({
|
|
17205
17419
|
host: u.hostname,
|
|
@@ -17217,7 +17431,7 @@ function downloadBinary(url, hopsLeft) {
|
|
|
17217
17431
|
return;
|
|
17218
17432
|
}
|
|
17219
17433
|
const next = new URL(res.headers.location, url).toString();
|
|
17220
|
-
downloadBinary(next, hopsLeft - 1).then(
|
|
17434
|
+
downloadBinary(next, hopsLeft - 1).then(resolve11, reject);
|
|
17221
17435
|
return;
|
|
17222
17436
|
}
|
|
17223
17437
|
if (status >= 400) {
|
|
@@ -17227,7 +17441,7 @@ function downloadBinary(url, hopsLeft) {
|
|
|
17227
17441
|
}
|
|
17228
17442
|
const chunks = [];
|
|
17229
17443
|
res.on("data", (chunk) => chunks.push(chunk));
|
|
17230
|
-
res.on("end", () =>
|
|
17444
|
+
res.on("end", () => resolve11(Buffer.concat(chunks)));
|
|
17231
17445
|
});
|
|
17232
17446
|
req.on("timeout", () => {
|
|
17233
17447
|
req.destroy();
|
|
@@ -17240,16 +17454,16 @@ function downloadBinary(url, hopsLeft) {
|
|
|
17240
17454
|
function atomicReplaceBundle(opts) {
|
|
17241
17455
|
const { source, target, oldVersion } = opts;
|
|
17242
17456
|
const keep = opts.keepBackups ?? 3;
|
|
17243
|
-
if (!
|
|
17457
|
+
if (!existsSync4(source)) {
|
|
17244
17458
|
throw new Error(`atomic_source_missing: ${source}`);
|
|
17245
17459
|
}
|
|
17246
|
-
mkdirSync3(
|
|
17460
|
+
mkdirSync3(dirname7(target), { recursive: true });
|
|
17247
17461
|
const newPath = `${target}.new`;
|
|
17248
17462
|
const backupPath = `${target}.bak.${oldVersion}`;
|
|
17249
17463
|
let strategy = "rename";
|
|
17250
17464
|
try {
|
|
17251
17465
|
copyFileSync(source, newPath);
|
|
17252
|
-
if (
|
|
17466
|
+
if (existsSync4(target)) {
|
|
17253
17467
|
try {
|
|
17254
17468
|
renameSync(target, backupPath);
|
|
17255
17469
|
} catch (e) {
|
|
@@ -17285,7 +17499,7 @@ function atomicReplaceBundle(opts) {
|
|
|
17285
17499
|
return { backupPath, strategy };
|
|
17286
17500
|
} catch (e) {
|
|
17287
17501
|
try {
|
|
17288
|
-
if (
|
|
17502
|
+
if (existsSync4(newPath))
|
|
17289
17503
|
unlinkSync(newPath);
|
|
17290
17504
|
} catch {}
|
|
17291
17505
|
throw e;
|
|
@@ -17295,7 +17509,7 @@ function cleanupOldBackups(target, keep) {
|
|
|
17295
17509
|
if (keep <= 0)
|
|
17296
17510
|
return;
|
|
17297
17511
|
try {
|
|
17298
|
-
const dir =
|
|
17512
|
+
const dir = dirname7(target);
|
|
17299
17513
|
const base = target.substring(dir.length + 1);
|
|
17300
17514
|
const prefix = `${base}.bak.`;
|
|
17301
17515
|
const all = readdirSync(dir).filter((f) => f.startsWith(prefix)).map((f) => {
|
|
@@ -17323,7 +17537,7 @@ function loadCompatibility(opts) {
|
|
|
17323
17537
|
return null;
|
|
17324
17538
|
file = join14(root, "compatibility.json");
|
|
17325
17539
|
}
|
|
17326
|
-
if (!
|
|
17540
|
+
if (!existsSync4(file))
|
|
17327
17541
|
return null;
|
|
17328
17542
|
const raw = readFileSync4(file, "utf8");
|
|
17329
17543
|
const obj = JSON.parse(raw);
|
|
@@ -17346,7 +17560,7 @@ function loadCompatibility(opts) {
|
|
|
17346
17560
|
function inferPluginRoot() {
|
|
17347
17561
|
try {
|
|
17348
17562
|
const here = fileURLToPath(import.meta.url);
|
|
17349
|
-
return
|
|
17563
|
+
return dirname7(dirname7(here));
|
|
17350
17564
|
} catch {
|
|
17351
17565
|
return null;
|
|
17352
17566
|
}
|
|
@@ -17386,13 +17600,29 @@ function compareOpencodeVersion(opts) {
|
|
|
17386
17600
|
}
|
|
17387
17601
|
|
|
17388
17602
|
// plugins/update-checker.ts
|
|
17389
|
-
var
|
|
17603
|
+
var PLUGIN_NAME22 = "update-checker";
|
|
17390
17604
|
var PLUGIN_VERSION2 = "2.0.0";
|
|
17391
|
-
logLifecycle(
|
|
17605
|
+
logLifecycle(PLUGIN_NAME22, "import", { version: PLUGIN_VERSION2 });
|
|
17392
17606
|
var updateCheckerServer = async (ctx) => {
|
|
17607
|
+
const yieldResult = shouldYieldToLocalPlugin({ directory: ctx.directory });
|
|
17608
|
+
if (yieldResult.yield) {
|
|
17609
|
+
safeWriteLog(PLUGIN_NAME22, {
|
|
17610
|
+
level: "info",
|
|
17611
|
+
msg: "dev_mode_yield_skip",
|
|
17612
|
+
reason: yieldResult.reason,
|
|
17613
|
+
markerPath: yieldResult.markerPath,
|
|
17614
|
+
detail: formatYieldLog(yieldResult)
|
|
17615
|
+
});
|
|
17616
|
+
logLifecycle(PLUGIN_NAME22, "activate", {
|
|
17617
|
+
yield_to_local: true,
|
|
17618
|
+
yield_reason: yieldResult.reason,
|
|
17619
|
+
skipped: "auto_install + background_check"
|
|
17620
|
+
});
|
|
17621
|
+
return {};
|
|
17622
|
+
}
|
|
17393
17623
|
const rt = loadRuntimeSync();
|
|
17394
17624
|
const u = rt.runtime.update;
|
|
17395
|
-
logLifecycle(
|
|
17625
|
+
logLifecycle(PLUGIN_NAME22, "activate", {
|
|
17396
17626
|
version: PLUGIN_VERSION2,
|
|
17397
17627
|
auto_check_enabled: u.auto_check_enabled,
|
|
17398
17628
|
interval_hours: u.interval_hours,
|
|
@@ -17404,14 +17634,14 @@ var updateCheckerServer = async (ctx) => {
|
|
|
17404
17634
|
repo_fallback: u.repo,
|
|
17405
17635
|
config_source: "codeforge.json"
|
|
17406
17636
|
});
|
|
17407
|
-
await safeAsync(
|
|
17637
|
+
await safeAsync(PLUGIN_NAME22, "opencode_version_check", async () => {
|
|
17408
17638
|
const compat = loadCompatibility();
|
|
17409
17639
|
const opencodeVer = detectOpencodeVersion();
|
|
17410
17640
|
const verdict = compareOpencodeVersion({
|
|
17411
17641
|
currentOpencodeVer: opencodeVer,
|
|
17412
17642
|
compat
|
|
17413
17643
|
});
|
|
17414
|
-
safeWriteLog(
|
|
17644
|
+
safeWriteLog(PLUGIN_NAME22, {
|
|
17415
17645
|
level: "info",
|
|
17416
17646
|
msg: "opencode_version_check",
|
|
17417
17647
|
opencodeVer,
|
|
@@ -17428,14 +17658,14 @@ var updateCheckerServer = async (ctx) => {
|
|
|
17428
17658
|
}
|
|
17429
17659
|
});
|
|
17430
17660
|
if (!u.auto_check_enabled) {
|
|
17431
|
-
safeWriteLog(
|
|
17661
|
+
safeWriteLog(PLUGIN_NAME22, {
|
|
17432
17662
|
level: "info",
|
|
17433
17663
|
msg: "auto-check disabled (codeforge.json update.auto_check_enabled=false)"
|
|
17434
17664
|
});
|
|
17435
17665
|
return {};
|
|
17436
17666
|
}
|
|
17437
17667
|
setImmediate(() => {
|
|
17438
|
-
safeAsync(
|
|
17668
|
+
safeAsync(PLUGIN_NAME22, "checkAndMaybeUpdate", async () => {
|
|
17439
17669
|
const local = readLocalVersion();
|
|
17440
17670
|
let npmResult = null;
|
|
17441
17671
|
try {
|
|
@@ -17446,7 +17676,7 @@ var updateCheckerServer = async (ctx) => {
|
|
|
17446
17676
|
timeoutMs: 5000
|
|
17447
17677
|
});
|
|
17448
17678
|
} catch (e) {
|
|
17449
|
-
safeWriteLog(
|
|
17679
|
+
safeWriteLog(PLUGIN_NAME22, {
|
|
17450
17680
|
level: "warn",
|
|
17451
17681
|
msg: "npm_fetch_failed",
|
|
17452
17682
|
error: e.message
|
|
@@ -17455,7 +17685,7 @@ var updateCheckerServer = async (ctx) => {
|
|
|
17455
17685
|
return;
|
|
17456
17686
|
}
|
|
17457
17687
|
if (!npmResult) {
|
|
17458
|
-
safeWriteLog(
|
|
17688
|
+
safeWriteLog(PLUGIN_NAME22, {
|
|
17459
17689
|
level: "info",
|
|
17460
17690
|
msg: "npm_no_release",
|
|
17461
17691
|
package: u.package,
|
|
@@ -17464,7 +17694,7 @@ var updateCheckerServer = async (ctx) => {
|
|
|
17464
17694
|
return;
|
|
17465
17695
|
}
|
|
17466
17696
|
const hasUpdate = cmpVersion(local, npmResult.version) < 0;
|
|
17467
|
-
safeWriteLog(
|
|
17697
|
+
safeWriteLog(PLUGIN_NAME22, {
|
|
17468
17698
|
level: "info",
|
|
17469
17699
|
msg: "npm_check_result",
|
|
17470
17700
|
local,
|
|
@@ -17479,10 +17709,10 @@ var updateCheckerServer = async (ctx) => {
|
|
|
17479
17709
|
更新命令:npx ${u.package} install --global`);
|
|
17480
17710
|
return;
|
|
17481
17711
|
}
|
|
17482
|
-
await safeAsync(
|
|
17712
|
+
await safeAsync(PLUGIN_NAME22, "auto_install_bundle", async () => {
|
|
17483
17713
|
const target = getOpencodeBundlePath();
|
|
17484
17714
|
if (!target) {
|
|
17485
|
-
safeWriteLog(
|
|
17715
|
+
safeWriteLog(PLUGIN_NAME22, {
|
|
17486
17716
|
level: "warn",
|
|
17487
17717
|
msg: "auto_install_skip",
|
|
17488
17718
|
reason: "无法定位 opencode bundle 路径"
|
|
@@ -17501,7 +17731,7 @@ var updateCheckerServer = async (ctx) => {
|
|
|
17501
17731
|
oldVersion: local,
|
|
17502
17732
|
keepBackups: u.backup_keep
|
|
17503
17733
|
});
|
|
17504
|
-
safeWriteLog(
|
|
17734
|
+
safeWriteLog(PLUGIN_NAME22, {
|
|
17505
17735
|
level: "info",
|
|
17506
17736
|
msg: "auto_install_success",
|
|
17507
17737
|
local,
|
|
@@ -17535,13 +17765,13 @@ function getOpencodeBundlePath() {
|
|
|
17535
17765
|
candidates.push(join15(localAppData, "opencode", "codeforge", "index.js"));
|
|
17536
17766
|
}
|
|
17537
17767
|
for (const c of candidates) {
|
|
17538
|
-
if (
|
|
17768
|
+
if (existsSync5(c))
|
|
17539
17769
|
return c;
|
|
17540
17770
|
}
|
|
17541
17771
|
return candidates[0] ?? null;
|
|
17542
17772
|
}
|
|
17543
17773
|
async function fallbackToGitHubReleases(ctx, u) {
|
|
17544
|
-
await safeAsync(
|
|
17774
|
+
await safeAsync(PLUGIN_NAME22, "github_fallback", async () => {
|
|
17545
17775
|
const result = await checkUpdateOnce({
|
|
17546
17776
|
repo: u.repo,
|
|
17547
17777
|
intervalMs: u.interval_hours * 3600 * 1000
|
|
@@ -17551,7 +17781,7 @@ async function fallbackToGitHubReleases(ctx, u) {
|
|
|
17551
17781
|
}
|
|
17552
17782
|
async function reportLegacyResult(ctx, result, repo) {
|
|
17553
17783
|
if (result.error && !result.fromCache) {
|
|
17554
|
-
safeWriteLog(
|
|
17784
|
+
safeWriteLog(PLUGIN_NAME22, {
|
|
17555
17785
|
level: "warn",
|
|
17556
17786
|
msg: `update check failed: ${result.error}`,
|
|
17557
17787
|
...result
|
|
@@ -17559,7 +17789,7 @@ async function reportLegacyResult(ctx, result, repo) {
|
|
|
17559
17789
|
return;
|
|
17560
17790
|
}
|
|
17561
17791
|
if (!result.hasUpdate) {
|
|
17562
|
-
safeWriteLog(
|
|
17792
|
+
safeWriteLog(PLUGIN_NAME22, {
|
|
17563
17793
|
level: "info",
|
|
17564
17794
|
msg: `up-to-date (local=${result.local}, remote=${result.remote}${result.fromCache ? ", from_cache" : ""})`,
|
|
17565
17795
|
...result
|
|
@@ -17569,7 +17799,7 @@ async function reportLegacyResult(ctx, result, repo) {
|
|
|
17569
17799
|
const updateCmd = `bunx --bun github:${repo} install`;
|
|
17570
17800
|
const toast = `[CodeForge] 有新版本:${result.local} → ${result.remote}
|
|
17571
17801
|
更新命令:${updateCmd}`;
|
|
17572
|
-
safeWriteLog(
|
|
17802
|
+
safeWriteLog(PLUGIN_NAME22, {
|
|
17573
17803
|
level: "info",
|
|
17574
17804
|
msg: "new_version_available_github_fallback",
|
|
17575
17805
|
local: result.local,
|
|
@@ -17580,17 +17810,17 @@ async function reportLegacyResult(ctx, result, repo) {
|
|
|
17580
17810
|
await postToast(ctx, toast);
|
|
17581
17811
|
}
|
|
17582
17812
|
async function postToast(ctx, message) {
|
|
17583
|
-
await safeAsync(
|
|
17813
|
+
await safeAsync(PLUGIN_NAME22, "client.app.log", async () => {
|
|
17584
17814
|
await ctx.client.app.log({
|
|
17585
17815
|
body: {
|
|
17586
|
-
service:
|
|
17816
|
+
service: PLUGIN_NAME22,
|
|
17587
17817
|
level: "info",
|
|
17588
17818
|
message
|
|
17589
17819
|
}
|
|
17590
17820
|
});
|
|
17591
17821
|
});
|
|
17592
17822
|
}
|
|
17593
|
-
var
|
|
17823
|
+
var handler22 = updateCheckerServer;
|
|
17594
17824
|
|
|
17595
17825
|
// plugins/workflow-engine.ts
|
|
17596
17826
|
init_opencode_plugin_helpers();
|
|
@@ -18094,9 +18324,9 @@ async function runStepAutoFeedback(step, adapter) {
|
|
|
18094
18324
|
}
|
|
18095
18325
|
|
|
18096
18326
|
// plugins/workflow-engine.ts
|
|
18097
|
-
var
|
|
18098
|
-
logLifecycle(
|
|
18099
|
-
var fallbackLog2 = makePluginLogger(
|
|
18327
|
+
var PLUGIN_NAME23 = "workflow-engine";
|
|
18328
|
+
logLifecycle(PLUGIN_NAME23, "import", {});
|
|
18329
|
+
var fallbackLog2 = makePluginLogger(PLUGIN_NAME23);
|
|
18100
18330
|
var _registry = null;
|
|
18101
18331
|
async function loadRegistry(workflowsDir) {
|
|
18102
18332
|
const { loaded, failed } = await loadWorkflowsFromDir(workflowsDir);
|
|
@@ -18116,32 +18346,32 @@ async function handleCommandInvoked(raw, workflowsDir = "workflows") {
|
|
|
18116
18346
|
const log12 = ctx.log ?? fallbackLog2;
|
|
18117
18347
|
const command = typeof ctx.command === "string" ? ctx.command : null;
|
|
18118
18348
|
if (!command) {
|
|
18119
|
-
log12.warn(`[${
|
|
18349
|
+
log12.warn(`[${PLUGIN_NAME23}] command.invoked 缺 command 字段`, ctx);
|
|
18120
18350
|
return null;
|
|
18121
18351
|
}
|
|
18122
18352
|
const reg = await ensureRegistry(workflowsDir);
|
|
18123
18353
|
if (reg.errors.length) {
|
|
18124
|
-
log12.warn(`[${
|
|
18354
|
+
log12.warn(`[${PLUGIN_NAME23}] 有 ${reg.errors.length} 个 workflow 加载失败`, reg.errors);
|
|
18125
18355
|
}
|
|
18126
18356
|
const wf = reg.workflows.find((w) => matchesTrigger(w, command));
|
|
18127
18357
|
if (!wf) {
|
|
18128
|
-
log12.info(`[${
|
|
18358
|
+
log12.info(`[${PLUGIN_NAME23}] no workflow matches "${command}"`);
|
|
18129
18359
|
return null;
|
|
18130
18360
|
}
|
|
18131
|
-
log12.info(`[${
|
|
18361
|
+
log12.info(`[${PLUGIN_NAME23}] dispatch "${command}" → workflow "${wf.name}"`);
|
|
18132
18362
|
try {
|
|
18133
18363
|
const result = await run(wf, {
|
|
18134
18364
|
mode: ctx.adapter ? "real" : "dry_run",
|
|
18135
18365
|
autonomy: ctx.autonomy ?? "semi",
|
|
18136
18366
|
adapter: ctx.adapter
|
|
18137
18367
|
});
|
|
18138
|
-
log12.info(`[${
|
|
18368
|
+
log12.info(`[${PLUGIN_NAME23}] workflow "${wf.name}" 完成 (${result.plan.mode})`, {
|
|
18139
18369
|
steps: result.plan.steps.length,
|
|
18140
18370
|
results: result.results.length
|
|
18141
18371
|
});
|
|
18142
18372
|
return result;
|
|
18143
18373
|
} catch (err) {
|
|
18144
|
-
log12.error(`[${
|
|
18374
|
+
log12.error(`[${PLUGIN_NAME23}] workflow "${wf.name}" 执行失败`, {
|
|
18145
18375
|
error: err instanceof Error ? err.message : String(err)
|
|
18146
18376
|
});
|
|
18147
18377
|
throw err;
|
|
@@ -18150,15 +18380,15 @@ async function handleCommandInvoked(raw, workflowsDir = "workflows") {
|
|
|
18150
18380
|
var workflowEngineServer = async (ctx) => {
|
|
18151
18381
|
const directory = ctx.directory ?? process.cwd();
|
|
18152
18382
|
const workflowsDir = path17.join(directory, "workflows");
|
|
18153
|
-
ensureRegistry(workflowsDir).catch((err) => fallbackLog2.warn(`[${
|
|
18383
|
+
ensureRegistry(workflowsDir).catch((err) => fallbackLog2.warn(`[${PLUGIN_NAME23}] preload workflows failed`, {
|
|
18154
18384
|
error: err instanceof Error ? err.message : String(err)
|
|
18155
18385
|
}));
|
|
18156
|
-
logLifecycle(
|
|
18386
|
+
logLifecycle(PLUGIN_NAME23, "activate", { directory, workflowsDir });
|
|
18157
18387
|
return {
|
|
18158
18388
|
"command.execute.before": async (input, output) => {
|
|
18159
|
-
await safeAsync(
|
|
18389
|
+
await safeAsync(PLUGIN_NAME23, "command.execute.before", async () => {
|
|
18160
18390
|
const cmd = input.command.startsWith("/") ? input.command : `/${input.command}`;
|
|
18161
|
-
safeWriteLog(
|
|
18391
|
+
safeWriteLog(PLUGIN_NAME23, {
|
|
18162
18392
|
hook: "command.execute.before",
|
|
18163
18393
|
command: cmd,
|
|
18164
18394
|
sessionID: input.sessionID
|
|
@@ -18173,14 +18403,14 @@ var workflowEngineServer = async (ctx) => {
|
|
|
18173
18403
|
});
|
|
18174
18404
|
},
|
|
18175
18405
|
"chat.message": async (input, output) => {
|
|
18176
|
-
await safeAsync(
|
|
18406
|
+
await safeAsync(PLUGIN_NAME23, "chat.message", async () => {
|
|
18177
18407
|
const text = extractUserText(output).trim();
|
|
18178
18408
|
if (!text.startsWith("/"))
|
|
18179
18409
|
return;
|
|
18180
18410
|
const cmd = text.split(/\s+/)[0];
|
|
18181
18411
|
if (!cmd)
|
|
18182
18412
|
return;
|
|
18183
|
-
safeWriteLog(
|
|
18413
|
+
safeWriteLog(PLUGIN_NAME23, {
|
|
18184
18414
|
hook: "chat.message",
|
|
18185
18415
|
command: cmd,
|
|
18186
18416
|
sessionID: input.sessionID
|
|
@@ -18190,7 +18420,7 @@ var workflowEngineServer = async (ctx) => {
|
|
|
18190
18420
|
}
|
|
18191
18421
|
};
|
|
18192
18422
|
};
|
|
18193
|
-
var
|
|
18423
|
+
var handler23 = workflowEngineServer;
|
|
18194
18424
|
|
|
18195
18425
|
// src/index.ts
|
|
18196
18426
|
var PLUGIN_ID = "codeforge";
|
|
@@ -18209,16 +18439,17 @@ var HANDLERS = [
|
|
|
18209
18439
|
{ name: "kh-reminder", init: handler11 },
|
|
18210
18440
|
{ name: "memories-context", init: handler12 },
|
|
18211
18441
|
{ name: "model-fallback", init: handler13 },
|
|
18212
|
-
{ name: "pwsh-utf8", init:
|
|
18213
|
-
{ name: "session-recovery", init:
|
|
18214
|
-
{ name: "subtask-heartbeat", init:
|
|
18215
|
-
{ name: "subtasks", init:
|
|
18216
|
-
{ name: "
|
|
18217
|
-
{ name: "
|
|
18442
|
+
{ name: "pwsh-utf8", init: handler16 },
|
|
18443
|
+
{ name: "session-recovery", init: handler17 },
|
|
18444
|
+
{ name: "subtask-heartbeat", init: handler14 },
|
|
18445
|
+
{ name: "subtasks", init: handler18 },
|
|
18446
|
+
{ name: "parallel-status", init: handler15 },
|
|
18447
|
+
{ name: "terminal-monitor", init: handler19 },
|
|
18448
|
+
{ name: "token-manager", init: handler20 },
|
|
18218
18449
|
{ name: "tool-heartbeat", init: handler7 },
|
|
18219
|
-
{ name: "tool-policy", init:
|
|
18220
|
-
{ name: "update-checker", init:
|
|
18221
|
-
{ name: "workflow-engine", init:
|
|
18450
|
+
{ name: "tool-policy", init: handler21 },
|
|
18451
|
+
{ name: "update-checker", init: handler22 },
|
|
18452
|
+
{ name: "workflow-engine", init: handler23 }
|
|
18222
18453
|
];
|
|
18223
18454
|
function makeSerialHook(hookName, fns) {
|
|
18224
18455
|
return async (input, output) => {
|
|
@@ -18233,69 +18464,91 @@ function makeSerialHook(hookName, fns) {
|
|
|
18233
18464
|
}
|
|
18234
18465
|
};
|
|
18235
18466
|
}
|
|
18236
|
-
|
|
18237
|
-
|
|
18238
|
-
|
|
18239
|
-
|
|
18240
|
-
|
|
18241
|
-
|
|
18242
|
-
|
|
18243
|
-
|
|
18467
|
+
function createCodeforgeServer(opts) {
|
|
18468
|
+
return async (input) => {
|
|
18469
|
+
if (opts.enableDevIsolation) {
|
|
18470
|
+
const yieldResult = shouldYieldToLocalPlugin({ directory: input.directory });
|
|
18471
|
+
if (yieldResult.yield) {
|
|
18472
|
+
const msg = formatYieldLog(yieldResult);
|
|
18473
|
+
log12.info(msg, { reason: yieldResult.reason, markerPath: yieldResult.markerPath });
|
|
18474
|
+
logLifecycle(PLUGIN_ID, "activate", {
|
|
18475
|
+
yield_to_local: true,
|
|
18476
|
+
yield_reason: yieldResult.reason,
|
|
18477
|
+
yield_marker_path: yieldResult.markerPath,
|
|
18478
|
+
directory: input.directory,
|
|
18479
|
+
handlers_total: HANDLERS.length,
|
|
18480
|
+
handlers_active: 0
|
|
18481
|
+
});
|
|
18482
|
+
return {};
|
|
18483
|
+
}
|
|
18244
18484
|
}
|
|
18245
|
-
|
|
18246
|
-
|
|
18247
|
-
|
|
18248
|
-
|
|
18249
|
-
|
|
18250
|
-
|
|
18251
|
-
|
|
18252
|
-
|
|
18253
|
-
|
|
18254
|
-
|
|
18255
|
-
|
|
18256
|
-
|
|
18257
|
-
|
|
18258
|
-
|
|
18259
|
-
|
|
18260
|
-
|
|
18261
|
-
|
|
18262
|
-
|
|
18263
|
-
|
|
18264
|
-
|
|
18265
|
-
|
|
18266
|
-
|
|
18267
|
-
|
|
18268
|
-
|
|
18269
|
-
|
|
18270
|
-
|
|
18271
|
-
|
|
18272
|
-
|
|
18273
|
-
|
|
18274
|
-
|
|
18275
|
-
|
|
18276
|
-
|
|
18277
|
-
|
|
18278
|
-
|
|
18279
|
-
|
|
18280
|
-
|
|
18281
|
-
|
|
18282
|
-
|
|
18283
|
-
|
|
18284
|
-
|
|
18285
|
-
|
|
18286
|
-
|
|
18287
|
-
|
|
18288
|
-
|
|
18289
|
-
|
|
18290
|
-
|
|
18291
|
-
|
|
18292
|
-
|
|
18293
|
-
|
|
18485
|
+
const results = await Promise.allSettled(HANDLERS.map((h) => h.init(input)));
|
|
18486
|
+
const hooksList = [];
|
|
18487
|
+
results.forEach((r, i) => {
|
|
18488
|
+
if (r.status === "fulfilled" && r.value && typeof r.value === "object") {
|
|
18489
|
+
hooksList.push(r.value);
|
|
18490
|
+
} else if (r.status === "rejected") {
|
|
18491
|
+
log12.warn(`[${PLUGIN_ID}] handler ${HANDLERS[i].name} init failed (隔离,其他 handler 继续)`, { error: r.reason instanceof Error ? r.reason.message : String(r.reason) });
|
|
18492
|
+
}
|
|
18493
|
+
});
|
|
18494
|
+
logLifecycle(PLUGIN_ID, "activate", {
|
|
18495
|
+
directory: input.directory,
|
|
18496
|
+
handlers_total: HANDLERS.length,
|
|
18497
|
+
handlers_active: hooksList.length,
|
|
18498
|
+
handlers_failed: HANDLERS.length - hooksList.length
|
|
18499
|
+
});
|
|
18500
|
+
const chatMessageBucket = [];
|
|
18501
|
+
const commandExecuteBeforeBucket = [];
|
|
18502
|
+
const chatParamsBucket = [];
|
|
18503
|
+
const toolExecuteBeforeBucket = [];
|
|
18504
|
+
const chatMessagesTransformBucket = [];
|
|
18505
|
+
const eventBucket = [];
|
|
18506
|
+
const toolMerged = {};
|
|
18507
|
+
for (const h of hooksList) {
|
|
18508
|
+
if (h["chat.message"])
|
|
18509
|
+
chatMessageBucket.push(h["chat.message"]);
|
|
18510
|
+
if (h["command.execute.before"])
|
|
18511
|
+
commandExecuteBeforeBucket.push(h["command.execute.before"]);
|
|
18512
|
+
if (h["chat.params"])
|
|
18513
|
+
chatParamsBucket.push(h["chat.params"]);
|
|
18514
|
+
if (h["tool.execute.before"])
|
|
18515
|
+
toolExecuteBeforeBucket.push(h["tool.execute.before"]);
|
|
18516
|
+
if (h["experimental.chat.messages.transform"]) {
|
|
18517
|
+
chatMessagesTransformBucket.push(h["experimental.chat.messages.transform"]);
|
|
18518
|
+
}
|
|
18519
|
+
if (h.event)
|
|
18520
|
+
eventBucket.push(h.event);
|
|
18521
|
+
if (h.tool)
|
|
18522
|
+
Object.assign(toolMerged, h.tool);
|
|
18523
|
+
}
|
|
18524
|
+
return {
|
|
18525
|
+
"chat.message": makeSerialHook("chat.message", chatMessageBucket),
|
|
18526
|
+
"command.execute.before": makeSerialHook("command.execute.before", commandExecuteBeforeBucket),
|
|
18527
|
+
"chat.params": makeSerialHook("chat.params", chatParamsBucket),
|
|
18528
|
+
"tool.execute.before": makeSerialHook("tool.execute.before", toolExecuteBeforeBucket),
|
|
18529
|
+
"experimental.chat.messages.transform": makeSerialHook("experimental.chat.messages.transform", chatMessagesTransformBucket),
|
|
18530
|
+
event: async (envelope) => {
|
|
18531
|
+
await Promise.all(eventBucket.map(async (fn) => {
|
|
18532
|
+
try {
|
|
18533
|
+
await fn(envelope);
|
|
18534
|
+
} catch (err) {
|
|
18535
|
+
log12.warn(`[${PLUGIN_ID}] event handler 异常(已隔离)`, {
|
|
18536
|
+
error: err instanceof Error ? err.message : String(err)
|
|
18537
|
+
});
|
|
18538
|
+
}
|
|
18539
|
+
}));
|
|
18540
|
+
},
|
|
18541
|
+
tool: toolMerged
|
|
18542
|
+
};
|
|
18294
18543
|
};
|
|
18295
|
-
}
|
|
18544
|
+
}
|
|
18545
|
+
var codeforgeServer = createCodeforgeServer({ enableDevIsolation: true });
|
|
18546
|
+
var codeforgeDevServer = createCodeforgeServer({ enableDevIsolation: false });
|
|
18296
18547
|
var pluginModule = { id: PLUGIN_ID, server: codeforgeServer };
|
|
18297
18548
|
var src_default = pluginModule;
|
|
18298
18549
|
export {
|
|
18299
18550
|
src_default as default,
|
|
18300
|
-
|
|
18551
|
+
createCodeforgeServer,
|
|
18552
|
+
codeforgeServer,
|
|
18553
|
+
codeforgeDevServer
|
|
18301
18554
|
};
|