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/README.md
CHANGED
|
@@ -25,15 +25,21 @@ Tool names like `Agent` and `StopAgent`, and parameter names like `prompt`, `des
|
|
|
25
25
|
## Features
|
|
26
26
|
|
|
27
27
|
- **Two tools** — `Agent` (spawn) and `StopAgent` (stop)
|
|
28
|
+
- **Manual spawn** — spawn agents from the `/agents` menu without asking the LLM. Full control over model, thinking, turns, and background mode.
|
|
28
29
|
- **Foreground & background** — block or fire-and-forget with auto-delivered results
|
|
29
30
|
- **Custom agent types** — define via `.md` files with YAML frontmatter (tools, model, thinking, turn limits)
|
|
30
31
|
- **Smart model resolution** — 6-level precedence: session → config → frontmatter → parent. Set once, forget
|
|
31
32
|
- **Concurrency control** — per-model and per-provider slot limits with automatic queuing
|
|
32
33
|
- **Cost tracking** — input/output/cache tokens and dollar cost per agent
|
|
34
|
+
- **Cost display** — toggle agent cost in stats and status bar (OFF by default)
|
|
33
35
|
- **Live widget** — persistent status bar above the editor showing running/completed agents
|
|
36
|
+
- **Widget settings** — force compact mode, max lines, opt-in ctrl+o sync
|
|
34
37
|
- **Result viewer** — fullscreen markdown viewer with stats
|
|
35
38
|
- **Steer** — inject mid-execution guidance into running agents
|
|
36
39
|
- **Output logs** — human-readable, `tail -f` friendly
|
|
40
|
+
- **Grace turns** — configurable grace turns after `max_turns` before hard abort
|
|
41
|
+
- **Reload safety** — warns when active agents are killed by session reload
|
|
42
|
+
- **Worktree support** — `worktree_path` parameter runs agents in a git worktree with validated path, worktree agent discovery, and UI label
|
|
37
43
|
|
|
38
44
|
## Install
|
|
39
45
|
|
|
@@ -95,6 +101,7 @@ Stop a running agent at any time via /agents command
|
|
|
95
101
|
| `description` | ✅ | Brief description for the LLM caller |
|
|
96
102
|
| `agent` | | Type name — `general-purpose`, `Explore`, or any custom type you define (see [Custom Agent Types](#custom-agent-types)). The available values are **auto-populated** from `.md` files in your agent directories — drop a file, it appears in the enum. Set `hidden: true` in frontmatter to hide a type from this list (still callable by name). |
|
|
97
103
|
| `run_in_background` | | Fire-and-forget; result delivered automatically when done |
|
|
104
|
+
| `worktree_path` | | Absolute path to a git worktree. Agent runs in that worktree's context, discovers agents from its `.pi/agents/` directory, and displays a worktree label in the widget and menus. Path is validated against the parent repo's git common dir. |
|
|
98
105
|
|
|
99
106
|
> `model`, `max_turns`, and `thinking` are **not visible to the LLM** through tool introspection — the extension injects them at call time from agent config and frontmatter. `model` is resolved via the [Model Resolution](#model-resolution) chain; `max_turns`/`thinking` come from the agent's config. See [Custom Agent Types](#custom-agent-types) to set them.
|
|
100
107
|
|
|
@@ -280,9 +287,12 @@ The LLM never passes `model` — it's injected at call time via the `tool_call`
|
|
|
280
287
|
|
|
281
288
|
Management menu with four sections:
|
|
282
289
|
|
|
283
|
-
- **
|
|
284
|
-
- **
|
|
285
|
-
- **
|
|
290
|
+
- **Running agents** — list with status and description; per-agent actions: view snapshot, view result, view error, steer, stop; bulk stop all running
|
|
291
|
+
- **Spawn agent** — manually spawn an agent without asking the LLM. Pick a type, enter a prompt, configure options (model, thinking, max turns, grace turns, background), and spawn. Options are pre-filled from agent config and current settings. Spawn immediately or customize first.
|
|
292
|
+
- **Settings** — model, concurrency, and widget settings grouped together
|
|
293
|
+
- **Model settings** — global default, per-type overrides, force background mode, cost display toggle, grace turns
|
|
294
|
+
- **Concurrency** — default limit, per-provider and per-model slots, reset to defaults
|
|
295
|
+
- **Widget settings** — force compact mode, max lines (full/compact), ctrl+o shortcut
|
|
286
296
|
- **Debug** — agent types, agent briefing (sends capabilities to the LLM)
|
|
287
297
|
|
|
288
298
|
## Interface
|
|
@@ -294,15 +304,28 @@ Persistent bar above the editor showing running and completed agents. Updates li
|
|
|
294
304
|
- Running agents show a spinner, current tool activity, turn count, token usage (with optional context-fill percent), and elapsed time
|
|
295
305
|
- Completed agents show a check mark with final stats
|
|
296
306
|
- Click `tail -f` path to follow output logs in real time
|
|
307
|
+
- Two display modes: **full** (header + `tail -f` path + activity) and **compact** (single line, description truncated to 30 chars, activity inline)
|
|
297
308
|
|
|
298
|
-
|
|
309
|
+
**Full mode** (tree structure with branch connectors):
|
|
299
310
|
```
|
|
300
311
|
├─ ⠙ Explore description 3🛠 ·5≤30⟳ ·12.0k(45%)·1h 2m 3s
|
|
312
|
+
│ │ tail -f /tmp/pi-agent-outputs/...
|
|
301
313
|
│ └ thinking…
|
|
302
314
|
```
|
|
303
315
|
|
|
316
|
+
**Compact mode** (single line, description truncated):
|
|
317
|
+
```
|
|
318
|
+
├─ ⠙ Explore description trunc… 3🛠 ·5≤30⟳ ·12.0k(45%)·1h 2m 3s thinking…
|
|
319
|
+
```
|
|
320
|
+
|
|
304
321
|
Turn format uses `≤` and `⟳` glyphs (`5≤30⟳` = 5 of 30 turns). Token count uses compact notation (`12.0k`) with optional context-fill percent in parentheses. No "tokens" label — the glyphs are self-explanatory.
|
|
305
322
|
|
|
323
|
+
**Compact mode is active when:**
|
|
324
|
+
- **Force compact mode** is ON (in `/agents > Widget settings`), OR
|
|
325
|
+
- **Ctrl+o shortcut** is ON and the user has pressed ctrl+o to collapse tool expansion
|
|
326
|
+
|
|
327
|
+
Force compact always wins. When force compact is ON, ctrl+o state changes are ignored.
|
|
328
|
+
|
|
306
329
|
### Result Viewer
|
|
307
330
|
|
|
308
331
|
Fullscreen markdown viewer for agent results. Opens automatically when viewing a completed agent's result from the `/agents` menu.
|
|
@@ -311,6 +334,8 @@ Key bindings: `↑↓` navigate · `PgUp/PgDn` · `g`/`G` top/bottom · `f` togg
|
|
|
311
334
|
|
|
312
335
|
Stats line: ` ↑12.0k · ↓8.0k · W3.0k · $0.024 · 15 turns · 47s`
|
|
313
336
|
|
|
337
|
+
When **Cost display** is enabled (ON), agent stats show dollar cost: `✓ Builder·2🛠 ·5⟳ ·12.3k·$0.008·10s`. The status bar shows total agent cost: `agents: $0.008` or `2 agents: $0.008`.
|
|
338
|
+
|
|
314
339
|
## Configuration
|
|
315
340
|
|
|
316
341
|
`~/.pi/agent/subagents-lite.json` — managed via `/agents` menu, or edit directly:
|
|
@@ -320,6 +345,12 @@ Stats line: ` ↑12.0k · ↓8.0k · W3.0k · $0.024 · 15 turns · 47s`
|
|
|
320
345
|
"agent": {
|
|
321
346
|
"default": null,
|
|
322
347
|
"forceBackground": false,
|
|
348
|
+
"showCost": true,
|
|
349
|
+
"graceTurns": 6,
|
|
350
|
+
"widgetMaxLines": 12,
|
|
351
|
+
"widgetMaxLinesCompact": 6,
|
|
352
|
+
"widgetCompact": false,
|
|
353
|
+
"widgetShortcut": false,
|
|
323
354
|
"Explore": "anthropic/claude-haiku-4-5-20251001"
|
|
324
355
|
},
|
|
325
356
|
"concurrency": {
|
|
@@ -332,7 +363,18 @@ Stats line: ` ↑12.0k · ↓8.0k · W3.0k · $0.024 · 15 turns · 47s`
|
|
|
332
363
|
}
|
|
333
364
|
```
|
|
334
365
|
|
|
335
|
-
> **Note:** `agent.default` (global fallback), `agent.forceBackground` (flag), and per-type overrides like `"Explore"` are peers in the same object. Agent type names become dynamic keys alongside the special fields.
|
|
366
|
+
> **Note:** `agent.default` (global fallback), `agent.forceBackground` (flag), `agent.showCost` (toggle cost display), `agent.graceTurns` (grace turns after `max_turns` before hard abort), widget settings (`widgetMaxLines`, `widgetMaxLinesCompact`, `widgetCompact`, `widgetShortcut`), and per-type overrides like `"Explore"` are peers in the same object. Agent type names become dynamic keys alongside the special fields.
|
|
367
|
+
|
|
368
|
+
### Widget settings
|
|
369
|
+
|
|
370
|
+
| Field | Default | Description |
|
|
371
|
+
|---|---|---|
|
|
372
|
+
| `widgetMaxLines` | `12` | Maximum body lines in full mode (excluding the heading). |
|
|
373
|
+
| `widgetMaxLinesCompact` | half of `widgetMaxLines` | Maximum body lines in compact mode. |
|
|
374
|
+
| `widgetCompact` | `false` | Force compact mode regardless of ctrl+o state. |
|
|
375
|
+
| `widgetShortcut` | `false` | Opt-in: when ON, ctrl+o (tool expansion toggle) syncs with widget compact mode. When OFF, compact mode is manual-only via `widgetCompact`. |
|
|
376
|
+
|
|
377
|
+
> **Reload safety:** if a session reload (e.g. `/reload` or extension reload) kills running agents, the UI notifies you with the count of lost agents. Output logs and completed results are preserved on disk.
|
|
336
378
|
|
|
337
379
|
## StopAgent Tool
|
|
338
380
|
|
package/package.json
CHANGED
package/src/agent-manager.ts
CHANGED
|
@@ -12,12 +12,13 @@ import { createOutputFilePath, streamToOutputFile, writeInitialEntry } from "./o
|
|
|
12
12
|
import {
|
|
13
13
|
type AgentInvocation,
|
|
14
14
|
type AgentRecord,
|
|
15
|
+
type AgentStatus,
|
|
15
16
|
type CompactionInfo,
|
|
16
17
|
SHORT_ID_LENGTH,
|
|
17
18
|
type SubagentType,
|
|
18
19
|
type ThinkingLevel,
|
|
19
20
|
} from "./types.js";
|
|
20
|
-
import { addUsage, getLifetimeTotal, type LifetimeUsage } from "./usage.js";
|
|
21
|
+
import { addUsage, getLifetimeTotal, getSessionContextPercent, type LifetimeUsage } from "./usage.js";
|
|
21
22
|
import { errorMessage } from "./utils.js";
|
|
22
23
|
|
|
23
24
|
/** How often to check for expired agent records (milliseconds). */
|
|
@@ -47,16 +48,16 @@ function createOutputCleanup(
|
|
|
47
48
|
const outputStats = { turnCount: 0, toolUseCount: 0, totalTokens: 0, cost: 0 };
|
|
48
49
|
const cleanup = streamToOutputFile(session, path, outputStats);
|
|
49
50
|
return () => {
|
|
50
|
-
outputStats.turnCount = record.turnCount ?? 0;
|
|
51
|
-
outputStats.toolUseCount = record.toolUses;
|
|
52
|
-
outputStats.totalTokens = getLifetimeTotal(record.lifetimeUsage);
|
|
53
|
-
outputStats.cost = record.lifetimeUsage.cost;
|
|
51
|
+
outputStats.turnCount = record.stats.turnCount ?? 0;
|
|
52
|
+
outputStats.toolUseCount = record.stats.toolUses;
|
|
53
|
+
outputStats.totalTokens = getLifetimeTotal(record.stats.lifetimeUsage);
|
|
54
|
+
outputStats.cost = record.stats.lifetimeUsage.cost;
|
|
54
55
|
cleanup();
|
|
55
56
|
};
|
|
56
57
|
}
|
|
57
58
|
|
|
58
59
|
/** Whether the agent status is terminal (no longer running or queued). */
|
|
59
|
-
function isTerminalStatus(status:
|
|
60
|
+
function isTerminalStatus(status: AgentStatus): boolean {
|
|
60
61
|
return status !== "running" && status !== "queued";
|
|
61
62
|
}
|
|
62
63
|
|
|
@@ -93,6 +94,10 @@ export interface SpawnOptions {
|
|
|
93
94
|
maxTurns?: number;
|
|
94
95
|
thinkingLevel?: ThinkingLevel;
|
|
95
96
|
isBackground?: boolean;
|
|
97
|
+
/** Resolved worktree path — forwarded as cwd to runAgent. */
|
|
98
|
+
worktreePath?: string;
|
|
99
|
+
/** Short display label for the worktree (set on record display after spawn). */
|
|
100
|
+
worktreeLabel?: string;
|
|
96
101
|
/**
|
|
97
102
|
* Model key for concurrency pool lookup (e.g. "llamacpp/4b_small").
|
|
98
103
|
* When set, the agent is counted against that model's concurrency limit.
|
|
@@ -125,6 +130,9 @@ export class AgentManager {
|
|
|
125
130
|
private onComplete?: OnAgentComplete;
|
|
126
131
|
private onStart?: OnAgentStart;
|
|
127
132
|
|
|
133
|
+
/** Session-level cumulative agent cost. Survives agent eviction. */
|
|
134
|
+
private totalAgentCost = 0;
|
|
135
|
+
|
|
128
136
|
/** Per-model concurrency slots keyed by "provider/modelId". */
|
|
129
137
|
private concurrencySlots = new Map<string, ConcurrencySlot>();
|
|
130
138
|
|
|
@@ -248,16 +256,26 @@ export class AgentManager {
|
|
|
248
256
|
|
|
249
257
|
const record: AgentRecord = {
|
|
250
258
|
id,
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
259
|
+
lifecycle: {
|
|
260
|
+
status: queued ? "queued" : "running",
|
|
261
|
+
startedAt: Date.now(),
|
|
262
|
+
},
|
|
263
|
+
display: {
|
|
264
|
+
type,
|
|
265
|
+
description: options.description,
|
|
266
|
+
invocation: options.invocation,
|
|
267
|
+
worktreePath: options.worktreePath,
|
|
268
|
+
worktreeLabel: options.worktreeLabel,
|
|
269
|
+
},
|
|
270
|
+
execution: {
|
|
271
|
+
abortController,
|
|
272
|
+
},
|
|
273
|
+
stats: {
|
|
274
|
+
lifetimeUsage: { input: 0, output: 0, cacheWrite: 0, cost: 0 },
|
|
275
|
+
toolUses: 0,
|
|
276
|
+
compactionCount: 0,
|
|
277
|
+
maxTurns: options.maxTurns,
|
|
278
|
+
},
|
|
261
279
|
};
|
|
262
280
|
this.agents.set(id, record);
|
|
263
281
|
|
|
@@ -286,12 +304,12 @@ export class AgentManager {
|
|
|
286
304
|
) {
|
|
287
305
|
if (concurrencySlot) concurrencySlot.running++;
|
|
288
306
|
|
|
289
|
-
record.status = "running";
|
|
290
|
-
record.startedAt = Date.now();
|
|
307
|
+
record.lifecycle.status = "running";
|
|
308
|
+
record.lifecycle.startedAt = Date.now();
|
|
291
309
|
|
|
292
310
|
// Create output file for this agent
|
|
293
|
-
record.outputFile = createOutputFilePath(id);
|
|
294
|
-
writeInitialEntry(record.outputFile, prompt);
|
|
311
|
+
record.display.outputFile = createOutputFilePath(id);
|
|
312
|
+
writeInitialEntry(record.display.outputFile, prompt);
|
|
295
313
|
|
|
296
314
|
this.onStart?.(record);
|
|
297
315
|
|
|
@@ -306,30 +324,31 @@ export class AgentManager {
|
|
|
306
324
|
model: options.model,
|
|
307
325
|
maxTurns: options.maxTurns,
|
|
308
326
|
thinkingLevel: options.thinkingLevel,
|
|
327
|
+
cwd: options.worktreePath,
|
|
309
328
|
graceTurns: options.graceTurns,
|
|
310
|
-
signal: record.abortController!.signal,
|
|
329
|
+
signal: record.execution.abortController!.signal,
|
|
311
330
|
...this.createRecordCallbacks(record, options),
|
|
312
331
|
onTurnEnd: (turnCount) => {
|
|
313
|
-
record.turnCount = turnCount;
|
|
332
|
+
record.stats.turnCount = turnCount;
|
|
314
333
|
options.onTurnEnd?.(turnCount);
|
|
315
334
|
},
|
|
316
335
|
onTextDelta: options.onTextDelta,
|
|
317
336
|
onSessionCreated: (session) => {
|
|
318
|
-
record.session = session;
|
|
337
|
+
record.execution.session = session;
|
|
319
338
|
// Flush any steers that arrived before the session was ready
|
|
320
|
-
if (record.pendingSteers?.length) {
|
|
321
|
-
for (const msg of record.pendingSteers) {
|
|
339
|
+
if (record.execution.pendingSteers?.length) {
|
|
340
|
+
for (const msg of record.execution.pendingSteers) {
|
|
322
341
|
session.steer(msg).catch(() => {
|
|
323
342
|
// Steer is advisory — a failure here (e.g. session already aborting)
|
|
324
343
|
// is fine; the user can re-send if needed.
|
|
325
344
|
});
|
|
326
345
|
}
|
|
327
|
-
record.pendingSteers = undefined;
|
|
346
|
+
record.execution.pendingSteers = undefined;
|
|
328
347
|
}
|
|
329
348
|
// Stream session events to the output file
|
|
330
|
-
if (record.outputFile) {
|
|
331
|
-
record.outputCleanup = createOutputCleanup(
|
|
332
|
-
session, record.outputFile, record,
|
|
349
|
+
if (record.display.outputFile) {
|
|
350
|
+
record.execution.outputCleanup = createOutputCleanup(
|
|
351
|
+
session, record.display.outputFile, record,
|
|
333
352
|
);
|
|
334
353
|
}
|
|
335
354
|
options.onSessionCreated?.(session);
|
|
@@ -337,28 +356,29 @@ export class AgentManager {
|
|
|
337
356
|
})
|
|
338
357
|
.then(({ responseText, session, aborted, steered }) => {
|
|
339
358
|
// Don't overwrite status if externally stopped via abort()
|
|
340
|
-
if (record.status !== "stopped") {
|
|
341
|
-
record.status = aborted ? "aborted" : steered ? "steered" : "completed";
|
|
359
|
+
if (record.lifecycle.status !== "stopped") {
|
|
360
|
+
record.lifecycle.status = aborted ? "aborted" : steered ? "steered" : "completed";
|
|
342
361
|
}
|
|
343
362
|
record.result = responseText;
|
|
344
|
-
record.session = session;
|
|
345
|
-
record.
|
|
363
|
+
record.execution.session = session;
|
|
364
|
+
record.stats.contextPercent = getSessionContextPercent(session);
|
|
365
|
+
record.lifecycle.completedAt ??= Date.now();
|
|
346
366
|
return responseText;
|
|
347
367
|
})
|
|
348
368
|
.catch((err) => {
|
|
349
369
|
// Don't overwrite status if externally stopped via abort()
|
|
350
|
-
if (record.status !== "stopped") {
|
|
351
|
-
record.status = "error";
|
|
370
|
+
if (record.lifecycle.status !== "stopped") {
|
|
371
|
+
record.lifecycle.status = "error";
|
|
352
372
|
}
|
|
353
373
|
record.error = errorMessage(err);
|
|
354
|
-
record.completedAt ??= Date.now();
|
|
374
|
+
record.lifecycle.completedAt ??= Date.now();
|
|
355
375
|
return "";
|
|
356
376
|
})
|
|
357
377
|
.finally(() => {
|
|
358
378
|
// Final flush of streaming output file
|
|
359
|
-
if (record.outputCleanup) {
|
|
360
|
-
try { record.outputCleanup(); } catch { /* ignore */ }
|
|
361
|
-
record.outputCleanup = undefined;
|
|
379
|
+
if (record.execution.outputCleanup) {
|
|
380
|
+
try { record.execution.outputCleanup(); } catch { /* ignore */ }
|
|
381
|
+
record.execution.outputCleanup = undefined;
|
|
362
382
|
}
|
|
363
383
|
|
|
364
384
|
// Decrement per-model concurrency count
|
|
@@ -368,14 +388,20 @@ export class AgentManager {
|
|
|
368
388
|
this.drainQueue();
|
|
369
389
|
});
|
|
370
390
|
|
|
371
|
-
record.promise = promise;
|
|
391
|
+
record.execution.promise = promise;
|
|
372
392
|
}
|
|
373
393
|
|
|
374
394
|
/** Notify completion callback, ignoring any errors. */
|
|
375
395
|
private safeNotifyComplete(record: AgentRecord): void {
|
|
396
|
+
this.totalAgentCost += record.stats.lifetimeUsage.cost;
|
|
376
397
|
try { this.onComplete?.(record); } catch { /* ignore */ }
|
|
377
398
|
}
|
|
378
399
|
|
|
400
|
+
/** Get the session-level cumulative agent cost. Survives agent eviction. */
|
|
401
|
+
getTotalAgentCost(): number {
|
|
402
|
+
return this.totalAgentCost;
|
|
403
|
+
}
|
|
404
|
+
|
|
379
405
|
/**
|
|
380
406
|
* Build common record-tracking callbacks shared by startAgent.
|
|
381
407
|
* Updates the record's toolUses, lifetimeUsage, and compactionCount.
|
|
@@ -391,15 +417,15 @@ export class AgentManager {
|
|
|
391
417
|
} {
|
|
392
418
|
return {
|
|
393
419
|
onToolActivity: (activity) => {
|
|
394
|
-
if (activity.type === "end") record.toolUses++;
|
|
420
|
+
if (activity.type === "end") record.stats.toolUses++;
|
|
395
421
|
options?.onToolActivity?.(activity);
|
|
396
422
|
},
|
|
397
423
|
onAssistantUsage: (usage) => {
|
|
398
|
-
addUsage(record.lifetimeUsage, usage);
|
|
424
|
+
addUsage(record.stats.lifetimeUsage, usage);
|
|
399
425
|
options?.onAssistantUsage?.(usage);
|
|
400
426
|
},
|
|
401
427
|
onCompaction: (info) => {
|
|
402
|
-
record.compactionCount++;
|
|
428
|
+
record.stats.compactionCount++;
|
|
403
429
|
options?.onCompaction?.(info);
|
|
404
430
|
},
|
|
405
431
|
};
|
|
@@ -410,7 +436,7 @@ export class AgentManager {
|
|
|
410
436
|
const started = new Set<string>();
|
|
411
437
|
for (const entry of this.queue) {
|
|
412
438
|
const record = this.agents.get(entry.id);
|
|
413
|
-
if (!record || record.status !== "queued") continue;
|
|
439
|
+
if (!record || record.lifecycle.status !== "queued") continue;
|
|
414
440
|
|
|
415
441
|
const slot = this.getSlot(entry.modelKey);
|
|
416
442
|
if (slot.running >= slot.limit) continue;
|
|
@@ -420,9 +446,9 @@ export class AgentManager {
|
|
|
420
446
|
started.add(entry.id);
|
|
421
447
|
} catch (err) {
|
|
422
448
|
// Late failure — surface on the record so the user can see it
|
|
423
|
-
record.status = "error";
|
|
449
|
+
record.lifecycle.status = "error";
|
|
424
450
|
record.error = errorMessage(err);
|
|
425
|
-
record.completedAt = Date.now();
|
|
451
|
+
record.lifecycle.completedAt = Date.now();
|
|
426
452
|
started.add(entry.id);
|
|
427
453
|
this.safeNotifyComplete(record);
|
|
428
454
|
}
|
|
@@ -439,17 +465,17 @@ export class AgentManager {
|
|
|
439
465
|
const record = this.agents.get(id);
|
|
440
466
|
if (!record) return false;
|
|
441
467
|
|
|
442
|
-
if (record.status !== "running") return false;
|
|
468
|
+
if (record.lifecycle.status !== "running") return false;
|
|
443
469
|
|
|
444
|
-
if (!record.session) {
|
|
470
|
+
if (!record.execution.session) {
|
|
445
471
|
// Session not yet created — queue the steer
|
|
446
|
-
if (!record.pendingSteers) record.pendingSteers = [];
|
|
447
|
-
record.pendingSteers.push(message);
|
|
472
|
+
if (!record.execution.pendingSteers) record.execution.pendingSteers = [];
|
|
473
|
+
record.execution.pendingSteers.push(message);
|
|
448
474
|
return true;
|
|
449
475
|
}
|
|
450
476
|
|
|
451
477
|
try {
|
|
452
|
-
await record.session.steer(message);
|
|
478
|
+
await record.execution.session.steer(message);
|
|
453
479
|
return true;
|
|
454
480
|
} catch {
|
|
455
481
|
// steer failures are surfaced to the caller via the boolean return value
|
|
@@ -463,7 +489,7 @@ export class AgentManager {
|
|
|
463
489
|
|
|
464
490
|
listAgents(): AgentRecord[] {
|
|
465
491
|
return [...this.agents.values()].sort(
|
|
466
|
-
(a, b) => b.startedAt - a.startedAt,
|
|
492
|
+
(a, b) => b.lifecycle.startedAt - a.lifecycle.startedAt,
|
|
467
493
|
);
|
|
468
494
|
}
|
|
469
495
|
|
|
@@ -479,30 +505,30 @@ export class AgentManager {
|
|
|
479
505
|
* Returns true if the agent was stopped, false if it wasn't running/queued.
|
|
480
506
|
*/
|
|
481
507
|
private stopAgent(record: AgentRecord): boolean {
|
|
482
|
-
if (record.status === "queued") {
|
|
508
|
+
if (record.lifecycle.status === "queued") {
|
|
483
509
|
this.queue = this.queue.filter(q => q.id !== record.id);
|
|
484
|
-
} else if (record.status !== "running") {
|
|
510
|
+
} else if (record.lifecycle.status !== "running") {
|
|
485
511
|
return false;
|
|
486
512
|
} else {
|
|
487
|
-
record.abortController?.abort();
|
|
513
|
+
record.execution.abortController?.abort();
|
|
488
514
|
}
|
|
489
|
-
record.status = "stopped";
|
|
490
|
-
record.completedAt = Date.now();
|
|
515
|
+
record.lifecycle.status = "stopped";
|
|
516
|
+
record.lifecycle.completedAt = Date.now();
|
|
491
517
|
return true;
|
|
492
518
|
}
|
|
493
519
|
|
|
494
520
|
/** Dispose a record's session and remove it from the map. */
|
|
495
521
|
private removeRecord(id: string, record: AgentRecord): void {
|
|
496
|
-
record.session?.dispose();
|
|
497
|
-
record.session = undefined;
|
|
522
|
+
record.execution.session?.dispose();
|
|
523
|
+
record.execution.session = undefined;
|
|
498
524
|
this.agents.delete(id);
|
|
499
525
|
}
|
|
500
526
|
|
|
501
527
|
private cleanup() {
|
|
502
528
|
const cutoff = Date.now() - CLEANUP_AGE_CUTOFF_MS;
|
|
503
529
|
for (const [id, record] of this.agents) {
|
|
504
|
-
if (!isTerminalStatus(record.status)) continue;
|
|
505
|
-
if ((record.completedAt ?? 0) >= cutoff) continue;
|
|
530
|
+
if (!isTerminalStatus(record.lifecycle.status)) continue;
|
|
531
|
+
if ((record.lifecycle.completedAt ?? 0) >= cutoff) continue;
|
|
506
532
|
this.removeRecord(id, record);
|
|
507
533
|
}
|
|
508
534
|
}
|
|
@@ -511,7 +537,7 @@ export class AgentManager {
|
|
|
511
537
|
clearInterval(this.cleanupInterval);
|
|
512
538
|
this.queue = [];
|
|
513
539
|
for (const record of this.agents.values()) {
|
|
514
|
-
record.session?.dispose();
|
|
540
|
+
record.execution.session?.dispose();
|
|
515
541
|
}
|
|
516
542
|
this.agents.clear();
|
|
517
543
|
}
|