billion-context-pi 0.1.27 → 0.1.29
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 +17 -2
- package/README.zh-CN.md +19 -2
- package/dist/config.d.ts +3 -1
- package/dist/delegate-events.d.ts +69 -0
- package/dist/delegate-tool.d.ts +3 -0
- package/dist/delegate-watchdog.d.ts +31 -0
- package/dist/index.js +580 -131
- package/dist/index.js.map +1 -1
- package/dist/log.d.ts +12 -2
- package/dist/runtime.d.ts +1 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -2411,9 +2411,9 @@ function projectMessage(message, id) {
|
|
|
2411
2411
|
if (calls.length === 1) {
|
|
2412
2412
|
const call = calls[0];
|
|
2413
2413
|
const argStr = stringifyArgs(call.arguments);
|
|
2414
|
-
const
|
|
2414
|
+
const text2 = argStr && textParts ? `${textParts}
|
|
2415
2415
|
${argStr}` : argStr || textParts;
|
|
2416
|
-
return [{ id, role: "assistant", contentType: "tool-call", toolName: call.name, toolCallId: call.id, text }];
|
|
2416
|
+
return [{ id, role: "assistant", contentType: "tool-call", toolName: call.name, toolCallId: call.id, text: text2 }];
|
|
2417
2417
|
}
|
|
2418
2418
|
return calls.map((call) => {
|
|
2419
2419
|
const argStr = stringifyArgs(call.arguments);
|
|
@@ -2427,7 +2427,9 @@ ${argStr}` : argStr || textParts;
|
|
|
2427
2427
|
};
|
|
2428
2428
|
});
|
|
2429
2429
|
}
|
|
2430
|
-
|
|
2430
|
+
const text = extractText(msg.content);
|
|
2431
|
+
if (!text.trim()) return [];
|
|
2432
|
+
return [{ id, role: "assistant", contentType: "text", text }];
|
|
2431
2433
|
}
|
|
2432
2434
|
const customText = extractText(msg.content) || fallbackText(msg);
|
|
2433
2435
|
return customText.length > 0 ? [{ id, role: "user", contentType: "text", text: customText }] : [];
|
|
@@ -2574,7 +2576,87 @@ function peelRefTagBlocks(blocks) {
|
|
|
2574
2576
|
|
|
2575
2577
|
// src/state.ts
|
|
2576
2578
|
import { promises as fs } from "fs";
|
|
2579
|
+
import * as path2 from "path";
|
|
2580
|
+
|
|
2581
|
+
// src/log.ts
|
|
2582
|
+
import { appendFileSync, mkdirSync, statSync, renameSync, existsSync } from "fs";
|
|
2577
2583
|
import * as path from "path";
|
|
2584
|
+
import { homedir } from "os";
|
|
2585
|
+
var MAX_BYTES = 10 * 1024 * 1024;
|
|
2586
|
+
var ENV_DEBUG = process.env.ACP_DEBUG === "1" || process.env.ACP_DEBUG === "true";
|
|
2587
|
+
function resolveLogFile() {
|
|
2588
|
+
return process.env.ACP_LOG_FILE ?? path.join(homedir(), ".pi", "acp.log");
|
|
2589
|
+
}
|
|
2590
|
+
var runtimeDebug = null;
|
|
2591
|
+
function setDebugEnabled(enabled) {
|
|
2592
|
+
runtimeDebug = enabled;
|
|
2593
|
+
}
|
|
2594
|
+
function debugOn() {
|
|
2595
|
+
return runtimeDebug ?? ENV_DEBUG;
|
|
2596
|
+
}
|
|
2597
|
+
function fmt(v) {
|
|
2598
|
+
if (typeof v === "string") return v;
|
|
2599
|
+
if (v instanceof Error) return v.stack || String(v);
|
|
2600
|
+
try {
|
|
2601
|
+
return JSON.stringify(v);
|
|
2602
|
+
} catch {
|
|
2603
|
+
return String(v);
|
|
2604
|
+
}
|
|
2605
|
+
}
|
|
2606
|
+
function ts() {
|
|
2607
|
+
return (/* @__PURE__ */ new Date()).toISOString();
|
|
2608
|
+
}
|
|
2609
|
+
function writeLine(level, scope, fields) {
|
|
2610
|
+
const file = resolveLogFile();
|
|
2611
|
+
try {
|
|
2612
|
+
if (existsSync(file) && statSync(file).size >= MAX_BYTES) {
|
|
2613
|
+
renameSync(file, file + ".old");
|
|
2614
|
+
}
|
|
2615
|
+
} catch {
|
|
2616
|
+
}
|
|
2617
|
+
const body = Object.keys(fields).map((k) => `${k}=${fmt(fields[k])}`).join(" ");
|
|
2618
|
+
const line = `${ts()} [${level}] [${scope}] ${body}
|
|
2619
|
+
`;
|
|
2620
|
+
try {
|
|
2621
|
+
mkdirSync(path.dirname(file), { recursive: true });
|
|
2622
|
+
appendFileSync(file, line);
|
|
2623
|
+
} catch {
|
|
2624
|
+
}
|
|
2625
|
+
}
|
|
2626
|
+
function closeLogStream() {
|
|
2627
|
+
}
|
|
2628
|
+
function logError(scope, fields) {
|
|
2629
|
+
writeLine("error", scope, fields);
|
|
2630
|
+
}
|
|
2631
|
+
function logWarn(scope, fields) {
|
|
2632
|
+
writeLine("warn", scope, fields);
|
|
2633
|
+
}
|
|
2634
|
+
function logInfo(scope, fields) {
|
|
2635
|
+
writeLine("info", scope, fields);
|
|
2636
|
+
}
|
|
2637
|
+
function logThrow(scope, err, extra = {}) {
|
|
2638
|
+
const fields = { ...extra };
|
|
2639
|
+
if (err instanceof Error) {
|
|
2640
|
+
fields.error = err.message;
|
|
2641
|
+
fields.stack = err.stack ?? "";
|
|
2642
|
+
} else {
|
|
2643
|
+
fields.error = String(err);
|
|
2644
|
+
}
|
|
2645
|
+
writeLine("error", scope, fields);
|
|
2646
|
+
}
|
|
2647
|
+
var debug = {
|
|
2648
|
+
get enabled() {
|
|
2649
|
+
return debugOn();
|
|
2650
|
+
},
|
|
2651
|
+
get logFile() {
|
|
2652
|
+
return resolveLogFile();
|
|
2653
|
+
},
|
|
2654
|
+
event(scope, fields) {
|
|
2655
|
+
if (debugOn()) writeLine("debug", scope, fields);
|
|
2656
|
+
}
|
|
2657
|
+
};
|
|
2658
|
+
|
|
2659
|
+
// src/state.ts
|
|
2578
2660
|
var STATE_SUFFIX = ".acp.json";
|
|
2579
2661
|
function stateFileFor(sessionFile) {
|
|
2580
2662
|
if (sessionFile) return sessionFile + STATE_SUFFIX;
|
|
@@ -2592,7 +2674,11 @@ var SessionStateStore = class {
|
|
|
2592
2674
|
const raw = await fs.readFile(file, "utf8");
|
|
2593
2675
|
const parsed = JSON.parse(raw);
|
|
2594
2676
|
if (parsed && Array.isArray(parsed.blocks)) state = mergeInitialState(parsed);
|
|
2595
|
-
} catch {
|
|
2677
|
+
} catch (e) {
|
|
2678
|
+
const code = e.code;
|
|
2679
|
+
if (code !== "ENOENT") {
|
|
2680
|
+
logWarn("state", { event: "load-failed", file, error: e instanceof Error ? e.message : String(e) });
|
|
2681
|
+
}
|
|
2596
2682
|
}
|
|
2597
2683
|
}
|
|
2598
2684
|
this.cache = state;
|
|
@@ -2604,12 +2690,17 @@ var SessionStateStore = class {
|
|
|
2604
2690
|
if (!file) return;
|
|
2605
2691
|
this.cache = state;
|
|
2606
2692
|
this.loadedKey = file;
|
|
2607
|
-
const dir =
|
|
2608
|
-
await fs.mkdir(dir, { recursive: true }).catch(() => {
|
|
2693
|
+
const dir = path2.dirname(file);
|
|
2694
|
+
await fs.mkdir(dir, { recursive: true }).catch((e) => {
|
|
2695
|
+
logError("state", { event: "save-mkdir-failed", dir, error: e instanceof Error ? e.message : String(e) });
|
|
2609
2696
|
});
|
|
2610
|
-
const tmp =
|
|
2611
|
-
|
|
2612
|
-
|
|
2697
|
+
const tmp = path2.join(dir, `.acp-tmp-${path2.basename(file)}`);
|
|
2698
|
+
try {
|
|
2699
|
+
await fs.writeFile(tmp, JSON.stringify(state), "utf8");
|
|
2700
|
+
await fs.rename(tmp, file);
|
|
2701
|
+
} catch (e) {
|
|
2702
|
+
logError("state", { event: "save-failed", file, error: e instanceof Error ? e.message : String(e) });
|
|
2703
|
+
}
|
|
2613
2704
|
}
|
|
2614
2705
|
invalidate() {
|
|
2615
2706
|
this.cache = null;
|
|
@@ -2635,6 +2726,10 @@ function readContextEntries(sm) {
|
|
|
2635
2726
|
if (typeof source.getBranch === "function") return source.getBranch();
|
|
2636
2727
|
return [];
|
|
2637
2728
|
}
|
|
2729
|
+
function isPiHost(sm) {
|
|
2730
|
+
const source = sm;
|
|
2731
|
+
return typeof source.buildContextEntries === "function";
|
|
2732
|
+
}
|
|
2638
2733
|
function createRuntime(adapter) {
|
|
2639
2734
|
const core = createCore({ countTokens: defaultCountTokens });
|
|
2640
2735
|
const store = new SessionStateStore();
|
|
@@ -7078,64 +7173,6 @@ __export(typebox_exports, {
|
|
|
7078
7173
|
With: () => With2
|
|
7079
7174
|
});
|
|
7080
7175
|
|
|
7081
|
-
// src/log.ts
|
|
7082
|
-
import { promises as fs2 } from "fs";
|
|
7083
|
-
import * as path2 from "path";
|
|
7084
|
-
import { homedir } from "os";
|
|
7085
|
-
var ENV_DEBUG = process.env.ACP_DEBUG === "1" || process.env.ACP_DEBUG === "true";
|
|
7086
|
-
var LOG_FILE = process.env.ACP_LOG_FILE ?? path2.join(homedir(), ".pi", "acp-debug.log");
|
|
7087
|
-
var runtimeDebug = null;
|
|
7088
|
-
var initialized = false;
|
|
7089
|
-
function setDebugEnabled(enabled) {
|
|
7090
|
-
runtimeDebug = enabled;
|
|
7091
|
-
}
|
|
7092
|
-
function debugOn() {
|
|
7093
|
-
return runtimeDebug ?? ENV_DEBUG;
|
|
7094
|
-
}
|
|
7095
|
-
async function write(line) {
|
|
7096
|
-
if (!debugOn()) return;
|
|
7097
|
-
if (!initialized) {
|
|
7098
|
-
initialized = true;
|
|
7099
|
-
await fs2.mkdir(path2.dirname(LOG_FILE), { recursive: true }).catch(() => {
|
|
7100
|
-
});
|
|
7101
|
-
}
|
|
7102
|
-
await fs2.appendFile(LOG_FILE, line, "utf8").catch(() => {
|
|
7103
|
-
});
|
|
7104
|
-
}
|
|
7105
|
-
var debug = {
|
|
7106
|
-
get enabled() {
|
|
7107
|
-
return debugOn();
|
|
7108
|
-
},
|
|
7109
|
-
get logFile() {
|
|
7110
|
-
return LOG_FILE;
|
|
7111
|
-
},
|
|
7112
|
-
event(scope, fields) {
|
|
7113
|
-
if (!debugOn()) return;
|
|
7114
|
-
const ts = (/* @__PURE__ */ new Date()).toISOString();
|
|
7115
|
-
const body = Object.entries(fields).map(([k, v]) => `${k}=${fmt(v)}`).join(" ");
|
|
7116
|
-
void write(`${ts} [${scope}] ${body}
|
|
7117
|
-
`);
|
|
7118
|
-
}
|
|
7119
|
-
};
|
|
7120
|
-
function fmt(v) {
|
|
7121
|
-
if (typeof v === "string") return v;
|
|
7122
|
-
if (Array.isArray(v)) {
|
|
7123
|
-
try {
|
|
7124
|
-
return JSON.stringify(v);
|
|
7125
|
-
} catch {
|
|
7126
|
-
return `[${v.length}]`;
|
|
7127
|
-
}
|
|
7128
|
-
}
|
|
7129
|
-
if (v && typeof v === "object") {
|
|
7130
|
-
try {
|
|
7131
|
-
return JSON.stringify(v);
|
|
7132
|
-
} catch {
|
|
7133
|
-
return String(v);
|
|
7134
|
-
}
|
|
7135
|
-
}
|
|
7136
|
-
return String(v);
|
|
7137
|
-
}
|
|
7138
|
-
|
|
7139
7176
|
// src/tokens.ts
|
|
7140
7177
|
function collectCoveredMessageIds(state) {
|
|
7141
7178
|
const ids = /* @__PURE__ */ new Set();
|
|
@@ -7191,7 +7228,13 @@ function makeCompressTool(runtime) {
|
|
|
7191
7228
|
],
|
|
7192
7229
|
parameters: CompressParams,
|
|
7193
7230
|
async execute(toolCallId, params, _signal, _onUpdate, ctx) {
|
|
7194
|
-
|
|
7231
|
+
let result;
|
|
7232
|
+
try {
|
|
7233
|
+
result = await handleCompress(params, runtime, ctx, toolCallId);
|
|
7234
|
+
} catch (e) {
|
|
7235
|
+
logThrow("compress", e, { sid: ctx.sessionManager.getSessionId(), ranges: params.content?.length ?? 0 });
|
|
7236
|
+
throw e;
|
|
7237
|
+
}
|
|
7195
7238
|
return { details: void 0, content: [{ type: "text", text: result }] };
|
|
7196
7239
|
}
|
|
7197
7240
|
};
|
|
@@ -7236,6 +7279,24 @@ async function handleCompress(args, runtime, ctx, toolCallId) {
|
|
|
7236
7279
|
activeAfter: applied.state.blocks.filter((b) => b.active).length,
|
|
7237
7280
|
newBlocks: newBlocks.map((b) => ({ blockId: b.blockId, tier: b.tier, summaryLen: b.summary.length, directMsgCount: b.directMessageIds.length, effectiveMsgCount: b.effectiveMessageIds.length, summary: b.summary }))
|
|
7238
7281
|
});
|
|
7282
|
+
logInfo("compress", {
|
|
7283
|
+
sid: ctx.sessionManager.getSessionId(),
|
|
7284
|
+
event: "applied",
|
|
7285
|
+
ranges: ranges.length,
|
|
7286
|
+
blocksCreated,
|
|
7287
|
+
tokensCompressed,
|
|
7288
|
+
beforeTokens,
|
|
7289
|
+
afterTokens,
|
|
7290
|
+
warnings: warnings.length,
|
|
7291
|
+
errors: errors.length,
|
|
7292
|
+
newBlockIds: newBlocks.map((b) => b.blockId)
|
|
7293
|
+
});
|
|
7294
|
+
if (errors.length > 0) {
|
|
7295
|
+
logError("compress", { sid: ctx.sessionManager.getSessionId(), event: "errors", count: errors.length, errors: errors.slice(0, 5) });
|
|
7296
|
+
}
|
|
7297
|
+
if (warnings.length > 0) {
|
|
7298
|
+
logError("compress", { sid: ctx.sessionManager.getSessionId(), event: "warnings", count: warnings.length, warnings: warnings.slice(0, 5) });
|
|
7299
|
+
}
|
|
7239
7300
|
const lines = [`\u25A3 ACP | ${formatK2(beforeTokens)} \u2192 ${formatK2(afterTokens)} tokens (~${formatK2(tokensCompressed)} reclaimed, ${blocksCreated} block${blocksCreated > 1 ? "s" : ""})`];
|
|
7240
7301
|
if (warnings.length > 0) lines.push("\u26A0\uFE0F " + warnings.join("; "));
|
|
7241
7302
|
if (errors.length > 0) lines.push("Errors: " + errors.join("; "));
|
|
@@ -7269,7 +7330,13 @@ function makeDecompressTool(runtime) {
|
|
|
7269
7330
|
],
|
|
7270
7331
|
parameters: DecompressParams,
|
|
7271
7332
|
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
7272
|
-
|
|
7333
|
+
let result;
|
|
7334
|
+
try {
|
|
7335
|
+
result = await handleDecompress(params, runtime, ctx);
|
|
7336
|
+
} catch (e) {
|
|
7337
|
+
logThrow("decompress", e, { sid: ctx.sessionManager.getSessionId(), blockId: params.blockId });
|
|
7338
|
+
throw e;
|
|
7339
|
+
}
|
|
7273
7340
|
return { details: void 0, content: [{ type: "text", text: result }] };
|
|
7274
7341
|
}
|
|
7275
7342
|
};
|
|
@@ -7318,16 +7385,21 @@ async function handleMessageRef(ref, ownerBlockId, args, ctx) {
|
|
|
7318
7385
|
const wantFile = args.toFile !== void 0 || args.inline === false || text.length >= MESSAGE_INLINE_THRESHOLD;
|
|
7319
7386
|
if (!wantFile) {
|
|
7320
7387
|
debug.event("decompress-message", { ref, ownerBlockId, mode: "inline", chars: text.length });
|
|
7388
|
+
logInfo("decompress", { sid: ctx.sessionManager.getSessionId(), event: "message", mode: "inline", ref, ownerBlockId, chars: text.length });
|
|
7321
7389
|
return `Message ${ref} (${role}, block ${ownerBlockId}, ${text.length} chars) restored inline:
|
|
7322
7390
|
|
|
7323
7391
|
${text}`;
|
|
7324
7392
|
}
|
|
7325
7393
|
const targetPath = args.toFile ? resolveToFilePath(args.toFile) : autoFilePath(`msg-${ref}`);
|
|
7326
|
-
if (typeof targetPath === "object" && "error" in targetPath)
|
|
7394
|
+
if (typeof targetPath === "object" && "error" in targetPath) {
|
|
7395
|
+
logError("decompress", { sid: ctx.sessionManager.getSessionId(), event: "message-path-rejected", ref, toFile: args.toFile });
|
|
7396
|
+
return targetPath.error;
|
|
7397
|
+
}
|
|
7327
7398
|
await mkdir(AUTO_DIR, { recursive: true }).catch(() => {
|
|
7328
7399
|
});
|
|
7329
7400
|
await writeFile(targetPath, text, "utf8");
|
|
7330
7401
|
debug.event("decompress-message", { ref, ownerBlockId, mode: "file", path: targetPath, chars: text.length });
|
|
7402
|
+
logInfo("decompress", { sid: ctx.sessionManager.getSessionId(), event: "message", mode: "file", ref, ownerBlockId, path: targetPath, chars: text.length });
|
|
7331
7403
|
return [
|
|
7332
7404
|
`Message ${ref} (${role}, block ${ownerBlockId}, ${text.length} chars) written to ${targetPath}.`,
|
|
7333
7405
|
"Block stays compressed \u2014 context unchanged. Use the read tool to access the content.",
|
|
@@ -7355,16 +7427,21 @@ async function handleDecompress(args, runtime, ctx) {
|
|
|
7355
7427
|
if (count === 0) return `Block ${blockId} has no restorable message content.`;
|
|
7356
7428
|
if (args.inline === true && !args.toFile) {
|
|
7357
7429
|
debug.event("decompress", { blockId, full, count, mode: "inline" });
|
|
7430
|
+
logInfo("decompress", { sid: ctx.sessionManager.getSessionId(), event: "block", mode: "inline", blockId, full, count });
|
|
7358
7431
|
return `Restored block ${blockId} (${count} item${count === 1 ? "" : "s"}) inline:
|
|
7359
7432
|
|
|
7360
7433
|
${text}`;
|
|
7361
7434
|
}
|
|
7362
7435
|
const targetPath = args.toFile ? resolveToFilePath(args.toFile) : autoFilePath(blockId);
|
|
7363
|
-
if (typeof targetPath === "object" && "error" in targetPath)
|
|
7436
|
+
if (typeof targetPath === "object" && "error" in targetPath) {
|
|
7437
|
+
logError("decompress", { sid: ctx.sessionManager.getSessionId(), event: "block-path-rejected", blockId, toFile: args.toFile });
|
|
7438
|
+
return targetPath.error;
|
|
7439
|
+
}
|
|
7364
7440
|
await mkdir(AUTO_DIR, { recursive: true }).catch(() => {
|
|
7365
7441
|
});
|
|
7366
7442
|
await writeFile(targetPath, text, "utf8");
|
|
7367
7443
|
debug.event("decompress", { blockId, full, count, mode: "file", path: targetPath, chars: text.length });
|
|
7444
|
+
logInfo("decompress", { sid: ctx.sessionManager.getSessionId(), event: "block", mode: "file", blockId, full, count, path: targetPath, chars: text.length });
|
|
7368
7445
|
const itemWord = count === 1 ? "item" : "items";
|
|
7369
7446
|
const lines = [
|
|
7370
7447
|
`Block ${blockId} (${count} ${itemWord}, ${text.length} chars) written to ${targetPath}.`,
|
|
@@ -7454,7 +7531,13 @@ function makeSearchTool(runtime) {
|
|
|
7454
7531
|
],
|
|
7455
7532
|
parameters: SearchParams,
|
|
7456
7533
|
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
7457
|
-
|
|
7534
|
+
let result;
|
|
7535
|
+
try {
|
|
7536
|
+
result = await handleSearch(params, runtime, ctx);
|
|
7537
|
+
} catch (e) {
|
|
7538
|
+
logThrow("search", e, { sid: ctx.sessionManager.getSessionId(), query: params.query });
|
|
7539
|
+
throw e;
|
|
7540
|
+
}
|
|
7458
7541
|
return { details: void 0, content: [{ type: "text", text: result }] };
|
|
7459
7542
|
}
|
|
7460
7543
|
};
|
|
@@ -7519,7 +7602,13 @@ function makeStatusTool(runtime) {
|
|
|
7519
7602
|
],
|
|
7520
7603
|
parameters: StatusParams,
|
|
7521
7604
|
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
7522
|
-
|
|
7605
|
+
let result;
|
|
7606
|
+
try {
|
|
7607
|
+
result = await handleStatus(params, runtime, ctx);
|
|
7608
|
+
} catch (e) {
|
|
7609
|
+
logThrow("status", e, { sid: ctx.sessionManager.getSessionId(), scope: params.scope ?? null });
|
|
7610
|
+
throw e;
|
|
7611
|
+
}
|
|
7523
7612
|
return { details: void 0, content: [{ type: "text", text: result }] };
|
|
7524
7613
|
}
|
|
7525
7614
|
};
|
|
@@ -7566,7 +7655,8 @@ ${extra.join("\n")}` : base;
|
|
|
7566
7655
|
import {
|
|
7567
7656
|
spawn
|
|
7568
7657
|
} from "child_process";
|
|
7569
|
-
import {
|
|
7658
|
+
import { createWriteStream } from "fs";
|
|
7659
|
+
import { mkdir as mkdir2, mkdtemp, writeFile as writeFile2, rm, appendFile } from "fs/promises";
|
|
7570
7660
|
import { tmpdir as tmpdir2 } from "os";
|
|
7571
7661
|
import { join as join4 } from "path";
|
|
7572
7662
|
|
|
@@ -7658,9 +7748,203 @@ var delegateStatusWidget = {
|
|
|
7658
7748
|
}
|
|
7659
7749
|
};
|
|
7660
7750
|
|
|
7751
|
+
// src/delegate-watchdog.ts
|
|
7752
|
+
function attachWatchdogs(child, hooks, opts) {
|
|
7753
|
+
let idleTimer;
|
|
7754
|
+
let eofTimer;
|
|
7755
|
+
let killGraceTimer;
|
|
7756
|
+
let timeoutTimer;
|
|
7757
|
+
const clearTimers = () => {
|
|
7758
|
+
if (idleTimer) clearTimeout(idleTimer);
|
|
7759
|
+
if (eofTimer) clearTimeout(eofTimer);
|
|
7760
|
+
if (killGraceTimer) clearTimeout(killGraceTimer);
|
|
7761
|
+
if (timeoutTimer) clearTimeout(timeoutTimer);
|
|
7762
|
+
};
|
|
7763
|
+
const killByWatchdog = (reason) => {
|
|
7764
|
+
if (hooks.isSettled()) return;
|
|
7765
|
+
hooks.onKill(reason);
|
|
7766
|
+
try {
|
|
7767
|
+
child.kill("SIGTERM");
|
|
7768
|
+
} catch {
|
|
7769
|
+
}
|
|
7770
|
+
killGraceTimer = setTimeout(() => {
|
|
7771
|
+
if (hooks.isSettled()) return;
|
|
7772
|
+
try {
|
|
7773
|
+
child.kill("SIGKILL");
|
|
7774
|
+
} catch {
|
|
7775
|
+
}
|
|
7776
|
+
}, opts.killGraceMs);
|
|
7777
|
+
killGraceTimer.unref?.();
|
|
7778
|
+
};
|
|
7779
|
+
const poke = () => {
|
|
7780
|
+
if (idleTimer) clearTimeout(idleTimer);
|
|
7781
|
+
idleTimer = setTimeout(() => killByWatchdog(`no output for ${opts.idleMs / 6e4}m`), opts.idleMs);
|
|
7782
|
+
idleTimer.unref?.();
|
|
7783
|
+
};
|
|
7784
|
+
poke();
|
|
7785
|
+
timeoutTimer = setTimeout(() => killByWatchdog(`${opts.timeoutMs / 6e4}m limit`), opts.timeoutMs);
|
|
7786
|
+
timeoutTimer.unref?.();
|
|
7787
|
+
const onStdoutEnd = () => {
|
|
7788
|
+
if (hooks.isSettled()) return;
|
|
7789
|
+
eofTimer = setTimeout(() => {
|
|
7790
|
+
if (hooks.isSettled()) return;
|
|
7791
|
+
hooks.onEofGrace();
|
|
7792
|
+
try {
|
|
7793
|
+
child.kill("SIGTERM");
|
|
7794
|
+
} catch {
|
|
7795
|
+
}
|
|
7796
|
+
}, opts.eofGraceMs);
|
|
7797
|
+
eofTimer.unref?.();
|
|
7798
|
+
};
|
|
7799
|
+
child.stdout?.once("end", onStdoutEnd);
|
|
7800
|
+
return {
|
|
7801
|
+
poke,
|
|
7802
|
+
dispose: () => {
|
|
7803
|
+
clearTimers();
|
|
7804
|
+
child.stdout?.removeListener("end", onStdoutEnd);
|
|
7805
|
+
}
|
|
7806
|
+
};
|
|
7807
|
+
}
|
|
7808
|
+
|
|
7809
|
+
// src/delegate-events.ts
|
|
7810
|
+
var ThinkingCollector = class {
|
|
7811
|
+
constructor(showThinking) {
|
|
7812
|
+
this.showThinking = showThinking;
|
|
7813
|
+
}
|
|
7814
|
+
showThinking;
|
|
7815
|
+
buf = "";
|
|
7816
|
+
push(delta) {
|
|
7817
|
+
this.buf += delta;
|
|
7818
|
+
}
|
|
7819
|
+
/** Return the segment line to write ("" when empty or disabled), resetting. */
|
|
7820
|
+
flush() {
|
|
7821
|
+
const text = this.buf.trim();
|
|
7822
|
+
this.buf = "";
|
|
7823
|
+
if (!this.showThinking || !text) return "";
|
|
7824
|
+
return `[thinking] ${text}
|
|
7825
|
+
`;
|
|
7826
|
+
}
|
|
7827
|
+
};
|
|
7828
|
+
function parseEventLine(line) {
|
|
7829
|
+
let ev;
|
|
7830
|
+
try {
|
|
7831
|
+
ev = JSON.parse(line);
|
|
7832
|
+
} catch {
|
|
7833
|
+
return null;
|
|
7834
|
+
}
|
|
7835
|
+
if (typeof ev !== "object" || ev === null) return null;
|
|
7836
|
+
const e = ev;
|
|
7837
|
+
if (e.type === "message_update") {
|
|
7838
|
+
const am = e.assistantMessageEvent;
|
|
7839
|
+
if (typeof am !== "object" || am === null) return null;
|
|
7840
|
+
const msg = am;
|
|
7841
|
+
switch (msg.type) {
|
|
7842
|
+
case "text_delta":
|
|
7843
|
+
return { kind: "reply-delta", delta: String(msg.delta ?? "") };
|
|
7844
|
+
case "text_end":
|
|
7845
|
+
return { kind: "reply-complete", content: String(msg.content ?? "") };
|
|
7846
|
+
case "thinking_delta":
|
|
7847
|
+
return { kind: "thinking-delta", delta: String(msg.delta ?? "") };
|
|
7848
|
+
case "thinking_end":
|
|
7849
|
+
return { kind: "thinking-end" };
|
|
7850
|
+
default:
|
|
7851
|
+
return null;
|
|
7852
|
+
}
|
|
7853
|
+
}
|
|
7854
|
+
if (e.type === "tool_execution_start") {
|
|
7855
|
+
return {
|
|
7856
|
+
kind: "tool-start",
|
|
7857
|
+
toolName: String(e.toolName ?? ""),
|
|
7858
|
+
argsText: formatArgs(e.args)
|
|
7859
|
+
};
|
|
7860
|
+
}
|
|
7861
|
+
if (e.type === "tool_execution_update") {
|
|
7862
|
+
return {
|
|
7863
|
+
kind: "tool-update",
|
|
7864
|
+
toolCallId: String(e.toolCallId ?? ""),
|
|
7865
|
+
text: extractContentText(e.partialResult)
|
|
7866
|
+
};
|
|
7867
|
+
}
|
|
7868
|
+
if (e.type === "tool_execution_end") {
|
|
7869
|
+
return {
|
|
7870
|
+
kind: "tool-end",
|
|
7871
|
+
toolName: String(e.toolName ?? ""),
|
|
7872
|
+
isError: Boolean(e.isError)
|
|
7873
|
+
};
|
|
7874
|
+
}
|
|
7875
|
+
if (e.type === "auto_retry_start") {
|
|
7876
|
+
return {
|
|
7877
|
+
kind: "retry-start",
|
|
7878
|
+
attempt: Number(e.attempt ?? 0),
|
|
7879
|
+
maxAttempts: Number(e.maxAttempts ?? 0),
|
|
7880
|
+
delayMs: Number(e.delayMs ?? 0),
|
|
7881
|
+
errorMessage: String(e.errorMessage ?? "")
|
|
7882
|
+
};
|
|
7883
|
+
}
|
|
7884
|
+
if (e.type === "auto_retry_end") {
|
|
7885
|
+
return {
|
|
7886
|
+
kind: "retry-end",
|
|
7887
|
+
success: Boolean(e.success),
|
|
7888
|
+
attempt: Number(e.attempt ?? 0)
|
|
7889
|
+
};
|
|
7890
|
+
}
|
|
7891
|
+
return null;
|
|
7892
|
+
}
|
|
7893
|
+
function formatArgs(args) {
|
|
7894
|
+
if (args && typeof args === "object") {
|
|
7895
|
+
const a = args;
|
|
7896
|
+
if (typeof a.command === "string") return a.command;
|
|
7897
|
+
}
|
|
7898
|
+
try {
|
|
7899
|
+
return JSON.stringify(args);
|
|
7900
|
+
} catch {
|
|
7901
|
+
return String(args);
|
|
7902
|
+
}
|
|
7903
|
+
}
|
|
7904
|
+
function extractContentText(payload) {
|
|
7905
|
+
if (!payload || typeof payload !== "object") return "";
|
|
7906
|
+
const content = payload.content;
|
|
7907
|
+
if (!Array.isArray(content)) return "";
|
|
7908
|
+
return content.map((c) => c && typeof c === "object" ? String(c.text ?? "") : "").join("");
|
|
7909
|
+
}
|
|
7910
|
+
function activityLines(ev, opts) {
|
|
7911
|
+
switch (ev.kind) {
|
|
7912
|
+
case "tool-start":
|
|
7913
|
+
return [`[tool] ${ev.toolName}${ev.argsText ? ` ${ev.argsText}` : ""}
|
|
7914
|
+
`];
|
|
7915
|
+
case "tool-update": {
|
|
7916
|
+
if (!ev.text) return [];
|
|
7917
|
+
return [ev.text.endsWith("\n") ? ev.text : `${ev.text}
|
|
7918
|
+
`];
|
|
7919
|
+
}
|
|
7920
|
+
case "tool-end":
|
|
7921
|
+
return [`[done] ${ev.toolName}${ev.isError ? " (error)" : ""}
|
|
7922
|
+
`];
|
|
7923
|
+
case "thinking-delta":
|
|
7924
|
+
return opts.showThinking ? [`[thinking] ${ev.delta}
|
|
7925
|
+
`] : [];
|
|
7926
|
+
case "retry-start":
|
|
7927
|
+
return [`[retry] attempt ${ev.attempt}/${ev.maxAttempts}, backoff ${ev.delayMs}ms${ev.errorMessage ? ` \u2014 ${ev.errorMessage}` : ""}
|
|
7928
|
+
`];
|
|
7929
|
+
case "retry-end":
|
|
7930
|
+
return [`[retry] attempt ${ev.attempt} ${ev.success ? "succeeded" : "failed"}
|
|
7931
|
+
`];
|
|
7932
|
+
default:
|
|
7933
|
+
return [];
|
|
7934
|
+
}
|
|
7935
|
+
}
|
|
7936
|
+
function newPortion(text, prev) {
|
|
7937
|
+
if (text.startsWith(prev)) return text.slice(prev.length);
|
|
7938
|
+
return text;
|
|
7939
|
+
}
|
|
7940
|
+
|
|
7661
7941
|
// src/delegate-tool.ts
|
|
7662
7942
|
var MAX_DEPTH = 2;
|
|
7663
7943
|
var SYNC_TIMEOUT_MS = 5 * 6e4;
|
|
7944
|
+
var EOF_GRACE_MS = 1e4;
|
|
7945
|
+
var IDLE_GRACE_MS = 5 * 6e4;
|
|
7946
|
+
var ASYNC_TIMEOUT_MS = 30 * 6e4;
|
|
7947
|
+
var KILL_GRACE_MS = 1e4;
|
|
7664
7948
|
var RESULT_SUMMARY_CHARS = 500;
|
|
7665
7949
|
var OUT_DIR = join4(tmpdir2(), "acp-delegate");
|
|
7666
7950
|
var ACP_TOOLS = ["compress", "decompress", "search_context", "acp_status"];
|
|
@@ -7728,6 +8012,11 @@ var DelegateParams = typebox_exports.Object({
|
|
|
7728
8012
|
typebox_exports.Boolean({
|
|
7729
8013
|
description: "If true (default), return immediately with a runId. In long-lived sessions (interactive/rpc) a short notification is injected into chat when the delegate finishes; in one-shot sessions (print/json, e.g. `pi -p` / SDK) async auto-downgrades to sync and the result is returned here. If false, always block and return the output here."
|
|
7730
8014
|
})
|
|
8015
|
+
),
|
|
8016
|
+
showThinking: typebox_exports.Optional(
|
|
8017
|
+
typebox_exports.Boolean({
|
|
8018
|
+
description: "If true, the delegate's thinking deltas are also written to the live activity file (default: false \u2014 only tool activity is shown)."
|
|
8019
|
+
})
|
|
7731
8020
|
)
|
|
7732
8021
|
});
|
|
7733
8022
|
var CancelParams = typebox_exports.Object({
|
|
@@ -7785,7 +8074,8 @@ The delegate runs in its own clean pi process \u2014 it does NOT see this conver
|
|
|
7785
8074
|
};
|
|
7786
8075
|
}
|
|
7787
8076
|
function formatRunResult(run) {
|
|
7788
|
-
const
|
|
8077
|
+
const timeoutNote = run.timedOut ? ` (timed out: ${run.timedOut})` : "";
|
|
8078
|
+
const header = run.status === "completed" ? `Delegate **${run.agent}** (runId \`${run.runId}\`) completed (exit ${run.exitCode ?? "?"})${timeoutNote}${remainingLineForWait(run.runId)}` : `Delegate **${run.agent}** (runId \`${run.runId}\`) ${run.status} (exit ${run.exitCode ?? "?"})${timeoutNote}${remainingLineForWait(run.runId)}`;
|
|
7789
8079
|
return formatPayload(header, run.result?.file ?? "", run.task, run.result?.body);
|
|
7790
8080
|
}
|
|
7791
8081
|
function remainingLineForWait(selfRunId) {
|
|
@@ -7898,6 +8188,7 @@ function makeDelegateCancelTool(_pi) {
|
|
|
7898
8188
|
run.child?.kill("SIGTERM");
|
|
7899
8189
|
} catch (err) {
|
|
7900
8190
|
debug.event("delegate-cancel-kill-error", { runId, error: String(err) });
|
|
8191
|
+
logError("delegate", { event: "cancel-kill-error", runId, error: String(err) });
|
|
7901
8192
|
}
|
|
7902
8193
|
delegateStatusWidget.poke();
|
|
7903
8194
|
return {
|
|
@@ -7924,13 +8215,15 @@ async function runDelegate(pi, args, ctx, signal) {
|
|
|
7924
8215
|
...process.env,
|
|
7925
8216
|
PI_ACP_DELEGATE_DEPTH: String(parentDepth + 1)
|
|
7926
8217
|
};
|
|
7927
|
-
const { cliArgs, tmpDir } = await buildChildArgs(args, agent.prompt, ctx);
|
|
8218
|
+
const { cliArgs, tmpDir, isAsync, useJsonStream } = await buildChildArgs(args, agent.prompt, ctx);
|
|
7928
8219
|
const requestedAsync = args.async !== false;
|
|
7929
|
-
const isAsync = requestedAsync && ctx.mode !== "print" && ctx.mode !== "json";
|
|
7930
8220
|
if (requestedAsync && !isAsync) {
|
|
7931
8221
|
debug.event("delegate-async-downgraded", { reason: `mode=${ctx.mode}` });
|
|
8222
|
+
logInfo("delegate", { event: "async-downgraded", reason: `mode=${ctx.mode}` });
|
|
7932
8223
|
}
|
|
7933
|
-
|
|
8224
|
+
const runId = `del_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 6)}`;
|
|
8225
|
+
debug.event("delegate-spawn", { agent: args.agent, runId, cwd, async: isAsync, useJsonStream, cliArgs });
|
|
8226
|
+
logInfo("delegate", { event: "spawn", agent: args.agent, runId, cwd, async: isAsync, useJsonStream, mode: ctx.mode, parentDepth });
|
|
7934
8227
|
const child = spawn(process.execPath, [process.argv[1], ...cliArgs], {
|
|
7935
8228
|
cwd,
|
|
7936
8229
|
env: childEnv,
|
|
@@ -7939,14 +8232,96 @@ async function runDelegate(pi, args, ctx, signal) {
|
|
|
7939
8232
|
});
|
|
7940
8233
|
child.stdin?.once("error", (e) => {
|
|
7941
8234
|
debug.event("delegate-stdin-error", { runId: "pre-spawn", error: String(e) });
|
|
8235
|
+
logError("delegate", { event: "stdin-error", runId, error: String(e) });
|
|
7942
8236
|
});
|
|
7943
8237
|
child.stdin?.end(args.task);
|
|
7944
|
-
let stdoutChunks = [];
|
|
7945
8238
|
let stderrText = "";
|
|
7946
|
-
const runId = `del_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 6)}`;
|
|
7947
8239
|
const startedAt = Date.now();
|
|
7948
8240
|
if (isAsync) {
|
|
7949
|
-
|
|
8241
|
+
let settled = false;
|
|
8242
|
+
const watchdog = attachWatchdogs(
|
|
8243
|
+
child,
|
|
8244
|
+
{
|
|
8245
|
+
isSettled: () => settled || run.status !== "running",
|
|
8246
|
+
onKill: (reason) => {
|
|
8247
|
+
run.timedOut = reason;
|
|
8248
|
+
debug.event("delegate-watchdog", { runId, reason });
|
|
8249
|
+
},
|
|
8250
|
+
onEofGrace: () => {
|
|
8251
|
+
run.timedOut = "output ended but process did not exit";
|
|
8252
|
+
debug.event("delegate-eof-grace", { runId, ms: EOF_GRACE_MS });
|
|
8253
|
+
}
|
|
8254
|
+
},
|
|
8255
|
+
{ eofGraceMs: EOF_GRACE_MS, idleMs: IDLE_GRACE_MS, timeoutMs: ASYNC_TIMEOUT_MS, killGraceMs: KILL_GRACE_MS }
|
|
8256
|
+
);
|
|
8257
|
+
const replyFile = join4(OUT_DIR, `${runId}.out`);
|
|
8258
|
+
const activityFile = join4(OUT_DIR, `${runId}.activity`);
|
|
8259
|
+
await mkdir2(OUT_DIR, { recursive: true });
|
|
8260
|
+
const replyStream = createWriteStream(replyFile, { flags: "a" });
|
|
8261
|
+
const activityStream = useJsonStream ? createWriteStream(activityFile, { flags: "a" }) : null;
|
|
8262
|
+
const endStream = (s) => new Promise((resolve2) => {
|
|
8263
|
+
if (!s || s.destroyed || s.closed) return resolve2();
|
|
8264
|
+
s.end(() => resolve2());
|
|
8265
|
+
});
|
|
8266
|
+
let replyText = "";
|
|
8267
|
+
let stdoutBuf = "";
|
|
8268
|
+
const lastToolText = /* @__PURE__ */ new Map();
|
|
8269
|
+
const thinking = new ThinkingCollector(args.showThinking === true);
|
|
8270
|
+
const flushThinking = () => {
|
|
8271
|
+
const line = thinking.flush();
|
|
8272
|
+
if (line) activityStream?.write(line);
|
|
8273
|
+
};
|
|
8274
|
+
const handleEventLine = (line) => {
|
|
8275
|
+
const ev = parseEventLine(line);
|
|
8276
|
+
if (!ev) return;
|
|
8277
|
+
if (ev.kind === "thinking-delta") {
|
|
8278
|
+
thinking.push(ev.delta);
|
|
8279
|
+
return;
|
|
8280
|
+
}
|
|
8281
|
+
if (ev.kind === "thinking-end") {
|
|
8282
|
+
flushThinking();
|
|
8283
|
+
return;
|
|
8284
|
+
}
|
|
8285
|
+
if (ev.kind === "reply-delta") {
|
|
8286
|
+
flushThinking();
|
|
8287
|
+
replyText += ev.delta;
|
|
8288
|
+
replyStream.write(ev.delta);
|
|
8289
|
+
return;
|
|
8290
|
+
}
|
|
8291
|
+
if (ev.kind === "reply-complete") {
|
|
8292
|
+
flushThinking();
|
|
8293
|
+
replyText = ev.content;
|
|
8294
|
+
return;
|
|
8295
|
+
}
|
|
8296
|
+
if (ev.kind === "tool-update") {
|
|
8297
|
+
flushThinking();
|
|
8298
|
+
const prev = lastToolText.get(ev.toolCallId) ?? "";
|
|
8299
|
+
const add = newPortion(ev.text, prev);
|
|
8300
|
+
lastToolText.set(ev.toolCallId, ev.text);
|
|
8301
|
+
if (add) activityStream?.write(add.endsWith("\n") ? add : `${add}
|
|
8302
|
+
`);
|
|
8303
|
+
return;
|
|
8304
|
+
}
|
|
8305
|
+
flushThinking();
|
|
8306
|
+
const lines = activityLines(ev, { showThinking: args.showThinking === true });
|
|
8307
|
+
if (lines.length) activityStream?.write(lines.join(""));
|
|
8308
|
+
};
|
|
8309
|
+
child.stdout?.on("data", (c) => {
|
|
8310
|
+
watchdog.poke();
|
|
8311
|
+
if (useJsonStream) {
|
|
8312
|
+
stdoutBuf += c.toString("utf8");
|
|
8313
|
+
let nl;
|
|
8314
|
+
while ((nl = stdoutBuf.indexOf("\n")) >= 0) {
|
|
8315
|
+
const line = stdoutBuf.slice(0, nl);
|
|
8316
|
+
stdoutBuf = stdoutBuf.slice(nl + 1);
|
|
8317
|
+
handleEventLine(line);
|
|
8318
|
+
}
|
|
8319
|
+
} else {
|
|
8320
|
+
const text = c.toString("utf8");
|
|
8321
|
+
replyText += text;
|
|
8322
|
+
replyStream.write(text);
|
|
8323
|
+
}
|
|
8324
|
+
});
|
|
7950
8325
|
child.stderr?.on("data", (c) => {
|
|
7951
8326
|
stderrText += c.toString("utf8");
|
|
7952
8327
|
});
|
|
@@ -7961,52 +8336,79 @@ async function runDelegate(pi, args, ctx, signal) {
|
|
|
7961
8336
|
};
|
|
7962
8337
|
runs.set(runId, run);
|
|
7963
8338
|
delegateStatusWidget.poke();
|
|
7964
|
-
|
|
7965
|
-
void
|
|
7966
|
-
|
|
7967
|
-
|
|
7968
|
-
|
|
7969
|
-
|
|
7970
|
-
|
|
7971
|
-
|
|
7972
|
-
|
|
7973
|
-
|
|
7974
|
-
|
|
7975
|
-
|
|
7976
|
-
|
|
7977
|
-
|
|
7978
|
-
|
|
7979
|
-
run.finishedAt = Date.now();
|
|
7980
|
-
if (run.waiter) {
|
|
7981
|
-
debug.event("delegate-done", { runId, code, status: run.status, injected: false, via: "wait", outLen: output.length, file: file2 });
|
|
7982
|
-
run.waiter();
|
|
8339
|
+
const finalize = (code) => {
|
|
8340
|
+
void (async () => {
|
|
8341
|
+
if (settled) return;
|
|
8342
|
+
settled = true;
|
|
8343
|
+
watchdog.dispose();
|
|
8344
|
+
void cleanupTmp(tmpDir);
|
|
8345
|
+
await Promise.all([endStream(replyStream), endStream(activityStream)]);
|
|
8346
|
+
run.exitCode = code;
|
|
8347
|
+
const output = replyText.trim();
|
|
8348
|
+
const body2 = code === 0 ? output || "(no output)" : stderrText.trim() || output || "(no output)";
|
|
8349
|
+
if (run.status === "cancelled") {
|
|
8350
|
+
await Promise.all([rm(replyFile, { force: true }), rm(activityFile, { force: true })]);
|
|
8351
|
+
run.finishedAt = Date.now();
|
|
8352
|
+
debug.event("delegate-done", { runId, code, status: run.status, injected: false, outLen: output.length });
|
|
8353
|
+
run.waiter?.();
|
|
7983
8354
|
delegateStatusWidget.poke();
|
|
7984
8355
|
return;
|
|
7985
8356
|
}
|
|
7986
|
-
|
|
7987
|
-
|
|
8357
|
+
try {
|
|
8358
|
+
const file2 = replyFile;
|
|
8359
|
+
if (output === "") {
|
|
8360
|
+
const fallback = stderrText.trim();
|
|
8361
|
+
await appendFile(file2, fallback ? `${fallback}
|
|
8362
|
+
` : "(no output)\n");
|
|
8363
|
+
}
|
|
8364
|
+
const effectiveCode = code ?? (output || stderrText ? 0 : null);
|
|
8365
|
+
run.result = { code, file: file2, body: body2 };
|
|
8366
|
+
run.status = effectiveCode === 0 ? "completed" : "failed";
|
|
8367
|
+
run.finishedAt = Date.now();
|
|
8368
|
+
if (run.waiter) {
|
|
8369
|
+
debug.event("delegate-done", { runId, code, status: run.status, injected: false, via: "wait", outLen: output.length, file: file2 });
|
|
8370
|
+
logInfo("delegate", { event: "done", runId, agent: args.agent, code, status: run.status, injected: false, via: "wait", outLen: output.length, file: file2 });
|
|
8371
|
+
run.waiter();
|
|
8372
|
+
delegateStatusWidget.poke();
|
|
8373
|
+
return;
|
|
8374
|
+
}
|
|
8375
|
+
if (run.consumed) {
|
|
8376
|
+
debug.event("delegate-done", { runId, code, status: run.status, injected: false, via: "consumed", outLen: output.length, file: file2 });
|
|
8377
|
+
logInfo("delegate", { event: "done", runId, agent: args.agent, code, status: run.status, injected: false, via: "consumed", outLen: output.length, file: file2 });
|
|
8378
|
+
delegateStatusWidget.poke();
|
|
8379
|
+
return;
|
|
8380
|
+
}
|
|
8381
|
+
const injected = injectResult(pi, args.agent, runId, args.task, code, file2, run.timedOut);
|
|
8382
|
+
run.injected = injected;
|
|
8383
|
+
debug.event("delegate-done", { runId, code, status: run.status, injected, outLen: output.length, file: file2 });
|
|
8384
|
+
logInfo("delegate", { event: "done", runId, agent: args.agent, code, status: run.status, injected, outLen: output.length, file: file2 });
|
|
8385
|
+
delegateStatusWidget.poke();
|
|
8386
|
+
} catch (err) {
|
|
8387
|
+
run.status = "failed";
|
|
8388
|
+
run.finishedAt = Date.now();
|
|
8389
|
+
debug.event("delegate-done-error", { runId, error: String(err) });
|
|
8390
|
+
logError("delegate", { event: "done-error", runId, agent: args.agent, error: String(err) });
|
|
8391
|
+
run.waiter?.();
|
|
7988
8392
|
delegateStatusWidget.poke();
|
|
7989
|
-
return;
|
|
7990
8393
|
}
|
|
7991
|
-
|
|
7992
|
-
|
|
7993
|
-
|
|
7994
|
-
delegateStatusWidget.poke();
|
|
7995
|
-
}).catch((err) => {
|
|
7996
|
-
run.status = "failed";
|
|
7997
|
-
run.finishedAt = Date.now();
|
|
7998
|
-
debug.event("delegate-done-error", { runId, error: String(err) });
|
|
7999
|
-
run.waiter?.();
|
|
8000
|
-
delegateStatusWidget.poke();
|
|
8001
|
-
});
|
|
8002
|
-
});
|
|
8394
|
+
})();
|
|
8395
|
+
};
|
|
8396
|
+
child.on("close", (code) => finalize(code));
|
|
8003
8397
|
child.on("error", (err) => {
|
|
8398
|
+
if (settled) return;
|
|
8399
|
+
settled = true;
|
|
8400
|
+
watchdog.dispose();
|
|
8004
8401
|
void cleanupTmp(tmpDir);
|
|
8402
|
+
void replyStream.destroy();
|
|
8403
|
+
void activityStream?.destroy();
|
|
8404
|
+
void rm(replyFile, { force: true });
|
|
8405
|
+
void rm(activityFile, { force: true });
|
|
8005
8406
|
if (run.status === "running" || run.status === "cancelled") {
|
|
8006
8407
|
run.status = run.status === "cancelled" ? "cancelled" : "failed";
|
|
8007
8408
|
run.finishedAt = Date.now();
|
|
8008
8409
|
run.result = { code: null, file: "", body: `spawn error: ${String(err)}` };
|
|
8009
8410
|
debug.event("delegate-spawn-error", { runId, error: String(err) });
|
|
8411
|
+
logError("delegate", { event: "spawn-error", runId, agent: args.agent, error: String(err) });
|
|
8010
8412
|
run.waiter?.();
|
|
8011
8413
|
delegateStatusWidget.poke();
|
|
8012
8414
|
}
|
|
@@ -8016,6 +8418,8 @@ async function runDelegate(pi, args, ctx, signal) {
|
|
|
8016
8418
|
`Delegated to **${args.agent}** (runId \`${runId}\`).`,
|
|
8017
8419
|
`Task: ${truncate2(args.task, 160)}`,
|
|
8018
8420
|
`Running in the background at \`${cwd}\`.`,
|
|
8421
|
+
useJsonStream ? `Live activity is streaming to \`${activityFile}\` \u2014 read it anytime to watch the delegate work (tool calls and their output${args.showThinking ? ", plus thinking" : ""}).` : `The reply is streaming to \`${replyFile}\` \u2014 read it anytime to see partial output (this host has no json event mode, so tool activity is not visible).`,
|
|
8422
|
+
`A watchdog force-finishes a hung run: no output for ${IDLE_GRACE_MS / 6e4}m, 10s after output ends, or a ${ASYNC_TIMEOUT_MS / 6e4}m hard limit \u2014 the result reflects whatever was produced.`,
|
|
8019
8423
|
``,
|
|
8020
8424
|
`Call acp_delegate_wait({ runId: "${runId}" }) to block for the result (default 10s timeout). If the wait times out, or you skip it, a completion notification (with the result file path) is still injected here automatically when the delegate finishes \u2014 so you may also just continue other work now and let the result find you.`
|
|
8021
8425
|
].join("\n");
|
|
@@ -8034,7 +8438,9 @@ async function buildChildArgs(args, rolePrompt, ctx) {
|
|
|
8034
8438
|
---
|
|
8035
8439
|
|
|
8036
8440
|
Complete the task below.`, "utf8");
|
|
8037
|
-
const
|
|
8441
|
+
const isAsync = args.async !== false && ctx.mode !== "print" && ctx.mode !== "json";
|
|
8442
|
+
const useJsonStream = isAsync && isPiHost(ctx.sessionManager);
|
|
8443
|
+
const cliArgs = useJsonStream ? ["--mode", "json", "--no-session", "--append-system-prompt", promptFile] : ["-p", "--no-session", "--append-system-prompt", promptFile];
|
|
8038
8444
|
const agentDef = AGENTS[args.agent];
|
|
8039
8445
|
if (agentDef?.restricted) {
|
|
8040
8446
|
const merged = [.../* @__PURE__ */ new Set([...agentDef.tools.split(",").map((s) => s.trim()), ...ACP_TOOLS])];
|
|
@@ -8047,7 +8453,7 @@ Complete the task below.`, "utf8");
|
|
|
8047
8453
|
} else if (ctx.model) {
|
|
8048
8454
|
cliArgs.push("--provider", ctx.model.provider, "--model", ctx.model.id);
|
|
8049
8455
|
}
|
|
8050
|
-
return { cliArgs, tmpDir };
|
|
8456
|
+
return { cliArgs, tmpDir, isAsync, useJsonStream };
|
|
8051
8457
|
}
|
|
8052
8458
|
function waitForChild(child, signal) {
|
|
8053
8459
|
return new Promise((resolve2) => {
|
|
@@ -8093,22 +8499,25 @@ function formatSyncResult(agent, runId, task, r, file) {
|
|
|
8093
8499
|
const body = r.timedOut ? "(timed out)" : r.stderr.trim() || "(no stderr)";
|
|
8094
8500
|
return formatPayload(header, file, task, body);
|
|
8095
8501
|
}
|
|
8096
|
-
function injectResult(pi, agent, runId, task, code, file) {
|
|
8502
|
+
function injectResult(pi, agent, runId, task, code, file, timedOut) {
|
|
8097
8503
|
const send = pi.sendUserMessage;
|
|
8098
8504
|
if (typeof send !== "function") {
|
|
8099
8505
|
debug.event("delegate-inject-skipped", { runId, reason: "sendUserMessage unavailable" });
|
|
8506
|
+
logWarn("delegate", { event: "inject-skipped", runId, reason: "sendUserMessage unavailable" });
|
|
8100
8507
|
return false;
|
|
8101
8508
|
}
|
|
8102
8509
|
const status = code === 0 ? "completed" : "failed";
|
|
8103
8510
|
const remaining = Array.from(runs.values()).filter((r) => r.status === "running").length;
|
|
8104
8511
|
const remainingLine = remaining > 0 ? ` ${remaining} delegate${remaining === 1 ? " is" : "s are"} still running; keep doing other work and their notifications will arrive as they finish.` : " No delegates are currently running.";
|
|
8105
|
-
const
|
|
8512
|
+
const timeoutNote = timedOut ? ` (timed out: ${timedOut})` : "";
|
|
8513
|
+
const header = `[acp_delegate ${status}] **${agent}** (runId \`${runId}\`, exit ${code ?? "?"})${timeoutNote}${remainingLine} This is an automated system notification, NOT a user message. Read the result file if you need the details, then continue your original task; do not treat this as a new user request.`;
|
|
8106
8514
|
const text = formatPayload(header, file, task);
|
|
8107
8515
|
try {
|
|
8108
8516
|
send.call(pi, text, { deliverAs: "followUp" });
|
|
8109
8517
|
return true;
|
|
8110
8518
|
} catch (err) {
|
|
8111
8519
|
debug.event("delegate-inject-error", { runId, error: String(err) });
|
|
8520
|
+
logError("delegate", { event: "inject-error", runId, agent, error: String(err) });
|
|
8112
8521
|
return false;
|
|
8113
8522
|
}
|
|
8114
8523
|
}
|
|
@@ -8136,6 +8545,7 @@ async function persistResult(runId, body) {
|
|
|
8136
8545
|
return file;
|
|
8137
8546
|
} catch (err) {
|
|
8138
8547
|
debug.event("delegate-persist-error", { runId, file, error: String(err) });
|
|
8548
|
+
logError("delegate", { event: "persist-error", runId, file, error: String(err) });
|
|
8139
8549
|
return "";
|
|
8140
8550
|
}
|
|
8141
8551
|
}
|
|
@@ -8246,7 +8656,7 @@ async function statusReport(runtime, ctx) {
|
|
|
8246
8656
|
const activeBlocksList = state.blocks.filter((b) => b.active);
|
|
8247
8657
|
const totalBlocksList = state.blocks;
|
|
8248
8658
|
const lines = [];
|
|
8249
|
-
const versionStr = "0.1.
|
|
8659
|
+
const versionStr = "0.1.29" ? `billion-context-pi@${"0.1.29"}` : "";
|
|
8250
8660
|
lines.push("\u256D\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256E");
|
|
8251
8661
|
lines.push("\u2502 ACP Context Analysis \u2502");
|
|
8252
8662
|
lines.push("\u2570\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256F");
|
|
@@ -8490,11 +8900,13 @@ function wireToolGuardrails(pi, runtime) {
|
|
|
8490
8900
|
if (next) {
|
|
8491
8901
|
modified = next;
|
|
8492
8902
|
debug.event("guardrail-output-cap", { max, hadPath: !!fullPath });
|
|
8903
|
+
logWarn("guardrail", { event: "output-cap", max, hadPath: !!fullPath });
|
|
8493
8904
|
}
|
|
8494
8905
|
}
|
|
8495
8906
|
if (timeoutSecs !== void 0) {
|
|
8496
8907
|
modified = appendTimeoutNotice(modified ?? event.content, timeoutSecs);
|
|
8497
8908
|
debug.event("guardrail-bash-timeout-notice", { secs: timeoutSecs });
|
|
8909
|
+
logInfo("guardrail", { event: "bash-timeout-notice", secs: timeoutSecs });
|
|
8498
8910
|
}
|
|
8499
8911
|
if (modified) return { content: modified };
|
|
8500
8912
|
});
|
|
@@ -8602,29 +9014,36 @@ async function checkForUpdate(autoUpdate, notify) {
|
|
|
8602
9014
|
signal: AbortSignal.timeout(5e3),
|
|
8603
9015
|
headers: { Accept: "application/json" }
|
|
8604
9016
|
});
|
|
8605
|
-
if (!res.ok)
|
|
9017
|
+
if (!res.ok) {
|
|
9018
|
+
logWarn("update", { event: "check-http", status: res.status });
|
|
9019
|
+
return;
|
|
9020
|
+
}
|
|
8606
9021
|
const data = await res.json();
|
|
8607
9022
|
const latest = data.version;
|
|
8608
9023
|
if (!latest) return;
|
|
8609
|
-
const current = runtimeVersion ?? "0.1.
|
|
9024
|
+
const current = runtimeVersion ?? "0.1.29";
|
|
9025
|
+
const hasUpdate = isNewer(latest, current);
|
|
8610
9026
|
debug.event("update-check", {
|
|
8611
9027
|
current,
|
|
8612
9028
|
latest,
|
|
8613
|
-
hasUpdate
|
|
9029
|
+
hasUpdate
|
|
8614
9030
|
});
|
|
8615
|
-
|
|
9031
|
+
logInfo("update", { event: "check", current, latest, hasUpdate });
|
|
9032
|
+
if (hasUpdate) {
|
|
8616
9033
|
const installed = await autoInstallLatest(latest);
|
|
8617
9034
|
if (installed && notify) {
|
|
8618
9035
|
notify(
|
|
8619
9036
|
`\x1B[32m\u2714 ACP auto-updated ${current} \u2192 ${latest}. Restart Pi to finish.\x1B[0m`
|
|
8620
9037
|
);
|
|
9038
|
+
logInfo("update", { event: "auto-installed", from: current, to: latest });
|
|
8621
9039
|
} else if (!installed && notify) {
|
|
8622
9040
|
notify(
|
|
8623
9041
|
`${PACKAGE_NAME} ${latest} available (you have ${current}). Run: pi update --extension npm:${PACKAGE_NAME}`
|
|
8624
9042
|
);
|
|
8625
9043
|
}
|
|
8626
9044
|
}
|
|
8627
|
-
} catch {
|
|
9045
|
+
} catch (e) {
|
|
9046
|
+
logWarn("update", { event: "check-error", error: e instanceof Error ? e.message : String(e) });
|
|
8628
9047
|
} finally {
|
|
8629
9048
|
updateInFlight = false;
|
|
8630
9049
|
}
|
|
@@ -8638,7 +9057,7 @@ async function getRuntimeVersion() {
|
|
|
8638
9057
|
|
|
8639
9058
|
// src/setup-subagent-tools.ts
|
|
8640
9059
|
import { readFile as readFile2, writeFile as writeFile4, stat, copyFile, rename } from "fs/promises";
|
|
8641
|
-
import { existsSync } from "fs";
|
|
9060
|
+
import { existsSync as existsSync2 } from "fs";
|
|
8642
9061
|
import { homedir as homedir4 } from "os";
|
|
8643
9062
|
import { join as join6 } from "path";
|
|
8644
9063
|
var ACP_TOOLS2 = ["compress", "decompress", "search_context", "acp_status"];
|
|
@@ -8702,7 +9121,7 @@ async function ensureSubagentAcpTools(settingsPath) {
|
|
|
8702
9121
|
return { path: path4, action: "skipped", reason: "all builtin agents already have ACP tools" };
|
|
8703
9122
|
}
|
|
8704
9123
|
const backupPath = `${path4}.acp-bak`;
|
|
8705
|
-
if (!
|
|
9124
|
+
if (!existsSync2(backupPath)) {
|
|
8706
9125
|
try {
|
|
8707
9126
|
await copyFile(path4, backupPath);
|
|
8708
9127
|
} catch {
|
|
@@ -8754,15 +9173,19 @@ async function runSetupAndNotify(notify) {
|
|
|
8754
9173
|
if (result.action === "updated" && notify) {
|
|
8755
9174
|
notify(`ACP: enabled context tools (compress/decompress/search_context/acp_status) for subagents`);
|
|
8756
9175
|
}
|
|
9176
|
+
if (result.action === "failed") {
|
|
9177
|
+
logWarn("setup", { event: "subagent-tools", action: result.action, reason: result.reason });
|
|
9178
|
+
}
|
|
8757
9179
|
return result;
|
|
8758
9180
|
} catch (e) {
|
|
8759
9181
|
debug.event("setup-subagent-tools-error", { msg: String(e) });
|
|
9182
|
+
logError("setup", { event: "subagent-tools-error", error: e instanceof Error ? e.message : String(e), stack: e instanceof Error ? e.stack ?? "" : "" });
|
|
8760
9183
|
return { path: "", action: "failed", reason: String(e) };
|
|
8761
9184
|
}
|
|
8762
9185
|
}
|
|
8763
9186
|
|
|
8764
9187
|
// src/user-config.ts
|
|
8765
|
-
import { promises as
|
|
9188
|
+
import { promises as fs2 } from "fs";
|
|
8766
9189
|
import * as path3 from "path";
|
|
8767
9190
|
import { homedir as homedir5 } from "os";
|
|
8768
9191
|
import { CONFIG_DIR_NAME } from "@earendil-works/pi-coding-agent";
|
|
@@ -8772,13 +9195,17 @@ async function loadUserConfig(cwd) {
|
|
|
8772
9195
|
for (const base of [join8(home, CONFIG_DIR_NAME), join8(cwd, CONFIG_DIR_NAME)]) {
|
|
8773
9196
|
const file = join8(base, "acp.json");
|
|
8774
9197
|
try {
|
|
8775
|
-
const raw = await
|
|
9198
|
+
const raw = await fs2.readFile(file, "utf8");
|
|
8776
9199
|
const parsed = JSON.parse(raw);
|
|
8777
9200
|
if (parsed && typeof parsed === "object") {
|
|
8778
9201
|
Object.assign(merged, pickKnown(parsed));
|
|
8779
9202
|
debug.event("config-loaded", { file });
|
|
8780
9203
|
}
|
|
8781
|
-
} catch {
|
|
9204
|
+
} catch (e) {
|
|
9205
|
+
const code = e.code;
|
|
9206
|
+
if (code !== "ENOENT") {
|
|
9207
|
+
logWarn("config", { event: "load-failed", file, error: e instanceof Error ? e.message : String(e) });
|
|
9208
|
+
}
|
|
8782
9209
|
}
|
|
8783
9210
|
}
|
|
8784
9211
|
return merged;
|
|
@@ -8832,11 +9259,14 @@ function wireSessionLifecycle(pi, runtime) {
|
|
|
8832
9259
|
pi.on("session_start", async (_event, ctx) => {
|
|
8833
9260
|
runtime.store.invalidate();
|
|
8834
9261
|
runtime.clearNudgeTracking();
|
|
9262
|
+
const sid = ctx.sessionManager.getSessionId();
|
|
9263
|
+
logInfo("session", { event: "start", sid, cwd: ctx.cwd, debug: runtime.adapter.debug ?? null, version: true ? "0.1.29" : null });
|
|
8835
9264
|
try {
|
|
8836
9265
|
const user = await loadUserConfig(ctx.cwd);
|
|
8837
9266
|
runtime.setAdapter(applyUserConfig(runtime.adapter, user));
|
|
8838
9267
|
if (runtime.adapter.debug !== void 0) setDebugEnabled(runtime.adapter.debug);
|
|
8839
|
-
} catch {
|
|
9268
|
+
} catch (e) {
|
|
9269
|
+
logThrow("config", e, { sid, phase: "session_start" });
|
|
8840
9270
|
}
|
|
8841
9271
|
if (runtime.adapter.delegate !== false) {
|
|
8842
9272
|
pi.registerTool(makeDelegateTool(pi));
|
|
@@ -8851,6 +9281,7 @@ function wireSessionLifecycle(pi, runtime) {
|
|
|
8851
9281
|
});
|
|
8852
9282
|
pi.on("session_shutdown", () => {
|
|
8853
9283
|
delegateStatusWidget.dispose();
|
|
9284
|
+
closeLogStream();
|
|
8854
9285
|
});
|
|
8855
9286
|
}
|
|
8856
9287
|
function wireContextTransform(pi, runtime) {
|
|
@@ -8879,6 +9310,18 @@ function wireContextTransform(pi, runtime) {
|
|
|
8879
9310
|
});
|
|
8880
9311
|
const turn = runtime.core.processTurn({ messages: coreMessages, state, config, tokenCount });
|
|
8881
9312
|
await runtime.save(turn.state, ctx);
|
|
9313
|
+
logInfo("turn", {
|
|
9314
|
+
sid,
|
|
9315
|
+
inMsgs: coreMessages.length,
|
|
9316
|
+
outMsgs: turn.messages.length,
|
|
9317
|
+
tokens: tokenCount,
|
|
9318
|
+
pct: realUsage?.percent ?? (config.modelContextLimit > 0 ? Math.round(tokenCount / config.modelContextLimit * 100) : null),
|
|
9319
|
+
limit: config.modelContextLimit,
|
|
9320
|
+
nudge: turn.nudge?.shouldInject ? turn.nudge.breakdown?.emergencyOverride === 1 ? "emergency" : "active" : "idle",
|
|
9321
|
+
nudgeReason: turn.nudge?.reason ?? null,
|
|
9322
|
+
blocks: turn.state.blocks.length,
|
|
9323
|
+
activeBlocks: turn.state.blocks.filter((b) => b.active).length
|
|
9324
|
+
});
|
|
8882
9325
|
debug.event("processTurn", {
|
|
8883
9326
|
outMsgs: turn.messages.length,
|
|
8884
9327
|
summaryMsgs: turn.messages.filter((m) => m.id.startsWith("acp_summary")).length,
|
|
@@ -8908,6 +9351,9 @@ function wireContextTransform(pi, runtime) {
|
|
|
8908
9351
|
const example = top ? `
|
|
8909
9352
|
|
|
8910
9353
|
Example: compress({ content: [{ startId: "${top.startRef}", endId: "${top.endRef}", summary: "..." }] })` : "";
|
|
9354
|
+
if (emergency) {
|
|
9355
|
+
logWarn("nudge", { sid: ctx.sessionManager.getSessionId(), event: "emergency-inject", pct: Math.round(turn.nudge.contextUsage * 100), voice: rendered.voice, compressible: turn.nudge.compressibleRanges.length });
|
|
9356
|
+
}
|
|
8911
9357
|
if (debugOn2 && ctx.hasUI) {
|
|
8912
9358
|
ctx.ui.notify(`[ACP nudge \u2192 context]${emergency ? " [EMERGENCY]" : ""}
|
|
8913
9359
|
${rendered.text}${example}`);
|
|
@@ -8923,6 +9369,9 @@ ${rendered.text}${example}`);
|
|
|
8923
9369
|
if (ctx.hasUI) ctx.ui.notify(msg);
|
|
8924
9370
|
});
|
|
8925
9371
|
return { messages: rebuilt };
|
|
9372
|
+
} catch (e) {
|
|
9373
|
+
logThrow("context", e, { sid, phase: "transform" });
|
|
9374
|
+
throw e;
|
|
8926
9375
|
} finally {
|
|
8927
9376
|
release();
|
|
8928
9377
|
}
|