billion-context-pi 0.1.48 → 0.1.49-pr.201.19
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 +1 -0
- package/dist/delegate-tool.d.ts +21 -1
- package/dist/index.js +202 -103
- package/dist/index.js.map +1 -1
- package/dist/messages.d.ts +1 -0
- package/dist/system-prompt.d.ts +1 -1
- package/dist/tokens.d.ts +11 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -104,6 +104,7 @@ The full delegate result is saved to a file (`/tmp/acp-delegate/<runId>.out`); t
|
|
|
104
104
|
|
|
105
105
|
- **Interactive (TUI) & RPC modes**: `async:true` (default) runs the child in the background; a short completion notification is injected into the chat when it finishes.
|
|
106
106
|
- **Print / JSON modes** (`pi -p`, SDK): `async:true` auto-downgrades to **synchronous** — the result returns as the tool result in the same turn (the parent exits after one turn, so background injection would be lost).
|
|
107
|
+
- **Failures are loud, never silent.** A run that fails (nonzero exit, spawn error, watchdog timeout) injects a `FAILED ⚠️` notification carrying a short error excerpt, so a failed delegate cannot hide among sibling completions. If a notification cannot be delivered at all, a recovery notice is attached to the next delegate notification or the next `acp_delegate` / `acp_delegate_wait` / `acp_delegate_cancel` tool result — a dispatched run's failure always reaches the model before it wraps up.
|
|
107
108
|
|
|
108
109
|
In the **interactive TUI**, async runs also show a live status widget below the editor (agent, elapsed seconds, task preview), so you always know what's running and for how long. Disabled automatically in RPC/print/JSON.
|
|
109
110
|
|
package/dist/delegate-tool.d.ts
CHANGED
|
@@ -104,6 +104,26 @@ declare const WaitParams: Type.TObject<{
|
|
|
104
104
|
}>;
|
|
105
105
|
export declare function accumulateUsage(a: Usage | undefined, b: Usage): Usage;
|
|
106
106
|
export declare function makeDelegateTool(pi: ExtensionAPI): ToolDefinition<typeof DelegateParams>;
|
|
107
|
+
/** Runs that reached a terminal state but whose result never reached the
|
|
108
|
+
* model: no parked waiter, not consumed by a tool result, and never injected
|
|
109
|
+
* as a notification. The model was promised a notification ("do NOT keep
|
|
110
|
+
* waiting"), so these must eventually be recovered, or a failed delegate
|
|
111
|
+
* stays invisible until the very end of the task. */
|
|
112
|
+
export declare function findUndeliveredRuns(all: DelegateRun[], excludeRunId?: string): DelegateRun[];
|
|
113
|
+
/** Compute the recovery notice for undelivered runs WITHOUT marking them
|
|
114
|
+
* delivered. The caller commits the marking (covered[].injected = true) only
|
|
115
|
+
* after the carrier message is actually sent: if the send throws, the runs
|
|
116
|
+
* must stay undelivered so a later carrier can recover them. */
|
|
117
|
+
export declare function buildRecoveryNotice(all: DelegateRun[], excludeRunId?: string): {
|
|
118
|
+
text: string;
|
|
119
|
+
covered: DelegateRun[];
|
|
120
|
+
};
|
|
121
|
+
/** Build a recovery notice for undelivered runs and mark each covered run
|
|
122
|
+
* injected=true (this notice IS its delivery) so it is never re-notified and
|
|
123
|
+
* a later wait dedups instead of re-delivering the payload. For carriers the
|
|
124
|
+
* host owns (delegate tool results); injectResult commits the marking itself
|
|
125
|
+
* only after its send succeeds (see buildRecoveryNotice). */
|
|
126
|
+
export declare function undeliveredNoticeFrom(all: DelegateRun[], excludeRunId?: string): string;
|
|
107
127
|
/** If the delegate already delivered its result via a system notification
|
|
108
128
|
* (the close handler injected before this wait was called), return a short
|
|
109
129
|
* "already delivered" message pointing at the result file, so the model
|
|
@@ -143,5 +163,5 @@ export declare function buildChildArgs(args: DelegateArgs, rolePrompt: string, c
|
|
|
143
163
|
isAsync: boolean;
|
|
144
164
|
useJsonStream: boolean;
|
|
145
165
|
}>;
|
|
146
|
-
export declare function injectResult(pi: ExtensionAPI, agent: string, runId: string, task: string, code: number | null, file: string, timedOut?: string, usage?: Usage, mode?: "merged" | "separate", usageAlreadyReported?: boolean): boolean;
|
|
166
|
+
export declare function injectResult(pi: ExtensionAPI, agent: string, runId: string, task: string, code: number | null, file: string, timedOut?: string, usage?: Usage, mode?: "merged" | "separate", usageAlreadyReported?: boolean, body?: string): boolean;
|
|
147
167
|
export {};
|
package/dist/index.js
CHANGED
|
@@ -3753,6 +3753,15 @@ function extractText(content) {
|
|
|
3753
3753
|
function stripRefTag(text) {
|
|
3754
3754
|
return text.replace(REF_TAG, "").replace(TRAILING_REF_TAG, "");
|
|
3755
3755
|
}
|
|
3756
|
+
function countImageBlocks(content) {
|
|
3757
|
+
if (!Array.isArray(content)) return 0;
|
|
3758
|
+
let n = 0;
|
|
3759
|
+
for (const block of content) {
|
|
3760
|
+
const b = block;
|
|
3761
|
+
if (b?.type === "image") n++;
|
|
3762
|
+
}
|
|
3763
|
+
return n;
|
|
3764
|
+
}
|
|
3756
3765
|
function messageIdentity(message) {
|
|
3757
3766
|
return JSON.stringify(normalizeIdentityValue(message, true));
|
|
3758
3767
|
}
|
|
@@ -9172,12 +9181,29 @@ function collectCoveredMessageIds(state) {
|
|
|
9172
9181
|
}
|
|
9173
9182
|
return ids;
|
|
9174
9183
|
}
|
|
9175
|
-
|
|
9184
|
+
var IMAGE_TOKEN_COST = 1600;
|
|
9185
|
+
function modelSupportsImages(model) {
|
|
9186
|
+
const input = model?.input;
|
|
9187
|
+
return Array.isArray(input) && input.includes("image");
|
|
9188
|
+
}
|
|
9189
|
+
function collectImageTokens(entries, visionCapable) {
|
|
9190
|
+
const out = /* @__PURE__ */ new Map();
|
|
9191
|
+
if (!visionCapable) return out;
|
|
9192
|
+
for (const e of entries) {
|
|
9193
|
+
if (e.type !== "message") continue;
|
|
9194
|
+
const n = countImageBlocks(e.message?.content);
|
|
9195
|
+
if (n > 0) out.set(e.id, n * IMAGE_TOKEN_COST);
|
|
9196
|
+
}
|
|
9197
|
+
return out;
|
|
9198
|
+
}
|
|
9199
|
+
function estimateTokens(messages, coveredIds, imageTokensById) {
|
|
9176
9200
|
let tokens = 0;
|
|
9177
9201
|
for (const m of messages) {
|
|
9178
9202
|
if (m.toolName === "compress") continue;
|
|
9179
9203
|
if (coveredIds?.has(m.id)) continue;
|
|
9180
9204
|
tokens += defaultCountTokens(m.text ?? "");
|
|
9205
|
+
const img = imageTokensById?.get(m.id);
|
|
9206
|
+
if (img) tokens += img;
|
|
9181
9207
|
}
|
|
9182
9208
|
return tokens;
|
|
9183
9209
|
}
|
|
@@ -9306,12 +9332,13 @@ async function handleCompress(args, runtime, ctx, toolCallId) {
|
|
|
9306
9332
|
if (typeof maybeRanges === "string") throw new Error(maybeRanges);
|
|
9307
9333
|
const ranges = maybeRanges;
|
|
9308
9334
|
if (ranges.length === 0) return "No ranges provided.";
|
|
9309
|
-
const { state: initialState, coreMessages } = await runtime.stateFor(ctx);
|
|
9335
|
+
const { state: initialState, coreMessages, entries } = await runtime.stateFor(ctx);
|
|
9310
9336
|
const config = runtime.configFor(ctx);
|
|
9311
9337
|
const modelId = ctx.model?.id ?? "default";
|
|
9312
9338
|
const systemPromptText = getSystemPromptText(ctx);
|
|
9313
9339
|
const systemPromptTokens = systemPromptText ? defaultCountTokens(systemPromptText) : 0;
|
|
9314
|
-
const
|
|
9340
|
+
const imageTokens = collectImageTokens(entries, modelSupportsImages(ctx.model));
|
|
9341
|
+
const sentTokens = estimateTokens(coreMessages, collectCoveredMessageIds(initialState), imageTokens) + systemPromptTokens;
|
|
9315
9342
|
const turn = runtime.core.processTurn({
|
|
9316
9343
|
messages: coreMessages,
|
|
9317
9344
|
state: initialState,
|
|
@@ -9321,7 +9348,7 @@ async function handleCompress(args, runtime, ctx, toolCallId) {
|
|
|
9321
9348
|
const state = turn.state;
|
|
9322
9349
|
const messages = turn.messages;
|
|
9323
9350
|
const density = runtime.density.densityFor(modelId);
|
|
9324
|
-
const beforeTokens = calibrateTokens(estimateTokens(messages, collectCoveredMessageIds(state)), density);
|
|
9351
|
+
const beforeTokens = calibrateTokens(estimateTokens(messages, collectCoveredMessageIds(state), imageTokens), density);
|
|
9325
9352
|
const summaryMaxChars = args.summaryMaxChars;
|
|
9326
9353
|
const topLevelTopic = args.topic;
|
|
9327
9354
|
debug.event("compress-in", {
|
|
@@ -9361,7 +9388,7 @@ async function handleCompress(args, runtime, ctx, toolCallId) {
|
|
|
9361
9388
|
config,
|
|
9362
9389
|
tokenCount: calibrateTokens(sentTokens, density)
|
|
9363
9390
|
});
|
|
9364
|
-
const afterTokens = calibrateTokens(estimateTokens(afterTurn.messages, collectCoveredMessageIds(applied.state)), density);
|
|
9391
|
+
const afterTokens = calibrateTokens(estimateTokens(afterTurn.messages, collectCoveredMessageIds(applied.state), imageTokens), density);
|
|
9365
9392
|
const reclaimed = Math.max(0, beforeTokens - afterTokens);
|
|
9366
9393
|
const newBlocks = applied.state.blocks.slice(-blocksCreated);
|
|
9367
9394
|
debug.event("compress-out", {
|
|
@@ -10414,30 +10441,63 @@ The delegate runs in its own clean pi process \u2014 it does NOT see this conver
|
|
|
10414
10441
|
"Delegate to get a focused result in a clean context, or to parallelize independent work.",
|
|
10415
10442
|
"The sub-agent has NO access to this conversation \u2014 write a fully self-contained task.",
|
|
10416
10443
|
"Prefer async=true and launch several; results arrive back automatically when each finishes.",
|
|
10444
|
+
"A FAILED notification (\u26A0\uFE0F) means that task produced no usable result \u2014 decide whether to re-dispatch it before wrapping up.",
|
|
10417
10445
|
"For changes you must apply yourself, delegate read-only investigation (reviewer/researcher/oracle) and keep the main context as the sole writer."
|
|
10418
10446
|
],
|
|
10419
10447
|
parameters: DelegateParams,
|
|
10420
10448
|
async execute(toolCallId, params, signal, _onUpdate, ctx) {
|
|
10421
10449
|
const args = params;
|
|
10422
|
-
|
|
10450
|
+
let outcome = await runDelegate(pi, args, ctx, signal);
|
|
10451
|
+
const notice = undeliveredNotice();
|
|
10452
|
+
if (notice) outcome = `${notice}
|
|
10453
|
+
|
|
10454
|
+
${outcome}`;
|
|
10423
10455
|
return { details: void 0, content: [{ type: "text", text: outcome }] };
|
|
10424
10456
|
}
|
|
10425
10457
|
};
|
|
10426
10458
|
}
|
|
10427
10459
|
function formatRunResult(run) {
|
|
10428
10460
|
const timeoutNote = run.timedOut ? ` (timed out: ${run.timedOut})` : "";
|
|
10429
|
-
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)}`;
|
|
10461
|
+
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 === "failed" ? "FAILED \u26A0\uFE0F" : run.status} (exit ${run.exitCode ?? "?"})${timeoutNote}${remainingLineForWait(run.runId)}`;
|
|
10430
10462
|
return formatPayload(header, run.result?.file ?? "", run.task, run.result?.body);
|
|
10431
10463
|
}
|
|
10432
10464
|
function remainingLineForWait(selfRunId) {
|
|
10433
10465
|
const remaining = Array.from(runs.values()).filter((r) => r.status === "running" && r.runId !== selfRunId).length;
|
|
10434
10466
|
return remaining > 0 ? ` ${remaining} delegate${remaining === 1 ? " is" : "s are"} still running.` : "";
|
|
10435
10467
|
}
|
|
10468
|
+
function findUndeliveredRuns(all, excludeRunId) {
|
|
10469
|
+
return all.filter(
|
|
10470
|
+
(r) => r.runId !== excludeRunId && (r.status === "completed" || r.status === "failed") && !r.waiter && !r.consumed && !r.injected
|
|
10471
|
+
);
|
|
10472
|
+
}
|
|
10473
|
+
function buildRecoveryNotice(all, excludeRunId) {
|
|
10474
|
+
const pending = findUndeliveredRuns(all, excludeRunId);
|
|
10475
|
+
if (pending.length === 0) return { text: "", covered: [] };
|
|
10476
|
+
const anyFailed = pending.some((r) => r.status === "failed");
|
|
10477
|
+
const header = `\u26A0\uFE0F Recovery notice: ${pending.length} earlier delegate result${pending.length === 1 ? "" : "s"} never reached you (notification delivery failed).${anyFailed ? " At least one FAILED \u2014 that task's work is missing; read its result below and decide whether to re-dispatch before concluding." : ""}`;
|
|
10478
|
+
return { text: [header, ...pending.map((r) => formatRunResult(r))].join("\n"), covered: pending };
|
|
10479
|
+
}
|
|
10480
|
+
function undeliveredNoticeFrom(all, excludeRunId) {
|
|
10481
|
+
const { text, covered } = buildRecoveryNotice(all, excludeRunId);
|
|
10482
|
+
for (const r of covered) r.injected = true;
|
|
10483
|
+
return text;
|
|
10484
|
+
}
|
|
10485
|
+
function undeliveredNotice(excludeRunId) {
|
|
10486
|
+
return undeliveredNoticeFrom(Array.from(runs.values()), excludeRunId);
|
|
10487
|
+
}
|
|
10488
|
+
function withUndeliveredNotice(result) {
|
|
10489
|
+
const notice = undeliveredNotice();
|
|
10490
|
+
const first = result.content[0];
|
|
10491
|
+
if (notice && first && first.type === "text") first.text = `${notice}
|
|
10492
|
+
|
|
10493
|
+
${first.text}`;
|
|
10494
|
+
return result;
|
|
10495
|
+
}
|
|
10436
10496
|
function injectedWaitMessage(run, runId, remainingLine) {
|
|
10437
10497
|
if (!run.injected) return null;
|
|
10438
10498
|
const file = run.result?.file;
|
|
10439
10499
|
const fileLine = file ? ` If you need details, read the result file: \`${file}\`.` : "";
|
|
10440
|
-
return `Delegate \`${runId}\` already delivered its result
|
|
10500
|
+
return `Delegate \`${runId}\` already delivered its result (system notification or recovery notice) when it finished \u2014 no need to wait on it again.${remainingLine}${fileLine}`;
|
|
10441
10501
|
}
|
|
10442
10502
|
function buildWaitResult(run, content, mode = "separate", contentType = "text") {
|
|
10443
10503
|
if (run.usage && !run.usageReported) {
|
|
@@ -10472,6 +10532,61 @@ function buildCancelResult(run, content, mode = "separate") {
|
|
|
10472
10532
|
return { details: void 0, content: [{ type: "text", text: content }] };
|
|
10473
10533
|
}
|
|
10474
10534
|
function makeDelegateWaitTool(_pi) {
|
|
10535
|
+
const exec = async (args, signal) => {
|
|
10536
|
+
const run = runs.get(args.runId);
|
|
10537
|
+
if (!run) {
|
|
10538
|
+
return { details: void 0, content: [{ type: "text", text: `No delegate run with runId \`${args.runId}\`. It may have already been reported or never existed.` }] };
|
|
10539
|
+
}
|
|
10540
|
+
const displayMode = delegateDisplayUsage;
|
|
10541
|
+
if (run.status === "cancelled") {
|
|
10542
|
+
run.consumed = true;
|
|
10543
|
+
return buildWaitResult(run, `Delegate \`${args.runId}\` was cancelled (no result).${remainingLineForWait(args.runId)}`, displayMode);
|
|
10544
|
+
}
|
|
10545
|
+
if (run.status !== "running") {
|
|
10546
|
+
const dedup = injectedWaitMessage(run, args.runId, remainingLineForWait(args.runId));
|
|
10547
|
+
if (dedup) {
|
|
10548
|
+
run.consumed = true;
|
|
10549
|
+
return buildWaitResult(run, dedup, displayMode);
|
|
10550
|
+
}
|
|
10551
|
+
run.consumed = true;
|
|
10552
|
+
if (!run.result) {
|
|
10553
|
+
return buildWaitResult(run, `Delegate \`${args.runId}\` finished but no result is available (persist error).`, displayMode);
|
|
10554
|
+
}
|
|
10555
|
+
return buildWaitResult(run, formatRunResult(run), displayMode);
|
|
10556
|
+
}
|
|
10557
|
+
const timeoutMs = resolveWaitTimeoutMs(args.timeout);
|
|
10558
|
+
if (run.waiter) {
|
|
10559
|
+
return { details: void 0, content: [{ type: "text", text: `Delegate \`${args.runId}\` already has a wait in progress; do not wait on it twice.` }] };
|
|
10560
|
+
}
|
|
10561
|
+
return new Promise((resolve3) => {
|
|
10562
|
+
let settled = false;
|
|
10563
|
+
let timer2;
|
|
10564
|
+
const finish2 = (result) => {
|
|
10565
|
+
if (settled) return;
|
|
10566
|
+
settled = true;
|
|
10567
|
+
run.waiter = void 0;
|
|
10568
|
+
if (timer2) clearTimeout(timer2);
|
|
10569
|
+
signal?.removeEventListener("abort", onAbort);
|
|
10570
|
+
resolve3(result);
|
|
10571
|
+
};
|
|
10572
|
+
const onAbort = () => {
|
|
10573
|
+
finish2({ details: void 0, content: [{ type: "text", text: `Aborted; delegate \`${args.runId}\` is still running in the background. A notification will be injected when it finishes.` }] });
|
|
10574
|
+
};
|
|
10575
|
+
run.waiter = () => {
|
|
10576
|
+
run.consumed = true;
|
|
10577
|
+
if (run.status === "cancelled") {
|
|
10578
|
+
finish2(buildWaitResult(run, `Delegate \`${run.runId}\` was cancelled (no result).${remainingLineForWait(run.runId)}`, displayMode));
|
|
10579
|
+
return;
|
|
10580
|
+
}
|
|
10581
|
+
finish2(buildWaitResult(run, formatRunResult(run), displayMode));
|
|
10582
|
+
};
|
|
10583
|
+
signal?.addEventListener("abort", onAbort);
|
|
10584
|
+
timer2 = setTimeout(
|
|
10585
|
+
() => finish2({ details: void 0, content: [{ type: "text", text: `Failed: delegate \`${args.runId}\` result not ready after ${Math.round(timeoutMs / 1e3)}s. Do NOT keep waiting or retry \u2014 go do other work now. The run continues in the background and a completion notification (with the result file path) will be injected into the chat when it finishes.` }] }),
|
|
10586
|
+
timeoutMs
|
|
10587
|
+
);
|
|
10588
|
+
});
|
|
10589
|
+
};
|
|
10475
10590
|
return {
|
|
10476
10591
|
name: "acp_delegate_wait",
|
|
10477
10592
|
label: "ACP Delegate Wait",
|
|
@@ -10483,64 +10598,32 @@ function makeDelegateWaitTool(_pi) {
|
|
|
10483
10598
|
],
|
|
10484
10599
|
parameters: WaitParams,
|
|
10485
10600
|
async execute(_toolCallId, params, signal) {
|
|
10486
|
-
|
|
10487
|
-
const run = runs.get(args.runId);
|
|
10488
|
-
if (!run) {
|
|
10489
|
-
return { details: void 0, content: [{ type: "text", text: `No delegate run with runId \`${args.runId}\`. It may have already been reported or never existed.` }] };
|
|
10490
|
-
}
|
|
10491
|
-
const displayMode = delegateDisplayUsage;
|
|
10492
|
-
if (run.status === "cancelled") {
|
|
10493
|
-
run.consumed = true;
|
|
10494
|
-
return buildWaitResult(run, `Delegate \`${args.runId}\` was cancelled (no result).${remainingLineForWait(args.runId)}`, displayMode);
|
|
10495
|
-
}
|
|
10496
|
-
if (run.status !== "running") {
|
|
10497
|
-
const dedup = injectedWaitMessage(run, args.runId, remainingLineForWait(args.runId));
|
|
10498
|
-
if (dedup) {
|
|
10499
|
-
run.consumed = true;
|
|
10500
|
-
return buildWaitResult(run, dedup, displayMode);
|
|
10501
|
-
}
|
|
10502
|
-
run.consumed = true;
|
|
10503
|
-
if (!run.result) {
|
|
10504
|
-
return buildWaitResult(run, `Delegate \`${args.runId}\` finished but no result is available (persist error).`, displayMode);
|
|
10505
|
-
}
|
|
10506
|
-
return buildWaitResult(run, formatRunResult(run), displayMode);
|
|
10507
|
-
}
|
|
10508
|
-
const timeoutMs = resolveWaitTimeoutMs(args.timeout);
|
|
10509
|
-
if (run.waiter) {
|
|
10510
|
-
return { details: void 0, content: [{ type: "text", text: `Delegate \`${args.runId}\` already has a wait in progress; do not wait on it twice.` }] };
|
|
10511
|
-
}
|
|
10512
|
-
return new Promise((resolve3) => {
|
|
10513
|
-
let settled = false;
|
|
10514
|
-
let timer2;
|
|
10515
|
-
const finish2 = (result) => {
|
|
10516
|
-
if (settled) return;
|
|
10517
|
-
settled = true;
|
|
10518
|
-
run.waiter = void 0;
|
|
10519
|
-
if (timer2) clearTimeout(timer2);
|
|
10520
|
-
signal?.removeEventListener("abort", onAbort);
|
|
10521
|
-
resolve3(result);
|
|
10522
|
-
};
|
|
10523
|
-
const onAbort = () => {
|
|
10524
|
-
finish2({ details: void 0, content: [{ type: "text", text: `Aborted; delegate \`${args.runId}\` is still running in the background. A notification will be injected when it finishes.` }] });
|
|
10525
|
-
};
|
|
10526
|
-
run.waiter = () => {
|
|
10527
|
-
run.consumed = true;
|
|
10528
|
-
if (run.status === "cancelled") {
|
|
10529
|
-
finish2(buildWaitResult(run, `Delegate \`${run.runId}\` was cancelled (no result).${remainingLineForWait(run.runId)}`, displayMode));
|
|
10530
|
-
return;
|
|
10531
|
-
}
|
|
10532
|
-
finish2(buildWaitResult(run, formatRunResult(run), displayMode));
|
|
10533
|
-
};
|
|
10534
|
-
signal?.addEventListener("abort", onAbort);
|
|
10535
|
-
timer2 = setTimeout(
|
|
10536
|
-
() => finish2({ details: void 0, content: [{ type: "text", text: `Failed: delegate \`${args.runId}\` result not ready after ${Math.round(timeoutMs / 1e3)}s. Do NOT keep waiting or retry \u2014 go do other work now. The run continues in the background and a completion notification (with the result file path) will be injected into the chat when it finishes.` }] }),
|
|
10537
|
-
timeoutMs
|
|
10538
|
-
);
|
|
10539
|
-
});
|
|
10601
|
+
return withUndeliveredNotice(await exec(params, signal));
|
|
10540
10602
|
}
|
|
10541
10603
|
};
|
|
10542
10604
|
}
|
|
10543
10605
|
function makeDelegateCancelTool(_pi) {
|
|
10606
|
+
const exec = async (params) => {
|
|
10607
|
+
const { runId } = params;
|
|
10608
|
+
const run = runs.get(runId);
|
|
10609
|
+
if (!run) {
|
|
10610
|
+
return { details: void 0, content: [{ type: "text", text: `Unknown runId "${runId}".` }] };
|
|
10611
|
+
}
|
|
10612
|
+
if (run.status !== "running") {
|
|
10613
|
+
return buildCancelResult(run, `Run ${runId} already ${run.status} (no action).`);
|
|
10614
|
+
}
|
|
10615
|
+
run.status = "cancelled";
|
|
10616
|
+
run.consumed = true;
|
|
10617
|
+
try {
|
|
10618
|
+
run.child?.kill("SIGTERM");
|
|
10619
|
+
} catch (err) {
|
|
10620
|
+
debug.event("delegate-cancel-kill-error", { runId, error: String(err) });
|
|
10621
|
+
logError("delegate", { event: "cancel-kill-error", runId, error: String(err) });
|
|
10622
|
+
}
|
|
10623
|
+
delegateStatusWidget.poke();
|
|
10624
|
+
const displayMode = delegateDisplayUsage;
|
|
10625
|
+
return buildCancelResult(run, `Cancelled ${runId} (${run.agent}).`, displayMode);
|
|
10626
|
+
};
|
|
10544
10627
|
return {
|
|
10545
10628
|
name: "acp_delegate_cancel",
|
|
10546
10629
|
label: "ACP Delegate Cancel",
|
|
@@ -10549,25 +10632,7 @@ function makeDelegateCancelTool(_pi) {
|
|
|
10549
10632
|
promptGuidelines: [],
|
|
10550
10633
|
parameters: CancelParams,
|
|
10551
10634
|
async execute(toolCallId, params) {
|
|
10552
|
-
|
|
10553
|
-
const run = runs.get(runId);
|
|
10554
|
-
if (!run) {
|
|
10555
|
-
return { details: void 0, content: [{ type: "text", text: `Unknown runId "${runId}".` }] };
|
|
10556
|
-
}
|
|
10557
|
-
if (run.status !== "running") {
|
|
10558
|
-
return buildCancelResult(run, `Run ${runId} already ${run.status} (no action).`);
|
|
10559
|
-
}
|
|
10560
|
-
run.status = "cancelled";
|
|
10561
|
-
run.consumed = true;
|
|
10562
|
-
try {
|
|
10563
|
-
run.child?.kill("SIGTERM");
|
|
10564
|
-
} catch (err) {
|
|
10565
|
-
debug.event("delegate-cancel-kill-error", { runId, error: String(err) });
|
|
10566
|
-
logError("delegate", { event: "cancel-kill-error", runId, error: String(err) });
|
|
10567
|
-
}
|
|
10568
|
-
delegateStatusWidget.poke();
|
|
10569
|
-
const displayMode = delegateDisplayUsage;
|
|
10570
|
-
return buildCancelResult(run, `Cancelled ${runId} (${run.agent}).`, displayMode);
|
|
10635
|
+
return withUndeliveredNotice(await exec(params));
|
|
10571
10636
|
}
|
|
10572
10637
|
};
|
|
10573
10638
|
}
|
|
@@ -10597,6 +10662,7 @@ async function runDelegate(pi, args, ctx, signal) {
|
|
|
10597
10662
|
const runId = `del_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 6)}`;
|
|
10598
10663
|
debug.event("delegate-spawn", { agent: args.agent, runId, cwd, async: isAsync, useJsonStream, cliArgs });
|
|
10599
10664
|
logInfo("delegate", { event: "spawn", agent: args.agent, runId, cwd, async: isAsync, useJsonStream, mode: ctx.mode, parentDepth });
|
|
10665
|
+
await mkdir2(OUT_DIR, { recursive: true });
|
|
10600
10666
|
const child = spawn(
|
|
10601
10667
|
process.execPath,
|
|
10602
10668
|
[resolvePiCliEntry(process.argv[1] ?? "", process.env, isPiHost(ctx.sessionManager)), ...cliArgs],
|
|
@@ -10628,7 +10694,6 @@ async function runDelegate(pi, args, ctx, signal) {
|
|
|
10628
10694
|
);
|
|
10629
10695
|
const replyFile = join6(OUT_DIR, `${runId}.out`);
|
|
10630
10696
|
const activityFile = join6(OUT_DIR, `${runId}.activity`);
|
|
10631
|
-
await mkdir2(OUT_DIR, { recursive: true });
|
|
10632
10697
|
const replyStream = createWriteStream(replyFile, { flags: "a" });
|
|
10633
10698
|
const activityStream = useJsonStream ? createWriteStream(activityFile, { flags: "a" }) : null;
|
|
10634
10699
|
const endStream = (s) => new Promise((resolve3) => {
|
|
@@ -10721,7 +10786,7 @@ async function runDelegate(pi, args, ctx, signal) {
|
|
|
10721
10786
|
return;
|
|
10722
10787
|
}
|
|
10723
10788
|
const mode = delegateDisplayUsage;
|
|
10724
|
-
const injected = injectResult(pi, args.agent, runId, args.task, code, file2, run.timedOut, run.usage, mode, run.usageReported);
|
|
10789
|
+
const injected = injectResult(pi, args.agent, runId, args.task, code, file2, run.timedOut, run.usage, mode, run.usageReported, run.status === "failed" ? body2 : void 0);
|
|
10725
10790
|
if (run.usage && !run.usageReported && (mode === "separate" || injected)) {
|
|
10726
10791
|
run.usageReported = true;
|
|
10727
10792
|
}
|
|
@@ -10734,7 +10799,7 @@ async function runDelegate(pi, args, ctx, signal) {
|
|
|
10734
10799
|
run.finishedAt = Date.now();
|
|
10735
10800
|
debug.event("delegate-done-error", { runId, error: String(err) });
|
|
10736
10801
|
logError("delegate", { event: "done-error", runId, agent: args.agent, error: String(err) });
|
|
10737
|
-
run
|
|
10802
|
+
notifyTerminalFailure(pi, run, `result persistence error: ${String(err)}`);
|
|
10738
10803
|
delegateStatusWidget.poke();
|
|
10739
10804
|
}
|
|
10740
10805
|
})();
|
|
@@ -10755,7 +10820,8 @@ async function runDelegate(pi, args, ctx, signal) {
|
|
|
10755
10820
|
run.result = { code: null, file: "", body: `spawn error: ${String(err)}` };
|
|
10756
10821
|
debug.event("delegate-spawn-error", { runId, error: String(err) });
|
|
10757
10822
|
logError("delegate", { event: "spawn-error", runId, agent: args.agent, error: String(err) });
|
|
10758
|
-
run.
|
|
10823
|
+
if (run.status === "failed") notifyTerminalFailure(pi, run, `spawn error: ${String(err)}`);
|
|
10824
|
+
else run.waiter?.();
|
|
10759
10825
|
delegateStatusWidget.poke();
|
|
10760
10826
|
}
|
|
10761
10827
|
});
|
|
@@ -10837,7 +10903,7 @@ function waitForChild(child, signal) {
|
|
|
10837
10903
|
});
|
|
10838
10904
|
}
|
|
10839
10905
|
function formatSyncResult(agent, runId, task, r, file) {
|
|
10840
|
-
const status = r.timedOut ? "timed out" : r.code === 0 ? "completed" : "
|
|
10906
|
+
const status = r.timedOut ? "timed out" : r.code === 0 ? "completed" : "FAILED \u26A0\uFE0F";
|
|
10841
10907
|
const header = `Delegate **${agent}** ${status} (runId \`${runId}\`, exit ${r.code ?? "?"}).`;
|
|
10842
10908
|
if (r.code === 0 && !r.timedOut) {
|
|
10843
10909
|
return formatPayload(header, file, task);
|
|
@@ -10845,14 +10911,15 @@ function formatSyncResult(agent, runId, task, r, file) {
|
|
|
10845
10911
|
const body = r.timedOut ? "(timed out)" : r.stderr.trim() || "(no stderr)";
|
|
10846
10912
|
return formatPayload(header, file, task, body);
|
|
10847
10913
|
}
|
|
10848
|
-
function injectResult(pi, agent, runId, task, code, file, timedOut, usage, mode = "separate", usageAlreadyReported) {
|
|
10914
|
+
function injectResult(pi, agent, runId, task, code, file, timedOut, usage, mode = "separate", usageAlreadyReported, body) {
|
|
10849
10915
|
const send = pi.sendUserMessage;
|
|
10850
10916
|
if (typeof send !== "function") {
|
|
10851
10917
|
debug.event("delegate-inject-skipped", { runId, reason: "sendUserMessage unavailable" });
|
|
10852
10918
|
logWarn("delegate", { event: "inject-skipped", runId, reason: "sendUserMessage unavailable" });
|
|
10853
10919
|
return false;
|
|
10854
10920
|
}
|
|
10855
|
-
const
|
|
10921
|
+
const failed = code !== 0;
|
|
10922
|
+
const status = failed ? "FAILED \u26A0\uFE0F" : "completed";
|
|
10856
10923
|
const remaining = Array.from(runs.values()).filter((r) => r.status === "running").length;
|
|
10857
10924
|
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.";
|
|
10858
10925
|
const timeoutNote = timedOut ? ` (timed out: ${timedOut})` : "";
|
|
@@ -10886,10 +10953,15 @@ Tokens: ${totalUsage.input.toLocaleString()} in, ${totalUsage.output.toLocaleStr
|
|
|
10886
10953
|
}
|
|
10887
10954
|
if (lines.length) usageNote = ` Usage: ${lines.join(", ")}.`;
|
|
10888
10955
|
}
|
|
10889
|
-
const
|
|
10890
|
-
const
|
|
10956
|
+
const closing = failed ? "This delegate did NOT complete its task \u2014 its result is missing from your work. Read the error excerpt (and the result file if present), then decide whether to re-dispatch the task before wrapping up. This is an automated system notification, NOT a user message." : "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.";
|
|
10957
|
+
const header = `[acp_delegate ${status}] **${agent}** (runId \`${runId}\`, exit ${code ?? "?"})${timeoutNote}${remainingLine}${usageNote} ${closing}`;
|
|
10958
|
+
const { text: recoveryText, covered } = buildRecoveryNotice(Array.from(runs.values()), runId);
|
|
10959
|
+
const text = formatPayload(header, file, task, failed ? body : void 0) + (recoveryText ? `
|
|
10960
|
+
|
|
10961
|
+
${recoveryText}` : "");
|
|
10891
10962
|
try {
|
|
10892
10963
|
send.call(pi, text, { deliverAs: "followUp" });
|
|
10964
|
+
for (const r of covered) r.injected = true;
|
|
10893
10965
|
return true;
|
|
10894
10966
|
} catch (err) {
|
|
10895
10967
|
debug.event("delegate-inject-error", { runId, error: String(err) });
|
|
@@ -10897,6 +10969,29 @@ Tokens: ${totalUsage.input.toLocaleString()} in, ${totalUsage.output.toLocaleStr
|
|
|
10897
10969
|
return false;
|
|
10898
10970
|
}
|
|
10899
10971
|
}
|
|
10972
|
+
function notifyTerminalFailure(pi, run, body) {
|
|
10973
|
+
const hadWaiter = run.waiter !== void 0;
|
|
10974
|
+
run.waiter?.();
|
|
10975
|
+
if (hadWaiter || run.consumed) return;
|
|
10976
|
+
const mode = delegateDisplayUsage;
|
|
10977
|
+
const injected = injectResult(
|
|
10978
|
+
pi,
|
|
10979
|
+
run.agent,
|
|
10980
|
+
run.runId,
|
|
10981
|
+
run.task,
|
|
10982
|
+
run.result?.code ?? null,
|
|
10983
|
+
run.result?.file ?? "",
|
|
10984
|
+
run.timedOut,
|
|
10985
|
+
run.usage,
|
|
10986
|
+
mode,
|
|
10987
|
+
run.usageReported,
|
|
10988
|
+
body
|
|
10989
|
+
);
|
|
10990
|
+
if (run.usage && !run.usageReported && (mode === "separate" || injected)) {
|
|
10991
|
+
run.usageReported = true;
|
|
10992
|
+
}
|
|
10993
|
+
run.injected = injected;
|
|
10994
|
+
}
|
|
10900
10995
|
function formatPayload(header, file, task, body) {
|
|
10901
10996
|
const lines = [header, "", `Task: ${truncate2(task, 160)}`];
|
|
10902
10997
|
if (file) {
|
|
@@ -10970,13 +11065,13 @@ function makeStatusTool(runtime) {
|
|
|
10970
11065
|
};
|
|
10971
11066
|
}
|
|
10972
11067
|
async function handleStatus(args, runtime, ctx) {
|
|
10973
|
-
const { state, coreMessages } = await runtime.stateFor(ctx);
|
|
11068
|
+
const { state, coreMessages, entries } = await runtime.stateFor(ctx);
|
|
10974
11069
|
const config = runtime.configFor(ctx);
|
|
10975
11070
|
const coveredIds = collectCoveredMessageIds(state);
|
|
10976
11071
|
const modelId = ctx.model?.id ?? "default";
|
|
10977
11072
|
const systemPromptText = getSystemPromptText(ctx);
|
|
10978
11073
|
const systemPromptTokens = systemPromptText ? defaultCountTokens(systemPromptText) : 0;
|
|
10979
|
-
const sentTokens = estimateTokens(coreMessages, coveredIds) + systemPromptTokens;
|
|
11074
|
+
const sentTokens = estimateTokens(coreMessages, coveredIds, collectImageTokens(entries, modelSupportsImages(ctx.model))) + systemPromptTokens;
|
|
10980
11075
|
const turn = runtime.core.processTurn({
|
|
10981
11076
|
messages: coreMessages,
|
|
10982
11077
|
state,
|
|
@@ -11460,12 +11555,14 @@ async function statusReport(runtime, ctx) {
|
|
|
11460
11555
|
const realUsage = ctx.getContextUsage?.();
|
|
11461
11556
|
const systemPromptText = getSystemPromptText(ctx);
|
|
11462
11557
|
const systemPromptTokens = systemPromptText ? defaultCountTokens(systemPromptText) : 0;
|
|
11463
|
-
const
|
|
11558
|
+
const imageTokens = collectImageTokens(entries, modelSupportsImages(ctx.model));
|
|
11559
|
+
const imageTokensTotal = [...imageTokens.values()].reduce((a, b) => a + b, 0);
|
|
11560
|
+
const sessionTokens = realUsage?.tokens && realUsage.tokens > 0 ? realUsage.tokens : defaultCountTokens(coreMessages.map((m) => m.text ?? "").join("\n")) + imageTokensTotal;
|
|
11464
11561
|
const coveredIds = collectCoveredMessageIds(state);
|
|
11465
11562
|
const modelId = ctx.model?.id ?? "default";
|
|
11466
|
-
const sentTokens = estimateTokens(coreMessages, coveredIds) + systemPromptTokens;
|
|
11563
|
+
const sentTokens = estimateTokens(coreMessages, coveredIds, imageTokens) + systemPromptTokens;
|
|
11467
11564
|
const turn = runtime.core.processTurn({ messages: coreMessages, state, config, tokenCount: calibrateTokens(sentTokens, runtime.density.densityFor(modelId)) });
|
|
11468
|
-
const versionStr = "0.1.
|
|
11565
|
+
const versionStr = "0.1.49" ? `billion-context-pi@${"0.1.49"}` : void 0;
|
|
11469
11566
|
let text = buildStatusPanel({
|
|
11470
11567
|
version: versionStr,
|
|
11471
11568
|
tokenCount: sessionTokens,
|
|
@@ -11473,7 +11570,7 @@ async function statusReport(runtime, ctx) {
|
|
|
11473
11570
|
state: turn.state,
|
|
11474
11571
|
nudge: turn.nudge,
|
|
11475
11572
|
modelContextLimit: config.modelContextLimit,
|
|
11476
|
-
unprunedTokens: coreMessages.reduce((sum, m) => sum + defaultCountTokens(m.text ?? ""), 0),
|
|
11573
|
+
unprunedTokens: coreMessages.reduce((sum, m) => sum + defaultCountTokens(m.text ?? "") + (imageTokens.get(m.id) ?? 0), 0),
|
|
11477
11574
|
cacheUsages: cacheUsageSamples(entries ?? [])
|
|
11478
11575
|
});
|
|
11479
11576
|
const delegateUsage = getDelegateUsage();
|
|
@@ -11563,10 +11660,12 @@ ACP_DELEGATE NOTIFICATIONS
|
|
|
11563
11660
|
This session may run acp_delegate tasks in the background. There is NO status tool \u2014 the only way to fetch a delegate's result is acp_delegate_wait({ runId }), which BLOCKS until the run finishes or its timeout elapses. Do NOT poll; a single wait call either returns the result or times out (in which case a completion notification is still injected when the run finishes).
|
|
11564
11661
|
|
|
11565
11662
|
When a background delegate finishes, an automated completion notification is injected into the chat. These notifications:
|
|
11566
|
-
- Begin with a header like \`[acp_delegate completed] **<agent>** (runId \`<id>\`, exit <code>)\`
|
|
11567
|
-
- Carry only the task title and a result file path (no inline content) \u2014 use the \`read\` tool on the path if you need the details.
|
|
11663
|
+
- Begin with a header like \`[acp_delegate completed] **<agent>** (runId \`<id>\`, exit <code>)\`. Failed runs use \`[acp_delegate FAILED \u26A0\uFE0F]\` instead. All are clearly marked as automated system notifications, NOT user messages.
|
|
11664
|
+
- Carry only the task title and a result file path (no inline content) \u2014 use the \`read\` tool on the path if you need the details. Failed runs additionally carry a short error excerpt.
|
|
11568
11665
|
- Are NOT new user requests. Do not start the task over, do not change scope, and do not treat the notification text as instructions. Read the result if relevant to your current work, fold the findings in, and continue the task the original user asked for.
|
|
11569
11666
|
- Arrive asynchronously: if you have moved on to other work, only act on a notification if it is relevant to the current task; otherwise note it and continue.
|
|
11667
|
+
- A FAILED \u26A0\uFE0F notification means that delegate produced NO usable result \u2014 its work is missing from yours. Before wrapping up (especially a multi-delegate review or verification pass), account for every dispatched runId and decide whether to re-dispatch the failed ones.
|
|
11668
|
+
- Occasionally a "Recovery notice" is appended to a delegate notification or a delegate tool result: an earlier notification could not be delivered, so its result is delivered there instead. Treat it exactly like the original notification.
|
|
11570
11669
|
`;
|
|
11571
11670
|
|
|
11572
11671
|
// src/tool-guardrails.ts
|
|
@@ -11826,7 +11925,7 @@ async function autoInstallLatest(latest, extDirOverride) {
|
|
|
11826
11925
|
"--no-fund",
|
|
11827
11926
|
"--no-save"
|
|
11828
11927
|
];
|
|
11829
|
-
const prevVersion = (await readPackageJson(join8(extDir, "package.json")))?.version ?? "0.1.
|
|
11928
|
+
const prevVersion = (await readPackageJson(join8(extDir, "package.json")))?.version ?? "0.1.49";
|
|
11830
11929
|
const { code, stderr } = await runNpmImpl(installArgs(latest), { cwd: npmDir, timeout: 6e4 });
|
|
11831
11930
|
if (code !== 0) {
|
|
11832
11931
|
logWarn("update", {
|
|
@@ -11839,7 +11938,7 @@ async function autoInstallLatest(latest, extDirOverride) {
|
|
|
11839
11938
|
}
|
|
11840
11939
|
const verify = await verifyInstall(npmDir, latest);
|
|
11841
11940
|
if (!verify.ok) {
|
|
11842
|
-
const rollbackTo = SEMVER_RE.test(prevVersion) ? prevVersion : "0.1.
|
|
11941
|
+
const rollbackTo = SEMVER_RE.test(prevVersion) ? prevVersion : "0.1.49";
|
|
11843
11942
|
logWarn("update", { event: "auto-install-verify-failed", latest, reason: verify.reason, rollbackTo });
|
|
11844
11943
|
const rb = await runNpmImpl(installArgs(rollbackTo), { cwd: npmDir, timeout: 6e4 });
|
|
11845
11944
|
logInfo("update", { event: "rollback", from: latest, to: rollbackTo, ok: rb.code === 0 });
|
|
@@ -11900,7 +11999,7 @@ async function checkForUpdate(autoUpdate, notify) {
|
|
|
11900
11999
|
const runtimeVersion = await getRuntimeVersion();
|
|
11901
12000
|
const latest = await fetchLatestVersion();
|
|
11902
12001
|
if (!latest) return;
|
|
11903
|
-
const current = runtimeVersion ?? "0.1.
|
|
12002
|
+
const current = runtimeVersion ?? "0.1.49";
|
|
11904
12003
|
const hasUpdate = isNewer(latest, current);
|
|
11905
12004
|
debug.event("update-check", {
|
|
11906
12005
|
current,
|
|
@@ -11979,7 +12078,7 @@ function wireSessionLifecycle(pi, runtime) {
|
|
|
11979
12078
|
const sid = ctx.sessionManager.getSessionId();
|
|
11980
12079
|
runtime.clearSessionTracking(sid);
|
|
11981
12080
|
const modelInfo = ctx.model;
|
|
11982
|
-
logInfo("session", { event: "start", sid, cwd: ctx.cwd, debug: runtime.adapter.debug ?? null, version: true ? "0.1.
|
|
12081
|
+
logInfo("session", { event: "start", sid, cwd: ctx.cwd, debug: runtime.adapter.debug ?? null, version: true ? "0.1.49" : null, model: modelInfo?.id ?? null, modelApi: modelInfo?.api ?? null, contextWindow: modelInfo?.contextWindow ?? null });
|
|
11983
12082
|
try {
|
|
11984
12083
|
await runtime.reloadConfig(ctx.cwd);
|
|
11985
12084
|
setDelegateDisplayUsage(resolveDelegate(runtime.adapter).displayUsage);
|
|
@@ -12038,7 +12137,7 @@ function wireContextTransform(pi, runtime) {
|
|
|
12038
12137
|
const realUsage = ctx.getContextUsage?.();
|
|
12039
12138
|
const systemPromptText = getSystemPromptText(ctx);
|
|
12040
12139
|
const systemPromptTokens = systemPromptText ? defaultCountTokens(systemPromptText) : 0;
|
|
12041
|
-
const sentTokens = estimateTokens(coreMessages, coveredIds) + systemPromptTokens;
|
|
12140
|
+
const sentTokens = estimateTokens(coreMessages, coveredIds, collectImageTokens(entries, modelSupportsImages(ctx.model))) + systemPromptTokens;
|
|
12042
12141
|
let tokenCount = calibrateTokens(sentTokens, runtime.density.densityFor(modelId));
|
|
12043
12142
|
if (ov.armed && config.modelContextLimit > 0) {
|
|
12044
12143
|
ov.armed = false;
|