billion-context-pi 0.1.27 → 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 +360 -47
- package/dist/index.js.map +1 -1
- package/dist/runtime.d.ts +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
export interface ToolStartEvent {
|
|
2
|
+
kind: "tool-start";
|
|
3
|
+
toolName: string;
|
|
4
|
+
argsText: string;
|
|
5
|
+
}
|
|
6
|
+
export interface ToolUpdateEvent {
|
|
7
|
+
kind: "tool-update";
|
|
8
|
+
toolCallId: string;
|
|
9
|
+
text: string;
|
|
10
|
+
}
|
|
11
|
+
export interface ToolEndEvent {
|
|
12
|
+
kind: "tool-end";
|
|
13
|
+
toolName: string;
|
|
14
|
+
isError: boolean;
|
|
15
|
+
}
|
|
16
|
+
export interface ReplyDeltaEvent {
|
|
17
|
+
kind: "reply-delta";
|
|
18
|
+
delta: string;
|
|
19
|
+
}
|
|
20
|
+
export interface ReplyCompleteEvent {
|
|
21
|
+
kind: "reply-complete";
|
|
22
|
+
content: string;
|
|
23
|
+
}
|
|
24
|
+
export interface ThinkingDeltaEvent {
|
|
25
|
+
kind: "thinking-delta";
|
|
26
|
+
delta: string;
|
|
27
|
+
}
|
|
28
|
+
export type ParsedEvent = ToolStartEvent | ToolUpdateEvent | ToolEndEvent | ReplyDeltaEvent | ReplyCompleteEvent | ThinkingDeltaEvent | ThinkingEndEvent | RetryStartEvent | RetryEndEvent;
|
|
29
|
+
export interface ThinkingEndEvent {
|
|
30
|
+
kind: "thinking-end";
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Accumulates thinking_delta tokens and emits one human-readable line per
|
|
34
|
+
* thinking segment (a segment ends at thinking_end / text_start). Nothing is
|
|
35
|
+
* emitted when showThinking is off.
|
|
36
|
+
*/
|
|
37
|
+
export declare class ThinkingCollector {
|
|
38
|
+
private readonly showThinking;
|
|
39
|
+
private buf;
|
|
40
|
+
constructor(showThinking: boolean);
|
|
41
|
+
push(delta: string): void;
|
|
42
|
+
/** Return the segment line to write ("" when empty or disabled), resetting. */
|
|
43
|
+
flush(): string;
|
|
44
|
+
}
|
|
45
|
+
export interface RetryStartEvent {
|
|
46
|
+
kind: "retry-start";
|
|
47
|
+
attempt: number;
|
|
48
|
+
maxAttempts: number;
|
|
49
|
+
delayMs: number;
|
|
50
|
+
errorMessage: string;
|
|
51
|
+
}
|
|
52
|
+
export interface RetryEndEvent {
|
|
53
|
+
kind: "retry-end";
|
|
54
|
+
success: boolean;
|
|
55
|
+
attempt: number;
|
|
56
|
+
}
|
|
57
|
+
export declare function parseEventLine(line: string): ParsedEvent | null;
|
|
58
|
+
/** Join `content[].text` blocks from a tool result / partialResult payload. */
|
|
59
|
+
export declare function extractContentText(payload: unknown): string;
|
|
60
|
+
/** Format a parsed event as human-readable activity file lines (each with a
|
|
61
|
+
* trailing newline; empty when none). */
|
|
62
|
+
export declare function activityLines(ev: ParsedEvent, opts: {
|
|
63
|
+
showThinking: boolean;
|
|
64
|
+
}): string[];
|
|
65
|
+
/**
|
|
66
|
+
* partialResult is an accumulated snapshot (not a delta), so each update
|
|
67
|
+
* carries everything so far. Return only the newly-appended portion.
|
|
68
|
+
*/
|
|
69
|
+
export declare function newPortion(text: string, prev: string): string;
|
package/dist/delegate-tool.d.ts
CHANGED
|
@@ -13,6 +13,7 @@ declare const DelegateParams: Type.TObject<{
|
|
|
13
13
|
cwd: Type.TOptional<Type.TString>;
|
|
14
14
|
model: Type.TOptional<Type.TString>;
|
|
15
15
|
async: Type.TOptional<Type.TBoolean>;
|
|
16
|
+
showThinking: Type.TOptional<Type.TBoolean>;
|
|
16
17
|
}>;
|
|
17
18
|
type DelegateArgs = Static<typeof DelegateParams>;
|
|
18
19
|
declare const CancelParams: Type.TObject<{
|
|
@@ -40,5 +41,7 @@ export declare function makeDelegateCancelTool(_pi: ExtensionAPI): ToolDefinitio
|
|
|
40
41
|
export declare function buildChildArgs(args: DelegateArgs, rolePrompt: string, ctx: ExtensionContext): Promise<{
|
|
41
42
|
cliArgs: string[];
|
|
42
43
|
tmpDir: string;
|
|
44
|
+
isAsync: boolean;
|
|
45
|
+
useJsonStream: boolean;
|
|
43
46
|
}>;
|
|
44
47
|
export {};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { Readable } from "node:stream";
|
|
2
|
+
export interface WatchdogOptions {
|
|
3
|
+
eofGraceMs: number;
|
|
4
|
+
idleMs: number;
|
|
5
|
+
timeoutMs: number;
|
|
6
|
+
killGraceMs: number;
|
|
7
|
+
}
|
|
8
|
+
export interface WatchdogHooks {
|
|
9
|
+
/** True once the run is finalized; watchdogs stop firing. */
|
|
10
|
+
isSettled(): boolean;
|
|
11
|
+
/** The child is about to be killed (SIGTERM). reason explains why. */
|
|
12
|
+
onKill(reason: string): void;
|
|
13
|
+
/** stdout EOF passed without the process exiting; force-finalize now. */
|
|
14
|
+
onEofGrace(): void;
|
|
15
|
+
}
|
|
16
|
+
export interface WatchdogHandle {
|
|
17
|
+
/** Re-arm the idle timer (call on every stdout data). */
|
|
18
|
+
poke(): void;
|
|
19
|
+
/** Stop all timers (call on finalize). */
|
|
20
|
+
dispose(): void;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Guarantees a hung child process gets killed. A stuck child holds its stdout
|
|
24
|
+
* fd open, so stdout EOF never fires — hence the idle timer (no output for
|
|
25
|
+
* idleMs) is the main defense; the hard time limit and the EOF grace period
|
|
26
|
+
* cover the rest. Kill is SIGTERM, escalated to SIGKILL after killGraceMs.
|
|
27
|
+
*/
|
|
28
|
+
export declare function attachWatchdogs(child: {
|
|
29
|
+
kill(signal: NodeJS.Signals): boolean;
|
|
30
|
+
stdout: Readable | null;
|
|
31
|
+
}, hooks: WatchdogHooks, opts: WatchdogOptions): WatchdogHandle;
|
package/dist/index.js
CHANGED
|
@@ -2635,6 +2635,10 @@ function readContextEntries(sm) {
|
|
|
2635
2635
|
if (typeof source.getBranch === "function") return source.getBranch();
|
|
2636
2636
|
return [];
|
|
2637
2637
|
}
|
|
2638
|
+
function isPiHost(sm) {
|
|
2639
|
+
const source = sm;
|
|
2640
|
+
return typeof source.buildContextEntries === "function";
|
|
2641
|
+
}
|
|
2638
2642
|
function createRuntime(adapter) {
|
|
2639
2643
|
const core = createCore({ countTokens: defaultCountTokens });
|
|
2640
2644
|
const store = new SessionStateStore();
|
|
@@ -7566,7 +7570,8 @@ ${extra.join("\n")}` : base;
|
|
|
7566
7570
|
import {
|
|
7567
7571
|
spawn
|
|
7568
7572
|
} from "child_process";
|
|
7569
|
-
import {
|
|
7573
|
+
import { createWriteStream } from "fs";
|
|
7574
|
+
import { mkdir as mkdir2, mkdtemp, writeFile as writeFile2, rm, appendFile } from "fs/promises";
|
|
7570
7575
|
import { tmpdir as tmpdir2 } from "os";
|
|
7571
7576
|
import { join as join4 } from "path";
|
|
7572
7577
|
|
|
@@ -7658,9 +7663,203 @@ var delegateStatusWidget = {
|
|
|
7658
7663
|
}
|
|
7659
7664
|
};
|
|
7660
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
|
+
|
|
7661
7856
|
// src/delegate-tool.ts
|
|
7662
7857
|
var MAX_DEPTH = 2;
|
|
7663
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;
|
|
7664
7863
|
var RESULT_SUMMARY_CHARS = 500;
|
|
7665
7864
|
var OUT_DIR = join4(tmpdir2(), "acp-delegate");
|
|
7666
7865
|
var ACP_TOOLS = ["compress", "decompress", "search_context", "acp_status"];
|
|
@@ -7728,6 +7927,11 @@ var DelegateParams = typebox_exports.Object({
|
|
|
7728
7927
|
typebox_exports.Boolean({
|
|
7729
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."
|
|
7730
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
|
+
})
|
|
7731
7935
|
)
|
|
7732
7936
|
});
|
|
7733
7937
|
var CancelParams = typebox_exports.Object({
|
|
@@ -7785,7 +7989,8 @@ The delegate runs in its own clean pi process \u2014 it does NOT see this conver
|
|
|
7785
7989
|
};
|
|
7786
7990
|
}
|
|
7787
7991
|
function formatRunResult(run) {
|
|
7788
|
-
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)}`;
|
|
7789
7994
|
return formatPayload(header, run.result?.file ?? "", run.task, run.result?.body);
|
|
7790
7995
|
}
|
|
7791
7996
|
function remainingLineForWait(selfRunId) {
|
|
@@ -7924,13 +8129,12 @@ async function runDelegate(pi, args, ctx, signal) {
|
|
|
7924
8129
|
...process.env,
|
|
7925
8130
|
PI_ACP_DELEGATE_DEPTH: String(parentDepth + 1)
|
|
7926
8131
|
};
|
|
7927
|
-
const { cliArgs, tmpDir } = await buildChildArgs(args, agent.prompt, ctx);
|
|
8132
|
+
const { cliArgs, tmpDir, isAsync, useJsonStream } = await buildChildArgs(args, agent.prompt, ctx);
|
|
7928
8133
|
const requestedAsync = args.async !== false;
|
|
7929
|
-
const isAsync = requestedAsync && ctx.mode !== "print" && ctx.mode !== "json";
|
|
7930
8134
|
if (requestedAsync && !isAsync) {
|
|
7931
8135
|
debug.event("delegate-async-downgraded", { reason: `mode=${ctx.mode}` });
|
|
7932
8136
|
}
|
|
7933
|
-
debug.event("delegate-spawn", { agent: args.agent, cwd, async: isAsync, cliArgs });
|
|
8137
|
+
debug.event("delegate-spawn", { agent: args.agent, cwd, async: isAsync, useJsonStream, cliArgs });
|
|
7934
8138
|
const child = spawn(process.execPath, [process.argv[1], ...cliArgs], {
|
|
7935
8139
|
cwd,
|
|
7936
8140
|
env: childEnv,
|
|
@@ -7941,12 +8145,94 @@ async function runDelegate(pi, args, ctx, signal) {
|
|
|
7941
8145
|
debug.event("delegate-stdin-error", { runId: "pre-spawn", error: String(e) });
|
|
7942
8146
|
});
|
|
7943
8147
|
child.stdin?.end(args.task);
|
|
7944
|
-
let stdoutChunks = [];
|
|
7945
8148
|
let stderrText = "";
|
|
7946
8149
|
const runId = `del_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 6)}`;
|
|
7947
8150
|
const startedAt = Date.now();
|
|
7948
8151
|
if (isAsync) {
|
|
7949
|
-
|
|
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
|
+
});
|
|
7950
8236
|
child.stderr?.on("data", (c) => {
|
|
7951
8237
|
stderrText += c.toString("utf8");
|
|
7952
8238
|
});
|
|
@@ -7961,47 +8247,69 @@ async function runDelegate(pi, args, ctx, signal) {
|
|
|
7961
8247
|
};
|
|
7962
8248
|
runs.set(runId, run);
|
|
7963
8249
|
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();
|
|
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?.();
|
|
7983
8265
|
delegateStatusWidget.poke();
|
|
7984
8266
|
return;
|
|
7985
8267
|
}
|
|
7986
|
-
|
|
7987
|
-
|
|
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?.();
|
|
7988
8299
|
delegateStatusWidget.poke();
|
|
7989
|
-
return;
|
|
7990
8300
|
}
|
|
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
|
-
});
|
|
8301
|
+
})();
|
|
8302
|
+
};
|
|
8303
|
+
child.on("close", (code) => finalize(code));
|
|
8003
8304
|
child.on("error", (err) => {
|
|
8305
|
+
if (settled) return;
|
|
8306
|
+
settled = true;
|
|
8307
|
+
watchdog.dispose();
|
|
8004
8308
|
void cleanupTmp(tmpDir);
|
|
8309
|
+
void replyStream.destroy();
|
|
8310
|
+
void activityStream?.destroy();
|
|
8311
|
+
void rm(replyFile, { force: true });
|
|
8312
|
+
void rm(activityFile, { force: true });
|
|
8005
8313
|
if (run.status === "running" || run.status === "cancelled") {
|
|
8006
8314
|
run.status = run.status === "cancelled" ? "cancelled" : "failed";
|
|
8007
8315
|
run.finishedAt = Date.now();
|
|
@@ -8016,6 +8324,8 @@ async function runDelegate(pi, args, ctx, signal) {
|
|
|
8016
8324
|
`Delegated to **${args.agent}** (runId \`${runId}\`).`,
|
|
8017
8325
|
`Task: ${truncate2(args.task, 160)}`,
|
|
8018
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.`,
|
|
8019
8329
|
``,
|
|
8020
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.`
|
|
8021
8331
|
].join("\n");
|
|
@@ -8034,7 +8344,9 @@ async function buildChildArgs(args, rolePrompt, ctx) {
|
|
|
8034
8344
|
---
|
|
8035
8345
|
|
|
8036
8346
|
Complete the task below.`, "utf8");
|
|
8037
|
-
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];
|
|
8038
8350
|
const agentDef = AGENTS[args.agent];
|
|
8039
8351
|
if (agentDef?.restricted) {
|
|
8040
8352
|
const merged = [.../* @__PURE__ */ new Set([...agentDef.tools.split(",").map((s) => s.trim()), ...ACP_TOOLS])];
|
|
@@ -8047,7 +8359,7 @@ Complete the task below.`, "utf8");
|
|
|
8047
8359
|
} else if (ctx.model) {
|
|
8048
8360
|
cliArgs.push("--provider", ctx.model.provider, "--model", ctx.model.id);
|
|
8049
8361
|
}
|
|
8050
|
-
return { cliArgs, tmpDir };
|
|
8362
|
+
return { cliArgs, tmpDir, isAsync, useJsonStream };
|
|
8051
8363
|
}
|
|
8052
8364
|
function waitForChild(child, signal) {
|
|
8053
8365
|
return new Promise((resolve2) => {
|
|
@@ -8093,7 +8405,7 @@ function formatSyncResult(agent, runId, task, r, file) {
|
|
|
8093
8405
|
const body = r.timedOut ? "(timed out)" : r.stderr.trim() || "(no stderr)";
|
|
8094
8406
|
return formatPayload(header, file, task, body);
|
|
8095
8407
|
}
|
|
8096
|
-
function injectResult(pi, agent, runId, task, code, file) {
|
|
8408
|
+
function injectResult(pi, agent, runId, task, code, file, timedOut) {
|
|
8097
8409
|
const send = pi.sendUserMessage;
|
|
8098
8410
|
if (typeof send !== "function") {
|
|
8099
8411
|
debug.event("delegate-inject-skipped", { runId, reason: "sendUserMessage unavailable" });
|
|
@@ -8102,7 +8414,8 @@ function injectResult(pi, agent, runId, task, code, file) {
|
|
|
8102
8414
|
const status = code === 0 ? "completed" : "failed";
|
|
8103
8415
|
const remaining = Array.from(runs.values()).filter((r) => r.status === "running").length;
|
|
8104
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.";
|
|
8105
|
-
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.`;
|
|
8106
8419
|
const text = formatPayload(header, file, task);
|
|
8107
8420
|
try {
|
|
8108
8421
|
send.call(pi, text, { deliverAs: "followUp" });
|
|
@@ -8246,7 +8559,7 @@ async function statusReport(runtime, ctx) {
|
|
|
8246
8559
|
const activeBlocksList = state.blocks.filter((b) => b.active);
|
|
8247
8560
|
const totalBlocksList = state.blocks;
|
|
8248
8561
|
const lines = [];
|
|
8249
|
-
const versionStr = "0.1.
|
|
8562
|
+
const versionStr = "0.1.28" ? `billion-context-pi@${"0.1.28"}` : "";
|
|
8250
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");
|
|
8251
8564
|
lines.push("\u2502 ACP Context Analysis \u2502");
|
|
8252
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");
|
|
@@ -8606,7 +8919,7 @@ async function checkForUpdate(autoUpdate, notify) {
|
|
|
8606
8919
|
const data = await res.json();
|
|
8607
8920
|
const latest = data.version;
|
|
8608
8921
|
if (!latest) return;
|
|
8609
|
-
const current = runtimeVersion ?? "0.1.
|
|
8922
|
+
const current = runtimeVersion ?? "0.1.28";
|
|
8610
8923
|
debug.event("update-check", {
|
|
8611
8924
|
current,
|
|
8612
8925
|
latest,
|