billion-context-pi 0.1.43 → 0.1.45
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -0
- package/README.zh-CN.md +12 -0
- package/dist/compress-tool.d.ts +8 -5
- package/dist/index.js +404 -196
- package/dist/index.js.map +1 -1
- package/dist/runtime.d.ts +10 -5
- package/dist/setup-subagent-tools.d.ts +46 -3
- package/dist/update.d.ts +26 -0
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -580,7 +580,7 @@ function truncateLargeToolOutputs(messages, tokenCount, config, countTokens, opt
|
|
|
580
580
|
);
|
|
581
581
|
return { messages: updated, truncatedCount, savedTokens };
|
|
582
582
|
}
|
|
583
|
-
var KEEP_LAST_ORPHANED =
|
|
583
|
+
var KEEP_LAST_ORPHANED = 2;
|
|
584
584
|
function rangeKey(startRef, endRef) {
|
|
585
585
|
return `${startRef}::${endRef}`;
|
|
586
586
|
}
|
|
@@ -3604,7 +3604,7 @@ async function abortableSleep(ms, signal) {
|
|
|
3604
3604
|
if (signal.aborted) return "aborted";
|
|
3605
3605
|
const remaining = end - Date.now();
|
|
3606
3606
|
if (remaining <= 0) return "ok";
|
|
3607
|
-
await new Promise((
|
|
3607
|
+
await new Promise((resolve3) => setTimeout(resolve3, Math.min(250, remaining)));
|
|
3608
3608
|
}
|
|
3609
3609
|
}
|
|
3610
3610
|
|
|
@@ -3930,17 +3930,20 @@ function createRuntime(adapter) {
|
|
|
3930
3930
|
for (const o of outcomes) {
|
|
3931
3931
|
if (compressOutcomeSeen.has(o.toolCallId)) continue;
|
|
3932
3932
|
compressOutcomeSeen.add(o.toolCallId);
|
|
3933
|
-
if (o.isError) {
|
|
3933
|
+
if (o.isError || o.noop === true) {
|
|
3934
3934
|
compressFailCount += 1;
|
|
3935
3935
|
} else if (o.success) {
|
|
3936
3936
|
compressFailCount = 0;
|
|
3937
3937
|
}
|
|
3938
3938
|
}
|
|
3939
3939
|
const latest = outcomes.length > 0 ? outcomes[outcomes.length - 1] : void 0;
|
|
3940
|
-
const retryFor = latest && latest.isError && compressFailCount >= 1 && compressFailCount < MAX_COMPRESS_ATTEMPTS ? latest.toolCallId : null;
|
|
3940
|
+
const retryFor = latest && (latest.isError || latest.noop === true) && compressFailCount >= 1 && compressFailCount < MAX_COMPRESS_ATTEMPTS ? latest.toolCallId : null;
|
|
3941
3941
|
const cappedNow = compressFailCount >= MAX_COMPRESS_ATTEMPTS && prevCount < MAX_COMPRESS_ATTEMPTS;
|
|
3942
3942
|
return { count: compressFailCount, retryFor, cappedNow };
|
|
3943
3943
|
}
|
|
3944
|
+
function compressRetryCappedFor(turnKey) {
|
|
3945
|
+
return compressFailTurnKey === turnKey && compressFailCount >= MAX_COMPRESS_ATTEMPTS;
|
|
3946
|
+
}
|
|
3944
3947
|
function clearCompressRetryTracking() {
|
|
3945
3948
|
compressOutcomeSeen.clear();
|
|
3946
3949
|
compressFailTurnKey = null;
|
|
@@ -3949,10 +3952,10 @@ function createRuntime(adapter) {
|
|
|
3949
3952
|
async function acquireLock(sid) {
|
|
3950
3953
|
const prev = locks.get(sid) ?? Promise.resolve();
|
|
3951
3954
|
let release;
|
|
3952
|
-
const next = new Promise((
|
|
3955
|
+
const next = new Promise((resolve3) => {
|
|
3953
3956
|
release = () => {
|
|
3954
3957
|
locks.delete(sid);
|
|
3955
|
-
|
|
3958
|
+
resolve3();
|
|
3956
3959
|
};
|
|
3957
3960
|
});
|
|
3958
3961
|
locks.set(sid, prev.then(() => next));
|
|
@@ -4033,7 +4036,7 @@ function createRuntime(adapter) {
|
|
|
4033
4036
|
nudgeShownTurns.add(k);
|
|
4034
4037
|
}, nudgeShownFor: (k) => nudgeShownTurns.has(k), clearNudgeTracking: () => {
|
|
4035
4038
|
nudgeShownTurns.clear();
|
|
4036
|
-
}, noteCompressOutcomes, clearCompressRetryTracking, liveContextLimit, configFor, reloadConfig, stateFor, save, acquireLock, overflowFor, overflowDrop, throttleFor, throttleDrop };
|
|
4039
|
+
}, noteCompressOutcomes, compressRetryCappedFor, clearCompressRetryTracking, liveContextLimit, configFor, reloadConfig, stateFor, save, acquireLock, overflowFor, overflowDrop, throttleFor, throttleDrop };
|
|
4037
4040
|
}
|
|
4038
4041
|
|
|
4039
4042
|
// node_modules/typebox/build/system/memory/memory.mjs
|
|
@@ -8545,8 +8548,16 @@ function normalizeRanges(content) {
|
|
|
8545
8548
|
}
|
|
8546
8549
|
return ranges;
|
|
8547
8550
|
}
|
|
8551
|
+
function compressPanelBlocks(text) {
|
|
8552
|
+
if (!text.trimStart().startsWith("\u25A3 ACP |")) return -1;
|
|
8553
|
+
const m = text.match(/, (\d+) blocks?\)/);
|
|
8554
|
+
return m ? Number(m[1]) : -1;
|
|
8555
|
+
}
|
|
8548
8556
|
function isCompressSuccessText(text) {
|
|
8549
|
-
return text
|
|
8557
|
+
return compressPanelBlocks(text) > 0;
|
|
8558
|
+
}
|
|
8559
|
+
function isCompressNoopText(text) {
|
|
8560
|
+
return compressPanelBlocks(text) === 0;
|
|
8550
8561
|
}
|
|
8551
8562
|
function tier3OnlyRewrite(newBlocks, allBlocks) {
|
|
8552
8563
|
if (newBlocks.length === 0) return null;
|
|
@@ -9874,7 +9885,7 @@ function makeDelegateWaitTool(_pi) {
|
|
|
9874
9885
|
if (run.waiter) {
|
|
9875
9886
|
return { details: void 0, content: [{ type: "text", text: `Delegate \`${args.runId}\` already has a wait in progress; do not wait on it twice.` }] };
|
|
9876
9887
|
}
|
|
9877
|
-
return new Promise((
|
|
9888
|
+
return new Promise((resolve3) => {
|
|
9878
9889
|
let settled = false;
|
|
9879
9890
|
let timer2;
|
|
9880
9891
|
const finish = (result) => {
|
|
@@ -9883,7 +9894,7 @@ function makeDelegateWaitTool(_pi) {
|
|
|
9883
9894
|
run.waiter = void 0;
|
|
9884
9895
|
if (timer2) clearTimeout(timer2);
|
|
9885
9896
|
signal?.removeEventListener("abort", onAbort);
|
|
9886
|
-
|
|
9897
|
+
resolve3(result);
|
|
9887
9898
|
};
|
|
9888
9899
|
const onAbort = () => {
|
|
9889
9900
|
finish({ details: void 0, content: [{ type: "text", text: `Aborted; delegate \`${args.runId}\` is still running in the background. A notification will be injected when it finishes.` }] });
|
|
@@ -9996,9 +10007,9 @@ async function runDelegate(pi, args, ctx, signal) {
|
|
|
9996
10007
|
await mkdir2(OUT_DIR, { recursive: true });
|
|
9997
10008
|
const replyStream = createWriteStream(replyFile, { flags: "a" });
|
|
9998
10009
|
const activityStream = useJsonStream ? createWriteStream(activityFile, { flags: "a" }) : null;
|
|
9999
|
-
const endStream = (s) => new Promise((
|
|
10000
|
-
if (!s || s.destroyed || s.closed) return
|
|
10001
|
-
s.end(() =>
|
|
10010
|
+
const endStream = (s) => new Promise((resolve3) => {
|
|
10011
|
+
if (!s || s.destroyed || s.closed) return resolve3();
|
|
10012
|
+
s.end(() => resolve3());
|
|
10002
10013
|
});
|
|
10003
10014
|
let stdoutBuf = "";
|
|
10004
10015
|
const applier = makeEventApplier(
|
|
@@ -10167,7 +10178,7 @@ Complete the task below.`, "utf8");
|
|
|
10167
10178
|
return { cliArgs, tmpDir, isAsync, useJsonStream };
|
|
10168
10179
|
}
|
|
10169
10180
|
function waitForChild(child, signal) {
|
|
10170
|
-
return new Promise((
|
|
10181
|
+
return new Promise((resolve3) => {
|
|
10171
10182
|
const stdoutChunks = [];
|
|
10172
10183
|
let stderrText = "";
|
|
10173
10184
|
child.stdout?.on("data", (c) => stdoutChunks.push(c));
|
|
@@ -10186,7 +10197,7 @@ function waitForChild(child, signal) {
|
|
|
10186
10197
|
function finish(r) {
|
|
10187
10198
|
clearTimeout(timer2);
|
|
10188
10199
|
signal?.removeEventListener("abort", onAbort);
|
|
10189
|
-
|
|
10200
|
+
resolve3(r);
|
|
10190
10201
|
}
|
|
10191
10202
|
child.on("close", (code) => {
|
|
10192
10203
|
finish({
|
|
@@ -10389,6 +10400,199 @@ async function handleStatus(args, runtime, ctx) {
|
|
|
10389
10400
|
${extra.join("\n")}` : base;
|
|
10390
10401
|
}
|
|
10391
10402
|
|
|
10403
|
+
// src/setup-subagent-tools.ts
|
|
10404
|
+
import * as fs3 from "fs";
|
|
10405
|
+
import * as os from "os";
|
|
10406
|
+
import * as path4 from "path";
|
|
10407
|
+
import { CONFIG_DIR_NAME as CONFIG_DIR_NAME3 } from "@earendil-works/pi-coding-agent";
|
|
10408
|
+
var ACP_TOOLS2 = ["compress", "decompress", "search_context", "acp_status"];
|
|
10409
|
+
function resolveAgentDir() {
|
|
10410
|
+
const envDir = process.env.PI_CODING_AGENT_DIR;
|
|
10411
|
+
if (envDir) {
|
|
10412
|
+
if (envDir === "~") return os.homedir();
|
|
10413
|
+
if (envDir.startsWith("~/")) return path4.join(os.homedir(), envDir.slice(2));
|
|
10414
|
+
return envDir;
|
|
10415
|
+
}
|
|
10416
|
+
return path4.join(os.homedir(), CONFIG_DIR_NAME3, "agent");
|
|
10417
|
+
}
|
|
10418
|
+
function parseFrontmatterTools(content) {
|
|
10419
|
+
const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
|
10420
|
+
if (!match) return null;
|
|
10421
|
+
const body = match[1];
|
|
10422
|
+
if (!body) return null;
|
|
10423
|
+
let name;
|
|
10424
|
+
let tools;
|
|
10425
|
+
for (const rawLine of body.split(/\r?\n/)) {
|
|
10426
|
+
const line = rawLine.trim();
|
|
10427
|
+
if (line.startsWith("name:")) {
|
|
10428
|
+
name = line.slice(5).trim().replace(/^["']|["']$/g, "");
|
|
10429
|
+
} else if (line.startsWith("tools:")) {
|
|
10430
|
+
const value = line.slice(6).trim();
|
|
10431
|
+
if (value) {
|
|
10432
|
+
tools = value.split(",").map((t) => t.trim().replace(/^["']|["']$/g, "")).filter(Boolean);
|
|
10433
|
+
}
|
|
10434
|
+
}
|
|
10435
|
+
}
|
|
10436
|
+
if (!name) return null;
|
|
10437
|
+
return tools ? { name, tools } : { name };
|
|
10438
|
+
}
|
|
10439
|
+
function findPiSubagentsInstall(agentDir, cwd) {
|
|
10440
|
+
const candidates = [
|
|
10441
|
+
path4.join(agentDir, "npm", "node_modules", "pi-subagents"),
|
|
10442
|
+
path4.join(cwd, CONFIG_DIR_NAME3, "npm", "node_modules", "pi-subagents")
|
|
10443
|
+
];
|
|
10444
|
+
const extensionRoots = [
|
|
10445
|
+
path4.join(agentDir, "extensions"),
|
|
10446
|
+
path4.join(cwd, CONFIG_DIR_NAME3, "extensions")
|
|
10447
|
+
];
|
|
10448
|
+
for (const dir of candidates) {
|
|
10449
|
+
if (fs3.existsSync(path4.join(dir, "package.json"))) return dir;
|
|
10450
|
+
}
|
|
10451
|
+
for (const root of extensionRoots) {
|
|
10452
|
+
let entries;
|
|
10453
|
+
try {
|
|
10454
|
+
entries = fs3.readdirSync(root, { withFileTypes: true });
|
|
10455
|
+
} catch {
|
|
10456
|
+
continue;
|
|
10457
|
+
}
|
|
10458
|
+
for (const entry of entries) {
|
|
10459
|
+
if (!entry.isDirectory()) continue;
|
|
10460
|
+
const pkgPath = path4.join(root, entry.name, "package.json");
|
|
10461
|
+
try {
|
|
10462
|
+
if (JSON.parse(fs3.readFileSync(pkgPath, "utf-8")).name === "pi-subagents") {
|
|
10463
|
+
return path4.join(root, entry.name);
|
|
10464
|
+
}
|
|
10465
|
+
} catch {
|
|
10466
|
+
}
|
|
10467
|
+
}
|
|
10468
|
+
}
|
|
10469
|
+
return null;
|
|
10470
|
+
}
|
|
10471
|
+
function discoverBuiltinAgents(installDir) {
|
|
10472
|
+
const agentsDir = path4.join(installDir, "agents");
|
|
10473
|
+
let entries;
|
|
10474
|
+
try {
|
|
10475
|
+
entries = fs3.readdirSync(agentsDir, { withFileTypes: true });
|
|
10476
|
+
} catch {
|
|
10477
|
+
return [];
|
|
10478
|
+
}
|
|
10479
|
+
const parsed = [];
|
|
10480
|
+
for (const entry of entries) {
|
|
10481
|
+
if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
|
|
10482
|
+
try {
|
|
10483
|
+
const result = parseFrontmatterTools(fs3.readFileSync(path4.join(agentsDir, entry.name), "utf-8"));
|
|
10484
|
+
if (result) parsed.push(result);
|
|
10485
|
+
} catch {
|
|
10486
|
+
}
|
|
10487
|
+
}
|
|
10488
|
+
return parsed;
|
|
10489
|
+
}
|
|
10490
|
+
function desiredTools(baseTools) {
|
|
10491
|
+
const tools = baseTools ? [...baseTools] : [];
|
|
10492
|
+
for (const tool of ACP_TOOLS2) {
|
|
10493
|
+
if (!tools.includes(tool)) tools.push(tool);
|
|
10494
|
+
}
|
|
10495
|
+
return tools;
|
|
10496
|
+
}
|
|
10497
|
+
function ensureSubagentAcpTools(settingsPath, options) {
|
|
10498
|
+
const agentDir = options?.agentDir ?? resolveAgentDir();
|
|
10499
|
+
const cwd = options?.cwd ?? process.cwd();
|
|
10500
|
+
const path_ = settingsPath ?? path4.join(agentDir, "settings.json");
|
|
10501
|
+
let installDir;
|
|
10502
|
+
if (options?.installDir) {
|
|
10503
|
+
installDir = path4.resolve(options.installDir);
|
|
10504
|
+
if (!fs3.existsSync(path4.join(installDir, "package.json"))) {
|
|
10505
|
+
return { path: path_, action: "failed", reason: `not a package: ${installDir}` };
|
|
10506
|
+
}
|
|
10507
|
+
} else {
|
|
10508
|
+
const detected = findPiSubagentsInstall(agentDir, cwd);
|
|
10509
|
+
if (!detected) {
|
|
10510
|
+
return { path: path_, action: "skipped", reason: "pi-subagents not installed" };
|
|
10511
|
+
}
|
|
10512
|
+
installDir = detected;
|
|
10513
|
+
}
|
|
10514
|
+
const builtins = discoverBuiltinAgents(installDir);
|
|
10515
|
+
if (builtins.length === 0) {
|
|
10516
|
+
return { path: path_, action: "skipped", reason: "pi-subagents ships no agents/*.md" };
|
|
10517
|
+
}
|
|
10518
|
+
let settingsRaw;
|
|
10519
|
+
try {
|
|
10520
|
+
settingsRaw = fs3.readFileSync(path_, "utf-8");
|
|
10521
|
+
} catch {
|
|
10522
|
+
return { path: path_, action: "skipped", reason: "not found" };
|
|
10523
|
+
}
|
|
10524
|
+
let settings2;
|
|
10525
|
+
try {
|
|
10526
|
+
settings2 = JSON.parse(settingsRaw);
|
|
10527
|
+
if (typeof settings2 !== "object" || settings2 === null || Array.isArray(settings2)) {
|
|
10528
|
+
return { path: path_, action: "failed", reason: "settings.json root is not an object" };
|
|
10529
|
+
}
|
|
10530
|
+
} catch {
|
|
10531
|
+
return { path: path_, action: "failed", reason: "settings.json is not valid JSON" };
|
|
10532
|
+
}
|
|
10533
|
+
const subagents = typeof settings2.subagents === "object" && settings2.subagents !== null ? settings2.subagents : {};
|
|
10534
|
+
const existingOverrides = typeof subagents.agentOverrides === "object" && subagents.agentOverrides !== null ? subagents.agentOverrides : {};
|
|
10535
|
+
let changed = false;
|
|
10536
|
+
const overrides = {};
|
|
10537
|
+
for (const [name, existing] of Object.entries(existingOverrides)) overrides[name] = existing ?? {};
|
|
10538
|
+
const frontmatterByName = new Map(builtins.map((b) => [b.name, b.tools]));
|
|
10539
|
+
for (const name of builtins.map((b) => b.name)) {
|
|
10540
|
+
const existing = overrides[name];
|
|
10541
|
+
const baseTools = existing?.tools && Array.isArray(existing.tools) && existing.tools.length > 0 ? existing.tools : frontmatterByName.get(name);
|
|
10542
|
+
if (baseTools === void 0) continue;
|
|
10543
|
+
const wanted = desiredTools(baseTools);
|
|
10544
|
+
const current = existing?.tools;
|
|
10545
|
+
if (Array.isArray(current) && current.length > 0 && wanted.every((tool) => current.includes(tool))) {
|
|
10546
|
+
continue;
|
|
10547
|
+
}
|
|
10548
|
+
overrides[name] = { ...existing, tools: wanted };
|
|
10549
|
+
changed = true;
|
|
10550
|
+
}
|
|
10551
|
+
if (!changed) {
|
|
10552
|
+
return { path: path_, action: "skipped", reason: "already have ACP tools" };
|
|
10553
|
+
}
|
|
10554
|
+
subagents.agentOverrides = overrides;
|
|
10555
|
+
settings2.subagents = subagents;
|
|
10556
|
+
const backupPath = `${path_}.acp-bak`;
|
|
10557
|
+
try {
|
|
10558
|
+
if (!fs3.existsSync(backupPath)) fs3.copyFileSync(path_, backupPath);
|
|
10559
|
+
} catch (err) {
|
|
10560
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
10561
|
+
return { path: path_, action: "failed", reason: `backup failed: ${message}` };
|
|
10562
|
+
}
|
|
10563
|
+
const expectedMtimeMs = fs3.statSync(path_).mtimeMs;
|
|
10564
|
+
const tmpPath = `${path_}.tmp-${process.pid}`;
|
|
10565
|
+
try {
|
|
10566
|
+
fs3.writeFileSync(tmpPath, JSON.stringify(settings2, null, 2) + "\n", "utf-8");
|
|
10567
|
+
if (fs3.statSync(path_).mtimeMs !== expectedMtimeMs) {
|
|
10568
|
+
fs3.unlinkSync(tmpPath);
|
|
10569
|
+
return { path: path_, action: "failed", reason: "settings.json changed during write" };
|
|
10570
|
+
}
|
|
10571
|
+
fs3.renameSync(tmpPath, path_);
|
|
10572
|
+
const written = JSON.parse(fs3.readFileSync(path_, "utf-8"));
|
|
10573
|
+
const writtenSub = written.subagents;
|
|
10574
|
+
const writtenOverrides = writtenSub?.agentOverrides ?? {};
|
|
10575
|
+
for (const b of builtins) {
|
|
10576
|
+
const entry = writtenOverrides[b.name];
|
|
10577
|
+
const tools = entry?.tools ?? [];
|
|
10578
|
+
if (frontmatterByName.get(b.name) !== void 0 && !ACP_TOOLS2.every((t) => tools.includes(t))) {
|
|
10579
|
+
fs3.copyFileSync(backupPath, path_);
|
|
10580
|
+
return { path: path_, action: "failed", reason: "post-write verification failed" };
|
|
10581
|
+
}
|
|
10582
|
+
}
|
|
10583
|
+
return { path: path_, action: "updated" };
|
|
10584
|
+
} catch (err) {
|
|
10585
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
10586
|
+
if (fs3.existsSync(backupPath)) {
|
|
10587
|
+
try {
|
|
10588
|
+
fs3.copyFileSync(backupPath, path_);
|
|
10589
|
+
} catch {
|
|
10590
|
+
}
|
|
10591
|
+
}
|
|
10592
|
+
return { path: path_, action: "failed", reason: message };
|
|
10593
|
+
}
|
|
10594
|
+
}
|
|
10595
|
+
|
|
10392
10596
|
// src/commands.ts
|
|
10393
10597
|
function makeCommands(runtime) {
|
|
10394
10598
|
return [
|
|
@@ -10453,6 +10657,25 @@ ${text}`);
|
|
|
10453
10657
|
ctx.ui.notify(lines.join("\n"));
|
|
10454
10658
|
}
|
|
10455
10659
|
}
|
|
10660
|
+
},
|
|
10661
|
+
{
|
|
10662
|
+
name: "acp-subagents",
|
|
10663
|
+
options: {
|
|
10664
|
+
description: "Add ACP context tools (compress/decompress/search_context/acp_status) to pi-subagents' builtin agents. One-time setup \u2014 re-run after upgrading pi-subagents. Usage: /acp-subagents [installDir]",
|
|
10665
|
+
handler: async (args, ctx) => {
|
|
10666
|
+
const installDir = args.trim();
|
|
10667
|
+
const result = ensureSubagentAcpTools(void 0, installDir ? { installDir } : void 0);
|
|
10668
|
+
if (result.action === "updated") {
|
|
10669
|
+
ctx.ui.notify(`ACP tools enabled for pi-subagents agents in ${result.path}`);
|
|
10670
|
+
} else if (result.action === "skipped") {
|
|
10671
|
+
ctx.ui.notify(
|
|
10672
|
+
`Nothing to do: ${result.reason ?? ""}. Install pi-subagents (pi install npm:pi-subagents) or pass its directory: /acp-subagents <installDir>`
|
|
10673
|
+
);
|
|
10674
|
+
} else {
|
|
10675
|
+
ctx.ui.notify(`Failed to update ${result.path}: ${result.reason ?? "unknown"}`);
|
|
10676
|
+
}
|
|
10677
|
+
}
|
|
10678
|
+
}
|
|
10456
10679
|
}
|
|
10457
10680
|
];
|
|
10458
10681
|
}
|
|
@@ -10467,7 +10690,7 @@ async function statusReport(runtime, ctx) {
|
|
|
10467
10690
|
const modelId = ctx.model?.id ?? "default";
|
|
10468
10691
|
const sentTokens = estimateTokens(coreMessages, coveredIds) + systemPromptTokens;
|
|
10469
10692
|
const turn = runtime.core.processTurn({ messages: coreMessages, state, config, tokenCount: calibrateTokens(sentTokens, runtime.density.densityFor(modelId)) });
|
|
10470
|
-
const versionStr = "0.1.
|
|
10693
|
+
const versionStr = "0.1.45" ? `billion-context-pi@${"0.1.45"}` : void 0;
|
|
10471
10694
|
let text = buildStatusPanel({
|
|
10472
10695
|
version: versionStr,
|
|
10473
10696
|
tokenCount: sessionTokens,
|
|
@@ -10685,18 +10908,48 @@ function wireToolGuardrails(pi, runtime) {
|
|
|
10685
10908
|
}
|
|
10686
10909
|
|
|
10687
10910
|
// src/update.ts
|
|
10688
|
-
import { readFile, writeFile as writeFile3, mkdir as mkdir3 } from "fs/promises";
|
|
10689
|
-
import { join as
|
|
10911
|
+
import { readFile, writeFile as writeFile3, mkdir as mkdir3, access } from "fs/promises";
|
|
10912
|
+
import { join as join8, dirname as dirname5 } from "path";
|
|
10690
10913
|
import { fileURLToPath } from "url";
|
|
10691
10914
|
import { execFile } from "child_process";
|
|
10692
|
-
import { homedir as
|
|
10693
|
-
import { CONFIG_DIR_NAME as
|
|
10915
|
+
import { homedir as homedir5 } from "os";
|
|
10916
|
+
import { CONFIG_DIR_NAME as CONFIG_DIR_NAME4 } from "@earendil-works/pi-coding-agent";
|
|
10694
10917
|
var PACKAGE_NAME = "billion-context-pi";
|
|
10695
10918
|
var REGISTRY_URL = `https://registry.npmjs.org/${PACKAGE_NAME}/latest`;
|
|
10696
10919
|
var SEMVER_RE = /^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z-.]+)?$/;
|
|
10697
10920
|
var CHECK_INTERVAL_MS = 3 * 60 * 1e3;
|
|
10698
|
-
var
|
|
10921
|
+
var throttleFile = () => process.env.ACP_UPDATE_THROTTLE_FILE ?? join8(homedir5(), CONFIG_DIR_NAME4, "agent", ".billion-context-pi-update-check");
|
|
10699
10922
|
var updateInFlight = false;
|
|
10923
|
+
var runNpm = async (args, opts) => {
|
|
10924
|
+
return new Promise((resolve3) => {
|
|
10925
|
+
execFile(
|
|
10926
|
+
"npm",
|
|
10927
|
+
args,
|
|
10928
|
+
{ ...opts, shell: process.platform === "win32", maxBuffer: 4 * 1024 * 1024 },
|
|
10929
|
+
(err, stdout, stderr) => resolve3({
|
|
10930
|
+
code: err ? 1 : 0,
|
|
10931
|
+
stdout: String(stdout ?? ""),
|
|
10932
|
+
stderr: String(stderr ?? "")
|
|
10933
|
+
})
|
|
10934
|
+
);
|
|
10935
|
+
});
|
|
10936
|
+
};
|
|
10937
|
+
var runNpmImpl = runNpm;
|
|
10938
|
+
var runNode = (args, opts) => {
|
|
10939
|
+
return new Promise((resolve3) => {
|
|
10940
|
+
execFile(
|
|
10941
|
+
process.execPath,
|
|
10942
|
+
args,
|
|
10943
|
+
{ ...opts, maxBuffer: 4 * 1024 * 1024 },
|
|
10944
|
+
(err, stdout, stderr) => resolve3({
|
|
10945
|
+
code: err ? 1 : 0,
|
|
10946
|
+
stdout: String(stdout ?? ""),
|
|
10947
|
+
stderr: String(stderr ?? "")
|
|
10948
|
+
})
|
|
10949
|
+
);
|
|
10950
|
+
});
|
|
10951
|
+
};
|
|
10952
|
+
var runNodeImpl = runNode;
|
|
10700
10953
|
function parseVersion(v) {
|
|
10701
10954
|
return v.replace(/^v/, "").split(".").map((n) => parseInt(n, 10) || 0);
|
|
10702
10955
|
}
|
|
@@ -10711,7 +10964,7 @@ function isNewer(latest, current) {
|
|
|
10711
10964
|
}
|
|
10712
10965
|
async function readLastCheck() {
|
|
10713
10966
|
try {
|
|
10714
|
-
const data = await readFile(
|
|
10967
|
+
const data = await readFile(throttleFile(), "utf-8");
|
|
10715
10968
|
return parseInt(data.trim(), 10) || 0;
|
|
10716
10969
|
} catch {
|
|
10717
10970
|
return 0;
|
|
@@ -10719,14 +10972,14 @@ async function readLastCheck() {
|
|
|
10719
10972
|
}
|
|
10720
10973
|
async function writeLastCheck(timestamp) {
|
|
10721
10974
|
try {
|
|
10722
|
-
await mkdir3(dirname5(
|
|
10723
|
-
await writeFile3(
|
|
10975
|
+
await mkdir3(dirname5(throttleFile()), { recursive: true });
|
|
10976
|
+
await writeFile3(throttleFile(), String(timestamp), "utf-8");
|
|
10724
10977
|
} catch {
|
|
10725
10978
|
}
|
|
10726
10979
|
}
|
|
10727
|
-
async function readPackageJson(
|
|
10980
|
+
async function readPackageJson(path5) {
|
|
10728
10981
|
try {
|
|
10729
|
-
const data = JSON.parse(await readFile(
|
|
10982
|
+
const data = JSON.parse(await readFile(path5, "utf-8"));
|
|
10730
10983
|
return data && typeof data === "object" ? data : void 0;
|
|
10731
10984
|
} catch {
|
|
10732
10985
|
return void 0;
|
|
@@ -10744,31 +10997,118 @@ function findNpmRoot(extDir) {
|
|
|
10744
10997
|
async function findExtensionDir() {
|
|
10745
10998
|
let dir = dirname5(fileURLToPath(import.meta.url));
|
|
10746
10999
|
for (; ; ) {
|
|
10747
|
-
const pkg = await readPackageJson(
|
|
11000
|
+
const pkg = await readPackageJson(join8(dir, "package.json"));
|
|
10748
11001
|
if (pkg?.name === PACKAGE_NAME) return dir;
|
|
10749
11002
|
const parent = dirname5(dir);
|
|
10750
11003
|
if (parent === dir) return void 0;
|
|
10751
11004
|
dir = parent;
|
|
10752
11005
|
}
|
|
10753
11006
|
}
|
|
10754
|
-
|
|
10755
|
-
|
|
10756
|
-
const
|
|
10757
|
-
|
|
11007
|
+
function declaredEntries(pkg) {
|
|
11008
|
+
const dot = pkg.exports?.["."];
|
|
11009
|
+
const exportEntry = typeof dot === "string" ? dot : dot?.import;
|
|
11010
|
+
return [.../* @__PURE__ */ new Set([pkg.pi?.extensions?.[0], exportEntry, pkg.main])].filter((v) => typeof v === "string" && v.length > 0);
|
|
11011
|
+
}
|
|
11012
|
+
async function verifyInstall(npmDir, latest) {
|
|
11013
|
+
const dir = join8(npmDir, "node_modules", PACKAGE_NAME);
|
|
11014
|
+
const pkg = await readPackageJson(join8(dir, "package.json"));
|
|
11015
|
+
if (!pkg?.version) return { ok: false, reason: "package-json-missing" };
|
|
11016
|
+
if (pkg.version !== latest) return { ok: false, reason: `version-mismatch:${pkg.version}` };
|
|
11017
|
+
const entries = declaredEntries(pkg);
|
|
11018
|
+
if (entries.length === 0) return { ok: false, reason: "no-entry-declared" };
|
|
11019
|
+
for (const rel of entries) {
|
|
11020
|
+
try {
|
|
11021
|
+
await access(join8(dir, rel));
|
|
11022
|
+
} catch {
|
|
11023
|
+
return { ok: false, reason: `entry-missing:${rel}` };
|
|
11024
|
+
}
|
|
11025
|
+
}
|
|
11026
|
+
const smokeEntry = pkg.pi?.extensions?.[0] ?? entries[0];
|
|
11027
|
+
if (!smokeEntry) return { ok: false, reason: "no-entry-declared" };
|
|
11028
|
+
const entry = join8(dir, smokeEntry);
|
|
11029
|
+
const SMOKE = "const{pathToFileURL}=require('node:url');import(pathToFileURL(process.argv[1]).href).then(()=>{},(e)=>{console.error(e&&e.stack||e);process.exit(1)})";
|
|
11030
|
+
const { code, stderr } = await runNodeImpl(["-e", SMOKE, entry], { timeout: 15e3 });
|
|
11031
|
+
if (code !== 0) return { ok: false, reason: `entry-import-failed:${stderr.trim().slice(-500)}` };
|
|
11032
|
+
return { ok: true };
|
|
11033
|
+
}
|
|
11034
|
+
async function autoInstallLatest(latest, extDirOverride) {
|
|
11035
|
+
if (!SEMVER_RE.test(latest)) return "failed";
|
|
11036
|
+
const extDir = extDirOverride ?? await findExtensionDir();
|
|
11037
|
+
if (!extDir) {
|
|
11038
|
+
logWarn("update", { event: "install-skip", reason: "extension-dir-not-found" });
|
|
11039
|
+
return "failed";
|
|
11040
|
+
}
|
|
10758
11041
|
const npmDir = findNpmRoot(extDir);
|
|
10759
|
-
if (!npmDir)
|
|
11042
|
+
if (!npmDir) {
|
|
11043
|
+
logWarn("update", { event: "install-skip", reason: "not-under-node-modules", extDir });
|
|
11044
|
+
return "failed";
|
|
11045
|
+
}
|
|
10760
11046
|
try {
|
|
10761
|
-
const
|
|
10762
|
-
|
|
10763
|
-
|
|
10764
|
-
|
|
10765
|
-
|
|
10766
|
-
|
|
10767
|
-
|
|
11047
|
+
const installArgs = (v) => [
|
|
11048
|
+
"install",
|
|
11049
|
+
`${PACKAGE_NAME}@${v}`,
|
|
11050
|
+
"--silent",
|
|
11051
|
+
"--no-audit",
|
|
11052
|
+
"--no-fund",
|
|
11053
|
+
"--no-save"
|
|
11054
|
+
];
|
|
11055
|
+
const prevVersion = (await readPackageJson(join8(extDir, "package.json")))?.version ?? "0.1.45";
|
|
11056
|
+
const { code, stderr } = await runNpmImpl(installArgs(latest), { cwd: npmDir, timeout: 6e4 });
|
|
11057
|
+
if (code !== 0) {
|
|
11058
|
+
logWarn("update", {
|
|
11059
|
+
event: "auto-install-failed",
|
|
11060
|
+
latest,
|
|
11061
|
+
npmDir,
|
|
11062
|
+
stderr: stderr.trim().slice(-2e3)
|
|
11063
|
+
});
|
|
11064
|
+
return "failed";
|
|
11065
|
+
}
|
|
11066
|
+
const verify = await verifyInstall(npmDir, latest);
|
|
11067
|
+
if (!verify.ok) {
|
|
11068
|
+
const rollbackTo = SEMVER_RE.test(prevVersion) ? prevVersion : "0.1.45";
|
|
11069
|
+
logWarn("update", { event: "auto-install-verify-failed", latest, reason: verify.reason, rollbackTo });
|
|
11070
|
+
const rb = await runNpmImpl(installArgs(rollbackTo), { cwd: npmDir, timeout: 6e4 });
|
|
11071
|
+
logInfo("update", { event: "rollback", from: latest, to: rollbackTo, ok: rb.code === 0 });
|
|
11072
|
+
return "rolled-back";
|
|
11073
|
+
}
|
|
11074
|
+
return "ok";
|
|
11075
|
+
} catch (e) {
|
|
11076
|
+
logWarn("update", {
|
|
11077
|
+
event: "auto-install-error",
|
|
11078
|
+
latest,
|
|
11079
|
+
error: e instanceof Error ? e.message : String(e)
|
|
11080
|
+
});
|
|
11081
|
+
return "failed";
|
|
11082
|
+
}
|
|
11083
|
+
}
|
|
11084
|
+
async function fetchLatestVersion() {
|
|
11085
|
+
try {
|
|
11086
|
+
const { code, stdout } = await runNpmImpl(["view", PACKAGE_NAME, "version"], {
|
|
11087
|
+
timeout: 2e4
|
|
10768
11088
|
});
|
|
10769
|
-
|
|
11089
|
+
if (code === 0) {
|
|
11090
|
+
const v = stdout.trim().split("\n").map((s) => s.trim()).filter(Boolean).pop();
|
|
11091
|
+
if (v && SEMVER_RE.test(v)) return v;
|
|
11092
|
+
}
|
|
10770
11093
|
} catch {
|
|
10771
|
-
|
|
11094
|
+
}
|
|
11095
|
+
try {
|
|
11096
|
+
const res = await fetch(REGISTRY_URL, {
|
|
11097
|
+
signal: AbortSignal.timeout(1e4),
|
|
11098
|
+
headers: { Accept: "application/json" }
|
|
11099
|
+
});
|
|
11100
|
+
if (!res.ok) {
|
|
11101
|
+
logWarn("update", { event: "check-http", status: res.status });
|
|
11102
|
+
return void 0;
|
|
11103
|
+
}
|
|
11104
|
+
const data = await res.json();
|
|
11105
|
+
return data.version;
|
|
11106
|
+
} catch (e) {
|
|
11107
|
+
logWarn("update", {
|
|
11108
|
+
event: "check-fetch-error",
|
|
11109
|
+
error: e instanceof Error ? e.message : String(e)
|
|
11110
|
+
});
|
|
11111
|
+
return void 0;
|
|
10772
11112
|
}
|
|
10773
11113
|
}
|
|
10774
11114
|
async function checkForUpdate(autoUpdate, notify) {
|
|
@@ -10784,18 +11124,9 @@ async function checkForUpdate(autoUpdate, notify) {
|
|
|
10784
11124
|
if (now - lastCheck < CHECK_INTERVAL_MS) return;
|
|
10785
11125
|
await writeLastCheck(now);
|
|
10786
11126
|
const runtimeVersion = await getRuntimeVersion();
|
|
10787
|
-
const
|
|
10788
|
-
signal: AbortSignal.timeout(5e3),
|
|
10789
|
-
headers: { Accept: "application/json" }
|
|
10790
|
-
});
|
|
10791
|
-
if (!res.ok) {
|
|
10792
|
-
logWarn("update", { event: "check-http", status: res.status });
|
|
10793
|
-
return;
|
|
10794
|
-
}
|
|
10795
|
-
const data = await res.json();
|
|
10796
|
-
const latest = data.version;
|
|
11127
|
+
const latest = await fetchLatestVersion();
|
|
10797
11128
|
if (!latest) return;
|
|
10798
|
-
const current = runtimeVersion ?? "0.1.
|
|
11129
|
+
const current = runtimeVersion ?? "0.1.45";
|
|
10799
11130
|
const hasUpdate = isNewer(latest, current);
|
|
10800
11131
|
debug.event("update-check", {
|
|
10801
11132
|
current,
|
|
@@ -10804,13 +11135,17 @@ async function checkForUpdate(autoUpdate, notify) {
|
|
|
10804
11135
|
});
|
|
10805
11136
|
logInfo("update", { event: "check", current, latest, hasUpdate });
|
|
10806
11137
|
if (hasUpdate) {
|
|
10807
|
-
const
|
|
10808
|
-
if (
|
|
11138
|
+
const outcome = await autoInstallLatest(latest);
|
|
11139
|
+
if (outcome === "ok" && notify) {
|
|
10809
11140
|
notify(
|
|
10810
11141
|
`\x1B[32m\u2714 ACP auto-updated ${current} \u2192 ${latest}. Restart Pi to finish.\x1B[0m`
|
|
10811
11142
|
);
|
|
10812
11143
|
logInfo("update", { event: "auto-installed", from: current, to: latest });
|
|
10813
|
-
} else if (
|
|
11144
|
+
} else if (outcome === "rolled-back" && notify) {
|
|
11145
|
+
notify(
|
|
11146
|
+
`\x1B[33mACP ${latest} failed verification and was rolled back. Keeping ${current}. A later release will auto-update.\x1B[0m`
|
|
11147
|
+
);
|
|
11148
|
+
} else if (notify) {
|
|
10814
11149
|
notify(
|
|
10815
11150
|
`${PACKAGE_NAME} ${latest} available (you have ${current}). Run: pi update --extension npm:${PACKAGE_NAME}`
|
|
10816
11151
|
);
|
|
@@ -10825,140 +11160,10 @@ async function checkForUpdate(autoUpdate, notify) {
|
|
|
10825
11160
|
async function getRuntimeVersion() {
|
|
10826
11161
|
const extDir = await findExtensionDir();
|
|
10827
11162
|
if (!extDir) return void 0;
|
|
10828
|
-
const pkg = await readPackageJson(
|
|
11163
|
+
const pkg = await readPackageJson(join8(extDir, "package.json"));
|
|
10829
11164
|
return pkg?.version;
|
|
10830
11165
|
}
|
|
10831
11166
|
|
|
10832
|
-
// src/setup-subagent-tools.ts
|
|
10833
|
-
import { readFile as readFile2, writeFile as writeFile4, stat, copyFile, rename } from "fs/promises";
|
|
10834
|
-
import { existsSync as existsSync4 } from "fs";
|
|
10835
|
-
import { homedir as homedir5 } from "os";
|
|
10836
|
-
import { join as join8 } from "path";
|
|
10837
|
-
import { CONFIG_DIR_NAME as CONFIG_DIR_NAME4 } from "@earendil-works/pi-coding-agent";
|
|
10838
|
-
var ACP_TOOLS2 = ["compress", "decompress", "search_context", "acp_status"];
|
|
10839
|
-
var BUILTIN_DEFAULT_TOOLS = {
|
|
10840
|
-
advisor: ["read", "grep", "find", "ls", "bash", "intercom"],
|
|
10841
|
-
"context-builder": ["read", "grep", "find", "ls", "bash", "write", "web_search", "intercom"],
|
|
10842
|
-
delegate: ["read", "grep", "find", "ls", "bash", "edit", "write", "contact_supervisor"],
|
|
10843
|
-
oracle: ["read", "grep", "find", "ls", "bash", "intercom"],
|
|
10844
|
-
planner: ["read", "grep", "find", "ls", "intercom"],
|
|
10845
|
-
researcher: ["read", "write", "web_search", "fetch_content", "get_search_content", "intercom"],
|
|
10846
|
-
reviewer: ["read", "grep", "find", "ls", "bash", "edit", "write", "intercom"],
|
|
10847
|
-
scout: ["read", "grep", "find", "ls", "bash", "write", "intercom"],
|
|
10848
|
-
worker: ["read", "grep", "find", "ls", "bash", "edit", "write", "contact_supervisor"]
|
|
10849
|
-
};
|
|
10850
|
-
function resolveAgentDir() {
|
|
10851
|
-
const configured = process.env.PI_CODING_AGENT_DIR;
|
|
10852
|
-
if (configured === "~") return homedir5();
|
|
10853
|
-
if (configured?.startsWith("~/")) return join8(homedir5(), configured.slice(2));
|
|
10854
|
-
return configured || join8(homedir5(), CONFIG_DIR_NAME4, "agent");
|
|
10855
|
-
}
|
|
10856
|
-
function desiredTools(existing, name) {
|
|
10857
|
-
const base = Array.isArray(existing?.tools) && existing.tools.length > 0 ? [...existing.tools] : [...BUILTIN_DEFAULT_TOOLS[name] ?? []];
|
|
10858
|
-
const hasAll = ACP_TOOLS2.every((t) => base.includes(t));
|
|
10859
|
-
if (hasAll) return { tools: base, changed: false };
|
|
10860
|
-
for (const t of ACP_TOOLS2) if (!base.includes(t)) base.push(t);
|
|
10861
|
-
return { tools: base, changed: true };
|
|
10862
|
-
}
|
|
10863
|
-
async function ensureSubagentAcpTools(settingsPath) {
|
|
10864
|
-
const path4 = settingsPath ?? join8(resolveAgentDir(), "settings.json");
|
|
10865
|
-
let raw;
|
|
10866
|
-
let mtimeMs;
|
|
10867
|
-
try {
|
|
10868
|
-
raw = await readFile2(path4, "utf-8");
|
|
10869
|
-
mtimeMs = (await stat(path4)).mtimeMs;
|
|
10870
|
-
} catch {
|
|
10871
|
-
return { path: path4, action: "skipped", reason: "settings.json not found; will retry next session" };
|
|
10872
|
-
}
|
|
10873
|
-
let settings2;
|
|
10874
|
-
try {
|
|
10875
|
-
const parsed = JSON.parse(raw);
|
|
10876
|
-
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
10877
|
-
return { path: path4, action: "failed", reason: "settings.json root is not a JSON object" };
|
|
10878
|
-
}
|
|
10879
|
-
settings2 = parsed;
|
|
10880
|
-
} catch (e) {
|
|
10881
|
-
return { path: path4, action: "failed", reason: `settings.json is not valid JSON: ${e.message}` };
|
|
10882
|
-
}
|
|
10883
|
-
const subagents = typeof settings2.subagents === "object" && settings2.subagents !== null && !Array.isArray(settings2.subagents) ? { ...settings2.subagents } : {};
|
|
10884
|
-
const overridesRaw = typeof subagents.agentOverrides === "object" && subagents.agentOverrides !== null && !Array.isArray(subagents.agentOverrides) ? { ...subagents.agentOverrides } : {};
|
|
10885
|
-
const updatedOverrides = { ...overridesRaw };
|
|
10886
|
-
let anyChanged = false;
|
|
10887
|
-
for (const name of Object.keys(BUILTIN_DEFAULT_TOOLS)) {
|
|
10888
|
-
const existing = overridesRaw[name] ?? void 0;
|
|
10889
|
-
const { tools, changed } = desiredTools(existing, name);
|
|
10890
|
-
if (changed) {
|
|
10891
|
-
updatedOverrides[name] = { ...existing ?? {}, tools };
|
|
10892
|
-
anyChanged = true;
|
|
10893
|
-
}
|
|
10894
|
-
}
|
|
10895
|
-
if (!anyChanged) {
|
|
10896
|
-
return { path: path4, action: "skipped", reason: "all builtin agents already have ACP tools" };
|
|
10897
|
-
}
|
|
10898
|
-
const backupPath = `${path4}.acp-bak`;
|
|
10899
|
-
if (!existsSync4(backupPath)) {
|
|
10900
|
-
try {
|
|
10901
|
-
await copyFile(path4, backupPath);
|
|
10902
|
-
} catch {
|
|
10903
|
-
}
|
|
10904
|
-
}
|
|
10905
|
-
try {
|
|
10906
|
-
if ((await stat(path4)).mtimeMs !== mtimeMs) {
|
|
10907
|
-
return { path: path4, action: "skipped", reason: "settings.json changed during setup (concurrent write); will retry next session" };
|
|
10908
|
-
}
|
|
10909
|
-
} catch {
|
|
10910
|
-
return { path: path4, action: "failed", reason: "settings.json disappeared during setup" };
|
|
10911
|
-
}
|
|
10912
|
-
const next = {
|
|
10913
|
-
...settings2,
|
|
10914
|
-
subagents: { ...subagents, agentOverrides: updatedOverrides }
|
|
10915
|
-
};
|
|
10916
|
-
const tmpPath = `${path4}.tmp-${process.pid}`;
|
|
10917
|
-
try {
|
|
10918
|
-
await writeFile4(tmpPath, `${JSON.stringify(next, null, 2)}
|
|
10919
|
-
`, "utf-8");
|
|
10920
|
-
await rename(tmpPath, path4);
|
|
10921
|
-
} catch (e) {
|
|
10922
|
-
return { path: path4, action: "failed", reason: `write failed: ${e.message}` };
|
|
10923
|
-
}
|
|
10924
|
-
try {
|
|
10925
|
-
const verify = JSON.parse(await readFile2(path4, "utf-8"));
|
|
10926
|
-
const sub = verify.subagents;
|
|
10927
|
-
const v = sub?.agentOverrides;
|
|
10928
|
-
if (typeof v !== "object" || v === null) throw new Error("agentOverrides missing after write");
|
|
10929
|
-
for (const name of Object.keys(BUILTIN_DEFAULT_TOOLS)) {
|
|
10930
|
-
const tools = v[name]?.tools;
|
|
10931
|
-
if (!Array.isArray(tools) || !ACP_TOOLS2.every((t) => tools.includes(t))) {
|
|
10932
|
-
throw new Error(`${name} missing ACP tools after write`);
|
|
10933
|
-
}
|
|
10934
|
-
}
|
|
10935
|
-
} catch (e) {
|
|
10936
|
-
try {
|
|
10937
|
-
await copyFile(backupPath, path4);
|
|
10938
|
-
} catch {
|
|
10939
|
-
}
|
|
10940
|
-
return { path: path4, action: "failed", reason: `verification failed; restored backup: ${e.message}` };
|
|
10941
|
-
}
|
|
10942
|
-
return { path: path4, action: "updated" };
|
|
10943
|
-
}
|
|
10944
|
-
async function runSetupAndNotify(notify) {
|
|
10945
|
-
try {
|
|
10946
|
-
const result = await ensureSubagentAcpTools();
|
|
10947
|
-
debug.event("setup-subagent-tools", { action: result.action, reason: result.reason });
|
|
10948
|
-
if (result.action === "updated" && notify) {
|
|
10949
|
-
notify(`ACP: enabled context tools (compress/decompress/search_context/acp_status) for subagents`);
|
|
10950
|
-
}
|
|
10951
|
-
if (result.action === "failed") {
|
|
10952
|
-
logWarn("setup", { event: "subagent-tools", action: result.action, reason: result.reason });
|
|
10953
|
-
}
|
|
10954
|
-
return result;
|
|
10955
|
-
} catch (e) {
|
|
10956
|
-
debug.event("setup-subagent-tools-error", { msg: String(e) });
|
|
10957
|
-
logError("setup", { event: "subagent-tools-error", error: e instanceof Error ? e.message : String(e), stack: e instanceof Error ? e.stack ?? "" : "" });
|
|
10958
|
-
return { path: "", action: "failed", reason: String(e) };
|
|
10959
|
-
}
|
|
10960
|
-
}
|
|
10961
|
-
|
|
10962
11167
|
// src/index.ts
|
|
10963
11168
|
function createAcpExtension(adapter = {}) {
|
|
10964
11169
|
return (pi) => {
|
|
@@ -10996,7 +11201,7 @@ function wireSessionLifecycle(pi, runtime) {
|
|
|
10996
11201
|
const sid = ctx.sessionManager.getSessionId();
|
|
10997
11202
|
runtime.clearSessionTracking(sid);
|
|
10998
11203
|
const modelInfo = ctx.model;
|
|
10999
|
-
logInfo("session", { event: "start", sid, cwd: ctx.cwd, debug: runtime.adapter.debug ?? null, version: true ? "0.1.
|
|
11204
|
+
logInfo("session", { event: "start", sid, cwd: ctx.cwd, debug: runtime.adapter.debug ?? null, version: true ? "0.1.45" : null, model: modelInfo?.id ?? null, modelApi: modelInfo?.api ?? null, contextWindow: modelInfo?.contextWindow ?? null });
|
|
11000
11205
|
try {
|
|
11001
11206
|
await runtime.reloadConfig(ctx.cwd);
|
|
11002
11207
|
setDelegateDisplayUsage(resolveDelegate(runtime.adapter).displayUsage);
|
|
@@ -11014,10 +11219,10 @@ function wireSessionLifecycle(pi, runtime) {
|
|
|
11014
11219
|
pi.registerTool(makeDelegateWaitTool(pi));
|
|
11015
11220
|
pi.registerTool(makeDelegateCancelTool(pi));
|
|
11016
11221
|
}
|
|
11017
|
-
|
|
11222
|
+
const updateCheck = checkForUpdate(runtime.adapter.autoUpdate ?? true, (msg) => {
|
|
11018
11223
|
if (ctx.hasUI) ctx.ui.notify(msg);
|
|
11019
11224
|
});
|
|
11020
|
-
|
|
11225
|
+
if (!ctx.hasUI) await updateCheck;
|
|
11021
11226
|
delegateStatusWidget.setContext(ctx, runningRunsSnapshot);
|
|
11022
11227
|
});
|
|
11023
11228
|
pi.on("session_shutdown", () => {
|
|
@@ -11119,10 +11324,13 @@ function wireContextTransform(pi, runtime) {
|
|
|
11119
11324
|
const rebuilt = coreOutToAgentMessages(turn.messages, originalById);
|
|
11120
11325
|
const debugOn2 = debug.enabled;
|
|
11121
11326
|
const turnKey = lastUserMessageId(entries) ?? sid;
|
|
11327
|
+
const compressOutcomes = collectCompressOutcomes(entries, turnStartIndex(entries));
|
|
11328
|
+
const outcome = compressOutcomes.length > 0 ? runtime.noteCompressOutcomes(turnKey, compressOutcomes) : null;
|
|
11122
11329
|
if (turn.nudge?.shouldInject) {
|
|
11123
11330
|
const emergency = turn.nudge.breakdown?.emergencyOverride === 1;
|
|
11124
11331
|
turn.nudge.compressibleRanges = viableRanges(turn.nudge.compressibleRanges);
|
|
11125
|
-
const
|
|
11332
|
+
const retryCapped = runtime.compressRetryCappedFor(turnKey);
|
|
11333
|
+
const alreadyShown = retryCapped || !emergency && runtime.nudgeShownFor(turnKey);
|
|
11126
11334
|
if (!alreadyShown) {
|
|
11127
11335
|
rebuilt.push(nudgeMessage(turn.nudge, turn.state.blocks.filter((b) => b.active), runtime.prompts));
|
|
11128
11336
|
const rendered = renderNudgeText(turn.nudge, runtime.prompts);
|
|
@@ -11143,9 +11351,7 @@ ${rendered.text}${example}`);
|
|
|
11143
11351
|
debug.event("nudge-suppressed", { sid: ctx.sessionManager.getSessionId(), turnKey, reason: turn.nudge.reason });
|
|
11144
11352
|
}
|
|
11145
11353
|
}
|
|
11146
|
-
|
|
11147
|
-
if (compressOutcomes.length > 0) {
|
|
11148
|
-
const outcome = runtime.noteCompressOutcomes(turnKey, compressOutcomes);
|
|
11354
|
+
if (outcome !== null) {
|
|
11149
11355
|
const failed = outcome.retryFor !== null ? compressOutcomes.find((o) => o.toolCallId === outcome.retryFor) : void 0;
|
|
11150
11356
|
if (failed) {
|
|
11151
11357
|
rebuilt.push(compressRetryMessage(failed.text, outcome.count, MAX_COMPRESS_ATTEMPTS));
|
|
@@ -11160,9 +11366,10 @@ ${rendered.text}${example}`);
|
|
|
11160
11366
|
}
|
|
11161
11367
|
}
|
|
11162
11368
|
debug.event("context-out", { outMsgs: rebuilt.length, injected: turn.nudge?.shouldInject ?? false, emergency: turn.nudge?.breakdown?.emergencyOverride === 1 });
|
|
11163
|
-
|
|
11369
|
+
const updateCheck = checkForUpdate(runtime.adapter.autoUpdate ?? true, (msg) => {
|
|
11164
11370
|
if (ctx.hasUI) ctx.ui.notify(msg);
|
|
11165
11371
|
});
|
|
11372
|
+
if (!ctx.hasUI) await updateCheck;
|
|
11166
11373
|
return { messages: rebuilt };
|
|
11167
11374
|
} catch (e) {
|
|
11168
11375
|
logThrow("context", e, { sid, phase: "transform" });
|
|
@@ -11288,7 +11495,7 @@ function collectCompressOutcomes(entries, startIndex) {
|
|
|
11288
11495
|
const m = entry.message;
|
|
11289
11496
|
if (m.role !== "toolResult" || m.toolName !== "compress" || !m.toolCallId) continue;
|
|
11290
11497
|
const text = extractText(m.content);
|
|
11291
|
-
out.push({ toolCallId: m.toolCallId, isError: m.isError === true, success: m.isError !== true && isCompressSuccessText(text), text });
|
|
11498
|
+
out.push({ toolCallId: m.toolCallId, isError: m.isError === true, success: m.isError !== true && isCompressSuccessText(text), noop: m.isError !== true && isCompressNoopText(text), text });
|
|
11292
11499
|
}
|
|
11293
11500
|
return out;
|
|
11294
11501
|
}
|
|
@@ -11304,7 +11511,8 @@ function compressRetryMessage(errorText, attempt, maxAttempts) {
|
|
|
11304
11511
|
"- content must be an ARRAY of { startId, endId, summary } objects (topic optional) \u2014 not a JSON-encoded string.",
|
|
11305
11512
|
'- Example: compress({ content: [{ startId: "m00005", endId: "m00080", summary: "..." }] })',
|
|
11306
11513
|
'- startId/endId are the mNNNNN refs from the <acp> tags (or block ids like "b3").',
|
|
11307
|
-
attempt >= maxAttempts - 1 ? "- This is your LAST retry for this turn \u2014 if it fails again, compression pauses until the next user message." : null
|
|
11514
|
+
attempt >= maxAttempts - 1 ? "- This is your LAST retry for this turn \u2014 if it fails again, compression pauses until the next user message." : null,
|
|
11515
|
+
"- If ranges were skipped (already compressed / too small), do NOT retry the same refs \u2014 run acp_status and use its CURRENT compressible ranges."
|
|
11308
11516
|
].filter((l) => l !== null).join("\n");
|
|
11309
11517
|
return { role: "user", content: [{ type: "text", text }], timestamp: Date.now() };
|
|
11310
11518
|
}
|