@clanker-code/pi-subagents 0.10.8 → 0.11.1
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/AGENTS.md +2 -0
- package/CHANGELOG.md +29 -0
- package/README.md +22 -2
- package/dist/agent-manager.d.ts +11 -0
- package/dist/agent-manager.js +55 -22
- package/dist/agent-runner.d.ts +14 -0
- package/dist/agent-runner.js +50 -4
- package/dist/agent-tool-description.d.ts +7 -1
- package/dist/agent-tool-description.js +3 -3
- package/dist/cross-extension-rpc.d.ts +4 -0
- package/dist/cross-extension-rpc.js +11 -1
- package/dist/dashboard-ui.d.ts +15 -0
- package/dist/dashboard-ui.js +231 -0
- package/dist/default-agents.js +0 -1
- package/dist/index.js +104 -13
- package/dist/peek.js +8 -2
- package/dist/schedule.d.ts +9 -1
- package/dist/schedule.js +7 -1
- package/dist/subagent-list-clear.d.ts +57 -0
- package/dist/subagent-list-clear.js +331 -0
- package/dist/ui/agent-tool-rendering.js +1 -1
- package/dist/ui/agent-widget-tree.js +19 -2
- package/dist/ui/agent-widget.d.ts +7 -1
- package/dist/ui/agent-widget.js +52 -10
- package/package.json +1 -1
- package/src/agent-manager.ts +48 -13
- package/src/agent-runner.ts +59 -3
- package/src/agent-tool-description.ts +10 -4
- package/src/cross-extension-rpc.ts +14 -1
- package/src/dashboard-ui.ts +291 -0
- package/src/default-agents.ts +0 -1
- package/src/index.ts +121 -17
- package/src/peek.ts +7 -2
- package/src/schedule.ts +20 -1
- package/src/subagent-list-clear.ts +405 -0
- package/src/ui/agent-tool-rendering.ts +1 -1
- package/src/ui/agent-widget-tree.ts +16 -2
- package/src/ui/agent-widget.ts +50 -10
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dashboard-ui.ts — Register dashboard UI modules for subagent visibility.
|
|
3
|
+
*
|
|
4
|
+
* Provides three integration points with pi-agent-dashboard:
|
|
5
|
+
* 1. Footer-segment decorator showing running/completed agent counts
|
|
6
|
+
* 2. Management-modal module with a table view of all subagent history
|
|
7
|
+
* 3. Round-trip event handlers for data fetch, abort, and steer actions
|
|
8
|
+
*/
|
|
9
|
+
import { formatMs, getDisplayName } from "./ui/agent-widget.js";
|
|
10
|
+
import { getLifetimeTotal } from "./usage.js";
|
|
11
|
+
const NAMESPACE = "subagents";
|
|
12
|
+
const MODULE_ID = "subagents-overview";
|
|
13
|
+
const DATA_EVENT = "subagents:rows";
|
|
14
|
+
const INVALIDATE_DEBOUNCE_MS = 500;
|
|
15
|
+
/**
|
|
16
|
+
* Build a row for the management-modal table from an AgentRecord.
|
|
17
|
+
*/
|
|
18
|
+
function buildAgentRow(record) {
|
|
19
|
+
const durationMs = record.completedAt
|
|
20
|
+
? record.completedAt - record.startedAt
|
|
21
|
+
: Date.now() - record.startedAt;
|
|
22
|
+
const totalTokens = getLifetimeTotal(record.lifetimeUsage);
|
|
23
|
+
return {
|
|
24
|
+
id: record.id,
|
|
25
|
+
type: getDisplayName(record.type),
|
|
26
|
+
description: record.description ?? "",
|
|
27
|
+
model: record.invocation?.modelName ?? "—",
|
|
28
|
+
status: record.status,
|
|
29
|
+
toolUses: record.toolUses ?? 0,
|
|
30
|
+
tokens: totalTokens > 0 ? formatTokenCount(totalTokens) : "—",
|
|
31
|
+
duration: formatMs(durationMs),
|
|
32
|
+
outputFile: record.outputFile ?? "",
|
|
33
|
+
startedAt: record.startedAt,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
function formatTokenCount(n) {
|
|
37
|
+
if (n >= 1_000_000)
|
|
38
|
+
return `${(n / 1_000_000).toFixed(1)}M`;
|
|
39
|
+
if (n >= 1_000)
|
|
40
|
+
return `${(n / 1_000).toFixed(1)}k`;
|
|
41
|
+
return String(n);
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Register all dashboard UI integration points.
|
|
45
|
+
* Call once during extension setup when pi.events is available.
|
|
46
|
+
*/
|
|
47
|
+
export function registerDashboardModules(pi, manager) {
|
|
48
|
+
if (!pi.events)
|
|
49
|
+
return;
|
|
50
|
+
let invalidateTimer;
|
|
51
|
+
function scheduleInvalidate() {
|
|
52
|
+
if (invalidateTimer)
|
|
53
|
+
return;
|
|
54
|
+
invalidateTimer = setTimeout(() => {
|
|
55
|
+
invalidateTimer = undefined;
|
|
56
|
+
pi.events.emit("ui:invalidate", {});
|
|
57
|
+
}, INVALIDATE_DEBOUNCE_MS);
|
|
58
|
+
}
|
|
59
|
+
// ── 1. Module Discovery (ui:list-modules) ──────────────────────────
|
|
60
|
+
// Guard against duplicate pushes: the bridge may call refreshUiModules
|
|
61
|
+
// multiple times per probe cycle when multiple sessions each register
|
|
62
|
+
// their own ui:invalidate listener. Check if our modules are already
|
|
63
|
+
// present before pushing.
|
|
64
|
+
pi.events.on("ui:list-modules", ((probe) => {
|
|
65
|
+
const alreadyContributed = probe.modules.some((m) => m.kind === "management-modal" && m.id === MODULE_ID);
|
|
66
|
+
if (alreadyContributed)
|
|
67
|
+
return;
|
|
68
|
+
const agents = manager.listAgents();
|
|
69
|
+
const running = agents.filter(a => a.status === "running").length;
|
|
70
|
+
const completed = agents.filter(a => a.status === "completed").length;
|
|
71
|
+
const total = agents.length;
|
|
72
|
+
// Footer-segment: running/completed counts
|
|
73
|
+
const parts = [];
|
|
74
|
+
if (running > 0)
|
|
75
|
+
parts.push(`● ${running} running`);
|
|
76
|
+
if (completed > 0)
|
|
77
|
+
parts.push(`✓ ${completed} done`);
|
|
78
|
+
if (total === 0)
|
|
79
|
+
parts.push("No agents");
|
|
80
|
+
probe.modules.push({
|
|
81
|
+
kind: "footer-segment",
|
|
82
|
+
namespace: NAMESPACE,
|
|
83
|
+
id: "agent-counts",
|
|
84
|
+
payload: {
|
|
85
|
+
text: parts.join(" · "),
|
|
86
|
+
tooltip: `${total} total agents (${running} running, ${completed} completed)`,
|
|
87
|
+
icon: "mdiRobot",
|
|
88
|
+
},
|
|
89
|
+
});
|
|
90
|
+
// Management-modal: subagent overview table
|
|
91
|
+
probe.modules.push({
|
|
92
|
+
kind: "management-modal",
|
|
93
|
+
id: MODULE_ID,
|
|
94
|
+
command: "/subagents",
|
|
95
|
+
title: "Subagents",
|
|
96
|
+
description: "View and manage background subagents",
|
|
97
|
+
icon: "mdiRobotOutline",
|
|
98
|
+
category: "subagents",
|
|
99
|
+
view: {
|
|
100
|
+
kind: "table",
|
|
101
|
+
dataEvent: DATA_EVENT,
|
|
102
|
+
rowKey: "id",
|
|
103
|
+
fields: [
|
|
104
|
+
{ key: "id", label: "ID", kind: "text", width: 120 },
|
|
105
|
+
{ key: "type", label: "Type", kind: "text", width: 100 },
|
|
106
|
+
{ key: "description", label: "Description", kind: "text" },
|
|
107
|
+
{ key: "model", label: "Model", kind: "text", width: 80 },
|
|
108
|
+
{ key: "status", label: "Status", kind: "text", width: 90 },
|
|
109
|
+
{ key: "toolUses", label: "Tools", kind: "number", width: 60 },
|
|
110
|
+
{ key: "tokens", label: "Tokens", kind: "text", width: 80 },
|
|
111
|
+
{ key: "duration", label: "Duration", kind: "text", width: 80 },
|
|
112
|
+
],
|
|
113
|
+
rowActions: [
|
|
114
|
+
{
|
|
115
|
+
id: "view-result",
|
|
116
|
+
label: "View Result",
|
|
117
|
+
icon: "mdiEye",
|
|
118
|
+
variant: "primary",
|
|
119
|
+
event: "subagents:ui:view-result",
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
id: "abort",
|
|
123
|
+
label: "Abort",
|
|
124
|
+
icon: "mdiStop",
|
|
125
|
+
variant: "danger",
|
|
126
|
+
event: "subagents:ui:abort",
|
|
127
|
+
confirm: "Abort this running agent?",
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
id: "steer",
|
|
131
|
+
label: "Steer",
|
|
132
|
+
icon: "mdiMessageArrowRight",
|
|
133
|
+
variant: "secondary",
|
|
134
|
+
event: "subagents:ui:steer",
|
|
135
|
+
},
|
|
136
|
+
],
|
|
137
|
+
emptyState: "No subagents have been spawned in this session.",
|
|
138
|
+
actions: [
|
|
139
|
+
{
|
|
140
|
+
id: "refresh",
|
|
141
|
+
label: "Refresh",
|
|
142
|
+
icon: "mdiRefresh",
|
|
143
|
+
variant: "secondary",
|
|
144
|
+
event: "subagents:ui:refresh",
|
|
145
|
+
},
|
|
146
|
+
],
|
|
147
|
+
},
|
|
148
|
+
});
|
|
149
|
+
}));
|
|
150
|
+
// ── 2. Data Fetch Handler ──────────────────────────────────────────
|
|
151
|
+
pi.events.on(DATA_EVENT, ((data) => {
|
|
152
|
+
const agents = manager.listAgents();
|
|
153
|
+
data.items = agents.map(buildAgentRow);
|
|
154
|
+
}));
|
|
155
|
+
// ── 3. Action Handlers ─────────────────────────────────────────────
|
|
156
|
+
// Refresh: just invalidate to re-probe + re-fetch
|
|
157
|
+
pi.events.on("subagents:ui:refresh", (() => {
|
|
158
|
+
scheduleInvalidate();
|
|
159
|
+
}));
|
|
160
|
+
// View Result: return the agent's result as table rows so the modal
|
|
161
|
+
// displays it. The bridge's synchronous fast path calls `_reply(items)`
|
|
162
|
+
// when `data.items` is populated by the handler — do NOT call
|
|
163
|
+
// `scheduleInvalidate()` here as the subsequent re-probe would
|
|
164
|
+
// overwrite the returned rows with the original table data.
|
|
165
|
+
pi.events.on("subagents:ui:view-result", ((data) => {
|
|
166
|
+
// Bridge spreads msg.params into data; row identity is at data.row.id.
|
|
167
|
+
const agentId = data.row?.id ?? data.id;
|
|
168
|
+
if (!agentId)
|
|
169
|
+
return;
|
|
170
|
+
const record = manager.getRecord(agentId);
|
|
171
|
+
if (!record)
|
|
172
|
+
return;
|
|
173
|
+
const resultText = record.result?.trim() || "No output yet.";
|
|
174
|
+
const preview = resultText.length > 2000
|
|
175
|
+
? resultText.slice(0, 2000) + "\n…(truncated)"
|
|
176
|
+
: resultText;
|
|
177
|
+
// Populate data.items — the bridge's synchronous fast path forwards
|
|
178
|
+
// this as a `ui_data_list` message back to the dashboard.
|
|
179
|
+
data.items = [{
|
|
180
|
+
id: record.id,
|
|
181
|
+
type: getDisplayName(record.type),
|
|
182
|
+
description: record.description,
|
|
183
|
+
status: record.status,
|
|
184
|
+
result: preview,
|
|
185
|
+
outputFile: record.outputFile ?? "",
|
|
186
|
+
}];
|
|
187
|
+
}));
|
|
188
|
+
// Abort: stop the running agent via the manager's abort() method
|
|
189
|
+
// which properly cancels the AbortController and cleans up state.
|
|
190
|
+
pi.events.on("subagents:ui:abort", ((data) => {
|
|
191
|
+
const agentId = data.row?.id ?? data.id;
|
|
192
|
+
if (!agentId)
|
|
193
|
+
return;
|
|
194
|
+
manager.abort(agentId);
|
|
195
|
+
scheduleInvalidate();
|
|
196
|
+
}));
|
|
197
|
+
// Steer: send a steering message to a running agent's session.
|
|
198
|
+
// The management-modal row action carries the row identity; we steer
|
|
199
|
+
// with a default "Continue" nudge. A future form view could accept
|
|
200
|
+
// custom text.
|
|
201
|
+
pi.events.on("subagents:ui:steer", ((data) => {
|
|
202
|
+
const agentId = data.row?.id ?? data.id;
|
|
203
|
+
if (!agentId)
|
|
204
|
+
return;
|
|
205
|
+
const record = manager.getRecord(agentId);
|
|
206
|
+
if (!record)
|
|
207
|
+
return;
|
|
208
|
+
if (record.status === "running" && record.session) {
|
|
209
|
+
// Session is live — steer immediately
|
|
210
|
+
record.session.steer("Continue").catch(() => { });
|
|
211
|
+
}
|
|
212
|
+
else if (record.status === "queued") {
|
|
213
|
+
// Session not yet created — queue the steer for flush on start
|
|
214
|
+
if (!record.pendingSteers)
|
|
215
|
+
record.pendingSteers = [];
|
|
216
|
+
record.pendingSteers.push("Continue");
|
|
217
|
+
}
|
|
218
|
+
scheduleInvalidate();
|
|
219
|
+
}));
|
|
220
|
+
// ── 4. Invalidate on agent lifecycle events ────────────────────────
|
|
221
|
+
const lifecycleEvents = [
|
|
222
|
+
"subagents:created",
|
|
223
|
+
"subagents:started",
|
|
224
|
+
"subagents:completed",
|
|
225
|
+
"subagents:failed",
|
|
226
|
+
"subagents:compacted",
|
|
227
|
+
];
|
|
228
|
+
for (const event of lifecycleEvents) {
|
|
229
|
+
pi.events.on(event, (() => scheduleInvalidate()));
|
|
230
|
+
}
|
|
231
|
+
}
|
package/dist/default-agents.js
CHANGED
|
@@ -30,7 +30,6 @@ export const DEFAULT_AGENTS = new Map([
|
|
|
30
30
|
builtinToolNames: READ_ONLY_TOOLS,
|
|
31
31
|
extensions: true,
|
|
32
32
|
skills: true,
|
|
33
|
-
model: "anthropic/claude-haiku-4-5-20251001",
|
|
34
33
|
systemPrompt: `# CRITICAL: READ-ONLY MODE - NO FILE MODIFICATIONS
|
|
35
34
|
You are a file search specialist. You excel at thoroughly navigating and exploring codebases.
|
|
36
35
|
Your role is EXCLUSIVELY to search and analyze existing code. You do NOT have access to file editing tools.
|
package/dist/index.js
CHANGED
|
@@ -23,8 +23,10 @@ import { getAgentConversation, getCurrentExtensionAgentId, getCurrentExtensionDe
|
|
|
23
23
|
import { buildAgentToolDescription, getModelLabelFromConfig } from "./agent-tool-description.js";
|
|
24
24
|
import { BUILTIN_TOOL_NAMES, getAgentConfig, getAllTypes, getAvailableTypes, isDefaultsDisabled, registerAgents, resolveType, setDefaultsDisabled } from "./agent-types.js";
|
|
25
25
|
import { formatOutputFileHint, limitText, MAX_RESULT_CHARS, MAX_VERBOSE_CHARS } from "./bounded-output.js";
|
|
26
|
+
import { extractText } from "./context.js";
|
|
26
27
|
import { registerRpcHandlers } from "./cross-extension-rpc.js";
|
|
27
28
|
import { loadCustomAgents } from "./custom-agents.js";
|
|
29
|
+
import { registerDashboardModules } from "./dashboard-ui.js";
|
|
28
30
|
import { isModelInScope, readEnabledModels, resolveEnabledModels } from "./enabled-models.js";
|
|
29
31
|
import { GroupJoinManager } from "./group-join.js";
|
|
30
32
|
import { resolveAgentInvocationConfig, resolveJoinMode } from "./invocation-config.js";
|
|
@@ -36,8 +38,9 @@ import { SubagentScheduler } from "./schedule.js";
|
|
|
36
38
|
import { resolveStorePath, ScheduleStore } from "./schedule-store.js";
|
|
37
39
|
import { applyAndEmitLoaded, DEFAULT_WAIT_TIMEOUT_SECONDS, saveAndEmitChanged } from "./settings.js";
|
|
38
40
|
import { getStatusNote } from "./status-note.js";
|
|
41
|
+
import { registerSubagentListClearTools } from "./subagent-list-clear.js";
|
|
39
42
|
import { MAX_RECURSIVE_DEPTH } from "./types.js";
|
|
40
|
-
import { renderAgentCall, renderAgentResult, renderSteerCall, tailPreview } from "./ui/agent-tool-rendering.js";
|
|
43
|
+
import { renderAgentCall, renderAgentResult, renderSteerCall, snipMiddleLines, tailPreview } from "./ui/agent-tool-rendering.js";
|
|
41
44
|
import { AgentWidget, buildInvocationTags, describeActivity, formatContextWindow, formatDuration, getDisplayName, getPromptModeLabel, } from "./ui/agent-widget.js";
|
|
42
45
|
import { menuSelect } from "./ui/menu-select.js";
|
|
43
46
|
import { showSchedulesMenu } from "./ui/schedule-menu.js";
|
|
@@ -376,7 +379,7 @@ export default function (pi) {
|
|
|
376
379
|
return; // sessionId not yet available — try again on next event
|
|
377
380
|
const path = resolveStorePath(ctx.cwd, sessionId);
|
|
378
381
|
const store = new ScheduleStore(path);
|
|
379
|
-
scheduler.start(pi, ctx, manager, store);
|
|
382
|
+
scheduler.start(pi, ctx, manager, store, { depth: nextSubagentDepth, parentAgentId: extensionAgentId });
|
|
380
383
|
pi.events.emit("subagents:scheduler_ready", { sessionId, jobCount: store.list().length });
|
|
381
384
|
}
|
|
382
385
|
catch (err) {
|
|
@@ -388,6 +391,8 @@ export default function (pi) {
|
|
|
388
391
|
// Capture ctx from session_start for RPC spawn handler + start the scheduler.
|
|
389
392
|
pi.on("session_start", async (_event, ctx) => {
|
|
390
393
|
currentCtx = ctx;
|
|
394
|
+
clearBatchState();
|
|
395
|
+
groupJoin.dispose();
|
|
391
396
|
manager.clearCompleted();
|
|
392
397
|
widget.clearSnapshots();
|
|
393
398
|
retryStash.clear();
|
|
@@ -395,8 +400,10 @@ export default function (pi) {
|
|
|
395
400
|
startScheduler(ctx);
|
|
396
401
|
});
|
|
397
402
|
pi.on("session_before_switch", () => {
|
|
403
|
+
clearBatchState();
|
|
404
|
+
groupJoin.dispose();
|
|
398
405
|
manager.clearCompleted();
|
|
399
|
-
widget.
|
|
406
|
+
widget.dispose();
|
|
400
407
|
retryStash.clear();
|
|
401
408
|
scheduler.stop();
|
|
402
409
|
});
|
|
@@ -405,6 +412,8 @@ export default function (pi) {
|
|
|
405
412
|
pi,
|
|
406
413
|
getCtx: () => currentCtx,
|
|
407
414
|
manager,
|
|
415
|
+
depth: nextSubagentDepth,
|
|
416
|
+
parentAgentId: extensionAgentId,
|
|
408
417
|
});
|
|
409
418
|
// Broadcast readiness so extensions loaded after us can discover us
|
|
410
419
|
pi.events.emit("subagents:ready", {});
|
|
@@ -414,13 +423,17 @@ export default function (pi) {
|
|
|
414
423
|
unsubSpawnRpc();
|
|
415
424
|
unsubStopRpc();
|
|
416
425
|
unsubPingRpc();
|
|
426
|
+
unsubWidgetCreated?.();
|
|
417
427
|
unsubWidgetStarted?.();
|
|
418
428
|
unsubWidgetCompleted?.();
|
|
419
429
|
unsubWidgetFailed?.();
|
|
420
430
|
currentCtx = undefined;
|
|
421
431
|
delete globalThis[MANAGER_KEY];
|
|
422
432
|
scheduler.stop();
|
|
433
|
+
clearBatchState();
|
|
434
|
+
groupJoin.dispose();
|
|
423
435
|
manager.abortAll();
|
|
436
|
+
widget.dispose();
|
|
424
437
|
for (const timer of pendingNudges.values())
|
|
425
438
|
clearTimeout(timer);
|
|
426
439
|
pendingNudges.clear();
|
|
@@ -434,6 +447,7 @@ export default function (pi) {
|
|
|
434
447
|
if (snapshot)
|
|
435
448
|
widget.upsertSnapshot(snapshot);
|
|
436
449
|
};
|
|
450
|
+
const unsubWidgetCreated = pi.events.on("subagents:created", upsertWidgetEventSnapshot);
|
|
437
451
|
const unsubWidgetStarted = pi.events.on("subagents:started", upsertWidgetEventSnapshot);
|
|
438
452
|
const unsubWidgetCompleted = pi.events.on("subagents:completed", upsertWidgetEventSnapshot);
|
|
439
453
|
const unsubWidgetFailed = pi.events.on("subagents:failed", upsertWidgetEventSnapshot);
|
|
@@ -501,6 +515,13 @@ export default function (pi) {
|
|
|
501
515
|
let currentBatchAgents = [];
|
|
502
516
|
let batchFinalizeTimer;
|
|
503
517
|
let batchCounter = 0;
|
|
518
|
+
function clearBatchState() {
|
|
519
|
+
if (batchFinalizeTimer) {
|
|
520
|
+
clearTimeout(batchFinalizeTimer);
|
|
521
|
+
batchFinalizeTimer = undefined;
|
|
522
|
+
}
|
|
523
|
+
currentBatchAgents = [];
|
|
524
|
+
}
|
|
504
525
|
/** Finalize the current batch: if 2+ smart-mode agents, register as a group. */
|
|
505
526
|
function finalizeBatch() {
|
|
506
527
|
batchFinalizeTimer = undefined;
|
|
@@ -575,7 +596,7 @@ export default function (pi) {
|
|
|
575
596
|
const scheduleParam = isSchedulingEnabled() ? scheduleParamShape : {};
|
|
576
597
|
const agentToolDescription = buildAgentToolDescription({
|
|
577
598
|
mode: getToolDescriptionMode(),
|
|
578
|
-
|
|
599
|
+
nextSubagentDepth,
|
|
579
600
|
schedulingEnabled: isSchedulingEnabled(),
|
|
580
601
|
});
|
|
581
602
|
pi.registerTool(defineTool({
|
|
@@ -597,7 +618,7 @@ export default function (pi) {
|
|
|
597
618
|
description: "A short (3-5 word) description of the task (shown in UI).",
|
|
598
619
|
}),
|
|
599
620
|
subagent_type: Type.Optional(Type.String({
|
|
600
|
-
description: `The type of specialized agent to use. Available types: ${getAvailableTypes().join(", ")}. Custom agents from .pi/agents/*.md (project) or ${getAgentDir()}/agents/*.md (global) are also available. OMIT when retrying (preserved by the handle) unless you want to override it.`,
|
|
621
|
+
description: `The type of specialized agent to use. Defaults to general-purpose when omitted. Available types: ${getAvailableTypes().join(", ")}. Custom agents from .pi/agents/*.md (project) or ${getAgentDir()}/agents/*.md (global) are also available. OMIT when retrying (preserved by the handle) unless you want to override it.`,
|
|
601
622
|
})),
|
|
602
623
|
model: Type.Optional(Type.String({
|
|
603
624
|
description: 'Optional model override. Accepts "provider/modelId" or fuzzy name (e.g. "haiku", "sonnet"). Omit to use the agent type\'s default.',
|
|
@@ -673,14 +694,14 @@ export default function (pi) {
|
|
|
673
694
|
const { retry: _omit, ...overrides } = params;
|
|
674
695
|
P = { ...stashed.params, ...overrides };
|
|
675
696
|
}
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
697
|
+
const requestedSubagentType = (P.subagent_type ?? "general-purpose");
|
|
698
|
+
emitPromptPreview(P.prompt, P.description, requestedSubagentType);
|
|
699
|
+
// Retry supplied the prompt from the stash; otherwise prompt is required.
|
|
700
|
+
// subagent_type defaults to general-purpose when omitted.
|
|
701
|
+
if (!retryHandle && !P.prompt) {
|
|
702
|
+
return textResult("Missing required argument: prompt.");
|
|
682
703
|
}
|
|
683
|
-
const rawType =
|
|
704
|
+
const rawType = requestedSubagentType;
|
|
684
705
|
const resolved = resolveType(rawType);
|
|
685
706
|
if (!resolved) {
|
|
686
707
|
// Unknown agent type — recoverable. List valid types so the orchestrator
|
|
@@ -853,6 +874,7 @@ export default function (pi) {
|
|
|
853
874
|
invocation: agentInvocation,
|
|
854
875
|
depth: nextSubagentDepth,
|
|
855
876
|
parentAgentId: extensionAgentId,
|
|
877
|
+
eventBus: pi.events,
|
|
856
878
|
outputFileForAgent: (agentId) => createOutputFilePath(ctx.cwd, agentId, ctx.sessionManager.getSessionId()),
|
|
857
879
|
onOutputFileCreated: (outputFile, agentId) => writeInitialEntry(outputFile, agentId, P.prompt, ctx.cwd),
|
|
858
880
|
...bgCallbacks,
|
|
@@ -896,6 +918,10 @@ export default function (pi) {
|
|
|
896
918
|
isBackground: true,
|
|
897
919
|
depth: record?.depth ?? nextSubagentDepth,
|
|
898
920
|
parentAgentId: extensionAgentId,
|
|
921
|
+
status: record?.status ?? "running",
|
|
922
|
+
startedAt: record?.startedAt,
|
|
923
|
+
toolUses: record?.toolUses ?? 0,
|
|
924
|
+
invocation: record?.invocation,
|
|
899
925
|
});
|
|
900
926
|
const isQueued = record?.status === "queued";
|
|
901
927
|
return textResult(`Agent ${isQueued ? "queued" : "started"} in background.\nAgent ID: ${id}\nType: ${displayName}\nDescription: ${P.description}\n` +
|
|
@@ -928,6 +954,56 @@ export default function (pi) {
|
|
|
928
954
|
description: "Return a lightweight tail/filter view of the agent's result or live output file, with line numbers. Ignored when verbose is true.",
|
|
929
955
|
})),
|
|
930
956
|
}),
|
|
957
|
+
renderResult(result, { expanded }, theme) {
|
|
958
|
+
const details = result.details;
|
|
959
|
+
const text = extractText(result.content);
|
|
960
|
+
// Header: status + stats + description
|
|
961
|
+
let line = "";
|
|
962
|
+
if (details) {
|
|
963
|
+
const icon = details.status === "error" || details.status === "stopped" || details.status === "aborted"
|
|
964
|
+
? theme.fg("error", "✗")
|
|
965
|
+
: details.status === "running" || details.status === "queued"
|
|
966
|
+
? theme.fg("accent", "◌")
|
|
967
|
+
: theme.fg("success", "✓");
|
|
968
|
+
const parts = [];
|
|
969
|
+
if (details.toolUses > 0)
|
|
970
|
+
parts.push(`${details.toolUses} tool use${details.toolUses === 1 ? "" : "s"}`);
|
|
971
|
+
if (details.tokens)
|
|
972
|
+
parts.push(details.tokens);
|
|
973
|
+
if (details.contextPercent !== null)
|
|
974
|
+
parts.push(`ctx ${Math.round(details.contextPercent)}%`);
|
|
975
|
+
if (details.duration)
|
|
976
|
+
parts.push(details.duration);
|
|
977
|
+
const stats = parts.map(p => theme.fg("dim", p)).join(" " + theme.fg("dim", "·") + " ");
|
|
978
|
+
line = `${icon} ${theme.bold(details.description)} ${theme.fg("dim", details.status)}`;
|
|
979
|
+
if (stats)
|
|
980
|
+
line += "\n " + stats;
|
|
981
|
+
}
|
|
982
|
+
// Body: snip when collapsed, full when expanded
|
|
983
|
+
// Extract the body portion (after the first blank line) to keep the
|
|
984
|
+
// tool-output header always visible and only snip the actual result.
|
|
985
|
+
if (text.trim()) {
|
|
986
|
+
const firstBlank = text.indexOf("\n\n");
|
|
987
|
+
const body = firstBlank >= 0 ? text.slice(firstBlank + 2) : text;
|
|
988
|
+
if (expanded) {
|
|
989
|
+
for (const l of text.split("\n")) {
|
|
990
|
+
line += "\n" + theme.fg("dim", ` ${l}`);
|
|
991
|
+
}
|
|
992
|
+
}
|
|
993
|
+
else {
|
|
994
|
+
// Show the tool-output header verbatim, then snip only the body
|
|
995
|
+
if (firstBlank >= 0) {
|
|
996
|
+
for (const l of text.slice(0, firstBlank).split("\n")) {
|
|
997
|
+
line += "\n" + theme.fg("dim", ` ${l}`);
|
|
998
|
+
}
|
|
999
|
+
}
|
|
1000
|
+
for (const l of snipMiddleLines(body, 20)) {
|
|
1001
|
+
line += "\n" + theme.fg("dim", ` ${l}`);
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
return new Text(line, 0, 0);
|
|
1006
|
+
},
|
|
931
1007
|
execute: async (_toolCallId, params, signal, _onUpdate, ctx) => {
|
|
932
1008
|
const record = manager.getRecord(params.agent_id);
|
|
933
1009
|
if (!record) {
|
|
@@ -974,6 +1050,15 @@ export default function (pi) {
|
|
|
974
1050
|
if (record.compactionCount)
|
|
975
1051
|
statsParts.push(`Compactions: ${record.compactionCount}`);
|
|
976
1052
|
statsParts.push(`Duration: ${duration}`);
|
|
1053
|
+
const details = {
|
|
1054
|
+
status: record.status,
|
|
1055
|
+
description: record.description,
|
|
1056
|
+
toolUses: record.toolUses,
|
|
1057
|
+
tokens: tokens || null,
|
|
1058
|
+
contextPercent,
|
|
1059
|
+
duration,
|
|
1060
|
+
outputFile: record.outputFile,
|
|
1061
|
+
};
|
|
977
1062
|
let output = `Agent: ${record.id}\n` +
|
|
978
1063
|
`Type: ${displayName} | Status: ${record.status}${getStatusNote(record.status)} | ${statsParts.join(" | ")}\n` +
|
|
979
1064
|
`Description: ${record.description}\n` +
|
|
@@ -1014,7 +1099,7 @@ export default function (pi) {
|
|
|
1014
1099
|
}
|
|
1015
1100
|
}
|
|
1016
1101
|
}
|
|
1017
|
-
return textResult(output);
|
|
1102
|
+
return textResult(output, details);
|
|
1018
1103
|
},
|
|
1019
1104
|
}));
|
|
1020
1105
|
// ---- steer_subagent tool ----
|
|
@@ -1072,6 +1157,9 @@ export default function (pi) {
|
|
|
1072
1157
|
}
|
|
1073
1158
|
},
|
|
1074
1159
|
}));
|
|
1160
|
+
// ---- list_subagents / clear_subagents tools ----
|
|
1161
|
+
registerSubagentListClearTools(pi, manager);
|
|
1162
|
+
registerDashboardModules(pi, manager);
|
|
1075
1163
|
// ---- list_models tool ----
|
|
1076
1164
|
pi.registerTool(defineTool({
|
|
1077
1165
|
name: SUBAGENT_TOOL_NAMES.LIST_MODELS,
|
|
@@ -1547,6 +1635,9 @@ Write the file using the write tool. Only write the file, nothing else.`;
|
|
|
1547
1635
|
const record = await manager.spawnAndWait(pi, ctx, "general-purpose", generatePrompt, {
|
|
1548
1636
|
description: `Generate ${name} agent`,
|
|
1549
1637
|
maxTurns: 5,
|
|
1638
|
+
eventBus: pi.events,
|
|
1639
|
+
depth: nextSubagentDepth,
|
|
1640
|
+
parentAgentId: extensionAgentId,
|
|
1550
1641
|
});
|
|
1551
1642
|
if (record.status === "error") {
|
|
1552
1643
|
ctx.ui.notify(`Generation failed: ${record.error}`, "warning");
|
package/dist/peek.js
CHANGED
|
@@ -81,6 +81,12 @@ function parseOutputFileLines(path) {
|
|
|
81
81
|
return [];
|
|
82
82
|
}
|
|
83
83
|
const out = [];
|
|
84
|
+
const pushRenderedLines = (text) => {
|
|
85
|
+
for (const renderedLine of text.trimEnd().split("\n")) {
|
|
86
|
+
if (renderedLine.trim())
|
|
87
|
+
out.push(renderedLine);
|
|
88
|
+
}
|
|
89
|
+
};
|
|
84
90
|
for (const line of raw.split("\n")) {
|
|
85
91
|
const trimmed = line.trim();
|
|
86
92
|
if (!trimmed)
|
|
@@ -96,12 +102,12 @@ function parseOutputFileLines(path) {
|
|
|
96
102
|
if (!Array.isArray(content)) {
|
|
97
103
|
// Some entries may carry a plain string content.
|
|
98
104
|
if (typeof content === "string" && content.trim())
|
|
99
|
-
|
|
105
|
+
pushRenderedLines(content);
|
|
100
106
|
continue;
|
|
101
107
|
}
|
|
102
108
|
for (const block of content) {
|
|
103
109
|
if (block?.type === "text" && typeof block.text === "string" && block.text.trim()) {
|
|
104
|
-
|
|
110
|
+
pushRenderedLines(block.text);
|
|
105
111
|
}
|
|
106
112
|
}
|
|
107
113
|
}
|
package/dist/schedule.d.ts
CHANGED
|
@@ -51,6 +51,12 @@ export interface NewJobInput {
|
|
|
51
51
|
isolated?: boolean;
|
|
52
52
|
isolation?: IsolationMode;
|
|
53
53
|
}
|
|
54
|
+
interface SchedulerSpawnDefaults {
|
|
55
|
+
/** Recursive depth for scheduled subagents fired from this session. */
|
|
56
|
+
depth?: number;
|
|
57
|
+
/** Parent subagent id for scheduled subagents fired from this session. */
|
|
58
|
+
parentAgentId?: string;
|
|
59
|
+
}
|
|
54
60
|
export declare class SubagentScheduler {
|
|
55
61
|
private jobs;
|
|
56
62
|
private intervals;
|
|
@@ -58,8 +64,9 @@ export declare class SubagentScheduler {
|
|
|
58
64
|
private pi;
|
|
59
65
|
private ctx;
|
|
60
66
|
private manager;
|
|
67
|
+
private spawnDefaults;
|
|
61
68
|
/** Start the scheduler: bind to a session's store and arm enabled jobs. */
|
|
62
|
-
start(pi: ExtensionAPI, ctx: ExtensionContext, manager: AgentManager, store: ScheduleStore): void;
|
|
69
|
+
start(pi: ExtensionAPI, ctx: ExtensionContext, manager: AgentManager, store: ScheduleStore, spawnDefaults?: SchedulerSpawnDefaults): void;
|
|
63
70
|
/** Stop all timers; drop refs. Safe to call repeatedly. */
|
|
64
71
|
stop(): void;
|
|
65
72
|
/** True if start() has bound a store and the scheduler is active. */
|
|
@@ -107,3 +114,4 @@ export declare class SubagentScheduler {
|
|
|
107
114
|
/** "10s"/"5m"/"1h"/"2d" → milliseconds. */
|
|
108
115
|
static parseInterval(s: string): number | null;
|
|
109
116
|
}
|
|
117
|
+
export {};
|
package/dist/schedule.js
CHANGED
|
@@ -24,12 +24,14 @@ export class SubagentScheduler {
|
|
|
24
24
|
pi;
|
|
25
25
|
ctx;
|
|
26
26
|
manager;
|
|
27
|
+
spawnDefaults = {};
|
|
27
28
|
/** Start the scheduler: bind to a session's store and arm enabled jobs. */
|
|
28
|
-
start(pi, ctx, manager, store) {
|
|
29
|
+
start(pi, ctx, manager, store, spawnDefaults = {}) {
|
|
29
30
|
this.pi = pi;
|
|
30
31
|
this.ctx = ctx;
|
|
31
32
|
this.manager = manager;
|
|
32
33
|
this.store = store;
|
|
34
|
+
this.spawnDefaults = spawnDefaults;
|
|
33
35
|
for (const job of store.list()) {
|
|
34
36
|
if (job.enabled)
|
|
35
37
|
this.scheduleJob(job);
|
|
@@ -47,6 +49,7 @@ export class SubagentScheduler {
|
|
|
47
49
|
this.pi = undefined;
|
|
48
50
|
this.ctx = undefined;
|
|
49
51
|
this.manager = undefined;
|
|
52
|
+
this.spawnDefaults = {};
|
|
50
53
|
}
|
|
51
54
|
/** True if start() has bound a store and the scheduler is active. */
|
|
52
55
|
isActive() {
|
|
@@ -222,6 +225,9 @@ export class SubagentScheduler {
|
|
|
222
225
|
isolated: job.isolated,
|
|
223
226
|
thinkingLevel: job.thinking,
|
|
224
227
|
isolation: job.isolation,
|
|
228
|
+
eventBus: pi.events,
|
|
229
|
+
depth: this.spawnDefaults.depth,
|
|
230
|
+
parentAgentId: this.spawnDefaults.parentAgentId,
|
|
225
231
|
});
|
|
226
232
|
}
|
|
227
233
|
catch (err) {
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { type ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import type { Component } from "@earendil-works/pi-tui";
|
|
3
|
+
import type { AgentManager } from "./agent-manager.js";
|
|
4
|
+
import type { AgentRecord } from "./types.js";
|
|
5
|
+
export interface ListSubagentsOptions {
|
|
6
|
+
all?: boolean;
|
|
7
|
+
now?: number;
|
|
8
|
+
recentSuccessLimit?: number;
|
|
9
|
+
}
|
|
10
|
+
export interface ListSubagentsAgentDetails {
|
|
11
|
+
id: string;
|
|
12
|
+
type: AgentRecord["type"];
|
|
13
|
+
description: string;
|
|
14
|
+
status: AgentRecord["status"];
|
|
15
|
+
startedAt: number;
|
|
16
|
+
completedAt?: number;
|
|
17
|
+
}
|
|
18
|
+
export interface ListSubagentsDetails {
|
|
19
|
+
total: number;
|
|
20
|
+
all: boolean;
|
|
21
|
+
visible: ListSubagentsAgentDetails[];
|
|
22
|
+
hiddenDoneCount: number;
|
|
23
|
+
activeCount: number;
|
|
24
|
+
problemCount: number;
|
|
25
|
+
recentDoneCount: number;
|
|
26
|
+
now: number;
|
|
27
|
+
}
|
|
28
|
+
export interface ClearSubagentsOptions {
|
|
29
|
+
agentIds?: string[];
|
|
30
|
+
now?: number;
|
|
31
|
+
olderThanMs?: number;
|
|
32
|
+
includeErrors?: boolean;
|
|
33
|
+
}
|
|
34
|
+
export interface ClearSelectionResult {
|
|
35
|
+
clearIds: string[];
|
|
36
|
+
errors: string[];
|
|
37
|
+
requestedCount: number;
|
|
38
|
+
keptActiveCount: number;
|
|
39
|
+
keptFailedCount: number;
|
|
40
|
+
keptYoungSuccessCount: number;
|
|
41
|
+
}
|
|
42
|
+
export interface ClearSubagentsDetails extends ClearSelectionResult {
|
|
43
|
+
clearedCount: number;
|
|
44
|
+
}
|
|
45
|
+
export type RenderTheme = {
|
|
46
|
+
fg(color: string, text: string): string;
|
|
47
|
+
bold(text: string): string;
|
|
48
|
+
};
|
|
49
|
+
export declare function buildListSubagentsDetails(records: AgentRecord[], options?: ListSubagentsOptions): ListSubagentsDetails;
|
|
50
|
+
export declare function clearSubagentRecords(records: AgentRecord[], options?: ClearSubagentsOptions): ClearSelectionResult;
|
|
51
|
+
export declare function buildClearSubagentsDetails(result: ClearSelectionResult): ClearSubagentsDetails;
|
|
52
|
+
export declare function renderListSubagentsDetails(details: ListSubagentsDetails, theme: RenderTheme): Component;
|
|
53
|
+
export declare function renderClearSubagentsDetails(details: ClearSubagentsDetails, theme: RenderTheme): Component;
|
|
54
|
+
export declare function renderEmptyCall(): Component;
|
|
55
|
+
export declare function formatListSubagentsText(details: ListSubagentsDetails): string;
|
|
56
|
+
export declare function formatClearSubagentsText(details: ClearSubagentsDetails): string;
|
|
57
|
+
export declare function registerSubagentListClearTools(pi: ExtensionAPI, manager: AgentManager): void;
|