pi-subagents-lite 1.4.5 → 1.4.7
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/package.json +1 -1
- package/src/agents/agent-manager.ts +20 -7
- package/src/agents/agent-runner.ts +3 -2
- package/src/agents/tool-execution.ts +13 -3
- package/src/agents/usage.ts +7 -0
- package/src/config/config-io.ts +1 -0
- package/src/config/config-store.ts +7 -0
- package/src/config/types.ts +1 -0
- package/src/spawn/spawn-coordinator.ts +8 -7
- package/src/status-note.ts +14 -4
- package/src/types.ts +8 -2
- package/src/ui/menu/menu-running-agents.ts +2 -2
- package/src/ui/menu/menu-widget-settings.ts +2 -0
package/package.json
CHANGED
|
@@ -8,17 +8,19 @@ import { randomUUID } from "node:crypto";
|
|
|
8
8
|
import type { AgentSession, ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
9
9
|
import { runAgent } from "./agent-runner.js";
|
|
10
10
|
import { AgentOutputLog } from "./output-file.js";
|
|
11
|
+
import { getStore } from "../shell.js";
|
|
11
12
|
import {
|
|
12
13
|
type AgentRecord,
|
|
13
14
|
type AgentStatus,
|
|
14
15
|
type CompactionInfo,
|
|
15
16
|
type RunCallbacks,
|
|
17
|
+
type StopInitiator,
|
|
16
18
|
SHORT_ID_LENGTH,
|
|
17
19
|
type SpawnConfig,
|
|
18
20
|
type ToolActivity,
|
|
19
21
|
} from "../types.js";
|
|
20
22
|
import type { SubagentType } from "./types.js";
|
|
21
|
-
import { addUsage, getLifetimeTotal, getSessionContextPercent, type
|
|
23
|
+
import { addUsage, getLifetimeTotal, getSessionContextPercent, type AgentUsage } from "./usage.js";
|
|
22
24
|
import { errorMessage } from "../utils.js";
|
|
23
25
|
|
|
24
26
|
/** How often to check for expired agent records (milliseconds). */
|
|
@@ -266,7 +268,7 @@ export class AgentManager {
|
|
|
266
268
|
|
|
267
269
|
// Wire parent abort signal to stop the subagent when the parent is interrupted
|
|
268
270
|
if (options.signal) {
|
|
269
|
-
options.signal.addEventListener("abort", () => this.abort(id), { once: true });
|
|
271
|
+
options.signal.addEventListener("abort", () => this.abort(id, "agent"), { once: true });
|
|
270
272
|
}
|
|
271
273
|
|
|
272
274
|
const promise = runAgent(ctx, type, prompt, {
|
|
@@ -373,7 +375,7 @@ export class AgentManager {
|
|
|
373
375
|
options?: Pick<SpawnOptions, "onToolActivity" | "onAssistantUsage" | "onCompaction">,
|
|
374
376
|
): {
|
|
375
377
|
onToolActivity: (activity: ToolActivity) => void;
|
|
376
|
-
onAssistantUsage: (usage:
|
|
378
|
+
onAssistantUsage: (usage: AgentUsage) => void;
|
|
377
379
|
onCompaction: (info: CompactionInfo) => void;
|
|
378
380
|
} {
|
|
379
381
|
return {
|
|
@@ -382,7 +384,17 @@ export class AgentManager {
|
|
|
382
384
|
options?.onToolActivity?.(activity);
|
|
383
385
|
},
|
|
384
386
|
onAssistantUsage: (usage) => {
|
|
385
|
-
|
|
387
|
+
// vLLM doesn't report cache hits, so usage.input is full prompt_tokens.
|
|
388
|
+
// Estimate new tokens as delta from previous message's input.
|
|
389
|
+
const deltaEnabled = getStore().agent.deltaInputTokens;
|
|
390
|
+
const cacheRead = usage.cacheRead;
|
|
391
|
+
let inputDelta = usage.input;
|
|
392
|
+
if (deltaEnabled && cacheRead === 0 && record.stats.prevInputTokens != null && usage.input > record.stats.prevInputTokens) {
|
|
393
|
+
inputDelta = usage.input - record.stats.prevInputTokens;
|
|
394
|
+
}
|
|
395
|
+
record.stats.prevInputTokens = usage.input;
|
|
396
|
+
|
|
397
|
+
addUsage(record.stats.lifetimeUsage, { ...usage, input: inputDelta });
|
|
386
398
|
options?.onAssistantUsage?.(usage);
|
|
387
399
|
},
|
|
388
400
|
onCompaction: (info) => {
|
|
@@ -454,18 +466,18 @@ export class AgentManager {
|
|
|
454
466
|
);
|
|
455
467
|
}
|
|
456
468
|
|
|
457
|
-
abort(id: string): boolean {
|
|
469
|
+
abort(id: string, stoppedBy?: StopInitiator): boolean {
|
|
458
470
|
const record = this.agents.get(id);
|
|
459
471
|
if (!record) return false;
|
|
460
472
|
|
|
461
|
-
return this.stopAgent(record);
|
|
473
|
+
return this.stopAgent(record, stoppedBy);
|
|
462
474
|
}
|
|
463
475
|
|
|
464
476
|
/**
|
|
465
477
|
* Stop an agent by aborting its session or removing it from the queue.
|
|
466
478
|
* Returns true if the agent was stopped, false if it wasn't running/queued.
|
|
467
479
|
*/
|
|
468
|
-
private stopAgent(record: AgentRecord): boolean {
|
|
480
|
+
private stopAgent(record: AgentRecord, stoppedBy?: StopInitiator): boolean {
|
|
469
481
|
if (record.lifecycle.status === "queued") {
|
|
470
482
|
this.queue = this.queue.filter(q => q.id !== record.id);
|
|
471
483
|
} else if (record.lifecycle.status !== "running") {
|
|
@@ -474,6 +486,7 @@ export class AgentManager {
|
|
|
474
486
|
record.execution.abortController?.abort();
|
|
475
487
|
}
|
|
476
488
|
record.lifecycle.status = "stopped";
|
|
489
|
+
record.lifecycle.stoppedBy = stoppedBy;
|
|
477
490
|
record.lifecycle.completedAt = Date.now();
|
|
478
491
|
return true;
|
|
479
492
|
}
|
|
@@ -20,7 +20,7 @@ import {
|
|
|
20
20
|
} from "@earendil-works/pi-coding-agent";
|
|
21
21
|
import { getAgentConfig, getConfig, getToolNamesForType, resolveVisibleTools } from "./agent-types.js";
|
|
22
22
|
import { extractText } from "../prompt/context.js";
|
|
23
|
-
import type {
|
|
23
|
+
import type { AgentUsage } from "./usage.js";
|
|
24
24
|
import { findModelInRegistry, GIT_EXEC_TIMEOUT_MS } from "../utils.js";
|
|
25
25
|
import { DEFAULT_AGENTS } from "./default-agents.js";
|
|
26
26
|
import { buildAgentPrompt, type PromptExtras } from "../prompt/prompts.js";
|
|
@@ -106,13 +106,14 @@ function forwardAbortSignal(session: AgentSession, signal?: AbortSignal): () =>
|
|
|
106
106
|
* assistant messages at runtime, but this shape isn't reflected in the
|
|
107
107
|
* AgentSessionEvent public types.
|
|
108
108
|
*/
|
|
109
|
-
function usageFromAssistantMessage(msg: Record<string, unknown>):
|
|
109
|
+
function usageFromAssistantMessage(msg: Record<string, unknown>): AgentUsage | undefined {
|
|
110
110
|
const usage = msg.usage as Record<string, unknown> | undefined;
|
|
111
111
|
if (!usage) return undefined;
|
|
112
112
|
return {
|
|
113
113
|
input: (usage.input as number) ?? 0,
|
|
114
114
|
output: (usage.output as number) ?? 0,
|
|
115
115
|
cacheWrite: (usage.cacheWrite as number) ?? 0,
|
|
116
|
+
cacheRead: (usage.cacheRead as number) ?? 0,
|
|
116
117
|
cost: ((usage.cost as Record<string, unknown>)?.total as number) ?? 0,
|
|
117
118
|
};
|
|
118
119
|
}
|
|
@@ -88,6 +88,17 @@ export function buildAgentDetails(
|
|
|
88
88
|
return details;
|
|
89
89
|
}
|
|
90
90
|
|
|
91
|
+
/**
|
|
92
|
+
* Result text plus status note, for display.
|
|
93
|
+
*
|
|
94
|
+
* Shared by the foreground tool result and the subagent-result nudge so both
|
|
95
|
+
* callers stay in sync on the nullish default and separator handling — they
|
|
96
|
+
* have diverged before. getStatusNote owns the leading separator.
|
|
97
|
+
*/
|
|
98
|
+
export function formatResultContent(record: AgentRecord): string {
|
|
99
|
+
return (record.result ?? "") + getStatusNote(record.lifecycle);
|
|
100
|
+
}
|
|
101
|
+
|
|
91
102
|
// ============================================================================
|
|
92
103
|
// Tool execute handlers
|
|
93
104
|
// ============================================================================
|
|
@@ -186,8 +197,7 @@ export async function executeAgentTool(
|
|
|
186
197
|
return errorResult(`Agent failed: ${record.error || "unknown error"}`, details);
|
|
187
198
|
}
|
|
188
199
|
|
|
189
|
-
|
|
190
|
-
return successResult((record.result ?? "") + statusNote, details);
|
|
200
|
+
return successResult(formatResultContent(record), details);
|
|
191
201
|
}
|
|
192
202
|
|
|
193
203
|
// ============================================================================
|
|
@@ -244,7 +254,7 @@ export async function executeStopAgentTool(
|
|
|
244
254
|
}
|
|
245
255
|
|
|
246
256
|
// Attempt to stop the running/queued agent
|
|
247
|
-
if (getManager()!.abort(agentId)) {
|
|
257
|
+
if (getManager()!.abort(agentId, "agent")) {
|
|
248
258
|
return successResult(`Stopped agent ${agentId.slice(0, SHORT_ID_LENGTH)}`);
|
|
249
259
|
}
|
|
250
260
|
|
package/src/agents/usage.ts
CHANGED
|
@@ -9,6 +9,13 @@
|
|
|
9
9
|
*/
|
|
10
10
|
export type LifetimeUsage = { input: number; output: number; cacheWrite: number; cost: number };
|
|
11
11
|
|
|
12
|
+
/**
|
|
13
|
+
* A single per-turn usage event as emitted upstream. Adds `cacheRead`, which
|
|
14
|
+
* LifetimeUsage omits from totals (see issue #38). Used to estimate input
|
|
15
|
+
* deltas for providers like vLLM that don't report cache hits.
|
|
16
|
+
*/
|
|
17
|
+
export type AgentUsage = LifetimeUsage & { cacheRead: number };
|
|
18
|
+
|
|
12
19
|
/** Sum of lifetime usage components (including cost), or 0 if undefined. */
|
|
13
20
|
export function getLifetimeTotal(u?: LifetimeUsage): number {
|
|
14
21
|
return u ? u.input + u.output + u.cacheWrite + u.cost : 0;
|
package/src/config/config-io.ts
CHANGED
|
@@ -74,6 +74,8 @@ export interface ResolvedAgentSettings {
|
|
|
74
74
|
readonly showContext: boolean;
|
|
75
75
|
/** Whether to show elapsed time in widget stats line. */
|
|
76
76
|
readonly showTime: boolean;
|
|
77
|
+
/** Whether to estimate input token delta for vLLM (no cache reporting). */
|
|
78
|
+
readonly deltaInputTokens: boolean;
|
|
77
79
|
/** Buffer size for streaming thinking blocks to output file. 0 = disabled. */
|
|
78
80
|
readonly outputThinkingBufferSize: number;
|
|
79
81
|
}
|
|
@@ -133,6 +135,7 @@ export class ConfigStore {
|
|
|
133
135
|
showOutput: a.showOutput !== false,
|
|
134
136
|
showContext: a.showContext !== false,
|
|
135
137
|
showTime: a.showTime !== false,
|
|
138
|
+
deltaInputTokens: a.deltaInputTokens !== false,
|
|
136
139
|
outputThinkingBufferSize: a.outputThinkingBufferSize ?? 0,
|
|
137
140
|
};
|
|
138
141
|
}
|
|
@@ -265,6 +268,10 @@ export class ConfigStore {
|
|
|
265
268
|
setShowOutput: (enabled: boolean) => this.setAgentVisibility("showOutput", enabled),
|
|
266
269
|
setShowContext: (enabled: boolean) => this.setAgentVisibility("showContext", enabled),
|
|
267
270
|
setShowTime: (enabled: boolean) => this.setAgentVisibility("showTime", enabled),
|
|
271
|
+
setDeltaInputTokens: (enabled: boolean): void => {
|
|
272
|
+
this.config.agent.deltaInputTokens = enabled;
|
|
273
|
+
this.persist();
|
|
274
|
+
},
|
|
268
275
|
setOutputThinkingBufferSize: (size: number): void => {
|
|
269
276
|
this.config.agent.outputThinkingBufferSize = size;
|
|
270
277
|
this.persist();
|
package/src/config/types.ts
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
|
-
import { getStatusNote } from "../status-note.js";
|
|
2
1
|
import { getPiInstance, getSessionCtx, getWidget } from "../shell.js";
|
|
2
|
+
import { SHORT_ID_LENGTH } from "../types.js";
|
|
3
|
+
|
|
4
|
+
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
5
|
+
import type { AgentRecord, SpawnConfig, ToolActivity } from "../types.js";
|
|
6
|
+
import type { AgentManager, SpawnOptions } from "../agents/agent-manager.js";
|
|
7
|
+
import { buildAgentDetails, formatResultContent } from "../agents/tool-execution.js";
|
|
8
|
+
|
|
3
9
|
/**
|
|
4
10
|
* spawn-coordinator.ts — Spawn-and-track coordination for subagents.
|
|
5
11
|
*
|
|
@@ -11,11 +17,6 @@ import { getPiInstance, getSessionCtx, getWidget } from "../shell.js";
|
|
|
11
17
|
* D6 (Nudge owned here), D2 (peers with AgentManager).
|
|
12
18
|
*/
|
|
13
19
|
|
|
14
|
-
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
15
|
-
import type { AgentRecord, SpawnConfig, ToolActivity } from "../types.js";
|
|
16
|
-
import type { AgentManager, SpawnOptions } from "../agents/agent-manager.js";
|
|
17
|
-
import { buildAgentDetails } from "../agents/tool-execution.js";
|
|
18
|
-
|
|
19
20
|
// ============================================================================
|
|
20
21
|
// Types
|
|
21
22
|
// ============================================================================
|
|
@@ -234,7 +235,7 @@ export class SpawnCoordinator {
|
|
|
234
235
|
pi.sendMessage(
|
|
235
236
|
{
|
|
236
237
|
customType: "subagent-result",
|
|
237
|
-
content: `[Subagent "${record.display.type}" ${record.lifecycle.status}]\n\n${
|
|
238
|
+
content: `[Subagent "${record.display.type}" ${record.id.slice(0, SHORT_ID_LENGTH)} ${record.lifecycle.status}]\n\n${formatResultContent(record)}`,
|
|
238
239
|
details,
|
|
239
240
|
display: true,
|
|
240
241
|
},
|
package/src/status-note.ts
CHANGED
|
@@ -1,10 +1,20 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import type { AgentLifecycle, AgentStatus, StopInitiator } from "./types.js";
|
|
2
|
+
|
|
3
|
+
const STATUS_NOTES: Partial<Record<AgentStatus, string>> = {
|
|
3
4
|
aborted: "hit the turn limit before completion; output may be incomplete",
|
|
4
5
|
turn_limited: "wrapped up at the turn limit — output may be partial",
|
|
5
6
|
};
|
|
6
7
|
|
|
7
|
-
|
|
8
|
-
|
|
8
|
+
const STOP_NOTES: Record<StopInitiator, string> = {
|
|
9
|
+
user: "STOPPED BY THE USER before completion — output is partial; the task was NOT finished",
|
|
10
|
+
agent: "stopped before completion — output is partial; the task was NOT finished",
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export function getStatusNote(lifecycle: AgentLifecycle): string {
|
|
14
|
+
const note =
|
|
15
|
+
lifecycle.status === "stopped"
|
|
16
|
+
// A stopped agent with no recorded initiator reads as an agent stop.
|
|
17
|
+
? STOP_NOTES[lifecycle.stoppedBy ?? "agent"]
|
|
18
|
+
: STATUS_NOTES[lifecycle.status];
|
|
9
19
|
return note ? ` (${note})` : "";
|
|
10
20
|
}
|
package/src/types.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
import type { Model } from "@earendil-works/pi-ai";
|
|
6
6
|
import type { AgentSession } from "@earendil-works/pi-coding-agent";
|
|
7
7
|
import type { AgentOutputLog } from "./agents/output-file.js";
|
|
8
|
-
import type { LifetimeUsage } from "./agents/usage.js";
|
|
8
|
+
import type { LifetimeUsage, AgentUsage } from "./agents/usage.js";
|
|
9
9
|
import type { SubagentType, AgentInvocation } from "./agents/types.js";
|
|
10
10
|
|
|
11
11
|
/** Thinking level for agent models. */
|
|
@@ -59,7 +59,7 @@ export interface RunCallbacks {
|
|
|
59
59
|
onTextDelta?: (delta: string, fullText: string) => void;
|
|
60
60
|
onSessionCreated?: (session: AgentSession) => void;
|
|
61
61
|
onTurnEnd?: (turnCount: number) => void;
|
|
62
|
-
onAssistantUsage?: (usage:
|
|
62
|
+
onAssistantUsage?: (usage: AgentUsage) => void;
|
|
63
63
|
onCompaction?: (info: CompactionInfo) => void;
|
|
64
64
|
}
|
|
65
65
|
|
|
@@ -95,6 +95,9 @@ export interface CompactionInfo {
|
|
|
95
95
|
/** Possible agent lifecycle statuses. */
|
|
96
96
|
export type AgentStatus = "queued" | "running" | "completed" | "turn_limited" | "aborted" | "stopped" | "error";
|
|
97
97
|
|
|
98
|
+
/** Who initiated an agent stop: "user" via UI menu, or "agent" via StopAgent tool. */
|
|
99
|
+
export type StopInitiator = "user" | "agent";
|
|
100
|
+
|
|
98
101
|
/**
|
|
99
102
|
* Lifecycle state: when the agent started, completed, and its current status.
|
|
100
103
|
* Used by agent-manager (lifecycle control), menus (status display), widget (linger logic).
|
|
@@ -103,6 +106,7 @@ export interface AgentLifecycle {
|
|
|
103
106
|
status: AgentStatus;
|
|
104
107
|
startedAt: number;
|
|
105
108
|
completedAt?: number;
|
|
109
|
+
stoppedBy?: StopInitiator;
|
|
106
110
|
}
|
|
107
111
|
|
|
108
112
|
/**
|
|
@@ -156,6 +160,8 @@ export interface AgentAccumulatedStats {
|
|
|
156
160
|
maxTurns?: number;
|
|
157
161
|
/** Number of times this agent's session has compacted. Initialized to 0 at spawn. */
|
|
158
162
|
compactionCount: number;
|
|
163
|
+
/** Previous input token count for delta estimation (vLLM doesn't report cache hits). */
|
|
164
|
+
prevInputTokens?: number;
|
|
159
165
|
/** Last-known context usage percentage (0–100), captured at completion. */
|
|
160
166
|
contextPercent?: number | null;
|
|
161
167
|
}
|
|
@@ -134,7 +134,7 @@ export function buildAgentActionsList(
|
|
|
134
134
|
input.onEscape = () => setActive(list);
|
|
135
135
|
setActive(input);
|
|
136
136
|
} else if (item.value === "stop") {
|
|
137
|
-
getManager()?.abort(record.id);
|
|
137
|
+
getManager()?.abort(record.id, "user");
|
|
138
138
|
ctx.ui.notify(`Stopped ${shortId}`, "info");
|
|
139
139
|
onClose();
|
|
140
140
|
}
|
|
@@ -186,7 +186,7 @@ export async function showRunningAgentsMenu(
|
|
|
186
186
|
agentList.onSelect = async (item) => {
|
|
187
187
|
if (item.value === "__stop-all") {
|
|
188
188
|
for (const r of running) {
|
|
189
|
-
getManager()?.abort(r.id);
|
|
189
|
+
getManager()?.abort(r.id, "user");
|
|
190
190
|
}
|
|
191
191
|
ctx.ui.notify(`Stopped ${running.length} agent(s)`, "info");
|
|
192
192
|
done(undefined);
|
|
@@ -26,6 +26,7 @@ function buildStatConfig(store: ReturnType<typeof getStore>) {
|
|
|
26
26
|
["showTools", { label: "Show tools", get: () => store.agent.showTools, set: (v) => store.mutate.agent.setShowTools(v) }],
|
|
27
27
|
["showTurns", { label: "Show turns", get: () => store.agent.showTurns, set: (v) => store.mutate.agent.setShowTurns(v) }],
|
|
28
28
|
["showInput", { label: "Show input tokens", get: () => store.agent.showInput, set: (v) => store.mutate.agent.setShowInput(v) }],
|
|
29
|
+
["deltaInputTokens", { label: "Delta input tokens", get: () => store.agent.deltaInputTokens, set: (v) => store.mutate.agent.setDeltaInputTokens(v) }],
|
|
29
30
|
["showOutput", { label: "Show output tokens", get: () => store.agent.showOutput, set: (v) => store.mutate.agent.setShowOutput(v) }],
|
|
30
31
|
["showContext", { label: "Show context %", get: () => store.agent.showContext, set: (v) => store.mutate.agent.setShowContext(v) }],
|
|
31
32
|
["showCost", { label: "Show cost", get: () => store.agent.showCost, set: (v) => store.mutate.agent.setShowCost(v) }],
|
|
@@ -66,6 +67,7 @@ export async function showWidgetSettingsMenu(ctx: ExtensionCommandContext): Prom
|
|
|
66
67
|
showTools: "Show tool count (🛠 ) in the widget.",
|
|
67
68
|
showTurns: "Show turn count (⟳ ) in the widget.",
|
|
68
69
|
showInput: "Show input tokens (↑) in the widget.",
|
|
70
|
+
deltaInputTokens: "Estimate input token delta for vLLM (no cache reporting).",
|
|
69
71
|
showOutput: "Show output tokens (↓) in the widget.",
|
|
70
72
|
showContext: "Show context-fill percent (%) in the widget.",
|
|
71
73
|
showCost: "Show dollar cost ($) in the widget.",
|