@andyqiu/codeforge 0.3.7 → 0.3.9
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/agents/codeforge.md +185 -0
- package/agents/coder.md +7 -3
- package/agents/planner.md +133 -400
- package/agents/reviewer.md +7 -3
- package/commands/parallel-status.md +56 -0
- package/commands/parallel.md +38 -1
- package/dist/index.js +695 -433
- package/install.sh +119 -14
- package/package.json +1 -1
- package/workflows/parallel-explore.yaml +18 -53
package/dist/index.js
CHANGED
|
@@ -83,6 +83,11 @@ function logLifecycle(plugin, phase, extra) {
|
|
|
83
83
|
...extra
|
|
84
84
|
});
|
|
85
85
|
}
|
|
86
|
+
function makePartId(prefix = "prt_") {
|
|
87
|
+
const rand = Math.random().toString(36).slice(2, 9);
|
|
88
|
+
const ts = Date.now().toString(36).slice(-7);
|
|
89
|
+
return `${prefix}${rand}${ts}`;
|
|
90
|
+
}
|
|
86
91
|
var LOG_DIR, LOG_FILE;
|
|
87
92
|
var init_opencode_plugin_helpers = __esm(() => {
|
|
88
93
|
LOG_DIR = process.env["CODEFORGE_LOG_DIR"] ?? join(homedir(), ".cache", "codeforge");
|
|
@@ -262,7 +267,7 @@ var init_auto_review_trigger = __esm(() => {
|
|
|
262
267
|
});
|
|
263
268
|
|
|
264
269
|
// lib/runtime-paths.ts
|
|
265
|
-
import { mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync, existsSync } from "node:fs";
|
|
270
|
+
import { mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync, existsSync as existsSync2 } from "node:fs";
|
|
266
271
|
import * as path2 from "node:path";
|
|
267
272
|
import * as os from "node:os";
|
|
268
273
|
import * as crypto from "node:crypto";
|
|
@@ -296,7 +301,7 @@ function runtimeDir(absRoot, opts = {}) {
|
|
|
296
301
|
return dir;
|
|
297
302
|
mkdirSync2(dir, { recursive: true });
|
|
298
303
|
const metaFile = path2.join(dir, ".meta.json");
|
|
299
|
-
if (
|
|
304
|
+
if (existsSync2(metaFile)) {
|
|
300
305
|
const existing = readMetaSafe(metaFile);
|
|
301
306
|
if (existing && existing.absPath && existing.absPath !== resolvedRoot) {
|
|
302
307
|
throw new Error(`runtime dir hash collision: ${projectKey(resolvedRoot)} already used by ${existing.absPath}, current is ${resolvedRoot}`);
|
|
@@ -335,7 +340,7 @@ function readMetaSafe(file) {
|
|
|
335
340
|
var init_runtime_paths = () => {};
|
|
336
341
|
|
|
337
342
|
// lib/global-config.ts
|
|
338
|
-
import { readFileSync as readFileSync3, existsSync as
|
|
343
|
+
import { readFileSync as readFileSync3, existsSync as existsSync3, statSync } from "node:fs";
|
|
339
344
|
import * as path3 from "node:path";
|
|
340
345
|
import * as os2 from "node:os";
|
|
341
346
|
function __resetGlobalConfigCache() {
|
|
@@ -360,7 +365,7 @@ function loadJsonIfExists(filePath) {
|
|
|
360
365
|
const cached = cacheGet(cacheKey);
|
|
361
366
|
if (cached !== undefined)
|
|
362
367
|
return cached;
|
|
363
|
-
if (!
|
|
368
|
+
if (!existsSync3(filePath)) {
|
|
364
369
|
cacheSet(cacheKey, null);
|
|
365
370
|
return null;
|
|
366
371
|
}
|
|
@@ -641,7 +646,7 @@ function isAbortError(err) {
|
|
|
641
646
|
return name === "AbortError" || name === "TimeoutError";
|
|
642
647
|
}
|
|
643
648
|
function defaultSleep(ms) {
|
|
644
|
-
return new Promise((
|
|
649
|
+
return new Promise((resolve5) => setTimeout(resolve5, ms));
|
|
645
650
|
}
|
|
646
651
|
function errorMessage2(err) {
|
|
647
652
|
return err instanceof Error ? err.message : String(err);
|
|
@@ -8025,11 +8030,11 @@ function shouldStopByStuck(history, cfg) {
|
|
|
8025
8030
|
async function withTimeout3(p, timeoutMs) {
|
|
8026
8031
|
if (timeoutMs <= 0)
|
|
8027
8032
|
return await p;
|
|
8028
|
-
return await new Promise((
|
|
8033
|
+
return await new Promise((resolve11, reject) => {
|
|
8029
8034
|
const timer = setTimeout(() => reject(new Error(`timeout after ${timeoutMs}ms`)), timeoutMs);
|
|
8030
8035
|
Promise.resolve(p).then((v) => {
|
|
8031
8036
|
clearTimeout(timer);
|
|
8032
|
-
|
|
8037
|
+
resolve11(v);
|
|
8033
8038
|
}, (err) => {
|
|
8034
8039
|
clearTimeout(timer);
|
|
8035
8040
|
reject(err);
|
|
@@ -8151,6 +8156,47 @@ var init_auto_feedback = __esm(() => {
|
|
|
8151
8156
|
// src/index.ts
|
|
8152
8157
|
init_opencode_plugin_helpers();
|
|
8153
8158
|
|
|
8159
|
+
// lib/dev-isolation.ts
|
|
8160
|
+
import { existsSync } from "node:fs";
|
|
8161
|
+
import { resolve, dirname } from "node:path";
|
|
8162
|
+
var MARKER_REL = ".codeforge/.dev-marker";
|
|
8163
|
+
var ENV_KEY = "CODEFORGE_DEV";
|
|
8164
|
+
function shouldYieldToLocalPlugin(opts = {}) {
|
|
8165
|
+
const env = opts.env ?? process.env;
|
|
8166
|
+
const fileExists = opts.fileExists ?? existsSync;
|
|
8167
|
+
const envVal = env[ENV_KEY];
|
|
8168
|
+
if (envVal === "1" || envVal === "true" || envVal === "yes") {
|
|
8169
|
+
return { yield: true, reason: "env" };
|
|
8170
|
+
}
|
|
8171
|
+
const startDir = opts.directory ? resolve(opts.directory) : process.cwd();
|
|
8172
|
+
const maxDepth = Math.max(1, opts.maxDepth ?? 20);
|
|
8173
|
+
let cur = startDir;
|
|
8174
|
+
for (let i = 0;i < maxDepth; i++) {
|
|
8175
|
+
const markerPath = resolve(cur, MARKER_REL);
|
|
8176
|
+
try {
|
|
8177
|
+
if (fileExists(markerPath)) {
|
|
8178
|
+
return { yield: true, reason: "marker", markerPath };
|
|
8179
|
+
}
|
|
8180
|
+
} catch {}
|
|
8181
|
+
const parent = dirname(cur);
|
|
8182
|
+
if (parent === cur)
|
|
8183
|
+
break;
|
|
8184
|
+
cur = parent;
|
|
8185
|
+
}
|
|
8186
|
+
return { yield: false, reason: null };
|
|
8187
|
+
}
|
|
8188
|
+
function formatYieldLog(result) {
|
|
8189
|
+
if (!result.yield)
|
|
8190
|
+
return "[codeforge] stable plugin 正常加载";
|
|
8191
|
+
if (result.reason === "env") {
|
|
8192
|
+
return "[codeforge] 检测到 CODEFORGE_DEV env,stable plugin 让位";
|
|
8193
|
+
}
|
|
8194
|
+
if (result.reason === "marker") {
|
|
8195
|
+
return `[codeforge] 检测到 dev marker (${result.markerPath}),stable plugin 让位`;
|
|
8196
|
+
}
|
|
8197
|
+
return "[codeforge] stable plugin 让位(未知原因)";
|
|
8198
|
+
}
|
|
8199
|
+
|
|
8154
8200
|
// plugins/agent-router.ts
|
|
8155
8201
|
init_opencode_plugin_helpers();
|
|
8156
8202
|
var PLUGIN_NAME = "agent-router";
|
|
@@ -8317,7 +8363,7 @@ init_opencode_plugin_helpers();
|
|
|
8317
8363
|
// lib/arena.ts
|
|
8318
8364
|
var DEFAULT_TIMEOUT = 90000;
|
|
8319
8365
|
async function withTimeout(p, timeoutMs, signal) {
|
|
8320
|
-
return new Promise((
|
|
8366
|
+
return new Promise((resolve2, reject) => {
|
|
8321
8367
|
if (signal?.aborted)
|
|
8322
8368
|
return reject(new Error("aborted"));
|
|
8323
8369
|
const onAbort = () => {
|
|
@@ -8335,7 +8381,7 @@ async function withTimeout(p, timeoutMs, signal) {
|
|
|
8335
8381
|
}
|
|
8336
8382
|
Promise.resolve().then(() => p).then((v) => {
|
|
8337
8383
|
cleanup();
|
|
8338
|
-
|
|
8384
|
+
resolve2(v);
|
|
8339
8385
|
}, (err) => {
|
|
8340
8386
|
cleanup();
|
|
8341
8387
|
reject(err);
|
|
@@ -8561,14 +8607,14 @@ async function git(opts, args) {
|
|
|
8561
8607
|
};
|
|
8562
8608
|
}
|
|
8563
8609
|
}
|
|
8564
|
-
function
|
|
8610
|
+
function resolve3(o) {
|
|
8565
8611
|
return {
|
|
8566
8612
|
root: path.resolve(o?.root ?? process.cwd()),
|
|
8567
8613
|
timeoutMs: o?.timeoutMs ?? DEFAULTS.timeoutMs
|
|
8568
8614
|
};
|
|
8569
8615
|
}
|
|
8570
8616
|
async function isGitRepo(o) {
|
|
8571
|
-
const opts =
|
|
8617
|
+
const opts = resolve3(o);
|
|
8572
8618
|
try {
|
|
8573
8619
|
await fs.access(path.join(opts.root, ".git"));
|
|
8574
8620
|
return true;
|
|
@@ -8577,7 +8623,7 @@ async function isGitRepo(o) {
|
|
|
8577
8623
|
}
|
|
8578
8624
|
}
|
|
8579
8625
|
async function listChanged(o) {
|
|
8580
|
-
const opts =
|
|
8626
|
+
const opts = resolve3(o);
|
|
8581
8627
|
if (!await isGitRepo(opts))
|
|
8582
8628
|
return [];
|
|
8583
8629
|
const r = await git(opts, ["status", "--porcelain"]);
|
|
@@ -8604,7 +8650,7 @@ ${input.body.trim()}
|
|
|
8604
8650
|
`;
|
|
8605
8651
|
}
|
|
8606
8652
|
async function commit(input, o) {
|
|
8607
|
-
const opts =
|
|
8653
|
+
const opts = resolve3(o);
|
|
8608
8654
|
if (!await isGitRepo(opts)) {
|
|
8609
8655
|
return { ok: false, reason: "not-a-git-repo" };
|
|
8610
8656
|
}
|
|
@@ -14731,10 +14777,303 @@ var modelFallbackServer = async (ctx) => {
|
|
|
14731
14777
|
};
|
|
14732
14778
|
var handler13 = modelFallbackServer;
|
|
14733
14779
|
|
|
14734
|
-
// plugins/
|
|
14780
|
+
// plugins/subtask-heartbeat.ts
|
|
14735
14781
|
init_opencode_plugin_helpers();
|
|
14736
|
-
var PLUGIN_NAME14 = "
|
|
14782
|
+
var PLUGIN_NAME14 = "subtask-heartbeat";
|
|
14737
14783
|
logLifecycle(PLUGIN_NAME14, "import", {});
|
|
14784
|
+
var HEARTBEAT_INTERVAL_MS2 = 30000;
|
|
14785
|
+
var HEARTBEAT_DEBOUNCE_MS = 25000;
|
|
14786
|
+
var TOAST_DURATION_MS3 = 5000;
|
|
14787
|
+
var START_TOAST_DURATION_MS = 2000;
|
|
14788
|
+
var inflight2 = new Map;
|
|
14789
|
+
function _snapshotInflight() {
|
|
14790
|
+
return [...inflight2.values()].map((r) => ({ ...r }));
|
|
14791
|
+
}
|
|
14792
|
+
function getInflightSnapshot() {
|
|
14793
|
+
return _snapshotInflight();
|
|
14794
|
+
}
|
|
14795
|
+
function extractCreatedChild(event) {
|
|
14796
|
+
if (!event || typeof event !== "object")
|
|
14797
|
+
return null;
|
|
14798
|
+
const e = event;
|
|
14799
|
+
if (e.type !== "session.created")
|
|
14800
|
+
return null;
|
|
14801
|
+
const info = e.properties?.info;
|
|
14802
|
+
if (!info || typeof info !== "object")
|
|
14803
|
+
return null;
|
|
14804
|
+
const session = info;
|
|
14805
|
+
if (typeof session.id !== "string")
|
|
14806
|
+
return null;
|
|
14807
|
+
if (typeof session.parentID !== "string" || session.parentID === "")
|
|
14808
|
+
return null;
|
|
14809
|
+
return { childID: session.id, parentID: session.parentID, agent: null };
|
|
14810
|
+
}
|
|
14811
|
+
function extractEndedSessionID(event) {
|
|
14812
|
+
if (!event || typeof event !== "object")
|
|
14813
|
+
return null;
|
|
14814
|
+
const e = event;
|
|
14815
|
+
if (typeof e.type !== "string")
|
|
14816
|
+
return null;
|
|
14817
|
+
if (e.type !== "session.idle" && e.type !== "session.deleted" && e.type !== "session.error") {
|
|
14818
|
+
return null;
|
|
14819
|
+
}
|
|
14820
|
+
const props = e.properties ?? {};
|
|
14821
|
+
const direct = props["sessionID"];
|
|
14822
|
+
if (typeof direct === "string" && direct)
|
|
14823
|
+
return { type: e.type, sessionID: direct };
|
|
14824
|
+
const info = props["info"];
|
|
14825
|
+
if (info && typeof info === "object") {
|
|
14826
|
+
const sid = info.id;
|
|
14827
|
+
if (typeof sid === "string" && sid)
|
|
14828
|
+
return { type: e.type, sessionID: sid };
|
|
14829
|
+
}
|
|
14830
|
+
return null;
|
|
14831
|
+
}
|
|
14832
|
+
function registerInflight(payload, now = Date.now()) {
|
|
14833
|
+
const r = {
|
|
14834
|
+
childID: payload.childID,
|
|
14835
|
+
parentID: payload.parentID,
|
|
14836
|
+
agent: payload.agent,
|
|
14837
|
+
startedAt: now,
|
|
14838
|
+
lastBeatAt: now,
|
|
14839
|
+
lastTool: null
|
|
14840
|
+
};
|
|
14841
|
+
inflight2.set(payload.childID, r);
|
|
14842
|
+
return r;
|
|
14843
|
+
}
|
|
14844
|
+
function recordToolBeat(sessionID, tool2, now = Date.now()) {
|
|
14845
|
+
const r = inflight2.get(sessionID);
|
|
14846
|
+
if (!r)
|
|
14847
|
+
return null;
|
|
14848
|
+
r.lastBeatAt = now;
|
|
14849
|
+
r.lastTool = tool2;
|
|
14850
|
+
return r;
|
|
14851
|
+
}
|
|
14852
|
+
function clearInflight2(sessionID) {
|
|
14853
|
+
const r = inflight2.get(sessionID);
|
|
14854
|
+
if (!r)
|
|
14855
|
+
return null;
|
|
14856
|
+
inflight2.delete(sessionID);
|
|
14857
|
+
return r;
|
|
14858
|
+
}
|
|
14859
|
+
function pickHeartbeats(now = Date.now()) {
|
|
14860
|
+
const out = [];
|
|
14861
|
+
for (const r of inflight2.values()) {
|
|
14862
|
+
if (now - r.lastBeatAt >= HEARTBEAT_DEBOUNCE_MS)
|
|
14863
|
+
out.push(r);
|
|
14864
|
+
}
|
|
14865
|
+
return out;
|
|
14866
|
+
}
|
|
14867
|
+
function fmtElapsed(ms) {
|
|
14868
|
+
const total = Math.max(0, Math.floor(ms / 1000));
|
|
14869
|
+
const m = Math.floor(total / 60);
|
|
14870
|
+
const s = total % 60;
|
|
14871
|
+
return m > 0 ? `${m}m${s.toString().padStart(2, "0")}s` : `${s}s`;
|
|
14872
|
+
}
|
|
14873
|
+
function buildStartToast(r) {
|
|
14874
|
+
const who = r.agent ?? "subagent";
|
|
14875
|
+
return {
|
|
14876
|
+
message: `\uD83D\uDE80 子 session 启动: ${who}`,
|
|
14877
|
+
variant: "info"
|
|
14878
|
+
};
|
|
14879
|
+
}
|
|
14880
|
+
function buildHeartbeatToast(r, now = Date.now()) {
|
|
14881
|
+
const who = r.agent ?? "subagent";
|
|
14882
|
+
const tool2 = r.lastTool ?? "thinking";
|
|
14883
|
+
return {
|
|
14884
|
+
message: `⏳ ${who} 仍在运行 ${fmtElapsed(now - r.startedAt)} | 当前: ${tool2}`,
|
|
14885
|
+
variant: "info"
|
|
14886
|
+
};
|
|
14887
|
+
}
|
|
14888
|
+
function buildEndToast(r, type, now = Date.now()) {
|
|
14889
|
+
const who = r.agent ?? "subagent";
|
|
14890
|
+
const elapsed = fmtElapsed(now - r.startedAt);
|
|
14891
|
+
if (type === "session.error") {
|
|
14892
|
+
return { message: `❌ ${who} 失败 (${elapsed})`, variant: "error" };
|
|
14893
|
+
}
|
|
14894
|
+
if (type === "session.deleted") {
|
|
14895
|
+
return { message: `\uD83D\uDDD1️ ${who} 被取消 (${elapsed})`, variant: "error" };
|
|
14896
|
+
}
|
|
14897
|
+
return { message: `✅ ${who} 完成 (${elapsed})`, variant: "success" };
|
|
14898
|
+
}
|
|
14899
|
+
function normalizeVariant3(raw) {
|
|
14900
|
+
if (raw === "info" || raw === "warning")
|
|
14901
|
+
return "default";
|
|
14902
|
+
return raw;
|
|
14903
|
+
}
|
|
14904
|
+
async function showToast3(client, payload, log7) {
|
|
14905
|
+
if (typeof client?.tui?.showToast !== "function") {
|
|
14906
|
+
log7?.debug?.("tui.showToast 不可用,noop");
|
|
14907
|
+
return false;
|
|
14908
|
+
}
|
|
14909
|
+
try {
|
|
14910
|
+
await client.tui.showToast({
|
|
14911
|
+
body: {
|
|
14912
|
+
message: payload.message,
|
|
14913
|
+
variant: normalizeVariant3(payload.variant),
|
|
14914
|
+
duration: payload.duration ?? TOAST_DURATION_MS3,
|
|
14915
|
+
title: payload.title ?? "CodeForge"
|
|
14916
|
+
}
|
|
14917
|
+
});
|
|
14918
|
+
return true;
|
|
14919
|
+
} catch (err) {
|
|
14920
|
+
log7?.warn("tui.showToast 抛错(已隔离)", {
|
|
14921
|
+
error: err instanceof Error ? err.message : String(err)
|
|
14922
|
+
});
|
|
14923
|
+
return false;
|
|
14924
|
+
}
|
|
14925
|
+
}
|
|
14926
|
+
var log7 = makePluginLogger(PLUGIN_NAME14);
|
|
14927
|
+
var subtaskHeartbeatServer = async (ctx) => {
|
|
14928
|
+
logLifecycle(PLUGIN_NAME14, "activate", {
|
|
14929
|
+
directory: ctx.directory,
|
|
14930
|
+
intervalMs: HEARTBEAT_INTERVAL_MS2
|
|
14931
|
+
});
|
|
14932
|
+
const client = ctx.client;
|
|
14933
|
+
const interval = setInterval(() => {
|
|
14934
|
+
safeAsync(PLUGIN_NAME14, "interval", async () => {
|
|
14935
|
+
const beats = pickHeartbeats();
|
|
14936
|
+
if (beats.length === 0)
|
|
14937
|
+
return;
|
|
14938
|
+
for (const r of beats) {
|
|
14939
|
+
const t = buildHeartbeatToast(r);
|
|
14940
|
+
const sent = await showToast3(client, t, log7);
|
|
14941
|
+
safeWriteLog(PLUGIN_NAME14, {
|
|
14942
|
+
hook: "interval",
|
|
14943
|
+
child: r.childID,
|
|
14944
|
+
parent: r.parentID,
|
|
14945
|
+
tool: r.lastTool,
|
|
14946
|
+
elapsed_ms: Date.now() - r.startedAt,
|
|
14947
|
+
toast_sent: sent
|
|
14948
|
+
});
|
|
14949
|
+
r.lastBeatAt = Date.now();
|
|
14950
|
+
}
|
|
14951
|
+
});
|
|
14952
|
+
}, HEARTBEAT_INTERVAL_MS2);
|
|
14953
|
+
if (typeof interval.unref === "function") {
|
|
14954
|
+
interval.unref();
|
|
14955
|
+
}
|
|
14956
|
+
return {
|
|
14957
|
+
event: async ({ event }) => {
|
|
14958
|
+
await safeAsync(PLUGIN_NAME14, "event", async () => {
|
|
14959
|
+
const created = extractCreatedChild(event);
|
|
14960
|
+
if (created) {
|
|
14961
|
+
const record = registerInflight(created);
|
|
14962
|
+
safeWriteLog(PLUGIN_NAME14, {
|
|
14963
|
+
hook: "event",
|
|
14964
|
+
type: "session.created",
|
|
14965
|
+
child: created.childID,
|
|
14966
|
+
parent: created.parentID
|
|
14967
|
+
});
|
|
14968
|
+
const startToast = buildStartToast(record);
|
|
14969
|
+
const sent = await showToast3(client, { ...startToast, duration: START_TOAST_DURATION_MS }, log7);
|
|
14970
|
+
safeWriteLog(PLUGIN_NAME14, {
|
|
14971
|
+
hook: "event",
|
|
14972
|
+
type: "session.created.toast",
|
|
14973
|
+
child: created.childID,
|
|
14974
|
+
toast_sent: sent
|
|
14975
|
+
});
|
|
14976
|
+
return;
|
|
14977
|
+
}
|
|
14978
|
+
const ended = extractEndedSessionID(event);
|
|
14979
|
+
if (ended) {
|
|
14980
|
+
const r = clearInflight2(ended.sessionID);
|
|
14981
|
+
if (r) {
|
|
14982
|
+
const t = buildEndToast(r, ended.type);
|
|
14983
|
+
const sent = await showToast3(client, t, log7);
|
|
14984
|
+
safeWriteLog(PLUGIN_NAME14, {
|
|
14985
|
+
hook: "event",
|
|
14986
|
+
type: ended.type,
|
|
14987
|
+
child: r.childID,
|
|
14988
|
+
elapsed_ms: Date.now() - r.startedAt,
|
|
14989
|
+
toast_sent: sent,
|
|
14990
|
+
end_toast_message: t.message
|
|
14991
|
+
});
|
|
14992
|
+
}
|
|
14993
|
+
}
|
|
14994
|
+
});
|
|
14995
|
+
},
|
|
14996
|
+
"tool.execute.before": async (input) => {
|
|
14997
|
+
await safeAsync(PLUGIN_NAME14, "tool.execute.before", async () => {
|
|
14998
|
+
if (!input || typeof input.sessionID !== "string" || typeof input.tool !== "string")
|
|
14999
|
+
return;
|
|
15000
|
+
recordToolBeat(input.sessionID, input.tool);
|
|
15001
|
+
});
|
|
15002
|
+
}
|
|
15003
|
+
};
|
|
15004
|
+
};
|
|
15005
|
+
var handler14 = subtaskHeartbeatServer;
|
|
15006
|
+
|
|
15007
|
+
// plugins/parallel-status.ts
|
|
15008
|
+
init_opencode_plugin_helpers();
|
|
15009
|
+
var PLUGIN_NAME15 = "parallel-status";
|
|
15010
|
+
logLifecycle(PLUGIN_NAME15, "import");
|
|
15011
|
+
var ID_MAX_LEN = 16;
|
|
15012
|
+
var ID_KEEP_LEN = 13;
|
|
15013
|
+
function shortId(s) {
|
|
15014
|
+
return s.length > ID_MAX_LEN ? s.slice(0, ID_KEEP_LEN) + "..." : s;
|
|
15015
|
+
}
|
|
15016
|
+
function formatElapsed(ms) {
|
|
15017
|
+
const total = Math.max(0, Math.floor(ms / 1000));
|
|
15018
|
+
const m = Math.floor(total / 60);
|
|
15019
|
+
const s = total % 60;
|
|
15020
|
+
return m > 0 ? `${m}m${s.toString().padStart(2, "0")}s` : `${s}s`;
|
|
15021
|
+
}
|
|
15022
|
+
function formatInflightMarkdown(snapshot, now = Date.now()) {
|
|
15023
|
+
if (snapshot.length === 0) {
|
|
15024
|
+
return "✅ 当前无 inflight subagent";
|
|
15025
|
+
}
|
|
15026
|
+
const lines = [];
|
|
15027
|
+
lines.push(`\uD83D\uDCCA 当前 ${snapshot.length} 个 subagent 在跑:`, "");
|
|
15028
|
+
lines.push("| # | child id | parent id | agent | 已跑 | 最近工具 |");
|
|
15029
|
+
lines.push("|---|----------|-----------|-------|------|----------|");
|
|
15030
|
+
snapshot.forEach((r, i) => {
|
|
15031
|
+
const elapsed = formatElapsed(now - r.startedAt);
|
|
15032
|
+
const agent = r.agent ?? "(unknown)";
|
|
15033
|
+
const tool2 = r.lastTool ?? "(thinking)";
|
|
15034
|
+
const child = shortId(r.childID);
|
|
15035
|
+
const parent = shortId(r.parentID);
|
|
15036
|
+
lines.push(`| ${i + 1} | \`${child}\` | \`${parent}\` | ${agent} | ${elapsed} | ${tool2} |`);
|
|
15037
|
+
});
|
|
15038
|
+
return lines.join(`
|
|
15039
|
+
`);
|
|
15040
|
+
}
|
|
15041
|
+
var parallelStatusServer = async (ctx) => {
|
|
15042
|
+
const log8 = makePluginLogger(PLUGIN_NAME15);
|
|
15043
|
+
logLifecycle(PLUGIN_NAME15, "activate", { directory: ctx.directory });
|
|
15044
|
+
return {
|
|
15045
|
+
"command.execute.before": async (input, output) => {
|
|
15046
|
+
try {
|
|
15047
|
+
if (!input || input.command !== "parallel-status")
|
|
15048
|
+
return;
|
|
15049
|
+
const snapshot = getInflightSnapshot();
|
|
15050
|
+
const text = formatInflightMarkdown(snapshot);
|
|
15051
|
+
if (Array.isArray(output?.parts)) {
|
|
15052
|
+
output.parts.length = 0;
|
|
15053
|
+
output.parts.push({
|
|
15054
|
+
id: makePartId(),
|
|
15055
|
+
sessionID: input.sessionID,
|
|
15056
|
+
messageID: "",
|
|
15057
|
+
type: "text",
|
|
15058
|
+
text,
|
|
15059
|
+
synthetic: false
|
|
15060
|
+
});
|
|
15061
|
+
}
|
|
15062
|
+
log8.info(`[${PLUGIN_NAME15}] 已回写 ${snapshot.length} 条 inflight`);
|
|
15063
|
+
} catch (err) {
|
|
15064
|
+
log8.error(`[${PLUGIN_NAME15}] command.execute.before 异常(已隔离)`, {
|
|
15065
|
+
error: err instanceof Error ? err.message : String(err)
|
|
15066
|
+
});
|
|
15067
|
+
}
|
|
15068
|
+
}
|
|
15069
|
+
};
|
|
15070
|
+
};
|
|
15071
|
+
var handler15 = parallelStatusServer;
|
|
15072
|
+
|
|
15073
|
+
// plugins/pwsh-utf8.ts
|
|
15074
|
+
init_opencode_plugin_helpers();
|
|
15075
|
+
var PLUGIN_NAME16 = "pwsh-utf8";
|
|
15076
|
+
logLifecycle(PLUGIN_NAME16, "import", {});
|
|
14738
15077
|
var PRELUDE = "chcp 65001 *> $null; " + "[Console]::OutputEncoding = [System.Text.UTF8Encoding]::new(); " + "$OutputEncoding = [System.Text.UTF8Encoding]::new(); ";
|
|
14739
15078
|
function prependUtf8Prelude(command) {
|
|
14740
15079
|
if (typeof command !== "string")
|
|
@@ -14747,14 +15086,14 @@ function prependUtf8Prelude(command) {
|
|
|
14747
15086
|
return command;
|
|
14748
15087
|
return PRELUDE + command;
|
|
14749
15088
|
}
|
|
14750
|
-
var
|
|
15089
|
+
var handler16 = async (_ctx) => {
|
|
14751
15090
|
const enabled = process.platform === "win32" && process.env.CODEFORGE_DISABLE_PWSH_UTF8 !== "1";
|
|
14752
|
-
logLifecycle(
|
|
15091
|
+
logLifecycle(PLUGIN_NAME16, "activate", { enabled, platform: process.platform });
|
|
14753
15092
|
if (!enabled)
|
|
14754
15093
|
return {};
|
|
14755
15094
|
return {
|
|
14756
15095
|
"tool.execute.before": async (input, output) => {
|
|
14757
|
-
await safeAsync(
|
|
15096
|
+
await safeAsync(PLUGIN_NAME16, "tool.execute.before", async () => {
|
|
14758
15097
|
if (input.tool !== "bash")
|
|
14759
15098
|
return;
|
|
14760
15099
|
const args = output.args ?? {};
|
|
@@ -14763,7 +15102,7 @@ var handler14 = async (_ctx) => {
|
|
|
14763
15102
|
if (next !== undefined && next !== original) {
|
|
14764
15103
|
args["command"] = next;
|
|
14765
15104
|
output.args = args;
|
|
14766
|
-
safeWriteLog(
|
|
15105
|
+
safeWriteLog(PLUGIN_NAME16, {
|
|
14767
15106
|
hook: "tool.execute.before",
|
|
14768
15107
|
tool: input.tool,
|
|
14769
15108
|
callID: input.callID,
|
|
@@ -14956,7 +15295,7 @@ function buildRecoveryPlan(events, meta = { id: null }, opts = {}) {
|
|
|
14956
15295
|
});
|
|
14957
15296
|
}
|
|
14958
15297
|
}
|
|
14959
|
-
const
|
|
15298
|
+
const inflight3 = [...toolMap.values()].sort((a, b) => b.last_ts - a.last_ts);
|
|
14960
15299
|
const proposed = window.some((t) => PENDING_CHANGES_TOOLS.has(t.tool));
|
|
14961
15300
|
const applied = window.some((t) => APPLY_TOOLS.has(t.tool) && t.ok);
|
|
14962
15301
|
const pending_changes_likely = proposed && !applied;
|
|
@@ -14980,7 +15319,7 @@ function buildRecoveryPlan(events, meta = { id: null }, opts = {}) {
|
|
|
14980
15319
|
idleMs,
|
|
14981
15320
|
lastUser,
|
|
14982
15321
|
lastAgent,
|
|
14983
|
-
inflight:
|
|
15322
|
+
inflight: inflight3,
|
|
14984
15323
|
pending_changes_likely,
|
|
14985
15324
|
open_subtasks_likely,
|
|
14986
15325
|
reason
|
|
@@ -14991,7 +15330,7 @@ function buildRecoveryPlan(events, meta = { id: null }, opts = {}) {
|
|
|
14991
15330
|
idle_ms: idleMs,
|
|
14992
15331
|
last_user_intent: lastUser,
|
|
14993
15332
|
last_agent: lastAgent,
|
|
14994
|
-
inflight_tools:
|
|
15333
|
+
inflight_tools: inflight3,
|
|
14995
15334
|
pending_changes_likely,
|
|
14996
15335
|
open_subtasks_likely,
|
|
14997
15336
|
summary
|
|
@@ -15096,8 +15435,8 @@ function isRecoveryWorthShowing(plan) {
|
|
|
15096
15435
|
}
|
|
15097
15436
|
|
|
15098
15437
|
// plugins/session-recovery.ts
|
|
15099
|
-
var
|
|
15100
|
-
logLifecycle(
|
|
15438
|
+
var PLUGIN_NAME17 = "session-recovery";
|
|
15439
|
+
logLifecycle(PLUGIN_NAME17, "import", {});
|
|
15101
15440
|
async function processSessionStart(currentSessionId, opts = {}) {
|
|
15102
15441
|
if (opts.disabled) {
|
|
15103
15442
|
return { ok: true, injected: false, reason: "disabled" };
|
|
@@ -15107,7 +15446,7 @@ async function processSessionStart(currentSessionId, opts = {}) {
|
|
|
15107
15446
|
excludeIds.add(currentSessionId);
|
|
15108
15447
|
const r = await scanLastSession({ ...opts, excludeIds: [...excludeIds] });
|
|
15109
15448
|
if (!r.ok) {
|
|
15110
|
-
opts.log?.warn?.(`[${
|
|
15449
|
+
opts.log?.warn?.(`[${PLUGIN_NAME17}] 扫描失败:${r.error}`);
|
|
15111
15450
|
return { ok: false, injected: false, reason: "scan_error", error: r.error };
|
|
15112
15451
|
}
|
|
15113
15452
|
const plan = r.plan;
|
|
@@ -15121,7 +15460,7 @@ async function processSessionStart(currentSessionId, opts = {}) {
|
|
|
15121
15460
|
await opts.injectRecovery(injection);
|
|
15122
15461
|
} catch (err) {
|
|
15123
15462
|
const msg = err instanceof Error ? err.message : String(err);
|
|
15124
|
-
opts.log?.warn?.(`[${
|
|
15463
|
+
opts.log?.warn?.(`[${PLUGIN_NAME17}] injectRecovery 异常:${msg}`);
|
|
15125
15464
|
return { ok: false, injected: false, plan, reason: "inject_error", error: msg };
|
|
15126
15465
|
}
|
|
15127
15466
|
}
|
|
@@ -15150,13 +15489,13 @@ function renderPrompt(plan) {
|
|
|
15150
15489
|
return lines.join(`
|
|
15151
15490
|
`);
|
|
15152
15491
|
}
|
|
15153
|
-
var
|
|
15492
|
+
var log8 = makePluginLogger(PLUGIN_NAME17);
|
|
15154
15493
|
var _lastInjection = null;
|
|
15155
15494
|
var sessionRecoveryServer = async (ctx) => {
|
|
15156
|
-
logLifecycle(
|
|
15495
|
+
logLifecycle(PLUGIN_NAME17, "activate", { directory: ctx.directory });
|
|
15157
15496
|
return {
|
|
15158
15497
|
event: async ({ event }) => {
|
|
15159
|
-
await safeAsync(
|
|
15498
|
+
await safeAsync(PLUGIN_NAME17, "event", async () => {
|
|
15160
15499
|
const e = event;
|
|
15161
15500
|
if (!e || typeof e.type !== "string")
|
|
15162
15501
|
return;
|
|
@@ -15166,12 +15505,12 @@ var sessionRecoveryServer = async (ctx) => {
|
|
|
15166
15505
|
const root = typeof e.properties?.["root"] === "string" ? e.properties["root"] : ctx.directory ?? process.cwd();
|
|
15167
15506
|
const r = await processSessionStart(sid, {
|
|
15168
15507
|
root,
|
|
15169
|
-
log:
|
|
15508
|
+
log: log8,
|
|
15170
15509
|
injectRecovery: (inj) => {
|
|
15171
15510
|
_lastInjection = inj;
|
|
15172
15511
|
}
|
|
15173
15512
|
});
|
|
15174
|
-
safeWriteLog(
|
|
15513
|
+
safeWriteLog(PLUGIN_NAME17, {
|
|
15175
15514
|
hook: "event",
|
|
15176
15515
|
type: "session.start",
|
|
15177
15516
|
ok: r.ok,
|
|
@@ -15180,234 +15519,13 @@ var sessionRecoveryServer = async (ctx) => {
|
|
|
15180
15519
|
last_session_id: r.plan?.last_session_id
|
|
15181
15520
|
});
|
|
15182
15521
|
if (r.injected && r.plan) {
|
|
15183
|
-
|
|
15522
|
+
log8.info(`[${PLUGIN_NAME17}] 注入恢复提示(last=${r.plan.last_session_id?.slice(0, 8) ?? "?"}, reason=${r.plan.reason})`);
|
|
15184
15523
|
}
|
|
15185
15524
|
});
|
|
15186
15525
|
}
|
|
15187
15526
|
};
|
|
15188
15527
|
};
|
|
15189
|
-
var
|
|
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;
|
|
15382
|
-
}
|
|
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
|
-
});
|
|
15407
|
-
}
|
|
15408
|
-
};
|
|
15409
|
-
};
|
|
15410
|
-
var handler16 = subtaskHeartbeatServer;
|
|
15528
|
+
var handler17 = sessionRecoveryServer;
|
|
15411
15529
|
|
|
15412
15530
|
// plugins/subtasks.ts
|
|
15413
15531
|
import { promises as fs10 } from "node:fs";
|
|
@@ -15437,6 +15555,18 @@ async function schedule(opts) {
|
|
|
15437
15555
|
const results = new Array(opts.subtasks.length);
|
|
15438
15556
|
let nextIdx = 0;
|
|
15439
15557
|
const workers = [];
|
|
15558
|
+
const fireFinish = async (i, res) => {
|
|
15559
|
+
if (!opts.onSubtaskFinish)
|
|
15560
|
+
return;
|
|
15561
|
+
try {
|
|
15562
|
+
await opts.onSubtaskFinish(res, i);
|
|
15563
|
+
} catch (err) {
|
|
15564
|
+
log9("warn", `[parallel] onSubtaskFinish 抛错(已隔离)`, {
|
|
15565
|
+
id: res.id,
|
|
15566
|
+
error: describe4(err)
|
|
15567
|
+
});
|
|
15568
|
+
}
|
|
15569
|
+
};
|
|
15440
15570
|
const runOne = async (i, spec) => {
|
|
15441
15571
|
const subStart = now();
|
|
15442
15572
|
let alloc;
|
|
@@ -15444,7 +15574,7 @@ async function schedule(opts) {
|
|
|
15444
15574
|
try {
|
|
15445
15575
|
alloc = await opts.deps.allocateWorktree(spec.id);
|
|
15446
15576
|
} catch (err) {
|
|
15447
|
-
|
|
15577
|
+
const res2 = {
|
|
15448
15578
|
id: spec.id,
|
|
15449
15579
|
ok: false,
|
|
15450
15580
|
summary: clamp(`worktree 分配失败:${describe4(err)}`, limit),
|
|
@@ -15452,9 +15582,21 @@ async function schedule(opts) {
|
|
|
15452
15582
|
duration_ms: now() - subStart,
|
|
15453
15583
|
error: describe4(err)
|
|
15454
15584
|
};
|
|
15585
|
+
results[i] = res2;
|
|
15586
|
+
await fireFinish(i, res2);
|
|
15455
15587
|
return;
|
|
15456
15588
|
}
|
|
15457
15589
|
}
|
|
15590
|
+
if (opts.onSubtaskStart) {
|
|
15591
|
+
try {
|
|
15592
|
+
await opts.onSubtaskStart(spec, i);
|
|
15593
|
+
} catch (err) {
|
|
15594
|
+
log9("warn", `[parallel] onSubtaskStart 抛错(已隔离)`, {
|
|
15595
|
+
id: spec.id,
|
|
15596
|
+
error: describe4(err)
|
|
15597
|
+
});
|
|
15598
|
+
}
|
|
15599
|
+
}
|
|
15458
15600
|
const ctl = new AbortController;
|
|
15459
15601
|
const cascade = () => ctl.abort();
|
|
15460
15602
|
if (globalCtl.signal.aborted)
|
|
@@ -15507,6 +15649,7 @@ async function schedule(opts) {
|
|
|
15507
15649
|
}
|
|
15508
15650
|
}
|
|
15509
15651
|
results[i] = res;
|
|
15652
|
+
await fireFinish(i, res);
|
|
15510
15653
|
};
|
|
15511
15654
|
const launch = async () => {
|
|
15512
15655
|
while (true) {
|
|
@@ -15515,13 +15658,15 @@ async function schedule(opts) {
|
|
|
15515
15658
|
return;
|
|
15516
15659
|
if (globalCtl.signal.aborted) {
|
|
15517
15660
|
const spec = opts.subtasks[i];
|
|
15518
|
-
|
|
15661
|
+
const res = {
|
|
15519
15662
|
id: spec.id,
|
|
15520
15663
|
ok: false,
|
|
15521
15664
|
summary: clamp("调度器已取消,未启动", limit),
|
|
15522
15665
|
status: "cancelled",
|
|
15523
15666
|
duration_ms: 0
|
|
15524
15667
|
};
|
|
15668
|
+
results[i] = res;
|
|
15669
|
+
await fireFinish(i, res);
|
|
15525
15670
|
continue;
|
|
15526
15671
|
}
|
|
15527
15672
|
await runOne(i, opts.subtasks[i]);
|
|
@@ -15646,6 +15791,7 @@ function pickStatus(r, taskAborted, globalAborted) {
|
|
|
15646
15791
|
}
|
|
15647
15792
|
|
|
15648
15793
|
// lib/opencode-runner.ts
|
|
15794
|
+
init_opencode_plugin_helpers();
|
|
15649
15795
|
function makeOpencodeRunner(opts) {
|
|
15650
15796
|
const log9 = opts.log ?? (() => {});
|
|
15651
15797
|
return async (spec, runCtx) => {
|
|
@@ -15802,20 +15948,20 @@ async function withTimeout2(p, ms, signal) {
|
|
|
15802
15948
|
throw err;
|
|
15803
15949
|
}
|
|
15804
15950
|
}
|
|
15805
|
-
return await new Promise((
|
|
15951
|
+
return await new Promise((resolve11, reject) => {
|
|
15806
15952
|
let settled = false;
|
|
15807
15953
|
const timer = setTimeout(() => {
|
|
15808
15954
|
if (settled)
|
|
15809
15955
|
return;
|
|
15810
15956
|
settled = true;
|
|
15811
|
-
|
|
15957
|
+
resolve11({ kind: "timeout" });
|
|
15812
15958
|
}, ms);
|
|
15813
15959
|
const onAbort = () => {
|
|
15814
15960
|
if (settled)
|
|
15815
15961
|
return;
|
|
15816
15962
|
settled = true;
|
|
15817
15963
|
clearTimeout(timer);
|
|
15818
|
-
|
|
15964
|
+
resolve11({ kind: "aborted" });
|
|
15819
15965
|
};
|
|
15820
15966
|
signal?.addEventListener("abort", onAbort, { once: true });
|
|
15821
15967
|
p.then((value) => {
|
|
@@ -15824,7 +15970,7 @@ async function withTimeout2(p, ms, signal) {
|
|
|
15824
15970
|
settled = true;
|
|
15825
15971
|
clearTimeout(timer);
|
|
15826
15972
|
signal?.removeEventListener("abort", onAbort);
|
|
15827
|
-
|
|
15973
|
+
resolve11({ kind: "ok", value });
|
|
15828
15974
|
}, (err) => {
|
|
15829
15975
|
if (settled)
|
|
15830
15976
|
return;
|
|
@@ -15860,11 +16006,51 @@ function clip4(s, max) {
|
|
|
15860
16006
|
return "";
|
|
15861
16007
|
return s.length <= max ? s : s.slice(0, max - 1) + "…";
|
|
15862
16008
|
}
|
|
16009
|
+
async function sendParentNotice(client, sessionID, text, opts = {}) {
|
|
16010
|
+
const log9 = opts.log ?? (() => {});
|
|
16011
|
+
if (!client?.session) {
|
|
16012
|
+
log9("warn", "[sendParentNotice] client.session 不可用,noop");
|
|
16013
|
+
return false;
|
|
16014
|
+
}
|
|
16015
|
+
const sessionAny = client.session;
|
|
16016
|
+
if (typeof sessionAny.promptAsync !== "function") {
|
|
16017
|
+
log9("warn", "[sendParentNotice] promptAsync 不可用(SDK 太老?),noop");
|
|
16018
|
+
return false;
|
|
16019
|
+
}
|
|
16020
|
+
try {
|
|
16021
|
+
const res = await sessionAny.promptAsync({
|
|
16022
|
+
path: { id: sessionID },
|
|
16023
|
+
query: opts.directory ? { directory: opts.directory } : undefined,
|
|
16024
|
+
body: {
|
|
16025
|
+
noReply: true,
|
|
16026
|
+
parts: [
|
|
16027
|
+
{
|
|
16028
|
+
id: makePartId(),
|
|
16029
|
+
type: "text",
|
|
16030
|
+
text,
|
|
16031
|
+
synthetic: false,
|
|
16032
|
+
ignored: false
|
|
16033
|
+
}
|
|
16034
|
+
]
|
|
16035
|
+
}
|
|
16036
|
+
});
|
|
16037
|
+
if (res && typeof res === "object" && "error" in res && res.error) {
|
|
16038
|
+
log9("warn", "[sendParentNotice] promptAsync 返回 error", { error: res.error });
|
|
16039
|
+
return false;
|
|
16040
|
+
}
|
|
16041
|
+
return true;
|
|
16042
|
+
} catch (err) {
|
|
16043
|
+
log9("warn", "[sendParentNotice] 抛错(已隔离)", {
|
|
16044
|
+
error: err instanceof Error ? err.message : String(err)
|
|
16045
|
+
});
|
|
16046
|
+
return false;
|
|
16047
|
+
}
|
|
16048
|
+
}
|
|
15863
16049
|
|
|
15864
16050
|
// plugins/subtasks.ts
|
|
15865
16051
|
init_opencode_plugin_helpers();
|
|
15866
16052
|
init_runtime_paths();
|
|
15867
|
-
var
|
|
16053
|
+
var PLUGIN_NAME18 = "subtasks";
|
|
15868
16054
|
function getLogFile(root = process.cwd()) {
|
|
15869
16055
|
return path13.join(runtimeDir(root), "logs", "subtasks.log");
|
|
15870
16056
|
}
|
|
@@ -15943,18 +16129,49 @@ async function handleParallelCommand(raw) {
|
|
|
15943
16129
|
specs = splitDescriptions(args.description, { parentId });
|
|
15944
16130
|
}
|
|
15945
16131
|
if (specs.length === 0) {
|
|
15946
|
-
log9?.warn(`[${
|
|
16132
|
+
log9?.warn(`[${PLUGIN_NAME18}] /parallel 缺有效子任务`);
|
|
15947
16133
|
await safeReply2(ctx, "⚠ /parallel 需要至少 1 个子任务(用 ; 或换行分隔)");
|
|
15948
16134
|
return { ok: false, reason: "no_subtasks" };
|
|
15949
16135
|
}
|
|
16136
|
+
const maxConcurrency = clampInt(args.maxConcurrency, 1, 16, 4);
|
|
16137
|
+
const totalTimeoutMs = clampInt(args.totalTimeout_ms, 1000, 60 * 60000, 30 * 60000);
|
|
16138
|
+
const canNotice = Boolean(ctx.client && ctx.parentSessionID);
|
|
16139
|
+
if (canNotice) {
|
|
16140
|
+
const lines = [
|
|
16141
|
+
`\uD83D\uDE80 /parallel 已派出 ${specs.length} 个子任务(并发=${maxConcurrency}):`,
|
|
16142
|
+
...specs.map((s, i) => ` ${i + 1}. \`${s.id}\` — ${s.description}`),
|
|
16143
|
+
"",
|
|
16144
|
+
"\uD83D\uDCA1 用 /parallel-status 随时查进度;TUI 按 Ctrl+→ 进子 session 看实时。"
|
|
16145
|
+
];
|
|
16146
|
+
await sendParentNotice(ctx.client, ctx.parentSessionID, lines.join(`
|
|
16147
|
+
`), {
|
|
16148
|
+
directory: ctx.directory,
|
|
16149
|
+
log: (lvl, msg, data) => {
|
|
16150
|
+
if (lvl === "error")
|
|
16151
|
+
log9?.error(msg, data);
|
|
16152
|
+
else if (lvl === "warn")
|
|
16153
|
+
log9?.warn(msg, data);
|
|
16154
|
+
else
|
|
16155
|
+
log9?.info(msg, data);
|
|
16156
|
+
}
|
|
16157
|
+
});
|
|
16158
|
+
}
|
|
15950
16159
|
const runner = ctx.runner ?? mockRunner;
|
|
15951
16160
|
let result;
|
|
15952
16161
|
try {
|
|
15953
16162
|
result = await schedule({
|
|
15954
16163
|
parentId,
|
|
15955
16164
|
subtasks: specs,
|
|
15956
|
-
maxConcurrency
|
|
15957
|
-
totalTimeout_ms:
|
|
16165
|
+
maxConcurrency,
|
|
16166
|
+
totalTimeout_ms: totalTimeoutMs,
|
|
16167
|
+
onSubtaskStart: canNotice ? async (spec, idx) => {
|
|
16168
|
+
await sendParentNotice(ctx.client, ctx.parentSessionID, `▶ 子任务 ${idx + 1}/${specs.length} 启动: \`${spec.id}\` — ${spec.description}`, { directory: ctx.directory });
|
|
16169
|
+
} : undefined,
|
|
16170
|
+
onSubtaskFinish: canNotice ? async (res, idx) => {
|
|
16171
|
+
const emoji = res.status === "success" ? "✅" : res.status === "need_review" ? "⚠" : res.status === "timeout" ? "⏱" : res.status === "cancelled" ? "\uD83D\uDDD1" : "❌";
|
|
16172
|
+
const fileCount = res.changedFiles?.length ?? 0;
|
|
16173
|
+
await sendParentNotice(ctx.client, ctx.parentSessionID, `${emoji} 子任务 ${idx + 1}/${specs.length} 完成 [${res.status}] \`${res.id}\` (${res.duration_ms}ms, files=${fileCount})`, { directory: ctx.directory });
|
|
16174
|
+
} : undefined,
|
|
15958
16175
|
deps: {
|
|
15959
16176
|
runSubtask: runner,
|
|
15960
16177
|
allocateWorktree: ctx.allocateWorktree,
|
|
@@ -15970,7 +16187,7 @@ async function handleParallelCommand(raw) {
|
|
|
15970
16187
|
});
|
|
15971
16188
|
} catch (err) {
|
|
15972
16189
|
const msg = err instanceof Error ? err.message : String(err);
|
|
15973
|
-
log9?.error(`[${
|
|
16190
|
+
log9?.error(`[${PLUGIN_NAME18}] schedule 抛错`, { error: msg });
|
|
15974
16191
|
await safeReply2(ctx, `❌ 并发调度失败:${msg}`);
|
|
15975
16192
|
return { ok: false, reason: msg };
|
|
15976
16193
|
}
|
|
@@ -15978,7 +16195,7 @@ async function handleParallelCommand(raw) {
|
|
|
15978
16195
|
const summaryLines = [head, "", "```", result.digest.text, "```"];
|
|
15979
16196
|
await safeReply2(ctx, summaryLines.join(`
|
|
15980
16197
|
`));
|
|
15981
|
-
log9?.info(`[${
|
|
16198
|
+
log9?.info(`[${PLUGIN_NAME18}] schedule 完成`, {
|
|
15982
16199
|
parentId,
|
|
15983
16200
|
success: result.digest.success,
|
|
15984
16201
|
failed: result.digest.failed,
|
|
@@ -15988,7 +16205,7 @@ async function handleParallelCommand(raw) {
|
|
|
15988
16205
|
try {
|
|
15989
16206
|
await ctx.onCompleted(result);
|
|
15990
16207
|
} catch (err) {
|
|
15991
|
-
log9?.warn(`[${
|
|
16208
|
+
log9?.warn(`[${PLUGIN_NAME18}] onCompleted hook 抛错(已隔离)`, {
|
|
15992
16209
|
error: err instanceof Error ? err.message : String(err)
|
|
15993
16210
|
});
|
|
15994
16211
|
}
|
|
@@ -16019,14 +16236,17 @@ async function maybeHandleMessage(raw) {
|
|
|
16019
16236
|
reply: ctx.reply,
|
|
16020
16237
|
runner: ctx.runner,
|
|
16021
16238
|
allocateWorktree: ctx.allocateWorktree,
|
|
16022
|
-
log: ctx.log
|
|
16239
|
+
log: ctx.log,
|
|
16240
|
+
client: ctx.client,
|
|
16241
|
+
parentSessionID: ctx.parentSessionID,
|
|
16242
|
+
directory: ctx.directory
|
|
16023
16243
|
});
|
|
16024
16244
|
}
|
|
16025
16245
|
async function writeLog(level, msg, data) {
|
|
16026
16246
|
const line = JSON.stringify({
|
|
16027
16247
|
ts: new Date().toISOString(),
|
|
16028
16248
|
level,
|
|
16029
|
-
plugin:
|
|
16249
|
+
plugin: PLUGIN_NAME18,
|
|
16030
16250
|
msg,
|
|
16031
16251
|
data
|
|
16032
16252
|
}) + `
|
|
@@ -16037,11 +16257,11 @@ async function writeLog(level, msg, data) {
|
|
|
16037
16257
|
await fs10.appendFile(logFile, line, "utf8");
|
|
16038
16258
|
} catch {}
|
|
16039
16259
|
}
|
|
16040
|
-
logLifecycle(
|
|
16260
|
+
logLifecycle(PLUGIN_NAME18, "import");
|
|
16041
16261
|
var subtasksServer = async (ctx) => {
|
|
16042
|
-
const log9 = makePluginLogger(
|
|
16262
|
+
const log9 = makePluginLogger(PLUGIN_NAME18);
|
|
16043
16263
|
const client = ctx?.client ?? undefined;
|
|
16044
|
-
logLifecycle(
|
|
16264
|
+
logLifecycle(PLUGIN_NAME18, "activate", {
|
|
16045
16265
|
directory: ctx.directory,
|
|
16046
16266
|
hasClient: Boolean(client)
|
|
16047
16267
|
});
|
|
@@ -16062,6 +16282,9 @@ var subtasksServer = async (ctx) => {
|
|
|
16062
16282
|
return Promise.resolve();
|
|
16063
16283
|
},
|
|
16064
16284
|
log: log9,
|
|
16285
|
+
client,
|
|
16286
|
+
parentSessionID: input.sessionID,
|
|
16287
|
+
directory: ctx.directory,
|
|
16065
16288
|
runner: client ? makeOpencodeRunner({
|
|
16066
16289
|
client,
|
|
16067
16290
|
parentSessionID: input.sessionID,
|
|
@@ -16087,7 +16310,7 @@ var subtasksServer = async (ctx) => {
|
|
|
16087
16310
|
if (replyLines.length > 0 && Array.isArray(output?.parts)) {
|
|
16088
16311
|
output.parts.length = 0;
|
|
16089
16312
|
output.parts.push({
|
|
16090
|
-
id:
|
|
16313
|
+
id: makePartId(),
|
|
16091
16314
|
sessionID: input.sessionID,
|
|
16092
16315
|
messageID: "",
|
|
16093
16316
|
type: "text",
|
|
@@ -16098,20 +16321,20 @@ var subtasksServer = async (ctx) => {
|
|
|
16098
16321
|
});
|
|
16099
16322
|
}
|
|
16100
16323
|
} catch (err) {
|
|
16101
|
-
log9.error(`[${
|
|
16324
|
+
log9.error(`[${PLUGIN_NAME18}] command.execute.before 异常(已隔离)`, {
|
|
16102
16325
|
error: err instanceof Error ? err.message : String(err)
|
|
16103
16326
|
});
|
|
16104
16327
|
}
|
|
16105
16328
|
}
|
|
16106
16329
|
};
|
|
16107
16330
|
};
|
|
16108
|
-
var
|
|
16331
|
+
var handler18 = subtasksServer;
|
|
16109
16332
|
|
|
16110
16333
|
// plugins/terminal-monitor.ts
|
|
16111
16334
|
init_opencode_plugin_helpers();
|
|
16112
16335
|
import * as crypto5 from "node:crypto";
|
|
16113
|
-
var
|
|
16114
|
-
logLifecycle(
|
|
16336
|
+
var PLUGIN_NAME19 = "terminal-monitor";
|
|
16337
|
+
logLifecycle(PLUGIN_NAME19, "import", {});
|
|
16115
16338
|
var DEFAULT_CONFIG7 = {
|
|
16116
16339
|
minScore: 0.6,
|
|
16117
16340
|
cooldownMs: 30000,
|
|
@@ -16393,17 +16616,17 @@ function describeError(err) {
|
|
|
16393
16616
|
return String(err);
|
|
16394
16617
|
}
|
|
16395
16618
|
}
|
|
16396
|
-
var log9 = makePluginLogger(
|
|
16619
|
+
var log9 = makePluginLogger(PLUGIN_NAME19);
|
|
16397
16620
|
var lru = new FingerprintLRU2;
|
|
16398
16621
|
var _lastNotification = null;
|
|
16399
16622
|
var terminalMonitorServer = async (ctx) => {
|
|
16400
|
-
logLifecycle(
|
|
16623
|
+
logLifecycle(PLUGIN_NAME19, "activate", {
|
|
16401
16624
|
directory: ctx.directory,
|
|
16402
16625
|
triggerEventTypes: ["terminal.output", "terminal.exit"]
|
|
16403
16626
|
});
|
|
16404
16627
|
return {
|
|
16405
16628
|
event: async ({ event }) => {
|
|
16406
|
-
await safeAsync(
|
|
16629
|
+
await safeAsync(PLUGIN_NAME19, "event", async () => {
|
|
16407
16630
|
const e = event;
|
|
16408
16631
|
if (!e || typeof e.type !== "string")
|
|
16409
16632
|
return;
|
|
@@ -16417,7 +16640,7 @@ var terminalMonitorServer = async (ctx) => {
|
|
|
16417
16640
|
_lastNotification = msg;
|
|
16418
16641
|
}
|
|
16419
16642
|
});
|
|
16420
|
-
safeWriteLog(
|
|
16643
|
+
safeWriteLog(PLUGIN_NAME19, {
|
|
16421
16644
|
hook: "event",
|
|
16422
16645
|
type: e.type,
|
|
16423
16646
|
notified: r.notified,
|
|
@@ -16425,18 +16648,18 @@ var terminalMonitorServer = async (ctx) => {
|
|
|
16425
16648
|
findings: r.notification?.findings.length ?? 0
|
|
16426
16649
|
});
|
|
16427
16650
|
if (r.notified && r.notification) {
|
|
16428
|
-
log9.info(`[${
|
|
16651
|
+
log9.info(`[${PLUGIN_NAME19}] 反馈 ${r.notification.findings.length} 条 → ${r.notification.summary}`);
|
|
16429
16652
|
}
|
|
16430
16653
|
});
|
|
16431
16654
|
}
|
|
16432
16655
|
};
|
|
16433
16656
|
};
|
|
16434
|
-
var
|
|
16657
|
+
var handler19 = terminalMonitorServer;
|
|
16435
16658
|
|
|
16436
16659
|
// plugins/token-manager.ts
|
|
16437
16660
|
init_opencode_plugin_helpers();
|
|
16438
|
-
var
|
|
16439
|
-
logLifecycle(
|
|
16661
|
+
var PLUGIN_NAME20 = "token-manager";
|
|
16662
|
+
logLifecycle(PLUGIN_NAME20, "import", {});
|
|
16440
16663
|
async function handleMessageBefore(raw, log10, defaults) {
|
|
16441
16664
|
const ctx = raw ?? {};
|
|
16442
16665
|
if (!Array.isArray(ctx.messages) || ctx.messages.length === 0)
|
|
@@ -16456,21 +16679,21 @@ async function handleMessageBefore(raw, log10, defaults) {
|
|
|
16456
16679
|
};
|
|
16457
16680
|
if (r.compressed) {
|
|
16458
16681
|
ctx.messages = r.messages;
|
|
16459
|
-
log10?.info(`[${
|
|
16682
|
+
log10?.info(`[${PLUGIN_NAME20}] 压缩 ${r.before.count}→${r.after.count} 条 / ${r.before.tokens}→${r.after.tokens} tokens`);
|
|
16460
16683
|
}
|
|
16461
16684
|
return r;
|
|
16462
16685
|
} catch (err) {
|
|
16463
|
-
log10?.warn(`[${
|
|
16686
|
+
log10?.warn(`[${PLUGIN_NAME20}] 压缩异常(已隔离)`, {
|
|
16464
16687
|
error: err instanceof Error ? err.message : String(err)
|
|
16465
16688
|
});
|
|
16466
16689
|
return null;
|
|
16467
16690
|
}
|
|
16468
16691
|
}
|
|
16469
|
-
var log10 = makePluginLogger(
|
|
16692
|
+
var log10 = makePluginLogger(PLUGIN_NAME20);
|
|
16470
16693
|
var tokenManagerServer = async (ctx) => {
|
|
16471
16694
|
const rt = loadRuntimeSync();
|
|
16472
16695
|
const threshold = rt.runtime.context.condenser_threshold_ratio;
|
|
16473
|
-
logLifecycle(
|
|
16696
|
+
logLifecycle(PLUGIN_NAME20, "activate", {
|
|
16474
16697
|
directory: ctx.directory,
|
|
16475
16698
|
threshold,
|
|
16476
16699
|
target: DEFAULT_CONDENSE.target,
|
|
@@ -16479,7 +16702,7 @@ var tokenManagerServer = async (ctx) => {
|
|
|
16479
16702
|
});
|
|
16480
16703
|
return {
|
|
16481
16704
|
"experimental.chat.messages.transform": async (_input, output) => {
|
|
16482
|
-
await safeAsync(
|
|
16705
|
+
await safeAsync(PLUGIN_NAME20, "experimental.chat.messages.transform", async () => {
|
|
16483
16706
|
const list = output.messages;
|
|
16484
16707
|
if (!Array.isArray(list) || list.length === 0)
|
|
16485
16708
|
return;
|
|
@@ -16492,7 +16715,7 @@ var tokenManagerServer = async (ctx) => {
|
|
|
16492
16715
|
const r = await handleMessageBefore({ messages: flat }, log10, { threshold });
|
|
16493
16716
|
if (!r)
|
|
16494
16717
|
return;
|
|
16495
|
-
safeWriteLog(
|
|
16718
|
+
safeWriteLog(PLUGIN_NAME20, {
|
|
16496
16719
|
hook: "experimental.chat.messages.transform",
|
|
16497
16720
|
mode: "observe-only",
|
|
16498
16721
|
before_msgs: r.before.count,
|
|
@@ -16503,13 +16726,13 @@ var tokenManagerServer = async (ctx) => {
|
|
|
16503
16726
|
reason: r.reason
|
|
16504
16727
|
});
|
|
16505
16728
|
if (r.compressed) {
|
|
16506
|
-
log10.warn(`[${
|
|
16729
|
+
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
16730
|
}
|
|
16508
16731
|
});
|
|
16509
16732
|
}
|
|
16510
16733
|
};
|
|
16511
16734
|
};
|
|
16512
|
-
var
|
|
16735
|
+
var handler20 = tokenManagerServer;
|
|
16513
16736
|
|
|
16514
16737
|
// plugins/tool-policy.ts
|
|
16515
16738
|
init_opencode_plugin_helpers();
|
|
@@ -16752,8 +16975,8 @@ function checkFileAccess(acl, file, op) {
|
|
|
16752
16975
|
}
|
|
16753
16976
|
|
|
16754
16977
|
// plugins/tool-policy.ts
|
|
16755
|
-
var
|
|
16756
|
-
logLifecycle(
|
|
16978
|
+
var PLUGIN_NAME21 = "tool-policy";
|
|
16979
|
+
logLifecycle(PLUGIN_NAME21, "import", {});
|
|
16757
16980
|
var EMPTY_ACL = { whitelistMode: false };
|
|
16758
16981
|
function decideToolCall(ctx, cfg = {}) {
|
|
16759
16982
|
const fallbackMode = cfg.defaultMode ?? DEFAULT_RUNTIME.autonomy.default_mode;
|
|
@@ -16805,13 +17028,13 @@ function classifyToolKind(toolName) {
|
|
|
16805
17028
|
return "webfetch";
|
|
16806
17029
|
return "other";
|
|
16807
17030
|
}
|
|
16808
|
-
var log11 = makePluginLogger(
|
|
17031
|
+
var log11 = makePluginLogger(PLUGIN_NAME21);
|
|
16809
17032
|
var toolPolicyServer = async (ctx) => {
|
|
16810
17033
|
const directory = ctx.directory ?? process.cwd();
|
|
16811
17034
|
const cfg = await loadPolicy(directory);
|
|
16812
17035
|
const rt = loadRuntimeSync();
|
|
16813
17036
|
cfg.defaultMode = rt.runtime.autonomy.default_mode;
|
|
16814
|
-
logLifecycle(
|
|
17037
|
+
logLifecycle(PLUGIN_NAME21, "activate", {
|
|
16815
17038
|
directory,
|
|
16816
17039
|
acl_loaded: !!cfg.acl,
|
|
16817
17040
|
default_mode: cfg.defaultMode,
|
|
@@ -16819,7 +17042,7 @@ var toolPolicyServer = async (ctx) => {
|
|
|
16819
17042
|
});
|
|
16820
17043
|
return {
|
|
16821
17044
|
"tool.execute.before": async (input, output) => {
|
|
16822
|
-
await safeAsync(
|
|
17045
|
+
await safeAsync(PLUGIN_NAME21, "tool.execute.before", async () => {
|
|
16823
17046
|
const toolName = input.tool;
|
|
16824
17047
|
const argsObj = output.args ?? {};
|
|
16825
17048
|
const files = [];
|
|
@@ -16835,7 +17058,7 @@ var toolPolicyServer = async (ctx) => {
|
|
|
16835
17058
|
mode: cfg.defaultMode,
|
|
16836
17059
|
files: files.length ? files : undefined
|
|
16837
17060
|
}, cfg);
|
|
16838
|
-
safeWriteLog(
|
|
17061
|
+
safeWriteLog(PLUGIN_NAME21, {
|
|
16839
17062
|
hook: "tool.execute.before",
|
|
16840
17063
|
tool: toolName,
|
|
16841
17064
|
callID: input.callID,
|
|
@@ -16844,19 +17067,19 @@ var toolPolicyServer = async (ctx) => {
|
|
|
16844
17067
|
reasons: decision.reasons
|
|
16845
17068
|
});
|
|
16846
17069
|
if (decision.action === "deny") {
|
|
16847
|
-
log11.warn(`[${
|
|
17070
|
+
log11.warn(`[${PLUGIN_NAME21}] DENY ${toolName}: ${decision.reasons.join("; ")}`);
|
|
16848
17071
|
} else if (decision.action === "confirm") {
|
|
16849
|
-
log11.info(`[${
|
|
17072
|
+
log11.info(`[${PLUGIN_NAME21}] CONFIRM ${toolName}: ${decision.reasons.join("; ")}`);
|
|
16850
17073
|
}
|
|
16851
17074
|
});
|
|
16852
17075
|
}
|
|
16853
17076
|
};
|
|
16854
17077
|
};
|
|
16855
|
-
var
|
|
17078
|
+
var handler21 = toolPolicyServer;
|
|
16856
17079
|
|
|
16857
17080
|
// plugins/update-checker.ts
|
|
16858
17081
|
init_opencode_plugin_helpers();
|
|
16859
|
-
import { existsSync as
|
|
17082
|
+
import { existsSync as existsSync5 } from "node:fs";
|
|
16860
17083
|
import { homedir as homedir7 } from "node:os";
|
|
16861
17084
|
import { join as join15 } from "node:path";
|
|
16862
17085
|
|
|
@@ -16864,7 +17087,7 @@ import { join as join15 } from "node:path";
|
|
|
16864
17087
|
import { createHash as createHash6 } from "node:crypto";
|
|
16865
17088
|
import {
|
|
16866
17089
|
copyFileSync,
|
|
16867
|
-
existsSync as
|
|
17090
|
+
existsSync as existsSync4,
|
|
16868
17091
|
mkdirSync as mkdirSync3,
|
|
16869
17092
|
mkdtempSync,
|
|
16870
17093
|
readFileSync as readFileSync4,
|
|
@@ -16875,7 +17098,7 @@ import {
|
|
|
16875
17098
|
writeFileSync as writeFileSync2
|
|
16876
17099
|
} from "node:fs";
|
|
16877
17100
|
import { homedir as homedir6, tmpdir } from "node:os";
|
|
16878
|
-
import { dirname as
|
|
17101
|
+
import { dirname as dirname7, join as join14 } from "node:path";
|
|
16879
17102
|
import { fileURLToPath } from "node:url";
|
|
16880
17103
|
import * as https from "node:https";
|
|
16881
17104
|
import * as zlib from "node:zlib";
|
|
@@ -16883,7 +17106,7 @@ import * as zlib from "node:zlib";
|
|
|
16883
17106
|
// lib/version-injected.ts
|
|
16884
17107
|
function getInjectedVersion() {
|
|
16885
17108
|
try {
|
|
16886
|
-
const v = "0.3.
|
|
17109
|
+
const v = "0.3.9";
|
|
16887
17110
|
if (typeof v === "string" && /^\d+\.\d+\.\d+/.test(v)) {
|
|
16888
17111
|
return v;
|
|
16889
17112
|
}
|
|
@@ -16972,7 +17195,7 @@ function readLocalVersion() {
|
|
|
16972
17195
|
return injected;
|
|
16973
17196
|
try {
|
|
16974
17197
|
const here = fileURLToPath(import.meta.url);
|
|
16975
|
-
const root =
|
|
17198
|
+
const root = dirname7(dirname7(here));
|
|
16976
17199
|
const pkg = JSON.parse(readFileSync4(join14(root, "package.json"), "utf8"));
|
|
16977
17200
|
return typeof pkg.version === "string" ? pkg.version : "0.0.0";
|
|
16978
17201
|
} catch {
|
|
@@ -16987,7 +17210,7 @@ function defaultCacheFile() {
|
|
|
16987
17210
|
}
|
|
16988
17211
|
function readCache(file) {
|
|
16989
17212
|
try {
|
|
16990
|
-
if (!
|
|
17213
|
+
if (!existsSync4(file))
|
|
16991
17214
|
return null;
|
|
16992
17215
|
const raw = readFileSync4(file, "utf8");
|
|
16993
17216
|
const obj = JSON.parse(raw);
|
|
@@ -17001,7 +17224,7 @@ function readCache(file) {
|
|
|
17001
17224
|
}
|
|
17002
17225
|
function writeCache(file, entry) {
|
|
17003
17226
|
try {
|
|
17004
|
-
mkdirSync3(
|
|
17227
|
+
mkdirSync3(dirname7(file), { recursive: true });
|
|
17005
17228
|
writeFileSync2(file, JSON.stringify(entry, null, 2), "utf8");
|
|
17006
17229
|
} catch {}
|
|
17007
17230
|
}
|
|
@@ -17020,7 +17243,7 @@ function fetchLatestTagFromGitHub(repo) {
|
|
|
17020
17243
|
});
|
|
17021
17244
|
}
|
|
17022
17245
|
function getJsonWithRedirect(url, hopsLeft) {
|
|
17023
|
-
return new Promise((
|
|
17246
|
+
return new Promise((resolve11, reject) => {
|
|
17024
17247
|
const u = new URL(url);
|
|
17025
17248
|
const headers = {
|
|
17026
17249
|
"User-Agent": "codeforge-update-checker",
|
|
@@ -17044,12 +17267,12 @@ function getJsonWithRedirect(url, hopsLeft) {
|
|
|
17044
17267
|
return;
|
|
17045
17268
|
}
|
|
17046
17269
|
const next = new URL(res.headers.location, url).toString();
|
|
17047
|
-
getJsonWithRedirect(next, hopsLeft - 1).then(
|
|
17270
|
+
getJsonWithRedirect(next, hopsLeft - 1).then(resolve11, reject);
|
|
17048
17271
|
return;
|
|
17049
17272
|
}
|
|
17050
17273
|
if (status === 404) {
|
|
17051
17274
|
res.resume();
|
|
17052
|
-
|
|
17275
|
+
resolve11(null);
|
|
17053
17276
|
return;
|
|
17054
17277
|
}
|
|
17055
17278
|
if (status >= 400) {
|
|
@@ -17060,7 +17283,7 @@ function getJsonWithRedirect(url, hopsLeft) {
|
|
|
17060
17283
|
let body = "";
|
|
17061
17284
|
res.setEncoding("utf8");
|
|
17062
17285
|
res.on("data", (chunk) => body += chunk);
|
|
17063
|
-
res.on("end", () =>
|
|
17286
|
+
res.on("end", () => resolve11(body));
|
|
17064
17287
|
});
|
|
17065
17288
|
req.on("timeout", () => {
|
|
17066
17289
|
req.destroy();
|
|
@@ -17100,7 +17323,7 @@ async function fetchLatestFromNpm(opts) {
|
|
|
17100
17323
|
return { version, tarballUrl, integrity };
|
|
17101
17324
|
}
|
|
17102
17325
|
function defaultHttpFetcher(url, timeoutMs) {
|
|
17103
|
-
return new Promise((
|
|
17326
|
+
return new Promise((resolve11, reject) => {
|
|
17104
17327
|
const u = new URL(url);
|
|
17105
17328
|
const headers = {
|
|
17106
17329
|
"User-Agent": "codeforge-update-checker",
|
|
@@ -17117,7 +17340,7 @@ function defaultHttpFetcher(url, timeoutMs) {
|
|
|
17117
17340
|
const status = res.statusCode ?? 0;
|
|
17118
17341
|
if (status === 404) {
|
|
17119
17342
|
res.resume();
|
|
17120
|
-
|
|
17343
|
+
resolve11(null);
|
|
17121
17344
|
return;
|
|
17122
17345
|
}
|
|
17123
17346
|
if (status >= 400) {
|
|
@@ -17128,7 +17351,7 @@ function defaultHttpFetcher(url, timeoutMs) {
|
|
|
17128
17351
|
let body = "";
|
|
17129
17352
|
res.setEncoding("utf8");
|
|
17130
17353
|
res.on("data", (chunk) => body += chunk);
|
|
17131
|
-
res.on("end", () =>
|
|
17354
|
+
res.on("end", () => resolve11(body));
|
|
17132
17355
|
});
|
|
17133
17356
|
req.on("timeout", () => {
|
|
17134
17357
|
req.destroy();
|
|
@@ -17147,7 +17370,7 @@ async function downloadAndExtractBundle(opts) {
|
|
|
17147
17370
|
const tarBuf = zlib.gunzipSync(tarballBuf);
|
|
17148
17371
|
extractTarToDir(tarBuf, tmpRoot);
|
|
17149
17372
|
const bundlePath = join14(tmpRoot, "package", "dist", "index.js");
|
|
17150
|
-
if (!
|
|
17373
|
+
if (!existsSync4(bundlePath)) {
|
|
17151
17374
|
throw new Error(`bundle_not_found: ${bundlePath}`);
|
|
17152
17375
|
}
|
|
17153
17376
|
return { bundlePath, extractDir: tmpRoot };
|
|
@@ -17187,7 +17410,7 @@ function extractTarToDir(tarBuf, destRoot) {
|
|
|
17187
17410
|
if (typeFlag === "0" || typeFlag === "" || typeFlag === "\x00") {
|
|
17188
17411
|
const fileBuf = tarBuf.subarray(offset, offset + size);
|
|
17189
17412
|
const dest = join14(destRoot, fullName);
|
|
17190
|
-
mkdirSync3(
|
|
17413
|
+
mkdirSync3(dirname7(dest), { recursive: true });
|
|
17191
17414
|
writeFileSync2(dest, fileBuf);
|
|
17192
17415
|
} else if (typeFlag === "5") {
|
|
17193
17416
|
mkdirSync3(join14(destRoot, fullName), { recursive: true });
|
|
@@ -17199,7 +17422,7 @@ function defaultBinaryFetcher(url) {
|
|
|
17199
17422
|
return downloadBinary(url, 3);
|
|
17200
17423
|
}
|
|
17201
17424
|
function downloadBinary(url, hopsLeft) {
|
|
17202
|
-
return new Promise((
|
|
17425
|
+
return new Promise((resolve11, reject) => {
|
|
17203
17426
|
const u = new URL(url);
|
|
17204
17427
|
const req = https.request({
|
|
17205
17428
|
host: u.hostname,
|
|
@@ -17217,7 +17440,7 @@ function downloadBinary(url, hopsLeft) {
|
|
|
17217
17440
|
return;
|
|
17218
17441
|
}
|
|
17219
17442
|
const next = new URL(res.headers.location, url).toString();
|
|
17220
|
-
downloadBinary(next, hopsLeft - 1).then(
|
|
17443
|
+
downloadBinary(next, hopsLeft - 1).then(resolve11, reject);
|
|
17221
17444
|
return;
|
|
17222
17445
|
}
|
|
17223
17446
|
if (status >= 400) {
|
|
@@ -17227,7 +17450,7 @@ function downloadBinary(url, hopsLeft) {
|
|
|
17227
17450
|
}
|
|
17228
17451
|
const chunks = [];
|
|
17229
17452
|
res.on("data", (chunk) => chunks.push(chunk));
|
|
17230
|
-
res.on("end", () =>
|
|
17453
|
+
res.on("end", () => resolve11(Buffer.concat(chunks)));
|
|
17231
17454
|
});
|
|
17232
17455
|
req.on("timeout", () => {
|
|
17233
17456
|
req.destroy();
|
|
@@ -17240,16 +17463,16 @@ function downloadBinary(url, hopsLeft) {
|
|
|
17240
17463
|
function atomicReplaceBundle(opts) {
|
|
17241
17464
|
const { source, target, oldVersion } = opts;
|
|
17242
17465
|
const keep = opts.keepBackups ?? 3;
|
|
17243
|
-
if (!
|
|
17466
|
+
if (!existsSync4(source)) {
|
|
17244
17467
|
throw new Error(`atomic_source_missing: ${source}`);
|
|
17245
17468
|
}
|
|
17246
|
-
mkdirSync3(
|
|
17469
|
+
mkdirSync3(dirname7(target), { recursive: true });
|
|
17247
17470
|
const newPath = `${target}.new`;
|
|
17248
17471
|
const backupPath = `${target}.bak.${oldVersion}`;
|
|
17249
17472
|
let strategy = "rename";
|
|
17250
17473
|
try {
|
|
17251
17474
|
copyFileSync(source, newPath);
|
|
17252
|
-
if (
|
|
17475
|
+
if (existsSync4(target)) {
|
|
17253
17476
|
try {
|
|
17254
17477
|
renameSync(target, backupPath);
|
|
17255
17478
|
} catch (e) {
|
|
@@ -17285,7 +17508,7 @@ function atomicReplaceBundle(opts) {
|
|
|
17285
17508
|
return { backupPath, strategy };
|
|
17286
17509
|
} catch (e) {
|
|
17287
17510
|
try {
|
|
17288
|
-
if (
|
|
17511
|
+
if (existsSync4(newPath))
|
|
17289
17512
|
unlinkSync(newPath);
|
|
17290
17513
|
} catch {}
|
|
17291
17514
|
throw e;
|
|
@@ -17295,7 +17518,7 @@ function cleanupOldBackups(target, keep) {
|
|
|
17295
17518
|
if (keep <= 0)
|
|
17296
17519
|
return;
|
|
17297
17520
|
try {
|
|
17298
|
-
const dir =
|
|
17521
|
+
const dir = dirname7(target);
|
|
17299
17522
|
const base = target.substring(dir.length + 1);
|
|
17300
17523
|
const prefix = `${base}.bak.`;
|
|
17301
17524
|
const all = readdirSync(dir).filter((f) => f.startsWith(prefix)).map((f) => {
|
|
@@ -17323,7 +17546,7 @@ function loadCompatibility(opts) {
|
|
|
17323
17546
|
return null;
|
|
17324
17547
|
file = join14(root, "compatibility.json");
|
|
17325
17548
|
}
|
|
17326
|
-
if (!
|
|
17549
|
+
if (!existsSync4(file))
|
|
17327
17550
|
return null;
|
|
17328
17551
|
const raw = readFileSync4(file, "utf8");
|
|
17329
17552
|
const obj = JSON.parse(raw);
|
|
@@ -17346,7 +17569,7 @@ function loadCompatibility(opts) {
|
|
|
17346
17569
|
function inferPluginRoot() {
|
|
17347
17570
|
try {
|
|
17348
17571
|
const here = fileURLToPath(import.meta.url);
|
|
17349
|
-
return
|
|
17572
|
+
return dirname7(dirname7(here));
|
|
17350
17573
|
} catch {
|
|
17351
17574
|
return null;
|
|
17352
17575
|
}
|
|
@@ -17386,13 +17609,29 @@ function compareOpencodeVersion(opts) {
|
|
|
17386
17609
|
}
|
|
17387
17610
|
|
|
17388
17611
|
// plugins/update-checker.ts
|
|
17389
|
-
var
|
|
17612
|
+
var PLUGIN_NAME22 = "update-checker";
|
|
17390
17613
|
var PLUGIN_VERSION2 = "2.0.0";
|
|
17391
|
-
logLifecycle(
|
|
17614
|
+
logLifecycle(PLUGIN_NAME22, "import", { version: PLUGIN_VERSION2 });
|
|
17392
17615
|
var updateCheckerServer = async (ctx) => {
|
|
17616
|
+
const yieldResult = shouldYieldToLocalPlugin({ directory: ctx.directory });
|
|
17617
|
+
if (yieldResult.yield) {
|
|
17618
|
+
safeWriteLog(PLUGIN_NAME22, {
|
|
17619
|
+
level: "info",
|
|
17620
|
+
msg: "dev_mode_yield_skip",
|
|
17621
|
+
reason: yieldResult.reason,
|
|
17622
|
+
markerPath: yieldResult.markerPath,
|
|
17623
|
+
detail: formatYieldLog(yieldResult)
|
|
17624
|
+
});
|
|
17625
|
+
logLifecycle(PLUGIN_NAME22, "activate", {
|
|
17626
|
+
yield_to_local: true,
|
|
17627
|
+
yield_reason: yieldResult.reason,
|
|
17628
|
+
skipped: "auto_install + background_check"
|
|
17629
|
+
});
|
|
17630
|
+
return {};
|
|
17631
|
+
}
|
|
17393
17632
|
const rt = loadRuntimeSync();
|
|
17394
17633
|
const u = rt.runtime.update;
|
|
17395
|
-
logLifecycle(
|
|
17634
|
+
logLifecycle(PLUGIN_NAME22, "activate", {
|
|
17396
17635
|
version: PLUGIN_VERSION2,
|
|
17397
17636
|
auto_check_enabled: u.auto_check_enabled,
|
|
17398
17637
|
interval_hours: u.interval_hours,
|
|
@@ -17404,14 +17643,14 @@ var updateCheckerServer = async (ctx) => {
|
|
|
17404
17643
|
repo_fallback: u.repo,
|
|
17405
17644
|
config_source: "codeforge.json"
|
|
17406
17645
|
});
|
|
17407
|
-
await safeAsync(
|
|
17646
|
+
await safeAsync(PLUGIN_NAME22, "opencode_version_check", async () => {
|
|
17408
17647
|
const compat = loadCompatibility();
|
|
17409
17648
|
const opencodeVer = detectOpencodeVersion();
|
|
17410
17649
|
const verdict = compareOpencodeVersion({
|
|
17411
17650
|
currentOpencodeVer: opencodeVer,
|
|
17412
17651
|
compat
|
|
17413
17652
|
});
|
|
17414
|
-
safeWriteLog(
|
|
17653
|
+
safeWriteLog(PLUGIN_NAME22, {
|
|
17415
17654
|
level: "info",
|
|
17416
17655
|
msg: "opencode_version_check",
|
|
17417
17656
|
opencodeVer,
|
|
@@ -17428,14 +17667,14 @@ var updateCheckerServer = async (ctx) => {
|
|
|
17428
17667
|
}
|
|
17429
17668
|
});
|
|
17430
17669
|
if (!u.auto_check_enabled) {
|
|
17431
|
-
safeWriteLog(
|
|
17670
|
+
safeWriteLog(PLUGIN_NAME22, {
|
|
17432
17671
|
level: "info",
|
|
17433
17672
|
msg: "auto-check disabled (codeforge.json update.auto_check_enabled=false)"
|
|
17434
17673
|
});
|
|
17435
17674
|
return {};
|
|
17436
17675
|
}
|
|
17437
17676
|
setImmediate(() => {
|
|
17438
|
-
safeAsync(
|
|
17677
|
+
safeAsync(PLUGIN_NAME22, "checkAndMaybeUpdate", async () => {
|
|
17439
17678
|
const local = readLocalVersion();
|
|
17440
17679
|
let npmResult = null;
|
|
17441
17680
|
try {
|
|
@@ -17446,7 +17685,7 @@ var updateCheckerServer = async (ctx) => {
|
|
|
17446
17685
|
timeoutMs: 5000
|
|
17447
17686
|
});
|
|
17448
17687
|
} catch (e) {
|
|
17449
|
-
safeWriteLog(
|
|
17688
|
+
safeWriteLog(PLUGIN_NAME22, {
|
|
17450
17689
|
level: "warn",
|
|
17451
17690
|
msg: "npm_fetch_failed",
|
|
17452
17691
|
error: e.message
|
|
@@ -17455,7 +17694,7 @@ var updateCheckerServer = async (ctx) => {
|
|
|
17455
17694
|
return;
|
|
17456
17695
|
}
|
|
17457
17696
|
if (!npmResult) {
|
|
17458
|
-
safeWriteLog(
|
|
17697
|
+
safeWriteLog(PLUGIN_NAME22, {
|
|
17459
17698
|
level: "info",
|
|
17460
17699
|
msg: "npm_no_release",
|
|
17461
17700
|
package: u.package,
|
|
@@ -17464,7 +17703,7 @@ var updateCheckerServer = async (ctx) => {
|
|
|
17464
17703
|
return;
|
|
17465
17704
|
}
|
|
17466
17705
|
const hasUpdate = cmpVersion(local, npmResult.version) < 0;
|
|
17467
|
-
safeWriteLog(
|
|
17706
|
+
safeWriteLog(PLUGIN_NAME22, {
|
|
17468
17707
|
level: "info",
|
|
17469
17708
|
msg: "npm_check_result",
|
|
17470
17709
|
local,
|
|
@@ -17479,10 +17718,10 @@ var updateCheckerServer = async (ctx) => {
|
|
|
17479
17718
|
更新命令:npx ${u.package} install --global`);
|
|
17480
17719
|
return;
|
|
17481
17720
|
}
|
|
17482
|
-
await safeAsync(
|
|
17721
|
+
await safeAsync(PLUGIN_NAME22, "auto_install_bundle", async () => {
|
|
17483
17722
|
const target = getOpencodeBundlePath();
|
|
17484
17723
|
if (!target) {
|
|
17485
|
-
safeWriteLog(
|
|
17724
|
+
safeWriteLog(PLUGIN_NAME22, {
|
|
17486
17725
|
level: "warn",
|
|
17487
17726
|
msg: "auto_install_skip",
|
|
17488
17727
|
reason: "无法定位 opencode bundle 路径"
|
|
@@ -17501,7 +17740,7 @@ var updateCheckerServer = async (ctx) => {
|
|
|
17501
17740
|
oldVersion: local,
|
|
17502
17741
|
keepBackups: u.backup_keep
|
|
17503
17742
|
});
|
|
17504
|
-
safeWriteLog(
|
|
17743
|
+
safeWriteLog(PLUGIN_NAME22, {
|
|
17505
17744
|
level: "info",
|
|
17506
17745
|
msg: "auto_install_success",
|
|
17507
17746
|
local,
|
|
@@ -17535,13 +17774,13 @@ function getOpencodeBundlePath() {
|
|
|
17535
17774
|
candidates.push(join15(localAppData, "opencode", "codeforge", "index.js"));
|
|
17536
17775
|
}
|
|
17537
17776
|
for (const c of candidates) {
|
|
17538
|
-
if (
|
|
17777
|
+
if (existsSync5(c))
|
|
17539
17778
|
return c;
|
|
17540
17779
|
}
|
|
17541
17780
|
return candidates[0] ?? null;
|
|
17542
17781
|
}
|
|
17543
17782
|
async function fallbackToGitHubReleases(ctx, u) {
|
|
17544
|
-
await safeAsync(
|
|
17783
|
+
await safeAsync(PLUGIN_NAME22, "github_fallback", async () => {
|
|
17545
17784
|
const result = await checkUpdateOnce({
|
|
17546
17785
|
repo: u.repo,
|
|
17547
17786
|
intervalMs: u.interval_hours * 3600 * 1000
|
|
@@ -17551,7 +17790,7 @@ async function fallbackToGitHubReleases(ctx, u) {
|
|
|
17551
17790
|
}
|
|
17552
17791
|
async function reportLegacyResult(ctx, result, repo) {
|
|
17553
17792
|
if (result.error && !result.fromCache) {
|
|
17554
|
-
safeWriteLog(
|
|
17793
|
+
safeWriteLog(PLUGIN_NAME22, {
|
|
17555
17794
|
level: "warn",
|
|
17556
17795
|
msg: `update check failed: ${result.error}`,
|
|
17557
17796
|
...result
|
|
@@ -17559,7 +17798,7 @@ async function reportLegacyResult(ctx, result, repo) {
|
|
|
17559
17798
|
return;
|
|
17560
17799
|
}
|
|
17561
17800
|
if (!result.hasUpdate) {
|
|
17562
|
-
safeWriteLog(
|
|
17801
|
+
safeWriteLog(PLUGIN_NAME22, {
|
|
17563
17802
|
level: "info",
|
|
17564
17803
|
msg: `up-to-date (local=${result.local}, remote=${result.remote}${result.fromCache ? ", from_cache" : ""})`,
|
|
17565
17804
|
...result
|
|
@@ -17569,7 +17808,7 @@ async function reportLegacyResult(ctx, result, repo) {
|
|
|
17569
17808
|
const updateCmd = `bunx --bun github:${repo} install`;
|
|
17570
17809
|
const toast = `[CodeForge] 有新版本:${result.local} → ${result.remote}
|
|
17571
17810
|
更新命令:${updateCmd}`;
|
|
17572
|
-
safeWriteLog(
|
|
17811
|
+
safeWriteLog(PLUGIN_NAME22, {
|
|
17573
17812
|
level: "info",
|
|
17574
17813
|
msg: "new_version_available_github_fallback",
|
|
17575
17814
|
local: result.local,
|
|
@@ -17580,17 +17819,17 @@ async function reportLegacyResult(ctx, result, repo) {
|
|
|
17580
17819
|
await postToast(ctx, toast);
|
|
17581
17820
|
}
|
|
17582
17821
|
async function postToast(ctx, message) {
|
|
17583
|
-
await safeAsync(
|
|
17822
|
+
await safeAsync(PLUGIN_NAME22, "client.app.log", async () => {
|
|
17584
17823
|
await ctx.client.app.log({
|
|
17585
17824
|
body: {
|
|
17586
|
-
service:
|
|
17825
|
+
service: PLUGIN_NAME22,
|
|
17587
17826
|
level: "info",
|
|
17588
17827
|
message
|
|
17589
17828
|
}
|
|
17590
17829
|
});
|
|
17591
17830
|
});
|
|
17592
17831
|
}
|
|
17593
|
-
var
|
|
17832
|
+
var handler22 = updateCheckerServer;
|
|
17594
17833
|
|
|
17595
17834
|
// plugins/workflow-engine.ts
|
|
17596
17835
|
init_opencode_plugin_helpers();
|
|
@@ -18094,9 +18333,9 @@ async function runStepAutoFeedback(step, adapter) {
|
|
|
18094
18333
|
}
|
|
18095
18334
|
|
|
18096
18335
|
// plugins/workflow-engine.ts
|
|
18097
|
-
var
|
|
18098
|
-
logLifecycle(
|
|
18099
|
-
var fallbackLog2 = makePluginLogger(
|
|
18336
|
+
var PLUGIN_NAME23 = "workflow-engine";
|
|
18337
|
+
logLifecycle(PLUGIN_NAME23, "import", {});
|
|
18338
|
+
var fallbackLog2 = makePluginLogger(PLUGIN_NAME23);
|
|
18100
18339
|
var _registry = null;
|
|
18101
18340
|
async function loadRegistry(workflowsDir) {
|
|
18102
18341
|
const { loaded, failed } = await loadWorkflowsFromDir(workflowsDir);
|
|
@@ -18116,32 +18355,32 @@ async function handleCommandInvoked(raw, workflowsDir = "workflows") {
|
|
|
18116
18355
|
const log12 = ctx.log ?? fallbackLog2;
|
|
18117
18356
|
const command = typeof ctx.command === "string" ? ctx.command : null;
|
|
18118
18357
|
if (!command) {
|
|
18119
|
-
log12.warn(`[${
|
|
18358
|
+
log12.warn(`[${PLUGIN_NAME23}] command.invoked 缺 command 字段`, ctx);
|
|
18120
18359
|
return null;
|
|
18121
18360
|
}
|
|
18122
18361
|
const reg = await ensureRegistry(workflowsDir);
|
|
18123
18362
|
if (reg.errors.length) {
|
|
18124
|
-
log12.warn(`[${
|
|
18363
|
+
log12.warn(`[${PLUGIN_NAME23}] 有 ${reg.errors.length} 个 workflow 加载失败`, reg.errors);
|
|
18125
18364
|
}
|
|
18126
18365
|
const wf = reg.workflows.find((w) => matchesTrigger(w, command));
|
|
18127
18366
|
if (!wf) {
|
|
18128
|
-
log12.info(`[${
|
|
18367
|
+
log12.info(`[${PLUGIN_NAME23}] no workflow matches "${command}"`);
|
|
18129
18368
|
return null;
|
|
18130
18369
|
}
|
|
18131
|
-
log12.info(`[${
|
|
18370
|
+
log12.info(`[${PLUGIN_NAME23}] dispatch "${command}" → workflow "${wf.name}"`);
|
|
18132
18371
|
try {
|
|
18133
18372
|
const result = await run(wf, {
|
|
18134
18373
|
mode: ctx.adapter ? "real" : "dry_run",
|
|
18135
18374
|
autonomy: ctx.autonomy ?? "semi",
|
|
18136
18375
|
adapter: ctx.adapter
|
|
18137
18376
|
});
|
|
18138
|
-
log12.info(`[${
|
|
18377
|
+
log12.info(`[${PLUGIN_NAME23}] workflow "${wf.name}" 完成 (${result.plan.mode})`, {
|
|
18139
18378
|
steps: result.plan.steps.length,
|
|
18140
18379
|
results: result.results.length
|
|
18141
18380
|
});
|
|
18142
18381
|
return result;
|
|
18143
18382
|
} catch (err) {
|
|
18144
|
-
log12.error(`[${
|
|
18383
|
+
log12.error(`[${PLUGIN_NAME23}] workflow "${wf.name}" 执行失败`, {
|
|
18145
18384
|
error: err instanceof Error ? err.message : String(err)
|
|
18146
18385
|
});
|
|
18147
18386
|
throw err;
|
|
@@ -18150,15 +18389,15 @@ async function handleCommandInvoked(raw, workflowsDir = "workflows") {
|
|
|
18150
18389
|
var workflowEngineServer = async (ctx) => {
|
|
18151
18390
|
const directory = ctx.directory ?? process.cwd();
|
|
18152
18391
|
const workflowsDir = path17.join(directory, "workflows");
|
|
18153
|
-
ensureRegistry(workflowsDir).catch((err) => fallbackLog2.warn(`[${
|
|
18392
|
+
ensureRegistry(workflowsDir).catch((err) => fallbackLog2.warn(`[${PLUGIN_NAME23}] preload workflows failed`, {
|
|
18154
18393
|
error: err instanceof Error ? err.message : String(err)
|
|
18155
18394
|
}));
|
|
18156
|
-
logLifecycle(
|
|
18395
|
+
logLifecycle(PLUGIN_NAME23, "activate", { directory, workflowsDir });
|
|
18157
18396
|
return {
|
|
18158
18397
|
"command.execute.before": async (input, output) => {
|
|
18159
|
-
await safeAsync(
|
|
18398
|
+
await safeAsync(PLUGIN_NAME23, "command.execute.before", async () => {
|
|
18160
18399
|
const cmd = input.command.startsWith("/") ? input.command : `/${input.command}`;
|
|
18161
|
-
safeWriteLog(
|
|
18400
|
+
safeWriteLog(PLUGIN_NAME23, {
|
|
18162
18401
|
hook: "command.execute.before",
|
|
18163
18402
|
command: cmd,
|
|
18164
18403
|
sessionID: input.sessionID
|
|
@@ -18173,14 +18412,14 @@ var workflowEngineServer = async (ctx) => {
|
|
|
18173
18412
|
});
|
|
18174
18413
|
},
|
|
18175
18414
|
"chat.message": async (input, output) => {
|
|
18176
|
-
await safeAsync(
|
|
18415
|
+
await safeAsync(PLUGIN_NAME23, "chat.message", async () => {
|
|
18177
18416
|
const text = extractUserText(output).trim();
|
|
18178
18417
|
if (!text.startsWith("/"))
|
|
18179
18418
|
return;
|
|
18180
18419
|
const cmd = text.split(/\s+/)[0];
|
|
18181
18420
|
if (!cmd)
|
|
18182
18421
|
return;
|
|
18183
|
-
safeWriteLog(
|
|
18422
|
+
safeWriteLog(PLUGIN_NAME23, {
|
|
18184
18423
|
hook: "chat.message",
|
|
18185
18424
|
command: cmd,
|
|
18186
18425
|
sessionID: input.sessionID
|
|
@@ -18190,7 +18429,7 @@ var workflowEngineServer = async (ctx) => {
|
|
|
18190
18429
|
}
|
|
18191
18430
|
};
|
|
18192
18431
|
};
|
|
18193
|
-
var
|
|
18432
|
+
var handler23 = workflowEngineServer;
|
|
18194
18433
|
|
|
18195
18434
|
// src/index.ts
|
|
18196
18435
|
var PLUGIN_ID = "codeforge";
|
|
@@ -18209,16 +18448,17 @@ var HANDLERS = [
|
|
|
18209
18448
|
{ name: "kh-reminder", init: handler11 },
|
|
18210
18449
|
{ name: "memories-context", init: handler12 },
|
|
18211
18450
|
{ 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: "
|
|
18451
|
+
{ name: "pwsh-utf8", init: handler16 },
|
|
18452
|
+
{ name: "session-recovery", init: handler17 },
|
|
18453
|
+
{ name: "subtask-heartbeat", init: handler14 },
|
|
18454
|
+
{ name: "subtasks", init: handler18 },
|
|
18455
|
+
{ name: "parallel-status", init: handler15 },
|
|
18456
|
+
{ name: "terminal-monitor", init: handler19 },
|
|
18457
|
+
{ name: "token-manager", init: handler20 },
|
|
18218
18458
|
{ name: "tool-heartbeat", init: handler7 },
|
|
18219
|
-
{ name: "tool-policy", init:
|
|
18220
|
-
{ name: "update-checker", init:
|
|
18221
|
-
{ name: "workflow-engine", init:
|
|
18459
|
+
{ name: "tool-policy", init: handler21 },
|
|
18460
|
+
{ name: "update-checker", init: handler22 },
|
|
18461
|
+
{ name: "workflow-engine", init: handler23 }
|
|
18222
18462
|
];
|
|
18223
18463
|
function makeSerialHook(hookName, fns) {
|
|
18224
18464
|
return async (input, output) => {
|
|
@@ -18233,69 +18473,91 @@ function makeSerialHook(hookName, fns) {
|
|
|
18233
18473
|
}
|
|
18234
18474
|
};
|
|
18235
18475
|
}
|
|
18236
|
-
|
|
18237
|
-
|
|
18238
|
-
|
|
18239
|
-
|
|
18240
|
-
|
|
18241
|
-
|
|
18242
|
-
|
|
18243
|
-
|
|
18476
|
+
function createCodeforgeServer(opts) {
|
|
18477
|
+
return async (input) => {
|
|
18478
|
+
if (opts.enableDevIsolation) {
|
|
18479
|
+
const yieldResult = shouldYieldToLocalPlugin({ directory: input.directory });
|
|
18480
|
+
if (yieldResult.yield) {
|
|
18481
|
+
const msg = formatYieldLog(yieldResult);
|
|
18482
|
+
log12.info(msg, { reason: yieldResult.reason, markerPath: yieldResult.markerPath });
|
|
18483
|
+
logLifecycle(PLUGIN_ID, "activate", {
|
|
18484
|
+
yield_to_local: true,
|
|
18485
|
+
yield_reason: yieldResult.reason,
|
|
18486
|
+
yield_marker_path: yieldResult.markerPath,
|
|
18487
|
+
directory: input.directory,
|
|
18488
|
+
handlers_total: HANDLERS.length,
|
|
18489
|
+
handlers_active: 0
|
|
18490
|
+
});
|
|
18491
|
+
return {};
|
|
18492
|
+
}
|
|
18244
18493
|
}
|
|
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
|
-
|
|
18494
|
+
const results = await Promise.allSettled(HANDLERS.map((h) => h.init(input)));
|
|
18495
|
+
const hooksList = [];
|
|
18496
|
+
results.forEach((r, i) => {
|
|
18497
|
+
if (r.status === "fulfilled" && r.value && typeof r.value === "object") {
|
|
18498
|
+
hooksList.push(r.value);
|
|
18499
|
+
} else if (r.status === "rejected") {
|
|
18500
|
+
log12.warn(`[${PLUGIN_ID}] handler ${HANDLERS[i].name} init failed (隔离,其他 handler 继续)`, { error: r.reason instanceof Error ? r.reason.message : String(r.reason) });
|
|
18501
|
+
}
|
|
18502
|
+
});
|
|
18503
|
+
logLifecycle(PLUGIN_ID, "activate", {
|
|
18504
|
+
directory: input.directory,
|
|
18505
|
+
handlers_total: HANDLERS.length,
|
|
18506
|
+
handlers_active: hooksList.length,
|
|
18507
|
+
handlers_failed: HANDLERS.length - hooksList.length
|
|
18508
|
+
});
|
|
18509
|
+
const chatMessageBucket = [];
|
|
18510
|
+
const commandExecuteBeforeBucket = [];
|
|
18511
|
+
const chatParamsBucket = [];
|
|
18512
|
+
const toolExecuteBeforeBucket = [];
|
|
18513
|
+
const chatMessagesTransformBucket = [];
|
|
18514
|
+
const eventBucket = [];
|
|
18515
|
+
const toolMerged = {};
|
|
18516
|
+
for (const h of hooksList) {
|
|
18517
|
+
if (h["chat.message"])
|
|
18518
|
+
chatMessageBucket.push(h["chat.message"]);
|
|
18519
|
+
if (h["command.execute.before"])
|
|
18520
|
+
commandExecuteBeforeBucket.push(h["command.execute.before"]);
|
|
18521
|
+
if (h["chat.params"])
|
|
18522
|
+
chatParamsBucket.push(h["chat.params"]);
|
|
18523
|
+
if (h["tool.execute.before"])
|
|
18524
|
+
toolExecuteBeforeBucket.push(h["tool.execute.before"]);
|
|
18525
|
+
if (h["experimental.chat.messages.transform"]) {
|
|
18526
|
+
chatMessagesTransformBucket.push(h["experimental.chat.messages.transform"]);
|
|
18527
|
+
}
|
|
18528
|
+
if (h.event)
|
|
18529
|
+
eventBucket.push(h.event);
|
|
18530
|
+
if (h.tool)
|
|
18531
|
+
Object.assign(toolMerged, h.tool);
|
|
18532
|
+
}
|
|
18533
|
+
return {
|
|
18534
|
+
"chat.message": makeSerialHook("chat.message", chatMessageBucket),
|
|
18535
|
+
"command.execute.before": makeSerialHook("command.execute.before", commandExecuteBeforeBucket),
|
|
18536
|
+
"chat.params": makeSerialHook("chat.params", chatParamsBucket),
|
|
18537
|
+
"tool.execute.before": makeSerialHook("tool.execute.before", toolExecuteBeforeBucket),
|
|
18538
|
+
"experimental.chat.messages.transform": makeSerialHook("experimental.chat.messages.transform", chatMessagesTransformBucket),
|
|
18539
|
+
event: async (envelope) => {
|
|
18540
|
+
await Promise.all(eventBucket.map(async (fn) => {
|
|
18541
|
+
try {
|
|
18542
|
+
await fn(envelope);
|
|
18543
|
+
} catch (err) {
|
|
18544
|
+
log12.warn(`[${PLUGIN_ID}] event handler 异常(已隔离)`, {
|
|
18545
|
+
error: err instanceof Error ? err.message : String(err)
|
|
18546
|
+
});
|
|
18547
|
+
}
|
|
18548
|
+
}));
|
|
18549
|
+
},
|
|
18550
|
+
tool: toolMerged
|
|
18551
|
+
};
|
|
18294
18552
|
};
|
|
18295
|
-
}
|
|
18553
|
+
}
|
|
18554
|
+
var codeforgeServer = createCodeforgeServer({ enableDevIsolation: true });
|
|
18555
|
+
var codeforgeDevServer = createCodeforgeServer({ enableDevIsolation: false });
|
|
18296
18556
|
var pluginModule = { id: PLUGIN_ID, server: codeforgeServer };
|
|
18297
18557
|
var src_default = pluginModule;
|
|
18298
18558
|
export {
|
|
18299
18559
|
src_default as default,
|
|
18300
|
-
|
|
18560
|
+
createCodeforgeServer,
|
|
18561
|
+
codeforgeServer,
|
|
18562
|
+
codeforgeDevServer
|
|
18301
18563
|
};
|