pi-subagents-lite 1.0.2 → 1.1.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 +43 -6
- package/package.json +1 -1
- package/src/agent-manager.ts +81 -62
- package/src/agent-runner.ts +194 -167
- 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 +124 -176
- package/src/menus.ts +188 -136
- package/src/model-precedence.ts +5 -0
- package/src/output-file.ts +1 -68
- package/src/renderer.ts +157 -0
- package/src/result-viewer.ts +2 -1
- package/src/state.ts +80 -0
- package/src/tool-execution.ts +145 -53
- package/src/types.ts +100 -31
- package/src/ui/agent-widget.ts +148 -145
- package/src/usage.ts +5 -0
- package/src/stop-agent-tool.ts +0 -77
package/README.md
CHANGED
|
@@ -30,10 +30,14 @@ Tool names like `Agent` and `StopAgent`, and parameter names like `prompt`, `des
|
|
|
30
30
|
- **Smart model resolution** — 6-level precedence: session → config → frontmatter → parent. Set once, forget
|
|
31
31
|
- **Concurrency control** — per-model and per-provider slot limits with automatic queuing
|
|
32
32
|
- **Cost tracking** — input/output/cache tokens and dollar cost per agent
|
|
33
|
+
- **Cost display** — toggle agent cost in stats and status bar (OFF by default)
|
|
33
34
|
- **Live widget** — persistent status bar above the editor showing running/completed agents
|
|
35
|
+
- **Widget settings** — force compact mode, max lines, opt-in ctrl+o sync
|
|
34
36
|
- **Result viewer** — fullscreen markdown viewer with stats
|
|
35
37
|
- **Steer** — inject mid-execution guidance into running agents
|
|
36
38
|
- **Output logs** — human-readable, `tail -f` friendly
|
|
39
|
+
- **Grace turns** — configurable grace turns after `max_turns` before hard abort
|
|
40
|
+
- **Reload safety** — warns when active agents are killed by session reload
|
|
37
41
|
|
|
38
42
|
## Install
|
|
39
43
|
|
|
@@ -278,11 +282,12 @@ The LLM never passes `model` — it's injected at call time via the `tool_call`
|
|
|
278
282
|
|
|
279
283
|
### `/agents`
|
|
280
284
|
|
|
281
|
-
Management menu with
|
|
285
|
+
Management menu with five sections:
|
|
282
286
|
|
|
283
|
-
- **Model settings** — global default, per-type overrides, force background mode
|
|
284
|
-
- **Concurrency** — default limit, per-provider and per-model slots
|
|
285
|
-
- **Running agents** — list
|
|
287
|
+
- **Model settings** — global default, per-type overrides, force background mode, cost display toggle, grace turns
|
|
288
|
+
- **Concurrency** — default limit, per-provider and per-model slots, reset to defaults
|
|
289
|
+
- **Running agents** — list with status and description; per-agent actions: view snapshot, view result, view error, steer, stop; bulk stop all running
|
|
290
|
+
- **Widget settings** — force compact mode, max lines (full/compact), ctrl+o shortcut
|
|
286
291
|
- **Debug** — agent types, agent briefing (sends capabilities to the LLM)
|
|
287
292
|
|
|
288
293
|
## Interface
|
|
@@ -294,15 +299,28 @@ Persistent bar above the editor showing running and completed agents. Updates li
|
|
|
294
299
|
- Running agents show a spinner, current tool activity, turn count, token usage (with optional context-fill percent), and elapsed time
|
|
295
300
|
- Completed agents show a check mark with final stats
|
|
296
301
|
- Click `tail -f` path to follow output logs in real time
|
|
302
|
+
- Two display modes: **full** (header + `tail -f` path + activity) and **compact** (single line, description truncated to 30 chars, activity inline)
|
|
297
303
|
|
|
298
|
-
|
|
304
|
+
**Full mode** (tree structure with branch connectors):
|
|
299
305
|
```
|
|
300
306
|
├─ ⠙ Explore description 3🛠 ·5≤30⟳ ·12.0k(45%)·1h 2m 3s
|
|
307
|
+
│ │ tail -f /tmp/pi-agent-outputs/...
|
|
301
308
|
│ └ thinking…
|
|
302
309
|
```
|
|
303
310
|
|
|
311
|
+
**Compact mode** (single line, description truncated):
|
|
312
|
+
```
|
|
313
|
+
├─ ⠙ Explore description trunc… 3🛠 ·5≤30⟳ ·12.0k(45%)·1h 2m 3s thinking…
|
|
314
|
+
```
|
|
315
|
+
|
|
304
316
|
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
317
|
|
|
318
|
+
**Compact mode is active when:**
|
|
319
|
+
- **Force compact mode** is ON (in `/agents > Widget settings`), OR
|
|
320
|
+
- **Ctrl+o shortcut** is ON and the user has pressed ctrl+o to collapse tool expansion
|
|
321
|
+
|
|
322
|
+
Force compact always wins. When force compact is ON, ctrl+o state changes are ignored.
|
|
323
|
+
|
|
306
324
|
### Result Viewer
|
|
307
325
|
|
|
308
326
|
Fullscreen markdown viewer for agent results. Opens automatically when viewing a completed agent's result from the `/agents` menu.
|
|
@@ -311,6 +329,8 @@ Key bindings: `↑↓` navigate · `PgUp/PgDn` · `g`/`G` top/bottom · `f` togg
|
|
|
311
329
|
|
|
312
330
|
Stats line: ` ↑12.0k · ↓8.0k · W3.0k · $0.024 · 15 turns · 47s`
|
|
313
331
|
|
|
332
|
+
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`.
|
|
333
|
+
|
|
314
334
|
## Configuration
|
|
315
335
|
|
|
316
336
|
`~/.pi/agent/subagents-lite.json` — managed via `/agents` menu, or edit directly:
|
|
@@ -320,6 +340,12 @@ Stats line: ` ↑12.0k · ↓8.0k · W3.0k · $0.024 · 15 turns · 47s`
|
|
|
320
340
|
"agent": {
|
|
321
341
|
"default": null,
|
|
322
342
|
"forceBackground": false,
|
|
343
|
+
"showCost": true,
|
|
344
|
+
"graceTurns": 6,
|
|
345
|
+
"widgetMaxLines": 12,
|
|
346
|
+
"widgetMaxLinesCompact": 6,
|
|
347
|
+
"widgetCompact": false,
|
|
348
|
+
"widgetShortcut": false,
|
|
323
349
|
"Explore": "anthropic/claude-haiku-4-5-20251001"
|
|
324
350
|
},
|
|
325
351
|
"concurrency": {
|
|
@@ -332,7 +358,18 @@ Stats line: ` ↑12.0k · ↓8.0k · W3.0k · $0.024 · 15 turns · 47s`
|
|
|
332
358
|
}
|
|
333
359
|
```
|
|
334
360
|
|
|
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.
|
|
361
|
+
> **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.
|
|
362
|
+
|
|
363
|
+
### Widget settings
|
|
364
|
+
|
|
365
|
+
| Field | Default | Description |
|
|
366
|
+
|---|---|---|
|
|
367
|
+
| `widgetMaxLines` | `12` | Maximum body lines in full mode (excluding the heading). |
|
|
368
|
+
| `widgetMaxLinesCompact` | half of `widgetMaxLines` | Maximum body lines in compact mode. |
|
|
369
|
+
| `widgetCompact` | `false` | Force compact mode regardless of ctrl+o state. |
|
|
370
|
+
| `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`. |
|
|
371
|
+
|
|
372
|
+
> **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
373
|
|
|
337
374
|
## StopAgent Tool
|
|
338
375
|
|
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
|
|
|
@@ -125,6 +126,9 @@ export class AgentManager {
|
|
|
125
126
|
private onComplete?: OnAgentComplete;
|
|
126
127
|
private onStart?: OnAgentStart;
|
|
127
128
|
|
|
129
|
+
/** Session-level cumulative agent cost. Survives agent eviction. */
|
|
130
|
+
private totalAgentCost = 0;
|
|
131
|
+
|
|
128
132
|
/** Per-model concurrency slots keyed by "provider/modelId". */
|
|
129
133
|
private concurrencySlots = new Map<string, ConcurrencySlot>();
|
|
130
134
|
|
|
@@ -248,16 +252,24 @@ export class AgentManager {
|
|
|
248
252
|
|
|
249
253
|
const record: AgentRecord = {
|
|
250
254
|
id,
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
255
|
+
lifecycle: {
|
|
256
|
+
status: queued ? "queued" : "running",
|
|
257
|
+
startedAt: Date.now(),
|
|
258
|
+
},
|
|
259
|
+
display: {
|
|
260
|
+
type,
|
|
261
|
+
description: options.description,
|
|
262
|
+
invocation: options.invocation,
|
|
263
|
+
},
|
|
264
|
+
execution: {
|
|
265
|
+
abortController,
|
|
266
|
+
},
|
|
267
|
+
stats: {
|
|
268
|
+
lifetimeUsage: { input: 0, output: 0, cacheWrite: 0, cost: 0 },
|
|
269
|
+
toolUses: 0,
|
|
270
|
+
compactionCount: 0,
|
|
271
|
+
maxTurns: options.maxTurns,
|
|
272
|
+
},
|
|
261
273
|
};
|
|
262
274
|
this.agents.set(id, record);
|
|
263
275
|
|
|
@@ -286,12 +298,12 @@ export class AgentManager {
|
|
|
286
298
|
) {
|
|
287
299
|
if (concurrencySlot) concurrencySlot.running++;
|
|
288
300
|
|
|
289
|
-
record.status = "running";
|
|
290
|
-
record.startedAt = Date.now();
|
|
301
|
+
record.lifecycle.status = "running";
|
|
302
|
+
record.lifecycle.startedAt = Date.now();
|
|
291
303
|
|
|
292
304
|
// Create output file for this agent
|
|
293
|
-
record.outputFile = createOutputFilePath(id);
|
|
294
|
-
writeInitialEntry(record.outputFile, prompt);
|
|
305
|
+
record.display.outputFile = createOutputFilePath(id);
|
|
306
|
+
writeInitialEntry(record.display.outputFile, prompt);
|
|
295
307
|
|
|
296
308
|
this.onStart?.(record);
|
|
297
309
|
|
|
@@ -307,29 +319,29 @@ export class AgentManager {
|
|
|
307
319
|
maxTurns: options.maxTurns,
|
|
308
320
|
thinkingLevel: options.thinkingLevel,
|
|
309
321
|
graceTurns: options.graceTurns,
|
|
310
|
-
signal: record.abortController!.signal,
|
|
322
|
+
signal: record.execution.abortController!.signal,
|
|
311
323
|
...this.createRecordCallbacks(record, options),
|
|
312
324
|
onTurnEnd: (turnCount) => {
|
|
313
|
-
record.turnCount = turnCount;
|
|
325
|
+
record.stats.turnCount = turnCount;
|
|
314
326
|
options.onTurnEnd?.(turnCount);
|
|
315
327
|
},
|
|
316
328
|
onTextDelta: options.onTextDelta,
|
|
317
329
|
onSessionCreated: (session) => {
|
|
318
|
-
record.session = session;
|
|
330
|
+
record.execution.session = session;
|
|
319
331
|
// Flush any steers that arrived before the session was ready
|
|
320
|
-
if (record.pendingSteers?.length) {
|
|
321
|
-
for (const msg of record.pendingSteers) {
|
|
332
|
+
if (record.execution.pendingSteers?.length) {
|
|
333
|
+
for (const msg of record.execution.pendingSteers) {
|
|
322
334
|
session.steer(msg).catch(() => {
|
|
323
335
|
// Steer is advisory — a failure here (e.g. session already aborting)
|
|
324
336
|
// is fine; the user can re-send if needed.
|
|
325
337
|
});
|
|
326
338
|
}
|
|
327
|
-
record.pendingSteers = undefined;
|
|
339
|
+
record.execution.pendingSteers = undefined;
|
|
328
340
|
}
|
|
329
341
|
// Stream session events to the output file
|
|
330
|
-
if (record.outputFile) {
|
|
331
|
-
record.outputCleanup = createOutputCleanup(
|
|
332
|
-
session, record.outputFile, record,
|
|
342
|
+
if (record.display.outputFile) {
|
|
343
|
+
record.execution.outputCleanup = createOutputCleanup(
|
|
344
|
+
session, record.display.outputFile, record,
|
|
333
345
|
);
|
|
334
346
|
}
|
|
335
347
|
options.onSessionCreated?.(session);
|
|
@@ -337,28 +349,29 @@ export class AgentManager {
|
|
|
337
349
|
})
|
|
338
350
|
.then(({ responseText, session, aborted, steered }) => {
|
|
339
351
|
// Don't overwrite status if externally stopped via abort()
|
|
340
|
-
if (record.status !== "stopped") {
|
|
341
|
-
record.status = aborted ? "aborted" : steered ? "steered" : "completed";
|
|
352
|
+
if (record.lifecycle.status !== "stopped") {
|
|
353
|
+
record.lifecycle.status = aborted ? "aborted" : steered ? "steered" : "completed";
|
|
342
354
|
}
|
|
343
355
|
record.result = responseText;
|
|
344
|
-
record.session = session;
|
|
345
|
-
record.
|
|
356
|
+
record.execution.session = session;
|
|
357
|
+
record.stats.contextPercent = getSessionContextPercent(session);
|
|
358
|
+
record.lifecycle.completedAt ??= Date.now();
|
|
346
359
|
return responseText;
|
|
347
360
|
})
|
|
348
361
|
.catch((err) => {
|
|
349
362
|
// Don't overwrite status if externally stopped via abort()
|
|
350
|
-
if (record.status !== "stopped") {
|
|
351
|
-
record.status = "error";
|
|
363
|
+
if (record.lifecycle.status !== "stopped") {
|
|
364
|
+
record.lifecycle.status = "error";
|
|
352
365
|
}
|
|
353
366
|
record.error = errorMessage(err);
|
|
354
|
-
record.completedAt ??= Date.now();
|
|
367
|
+
record.lifecycle.completedAt ??= Date.now();
|
|
355
368
|
return "";
|
|
356
369
|
})
|
|
357
370
|
.finally(() => {
|
|
358
371
|
// Final flush of streaming output file
|
|
359
|
-
if (record.outputCleanup) {
|
|
360
|
-
try { record.outputCleanup(); } catch { /* ignore */ }
|
|
361
|
-
record.outputCleanup = undefined;
|
|
372
|
+
if (record.execution.outputCleanup) {
|
|
373
|
+
try { record.execution.outputCleanup(); } catch { /* ignore */ }
|
|
374
|
+
record.execution.outputCleanup = undefined;
|
|
362
375
|
}
|
|
363
376
|
|
|
364
377
|
// Decrement per-model concurrency count
|
|
@@ -368,14 +381,20 @@ export class AgentManager {
|
|
|
368
381
|
this.drainQueue();
|
|
369
382
|
});
|
|
370
383
|
|
|
371
|
-
record.promise = promise;
|
|
384
|
+
record.execution.promise = promise;
|
|
372
385
|
}
|
|
373
386
|
|
|
374
387
|
/** Notify completion callback, ignoring any errors. */
|
|
375
388
|
private safeNotifyComplete(record: AgentRecord): void {
|
|
389
|
+
this.totalAgentCost += record.stats.lifetimeUsage.cost;
|
|
376
390
|
try { this.onComplete?.(record); } catch { /* ignore */ }
|
|
377
391
|
}
|
|
378
392
|
|
|
393
|
+
/** Get the session-level cumulative agent cost. Survives agent eviction. */
|
|
394
|
+
getTotalAgentCost(): number {
|
|
395
|
+
return this.totalAgentCost;
|
|
396
|
+
}
|
|
397
|
+
|
|
379
398
|
/**
|
|
380
399
|
* Build common record-tracking callbacks shared by startAgent.
|
|
381
400
|
* Updates the record's toolUses, lifetimeUsage, and compactionCount.
|
|
@@ -391,15 +410,15 @@ export class AgentManager {
|
|
|
391
410
|
} {
|
|
392
411
|
return {
|
|
393
412
|
onToolActivity: (activity) => {
|
|
394
|
-
if (activity.type === "end") record.toolUses++;
|
|
413
|
+
if (activity.type === "end") record.stats.toolUses++;
|
|
395
414
|
options?.onToolActivity?.(activity);
|
|
396
415
|
},
|
|
397
416
|
onAssistantUsage: (usage) => {
|
|
398
|
-
addUsage(record.lifetimeUsage, usage);
|
|
417
|
+
addUsage(record.stats.lifetimeUsage, usage);
|
|
399
418
|
options?.onAssistantUsage?.(usage);
|
|
400
419
|
},
|
|
401
420
|
onCompaction: (info) => {
|
|
402
|
-
record.compactionCount++;
|
|
421
|
+
record.stats.compactionCount++;
|
|
403
422
|
options?.onCompaction?.(info);
|
|
404
423
|
},
|
|
405
424
|
};
|
|
@@ -410,7 +429,7 @@ export class AgentManager {
|
|
|
410
429
|
const started = new Set<string>();
|
|
411
430
|
for (const entry of this.queue) {
|
|
412
431
|
const record = this.agents.get(entry.id);
|
|
413
|
-
if (!record || record.status !== "queued") continue;
|
|
432
|
+
if (!record || record.lifecycle.status !== "queued") continue;
|
|
414
433
|
|
|
415
434
|
const slot = this.getSlot(entry.modelKey);
|
|
416
435
|
if (slot.running >= slot.limit) continue;
|
|
@@ -420,9 +439,9 @@ export class AgentManager {
|
|
|
420
439
|
started.add(entry.id);
|
|
421
440
|
} catch (err) {
|
|
422
441
|
// Late failure — surface on the record so the user can see it
|
|
423
|
-
record.status = "error";
|
|
442
|
+
record.lifecycle.status = "error";
|
|
424
443
|
record.error = errorMessage(err);
|
|
425
|
-
record.completedAt = Date.now();
|
|
444
|
+
record.lifecycle.completedAt = Date.now();
|
|
426
445
|
started.add(entry.id);
|
|
427
446
|
this.safeNotifyComplete(record);
|
|
428
447
|
}
|
|
@@ -439,17 +458,17 @@ export class AgentManager {
|
|
|
439
458
|
const record = this.agents.get(id);
|
|
440
459
|
if (!record) return false;
|
|
441
460
|
|
|
442
|
-
if (record.status !== "running") return false;
|
|
461
|
+
if (record.lifecycle.status !== "running") return false;
|
|
443
462
|
|
|
444
|
-
if (!record.session) {
|
|
463
|
+
if (!record.execution.session) {
|
|
445
464
|
// Session not yet created — queue the steer
|
|
446
|
-
if (!record.pendingSteers) record.pendingSteers = [];
|
|
447
|
-
record.pendingSteers.push(message);
|
|
465
|
+
if (!record.execution.pendingSteers) record.execution.pendingSteers = [];
|
|
466
|
+
record.execution.pendingSteers.push(message);
|
|
448
467
|
return true;
|
|
449
468
|
}
|
|
450
469
|
|
|
451
470
|
try {
|
|
452
|
-
await record.session.steer(message);
|
|
471
|
+
await record.execution.session.steer(message);
|
|
453
472
|
return true;
|
|
454
473
|
} catch {
|
|
455
474
|
// steer failures are surfaced to the caller via the boolean return value
|
|
@@ -463,7 +482,7 @@ export class AgentManager {
|
|
|
463
482
|
|
|
464
483
|
listAgents(): AgentRecord[] {
|
|
465
484
|
return [...this.agents.values()].sort(
|
|
466
|
-
(a, b) => b.startedAt - a.startedAt,
|
|
485
|
+
(a, b) => b.lifecycle.startedAt - a.lifecycle.startedAt,
|
|
467
486
|
);
|
|
468
487
|
}
|
|
469
488
|
|
|
@@ -479,30 +498,30 @@ export class AgentManager {
|
|
|
479
498
|
* Returns true if the agent was stopped, false if it wasn't running/queued.
|
|
480
499
|
*/
|
|
481
500
|
private stopAgent(record: AgentRecord): boolean {
|
|
482
|
-
if (record.status === "queued") {
|
|
501
|
+
if (record.lifecycle.status === "queued") {
|
|
483
502
|
this.queue = this.queue.filter(q => q.id !== record.id);
|
|
484
|
-
} else if (record.status !== "running") {
|
|
503
|
+
} else if (record.lifecycle.status !== "running") {
|
|
485
504
|
return false;
|
|
486
505
|
} else {
|
|
487
|
-
record.abortController?.abort();
|
|
506
|
+
record.execution.abortController?.abort();
|
|
488
507
|
}
|
|
489
|
-
record.status = "stopped";
|
|
490
|
-
record.completedAt = Date.now();
|
|
508
|
+
record.lifecycle.status = "stopped";
|
|
509
|
+
record.lifecycle.completedAt = Date.now();
|
|
491
510
|
return true;
|
|
492
511
|
}
|
|
493
512
|
|
|
494
513
|
/** Dispose a record's session and remove it from the map. */
|
|
495
514
|
private removeRecord(id: string, record: AgentRecord): void {
|
|
496
|
-
record.session?.dispose();
|
|
497
|
-
record.session = undefined;
|
|
515
|
+
record.execution.session?.dispose();
|
|
516
|
+
record.execution.session = undefined;
|
|
498
517
|
this.agents.delete(id);
|
|
499
518
|
}
|
|
500
519
|
|
|
501
520
|
private cleanup() {
|
|
502
521
|
const cutoff = Date.now() - CLEANUP_AGE_CUTOFF_MS;
|
|
503
522
|
for (const [id, record] of this.agents) {
|
|
504
|
-
if (!isTerminalStatus(record.status)) continue;
|
|
505
|
-
if ((record.completedAt ?? 0) >= cutoff) continue;
|
|
523
|
+
if (!isTerminalStatus(record.lifecycle.status)) continue;
|
|
524
|
+
if ((record.lifecycle.completedAt ?? 0) >= cutoff) continue;
|
|
506
525
|
this.removeRecord(id, record);
|
|
507
526
|
}
|
|
508
527
|
}
|
|
@@ -511,7 +530,7 @@ export class AgentManager {
|
|
|
511
530
|
clearInterval(this.cleanupInterval);
|
|
512
531
|
this.queue = [];
|
|
513
532
|
for (const record of this.agents.values()) {
|
|
514
|
-
record.session?.dispose();
|
|
533
|
+
record.execution.session?.dispose();
|
|
515
534
|
}
|
|
516
535
|
this.agents.clear();
|
|
517
536
|
}
|