@andyqiu/codeforge 0.3.5 → 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/commands/refactor.md +79 -0
- package/commands/review.md +66 -0
- package/commands/tdd.md +91 -0
- package/dist/index.js +1138 -602
- package/package.json +5 -1
- package/workflows/code-review.yaml +66 -0
- package/workflows/refactor.yaml +106 -0
- package/workflows/tdd.yaml +99 -0
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
|
}
|
|
@@ -8965,6 +9006,7 @@ import { promises as fs2 } from "node:fs";
|
|
|
8965
9006
|
import * as path4 from "node:path";
|
|
8966
9007
|
|
|
8967
9008
|
// lib/channels.ts
|
|
9009
|
+
import { createHmac } from "node:crypto";
|
|
8968
9010
|
var TEMPLATE_RE = /\$\{([a-zA-Z_][a-zA-Z0-9_.]*)(?:\|([^}]*))?\}/g;
|
|
8969
9011
|
function renderChannelTemplate(template, ev) {
|
|
8970
9012
|
const missing = new Set;
|
|
@@ -9078,6 +9120,10 @@ async function sendOne(ev, ch, deps) {
|
|
|
9078
9120
|
return await sendKh(ev, ch, deps);
|
|
9079
9121
|
case "mcp":
|
|
9080
9122
|
return await sendMcp(ev, ch, deps);
|
|
9123
|
+
case "slack":
|
|
9124
|
+
return await sendSlack(ev, ch, deps);
|
|
9125
|
+
case "lark":
|
|
9126
|
+
return await sendLark(ev, ch, deps);
|
|
9081
9127
|
}
|
|
9082
9128
|
}
|
|
9083
9129
|
async function sendWebhook(ev, ch, deps) {
|
|
@@ -9217,6 +9263,224 @@ async function sendMcp(ev, ch, deps) {
|
|
|
9217
9263
|
error: r.ok ? undefined : r.error ?? "mcp 调用失败"
|
|
9218
9264
|
};
|
|
9219
9265
|
}
|
|
9266
|
+
function transformSlackToWebhook(ch, ev) {
|
|
9267
|
+
const missing = new Set;
|
|
9268
|
+
const titleTpl = ch.title_template ?? "${title|}";
|
|
9269
|
+
const msgTpl = ch.message_template ?? "${message|}";
|
|
9270
|
+
const title = renderChannelTemplate(titleTpl, ev);
|
|
9271
|
+
const message = renderChannelTemplate(msgTpl, ev);
|
|
9272
|
+
title.missing.forEach((k) => missing.add(k));
|
|
9273
|
+
message.missing.forEach((k) => missing.add(k));
|
|
9274
|
+
const titleText = title.rendered.trim() ? title.rendered : ev.event;
|
|
9275
|
+
const mentionText = (ch.mentions ?? []).map((id) => id.startsWith("@") || id.startsWith("<") ? id : `<@${id}>`).join(" ");
|
|
9276
|
+
const color = severityToSlackColor(ev.severity);
|
|
9277
|
+
const blocks = [
|
|
9278
|
+
{
|
|
9279
|
+
type: "header",
|
|
9280
|
+
text: { type: "plain_text", text: titleText.slice(0, 150), emoji: true }
|
|
9281
|
+
}
|
|
9282
|
+
];
|
|
9283
|
+
if (message.rendered.trim()) {
|
|
9284
|
+
blocks.push({
|
|
9285
|
+
type: "section",
|
|
9286
|
+
text: { type: "mrkdwn", text: message.rendered.slice(0, 3000) }
|
|
9287
|
+
});
|
|
9288
|
+
}
|
|
9289
|
+
if (mentionText) {
|
|
9290
|
+
blocks.push({
|
|
9291
|
+
type: "context",
|
|
9292
|
+
elements: [{ type: "mrkdwn", text: mentionText }]
|
|
9293
|
+
});
|
|
9294
|
+
}
|
|
9295
|
+
const footerParts = [`event=\`${ev.event}\``];
|
|
9296
|
+
if (ev.session_id)
|
|
9297
|
+
footerParts.push(`session=\`${ev.session_id.slice(0, 8)}\``);
|
|
9298
|
+
footerParts.push(`ts=<!date^${Math.floor(ev.timestamp / 1000)}^{date_short_pretty} {time}|${new Date(ev.timestamp).toISOString()}>`);
|
|
9299
|
+
blocks.push({
|
|
9300
|
+
type: "context",
|
|
9301
|
+
elements: [{ type: "mrkdwn", text: footerParts.join(" · ") }]
|
|
9302
|
+
});
|
|
9303
|
+
const payload = {
|
|
9304
|
+
attachments: [{ color, blocks }]
|
|
9305
|
+
};
|
|
9306
|
+
if (ch.channel)
|
|
9307
|
+
payload.channel = ch.channel;
|
|
9308
|
+
if (ch.username)
|
|
9309
|
+
payload.username = ch.username;
|
|
9310
|
+
if (ch.icon_emoji)
|
|
9311
|
+
payload.icon_emoji = ch.icon_emoji;
|
|
9312
|
+
return { body: JSON.stringify(payload), missing: [...missing] };
|
|
9313
|
+
}
|
|
9314
|
+
function severityToSlackColor(sev) {
|
|
9315
|
+
if (sev === undefined)
|
|
9316
|
+
return "#cccccc";
|
|
9317
|
+
if (sev >= 40)
|
|
9318
|
+
return "#d50000";
|
|
9319
|
+
if (sev >= 30)
|
|
9320
|
+
return "#e91e63";
|
|
9321
|
+
if (sev >= 20)
|
|
9322
|
+
return "#ff9800";
|
|
9323
|
+
if (sev >= 10)
|
|
9324
|
+
return "#36a64f";
|
|
9325
|
+
return "#cccccc";
|
|
9326
|
+
}
|
|
9327
|
+
async function sendSlack(ev, ch, deps) {
|
|
9328
|
+
if (!deps.fetch) {
|
|
9329
|
+
return { name: ch.name, type: "slack", status: "error", error: "deps.fetch 未注入" };
|
|
9330
|
+
}
|
|
9331
|
+
const { body } = transformSlackToWebhook(ch, ev);
|
|
9332
|
+
try {
|
|
9333
|
+
const resp = await deps.fetch(ch.webhook_url, {
|
|
9334
|
+
method: "POST",
|
|
9335
|
+
headers: { "Content-Type": "application/json" },
|
|
9336
|
+
body
|
|
9337
|
+
});
|
|
9338
|
+
const ok = resp.status >= 200 && resp.status < 300;
|
|
9339
|
+
if (ok) {
|
|
9340
|
+
return {
|
|
9341
|
+
name: ch.name,
|
|
9342
|
+
type: "slack",
|
|
9343
|
+
status: "sent",
|
|
9344
|
+
http_status: resp.status,
|
|
9345
|
+
rendered: body
|
|
9346
|
+
};
|
|
9347
|
+
}
|
|
9348
|
+
return {
|
|
9349
|
+
name: ch.name,
|
|
9350
|
+
type: "slack",
|
|
9351
|
+
status: "error",
|
|
9352
|
+
http_status: resp.status,
|
|
9353
|
+
error: `slack returned ${resp.status}: ${resp.body?.slice(0, 200) ?? ""}`,
|
|
9354
|
+
rendered: body
|
|
9355
|
+
};
|
|
9356
|
+
} catch (err) {
|
|
9357
|
+
return {
|
|
9358
|
+
name: ch.name,
|
|
9359
|
+
type: "slack",
|
|
9360
|
+
status: "error",
|
|
9361
|
+
error: describe2(err),
|
|
9362
|
+
rendered: body
|
|
9363
|
+
};
|
|
9364
|
+
}
|
|
9365
|
+
}
|
|
9366
|
+
function transformLarkToWebhook(ch, ev) {
|
|
9367
|
+
const missing = new Set;
|
|
9368
|
+
const titleTpl = ch.title_template ?? "${title|}";
|
|
9369
|
+
const msgTpl = ch.message_template ?? "${message|}";
|
|
9370
|
+
const titleR = renderChannelTemplate(titleTpl, ev);
|
|
9371
|
+
const msgR = renderChannelTemplate(msgTpl, ev);
|
|
9372
|
+
titleR.missing.forEach((k) => missing.add(k));
|
|
9373
|
+
msgR.missing.forEach((k) => missing.add(k));
|
|
9374
|
+
const titleText = titleR.rendered.trim() || ev.event;
|
|
9375
|
+
const messageText = msgR.rendered.trim();
|
|
9376
|
+
const mentionMarkdown = (ch.mentions ?? []).map((id) => {
|
|
9377
|
+
if (id === "@all" || id === "all")
|
|
9378
|
+
return '<at user_id="all"></at>';
|
|
9379
|
+
if (id.startsWith("<at"))
|
|
9380
|
+
return id;
|
|
9381
|
+
return `<at user_id="${id}"></at>`;
|
|
9382
|
+
}).join(" ");
|
|
9383
|
+
const headerTemplate = severityToLarkHeader(ev.severity);
|
|
9384
|
+
const elements = [];
|
|
9385
|
+
if (messageText || mentionMarkdown) {
|
|
9386
|
+
const fullMsg = [messageText, mentionMarkdown].filter(Boolean).join(`
|
|
9387
|
+
|
|
9388
|
+
`);
|
|
9389
|
+
elements.push({
|
|
9390
|
+
tag: "div",
|
|
9391
|
+
text: { tag: "lark_md", content: fullMsg.slice(0, 3000) }
|
|
9392
|
+
});
|
|
9393
|
+
}
|
|
9394
|
+
const footer = [
|
|
9395
|
+
`**event**: \`${ev.event}\``,
|
|
9396
|
+
ev.session_id ? `**session**: \`${ev.session_id.slice(0, 8)}\`` : null,
|
|
9397
|
+
`**ts**: ${new Date(ev.timestamp).toISOString()}`
|
|
9398
|
+
].filter(Boolean).join(" · ");
|
|
9399
|
+
elements.push({
|
|
9400
|
+
tag: "note",
|
|
9401
|
+
elements: [{ tag: "lark_md", content: footer }]
|
|
9402
|
+
});
|
|
9403
|
+
const payload = {
|
|
9404
|
+
msg_type: "interactive",
|
|
9405
|
+
card: {
|
|
9406
|
+
config: { wide_screen_mode: true },
|
|
9407
|
+
header: {
|
|
9408
|
+
template: headerTemplate,
|
|
9409
|
+
title: { tag: "plain_text", content: titleText.slice(0, 150) }
|
|
9410
|
+
},
|
|
9411
|
+
elements
|
|
9412
|
+
}
|
|
9413
|
+
};
|
|
9414
|
+
return { body: JSON.stringify(payload), missing: [...missing] };
|
|
9415
|
+
}
|
|
9416
|
+
function severityToLarkHeader(sev) {
|
|
9417
|
+
if (sev === undefined)
|
|
9418
|
+
return "grey";
|
|
9419
|
+
if (sev >= 40)
|
|
9420
|
+
return "carmine";
|
|
9421
|
+
if (sev >= 30)
|
|
9422
|
+
return "red";
|
|
9423
|
+
if (sev >= 20)
|
|
9424
|
+
return "orange";
|
|
9425
|
+
if (sev >= 10)
|
|
9426
|
+
return "blue";
|
|
9427
|
+
return "grey";
|
|
9428
|
+
}
|
|
9429
|
+
function computeLarkSign(secret, timestampSec) {
|
|
9430
|
+
const stringToSign = `${timestampSec}
|
|
9431
|
+
${secret}`;
|
|
9432
|
+
const hmac = createHmac("sha256", stringToSign);
|
|
9433
|
+
hmac.update("");
|
|
9434
|
+
return hmac.digest("base64");
|
|
9435
|
+
}
|
|
9436
|
+
async function sendLark(ev, ch, deps) {
|
|
9437
|
+
if (!deps.fetch) {
|
|
9438
|
+
return { name: ch.name, type: "lark", status: "error", error: "deps.fetch 未注入" };
|
|
9439
|
+
}
|
|
9440
|
+
const { body: cardBody } = transformLarkToWebhook(ch, ev);
|
|
9441
|
+
let body = cardBody;
|
|
9442
|
+
if (ch.secret) {
|
|
9443
|
+
const tsSec = Math.floor((deps.now ? deps.now() : Date.now()) / 1000);
|
|
9444
|
+
const sign = computeLarkSign(ch.secret, tsSec);
|
|
9445
|
+
const parsed = JSON.parse(cardBody);
|
|
9446
|
+
parsed.timestamp = String(tsSec);
|
|
9447
|
+
parsed.sign = sign;
|
|
9448
|
+
body = JSON.stringify(parsed);
|
|
9449
|
+
}
|
|
9450
|
+
try {
|
|
9451
|
+
const resp = await deps.fetch(ch.webhook_url, {
|
|
9452
|
+
method: "POST",
|
|
9453
|
+
headers: { "Content-Type": "application/json" },
|
|
9454
|
+
body
|
|
9455
|
+
});
|
|
9456
|
+
const ok = resp.status >= 200 && resp.status < 300;
|
|
9457
|
+
if (ok) {
|
|
9458
|
+
return {
|
|
9459
|
+
name: ch.name,
|
|
9460
|
+
type: "lark",
|
|
9461
|
+
status: "sent",
|
|
9462
|
+
http_status: resp.status,
|
|
9463
|
+
rendered: body
|
|
9464
|
+
};
|
|
9465
|
+
}
|
|
9466
|
+
return {
|
|
9467
|
+
name: ch.name,
|
|
9468
|
+
type: "lark",
|
|
9469
|
+
status: "error",
|
|
9470
|
+
http_status: resp.status,
|
|
9471
|
+
error: `lark returned ${resp.status}: ${resp.body?.slice(0, 200) ?? ""}`,
|
|
9472
|
+
rendered: body
|
|
9473
|
+
};
|
|
9474
|
+
} catch (err) {
|
|
9475
|
+
return {
|
|
9476
|
+
name: ch.name,
|
|
9477
|
+
type: "lark",
|
|
9478
|
+
status: "error",
|
|
9479
|
+
error: describe2(err),
|
|
9480
|
+
rendered: body
|
|
9481
|
+
};
|
|
9482
|
+
}
|
|
9483
|
+
}
|
|
9220
9484
|
function dedupeTags(defaults, evTags) {
|
|
9221
9485
|
const set = new Set;
|
|
9222
9486
|
if (defaults) {
|
|
@@ -11141,6 +11405,51 @@ function applyFocus(ranked, focus) {
|
|
|
11141
11405
|
const rest = others.filter((n) => !isAdj(n.rel));
|
|
11142
11406
|
return [center, ...adj, ...rest];
|
|
11143
11407
|
}
|
|
11408
|
+
function renderMermaid(map, opts = {}) {
|
|
11409
|
+
const top = opts.top ?? 20;
|
|
11410
|
+
const dir = opts.direction ?? "LR";
|
|
11411
|
+
const focus = opts.focus ? toPosix(opts.focus.replace(/^\.\//, "")) : undefined;
|
|
11412
|
+
const sorted = [...map.ranked].sort((a, b) => b.score - a.score).slice(0, top);
|
|
11413
|
+
const idMap = new Map;
|
|
11414
|
+
for (const f of sorted) {
|
|
11415
|
+
idMap.set(f.rel, "n" + sha1Short(f.rel));
|
|
11416
|
+
}
|
|
11417
|
+
const lines = [];
|
|
11418
|
+
lines.push(`flowchart ${dir}`);
|
|
11419
|
+
for (const f of sorted) {
|
|
11420
|
+
const id = idMap.get(f.rel);
|
|
11421
|
+
const basename = f.rel.split(/[\\/]/).pop() ?? f.rel;
|
|
11422
|
+
const label = `${escapeLabel(basename)}<br/><small>${f.score.toFixed(2)}</small>`;
|
|
11423
|
+
lines.push(` ${id}["${label}"]`);
|
|
11424
|
+
}
|
|
11425
|
+
const topSet = new Set(sorted.map((f) => f.rel));
|
|
11426
|
+
for (const f of sorted) {
|
|
11427
|
+
const from = idMap.get(f.rel);
|
|
11428
|
+
for (const dep of f.deps ?? []) {
|
|
11429
|
+
if (topSet.has(dep)) {
|
|
11430
|
+
const to = idMap.get(dep);
|
|
11431
|
+
lines.push(` ${from} --> ${to}`);
|
|
11432
|
+
}
|
|
11433
|
+
}
|
|
11434
|
+
}
|
|
11435
|
+
if (focus && idMap.has(focus)) {
|
|
11436
|
+
lines.push(` classDef focus fill:#ff9800,stroke:#e65100,color:#fff`);
|
|
11437
|
+
lines.push(` ${idMap.get(focus)}:::focus`);
|
|
11438
|
+
}
|
|
11439
|
+
return lines.join(`
|
|
11440
|
+
`);
|
|
11441
|
+
}
|
|
11442
|
+
function sha1Short(s) {
|
|
11443
|
+
let h = 2166136261 >>> 0;
|
|
11444
|
+
for (let i = 0;i < s.length; i++) {
|
|
11445
|
+
h ^= s.charCodeAt(i);
|
|
11446
|
+
h = Math.imul(h, 16777619) >>> 0;
|
|
11447
|
+
}
|
|
11448
|
+
return h.toString(16).padStart(8, "0");
|
|
11449
|
+
}
|
|
11450
|
+
function escapeLabel(s) {
|
|
11451
|
+
return s.replace(/["`]/g, "'");
|
|
11452
|
+
}
|
|
11144
11453
|
|
|
11145
11454
|
// tools/repo-map.ts
|
|
11146
11455
|
var description17 = [
|
|
@@ -11149,6 +11458,7 @@ var description17 = [
|
|
|
11149
11458
|
"- planner agent 接到新需求 → 先 smart_search → 再 repo-map 找代码入口",
|
|
11150
11459
|
"- 用户问「这个项目怎么组织的 / 入口在哪 / 哪些是核心模块」",
|
|
11151
11460
|
"- 跨多个文件的重构前,先用 focus= 看清依赖关系",
|
|
11461
|
+
'- 想要可视化依赖图时传 format="mermaid"(Markdown 渲染器会自动出图)',
|
|
11152
11462
|
"**何时不需要**:",
|
|
11153
11463
|
"- 用户已指明确切文件,且只在该文件内改动",
|
|
11154
11464
|
"- 项目地图本会话已生成且未发生大改"
|
|
@@ -11159,6 +11469,7 @@ var ArgsSchema17 = z18.object({
|
|
|
11159
11469
|
top: z18.number().int().min(1).max(100).optional().describe("展示 top N 文件,默认 20;想要全图可传 100"),
|
|
11160
11470
|
focus: z18.string().optional().describe("聚焦文件(仓内 POSIX 相对路径):把它和它的直接依赖 / 反向依赖排在前面"),
|
|
11161
11471
|
max_files: z18.number().int().min(10).max(5000).optional().describe("扫描文件数上限,默认 500;超过自动截断"),
|
|
11472
|
+
format: z18.enum(["markdown", "mermaid", "both"]).optional().describe("输出格式:markdown(默认,文字 + 星级评分)/mermaid(flowchart 流程图,可贴 mermaid.live 渲染)/both(两者,方便对照)"),
|
|
11162
11473
|
_raw: z18.boolean().optional()
|
|
11163
11474
|
});
|
|
11164
11475
|
async function execute17(input) {
|
|
@@ -11171,19 +11482,32 @@ async function execute17(input) {
|
|
|
11171
11482
|
};
|
|
11172
11483
|
}
|
|
11173
11484
|
const args = parsed.data;
|
|
11485
|
+
const format = args.format ?? "markdown";
|
|
11174
11486
|
try {
|
|
11175
11487
|
const map = await buildRepoMap({
|
|
11176
11488
|
root: args.root,
|
|
11177
11489
|
maxFiles: args.max_files
|
|
11178
11490
|
});
|
|
11179
|
-
|
|
11180
|
-
|
|
11181
|
-
focus: args.focus
|
|
11182
|
-
})
|
|
11491
|
+
let body;
|
|
11492
|
+
if (format === "markdown") {
|
|
11493
|
+
body = renderMarkdown(map, { top: args.top, focus: args.focus });
|
|
11494
|
+
} else if (format === "mermaid") {
|
|
11495
|
+
const mmd = renderMermaid(map, { top: args.top, focus: args.focus });
|
|
11496
|
+
body = "```mermaid\n" + mmd + "\n```";
|
|
11497
|
+
} else {
|
|
11498
|
+
const md = renderMarkdown(map, { top: args.top, focus: args.focus });
|
|
11499
|
+
const mmd = renderMermaid(map, { top: args.top, focus: args.focus });
|
|
11500
|
+
body = md + `
|
|
11501
|
+
|
|
11502
|
+
## Dependency Graph
|
|
11503
|
+
|
|
11504
|
+
\`\`\`mermaid
|
|
11505
|
+
` + mmd + "\n```";
|
|
11506
|
+
}
|
|
11183
11507
|
const truncated = args.max_files ? map.totalFiles >= args.max_files : map.totalFiles >= 500;
|
|
11184
11508
|
return {
|
|
11185
11509
|
ok: true,
|
|
11186
|
-
markdown:
|
|
11510
|
+
markdown: body,
|
|
11187
11511
|
raw: args._raw ? map : undefined,
|
|
11188
11512
|
stats: {
|
|
11189
11513
|
totalFiles: map.totalFiles,
|
|
@@ -14448,112 +14772,405 @@ var modelFallbackServer = async (ctx) => {
|
|
|
14448
14772
|
};
|
|
14449
14773
|
var handler13 = modelFallbackServer;
|
|
14450
14774
|
|
|
14451
|
-
// plugins/
|
|
14775
|
+
// plugins/subtask-heartbeat.ts
|
|
14452
14776
|
init_opencode_plugin_helpers();
|
|
14453
|
-
var PLUGIN_NAME14 = "
|
|
14777
|
+
var PLUGIN_NAME14 = "subtask-heartbeat";
|
|
14454
14778
|
logLifecycle(PLUGIN_NAME14, "import", {});
|
|
14455
|
-
var
|
|
14456
|
-
|
|
14457
|
-
|
|
14458
|
-
|
|
14459
|
-
|
|
14460
|
-
|
|
14461
|
-
|
|
14462
|
-
return command;
|
|
14463
|
-
if (/\[Console\]::OutputEncoding\s*=/i.test(command))
|
|
14464
|
-
return command;
|
|
14465
|
-
return PRELUDE + command;
|
|
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 }));
|
|
14466
14786
|
}
|
|
14467
|
-
|
|
14468
|
-
|
|
14469
|
-
logLifecycle(PLUGIN_NAME14, "activate", { enabled, platform: process.platform });
|
|
14470
|
-
if (!enabled)
|
|
14471
|
-
return {};
|
|
14472
|
-
return {
|
|
14473
|
-
"tool.execute.before": async (input, output) => {
|
|
14474
|
-
await safeAsync(PLUGIN_NAME14, "tool.execute.before", async () => {
|
|
14475
|
-
if (input.tool !== "bash")
|
|
14476
|
-
return;
|
|
14477
|
-
const args = output.args ?? {};
|
|
14478
|
-
const original = args["command"];
|
|
14479
|
-
const next = prependUtf8Prelude(original);
|
|
14480
|
-
if (next !== undefined && next !== original) {
|
|
14481
|
-
args["command"] = next;
|
|
14482
|
-
output.args = args;
|
|
14483
|
-
safeWriteLog(PLUGIN_NAME14, {
|
|
14484
|
-
hook: "tool.execute.before",
|
|
14485
|
-
tool: input.tool,
|
|
14486
|
-
callID: input.callID,
|
|
14487
|
-
sessionID: input.sessionID,
|
|
14488
|
-
injected: true
|
|
14489
|
-
});
|
|
14490
|
-
}
|
|
14491
|
-
});
|
|
14492
|
-
}
|
|
14493
|
-
};
|
|
14494
|
-
};
|
|
14495
|
-
|
|
14496
|
-
// plugins/session-recovery.ts
|
|
14497
|
-
init_opencode_plugin_helpers();
|
|
14498
|
-
|
|
14499
|
-
// lib/event-stream.ts
|
|
14500
|
-
import { promises as fs9 } from "node:fs";
|
|
14501
|
-
init_runtime_paths();
|
|
14502
|
-
import * as path12 from "node:path";
|
|
14503
|
-
async function loadSession(id, opts = {}) {
|
|
14504
|
-
const file = resolveSessionFile(id, opts);
|
|
14505
|
-
const raw = await fs9.readFile(file, "utf8");
|
|
14506
|
-
return parseJsonl(id, raw);
|
|
14787
|
+
function getInflightSnapshot() {
|
|
14788
|
+
return _snapshotInflight();
|
|
14507
14789
|
}
|
|
14508
|
-
|
|
14509
|
-
|
|
14510
|
-
|
|
14511
|
-
|
|
14512
|
-
|
|
14513
|
-
|
|
14514
|
-
|
|
14515
|
-
|
|
14516
|
-
|
|
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 };
|
|
14517
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()) {
|
|
14518
14855
|
const out = [];
|
|
14519
|
-
for (const
|
|
14520
|
-
if (
|
|
14521
|
-
|
|
14522
|
-
const file = path12.join(dir, e.name);
|
|
14523
|
-
const id = e.name.replace(/\.jsonl$/, "");
|
|
14524
|
-
try {
|
|
14525
|
-
const stat = await fs9.stat(file);
|
|
14526
|
-
const headerLine = await readFirstLine(file);
|
|
14527
|
-
let started_at = stat.birthtimeMs;
|
|
14528
|
-
if (headerLine) {
|
|
14529
|
-
try {
|
|
14530
|
-
const h = JSON.parse(headerLine);
|
|
14531
|
-
if (h.__header && typeof h.started_at === "number") {
|
|
14532
|
-
started_at = h.started_at;
|
|
14533
|
-
}
|
|
14534
|
-
} catch {}
|
|
14535
|
-
}
|
|
14536
|
-
out.push({
|
|
14537
|
-
id,
|
|
14538
|
-
file,
|
|
14539
|
-
started_at,
|
|
14540
|
-
size: stat.size,
|
|
14541
|
-
mtime_ms: stat.mtimeMs
|
|
14542
|
-
});
|
|
14543
|
-
} catch {}
|
|
14856
|
+
for (const r of inflight2.values()) {
|
|
14857
|
+
if (now - r.lastBeatAt >= HEARTBEAT_DEBOUNCE_MS)
|
|
14858
|
+
out.push(r);
|
|
14544
14859
|
}
|
|
14545
|
-
out.sort((a, b) => b.started_at - a.started_at);
|
|
14546
14860
|
return out;
|
|
14547
14861
|
}
|
|
14548
|
-
function
|
|
14549
|
-
const
|
|
14550
|
-
|
|
14551
|
-
|
|
14552
|
-
|
|
14553
|
-
return path12.join(resolveDir(opts), `${id}.jsonl`);
|
|
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`;
|
|
14554
14867
|
}
|
|
14555
|
-
function
|
|
14556
|
-
const
|
|
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", {});
|
|
15072
|
+
var PRELUDE = "chcp 65001 *> $null; " + "[Console]::OutputEncoding = [System.Text.UTF8Encoding]::new(); " + "$OutputEncoding = [System.Text.UTF8Encoding]::new(); ";
|
|
15073
|
+
function prependUtf8Prelude(command) {
|
|
15074
|
+
if (typeof command !== "string")
|
|
15075
|
+
return;
|
|
15076
|
+
if (command.length === 0)
|
|
15077
|
+
return command;
|
|
15078
|
+
if (/chcp\s+65001/i.test(command))
|
|
15079
|
+
return command;
|
|
15080
|
+
if (/\[Console\]::OutputEncoding\s*=/i.test(command))
|
|
15081
|
+
return command;
|
|
15082
|
+
return PRELUDE + command;
|
|
15083
|
+
}
|
|
15084
|
+
var handler16 = async (_ctx) => {
|
|
15085
|
+
const enabled = process.platform === "win32" && process.env.CODEFORGE_DISABLE_PWSH_UTF8 !== "1";
|
|
15086
|
+
logLifecycle(PLUGIN_NAME16, "activate", { enabled, platform: process.platform });
|
|
15087
|
+
if (!enabled)
|
|
15088
|
+
return {};
|
|
15089
|
+
return {
|
|
15090
|
+
"tool.execute.before": async (input, output) => {
|
|
15091
|
+
await safeAsync(PLUGIN_NAME16, "tool.execute.before", async () => {
|
|
15092
|
+
if (input.tool !== "bash")
|
|
15093
|
+
return;
|
|
15094
|
+
const args = output.args ?? {};
|
|
15095
|
+
const original = args["command"];
|
|
15096
|
+
const next = prependUtf8Prelude(original);
|
|
15097
|
+
if (next !== undefined && next !== original) {
|
|
15098
|
+
args["command"] = next;
|
|
15099
|
+
output.args = args;
|
|
15100
|
+
safeWriteLog(PLUGIN_NAME16, {
|
|
15101
|
+
hook: "tool.execute.before",
|
|
15102
|
+
tool: input.tool,
|
|
15103
|
+
callID: input.callID,
|
|
15104
|
+
sessionID: input.sessionID,
|
|
15105
|
+
injected: true
|
|
15106
|
+
});
|
|
15107
|
+
}
|
|
15108
|
+
});
|
|
15109
|
+
}
|
|
15110
|
+
};
|
|
15111
|
+
};
|
|
15112
|
+
|
|
15113
|
+
// plugins/session-recovery.ts
|
|
15114
|
+
init_opencode_plugin_helpers();
|
|
15115
|
+
|
|
15116
|
+
// lib/event-stream.ts
|
|
15117
|
+
import { promises as fs9 } from "node:fs";
|
|
15118
|
+
init_runtime_paths();
|
|
15119
|
+
import * as path12 from "node:path";
|
|
15120
|
+
async function loadSession(id, opts = {}) {
|
|
15121
|
+
const file = resolveSessionFile(id, opts);
|
|
15122
|
+
const raw = await fs9.readFile(file, "utf8");
|
|
15123
|
+
return parseJsonl(id, raw);
|
|
15124
|
+
}
|
|
15125
|
+
async function listSessions(opts = {}) {
|
|
15126
|
+
const dir = resolveDir(opts);
|
|
15127
|
+
let entries;
|
|
15128
|
+
try {
|
|
15129
|
+
entries = await fs9.readdir(dir, { withFileTypes: true });
|
|
15130
|
+
} catch (err) {
|
|
15131
|
+
if (err.code === "ENOENT")
|
|
15132
|
+
return [];
|
|
15133
|
+
throw err;
|
|
15134
|
+
}
|
|
15135
|
+
const out = [];
|
|
15136
|
+
for (const e of entries) {
|
|
15137
|
+
if (!e.isFile() || !e.name.endsWith(".jsonl"))
|
|
15138
|
+
continue;
|
|
15139
|
+
const file = path12.join(dir, e.name);
|
|
15140
|
+
const id = e.name.replace(/\.jsonl$/, "");
|
|
15141
|
+
try {
|
|
15142
|
+
const stat = await fs9.stat(file);
|
|
15143
|
+
const headerLine = await readFirstLine(file);
|
|
15144
|
+
let started_at = stat.birthtimeMs;
|
|
15145
|
+
if (headerLine) {
|
|
15146
|
+
try {
|
|
15147
|
+
const h = JSON.parse(headerLine);
|
|
15148
|
+
if (h.__header && typeof h.started_at === "number") {
|
|
15149
|
+
started_at = h.started_at;
|
|
15150
|
+
}
|
|
15151
|
+
} catch {}
|
|
15152
|
+
}
|
|
15153
|
+
out.push({
|
|
15154
|
+
id,
|
|
15155
|
+
file,
|
|
15156
|
+
started_at,
|
|
15157
|
+
size: stat.size,
|
|
15158
|
+
mtime_ms: stat.mtimeMs
|
|
15159
|
+
});
|
|
15160
|
+
} catch {}
|
|
15161
|
+
}
|
|
15162
|
+
out.sort((a, b) => b.started_at - a.started_at);
|
|
15163
|
+
return out;
|
|
15164
|
+
}
|
|
15165
|
+
function resolveDir(opts = {}) {
|
|
15166
|
+
const root = path12.resolve(opts.root ?? process.cwd());
|
|
15167
|
+
return opts.sessions_dir ? path12.resolve(root, opts.sessions_dir) : path12.join(runtimeDir(root), "sessions");
|
|
15168
|
+
}
|
|
15169
|
+
function resolveSessionFile(id, opts = {}) {
|
|
15170
|
+
return path12.join(resolveDir(opts), `${id}.jsonl`);
|
|
15171
|
+
}
|
|
15172
|
+
function parseJsonl(id, raw) {
|
|
15173
|
+
const events = [];
|
|
14557
15174
|
let started_at = null;
|
|
14558
15175
|
for (const line of raw.split(/\r?\n/)) {
|
|
14559
15176
|
if (!line.trim())
|
|
@@ -14673,7 +15290,7 @@ function buildRecoveryPlan(events, meta = { id: null }, opts = {}) {
|
|
|
14673
15290
|
});
|
|
14674
15291
|
}
|
|
14675
15292
|
}
|
|
14676
|
-
const
|
|
15293
|
+
const inflight3 = [...toolMap.values()].sort((a, b) => b.last_ts - a.last_ts);
|
|
14677
15294
|
const proposed = window.some((t) => PENDING_CHANGES_TOOLS.has(t.tool));
|
|
14678
15295
|
const applied = window.some((t) => APPLY_TOOLS.has(t.tool) && t.ok);
|
|
14679
15296
|
const pending_changes_likely = proposed && !applied;
|
|
@@ -14697,7 +15314,7 @@ function buildRecoveryPlan(events, meta = { id: null }, opts = {}) {
|
|
|
14697
15314
|
idleMs,
|
|
14698
15315
|
lastUser,
|
|
14699
15316
|
lastAgent,
|
|
14700
|
-
inflight:
|
|
15317
|
+
inflight: inflight3,
|
|
14701
15318
|
pending_changes_likely,
|
|
14702
15319
|
open_subtasks_likely,
|
|
14703
15320
|
reason
|
|
@@ -14708,7 +15325,7 @@ function buildRecoveryPlan(events, meta = { id: null }, opts = {}) {
|
|
|
14708
15325
|
idle_ms: idleMs,
|
|
14709
15326
|
last_user_intent: lastUser,
|
|
14710
15327
|
last_agent: lastAgent,
|
|
14711
|
-
inflight_tools:
|
|
15328
|
+
inflight_tools: inflight3,
|
|
14712
15329
|
pending_changes_likely,
|
|
14713
15330
|
open_subtasks_likely,
|
|
14714
15331
|
summary
|
|
@@ -14803,328 +15420,107 @@ function emptyPlan(reason) {
|
|
|
14803
15420
|
summary: "无历史会话,无需恢复。"
|
|
14804
15421
|
};
|
|
14805
15422
|
}
|
|
14806
|
-
function isRecoveryWorthShowing(plan) {
|
|
14807
|
-
if (!plan.last_session_id)
|
|
14808
|
-
return false;
|
|
14809
|
-
if (plan.reason === "no_session")
|
|
14810
|
-
return false;
|
|
14811
|
-
const hasSignal = plan.pending_changes_likely || plan.open_subtasks_likely || plan.inflight_tools.length > 0 || plan.reason === "explicit_marker";
|
|
14812
|
-
return hasSignal;
|
|
14813
|
-
}
|
|
14814
|
-
|
|
14815
|
-
// plugins/session-recovery.ts
|
|
14816
|
-
var
|
|
14817
|
-
logLifecycle(
|
|
14818
|
-
async function processSessionStart(currentSessionId, opts = {}) {
|
|
14819
|
-
if (opts.disabled) {
|
|
14820
|
-
return { ok: true, injected: false, reason: "disabled" };
|
|
14821
|
-
}
|
|
14822
|
-
const excludeIds = new Set(opts.excludeIds ?? []);
|
|
14823
|
-
if (currentSessionId)
|
|
14824
|
-
excludeIds.add(currentSessionId);
|
|
14825
|
-
const r = await scanLastSession({ ...opts, excludeIds: [...excludeIds] });
|
|
14826
|
-
if (!r.ok) {
|
|
14827
|
-
opts.log?.warn?.(`[${PLUGIN_NAME15}] 扫描失败:${r.error}`);
|
|
14828
|
-
return { ok: false, injected: false, reason: "scan_error", error: r.error };
|
|
14829
|
-
}
|
|
14830
|
-
const plan = r.plan;
|
|
14831
|
-
if (!isRecoveryWorthShowing(plan)) {
|
|
14832
|
-
return { ok: true, injected: false, plan, reason: "no_signal" };
|
|
14833
|
-
}
|
|
14834
|
-
const prompt = renderPrompt(plan);
|
|
14835
|
-
const injection = { source: "session-recovery", plan, prompt };
|
|
14836
|
-
if (opts.injectRecovery) {
|
|
14837
|
-
try {
|
|
14838
|
-
await opts.injectRecovery(injection);
|
|
14839
|
-
} catch (err) {
|
|
14840
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
14841
|
-
opts.log?.warn?.(`[${PLUGIN_NAME15}] injectRecovery 异常:${msg}`);
|
|
14842
|
-
return { ok: false, injected: false, plan, reason: "inject_error", error: msg };
|
|
14843
|
-
}
|
|
14844
|
-
}
|
|
14845
|
-
return { ok: true, injected: true, plan, reason: "ok" };
|
|
14846
|
-
}
|
|
14847
|
-
function renderPrompt(plan) {
|
|
14848
|
-
const lines = [];
|
|
14849
|
-
lines.push("【会话恢复提示】");
|
|
14850
|
-
lines.push(plan.summary);
|
|
14851
|
-
lines.push("");
|
|
14852
|
-
lines.push("如果用户接下来未明确提及,请优先:");
|
|
14853
|
-
if (plan.pending_changes_likely) {
|
|
14854
|
-
lines.push(" • 询问是否要 review 上次的暂存区改动并 apply / 丢弃");
|
|
14855
|
-
}
|
|
14856
|
-
if (plan.open_subtasks_likely) {
|
|
14857
|
-
lines.push(" • 询问是否要继续上次未完成的子任务");
|
|
14858
|
-
}
|
|
14859
|
-
if (plan.last_user_intent) {
|
|
14860
|
-
lines.push(` • 确认是否要继续推进上次的目标:"${plan.last_user_intent}"`);
|
|
14861
|
-
}
|
|
14862
|
-
if (!plan.pending_changes_likely && !plan.open_subtasks_likely && !plan.last_user_intent) {
|
|
14863
|
-
lines.push(" • 询问是否要恢复上次的工作(信号较弱,可能不需要)");
|
|
14864
|
-
}
|
|
14865
|
-
lines.push("");
|
|
14866
|
-
lines.push("(如果用户明确开了新话题,本提示可忽略)");
|
|
14867
|
-
return lines.join(`
|
|
14868
|
-
`);
|
|
14869
|
-
}
|
|
14870
|
-
var log7 = makePluginLogger(PLUGIN_NAME15);
|
|
14871
|
-
var _lastInjection = null;
|
|
14872
|
-
var sessionRecoveryServer = async (ctx) => {
|
|
14873
|
-
logLifecycle(PLUGIN_NAME15, "activate", { directory: ctx.directory });
|
|
14874
|
-
return {
|
|
14875
|
-
event: async ({ event }) => {
|
|
14876
|
-
await safeAsync(PLUGIN_NAME15, "event", async () => {
|
|
14877
|
-
const e = event;
|
|
14878
|
-
if (!e || typeof e.type !== "string")
|
|
14879
|
-
return;
|
|
14880
|
-
if (e.type !== "session.start")
|
|
14881
|
-
return;
|
|
14882
|
-
const sid = typeof e.properties?.["session_id"] === "string" ? e.properties["session_id"] : undefined;
|
|
14883
|
-
const root = typeof e.properties?.["root"] === "string" ? e.properties["root"] : ctx.directory ?? process.cwd();
|
|
14884
|
-
const r = await processSessionStart(sid, {
|
|
14885
|
-
root,
|
|
14886
|
-
log: log7,
|
|
14887
|
-
injectRecovery: (inj) => {
|
|
14888
|
-
_lastInjection = inj;
|
|
14889
|
-
}
|
|
14890
|
-
});
|
|
14891
|
-
safeWriteLog(PLUGIN_NAME15, {
|
|
14892
|
-
hook: "event",
|
|
14893
|
-
type: "session.start",
|
|
14894
|
-
ok: r.ok,
|
|
14895
|
-
injected: r.injected,
|
|
14896
|
-
reason: r.reason,
|
|
14897
|
-
last_session_id: r.plan?.last_session_id
|
|
14898
|
-
});
|
|
14899
|
-
if (r.injected && r.plan) {
|
|
14900
|
-
log7.info(`[${PLUGIN_NAME15}] 注入恢复提示(last=${r.plan.last_session_id?.slice(0, 8) ?? "?"}, reason=${r.plan.reason})`);
|
|
14901
|
-
}
|
|
14902
|
-
});
|
|
14903
|
-
}
|
|
14904
|
-
};
|
|
14905
|
-
};
|
|
14906
|
-
var handler15 = sessionRecoveryServer;
|
|
14907
|
-
|
|
14908
|
-
// plugins/subtask-heartbeat.ts
|
|
14909
|
-
init_opencode_plugin_helpers();
|
|
14910
|
-
var PLUGIN_NAME16 = "subtask-heartbeat";
|
|
14911
|
-
logLifecycle(PLUGIN_NAME16, "import", {});
|
|
14912
|
-
var HEARTBEAT_INTERVAL_MS2 = 30000;
|
|
14913
|
-
var HEARTBEAT_DEBOUNCE_MS = 25000;
|
|
14914
|
-
var TOAST_DURATION_MS3 = 5000;
|
|
14915
|
-
var START_TOAST_DURATION_MS = 2000;
|
|
14916
|
-
var inflight2 = new Map;
|
|
14917
|
-
function extractCreatedChild(event) {
|
|
14918
|
-
if (!event || typeof event !== "object")
|
|
14919
|
-
return null;
|
|
14920
|
-
const e = event;
|
|
14921
|
-
if (e.type !== "session.created")
|
|
14922
|
-
return null;
|
|
14923
|
-
const info = e.properties?.info;
|
|
14924
|
-
if (!info || typeof info !== "object")
|
|
14925
|
-
return null;
|
|
14926
|
-
const session = info;
|
|
14927
|
-
if (typeof session.id !== "string")
|
|
14928
|
-
return null;
|
|
14929
|
-
if (typeof session.parentID !== "string" || session.parentID === "")
|
|
14930
|
-
return null;
|
|
14931
|
-
return { childID: session.id, parentID: session.parentID, agent: null };
|
|
14932
|
-
}
|
|
14933
|
-
function extractEndedSessionID(event) {
|
|
14934
|
-
if (!event || typeof event !== "object")
|
|
14935
|
-
return null;
|
|
14936
|
-
const e = event;
|
|
14937
|
-
if (typeof e.type !== "string")
|
|
14938
|
-
return null;
|
|
14939
|
-
if (e.type !== "session.idle" && e.type !== "session.deleted" && e.type !== "session.error") {
|
|
14940
|
-
return null;
|
|
14941
|
-
}
|
|
14942
|
-
const props = e.properties ?? {};
|
|
14943
|
-
const direct = props["sessionID"];
|
|
14944
|
-
if (typeof direct === "string" && direct)
|
|
14945
|
-
return { type: e.type, sessionID: direct };
|
|
14946
|
-
const info = props["info"];
|
|
14947
|
-
if (info && typeof info === "object") {
|
|
14948
|
-
const sid = info.id;
|
|
14949
|
-
if (typeof sid === "string" && sid)
|
|
14950
|
-
return { type: e.type, sessionID: sid };
|
|
14951
|
-
}
|
|
14952
|
-
return null;
|
|
14953
|
-
}
|
|
14954
|
-
function registerInflight(payload, now = Date.now()) {
|
|
14955
|
-
const r = {
|
|
14956
|
-
childID: payload.childID,
|
|
14957
|
-
parentID: payload.parentID,
|
|
14958
|
-
agent: payload.agent,
|
|
14959
|
-
startedAt: now,
|
|
14960
|
-
lastBeatAt: now,
|
|
14961
|
-
lastTool: null
|
|
14962
|
-
};
|
|
14963
|
-
inflight2.set(payload.childID, r);
|
|
14964
|
-
return r;
|
|
14965
|
-
}
|
|
14966
|
-
function recordToolBeat(sessionID, tool2, now = Date.now()) {
|
|
14967
|
-
const r = inflight2.get(sessionID);
|
|
14968
|
-
if (!r)
|
|
14969
|
-
return null;
|
|
14970
|
-
r.lastBeatAt = now;
|
|
14971
|
-
r.lastTool = tool2;
|
|
14972
|
-
return r;
|
|
14973
|
-
}
|
|
14974
|
-
function clearInflight2(sessionID) {
|
|
14975
|
-
const r = inflight2.get(sessionID);
|
|
14976
|
-
if (!r)
|
|
14977
|
-
return null;
|
|
14978
|
-
inflight2.delete(sessionID);
|
|
14979
|
-
return r;
|
|
14980
|
-
}
|
|
14981
|
-
function pickHeartbeats(now = Date.now()) {
|
|
14982
|
-
const out = [];
|
|
14983
|
-
for (const r of inflight2.values()) {
|
|
14984
|
-
if (now - r.lastBeatAt >= HEARTBEAT_DEBOUNCE_MS)
|
|
14985
|
-
out.push(r);
|
|
14986
|
-
}
|
|
14987
|
-
return out;
|
|
14988
|
-
}
|
|
14989
|
-
function fmtElapsed(ms) {
|
|
14990
|
-
const total = Math.max(0, Math.floor(ms / 1000));
|
|
14991
|
-
const m = Math.floor(total / 60);
|
|
14992
|
-
const s = total % 60;
|
|
14993
|
-
return m > 0 ? `${m}m${s.toString().padStart(2, "0")}s` : `${s}s`;
|
|
14994
|
-
}
|
|
14995
|
-
function buildStartToast(r) {
|
|
14996
|
-
const who = r.agent ?? "subagent";
|
|
14997
|
-
return {
|
|
14998
|
-
message: `\uD83D\uDE80 子 session 启动: ${who}`,
|
|
14999
|
-
variant: "info"
|
|
15000
|
-
};
|
|
15001
|
-
}
|
|
15002
|
-
function buildHeartbeatToast(r, now = Date.now()) {
|
|
15003
|
-
const who = r.agent ?? "subagent";
|
|
15004
|
-
const tool2 = r.lastTool ?? "thinking";
|
|
15005
|
-
return {
|
|
15006
|
-
message: `⏳ ${who} 仍在运行 ${fmtElapsed(now - r.startedAt)} | 当前: ${tool2}`,
|
|
15007
|
-
variant: "info"
|
|
15008
|
-
};
|
|
15009
|
-
}
|
|
15010
|
-
function buildEndToast(r, type, now = Date.now()) {
|
|
15011
|
-
const who = r.agent ?? "subagent";
|
|
15012
|
-
const elapsed = fmtElapsed(now - r.startedAt);
|
|
15013
|
-
if (type === "session.error") {
|
|
15014
|
-
return { message: `❌ ${who} 失败 (${elapsed})`, variant: "error" };
|
|
15423
|
+
function isRecoveryWorthShowing(plan) {
|
|
15424
|
+
if (!plan.last_session_id)
|
|
15425
|
+
return false;
|
|
15426
|
+
if (plan.reason === "no_session")
|
|
15427
|
+
return false;
|
|
15428
|
+
const hasSignal = plan.pending_changes_likely || plan.open_subtasks_likely || plan.inflight_tools.length > 0 || plan.reason === "explicit_marker";
|
|
15429
|
+
return hasSignal;
|
|
15430
|
+
}
|
|
15431
|
+
|
|
15432
|
+
// plugins/session-recovery.ts
|
|
15433
|
+
var PLUGIN_NAME17 = "session-recovery";
|
|
15434
|
+
logLifecycle(PLUGIN_NAME17, "import", {});
|
|
15435
|
+
async function processSessionStart(currentSessionId, opts = {}) {
|
|
15436
|
+
if (opts.disabled) {
|
|
15437
|
+
return { ok: true, injected: false, reason: "disabled" };
|
|
15015
15438
|
}
|
|
15016
|
-
|
|
15017
|
-
|
|
15439
|
+
const excludeIds = new Set(opts.excludeIds ?? []);
|
|
15440
|
+
if (currentSessionId)
|
|
15441
|
+
excludeIds.add(currentSessionId);
|
|
15442
|
+
const r = await scanLastSession({ ...opts, excludeIds: [...excludeIds] });
|
|
15443
|
+
if (!r.ok) {
|
|
15444
|
+
opts.log?.warn?.(`[${PLUGIN_NAME17}] 扫描失败:${r.error}`);
|
|
15445
|
+
return { ok: false, injected: false, reason: "scan_error", error: r.error };
|
|
15018
15446
|
}
|
|
15019
|
-
|
|
15020
|
-
|
|
15021
|
-
|
|
15022
|
-
if (raw === "info" || raw === "warning")
|
|
15023
|
-
return "default";
|
|
15024
|
-
return raw;
|
|
15025
|
-
}
|
|
15026
|
-
async function showToast3(client, payload, log8) {
|
|
15027
|
-
if (typeof client?.tui?.showToast !== "function") {
|
|
15028
|
-
log8?.debug?.("tui.showToast 不可用,noop");
|
|
15029
|
-
return false;
|
|
15447
|
+
const plan = r.plan;
|
|
15448
|
+
if (!isRecoveryWorthShowing(plan)) {
|
|
15449
|
+
return { ok: true, injected: false, plan, reason: "no_signal" };
|
|
15030
15450
|
}
|
|
15031
|
-
|
|
15032
|
-
|
|
15033
|
-
|
|
15034
|
-
|
|
15035
|
-
|
|
15036
|
-
|
|
15037
|
-
|
|
15038
|
-
}
|
|
15039
|
-
|
|
15040
|
-
|
|
15041
|
-
} catch (err) {
|
|
15042
|
-
log8?.warn("tui.showToast 抛错(已隔离)", {
|
|
15043
|
-
error: err instanceof Error ? err.message : String(err)
|
|
15044
|
-
});
|
|
15045
|
-
return false;
|
|
15451
|
+
const prompt = renderPrompt(plan);
|
|
15452
|
+
const injection = { source: "session-recovery", plan, prompt };
|
|
15453
|
+
if (opts.injectRecovery) {
|
|
15454
|
+
try {
|
|
15455
|
+
await opts.injectRecovery(injection);
|
|
15456
|
+
} catch (err) {
|
|
15457
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
15458
|
+
opts.log?.warn?.(`[${PLUGIN_NAME17}] injectRecovery 异常:${msg}`);
|
|
15459
|
+
return { ok: false, injected: false, plan, reason: "inject_error", error: msg };
|
|
15460
|
+
}
|
|
15046
15461
|
}
|
|
15462
|
+
return { ok: true, injected: true, plan, reason: "ok" };
|
|
15047
15463
|
}
|
|
15048
|
-
|
|
15049
|
-
|
|
15050
|
-
|
|
15051
|
-
|
|
15052
|
-
|
|
15053
|
-
|
|
15054
|
-
|
|
15055
|
-
|
|
15056
|
-
|
|
15057
|
-
|
|
15058
|
-
|
|
15059
|
-
|
|
15060
|
-
|
|
15061
|
-
|
|
15062
|
-
const sent = await showToast3(client, t, log8);
|
|
15063
|
-
safeWriteLog(PLUGIN_NAME16, {
|
|
15064
|
-
hook: "interval",
|
|
15065
|
-
child: r.childID,
|
|
15066
|
-
parent: r.parentID,
|
|
15067
|
-
tool: r.lastTool,
|
|
15068
|
-
elapsed_ms: Date.now() - r.startedAt,
|
|
15069
|
-
toast_sent: sent
|
|
15070
|
-
});
|
|
15071
|
-
r.lastBeatAt = Date.now();
|
|
15072
|
-
}
|
|
15073
|
-
});
|
|
15074
|
-
}, HEARTBEAT_INTERVAL_MS2);
|
|
15075
|
-
if (typeof interval.unref === "function") {
|
|
15076
|
-
interval.unref();
|
|
15464
|
+
function renderPrompt(plan) {
|
|
15465
|
+
const lines = [];
|
|
15466
|
+
lines.push("【会话恢复提示】");
|
|
15467
|
+
lines.push(plan.summary);
|
|
15468
|
+
lines.push("");
|
|
15469
|
+
lines.push("如果用户接下来未明确提及,请优先:");
|
|
15470
|
+
if (plan.pending_changes_likely) {
|
|
15471
|
+
lines.push(" • 询问是否要 review 上次的暂存区改动并 apply / 丢弃");
|
|
15472
|
+
}
|
|
15473
|
+
if (plan.open_subtasks_likely) {
|
|
15474
|
+
lines.push(" • 询问是否要继续上次未完成的子任务");
|
|
15475
|
+
}
|
|
15476
|
+
if (plan.last_user_intent) {
|
|
15477
|
+
lines.push(` • 确认是否要继续推进上次的目标:"${plan.last_user_intent}"`);
|
|
15077
15478
|
}
|
|
15479
|
+
if (!plan.pending_changes_likely && !plan.open_subtasks_likely && !plan.last_user_intent) {
|
|
15480
|
+
lines.push(" • 询问是否要恢复上次的工作(信号较弱,可能不需要)");
|
|
15481
|
+
}
|
|
15482
|
+
lines.push("");
|
|
15483
|
+
lines.push("(如果用户明确开了新话题,本提示可忽略)");
|
|
15484
|
+
return lines.join(`
|
|
15485
|
+
`);
|
|
15486
|
+
}
|
|
15487
|
+
var log8 = makePluginLogger(PLUGIN_NAME17);
|
|
15488
|
+
var _lastInjection = null;
|
|
15489
|
+
var sessionRecoveryServer = async (ctx) => {
|
|
15490
|
+
logLifecycle(PLUGIN_NAME17, "activate", { directory: ctx.directory });
|
|
15078
15491
|
return {
|
|
15079
15492
|
event: async ({ event }) => {
|
|
15080
|
-
await safeAsync(
|
|
15081
|
-
const
|
|
15082
|
-
if (
|
|
15083
|
-
const record = registerInflight(created);
|
|
15084
|
-
safeWriteLog(PLUGIN_NAME16, {
|
|
15085
|
-
hook: "event",
|
|
15086
|
-
type: "session.created",
|
|
15087
|
-
child: created.childID,
|
|
15088
|
-
parent: created.parentID
|
|
15089
|
-
});
|
|
15090
|
-
const startToast = buildStartToast(record);
|
|
15091
|
-
const sent = await showToast3(client, { ...startToast, duration: START_TOAST_DURATION_MS }, log8);
|
|
15092
|
-
safeWriteLog(PLUGIN_NAME16, {
|
|
15093
|
-
hook: "event",
|
|
15094
|
-
type: "session.created.toast",
|
|
15095
|
-
child: created.childID,
|
|
15096
|
-
toast_sent: sent
|
|
15097
|
-
});
|
|
15493
|
+
await safeAsync(PLUGIN_NAME17, "event", async () => {
|
|
15494
|
+
const e = event;
|
|
15495
|
+
if (!e || typeof e.type !== "string")
|
|
15098
15496
|
return;
|
|
15099
|
-
|
|
15100
|
-
|
|
15101
|
-
|
|
15102
|
-
|
|
15103
|
-
|
|
15104
|
-
|
|
15105
|
-
|
|
15106
|
-
|
|
15107
|
-
|
|
15108
|
-
type: ended.type,
|
|
15109
|
-
child: r.childID,
|
|
15110
|
-
elapsed_ms: Date.now() - r.startedAt,
|
|
15111
|
-
toast_sent: sent,
|
|
15112
|
-
end_toast_message: t.message
|
|
15113
|
-
});
|
|
15497
|
+
if (e.type !== "session.start")
|
|
15498
|
+
return;
|
|
15499
|
+
const sid = typeof e.properties?.["session_id"] === "string" ? e.properties["session_id"] : undefined;
|
|
15500
|
+
const root = typeof e.properties?.["root"] === "string" ? e.properties["root"] : ctx.directory ?? process.cwd();
|
|
15501
|
+
const r = await processSessionStart(sid, {
|
|
15502
|
+
root,
|
|
15503
|
+
log: log8,
|
|
15504
|
+
injectRecovery: (inj) => {
|
|
15505
|
+
_lastInjection = inj;
|
|
15114
15506
|
}
|
|
15507
|
+
});
|
|
15508
|
+
safeWriteLog(PLUGIN_NAME17, {
|
|
15509
|
+
hook: "event",
|
|
15510
|
+
type: "session.start",
|
|
15511
|
+
ok: r.ok,
|
|
15512
|
+
injected: r.injected,
|
|
15513
|
+
reason: r.reason,
|
|
15514
|
+
last_session_id: r.plan?.last_session_id
|
|
15515
|
+
});
|
|
15516
|
+
if (r.injected && r.plan) {
|
|
15517
|
+
log8.info(`[${PLUGIN_NAME17}] 注入恢复提示(last=${r.plan.last_session_id?.slice(0, 8) ?? "?"}, reason=${r.plan.reason})`);
|
|
15115
15518
|
}
|
|
15116
15519
|
});
|
|
15117
|
-
},
|
|
15118
|
-
"tool.execute.before": async (input) => {
|
|
15119
|
-
await safeAsync(PLUGIN_NAME16, "tool.execute.before", async () => {
|
|
15120
|
-
if (!input || typeof input.sessionID !== "string" || typeof input.tool !== "string")
|
|
15121
|
-
return;
|
|
15122
|
-
recordToolBeat(input.sessionID, input.tool);
|
|
15123
|
-
});
|
|
15124
15520
|
}
|
|
15125
15521
|
};
|
|
15126
15522
|
};
|
|
15127
|
-
var
|
|
15523
|
+
var handler17 = sessionRecoveryServer;
|
|
15128
15524
|
|
|
15129
15525
|
// plugins/subtasks.ts
|
|
15130
15526
|
import { promises as fs10 } from "node:fs";
|
|
@@ -15154,6 +15550,18 @@ async function schedule(opts) {
|
|
|
15154
15550
|
const results = new Array(opts.subtasks.length);
|
|
15155
15551
|
let nextIdx = 0;
|
|
15156
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
|
+
};
|
|
15157
15565
|
const runOne = async (i, spec) => {
|
|
15158
15566
|
const subStart = now();
|
|
15159
15567
|
let alloc;
|
|
@@ -15161,7 +15569,7 @@ async function schedule(opts) {
|
|
|
15161
15569
|
try {
|
|
15162
15570
|
alloc = await opts.deps.allocateWorktree(spec.id);
|
|
15163
15571
|
} catch (err) {
|
|
15164
|
-
|
|
15572
|
+
const res2 = {
|
|
15165
15573
|
id: spec.id,
|
|
15166
15574
|
ok: false,
|
|
15167
15575
|
summary: clamp(`worktree 分配失败:${describe4(err)}`, limit),
|
|
@@ -15169,9 +15577,21 @@ async function schedule(opts) {
|
|
|
15169
15577
|
duration_ms: now() - subStart,
|
|
15170
15578
|
error: describe4(err)
|
|
15171
15579
|
};
|
|
15580
|
+
results[i] = res2;
|
|
15581
|
+
await fireFinish(i, res2);
|
|
15172
15582
|
return;
|
|
15173
15583
|
}
|
|
15174
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
|
+
}
|
|
15175
15595
|
const ctl = new AbortController;
|
|
15176
15596
|
const cascade = () => ctl.abort();
|
|
15177
15597
|
if (globalCtl.signal.aborted)
|
|
@@ -15224,6 +15644,7 @@ async function schedule(opts) {
|
|
|
15224
15644
|
}
|
|
15225
15645
|
}
|
|
15226
15646
|
results[i] = res;
|
|
15647
|
+
await fireFinish(i, res);
|
|
15227
15648
|
};
|
|
15228
15649
|
const launch = async () => {
|
|
15229
15650
|
while (true) {
|
|
@@ -15232,13 +15653,15 @@ async function schedule(opts) {
|
|
|
15232
15653
|
return;
|
|
15233
15654
|
if (globalCtl.signal.aborted) {
|
|
15234
15655
|
const spec = opts.subtasks[i];
|
|
15235
|
-
|
|
15656
|
+
const res = {
|
|
15236
15657
|
id: spec.id,
|
|
15237
15658
|
ok: false,
|
|
15238
15659
|
summary: clamp("调度器已取消,未启动", limit),
|
|
15239
15660
|
status: "cancelled",
|
|
15240
15661
|
duration_ms: 0
|
|
15241
15662
|
};
|
|
15663
|
+
results[i] = res;
|
|
15664
|
+
await fireFinish(i, res);
|
|
15242
15665
|
continue;
|
|
15243
15666
|
}
|
|
15244
15667
|
await runOne(i, opts.subtasks[i]);
|
|
@@ -15519,20 +15942,20 @@ async function withTimeout2(p, ms, signal) {
|
|
|
15519
15942
|
throw err;
|
|
15520
15943
|
}
|
|
15521
15944
|
}
|
|
15522
|
-
return await new Promise((
|
|
15945
|
+
return await new Promise((resolve11, reject) => {
|
|
15523
15946
|
let settled = false;
|
|
15524
15947
|
const timer = setTimeout(() => {
|
|
15525
15948
|
if (settled)
|
|
15526
15949
|
return;
|
|
15527
15950
|
settled = true;
|
|
15528
|
-
|
|
15951
|
+
resolve11({ kind: "timeout" });
|
|
15529
15952
|
}, ms);
|
|
15530
15953
|
const onAbort = () => {
|
|
15531
15954
|
if (settled)
|
|
15532
15955
|
return;
|
|
15533
15956
|
settled = true;
|
|
15534
15957
|
clearTimeout(timer);
|
|
15535
|
-
|
|
15958
|
+
resolve11({ kind: "aborted" });
|
|
15536
15959
|
};
|
|
15537
15960
|
signal?.addEventListener("abort", onAbort, { once: true });
|
|
15538
15961
|
p.then((value) => {
|
|
@@ -15541,7 +15964,7 @@ async function withTimeout2(p, ms, signal) {
|
|
|
15541
15964
|
settled = true;
|
|
15542
15965
|
clearTimeout(timer);
|
|
15543
15966
|
signal?.removeEventListener("abort", onAbort);
|
|
15544
|
-
|
|
15967
|
+
resolve11({ kind: "ok", value });
|
|
15545
15968
|
}, (err) => {
|
|
15546
15969
|
if (settled)
|
|
15547
15970
|
return;
|
|
@@ -15577,11 +16000,48 @@ function clip4(s, max) {
|
|
|
15577
16000
|
return "";
|
|
15578
16001
|
return s.length <= max ? s : s.slice(0, max - 1) + "…";
|
|
15579
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
|
+
}
|
|
15580
16040
|
|
|
15581
16041
|
// plugins/subtasks.ts
|
|
15582
16042
|
init_opencode_plugin_helpers();
|
|
15583
16043
|
init_runtime_paths();
|
|
15584
|
-
var
|
|
16044
|
+
var PLUGIN_NAME18 = "subtasks";
|
|
15585
16045
|
function getLogFile(root = process.cwd()) {
|
|
15586
16046
|
return path13.join(runtimeDir(root), "logs", "subtasks.log");
|
|
15587
16047
|
}
|
|
@@ -15660,18 +16120,49 @@ async function handleParallelCommand(raw) {
|
|
|
15660
16120
|
specs = splitDescriptions(args.description, { parentId });
|
|
15661
16121
|
}
|
|
15662
16122
|
if (specs.length === 0) {
|
|
15663
|
-
log9?.warn(`[${
|
|
16123
|
+
log9?.warn(`[${PLUGIN_NAME18}] /parallel 缺有效子任务`);
|
|
15664
16124
|
await safeReply2(ctx, "⚠ /parallel 需要至少 1 个子任务(用 ; 或换行分隔)");
|
|
15665
16125
|
return { ok: false, reason: "no_subtasks" };
|
|
15666
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
|
+
}
|
|
15667
16150
|
const runner = ctx.runner ?? mockRunner;
|
|
15668
16151
|
let result;
|
|
15669
16152
|
try {
|
|
15670
16153
|
result = await schedule({
|
|
15671
16154
|
parentId,
|
|
15672
16155
|
subtasks: specs,
|
|
15673
|
-
maxConcurrency
|
|
15674
|
-
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,
|
|
15675
16166
|
deps: {
|
|
15676
16167
|
runSubtask: runner,
|
|
15677
16168
|
allocateWorktree: ctx.allocateWorktree,
|
|
@@ -15687,7 +16178,7 @@ async function handleParallelCommand(raw) {
|
|
|
15687
16178
|
});
|
|
15688
16179
|
} catch (err) {
|
|
15689
16180
|
const msg = err instanceof Error ? err.message : String(err);
|
|
15690
|
-
log9?.error(`[${
|
|
16181
|
+
log9?.error(`[${PLUGIN_NAME18}] schedule 抛错`, { error: msg });
|
|
15691
16182
|
await safeReply2(ctx, `❌ 并发调度失败:${msg}`);
|
|
15692
16183
|
return { ok: false, reason: msg };
|
|
15693
16184
|
}
|
|
@@ -15695,7 +16186,7 @@ async function handleParallelCommand(raw) {
|
|
|
15695
16186
|
const summaryLines = [head, "", "```", result.digest.text, "```"];
|
|
15696
16187
|
await safeReply2(ctx, summaryLines.join(`
|
|
15697
16188
|
`));
|
|
15698
|
-
log9?.info(`[${
|
|
16189
|
+
log9?.info(`[${PLUGIN_NAME18}] schedule 完成`, {
|
|
15699
16190
|
parentId,
|
|
15700
16191
|
success: result.digest.success,
|
|
15701
16192
|
failed: result.digest.failed,
|
|
@@ -15705,7 +16196,7 @@ async function handleParallelCommand(raw) {
|
|
|
15705
16196
|
try {
|
|
15706
16197
|
await ctx.onCompleted(result);
|
|
15707
16198
|
} catch (err) {
|
|
15708
|
-
log9?.warn(`[${
|
|
16199
|
+
log9?.warn(`[${PLUGIN_NAME18}] onCompleted hook 抛错(已隔离)`, {
|
|
15709
16200
|
error: err instanceof Error ? err.message : String(err)
|
|
15710
16201
|
});
|
|
15711
16202
|
}
|
|
@@ -15736,14 +16227,17 @@ async function maybeHandleMessage(raw) {
|
|
|
15736
16227
|
reply: ctx.reply,
|
|
15737
16228
|
runner: ctx.runner,
|
|
15738
16229
|
allocateWorktree: ctx.allocateWorktree,
|
|
15739
|
-
log: ctx.log
|
|
16230
|
+
log: ctx.log,
|
|
16231
|
+
client: ctx.client,
|
|
16232
|
+
parentSessionID: ctx.parentSessionID,
|
|
16233
|
+
directory: ctx.directory
|
|
15740
16234
|
});
|
|
15741
16235
|
}
|
|
15742
16236
|
async function writeLog(level, msg, data) {
|
|
15743
16237
|
const line = JSON.stringify({
|
|
15744
16238
|
ts: new Date().toISOString(),
|
|
15745
16239
|
level,
|
|
15746
|
-
plugin:
|
|
16240
|
+
plugin: PLUGIN_NAME18,
|
|
15747
16241
|
msg,
|
|
15748
16242
|
data
|
|
15749
16243
|
}) + `
|
|
@@ -15754,11 +16248,11 @@ async function writeLog(level, msg, data) {
|
|
|
15754
16248
|
await fs10.appendFile(logFile, line, "utf8");
|
|
15755
16249
|
} catch {}
|
|
15756
16250
|
}
|
|
15757
|
-
logLifecycle(
|
|
16251
|
+
logLifecycle(PLUGIN_NAME18, "import");
|
|
15758
16252
|
var subtasksServer = async (ctx) => {
|
|
15759
|
-
const log9 = makePluginLogger(
|
|
16253
|
+
const log9 = makePluginLogger(PLUGIN_NAME18);
|
|
15760
16254
|
const client = ctx?.client ?? undefined;
|
|
15761
|
-
logLifecycle(
|
|
16255
|
+
logLifecycle(PLUGIN_NAME18, "activate", {
|
|
15762
16256
|
directory: ctx.directory,
|
|
15763
16257
|
hasClient: Boolean(client)
|
|
15764
16258
|
});
|
|
@@ -15779,6 +16273,9 @@ var subtasksServer = async (ctx) => {
|
|
|
15779
16273
|
return Promise.resolve();
|
|
15780
16274
|
},
|
|
15781
16275
|
log: log9,
|
|
16276
|
+
client,
|
|
16277
|
+
parentSessionID: input.sessionID,
|
|
16278
|
+
directory: ctx.directory,
|
|
15782
16279
|
runner: client ? makeOpencodeRunner({
|
|
15783
16280
|
client,
|
|
15784
16281
|
parentSessionID: input.sessionID,
|
|
@@ -15815,20 +16312,20 @@ var subtasksServer = async (ctx) => {
|
|
|
15815
16312
|
});
|
|
15816
16313
|
}
|
|
15817
16314
|
} catch (err) {
|
|
15818
|
-
log9.error(`[${
|
|
16315
|
+
log9.error(`[${PLUGIN_NAME18}] command.execute.before 异常(已隔离)`, {
|
|
15819
16316
|
error: err instanceof Error ? err.message : String(err)
|
|
15820
16317
|
});
|
|
15821
16318
|
}
|
|
15822
16319
|
}
|
|
15823
16320
|
};
|
|
15824
16321
|
};
|
|
15825
|
-
var
|
|
16322
|
+
var handler18 = subtasksServer;
|
|
15826
16323
|
|
|
15827
16324
|
// plugins/terminal-monitor.ts
|
|
15828
16325
|
init_opencode_plugin_helpers();
|
|
15829
16326
|
import * as crypto5 from "node:crypto";
|
|
15830
|
-
var
|
|
15831
|
-
logLifecycle(
|
|
16327
|
+
var PLUGIN_NAME19 = "terminal-monitor";
|
|
16328
|
+
logLifecycle(PLUGIN_NAME19, "import", {});
|
|
15832
16329
|
var DEFAULT_CONFIG7 = {
|
|
15833
16330
|
minScore: 0.6,
|
|
15834
16331
|
cooldownMs: 30000,
|
|
@@ -16110,17 +16607,17 @@ function describeError(err) {
|
|
|
16110
16607
|
return String(err);
|
|
16111
16608
|
}
|
|
16112
16609
|
}
|
|
16113
|
-
var log9 = makePluginLogger(
|
|
16610
|
+
var log9 = makePluginLogger(PLUGIN_NAME19);
|
|
16114
16611
|
var lru = new FingerprintLRU2;
|
|
16115
16612
|
var _lastNotification = null;
|
|
16116
16613
|
var terminalMonitorServer = async (ctx) => {
|
|
16117
|
-
logLifecycle(
|
|
16614
|
+
logLifecycle(PLUGIN_NAME19, "activate", {
|
|
16118
16615
|
directory: ctx.directory,
|
|
16119
16616
|
triggerEventTypes: ["terminal.output", "terminal.exit"]
|
|
16120
16617
|
});
|
|
16121
16618
|
return {
|
|
16122
16619
|
event: async ({ event }) => {
|
|
16123
|
-
await safeAsync(
|
|
16620
|
+
await safeAsync(PLUGIN_NAME19, "event", async () => {
|
|
16124
16621
|
const e = event;
|
|
16125
16622
|
if (!e || typeof e.type !== "string")
|
|
16126
16623
|
return;
|
|
@@ -16134,7 +16631,7 @@ var terminalMonitorServer = async (ctx) => {
|
|
|
16134
16631
|
_lastNotification = msg;
|
|
16135
16632
|
}
|
|
16136
16633
|
});
|
|
16137
|
-
safeWriteLog(
|
|
16634
|
+
safeWriteLog(PLUGIN_NAME19, {
|
|
16138
16635
|
hook: "event",
|
|
16139
16636
|
type: e.type,
|
|
16140
16637
|
notified: r.notified,
|
|
@@ -16142,18 +16639,18 @@ var terminalMonitorServer = async (ctx) => {
|
|
|
16142
16639
|
findings: r.notification?.findings.length ?? 0
|
|
16143
16640
|
});
|
|
16144
16641
|
if (r.notified && r.notification) {
|
|
16145
|
-
log9.info(`[${
|
|
16642
|
+
log9.info(`[${PLUGIN_NAME19}] 反馈 ${r.notification.findings.length} 条 → ${r.notification.summary}`);
|
|
16146
16643
|
}
|
|
16147
16644
|
});
|
|
16148
16645
|
}
|
|
16149
16646
|
};
|
|
16150
16647
|
};
|
|
16151
|
-
var
|
|
16648
|
+
var handler19 = terminalMonitorServer;
|
|
16152
16649
|
|
|
16153
16650
|
// plugins/token-manager.ts
|
|
16154
16651
|
init_opencode_plugin_helpers();
|
|
16155
|
-
var
|
|
16156
|
-
logLifecycle(
|
|
16652
|
+
var PLUGIN_NAME20 = "token-manager";
|
|
16653
|
+
logLifecycle(PLUGIN_NAME20, "import", {});
|
|
16157
16654
|
async function handleMessageBefore(raw, log10, defaults) {
|
|
16158
16655
|
const ctx = raw ?? {};
|
|
16159
16656
|
if (!Array.isArray(ctx.messages) || ctx.messages.length === 0)
|
|
@@ -16173,21 +16670,21 @@ async function handleMessageBefore(raw, log10, defaults) {
|
|
|
16173
16670
|
};
|
|
16174
16671
|
if (r.compressed) {
|
|
16175
16672
|
ctx.messages = r.messages;
|
|
16176
|
-
log10?.info(`[${
|
|
16673
|
+
log10?.info(`[${PLUGIN_NAME20}] 压缩 ${r.before.count}→${r.after.count} 条 / ${r.before.tokens}→${r.after.tokens} tokens`);
|
|
16177
16674
|
}
|
|
16178
16675
|
return r;
|
|
16179
16676
|
} catch (err) {
|
|
16180
|
-
log10?.warn(`[${
|
|
16677
|
+
log10?.warn(`[${PLUGIN_NAME20}] 压缩异常(已隔离)`, {
|
|
16181
16678
|
error: err instanceof Error ? err.message : String(err)
|
|
16182
16679
|
});
|
|
16183
16680
|
return null;
|
|
16184
16681
|
}
|
|
16185
16682
|
}
|
|
16186
|
-
var log10 = makePluginLogger(
|
|
16683
|
+
var log10 = makePluginLogger(PLUGIN_NAME20);
|
|
16187
16684
|
var tokenManagerServer = async (ctx) => {
|
|
16188
16685
|
const rt = loadRuntimeSync();
|
|
16189
16686
|
const threshold = rt.runtime.context.condenser_threshold_ratio;
|
|
16190
|
-
logLifecycle(
|
|
16687
|
+
logLifecycle(PLUGIN_NAME20, "activate", {
|
|
16191
16688
|
directory: ctx.directory,
|
|
16192
16689
|
threshold,
|
|
16193
16690
|
target: DEFAULT_CONDENSE.target,
|
|
@@ -16196,7 +16693,7 @@ var tokenManagerServer = async (ctx) => {
|
|
|
16196
16693
|
});
|
|
16197
16694
|
return {
|
|
16198
16695
|
"experimental.chat.messages.transform": async (_input, output) => {
|
|
16199
|
-
await safeAsync(
|
|
16696
|
+
await safeAsync(PLUGIN_NAME20, "experimental.chat.messages.transform", async () => {
|
|
16200
16697
|
const list = output.messages;
|
|
16201
16698
|
if (!Array.isArray(list) || list.length === 0)
|
|
16202
16699
|
return;
|
|
@@ -16209,7 +16706,7 @@ var tokenManagerServer = async (ctx) => {
|
|
|
16209
16706
|
const r = await handleMessageBefore({ messages: flat }, log10, { threshold });
|
|
16210
16707
|
if (!r)
|
|
16211
16708
|
return;
|
|
16212
|
-
safeWriteLog(
|
|
16709
|
+
safeWriteLog(PLUGIN_NAME20, {
|
|
16213
16710
|
hook: "experimental.chat.messages.transform",
|
|
16214
16711
|
mode: "observe-only",
|
|
16215
16712
|
before_msgs: r.before.count,
|
|
@@ -16220,13 +16717,13 @@ var tokenManagerServer = async (ctx) => {
|
|
|
16220
16717
|
reason: r.reason
|
|
16221
16718
|
});
|
|
16222
16719
|
if (r.compressed) {
|
|
16223
|
-
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)`);
|
|
16224
16721
|
}
|
|
16225
16722
|
});
|
|
16226
16723
|
}
|
|
16227
16724
|
};
|
|
16228
16725
|
};
|
|
16229
|
-
var
|
|
16726
|
+
var handler20 = tokenManagerServer;
|
|
16230
16727
|
|
|
16231
16728
|
// plugins/tool-policy.ts
|
|
16232
16729
|
init_opencode_plugin_helpers();
|
|
@@ -16469,8 +16966,8 @@ function checkFileAccess(acl, file, op) {
|
|
|
16469
16966
|
}
|
|
16470
16967
|
|
|
16471
16968
|
// plugins/tool-policy.ts
|
|
16472
|
-
var
|
|
16473
|
-
logLifecycle(
|
|
16969
|
+
var PLUGIN_NAME21 = "tool-policy";
|
|
16970
|
+
logLifecycle(PLUGIN_NAME21, "import", {});
|
|
16474
16971
|
var EMPTY_ACL = { whitelistMode: false };
|
|
16475
16972
|
function decideToolCall(ctx, cfg = {}) {
|
|
16476
16973
|
const fallbackMode = cfg.defaultMode ?? DEFAULT_RUNTIME.autonomy.default_mode;
|
|
@@ -16522,13 +17019,13 @@ function classifyToolKind(toolName) {
|
|
|
16522
17019
|
return "webfetch";
|
|
16523
17020
|
return "other";
|
|
16524
17021
|
}
|
|
16525
|
-
var log11 = makePluginLogger(
|
|
17022
|
+
var log11 = makePluginLogger(PLUGIN_NAME21);
|
|
16526
17023
|
var toolPolicyServer = async (ctx) => {
|
|
16527
17024
|
const directory = ctx.directory ?? process.cwd();
|
|
16528
17025
|
const cfg = await loadPolicy(directory);
|
|
16529
17026
|
const rt = loadRuntimeSync();
|
|
16530
17027
|
cfg.defaultMode = rt.runtime.autonomy.default_mode;
|
|
16531
|
-
logLifecycle(
|
|
17028
|
+
logLifecycle(PLUGIN_NAME21, "activate", {
|
|
16532
17029
|
directory,
|
|
16533
17030
|
acl_loaded: !!cfg.acl,
|
|
16534
17031
|
default_mode: cfg.defaultMode,
|
|
@@ -16536,7 +17033,7 @@ var toolPolicyServer = async (ctx) => {
|
|
|
16536
17033
|
});
|
|
16537
17034
|
return {
|
|
16538
17035
|
"tool.execute.before": async (input, output) => {
|
|
16539
|
-
await safeAsync(
|
|
17036
|
+
await safeAsync(PLUGIN_NAME21, "tool.execute.before", async () => {
|
|
16540
17037
|
const toolName = input.tool;
|
|
16541
17038
|
const argsObj = output.args ?? {};
|
|
16542
17039
|
const files = [];
|
|
@@ -16552,7 +17049,7 @@ var toolPolicyServer = async (ctx) => {
|
|
|
16552
17049
|
mode: cfg.defaultMode,
|
|
16553
17050
|
files: files.length ? files : undefined
|
|
16554
17051
|
}, cfg);
|
|
16555
|
-
safeWriteLog(
|
|
17052
|
+
safeWriteLog(PLUGIN_NAME21, {
|
|
16556
17053
|
hook: "tool.execute.before",
|
|
16557
17054
|
tool: toolName,
|
|
16558
17055
|
callID: input.callID,
|
|
@@ -16561,19 +17058,19 @@ var toolPolicyServer = async (ctx) => {
|
|
|
16561
17058
|
reasons: decision.reasons
|
|
16562
17059
|
});
|
|
16563
17060
|
if (decision.action === "deny") {
|
|
16564
|
-
log11.warn(`[${
|
|
17061
|
+
log11.warn(`[${PLUGIN_NAME21}] DENY ${toolName}: ${decision.reasons.join("; ")}`);
|
|
16565
17062
|
} else if (decision.action === "confirm") {
|
|
16566
|
-
log11.info(`[${
|
|
17063
|
+
log11.info(`[${PLUGIN_NAME21}] CONFIRM ${toolName}: ${decision.reasons.join("; ")}`);
|
|
16567
17064
|
}
|
|
16568
17065
|
});
|
|
16569
17066
|
}
|
|
16570
17067
|
};
|
|
16571
17068
|
};
|
|
16572
|
-
var
|
|
17069
|
+
var handler21 = toolPolicyServer;
|
|
16573
17070
|
|
|
16574
17071
|
// plugins/update-checker.ts
|
|
16575
17072
|
init_opencode_plugin_helpers();
|
|
16576
|
-
import { existsSync as
|
|
17073
|
+
import { existsSync as existsSync5 } from "node:fs";
|
|
16577
17074
|
import { homedir as homedir7 } from "node:os";
|
|
16578
17075
|
import { join as join15 } from "node:path";
|
|
16579
17076
|
|
|
@@ -16581,7 +17078,7 @@ import { join as join15 } from "node:path";
|
|
|
16581
17078
|
import { createHash as createHash6 } from "node:crypto";
|
|
16582
17079
|
import {
|
|
16583
17080
|
copyFileSync,
|
|
16584
|
-
existsSync as
|
|
17081
|
+
existsSync as existsSync4,
|
|
16585
17082
|
mkdirSync as mkdirSync3,
|
|
16586
17083
|
mkdtempSync,
|
|
16587
17084
|
readFileSync as readFileSync4,
|
|
@@ -16592,7 +17089,7 @@ import {
|
|
|
16592
17089
|
writeFileSync as writeFileSync2
|
|
16593
17090
|
} from "node:fs";
|
|
16594
17091
|
import { homedir as homedir6, tmpdir } from "node:os";
|
|
16595
|
-
import { dirname as
|
|
17092
|
+
import { dirname as dirname7, join as join14 } from "node:path";
|
|
16596
17093
|
import { fileURLToPath } from "node:url";
|
|
16597
17094
|
import * as https from "node:https";
|
|
16598
17095
|
import * as zlib from "node:zlib";
|
|
@@ -16600,7 +17097,7 @@ import * as zlib from "node:zlib";
|
|
|
16600
17097
|
// lib/version-injected.ts
|
|
16601
17098
|
function getInjectedVersion() {
|
|
16602
17099
|
try {
|
|
16603
|
-
const v = "0.3.
|
|
17100
|
+
const v = "0.3.8";
|
|
16604
17101
|
if (typeof v === "string" && /^\d+\.\d+\.\d+/.test(v)) {
|
|
16605
17102
|
return v;
|
|
16606
17103
|
}
|
|
@@ -16689,7 +17186,7 @@ function readLocalVersion() {
|
|
|
16689
17186
|
return injected;
|
|
16690
17187
|
try {
|
|
16691
17188
|
const here = fileURLToPath(import.meta.url);
|
|
16692
|
-
const root =
|
|
17189
|
+
const root = dirname7(dirname7(here));
|
|
16693
17190
|
const pkg = JSON.parse(readFileSync4(join14(root, "package.json"), "utf8"));
|
|
16694
17191
|
return typeof pkg.version === "string" ? pkg.version : "0.0.0";
|
|
16695
17192
|
} catch {
|
|
@@ -16704,7 +17201,7 @@ function defaultCacheFile() {
|
|
|
16704
17201
|
}
|
|
16705
17202
|
function readCache(file) {
|
|
16706
17203
|
try {
|
|
16707
|
-
if (!
|
|
17204
|
+
if (!existsSync4(file))
|
|
16708
17205
|
return null;
|
|
16709
17206
|
const raw = readFileSync4(file, "utf8");
|
|
16710
17207
|
const obj = JSON.parse(raw);
|
|
@@ -16718,7 +17215,7 @@ function readCache(file) {
|
|
|
16718
17215
|
}
|
|
16719
17216
|
function writeCache(file, entry) {
|
|
16720
17217
|
try {
|
|
16721
|
-
mkdirSync3(
|
|
17218
|
+
mkdirSync3(dirname7(file), { recursive: true });
|
|
16722
17219
|
writeFileSync2(file, JSON.stringify(entry, null, 2), "utf8");
|
|
16723
17220
|
} catch {}
|
|
16724
17221
|
}
|
|
@@ -16737,7 +17234,7 @@ function fetchLatestTagFromGitHub(repo) {
|
|
|
16737
17234
|
});
|
|
16738
17235
|
}
|
|
16739
17236
|
function getJsonWithRedirect(url, hopsLeft) {
|
|
16740
|
-
return new Promise((
|
|
17237
|
+
return new Promise((resolve11, reject) => {
|
|
16741
17238
|
const u = new URL(url);
|
|
16742
17239
|
const headers = {
|
|
16743
17240
|
"User-Agent": "codeforge-update-checker",
|
|
@@ -16761,12 +17258,12 @@ function getJsonWithRedirect(url, hopsLeft) {
|
|
|
16761
17258
|
return;
|
|
16762
17259
|
}
|
|
16763
17260
|
const next = new URL(res.headers.location, url).toString();
|
|
16764
|
-
getJsonWithRedirect(next, hopsLeft - 1).then(
|
|
17261
|
+
getJsonWithRedirect(next, hopsLeft - 1).then(resolve11, reject);
|
|
16765
17262
|
return;
|
|
16766
17263
|
}
|
|
16767
17264
|
if (status === 404) {
|
|
16768
17265
|
res.resume();
|
|
16769
|
-
|
|
17266
|
+
resolve11(null);
|
|
16770
17267
|
return;
|
|
16771
17268
|
}
|
|
16772
17269
|
if (status >= 400) {
|
|
@@ -16777,7 +17274,7 @@ function getJsonWithRedirect(url, hopsLeft) {
|
|
|
16777
17274
|
let body = "";
|
|
16778
17275
|
res.setEncoding("utf8");
|
|
16779
17276
|
res.on("data", (chunk) => body += chunk);
|
|
16780
|
-
res.on("end", () =>
|
|
17277
|
+
res.on("end", () => resolve11(body));
|
|
16781
17278
|
});
|
|
16782
17279
|
req.on("timeout", () => {
|
|
16783
17280
|
req.destroy();
|
|
@@ -16817,7 +17314,7 @@ async function fetchLatestFromNpm(opts) {
|
|
|
16817
17314
|
return { version, tarballUrl, integrity };
|
|
16818
17315
|
}
|
|
16819
17316
|
function defaultHttpFetcher(url, timeoutMs) {
|
|
16820
|
-
return new Promise((
|
|
17317
|
+
return new Promise((resolve11, reject) => {
|
|
16821
17318
|
const u = new URL(url);
|
|
16822
17319
|
const headers = {
|
|
16823
17320
|
"User-Agent": "codeforge-update-checker",
|
|
@@ -16834,7 +17331,7 @@ function defaultHttpFetcher(url, timeoutMs) {
|
|
|
16834
17331
|
const status = res.statusCode ?? 0;
|
|
16835
17332
|
if (status === 404) {
|
|
16836
17333
|
res.resume();
|
|
16837
|
-
|
|
17334
|
+
resolve11(null);
|
|
16838
17335
|
return;
|
|
16839
17336
|
}
|
|
16840
17337
|
if (status >= 400) {
|
|
@@ -16845,7 +17342,7 @@ function defaultHttpFetcher(url, timeoutMs) {
|
|
|
16845
17342
|
let body = "";
|
|
16846
17343
|
res.setEncoding("utf8");
|
|
16847
17344
|
res.on("data", (chunk) => body += chunk);
|
|
16848
|
-
res.on("end", () =>
|
|
17345
|
+
res.on("end", () => resolve11(body));
|
|
16849
17346
|
});
|
|
16850
17347
|
req.on("timeout", () => {
|
|
16851
17348
|
req.destroy();
|
|
@@ -16864,7 +17361,7 @@ async function downloadAndExtractBundle(opts) {
|
|
|
16864
17361
|
const tarBuf = zlib.gunzipSync(tarballBuf);
|
|
16865
17362
|
extractTarToDir(tarBuf, tmpRoot);
|
|
16866
17363
|
const bundlePath = join14(tmpRoot, "package", "dist", "index.js");
|
|
16867
|
-
if (!
|
|
17364
|
+
if (!existsSync4(bundlePath)) {
|
|
16868
17365
|
throw new Error(`bundle_not_found: ${bundlePath}`);
|
|
16869
17366
|
}
|
|
16870
17367
|
return { bundlePath, extractDir: tmpRoot };
|
|
@@ -16904,7 +17401,7 @@ function extractTarToDir(tarBuf, destRoot) {
|
|
|
16904
17401
|
if (typeFlag === "0" || typeFlag === "" || typeFlag === "\x00") {
|
|
16905
17402
|
const fileBuf = tarBuf.subarray(offset, offset + size);
|
|
16906
17403
|
const dest = join14(destRoot, fullName);
|
|
16907
|
-
mkdirSync3(
|
|
17404
|
+
mkdirSync3(dirname7(dest), { recursive: true });
|
|
16908
17405
|
writeFileSync2(dest, fileBuf);
|
|
16909
17406
|
} else if (typeFlag === "5") {
|
|
16910
17407
|
mkdirSync3(join14(destRoot, fullName), { recursive: true });
|
|
@@ -16916,7 +17413,7 @@ function defaultBinaryFetcher(url) {
|
|
|
16916
17413
|
return downloadBinary(url, 3);
|
|
16917
17414
|
}
|
|
16918
17415
|
function downloadBinary(url, hopsLeft) {
|
|
16919
|
-
return new Promise((
|
|
17416
|
+
return new Promise((resolve11, reject) => {
|
|
16920
17417
|
const u = new URL(url);
|
|
16921
17418
|
const req = https.request({
|
|
16922
17419
|
host: u.hostname,
|
|
@@ -16934,7 +17431,7 @@ function downloadBinary(url, hopsLeft) {
|
|
|
16934
17431
|
return;
|
|
16935
17432
|
}
|
|
16936
17433
|
const next = new URL(res.headers.location, url).toString();
|
|
16937
|
-
downloadBinary(next, hopsLeft - 1).then(
|
|
17434
|
+
downloadBinary(next, hopsLeft - 1).then(resolve11, reject);
|
|
16938
17435
|
return;
|
|
16939
17436
|
}
|
|
16940
17437
|
if (status >= 400) {
|
|
@@ -16944,7 +17441,7 @@ function downloadBinary(url, hopsLeft) {
|
|
|
16944
17441
|
}
|
|
16945
17442
|
const chunks = [];
|
|
16946
17443
|
res.on("data", (chunk) => chunks.push(chunk));
|
|
16947
|
-
res.on("end", () =>
|
|
17444
|
+
res.on("end", () => resolve11(Buffer.concat(chunks)));
|
|
16948
17445
|
});
|
|
16949
17446
|
req.on("timeout", () => {
|
|
16950
17447
|
req.destroy();
|
|
@@ -16957,16 +17454,16 @@ function downloadBinary(url, hopsLeft) {
|
|
|
16957
17454
|
function atomicReplaceBundle(opts) {
|
|
16958
17455
|
const { source, target, oldVersion } = opts;
|
|
16959
17456
|
const keep = opts.keepBackups ?? 3;
|
|
16960
|
-
if (!
|
|
17457
|
+
if (!existsSync4(source)) {
|
|
16961
17458
|
throw new Error(`atomic_source_missing: ${source}`);
|
|
16962
17459
|
}
|
|
16963
|
-
mkdirSync3(
|
|
17460
|
+
mkdirSync3(dirname7(target), { recursive: true });
|
|
16964
17461
|
const newPath = `${target}.new`;
|
|
16965
17462
|
const backupPath = `${target}.bak.${oldVersion}`;
|
|
16966
17463
|
let strategy = "rename";
|
|
16967
17464
|
try {
|
|
16968
17465
|
copyFileSync(source, newPath);
|
|
16969
|
-
if (
|
|
17466
|
+
if (existsSync4(target)) {
|
|
16970
17467
|
try {
|
|
16971
17468
|
renameSync(target, backupPath);
|
|
16972
17469
|
} catch (e) {
|
|
@@ -17002,7 +17499,7 @@ function atomicReplaceBundle(opts) {
|
|
|
17002
17499
|
return { backupPath, strategy };
|
|
17003
17500
|
} catch (e) {
|
|
17004
17501
|
try {
|
|
17005
|
-
if (
|
|
17502
|
+
if (existsSync4(newPath))
|
|
17006
17503
|
unlinkSync(newPath);
|
|
17007
17504
|
} catch {}
|
|
17008
17505
|
throw e;
|
|
@@ -17012,7 +17509,7 @@ function cleanupOldBackups(target, keep) {
|
|
|
17012
17509
|
if (keep <= 0)
|
|
17013
17510
|
return;
|
|
17014
17511
|
try {
|
|
17015
|
-
const dir =
|
|
17512
|
+
const dir = dirname7(target);
|
|
17016
17513
|
const base = target.substring(dir.length + 1);
|
|
17017
17514
|
const prefix = `${base}.bak.`;
|
|
17018
17515
|
const all = readdirSync(dir).filter((f) => f.startsWith(prefix)).map((f) => {
|
|
@@ -17040,7 +17537,7 @@ function loadCompatibility(opts) {
|
|
|
17040
17537
|
return null;
|
|
17041
17538
|
file = join14(root, "compatibility.json");
|
|
17042
17539
|
}
|
|
17043
|
-
if (!
|
|
17540
|
+
if (!existsSync4(file))
|
|
17044
17541
|
return null;
|
|
17045
17542
|
const raw = readFileSync4(file, "utf8");
|
|
17046
17543
|
const obj = JSON.parse(raw);
|
|
@@ -17063,7 +17560,7 @@ function loadCompatibility(opts) {
|
|
|
17063
17560
|
function inferPluginRoot() {
|
|
17064
17561
|
try {
|
|
17065
17562
|
const here = fileURLToPath(import.meta.url);
|
|
17066
|
-
return
|
|
17563
|
+
return dirname7(dirname7(here));
|
|
17067
17564
|
} catch {
|
|
17068
17565
|
return null;
|
|
17069
17566
|
}
|
|
@@ -17103,13 +17600,29 @@ function compareOpencodeVersion(opts) {
|
|
|
17103
17600
|
}
|
|
17104
17601
|
|
|
17105
17602
|
// plugins/update-checker.ts
|
|
17106
|
-
var
|
|
17603
|
+
var PLUGIN_NAME22 = "update-checker";
|
|
17107
17604
|
var PLUGIN_VERSION2 = "2.0.0";
|
|
17108
|
-
logLifecycle(
|
|
17605
|
+
logLifecycle(PLUGIN_NAME22, "import", { version: PLUGIN_VERSION2 });
|
|
17109
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
|
+
}
|
|
17110
17623
|
const rt = loadRuntimeSync();
|
|
17111
17624
|
const u = rt.runtime.update;
|
|
17112
|
-
logLifecycle(
|
|
17625
|
+
logLifecycle(PLUGIN_NAME22, "activate", {
|
|
17113
17626
|
version: PLUGIN_VERSION2,
|
|
17114
17627
|
auto_check_enabled: u.auto_check_enabled,
|
|
17115
17628
|
interval_hours: u.interval_hours,
|
|
@@ -17121,14 +17634,14 @@ var updateCheckerServer = async (ctx) => {
|
|
|
17121
17634
|
repo_fallback: u.repo,
|
|
17122
17635
|
config_source: "codeforge.json"
|
|
17123
17636
|
});
|
|
17124
|
-
await safeAsync(
|
|
17637
|
+
await safeAsync(PLUGIN_NAME22, "opencode_version_check", async () => {
|
|
17125
17638
|
const compat = loadCompatibility();
|
|
17126
17639
|
const opencodeVer = detectOpencodeVersion();
|
|
17127
17640
|
const verdict = compareOpencodeVersion({
|
|
17128
17641
|
currentOpencodeVer: opencodeVer,
|
|
17129
17642
|
compat
|
|
17130
17643
|
});
|
|
17131
|
-
safeWriteLog(
|
|
17644
|
+
safeWriteLog(PLUGIN_NAME22, {
|
|
17132
17645
|
level: "info",
|
|
17133
17646
|
msg: "opencode_version_check",
|
|
17134
17647
|
opencodeVer,
|
|
@@ -17145,14 +17658,14 @@ var updateCheckerServer = async (ctx) => {
|
|
|
17145
17658
|
}
|
|
17146
17659
|
});
|
|
17147
17660
|
if (!u.auto_check_enabled) {
|
|
17148
|
-
safeWriteLog(
|
|
17661
|
+
safeWriteLog(PLUGIN_NAME22, {
|
|
17149
17662
|
level: "info",
|
|
17150
17663
|
msg: "auto-check disabled (codeforge.json update.auto_check_enabled=false)"
|
|
17151
17664
|
});
|
|
17152
17665
|
return {};
|
|
17153
17666
|
}
|
|
17154
17667
|
setImmediate(() => {
|
|
17155
|
-
safeAsync(
|
|
17668
|
+
safeAsync(PLUGIN_NAME22, "checkAndMaybeUpdate", async () => {
|
|
17156
17669
|
const local = readLocalVersion();
|
|
17157
17670
|
let npmResult = null;
|
|
17158
17671
|
try {
|
|
@@ -17163,7 +17676,7 @@ var updateCheckerServer = async (ctx) => {
|
|
|
17163
17676
|
timeoutMs: 5000
|
|
17164
17677
|
});
|
|
17165
17678
|
} catch (e) {
|
|
17166
|
-
safeWriteLog(
|
|
17679
|
+
safeWriteLog(PLUGIN_NAME22, {
|
|
17167
17680
|
level: "warn",
|
|
17168
17681
|
msg: "npm_fetch_failed",
|
|
17169
17682
|
error: e.message
|
|
@@ -17172,7 +17685,7 @@ var updateCheckerServer = async (ctx) => {
|
|
|
17172
17685
|
return;
|
|
17173
17686
|
}
|
|
17174
17687
|
if (!npmResult) {
|
|
17175
|
-
safeWriteLog(
|
|
17688
|
+
safeWriteLog(PLUGIN_NAME22, {
|
|
17176
17689
|
level: "info",
|
|
17177
17690
|
msg: "npm_no_release",
|
|
17178
17691
|
package: u.package,
|
|
@@ -17181,7 +17694,7 @@ var updateCheckerServer = async (ctx) => {
|
|
|
17181
17694
|
return;
|
|
17182
17695
|
}
|
|
17183
17696
|
const hasUpdate = cmpVersion(local, npmResult.version) < 0;
|
|
17184
|
-
safeWriteLog(
|
|
17697
|
+
safeWriteLog(PLUGIN_NAME22, {
|
|
17185
17698
|
level: "info",
|
|
17186
17699
|
msg: "npm_check_result",
|
|
17187
17700
|
local,
|
|
@@ -17196,10 +17709,10 @@ var updateCheckerServer = async (ctx) => {
|
|
|
17196
17709
|
更新命令:npx ${u.package} install --global`);
|
|
17197
17710
|
return;
|
|
17198
17711
|
}
|
|
17199
|
-
await safeAsync(
|
|
17712
|
+
await safeAsync(PLUGIN_NAME22, "auto_install_bundle", async () => {
|
|
17200
17713
|
const target = getOpencodeBundlePath();
|
|
17201
17714
|
if (!target) {
|
|
17202
|
-
safeWriteLog(
|
|
17715
|
+
safeWriteLog(PLUGIN_NAME22, {
|
|
17203
17716
|
level: "warn",
|
|
17204
17717
|
msg: "auto_install_skip",
|
|
17205
17718
|
reason: "无法定位 opencode bundle 路径"
|
|
@@ -17218,7 +17731,7 @@ var updateCheckerServer = async (ctx) => {
|
|
|
17218
17731
|
oldVersion: local,
|
|
17219
17732
|
keepBackups: u.backup_keep
|
|
17220
17733
|
});
|
|
17221
|
-
safeWriteLog(
|
|
17734
|
+
safeWriteLog(PLUGIN_NAME22, {
|
|
17222
17735
|
level: "info",
|
|
17223
17736
|
msg: "auto_install_success",
|
|
17224
17737
|
local,
|
|
@@ -17252,13 +17765,13 @@ function getOpencodeBundlePath() {
|
|
|
17252
17765
|
candidates.push(join15(localAppData, "opencode", "codeforge", "index.js"));
|
|
17253
17766
|
}
|
|
17254
17767
|
for (const c of candidates) {
|
|
17255
|
-
if (
|
|
17768
|
+
if (existsSync5(c))
|
|
17256
17769
|
return c;
|
|
17257
17770
|
}
|
|
17258
17771
|
return candidates[0] ?? null;
|
|
17259
17772
|
}
|
|
17260
17773
|
async function fallbackToGitHubReleases(ctx, u) {
|
|
17261
|
-
await safeAsync(
|
|
17774
|
+
await safeAsync(PLUGIN_NAME22, "github_fallback", async () => {
|
|
17262
17775
|
const result = await checkUpdateOnce({
|
|
17263
17776
|
repo: u.repo,
|
|
17264
17777
|
intervalMs: u.interval_hours * 3600 * 1000
|
|
@@ -17268,7 +17781,7 @@ async function fallbackToGitHubReleases(ctx, u) {
|
|
|
17268
17781
|
}
|
|
17269
17782
|
async function reportLegacyResult(ctx, result, repo) {
|
|
17270
17783
|
if (result.error && !result.fromCache) {
|
|
17271
|
-
safeWriteLog(
|
|
17784
|
+
safeWriteLog(PLUGIN_NAME22, {
|
|
17272
17785
|
level: "warn",
|
|
17273
17786
|
msg: `update check failed: ${result.error}`,
|
|
17274
17787
|
...result
|
|
@@ -17276,7 +17789,7 @@ async function reportLegacyResult(ctx, result, repo) {
|
|
|
17276
17789
|
return;
|
|
17277
17790
|
}
|
|
17278
17791
|
if (!result.hasUpdate) {
|
|
17279
|
-
safeWriteLog(
|
|
17792
|
+
safeWriteLog(PLUGIN_NAME22, {
|
|
17280
17793
|
level: "info",
|
|
17281
17794
|
msg: `up-to-date (local=${result.local}, remote=${result.remote}${result.fromCache ? ", from_cache" : ""})`,
|
|
17282
17795
|
...result
|
|
@@ -17286,7 +17799,7 @@ async function reportLegacyResult(ctx, result, repo) {
|
|
|
17286
17799
|
const updateCmd = `bunx --bun github:${repo} install`;
|
|
17287
17800
|
const toast = `[CodeForge] 有新版本:${result.local} → ${result.remote}
|
|
17288
17801
|
更新命令:${updateCmd}`;
|
|
17289
|
-
safeWriteLog(
|
|
17802
|
+
safeWriteLog(PLUGIN_NAME22, {
|
|
17290
17803
|
level: "info",
|
|
17291
17804
|
msg: "new_version_available_github_fallback",
|
|
17292
17805
|
local: result.local,
|
|
@@ -17297,17 +17810,17 @@ async function reportLegacyResult(ctx, result, repo) {
|
|
|
17297
17810
|
await postToast(ctx, toast);
|
|
17298
17811
|
}
|
|
17299
17812
|
async function postToast(ctx, message) {
|
|
17300
|
-
await safeAsync(
|
|
17813
|
+
await safeAsync(PLUGIN_NAME22, "client.app.log", async () => {
|
|
17301
17814
|
await ctx.client.app.log({
|
|
17302
17815
|
body: {
|
|
17303
|
-
service:
|
|
17816
|
+
service: PLUGIN_NAME22,
|
|
17304
17817
|
level: "info",
|
|
17305
17818
|
message
|
|
17306
17819
|
}
|
|
17307
17820
|
});
|
|
17308
17821
|
});
|
|
17309
17822
|
}
|
|
17310
|
-
var
|
|
17823
|
+
var handler22 = updateCheckerServer;
|
|
17311
17824
|
|
|
17312
17825
|
// plugins/workflow-engine.ts
|
|
17313
17826
|
init_opencode_plugin_helpers();
|
|
@@ -17811,9 +18324,9 @@ async function runStepAutoFeedback(step, adapter) {
|
|
|
17811
18324
|
}
|
|
17812
18325
|
|
|
17813
18326
|
// plugins/workflow-engine.ts
|
|
17814
|
-
var
|
|
17815
|
-
logLifecycle(
|
|
17816
|
-
var fallbackLog2 = makePluginLogger(
|
|
18327
|
+
var PLUGIN_NAME23 = "workflow-engine";
|
|
18328
|
+
logLifecycle(PLUGIN_NAME23, "import", {});
|
|
18329
|
+
var fallbackLog2 = makePluginLogger(PLUGIN_NAME23);
|
|
17817
18330
|
var _registry = null;
|
|
17818
18331
|
async function loadRegistry(workflowsDir) {
|
|
17819
18332
|
const { loaded, failed } = await loadWorkflowsFromDir(workflowsDir);
|
|
@@ -17833,32 +18346,32 @@ async function handleCommandInvoked(raw, workflowsDir = "workflows") {
|
|
|
17833
18346
|
const log12 = ctx.log ?? fallbackLog2;
|
|
17834
18347
|
const command = typeof ctx.command === "string" ? ctx.command : null;
|
|
17835
18348
|
if (!command) {
|
|
17836
|
-
log12.warn(`[${
|
|
18349
|
+
log12.warn(`[${PLUGIN_NAME23}] command.invoked 缺 command 字段`, ctx);
|
|
17837
18350
|
return null;
|
|
17838
18351
|
}
|
|
17839
18352
|
const reg = await ensureRegistry(workflowsDir);
|
|
17840
18353
|
if (reg.errors.length) {
|
|
17841
|
-
log12.warn(`[${
|
|
18354
|
+
log12.warn(`[${PLUGIN_NAME23}] 有 ${reg.errors.length} 个 workflow 加载失败`, reg.errors);
|
|
17842
18355
|
}
|
|
17843
18356
|
const wf = reg.workflows.find((w) => matchesTrigger(w, command));
|
|
17844
18357
|
if (!wf) {
|
|
17845
|
-
log12.info(`[${
|
|
18358
|
+
log12.info(`[${PLUGIN_NAME23}] no workflow matches "${command}"`);
|
|
17846
18359
|
return null;
|
|
17847
18360
|
}
|
|
17848
|
-
log12.info(`[${
|
|
18361
|
+
log12.info(`[${PLUGIN_NAME23}] dispatch "${command}" → workflow "${wf.name}"`);
|
|
17849
18362
|
try {
|
|
17850
18363
|
const result = await run(wf, {
|
|
17851
18364
|
mode: ctx.adapter ? "real" : "dry_run",
|
|
17852
18365
|
autonomy: ctx.autonomy ?? "semi",
|
|
17853
18366
|
adapter: ctx.adapter
|
|
17854
18367
|
});
|
|
17855
|
-
log12.info(`[${
|
|
18368
|
+
log12.info(`[${PLUGIN_NAME23}] workflow "${wf.name}" 完成 (${result.plan.mode})`, {
|
|
17856
18369
|
steps: result.plan.steps.length,
|
|
17857
18370
|
results: result.results.length
|
|
17858
18371
|
});
|
|
17859
18372
|
return result;
|
|
17860
18373
|
} catch (err) {
|
|
17861
|
-
log12.error(`[${
|
|
18374
|
+
log12.error(`[${PLUGIN_NAME23}] workflow "${wf.name}" 执行失败`, {
|
|
17862
18375
|
error: err instanceof Error ? err.message : String(err)
|
|
17863
18376
|
});
|
|
17864
18377
|
throw err;
|
|
@@ -17867,15 +18380,15 @@ async function handleCommandInvoked(raw, workflowsDir = "workflows") {
|
|
|
17867
18380
|
var workflowEngineServer = async (ctx) => {
|
|
17868
18381
|
const directory = ctx.directory ?? process.cwd();
|
|
17869
18382
|
const workflowsDir = path17.join(directory, "workflows");
|
|
17870
|
-
ensureRegistry(workflowsDir).catch((err) => fallbackLog2.warn(`[${
|
|
18383
|
+
ensureRegistry(workflowsDir).catch((err) => fallbackLog2.warn(`[${PLUGIN_NAME23}] preload workflows failed`, {
|
|
17871
18384
|
error: err instanceof Error ? err.message : String(err)
|
|
17872
18385
|
}));
|
|
17873
|
-
logLifecycle(
|
|
18386
|
+
logLifecycle(PLUGIN_NAME23, "activate", { directory, workflowsDir });
|
|
17874
18387
|
return {
|
|
17875
18388
|
"command.execute.before": async (input, output) => {
|
|
17876
|
-
await safeAsync(
|
|
18389
|
+
await safeAsync(PLUGIN_NAME23, "command.execute.before", async () => {
|
|
17877
18390
|
const cmd = input.command.startsWith("/") ? input.command : `/${input.command}`;
|
|
17878
|
-
safeWriteLog(
|
|
18391
|
+
safeWriteLog(PLUGIN_NAME23, {
|
|
17879
18392
|
hook: "command.execute.before",
|
|
17880
18393
|
command: cmd,
|
|
17881
18394
|
sessionID: input.sessionID
|
|
@@ -17890,14 +18403,14 @@ var workflowEngineServer = async (ctx) => {
|
|
|
17890
18403
|
});
|
|
17891
18404
|
},
|
|
17892
18405
|
"chat.message": async (input, output) => {
|
|
17893
|
-
await safeAsync(
|
|
18406
|
+
await safeAsync(PLUGIN_NAME23, "chat.message", async () => {
|
|
17894
18407
|
const text = extractUserText(output).trim();
|
|
17895
18408
|
if (!text.startsWith("/"))
|
|
17896
18409
|
return;
|
|
17897
18410
|
const cmd = text.split(/\s+/)[0];
|
|
17898
18411
|
if (!cmd)
|
|
17899
18412
|
return;
|
|
17900
|
-
safeWriteLog(
|
|
18413
|
+
safeWriteLog(PLUGIN_NAME23, {
|
|
17901
18414
|
hook: "chat.message",
|
|
17902
18415
|
command: cmd,
|
|
17903
18416
|
sessionID: input.sessionID
|
|
@@ -17907,7 +18420,7 @@ var workflowEngineServer = async (ctx) => {
|
|
|
17907
18420
|
}
|
|
17908
18421
|
};
|
|
17909
18422
|
};
|
|
17910
|
-
var
|
|
18423
|
+
var handler23 = workflowEngineServer;
|
|
17911
18424
|
|
|
17912
18425
|
// src/index.ts
|
|
17913
18426
|
var PLUGIN_ID = "codeforge";
|
|
@@ -17926,16 +18439,17 @@ var HANDLERS = [
|
|
|
17926
18439
|
{ name: "kh-reminder", init: handler11 },
|
|
17927
18440
|
{ name: "memories-context", init: handler12 },
|
|
17928
18441
|
{ name: "model-fallback", init: handler13 },
|
|
17929
|
-
{ name: "pwsh-utf8", init:
|
|
17930
|
-
{ name: "session-recovery", init:
|
|
17931
|
-
{ name: "subtask-heartbeat", init:
|
|
17932
|
-
{ name: "subtasks", init:
|
|
17933
|
-
{ name: "
|
|
17934
|
-
{ 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 },
|
|
17935
18449
|
{ name: "tool-heartbeat", init: handler7 },
|
|
17936
|
-
{ name: "tool-policy", init:
|
|
17937
|
-
{ name: "update-checker", init:
|
|
17938
|
-
{ name: "workflow-engine", init:
|
|
18450
|
+
{ name: "tool-policy", init: handler21 },
|
|
18451
|
+
{ name: "update-checker", init: handler22 },
|
|
18452
|
+
{ name: "workflow-engine", init: handler23 }
|
|
17939
18453
|
];
|
|
17940
18454
|
function makeSerialHook(hookName, fns) {
|
|
17941
18455
|
return async (input, output) => {
|
|
@@ -17950,69 +18464,91 @@ function makeSerialHook(hookName, fns) {
|
|
|
17950
18464
|
}
|
|
17951
18465
|
};
|
|
17952
18466
|
}
|
|
17953
|
-
|
|
17954
|
-
|
|
17955
|
-
|
|
17956
|
-
|
|
17957
|
-
|
|
17958
|
-
|
|
17959
|
-
|
|
17960
|
-
|
|
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
|
+
}
|
|
17961
18484
|
}
|
|
17962
|
-
|
|
17963
|
-
|
|
17964
|
-
|
|
17965
|
-
|
|
17966
|
-
|
|
17967
|
-
|
|
17968
|
-
|
|
17969
|
-
|
|
17970
|
-
|
|
17971
|
-
|
|
17972
|
-
|
|
17973
|
-
|
|
17974
|
-
|
|
17975
|
-
|
|
17976
|
-
|
|
17977
|
-
|
|
17978
|
-
|
|
17979
|
-
|
|
17980
|
-
|
|
17981
|
-
|
|
17982
|
-
|
|
17983
|
-
|
|
17984
|
-
|
|
17985
|
-
|
|
17986
|
-
|
|
17987
|
-
|
|
17988
|
-
|
|
17989
|
-
|
|
17990
|
-
|
|
17991
|
-
|
|
17992
|
-
|
|
17993
|
-
|
|
17994
|
-
|
|
17995
|
-
|
|
17996
|
-
|
|
17997
|
-
|
|
17998
|
-
|
|
17999
|
-
|
|
18000
|
-
|
|
18001
|
-
|
|
18002
|
-
|
|
18003
|
-
|
|
18004
|
-
|
|
18005
|
-
|
|
18006
|
-
|
|
18007
|
-
|
|
18008
|
-
|
|
18009
|
-
|
|
18010
|
-
|
|
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
|
+
};
|
|
18011
18543
|
};
|
|
18012
|
-
}
|
|
18544
|
+
}
|
|
18545
|
+
var codeforgeServer = createCodeforgeServer({ enableDevIsolation: true });
|
|
18546
|
+
var codeforgeDevServer = createCodeforgeServer({ enableDevIsolation: false });
|
|
18013
18547
|
var pluginModule = { id: PLUGIN_ID, server: codeforgeServer };
|
|
18014
18548
|
var src_default = pluginModule;
|
|
18015
18549
|
export {
|
|
18016
18550
|
src_default as default,
|
|
18017
|
-
|
|
18551
|
+
createCodeforgeServer,
|
|
18552
|
+
codeforgeServer,
|
|
18553
|
+
codeforgeDevServer
|
|
18018
18554
|
};
|