Skip to content

Commit 53a23c0

Browse files
committed
improve background functionality
1 parent 7595d98 commit 53a23c0

5 files changed

Lines changed: 50 additions & 13 deletions

File tree

pi/AGENTS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
## Tools
2020

21+
- Use the normal bash tool unless a command is expected to run indefinitely, such as a dev server.
2122
- Run commands needing AWS auth through `aws-sso-exec` (for example, `aws-sso-exec aws sts get-caller-identity`). It uses native AWS SSO and opens a browser for the user to auth only when required.
2223
- Use Colima instead of Docker.
2324
- Use the `ffgrep` tool or `rg` instead of `grep`

pi/extensions/background/subagents.ts

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ import { Markdown, Text } from "@earendil-works/pi-tui";
2929
import { Type } from "typebox";
3030
import { formatElapsed, latestText, REASONING_EFFORTS, type SubagentSnapshot } from "./src/domain.ts";
3131
import { formatContextUtilization } from "../shared/context-utilization.ts";
32-
import { registerTransientSegment } from "../shared/footer-segments.ts";
32+
import { registerBackgroundCost, registerTransientSegment } from "../shared/footer-segments.ts";
3333
import { resolveStandaloneChildProjectTrust } from "../shared/child-session.ts";
3434
import { SubagentManager, type SubagentManagerShape } from "./src/manager.ts";
3535
import {
@@ -86,6 +86,7 @@ export function setupSubagents(pi: ExtensionAPI, background: BackgroundHub) {
8686
let sessionContext: ExtensionContext | undefined;
8787
let ui: ExtensionUIContext | undefined;
8888
let unsubStatus: (() => void) | undefined;
89+
let costKey: string | undefined;
8990
const resultDelivery = createDeferredResultDelivery<SubagentSnapshot>();
9091

9192
const getRuntime = () => (runtime ??= createSubagentRuntime());
@@ -106,17 +107,21 @@ export function setupSubagents(pi: ExtensionAPI, background: BackgroundHub) {
106107
};
107108

108109
const updateStatus = (manager: SubagentManagerShape) => {
109-
if (!ui) return;
110110
const subs = manager.view.list();
111+
if (costKey)
112+
registerBackgroundCost(
113+
costKey,
114+
subs.reduce((total, snap) => total + snap.cost, 0),
115+
);
116+
if (!ui) return;
111117
if (subs.length === 0) {
112118
registerTransientSegment("subagents", null);
113119
return;
114120
}
115121
const running = subs.filter((snap) => snap.status === "running").length;
116122
const failed = subs.filter((snap) => snap.status === "error").length;
117123
const done = subs.length - running - failed;
118-
const cost = subs.reduce((total, snap) => total + snap.cost, 0);
119-
const parts: string[] = [`$${cost.toFixed(2)} sub`];
124+
const parts: string[] = [];
120125
if (running > 0) parts.push(`${running} running`);
121126
if (done > 0) parts.push(`${done} done`);
122127
if (failed > 0) parts.push(`${failed} failed`);
@@ -163,6 +168,7 @@ export function setupSubagents(pi: ExtensionAPI, background: BackgroundHub) {
163168

164169
pi.on("session_start", (_event, ctx) => {
165170
sessionContext = ctx;
171+
costKey = `subagents:${ctx.sessionManager.getSessionId()}`;
166172
if (ctx.hasUI) ui = ctx.ui;
167173
unregisterProvider?.();
168174
unregisterProvider = background.registerProvider("subagents", {
@@ -203,6 +209,8 @@ export function setupSubagents(pi: ExtensionAPI, background: BackgroundHub) {
203209
unsubStatus?.();
204210
unsubStatus = undefined;
205211
registerTransientSegment("subagents", null);
212+
if (costKey) registerBackgroundCost(costKey, null);
213+
costKey = undefined;
206214
const closing = runtime;
207215
runtime = undefined;
208216
managerPromise = undefined;
@@ -246,11 +254,14 @@ export function setupSubagents(pi: ExtensionAPI, background: BackgroundHub) {
246254
),
247255
}),
248256
renderCall(args, theme) {
249-
return new Text(
250-
theme.fg("toolTitle", "subagent_spawn") + (args.name ? " " + theme.fg("dim", String(args.name)) : ""),
251-
0,
252-
0,
253-
);
257+
const lines = [
258+
theme.fg("toolTitle", "subagent_spawn") + (args.name ? " " + theme.fg("dim", args.name) : ""),
259+
...(args.prompt ? [theme.fg("text", args.prompt)] : []),
260+
...(args.working_dir ? [theme.fg("muted", `cwd: ${args.working_dir}`)] : []),
261+
...(args.model ? [theme.fg("muted", `model: ${args.model}`)] : []),
262+
...(args.reasoning_effort ? [theme.fg("muted", `effort: ${args.reasoning_effort}`)] : []),
263+
];
264+
return new Text(lines.join("\n"), 0, 0);
254265
},
255266
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
256267
const manager = await getManager();

pi/extensions/background/terminals.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ import {
2828
type Theme,
2929
} from "@earendil-works/pi-coding-agent";
3030
import type { Component, Focusable, TUI } from "@earendil-works/pi-tui";
31-
import { truncateToWidth } from "@earendil-works/pi-tui";
31+
import { Text, truncateToWidth } from "@earendil-works/pi-tui";
3232
import { Type } from "typebox";
3333
import { registerTransientSegment } from "../shared/footer-segments.ts";
3434
import { killProcessTree } from "../shared/process-tree.ts";
@@ -461,6 +461,14 @@ export function setupTerminals(pi: ExtensionAPI, background: BackgroundHub) {
461461
title: Type.String({ description: "Short human-readable label for this terminal, shown in listings" }),
462462
working_dir: Type.Optional(Type.String({ description: "Working directory (default: current directory)" })),
463463
}),
464+
renderCall(args, theme) {
465+
const lines = [
466+
theme.fg("toolTitle", "terminal_run") + (args.title ? " " + theme.fg("dim", args.title) : ""),
467+
...(args.command ? [theme.fg("text", `$ ${args.command}`)] : []),
468+
...(args.working_dir ? [theme.fg("muted", `cwd: ${args.working_dir}`)] : []),
469+
];
470+
return new Text(lines.join("\n"), 0, 0);
471+
},
464472
async execute(_id, params, signal, _onUpdate, ctx) {
465473
const cwd = path.resolve(ctx.cwd, params.working_dir ?? ".");
466474
if (!fs.existsSync(cwd) || !fs.statSync(cwd).isDirectory()) {

pi/extensions/powerline.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-a
2020

2121
type ThinkingLevel = "off" | "minimal" | "low" | "medium" | "high" | "xhigh" | "max";
2222
import { visibleWidth, truncateToWidth } from "@earendil-works/pi-tui";
23-
import { getTransientSegments, setTransientOnChange } from "./shared/footer-segments.ts";
23+
import { getBackgroundCost, getTransientSegments, setTransientOnChange } from "./shared/footer-segments.ts";
2424

2525
// ── Powerline characters ────────────────────────────────────────────────────
2626
const ARROW_RIGHT = "\uE0B4"; // right half-circle
@@ -332,9 +332,9 @@ export default function powerlineExtension(pi: ExtensionAPI): void {
332332
right.push({ text: `󰥔 ${elapsed} ago`, bg: C.panel, fg: C.dim });
333333
}
334334

335-
// Session cost (always shown)
335+
// Session cost, including background subagents (always shown)
336336
right.push({
337-
text: formatCost(sessionCost),
337+
text: formatCost(sessionCost + getBackgroundCost()),
338338
bg: C.panelAlt,
339339
fg: C.dim,
340340
});

pi/extensions/shared/footer-segments.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ export type TransientSegment = {
2727

2828
// Module-level singleton: one shared instance per process.
2929
const registry = new Map<string, TransientSegment>();
30+
const backgroundCosts = new Map<string, number>();
3031
let onChangeCallback: (() => void) | undefined;
3132

3233
/**
@@ -48,6 +49,22 @@ export function getTransientSegments(): ReadonlyMap<string, TransientSegment> {
4849
return registry;
4950
}
5051

52+
/** Register or clear a background task cost for inclusion in the main session cost. */
53+
export function registerBackgroundCost(key: string, cost: number | null): void {
54+
if (cost === null) {
55+
if (!backgroundCosts.delete(key)) return;
56+
} else {
57+
backgroundCosts.set(key, cost);
58+
}
59+
onChangeCallback?.();
60+
}
61+
62+
export function getBackgroundCost(): number {
63+
let total = 0;
64+
for (const cost of backgroundCosts.values()) total += cost;
65+
return total;
66+
}
67+
5168
/**
5269
* Called once by the footer extension. Returns a cleanup function.
5370
* Only one callback is active at a time (the footer owns this).

0 commit comments

Comments
 (0)