pi-subagents-lite 1.0.2 → 1.2.0
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 +47 -5
- package/package.json +1 -1
- package/src/agent-manager.ts +88 -62
- package/src/agent-runner.ts +194 -167
- package/src/agent-types.ts +21 -1
- package/src/config-io.ts +9 -1
- package/src/config-mutator.ts +183 -0
- package/src/context.ts +1 -1
- package/src/format.ts +173 -0
- package/src/index.ts +127 -177
- package/src/menus.ts +586 -137
- package/src/model-precedence.ts +5 -0
- package/src/output-file.ts +1 -68
- package/src/renderer.ts +163 -0
- package/src/result-viewer.ts +2 -1
- package/src/state.ts +83 -0
- package/src/tool-execution.ts +179 -56
- package/src/types.ts +104 -31
- package/src/ui/agent-widget.ts +159 -146
- package/src/usage.ts +5 -0
- package/src/worktree-validator.ts +199 -0
- package/src/stop-agent-tool.ts +0 -77
package/src/tool-execution.ts
CHANGED
|
@@ -8,22 +8,25 @@
|
|
|
8
8
|
import type { ExtensionContext, ToolCallEvent } from "@earendil-works/pi-coding-agent";
|
|
9
9
|
|
|
10
10
|
import type { AgentRecord } from "./types.js";
|
|
11
|
+
import { SHORT_ID_LENGTH } from "./types.js";
|
|
11
12
|
import type { SpawnOptions as AgentManagerSpawnOptions } from "./agent-manager.js";
|
|
12
13
|
import type { AgentActivity } from "./ui/agent-widget.js";
|
|
13
14
|
import { resolveType, getAgentConfig, discoverNewAgents } from "./agent-types.js";
|
|
14
15
|
import { resolveModel } from "./model-precedence.js";
|
|
15
16
|
import { addUsage, getLifetimeTotal, getSessionContextPercent, type LifetimeUsage } from "./usage.js";
|
|
17
|
+
import { validateWorktreePath } from "./worktree-validator.js";
|
|
16
18
|
|
|
17
|
-
// Shared state imported from
|
|
19
|
+
// Shared state imported from state.ts
|
|
18
20
|
import { parseModelKey, findModelInRegistry, parseThinkingLevel } from "./utils.js";
|
|
19
21
|
import {
|
|
20
22
|
__config,
|
|
21
23
|
sessionOverrides,
|
|
22
|
-
manager,
|
|
23
24
|
piInstance,
|
|
24
25
|
agentActivity,
|
|
25
|
-
|
|
26
|
-
|
|
26
|
+
getManager,
|
|
27
|
+
getWidget,
|
|
28
|
+
sessionCtx,
|
|
29
|
+
} from "./state.js";
|
|
27
30
|
|
|
28
31
|
// ============================================================================
|
|
29
32
|
// Module-level state
|
|
@@ -59,8 +62,9 @@ export function errorResult(text: string, details?: Record<string, unknown>) {
|
|
|
59
62
|
/**
|
|
60
63
|
* Create an AgentActivity state and spawn callbacks for tracking tool usage.
|
|
61
64
|
* Used by both foreground and background paths to avoid duplication.
|
|
65
|
+
* Exported for use by the menu spawn flow.
|
|
62
66
|
*/
|
|
63
|
-
function createActivityTracker(maxTurns?: number, onStreamUpdate?: () => void) {
|
|
67
|
+
export function createActivityTracker(maxTurns?: number, onStreamUpdate?: () => void) {
|
|
64
68
|
const state: AgentActivity = {
|
|
65
69
|
activeTools: new Map(),
|
|
66
70
|
toolUses: 0,
|
|
@@ -103,6 +107,65 @@ function createActivityTracker(maxTurns?: number, onStreamUpdate?: () => void) {
|
|
|
103
107
|
return { state, callbacks };
|
|
104
108
|
}
|
|
105
109
|
|
|
110
|
+
// ============================================================================
|
|
111
|
+
// buildAgentDetails — consolidated stats/details construction
|
|
112
|
+
// ============================================================================
|
|
113
|
+
|
|
114
|
+
interface AgentDetailsOptions {
|
|
115
|
+
/** Include full stats (turns, tokens, context%, compactions, cost). Default: false. */
|
|
116
|
+
includeStats?: boolean;
|
|
117
|
+
/** Include status and outputFile. Default: false. */
|
|
118
|
+
includeStatus?: boolean;
|
|
119
|
+
/** Override the turnCount (e.g. from activity tracker). Default: record.turnCount. */
|
|
120
|
+
turnCount?: number;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Build a details Record from an AgentRecord, controlled by options.
|
|
125
|
+
*
|
|
126
|
+
* Always includes `type` and `description`. Optional groups:
|
|
127
|
+
* - `includeStatus`: adds `status`, `outputFile`
|
|
128
|
+
* - `includeStats`: adds turn/token/cost/context/compaction/model fields
|
|
129
|
+
*
|
|
130
|
+
* Consolidates the identical field-selection logic previously duplicated
|
|
131
|
+
* across emitIndividualNudge, executeSpawnForeground, and executeSpawnBackground.
|
|
132
|
+
*/
|
|
133
|
+
export function buildAgentDetails(
|
|
134
|
+
record: AgentRecord,
|
|
135
|
+
options?: AgentDetailsOptions,
|
|
136
|
+
): Record<string, unknown> {
|
|
137
|
+
const details: Record<string, unknown> = {
|
|
138
|
+
type: record.display.type,
|
|
139
|
+
description: record.display.description,
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
if (record.display.worktreePath) {
|
|
143
|
+
details.worktreePath = record.display.worktreePath;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (options?.includeStatus) {
|
|
147
|
+
details.status = record.lifecycle.status;
|
|
148
|
+
details.outputFile = record.display.outputFile;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (options?.includeStats) {
|
|
152
|
+
const totalTokens = getLifetimeTotal(record.stats.lifetimeUsage);
|
|
153
|
+
const elapsedMs = record.lifecycle.completedAt ? record.lifecycle.completedAt - record.lifecycle.startedAt : 0;
|
|
154
|
+
|
|
155
|
+
details.turnCount = options.turnCount ?? record.stats.turnCount;
|
|
156
|
+
details.maxTurns = record.stats.maxTurns;
|
|
157
|
+
details.toolUses = record.stats.toolUses;
|
|
158
|
+
details.tokens = totalTokens;
|
|
159
|
+
details.contextPercent = getSessionContextPercent(record.execution.session);
|
|
160
|
+
details.durationMs = elapsedMs;
|
|
161
|
+
details.compactions = record.stats.compactionCount;
|
|
162
|
+
details.modelName = record.display.invocation?.modelName;
|
|
163
|
+
details.cost = record.stats.lifetimeUsage.cost;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return details;
|
|
167
|
+
}
|
|
168
|
+
|
|
106
169
|
// ============================================================================
|
|
107
170
|
// Nudge scheduling — batch completion notifications within the hold window
|
|
108
171
|
// ============================================================================
|
|
@@ -118,7 +181,7 @@ export function scheduleNudge(agentId: string): void {
|
|
|
118
181
|
pendingNudges.clear();
|
|
119
182
|
|
|
120
183
|
for (const id of batch) {
|
|
121
|
-
emitIndividualNudge(id,
|
|
184
|
+
emitIndividualNudge(id, getManager()?.getRecord(id));
|
|
122
185
|
}
|
|
123
186
|
}, NUDGE_DELAY_MS);
|
|
124
187
|
}
|
|
@@ -126,31 +189,16 @@ export function scheduleNudge(agentId: string): void {
|
|
|
126
189
|
function emitIndividualNudge(agentId: string, record?: AgentRecord): void {
|
|
127
190
|
if (!record) return;
|
|
128
191
|
|
|
129
|
-
const
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
:
|
|
133
|
-
|
|
134
|
-
const details: Record<string, unknown> = {
|
|
135
|
-
type: record.type,
|
|
136
|
-
description: record.description,
|
|
137
|
-
status: record.status,
|
|
138
|
-
outputFile: record.outputFile,
|
|
139
|
-
turnCount: record.turnCount ?? agentActivity.get(agentId)?.turnCount,
|
|
140
|
-
maxTurns: record.maxTurns,
|
|
141
|
-
toolUses: record.toolUses,
|
|
142
|
-
tokens: totalTokens,
|
|
143
|
-
cost: record.lifetimeUsage.cost,
|
|
144
|
-
contextPercent: getSessionContextPercent(record.session),
|
|
145
|
-
durationMs: elapsedMs,
|
|
146
|
-
compactions: record.compactionCount,
|
|
147
|
-
modelName: record.invocation?.modelName,
|
|
148
|
-
};
|
|
192
|
+
const details = buildAgentDetails(record, {
|
|
193
|
+
includeStats: true,
|
|
194
|
+
includeStatus: true,
|
|
195
|
+
turnCount: record.stats.turnCount ?? agentActivity.get(agentId)?.turnCount,
|
|
196
|
+
});
|
|
149
197
|
|
|
150
198
|
piInstance.sendMessage(
|
|
151
199
|
{
|
|
152
200
|
customType: "subagent-result",
|
|
153
|
-
content: `[Subagent "${record.type}"
|
|
201
|
+
content: `[Subagent "${record.display.type}" ${record.lifecycle.status}]\n\n${record.result ?? ""}`,
|
|
154
202
|
details,
|
|
155
203
|
display: true,
|
|
156
204
|
},
|
|
@@ -172,11 +220,32 @@ export async function executeAgentTool(
|
|
|
172
220
|
_onUpdate: ((update: any) => void) | undefined,
|
|
173
221
|
ctx: ExtensionContext,
|
|
174
222
|
): Promise<any> {
|
|
223
|
+
// Validate worktree_path early — needed for on-demand agent discovery
|
|
224
|
+
const rawWorktreePath = params.worktree_path as string | undefined;
|
|
225
|
+
let validatedWorktreePath: string | undefined;
|
|
226
|
+
let worktreeLabel: string | undefined;
|
|
227
|
+
if (rawWorktreePath && rawWorktreePath.trim() !== "") {
|
|
228
|
+
try {
|
|
229
|
+
const parentCwd = sessionCtx?.cwd ?? ctx.cwd;
|
|
230
|
+
const validation = await validateWorktreePath(piInstance, rawWorktreePath, parentCwd);
|
|
231
|
+
if (!validation.ok) {
|
|
232
|
+
return errorResult(validation.error);
|
|
233
|
+
}
|
|
234
|
+
validatedWorktreePath = validation.resolvedPath;
|
|
235
|
+
worktreeLabel = validation.label;
|
|
236
|
+
} catch (err: unknown) {
|
|
237
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
238
|
+
return errorResult(`worktree_path validation failed: ${msg}`);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
175
242
|
const type = (params.agent as string) || "general-purpose";
|
|
176
243
|
let resolvedType = resolveType(type);
|
|
177
244
|
if (!resolvedType) {
|
|
178
|
-
// Not found in registry — try scanning filesystem for agents added during the session
|
|
179
|
-
|
|
245
|
+
// Not found in registry — try scanning filesystem for agents added during the session.
|
|
246
|
+
// When worktree_path is set, also scan the worktree's .pi/agents/ directory.
|
|
247
|
+
const worktreeDir = validatedWorktreePath ? `${validatedWorktreePath}/.pi/agents` : undefined;
|
|
248
|
+
await discoverNewAgents(worktreeDir);
|
|
180
249
|
resolvedType = resolveType(type);
|
|
181
250
|
}
|
|
182
251
|
if (!resolvedType) {
|
|
@@ -187,6 +256,7 @@ export async function executeAgentTool(
|
|
|
187
256
|
const description = params.description as string;
|
|
188
257
|
const runInBackground = params.run_in_background as boolean | undefined;
|
|
189
258
|
const maxTurns = params.max_turns as number | undefined ?? getAgentConfig(resolvedType)?.maxTurns;
|
|
259
|
+
|
|
190
260
|
const modelStr = params.model as string | undefined;
|
|
191
261
|
const model = findModelInRegistry(modelStr, ctx.modelRegistry, ctx.model);
|
|
192
262
|
const modelKey = model ? `${model.provider}/${model.id}` : undefined;
|
|
@@ -206,6 +276,8 @@ export async function executeAgentTool(
|
|
|
206
276
|
modelKey,
|
|
207
277
|
invocation: { modelName },
|
|
208
278
|
graceTurns: __config.agent.graceTurns,
|
|
279
|
+
worktreePath: validatedWorktreePath,
|
|
280
|
+
worktreeLabel,
|
|
209
281
|
};
|
|
210
282
|
|
|
211
283
|
if (runInBackground || __config.agent.forceBackground) {
|
|
@@ -225,20 +297,20 @@ async function executeSpawnBackground(
|
|
|
225
297
|
spawnOptions.maxTurns,
|
|
226
298
|
);
|
|
227
299
|
|
|
228
|
-
const agentId =
|
|
300
|
+
const agentId = getManager().spawn(piInstance, ctx, resolvedType, prompt, {
|
|
229
301
|
...spawnOptions,
|
|
230
302
|
isBackground: true,
|
|
231
303
|
...callbacks,
|
|
232
304
|
});
|
|
233
305
|
backgroundAgentIds.add(agentId);
|
|
234
306
|
agentActivity.set(agentId, state);
|
|
235
|
-
|
|
236
|
-
|
|
307
|
+
getWidget()?.ensureTimer();
|
|
308
|
+
getWidget()?.update();
|
|
237
309
|
|
|
238
|
-
const record =
|
|
239
|
-
const details
|
|
310
|
+
const record = getManager().getRecord(agentId)!;
|
|
311
|
+
const details = buildAgentDetails(record);
|
|
240
312
|
const suffix = `A notification will arrive when done - User asks you not to poll, check status or duplicate the delegated work.\n\nAgent ID: ${agentId}`;
|
|
241
|
-
const label = record.status === "queued" ? "Agent queued" : "Agent running";
|
|
313
|
+
const label = record.lifecycle.status === "queued" ? "Agent queued" : "Agent running";
|
|
242
314
|
|
|
243
315
|
return successResult(`[${label}] ${suffix}`, details);
|
|
244
316
|
}
|
|
@@ -253,37 +325,27 @@ async function executeSpawnForeground(
|
|
|
253
325
|
spawnOptions.maxTurns,
|
|
254
326
|
);
|
|
255
327
|
|
|
256
|
-
const fgId =
|
|
328
|
+
const fgId = getManager().spawn(piInstance, ctx, resolvedType, prompt, {
|
|
257
329
|
...spawnOptions,
|
|
258
330
|
...fgCallbacks,
|
|
259
331
|
isBackground: false,
|
|
260
332
|
});
|
|
261
333
|
agentActivity.set(fgId, fgState);
|
|
262
|
-
|
|
334
|
+
getWidget()?.ensureTimer();
|
|
263
335
|
|
|
264
|
-
const record =
|
|
265
|
-
await record.promise;
|
|
336
|
+
const record = getManager().getRecord(fgId)!;
|
|
337
|
+
await record.execution.promise;
|
|
266
338
|
|
|
267
339
|
agentActivity.delete(fgId);
|
|
268
|
-
|
|
269
|
-
|
|
340
|
+
getWidget()?.markFinished(fgId);
|
|
341
|
+
getWidget()?.update();
|
|
270
342
|
|
|
271
|
-
const
|
|
272
|
-
|
|
273
|
-
const stats: Record<string, unknown> = {
|
|
274
|
-
type: resolvedType,
|
|
343
|
+
const stats = buildAgentDetails(record, {
|
|
344
|
+
includeStats: true,
|
|
275
345
|
turnCount: fgState.turnCount,
|
|
276
|
-
|
|
277
|
-
toolUses: record.toolUses,
|
|
278
|
-
tokens: totalTokens,
|
|
279
|
-
contextPercent: getSessionContextPercent(fgState.session),
|
|
280
|
-
durationMs: elapsedMs,
|
|
281
|
-
description: spawnOptions.description,
|
|
282
|
-
compactions: record.compactionCount,
|
|
283
|
-
modelName: record.invocation?.modelName,
|
|
284
|
-
};
|
|
346
|
+
});
|
|
285
347
|
|
|
286
|
-
if (record.status === "error") {
|
|
348
|
+
if (record.lifecycle.status === "error") {
|
|
287
349
|
return errorResult(`Agent failed: ${record.error || "unknown error"}`, stats);
|
|
288
350
|
}
|
|
289
351
|
|
|
@@ -291,8 +353,69 @@ async function executeSpawnForeground(
|
|
|
291
353
|
}
|
|
292
354
|
|
|
293
355
|
// ============================================================================
|
|
294
|
-
//
|
|
356
|
+
// Running agents list helper (used by executeStopAgentTool)
|
|
357
|
+
// ============================================================================
|
|
358
|
+
|
|
359
|
+
/**
|
|
360
|
+
* Build a compact list of running (or queued) agents.
|
|
361
|
+
* Format: "type·short_id, type·short_id" — one line, easy for LLM to parse.
|
|
362
|
+
*/
|
|
363
|
+
function formatRunningAgents(): string {
|
|
364
|
+
const agents = getManager().listAgents().filter(
|
|
365
|
+
(a) => a.lifecycle.status === "running" || a.lifecycle.status === "queued",
|
|
366
|
+
);
|
|
367
|
+
|
|
368
|
+
if (agents.length === 0) return "none";
|
|
369
|
+
|
|
370
|
+
return agents
|
|
371
|
+
.map((a) => `${a.display.type}·${a.id.slice(0, SHORT_ID_LENGTH)}`)
|
|
372
|
+
.join(", ");
|
|
373
|
+
}
|
|
374
|
+
|
|
295
375
|
// ============================================================================
|
|
376
|
+
// StopAgent execute handler
|
|
377
|
+
// ============================================================================
|
|
378
|
+
|
|
379
|
+
export async function executeStopAgentTool(
|
|
380
|
+
_toolCallId: string,
|
|
381
|
+
params: Record<string, unknown>,
|
|
382
|
+
_signal: AbortSignal | undefined,
|
|
383
|
+
_onUpdate: ((update: any) => void) | undefined,
|
|
384
|
+
_ctx: ExtensionContext,
|
|
385
|
+
): Promise<any> {
|
|
386
|
+
const agentId = params.agent_id as string | undefined;
|
|
387
|
+
|
|
388
|
+
if (!agentId) {
|
|
389
|
+
return errorResult("agent_id is required");
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
const record = getManager().getRecord(agentId);
|
|
393
|
+
|
|
394
|
+
if (!record) {
|
|
395
|
+
// Agent not found → return error + list of running agents
|
|
396
|
+
return errorResult(
|
|
397
|
+
`Agent ${agentId} not found. Running agents: ${formatRunningAgents()}`,
|
|
398
|
+
);
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
// Check if already in a terminal state (not running or queued)
|
|
402
|
+
if (record.lifecycle.status !== "running" && record.lifecycle.status !== "queued") {
|
|
403
|
+
return successResult(
|
|
404
|
+
`Agent ${agentId} is already ${record.lifecycle.status}. Running agents: ${formatRunningAgents()}`,
|
|
405
|
+
);
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
// Attempt to stop the running/queued agent
|
|
409
|
+
if (getManager().abort(agentId)) {
|
|
410
|
+
return successResult(`Stopped agent ${agentId.slice(0, SHORT_ID_LENGTH)}`);
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
return errorResult(`Failed to stop agent ${agentId}`);
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
// ============================================================================
|
|
417
|
+
// Tool_call listener — inject model into Agent tool calls
|
|
418
|
+
// =============================================================================
|
|
296
419
|
|
|
297
420
|
export async function toolCallListener(
|
|
298
421
|
event: ToolCallEvent,
|
package/src/types.ts
CHANGED
|
@@ -50,39 +50,16 @@ export interface AgentConfig {
|
|
|
50
50
|
|
|
51
51
|
export interface AgentRecord {
|
|
52
52
|
id: string;
|
|
53
|
-
type: SubagentType;
|
|
54
|
-
description: string;
|
|
55
|
-
status: "queued" | "running" | "completed" | "steered" | "aborted" | "stopped" | "error";
|
|
56
53
|
result?: string;
|
|
57
54
|
error?: string;
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
|
|
66
|
-
/** The tool_use_id from the original Agent tool call. */
|
|
67
|
-
toolCallId?: string;
|
|
68
|
-
/** Path to the streaming output transcript file. */
|
|
69
|
-
outputFile?: string;
|
|
70
|
-
/** Cleanup function for the output file stream subscription. */
|
|
71
|
-
outputCleanup?: () => void;
|
|
72
|
-
/**
|
|
73
|
-
* Lifetime usage breakdown, accumulated via `message_end` events. Survives
|
|
74
|
-
* compaction. Total = input + output + cacheWrite + cost (cacheRead deliberately
|
|
75
|
-
* excluded — see issue #38). Initialized to zeros at spawn.
|
|
76
|
-
*/
|
|
77
|
-
lifetimeUsage: LifetimeUsage;
|
|
78
|
-
/** Final turn count (set on completion). Used by widget after activity cleanup. */
|
|
79
|
-
turnCount?: number;
|
|
80
|
-
/** Max turns limit (from invocation or default). */
|
|
81
|
-
maxTurns?: number;
|
|
82
|
-
/** Number of times this agent's session has compacted. Initialized to 0 at spawn. */
|
|
83
|
-
compactionCount: number;
|
|
84
|
-
/** Resolved spawn params, captured for UI display. Fixed at spawn time. */
|
|
85
|
-
invocation?: AgentInvocation;
|
|
55
|
+
/** Lifecycle state: status, timestamps. */
|
|
56
|
+
lifecycle: AgentLifecycle;
|
|
57
|
+
/** Display-oriented info: type, description, output file, invocation. */
|
|
58
|
+
display: AgentDisplayInfo;
|
|
59
|
+
/** Execution internals: session, abort controller, pending steers. */
|
|
60
|
+
execution: AgentExecutionState;
|
|
61
|
+
/** Accumulated statistics: usage, tool uses, turns. */
|
|
62
|
+
stats: AgentAccumulatedStats;
|
|
86
63
|
}
|
|
87
64
|
|
|
88
65
|
export interface AgentInvocation {
|
|
@@ -102,6 +79,30 @@ export interface EnvInfo {
|
|
|
102
79
|
/** How many characters of agent ID to show in display. */
|
|
103
80
|
export const SHORT_ID_LENGTH = 8;
|
|
104
81
|
|
|
82
|
+
/**
|
|
83
|
+
* Theme for terminal rendering — used by format.ts, renderer.ts, and UI widgets.
|
|
84
|
+
* Defined here (not in ui/agent-widget.ts) so non-UI modules can import it
|
|
85
|
+
* without depending on the UI layer.
|
|
86
|
+
*/
|
|
87
|
+
export type Theme = {
|
|
88
|
+
fg(color: string, text: string): string;
|
|
89
|
+
bg(color: string, text: string): string;
|
|
90
|
+
bold(text: string): string;
|
|
91
|
+
italic?: (text: string) => string;
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
/** Non-model keys in config.agent — preserved when clearing all overrides. */
|
|
95
|
+
export const CONFIG_AGENT_NON_MODEL_KEYS = [
|
|
96
|
+
"default",
|
|
97
|
+
"forceBackground",
|
|
98
|
+
"graceTurns",
|
|
99
|
+
"showCost",
|
|
100
|
+
"widgetMaxLines",
|
|
101
|
+
"widgetMaxLinesCompact",
|
|
102
|
+
"widgetCompact",
|
|
103
|
+
"widgetShortcut",
|
|
104
|
+
];
|
|
105
|
+
|
|
105
106
|
/** Reason for a context compaction event. */
|
|
106
107
|
export type CompactionReason = "manual" | "threshold" | "overflow";
|
|
107
108
|
|
|
@@ -111,4 +112,76 @@ export interface CompactionInfo {
|
|
|
111
112
|
tokensBefore: number;
|
|
112
113
|
}
|
|
113
114
|
|
|
115
|
+
// ---------------------------------------------------------------------------
|
|
116
|
+
// Sub-object interfaces for decomposed AgentRecord
|
|
117
|
+
// ---------------------------------------------------------------------------
|
|
118
|
+
|
|
119
|
+
/** Possible agent lifecycle statuses. */
|
|
120
|
+
export type AgentStatus = "queued" | "running" | "completed" | "steered" | "aborted" | "stopped" | "error";
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Lifecycle state: when the agent started, completed, and its current status.
|
|
124
|
+
* Used by agent-manager (lifecycle control), menus (status display), widget (linger logic).
|
|
125
|
+
*/
|
|
126
|
+
export interface AgentLifecycle {
|
|
127
|
+
status: AgentStatus;
|
|
128
|
+
startedAt: number;
|
|
129
|
+
completedAt?: number;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Display-oriented fields: type name, description, output file, invocation params.
|
|
134
|
+
* Used by widget (rendering), menus (listing), renderer (display).
|
|
135
|
+
*/
|
|
136
|
+
export interface AgentDisplayInfo {
|
|
137
|
+
type: SubagentType;
|
|
138
|
+
description: string;
|
|
139
|
+
/** Path to the streaming output transcript file. */
|
|
140
|
+
outputFile?: string;
|
|
141
|
+
/** Resolved spawn params, captured for UI display. Fixed at spawn time. */
|
|
142
|
+
invocation?: AgentInvocation;
|
|
143
|
+
/** The tool_use_id from the original Agent tool call. */
|
|
144
|
+
toolCallId?: string;
|
|
145
|
+
/** Resolved absolute path of the worktree this agent is running in. */
|
|
146
|
+
worktreePath?: string;
|
|
147
|
+
/** Short display label for the worktree (e.g., "feature" or "feature/packages/web"). */
|
|
148
|
+
worktreeLabel?: string;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Execution internals: session handle, abort controller, pending steers.
|
|
153
|
+
* Used by agent-manager (session lifecycle), tool-execution (steering, nudge).
|
|
154
|
+
*/
|
|
155
|
+
export interface AgentExecutionState {
|
|
156
|
+
session?: AgentSession;
|
|
157
|
+
abortController?: AbortController;
|
|
158
|
+
promise?: Promise<string>;
|
|
159
|
+
/** Steering messages queued before the session was ready. */
|
|
160
|
+
pendingSteers?: string[];
|
|
161
|
+
/** Cleanup function for the output file stream subscription. */
|
|
162
|
+
outputCleanup?: () => void;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Accumulated statistics: usage breakdown, tool uses, turn count.
|
|
167
|
+
* Used by widget (stats display), tool-execution (details building), menus (result viewer).
|
|
168
|
+
*/
|
|
169
|
+
export interface AgentAccumulatedStats {
|
|
170
|
+
/**
|
|
171
|
+
* Lifetime usage breakdown, accumulated via `message_end` events. Survives
|
|
172
|
+
* compaction. Total = input + output + cacheWrite + cost (cacheRead deliberately
|
|
173
|
+
* excluded — see issue #38). Initialized to zeros at spawn.
|
|
174
|
+
*/
|
|
175
|
+
lifetimeUsage: LifetimeUsage;
|
|
176
|
+
toolUses: number;
|
|
177
|
+
/** Final turn count (set on completion). Used by widget after activity cleanup. */
|
|
178
|
+
turnCount?: number;
|
|
179
|
+
/** Max turns limit (from invocation or default). */
|
|
180
|
+
maxTurns?: number;
|
|
181
|
+
/** Number of times this agent's session has compacted. Initialized to 0 at spawn. */
|
|
182
|
+
compactionCount: number;
|
|
183
|
+
/** Last-known context usage percentage (0–100), captured at completion. */
|
|
184
|
+
contextPercent?: number | null;
|
|
185
|
+
}
|
|
186
|
+
|
|
114
187
|
|