billion-context-pi 0.1.26 → 0.1.28
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/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 +379 -50
- package/dist/index.js.map +1 -1
- package/dist/runtime.d.ts +2 -0
- package/dist/tool-guardrails.d.ts +4 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -2429,9 +2429,17 @@ ${argStr}` : argStr || textParts;
|
|
|
2429
2429
|
}
|
|
2430
2430
|
return [{ id, role: "assistant", contentType: "text", text: extractText(msg.content) }];
|
|
2431
2431
|
}
|
|
2432
|
-
const customText = extractText(msg.content);
|
|
2432
|
+
const customText = extractText(msg.content) || fallbackText(msg);
|
|
2433
2433
|
return customText.length > 0 ? [{ id, role: "user", contentType: "text", text: customText }] : [];
|
|
2434
2434
|
}
|
|
2435
|
+
function fallbackText(msg) {
|
|
2436
|
+
const parts = [];
|
|
2437
|
+
if (msg.command) parts.push(`$ ${msg.command}`);
|
|
2438
|
+
const out = extractText(msg.output);
|
|
2439
|
+
if (out) parts.push(out);
|
|
2440
|
+
if (msg.summary) parts.push(msg.summary);
|
|
2441
|
+
return parts.join("\n").trim();
|
|
2442
|
+
}
|
|
2435
2443
|
function stringifyArgs(args) {
|
|
2436
2444
|
if (!args) return "";
|
|
2437
2445
|
if (typeof args === "string") return args;
|
|
@@ -2621,6 +2629,16 @@ function mergeInitialState(parsed) {
|
|
|
2621
2629
|
}
|
|
2622
2630
|
|
|
2623
2631
|
// src/runtime.ts
|
|
2632
|
+
function readContextEntries(sm) {
|
|
2633
|
+
const source = sm;
|
|
2634
|
+
if (typeof source.buildContextEntries === "function") return source.buildContextEntries();
|
|
2635
|
+
if (typeof source.getBranch === "function") return source.getBranch();
|
|
2636
|
+
return [];
|
|
2637
|
+
}
|
|
2638
|
+
function isPiHost(sm) {
|
|
2639
|
+
const source = sm;
|
|
2640
|
+
return typeof source.buildContextEntries === "function";
|
|
2641
|
+
}
|
|
2624
2642
|
function createRuntime(adapter) {
|
|
2625
2643
|
const core = createCore({ countTokens: defaultCountTokens });
|
|
2626
2644
|
const store = new SessionStateStore();
|
|
@@ -2652,7 +2670,7 @@ function createRuntime(adapter) {
|
|
|
2652
2670
|
async function stateFor(ctx) {
|
|
2653
2671
|
const sm = ctx.sessionManager;
|
|
2654
2672
|
const state = await store.load(sm.getSessionFile() ?? void 0, sm.getSessionId());
|
|
2655
|
-
const entries = sm
|
|
2673
|
+
const entries = readContextEntries(sm);
|
|
2656
2674
|
return { state, coreMessages: entriesToCoreMessages(entries), entries };
|
|
2657
2675
|
}
|
|
2658
2676
|
async function save(state, ctx) {
|
|
@@ -7552,7 +7570,8 @@ ${extra.join("\n")}` : base;
|
|
|
7552
7570
|
import {
|
|
7553
7571
|
spawn
|
|
7554
7572
|
} from "child_process";
|
|
7555
|
-
import {
|
|
7573
|
+
import { createWriteStream } from "fs";
|
|
7574
|
+
import { mkdir as mkdir2, mkdtemp, writeFile as writeFile2, rm, appendFile } from "fs/promises";
|
|
7556
7575
|
import { tmpdir as tmpdir2 } from "os";
|
|
7557
7576
|
import { join as join4 } from "path";
|
|
7558
7577
|
|
|
@@ -7644,9 +7663,203 @@ var delegateStatusWidget = {
|
|
|
7644
7663
|
}
|
|
7645
7664
|
};
|
|
7646
7665
|
|
|
7666
|
+
// src/delegate-watchdog.ts
|
|
7667
|
+
function attachWatchdogs(child, hooks, opts) {
|
|
7668
|
+
let idleTimer;
|
|
7669
|
+
let eofTimer;
|
|
7670
|
+
let killGraceTimer;
|
|
7671
|
+
let timeoutTimer;
|
|
7672
|
+
const clearTimers = () => {
|
|
7673
|
+
if (idleTimer) clearTimeout(idleTimer);
|
|
7674
|
+
if (eofTimer) clearTimeout(eofTimer);
|
|
7675
|
+
if (killGraceTimer) clearTimeout(killGraceTimer);
|
|
7676
|
+
if (timeoutTimer) clearTimeout(timeoutTimer);
|
|
7677
|
+
};
|
|
7678
|
+
const killByWatchdog = (reason) => {
|
|
7679
|
+
if (hooks.isSettled()) return;
|
|
7680
|
+
hooks.onKill(reason);
|
|
7681
|
+
try {
|
|
7682
|
+
child.kill("SIGTERM");
|
|
7683
|
+
} catch {
|
|
7684
|
+
}
|
|
7685
|
+
killGraceTimer = setTimeout(() => {
|
|
7686
|
+
if (hooks.isSettled()) return;
|
|
7687
|
+
try {
|
|
7688
|
+
child.kill("SIGKILL");
|
|
7689
|
+
} catch {
|
|
7690
|
+
}
|
|
7691
|
+
}, opts.killGraceMs);
|
|
7692
|
+
killGraceTimer.unref?.();
|
|
7693
|
+
};
|
|
7694
|
+
const poke = () => {
|
|
7695
|
+
if (idleTimer) clearTimeout(idleTimer);
|
|
7696
|
+
idleTimer = setTimeout(() => killByWatchdog(`no output for ${opts.idleMs / 6e4}m`), opts.idleMs);
|
|
7697
|
+
idleTimer.unref?.();
|
|
7698
|
+
};
|
|
7699
|
+
poke();
|
|
7700
|
+
timeoutTimer = setTimeout(() => killByWatchdog(`${opts.timeoutMs / 6e4}m limit`), opts.timeoutMs);
|
|
7701
|
+
timeoutTimer.unref?.();
|
|
7702
|
+
const onStdoutEnd = () => {
|
|
7703
|
+
if (hooks.isSettled()) return;
|
|
7704
|
+
eofTimer = setTimeout(() => {
|
|
7705
|
+
if (hooks.isSettled()) return;
|
|
7706
|
+
hooks.onEofGrace();
|
|
7707
|
+
try {
|
|
7708
|
+
child.kill("SIGTERM");
|
|
7709
|
+
} catch {
|
|
7710
|
+
}
|
|
7711
|
+
}, opts.eofGraceMs);
|
|
7712
|
+
eofTimer.unref?.();
|
|
7713
|
+
};
|
|
7714
|
+
child.stdout?.once("end", onStdoutEnd);
|
|
7715
|
+
return {
|
|
7716
|
+
poke,
|
|
7717
|
+
dispose: () => {
|
|
7718
|
+
clearTimers();
|
|
7719
|
+
child.stdout?.removeListener("end", onStdoutEnd);
|
|
7720
|
+
}
|
|
7721
|
+
};
|
|
7722
|
+
}
|
|
7723
|
+
|
|
7724
|
+
// src/delegate-events.ts
|
|
7725
|
+
var ThinkingCollector = class {
|
|
7726
|
+
constructor(showThinking) {
|
|
7727
|
+
this.showThinking = showThinking;
|
|
7728
|
+
}
|
|
7729
|
+
showThinking;
|
|
7730
|
+
buf = "";
|
|
7731
|
+
push(delta) {
|
|
7732
|
+
this.buf += delta;
|
|
7733
|
+
}
|
|
7734
|
+
/** Return the segment line to write ("" when empty or disabled), resetting. */
|
|
7735
|
+
flush() {
|
|
7736
|
+
const text = this.buf.trim();
|
|
7737
|
+
this.buf = "";
|
|
7738
|
+
if (!this.showThinking || !text) return "";
|
|
7739
|
+
return `[thinking] ${text}
|
|
7740
|
+
`;
|
|
7741
|
+
}
|
|
7742
|
+
};
|
|
7743
|
+
function parseEventLine(line) {
|
|
7744
|
+
let ev;
|
|
7745
|
+
try {
|
|
7746
|
+
ev = JSON.parse(line);
|
|
7747
|
+
} catch {
|
|
7748
|
+
return null;
|
|
7749
|
+
}
|
|
7750
|
+
if (typeof ev !== "object" || ev === null) return null;
|
|
7751
|
+
const e = ev;
|
|
7752
|
+
if (e.type === "message_update") {
|
|
7753
|
+
const am = e.assistantMessageEvent;
|
|
7754
|
+
if (typeof am !== "object" || am === null) return null;
|
|
7755
|
+
const msg = am;
|
|
7756
|
+
switch (msg.type) {
|
|
7757
|
+
case "text_delta":
|
|
7758
|
+
return { kind: "reply-delta", delta: String(msg.delta ?? "") };
|
|
7759
|
+
case "text_end":
|
|
7760
|
+
return { kind: "reply-complete", content: String(msg.content ?? "") };
|
|
7761
|
+
case "thinking_delta":
|
|
7762
|
+
return { kind: "thinking-delta", delta: String(msg.delta ?? "") };
|
|
7763
|
+
case "thinking_end":
|
|
7764
|
+
return { kind: "thinking-end" };
|
|
7765
|
+
default:
|
|
7766
|
+
return null;
|
|
7767
|
+
}
|
|
7768
|
+
}
|
|
7769
|
+
if (e.type === "tool_execution_start") {
|
|
7770
|
+
return {
|
|
7771
|
+
kind: "tool-start",
|
|
7772
|
+
toolName: String(e.toolName ?? ""),
|
|
7773
|
+
argsText: formatArgs(e.args)
|
|
7774
|
+
};
|
|
7775
|
+
}
|
|
7776
|
+
if (e.type === "tool_execution_update") {
|
|
7777
|
+
return {
|
|
7778
|
+
kind: "tool-update",
|
|
7779
|
+
toolCallId: String(e.toolCallId ?? ""),
|
|
7780
|
+
text: extractContentText(e.partialResult)
|
|
7781
|
+
};
|
|
7782
|
+
}
|
|
7783
|
+
if (e.type === "tool_execution_end") {
|
|
7784
|
+
return {
|
|
7785
|
+
kind: "tool-end",
|
|
7786
|
+
toolName: String(e.toolName ?? ""),
|
|
7787
|
+
isError: Boolean(e.isError)
|
|
7788
|
+
};
|
|
7789
|
+
}
|
|
7790
|
+
if (e.type === "auto_retry_start") {
|
|
7791
|
+
return {
|
|
7792
|
+
kind: "retry-start",
|
|
7793
|
+
attempt: Number(e.attempt ?? 0),
|
|
7794
|
+
maxAttempts: Number(e.maxAttempts ?? 0),
|
|
7795
|
+
delayMs: Number(e.delayMs ?? 0),
|
|
7796
|
+
errorMessage: String(e.errorMessage ?? "")
|
|
7797
|
+
};
|
|
7798
|
+
}
|
|
7799
|
+
if (e.type === "auto_retry_end") {
|
|
7800
|
+
return {
|
|
7801
|
+
kind: "retry-end",
|
|
7802
|
+
success: Boolean(e.success),
|
|
7803
|
+
attempt: Number(e.attempt ?? 0)
|
|
7804
|
+
};
|
|
7805
|
+
}
|
|
7806
|
+
return null;
|
|
7807
|
+
}
|
|
7808
|
+
function formatArgs(args) {
|
|
7809
|
+
if (args && typeof args === "object") {
|
|
7810
|
+
const a = args;
|
|
7811
|
+
if (typeof a.command === "string") return a.command;
|
|
7812
|
+
}
|
|
7813
|
+
try {
|
|
7814
|
+
return JSON.stringify(args);
|
|
7815
|
+
} catch {
|
|
7816
|
+
return String(args);
|
|
7817
|
+
}
|
|
7818
|
+
}
|
|
7819
|
+
function extractContentText(payload) {
|
|
7820
|
+
if (!payload || typeof payload !== "object") return "";
|
|
7821
|
+
const content = payload.content;
|
|
7822
|
+
if (!Array.isArray(content)) return "";
|
|
7823
|
+
return content.map((c) => c && typeof c === "object" ? String(c.text ?? "") : "").join("");
|
|
7824
|
+
}
|
|
7825
|
+
function activityLines(ev, opts) {
|
|
7826
|
+
switch (ev.kind) {
|
|
7827
|
+
case "tool-start":
|
|
7828
|
+
return [`[tool] ${ev.toolName}${ev.argsText ? ` ${ev.argsText}` : ""}
|
|
7829
|
+
`];
|
|
7830
|
+
case "tool-update": {
|
|
7831
|
+
if (!ev.text) return [];
|
|
7832
|
+
return [ev.text.endsWith("\n") ? ev.text : `${ev.text}
|
|
7833
|
+
`];
|
|
7834
|
+
}
|
|
7835
|
+
case "tool-end":
|
|
7836
|
+
return [`[done] ${ev.toolName}${ev.isError ? " (error)" : ""}
|
|
7837
|
+
`];
|
|
7838
|
+
case "thinking-delta":
|
|
7839
|
+
return opts.showThinking ? [`[thinking] ${ev.delta}
|
|
7840
|
+
`] : [];
|
|
7841
|
+
case "retry-start":
|
|
7842
|
+
return [`[retry] attempt ${ev.attempt}/${ev.maxAttempts}, backoff ${ev.delayMs}ms${ev.errorMessage ? ` \u2014 ${ev.errorMessage}` : ""}
|
|
7843
|
+
`];
|
|
7844
|
+
case "retry-end":
|
|
7845
|
+
return [`[retry] attempt ${ev.attempt} ${ev.success ? "succeeded" : "failed"}
|
|
7846
|
+
`];
|
|
7847
|
+
default:
|
|
7848
|
+
return [];
|
|
7849
|
+
}
|
|
7850
|
+
}
|
|
7851
|
+
function newPortion(text, prev) {
|
|
7852
|
+
if (text.startsWith(prev)) return text.slice(prev.length);
|
|
7853
|
+
return text;
|
|
7854
|
+
}
|
|
7855
|
+
|
|
7647
7856
|
// src/delegate-tool.ts
|
|
7648
7857
|
var MAX_DEPTH = 2;
|
|
7649
7858
|
var SYNC_TIMEOUT_MS = 5 * 6e4;
|
|
7859
|
+
var EOF_GRACE_MS = 1e4;
|
|
7860
|
+
var IDLE_GRACE_MS = 5 * 6e4;
|
|
7861
|
+
var ASYNC_TIMEOUT_MS = 30 * 6e4;
|
|
7862
|
+
var KILL_GRACE_MS = 1e4;
|
|
7650
7863
|
var RESULT_SUMMARY_CHARS = 500;
|
|
7651
7864
|
var OUT_DIR = join4(tmpdir2(), "acp-delegate");
|
|
7652
7865
|
var ACP_TOOLS = ["compress", "decompress", "search_context", "acp_status"];
|
|
@@ -7714,6 +7927,11 @@ var DelegateParams = typebox_exports.Object({
|
|
|
7714
7927
|
typebox_exports.Boolean({
|
|
7715
7928
|
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."
|
|
7716
7929
|
})
|
|
7930
|
+
),
|
|
7931
|
+
showThinking: typebox_exports.Optional(
|
|
7932
|
+
typebox_exports.Boolean({
|
|
7933
|
+
description: "If true, the delegate's thinking deltas are also written to the live activity file (default: false \u2014 only tool activity is shown)."
|
|
7934
|
+
})
|
|
7717
7935
|
)
|
|
7718
7936
|
});
|
|
7719
7937
|
var CancelParams = typebox_exports.Object({
|
|
@@ -7771,7 +7989,8 @@ The delegate runs in its own clean pi process \u2014 it does NOT see this conver
|
|
|
7771
7989
|
};
|
|
7772
7990
|
}
|
|
7773
7991
|
function formatRunResult(run) {
|
|
7774
|
-
const
|
|
7992
|
+
const timeoutNote = run.timedOut ? ` (timed out: ${run.timedOut})` : "";
|
|
7993
|
+
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)}`;
|
|
7775
7994
|
return formatPayload(header, run.result?.file ?? "", run.task, run.result?.body);
|
|
7776
7995
|
}
|
|
7777
7996
|
function remainingLineForWait(selfRunId) {
|
|
@@ -7910,13 +8129,12 @@ async function runDelegate(pi, args, ctx, signal) {
|
|
|
7910
8129
|
...process.env,
|
|
7911
8130
|
PI_ACP_DELEGATE_DEPTH: String(parentDepth + 1)
|
|
7912
8131
|
};
|
|
7913
|
-
const { cliArgs, tmpDir } = await buildChildArgs(args, agent.prompt, ctx);
|
|
8132
|
+
const { cliArgs, tmpDir, isAsync, useJsonStream } = await buildChildArgs(args, agent.prompt, ctx);
|
|
7914
8133
|
const requestedAsync = args.async !== false;
|
|
7915
|
-
const isAsync = requestedAsync && ctx.mode !== "print" && ctx.mode !== "json";
|
|
7916
8134
|
if (requestedAsync && !isAsync) {
|
|
7917
8135
|
debug.event("delegate-async-downgraded", { reason: `mode=${ctx.mode}` });
|
|
7918
8136
|
}
|
|
7919
|
-
debug.event("delegate-spawn", { agent: args.agent, cwd, async: isAsync, cliArgs });
|
|
8137
|
+
debug.event("delegate-spawn", { agent: args.agent, cwd, async: isAsync, useJsonStream, cliArgs });
|
|
7920
8138
|
const child = spawn(process.execPath, [process.argv[1], ...cliArgs], {
|
|
7921
8139
|
cwd,
|
|
7922
8140
|
env: childEnv,
|
|
@@ -7927,12 +8145,94 @@ async function runDelegate(pi, args, ctx, signal) {
|
|
|
7927
8145
|
debug.event("delegate-stdin-error", { runId: "pre-spawn", error: String(e) });
|
|
7928
8146
|
});
|
|
7929
8147
|
child.stdin?.end(args.task);
|
|
7930
|
-
let stdoutChunks = [];
|
|
7931
8148
|
let stderrText = "";
|
|
7932
8149
|
const runId = `del_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 6)}`;
|
|
7933
8150
|
const startedAt = Date.now();
|
|
7934
8151
|
if (isAsync) {
|
|
7935
|
-
|
|
8152
|
+
let settled = false;
|
|
8153
|
+
const watchdog = attachWatchdogs(
|
|
8154
|
+
child,
|
|
8155
|
+
{
|
|
8156
|
+
isSettled: () => settled || run.status !== "running",
|
|
8157
|
+
onKill: (reason) => {
|
|
8158
|
+
run.timedOut = reason;
|
|
8159
|
+
debug.event("delegate-watchdog", { runId, reason });
|
|
8160
|
+
},
|
|
8161
|
+
onEofGrace: () => {
|
|
8162
|
+
run.timedOut = "output ended but process did not exit";
|
|
8163
|
+
debug.event("delegate-eof-grace", { runId, ms: EOF_GRACE_MS });
|
|
8164
|
+
}
|
|
8165
|
+
},
|
|
8166
|
+
{ eofGraceMs: EOF_GRACE_MS, idleMs: IDLE_GRACE_MS, timeoutMs: ASYNC_TIMEOUT_MS, killGraceMs: KILL_GRACE_MS }
|
|
8167
|
+
);
|
|
8168
|
+
const replyFile = join4(OUT_DIR, `${runId}.out`);
|
|
8169
|
+
const activityFile = join4(OUT_DIR, `${runId}.activity`);
|
|
8170
|
+
await mkdir2(OUT_DIR, { recursive: true });
|
|
8171
|
+
const replyStream = createWriteStream(replyFile, { flags: "a" });
|
|
8172
|
+
const activityStream = useJsonStream ? createWriteStream(activityFile, { flags: "a" }) : null;
|
|
8173
|
+
const endStream = (s) => new Promise((resolve2) => {
|
|
8174
|
+
if (!s || s.destroyed || s.closed) return resolve2();
|
|
8175
|
+
s.end(() => resolve2());
|
|
8176
|
+
});
|
|
8177
|
+
let replyText = "";
|
|
8178
|
+
let stdoutBuf = "";
|
|
8179
|
+
const lastToolText = /* @__PURE__ */ new Map();
|
|
8180
|
+
const thinking = new ThinkingCollector(args.showThinking === true);
|
|
8181
|
+
const flushThinking = () => {
|
|
8182
|
+
const line = thinking.flush();
|
|
8183
|
+
if (line) activityStream?.write(line);
|
|
8184
|
+
};
|
|
8185
|
+
const handleEventLine = (line) => {
|
|
8186
|
+
const ev = parseEventLine(line);
|
|
8187
|
+
if (!ev) return;
|
|
8188
|
+
if (ev.kind === "thinking-delta") {
|
|
8189
|
+
thinking.push(ev.delta);
|
|
8190
|
+
return;
|
|
8191
|
+
}
|
|
8192
|
+
if (ev.kind === "thinking-end") {
|
|
8193
|
+
flushThinking();
|
|
8194
|
+
return;
|
|
8195
|
+
}
|
|
8196
|
+
if (ev.kind === "reply-delta") {
|
|
8197
|
+
flushThinking();
|
|
8198
|
+
replyText += ev.delta;
|
|
8199
|
+
replyStream.write(ev.delta);
|
|
8200
|
+
return;
|
|
8201
|
+
}
|
|
8202
|
+
if (ev.kind === "reply-complete") {
|
|
8203
|
+
flushThinking();
|
|
8204
|
+
replyText = ev.content;
|
|
8205
|
+
return;
|
|
8206
|
+
}
|
|
8207
|
+
if (ev.kind === "tool-update") {
|
|
8208
|
+
flushThinking();
|
|
8209
|
+
const prev = lastToolText.get(ev.toolCallId) ?? "";
|
|
8210
|
+
const add = newPortion(ev.text, prev);
|
|
8211
|
+
lastToolText.set(ev.toolCallId, ev.text);
|
|
8212
|
+
if (add) activityStream?.write(add.endsWith("\n") ? add : `${add}
|
|
8213
|
+
`);
|
|
8214
|
+
return;
|
|
8215
|
+
}
|
|
8216
|
+
flushThinking();
|
|
8217
|
+
const lines = activityLines(ev, { showThinking: args.showThinking === true });
|
|
8218
|
+
if (lines.length) activityStream?.write(lines.join(""));
|
|
8219
|
+
};
|
|
8220
|
+
child.stdout?.on("data", (c) => {
|
|
8221
|
+
watchdog.poke();
|
|
8222
|
+
if (useJsonStream) {
|
|
8223
|
+
stdoutBuf += c.toString("utf8");
|
|
8224
|
+
let nl;
|
|
8225
|
+
while ((nl = stdoutBuf.indexOf("\n")) >= 0) {
|
|
8226
|
+
const line = stdoutBuf.slice(0, nl);
|
|
8227
|
+
stdoutBuf = stdoutBuf.slice(nl + 1);
|
|
8228
|
+
handleEventLine(line);
|
|
8229
|
+
}
|
|
8230
|
+
} else {
|
|
8231
|
+
const text = c.toString("utf8");
|
|
8232
|
+
replyText += text;
|
|
8233
|
+
replyStream.write(text);
|
|
8234
|
+
}
|
|
8235
|
+
});
|
|
7936
8236
|
child.stderr?.on("data", (c) => {
|
|
7937
8237
|
stderrText += c.toString("utf8");
|
|
7938
8238
|
});
|
|
@@ -7947,47 +8247,69 @@ async function runDelegate(pi, args, ctx, signal) {
|
|
|
7947
8247
|
};
|
|
7948
8248
|
runs.set(runId, run);
|
|
7949
8249
|
delegateStatusWidget.poke();
|
|
7950
|
-
|
|
7951
|
-
void
|
|
7952
|
-
|
|
7953
|
-
|
|
7954
|
-
|
|
7955
|
-
|
|
7956
|
-
|
|
7957
|
-
|
|
7958
|
-
|
|
7959
|
-
|
|
7960
|
-
|
|
7961
|
-
|
|
7962
|
-
|
|
7963
|
-
|
|
7964
|
-
|
|
7965
|
-
run.finishedAt = Date.now();
|
|
7966
|
-
if (run.waiter) {
|
|
7967
|
-
debug.event("delegate-done", { runId, code, status: run.status, injected: false, via: "wait", outLen: output.length, file: file2 });
|
|
7968
|
-
run.waiter();
|
|
8250
|
+
const finalize = (code) => {
|
|
8251
|
+
void (async () => {
|
|
8252
|
+
if (settled) return;
|
|
8253
|
+
settled = true;
|
|
8254
|
+
watchdog.dispose();
|
|
8255
|
+
void cleanupTmp(tmpDir);
|
|
8256
|
+
await Promise.all([endStream(replyStream), endStream(activityStream)]);
|
|
8257
|
+
run.exitCode = code;
|
|
8258
|
+
const output = replyText.trim();
|
|
8259
|
+
const body2 = code === 0 ? output || "(no output)" : stderrText.trim() || output || "(no output)";
|
|
8260
|
+
if (run.status === "cancelled") {
|
|
8261
|
+
await Promise.all([rm(replyFile, { force: true }), rm(activityFile, { force: true })]);
|
|
8262
|
+
run.finishedAt = Date.now();
|
|
8263
|
+
debug.event("delegate-done", { runId, code, status: run.status, injected: false, outLen: output.length });
|
|
8264
|
+
run.waiter?.();
|
|
7969
8265
|
delegateStatusWidget.poke();
|
|
7970
8266
|
return;
|
|
7971
8267
|
}
|
|
7972
|
-
|
|
7973
|
-
|
|
8268
|
+
try {
|
|
8269
|
+
const file2 = replyFile;
|
|
8270
|
+
if (output === "") {
|
|
8271
|
+
const fallback = stderrText.trim();
|
|
8272
|
+
await appendFile(file2, fallback ? `${fallback}
|
|
8273
|
+
` : "(no output)\n");
|
|
8274
|
+
}
|
|
8275
|
+
const effectiveCode = code ?? (output || stderrText ? 0 : null);
|
|
8276
|
+
run.result = { code, file: file2, body: body2 };
|
|
8277
|
+
run.status = effectiveCode === 0 ? "completed" : "failed";
|
|
8278
|
+
run.finishedAt = Date.now();
|
|
8279
|
+
if (run.waiter) {
|
|
8280
|
+
debug.event("delegate-done", { runId, code, status: run.status, injected: false, via: "wait", outLen: output.length, file: file2 });
|
|
8281
|
+
run.waiter();
|
|
8282
|
+
delegateStatusWidget.poke();
|
|
8283
|
+
return;
|
|
8284
|
+
}
|
|
8285
|
+
if (run.consumed) {
|
|
8286
|
+
debug.event("delegate-done", { runId, code, status: run.status, injected: false, via: "consumed", outLen: output.length, file: file2 });
|
|
8287
|
+
delegateStatusWidget.poke();
|
|
8288
|
+
return;
|
|
8289
|
+
}
|
|
8290
|
+
const injected = injectResult(pi, args.agent, runId, args.task, code, file2, run.timedOut);
|
|
8291
|
+
run.injected = injected;
|
|
8292
|
+
debug.event("delegate-done", { runId, code, status: run.status, injected, outLen: output.length, file: file2 });
|
|
8293
|
+
delegateStatusWidget.poke();
|
|
8294
|
+
} catch (err) {
|
|
8295
|
+
run.status = "failed";
|
|
8296
|
+
run.finishedAt = Date.now();
|
|
8297
|
+
debug.event("delegate-done-error", { runId, error: String(err) });
|
|
8298
|
+
run.waiter?.();
|
|
7974
8299
|
delegateStatusWidget.poke();
|
|
7975
|
-
return;
|
|
7976
8300
|
}
|
|
7977
|
-
|
|
7978
|
-
|
|
7979
|
-
|
|
7980
|
-
delegateStatusWidget.poke();
|
|
7981
|
-
}).catch((err) => {
|
|
7982
|
-
run.status = "failed";
|
|
7983
|
-
run.finishedAt = Date.now();
|
|
7984
|
-
debug.event("delegate-done-error", { runId, error: String(err) });
|
|
7985
|
-
run.waiter?.();
|
|
7986
|
-
delegateStatusWidget.poke();
|
|
7987
|
-
});
|
|
7988
|
-
});
|
|
8301
|
+
})();
|
|
8302
|
+
};
|
|
8303
|
+
child.on("close", (code) => finalize(code));
|
|
7989
8304
|
child.on("error", (err) => {
|
|
8305
|
+
if (settled) return;
|
|
8306
|
+
settled = true;
|
|
8307
|
+
watchdog.dispose();
|
|
7990
8308
|
void cleanupTmp(tmpDir);
|
|
8309
|
+
void replyStream.destroy();
|
|
8310
|
+
void activityStream?.destroy();
|
|
8311
|
+
void rm(replyFile, { force: true });
|
|
8312
|
+
void rm(activityFile, { force: true });
|
|
7991
8313
|
if (run.status === "running" || run.status === "cancelled") {
|
|
7992
8314
|
run.status = run.status === "cancelled" ? "cancelled" : "failed";
|
|
7993
8315
|
run.finishedAt = Date.now();
|
|
@@ -8002,6 +8324,8 @@ async function runDelegate(pi, args, ctx, signal) {
|
|
|
8002
8324
|
`Delegated to **${args.agent}** (runId \`${runId}\`).`,
|
|
8003
8325
|
`Task: ${truncate2(args.task, 160)}`,
|
|
8004
8326
|
`Running in the background at \`${cwd}\`.`,
|
|
8327
|
+
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).`,
|
|
8328
|
+
`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.`,
|
|
8005
8329
|
``,
|
|
8006
8330
|
`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.`
|
|
8007
8331
|
].join("\n");
|
|
@@ -8020,7 +8344,9 @@ async function buildChildArgs(args, rolePrompt, ctx) {
|
|
|
8020
8344
|
---
|
|
8021
8345
|
|
|
8022
8346
|
Complete the task below.`, "utf8");
|
|
8023
|
-
const
|
|
8347
|
+
const isAsync = args.async !== false && ctx.mode !== "print" && ctx.mode !== "json";
|
|
8348
|
+
const useJsonStream = isAsync && isPiHost(ctx.sessionManager);
|
|
8349
|
+
const cliArgs = useJsonStream ? ["--mode", "json", "--no-session", "--append-system-prompt", promptFile] : ["-p", "--no-session", "--append-system-prompt", promptFile];
|
|
8024
8350
|
const agentDef = AGENTS[args.agent];
|
|
8025
8351
|
if (agentDef?.restricted) {
|
|
8026
8352
|
const merged = [.../* @__PURE__ */ new Set([...agentDef.tools.split(",").map((s) => s.trim()), ...ACP_TOOLS])];
|
|
@@ -8033,7 +8359,7 @@ Complete the task below.`, "utf8");
|
|
|
8033
8359
|
} else if (ctx.model) {
|
|
8034
8360
|
cliArgs.push("--provider", ctx.model.provider, "--model", ctx.model.id);
|
|
8035
8361
|
}
|
|
8036
|
-
return { cliArgs, tmpDir };
|
|
8362
|
+
return { cliArgs, tmpDir, isAsync, useJsonStream };
|
|
8037
8363
|
}
|
|
8038
8364
|
function waitForChild(child, signal) {
|
|
8039
8365
|
return new Promise((resolve2) => {
|
|
@@ -8079,7 +8405,7 @@ function formatSyncResult(agent, runId, task, r, file) {
|
|
|
8079
8405
|
const body = r.timedOut ? "(timed out)" : r.stderr.trim() || "(no stderr)";
|
|
8080
8406
|
return formatPayload(header, file, task, body);
|
|
8081
8407
|
}
|
|
8082
|
-
function injectResult(pi, agent, runId, task, code, file) {
|
|
8408
|
+
function injectResult(pi, agent, runId, task, code, file, timedOut) {
|
|
8083
8409
|
const send = pi.sendUserMessage;
|
|
8084
8410
|
if (typeof send !== "function") {
|
|
8085
8411
|
debug.event("delegate-inject-skipped", { runId, reason: "sendUserMessage unavailable" });
|
|
@@ -8088,7 +8414,8 @@ function injectResult(pi, agent, runId, task, code, file) {
|
|
|
8088
8414
|
const status = code === 0 ? "completed" : "failed";
|
|
8089
8415
|
const remaining = Array.from(runs.values()).filter((r) => r.status === "running").length;
|
|
8090
8416
|
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.";
|
|
8091
|
-
const
|
|
8417
|
+
const timeoutNote = timedOut ? ` (timed out: ${timedOut})` : "";
|
|
8418
|
+
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.`;
|
|
8092
8419
|
const text = formatPayload(header, file, task);
|
|
8093
8420
|
try {
|
|
8094
8421
|
send.call(pi, text, { deliverAs: "followUp" });
|
|
@@ -8232,7 +8559,7 @@ async function statusReport(runtime, ctx) {
|
|
|
8232
8559
|
const activeBlocksList = state.blocks.filter((b) => b.active);
|
|
8233
8560
|
const totalBlocksList = state.blocks;
|
|
8234
8561
|
const lines = [];
|
|
8235
|
-
const versionStr = "0.1.
|
|
8562
|
+
const versionStr = "0.1.28" ? `billion-context-pi@${"0.1.28"}` : "";
|
|
8236
8563
|
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");
|
|
8237
8564
|
lines.push("\u2502 ACP Context Analysis \u2502");
|
|
8238
8565
|
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");
|
|
@@ -8376,9 +8703,11 @@ When a background delegate finishes, an automated completion notification is inj
|
|
|
8376
8703
|
|
|
8377
8704
|
// src/tool-guardrails.ts
|
|
8378
8705
|
import {
|
|
8379
|
-
isBashToolResult,
|
|
8380
8706
|
isToolCallEventType
|
|
8381
8707
|
} from "@earendil-works/pi-coding-agent";
|
|
8708
|
+
function isBashToolResult(e) {
|
|
8709
|
+
return e.toolName === "bash";
|
|
8710
|
+
}
|
|
8382
8711
|
function resolveBashTimeout(input, defaultTimeout) {
|
|
8383
8712
|
if (input.timeout !== void 0) return void 0;
|
|
8384
8713
|
const d = defaultTimeout ?? DEFAULT_TOOL_BASH_TIMEOUT;
|
|
@@ -8590,7 +8919,7 @@ async function checkForUpdate(autoUpdate, notify) {
|
|
|
8590
8919
|
const data = await res.json();
|
|
8591
8920
|
const latest = data.version;
|
|
8592
8921
|
if (!latest) return;
|
|
8593
|
-
const current = runtimeVersion ?? "0.1.
|
|
8922
|
+
const current = runtimeVersion ?? "0.1.28";
|
|
8594
8923
|
debug.event("update-check", {
|
|
8595
8924
|
current,
|
|
8596
8925
|
latest,
|