@clanker-code/pi-subagents 0.10.8 → 0.11.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/AGENTS.md +2 -0
- package/CHANGELOG.md +17 -0
- package/README.md +22 -2
- package/bugs.txt +57 -0
- package/dist/agent-manager.d.ts +8 -0
- package/dist/agent-manager.js +54 -22
- package/dist/agent-runner.d.ts +3 -0
- package/dist/agent-runner.js +12 -3
- package/dist/dashboard-ui.d.ts +15 -0
- package/dist/dashboard-ui.js +206 -0
- package/dist/default-agents.js +0 -1
- package/dist/index.js +96 -11
- package/dist/peek.js +8 -2
- 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 +44 -13
- package/src/agent-runner.ts +14 -3
- package/src/dashboard-ui.ts +270 -0
- package/src/default-agents.ts +0 -1
- package/src/index.ts +113 -15
- package/src/peek.ts +7 -2
- 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,270 @@
|
|
|
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
|
+
|
|
10
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
11
|
+
import type { AgentManager } from "./agent-manager.js";
|
|
12
|
+
import { formatMs, getDisplayName } from "./ui/agent-widget.js";
|
|
13
|
+
import { getLifetimeTotal } from "./usage.js";
|
|
14
|
+
|
|
15
|
+
// Dashboard shared types (inlined to avoid adding a dependency)
|
|
16
|
+
interface DecoratorDescriptor {
|
|
17
|
+
kind: "footer-segment" | "agent-metric" | "breadcrumb" | "gate" | "toast";
|
|
18
|
+
namespace: string;
|
|
19
|
+
id: string;
|
|
20
|
+
payload: Record<string, unknown>;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
interface ExtensionUiModule {
|
|
24
|
+
kind: "management-modal";
|
|
25
|
+
id: string;
|
|
26
|
+
command: string;
|
|
27
|
+
title: string;
|
|
28
|
+
description?: string;
|
|
29
|
+
icon?: string;
|
|
30
|
+
category?: string;
|
|
31
|
+
view: {
|
|
32
|
+
kind: "table" | "grid" | "form";
|
|
33
|
+
dataEvent?: string;
|
|
34
|
+
rowKey?: string;
|
|
35
|
+
fields?: Array<{
|
|
36
|
+
key: string;
|
|
37
|
+
label: string;
|
|
38
|
+
kind: string;
|
|
39
|
+
width?: string | number;
|
|
40
|
+
}>;
|
|
41
|
+
rowActions?: Array<{
|
|
42
|
+
id: string;
|
|
43
|
+
label: string;
|
|
44
|
+
icon?: string;
|
|
45
|
+
variant?: "primary" | "secondary" | "danger";
|
|
46
|
+
event: string;
|
|
47
|
+
confirm?: string;
|
|
48
|
+
}>;
|
|
49
|
+
emptyState?: string;
|
|
50
|
+
actions?: Array<{
|
|
51
|
+
id: string;
|
|
52
|
+
label: string;
|
|
53
|
+
icon?: string;
|
|
54
|
+
variant?: "primary" | "secondary" | "danger";
|
|
55
|
+
event: string;
|
|
56
|
+
}>;
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
type ModuleProbe = { modules: Array<ExtensionUiModule | DecoratorDescriptor> };
|
|
61
|
+
|
|
62
|
+
const NAMESPACE = "subagents";
|
|
63
|
+
const MODULE_ID = "subagents-overview";
|
|
64
|
+
const DATA_EVENT = "subagents:rows";
|
|
65
|
+
const INVALIDATE_DEBOUNCE_MS = 500;
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Build a row for the management-modal table from an AgentRecord.
|
|
69
|
+
*/
|
|
70
|
+
function buildAgentRow(record: any) {
|
|
71
|
+
const durationMs = record.completedAt
|
|
72
|
+
? record.completedAt - record.startedAt
|
|
73
|
+
: Date.now() - record.startedAt;
|
|
74
|
+
const totalTokens = getLifetimeTotal(record.lifetimeUsage);
|
|
75
|
+
|
|
76
|
+
return {
|
|
77
|
+
id: record.id,
|
|
78
|
+
type: getDisplayName(record.type),
|
|
79
|
+
description: record.description ?? "",
|
|
80
|
+
status: record.status,
|
|
81
|
+
toolUses: record.toolUses ?? 0,
|
|
82
|
+
tokens: totalTokens > 0 ? formatTokenCount(totalTokens) : "—",
|
|
83
|
+
duration: formatMs(durationMs),
|
|
84
|
+
outputFile: record.outputFile ?? "",
|
|
85
|
+
startedAt: record.startedAt,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function formatTokenCount(n: number): string {
|
|
90
|
+
if (n >= 1_000_000) return `${(n / 1_000_000).toFixed(1)}M`;
|
|
91
|
+
if (n >= 1_000) return `${(n / 1_000).toFixed(1)}k`;
|
|
92
|
+
return String(n);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Register all dashboard UI integration points.
|
|
97
|
+
* Call once during extension setup when pi.events is available.
|
|
98
|
+
*/
|
|
99
|
+
export function registerDashboardModules(pi: ExtensionAPI, manager: AgentManager): void {
|
|
100
|
+
if (!pi.events) return;
|
|
101
|
+
|
|
102
|
+
let invalidateTimer: ReturnType<typeof setTimeout> | undefined;
|
|
103
|
+
|
|
104
|
+
function scheduleInvalidate() {
|
|
105
|
+
if (invalidateTimer) return;
|
|
106
|
+
invalidateTimer = setTimeout(() => {
|
|
107
|
+
invalidateTimer = undefined;
|
|
108
|
+
pi.events.emit("ui:invalidate", {});
|
|
109
|
+
}, INVALIDATE_DEBOUNCE_MS);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// ── 1. Module Discovery (ui:list-modules) ──────────────────────────
|
|
113
|
+
pi.events.on("ui:list-modules", ((probe: ModuleProbe) => {
|
|
114
|
+
const agents = manager.listAgents();
|
|
115
|
+
const running = agents.filter(a => a.status === "running").length;
|
|
116
|
+
const completed = agents.filter(a => a.status === "completed").length;
|
|
117
|
+
const total = agents.length;
|
|
118
|
+
|
|
119
|
+
// Footer-segment: running/completed counts
|
|
120
|
+
const parts: string[] = [];
|
|
121
|
+
if (running > 0) parts.push(`● ${running} running`);
|
|
122
|
+
if (completed > 0) parts.push(`✓ ${completed} done`);
|
|
123
|
+
if (total === 0) parts.push("No agents");
|
|
124
|
+
|
|
125
|
+
probe.modules.push({
|
|
126
|
+
kind: "footer-segment",
|
|
127
|
+
namespace: NAMESPACE,
|
|
128
|
+
id: "agent-counts",
|
|
129
|
+
payload: {
|
|
130
|
+
text: parts.join(" · "),
|
|
131
|
+
tooltip: `${total} total agents (${running} running, ${completed} completed)`,
|
|
132
|
+
icon: "mdiRobot",
|
|
133
|
+
},
|
|
134
|
+
} as DecoratorDescriptor);
|
|
135
|
+
|
|
136
|
+
// Management-modal: subagent overview table
|
|
137
|
+
probe.modules.push({
|
|
138
|
+
kind: "management-modal",
|
|
139
|
+
id: MODULE_ID,
|
|
140
|
+
command: "/subagents",
|
|
141
|
+
title: "Subagents",
|
|
142
|
+
description: "View and manage background subagents",
|
|
143
|
+
icon: "mdiRobotOutline",
|
|
144
|
+
category: "subagents",
|
|
145
|
+
view: {
|
|
146
|
+
kind: "table",
|
|
147
|
+
dataEvent: DATA_EVENT,
|
|
148
|
+
rowKey: "id",
|
|
149
|
+
fields: [
|
|
150
|
+
{ key: "id", label: "ID", kind: "text", width: 120 },
|
|
151
|
+
{ key: "type", label: "Type", kind: "text", width: 100 },
|
|
152
|
+
{ key: "description", label: "Description", kind: "text" },
|
|
153
|
+
{ key: "status", label: "Status", kind: "text", width: 90 },
|
|
154
|
+
{ key: "toolUses", label: "Tools", kind: "number", width: 60 },
|
|
155
|
+
{ key: "tokens", label: "Tokens", kind: "text", width: 80 },
|
|
156
|
+
{ key: "duration", label: "Duration", kind: "text", width: 80 },
|
|
157
|
+
],
|
|
158
|
+
rowActions: [
|
|
159
|
+
{
|
|
160
|
+
id: "view-result",
|
|
161
|
+
label: "View Result",
|
|
162
|
+
icon: "mdiEye",
|
|
163
|
+
variant: "primary",
|
|
164
|
+
event: "subagents:ui:view-result",
|
|
165
|
+
},
|
|
166
|
+
{
|
|
167
|
+
id: "abort",
|
|
168
|
+
label: "Abort",
|
|
169
|
+
icon: "mdiStop",
|
|
170
|
+
variant: "danger",
|
|
171
|
+
event: "subagents:ui:abort",
|
|
172
|
+
confirm: "Abort this running agent?",
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
id: "steer",
|
|
176
|
+
label: "Steer",
|
|
177
|
+
icon: "mdiMessageArrowRight",
|
|
178
|
+
variant: "secondary",
|
|
179
|
+
event: "subagents:ui:steer",
|
|
180
|
+
},
|
|
181
|
+
],
|
|
182
|
+
emptyState: "No subagents have been spawned in this session.",
|
|
183
|
+
actions: [
|
|
184
|
+
{
|
|
185
|
+
id: "refresh",
|
|
186
|
+
label: "Refresh",
|
|
187
|
+
icon: "mdiRefresh",
|
|
188
|
+
variant: "secondary",
|
|
189
|
+
event: "subagents:ui:refresh",
|
|
190
|
+
},
|
|
191
|
+
],
|
|
192
|
+
},
|
|
193
|
+
} as ExtensionUiModule);
|
|
194
|
+
}) as any);
|
|
195
|
+
|
|
196
|
+
// ── 2. Data Fetch Handler ──────────────────────────────────────────
|
|
197
|
+
pi.events.on(DATA_EVENT, ((data: any) => {
|
|
198
|
+
const agents = manager.listAgents();
|
|
199
|
+
data.items = agents.map(buildAgentRow);
|
|
200
|
+
}) as any);
|
|
201
|
+
|
|
202
|
+
// ── 3. Action Handlers ─────────────────────────────────────────────
|
|
203
|
+
|
|
204
|
+
// Refresh: just invalidate to re-probe + re-fetch
|
|
205
|
+
pi.events.on("subagents:ui:refresh", (() => {
|
|
206
|
+
scheduleInvalidate();
|
|
207
|
+
}) as any);
|
|
208
|
+
|
|
209
|
+
// View Result: emit the result as a toast so the dashboard shows it
|
|
210
|
+
pi.events.on("subagents:ui:view-result", ((data: any) => {
|
|
211
|
+
const agentId = data.params?.id ?? data.id;
|
|
212
|
+
const record = manager.getRecord(agentId);
|
|
213
|
+
if (!record) {
|
|
214
|
+
pi.events.emit("ui:invalidate", { id: MODULE_ID });
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const resultText = record.result?.trim() || "No output yet.";
|
|
219
|
+
const preview = resultText.length > 2000
|
|
220
|
+
? resultText.slice(0, 2000) + "\n…(truncated)"
|
|
221
|
+
: resultText;
|
|
222
|
+
|
|
223
|
+
pi.events.emit("ui:invalidate", { id: MODULE_ID });
|
|
224
|
+
|
|
225
|
+
// Return result as items so it shows in a detail view
|
|
226
|
+
data.items = [{
|
|
227
|
+
id: record.id,
|
|
228
|
+
type: getDisplayName(record.type),
|
|
229
|
+
description: record.description,
|
|
230
|
+
status: record.status,
|
|
231
|
+
result: preview,
|
|
232
|
+
outputFile: record.outputFile ?? "",
|
|
233
|
+
}];
|
|
234
|
+
}) as any);
|
|
235
|
+
|
|
236
|
+
// Abort: signal the agent to stop
|
|
237
|
+
pi.events.on("subagents:ui:abort", ((data: any) => {
|
|
238
|
+
const agentId = data.params?.id ?? data.id;
|
|
239
|
+
const record = manager.getRecord(agentId);
|
|
240
|
+
if (record?.session) {
|
|
241
|
+
// Use the session's abort mechanism
|
|
242
|
+
try {
|
|
243
|
+
record.session.dispose?.();
|
|
244
|
+
} catch {
|
|
245
|
+
// Ignore disposal errors
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
scheduleInvalidate();
|
|
249
|
+
}) as any);
|
|
250
|
+
|
|
251
|
+
// Steer: for now just invalidate — full steer requires a prompt input
|
|
252
|
+
// which the management-modal form view could support in the future
|
|
253
|
+
pi.events.on("subagents:ui:steer", ((_data: any) => {
|
|
254
|
+
// TODO: Could open a form view for entering the steer message
|
|
255
|
+
scheduleInvalidate();
|
|
256
|
+
}) as any);
|
|
257
|
+
|
|
258
|
+
// ── 4. Invalidate on agent lifecycle events ────────────────────────
|
|
259
|
+
const lifecycleEvents = [
|
|
260
|
+
"subagents:created",
|
|
261
|
+
"subagents:started",
|
|
262
|
+
"subagents:completed",
|
|
263
|
+
"subagents:failed",
|
|
264
|
+
"subagents:compacted",
|
|
265
|
+
];
|
|
266
|
+
|
|
267
|
+
for (const event of lifecycleEvents) {
|
|
268
|
+
pi.events.on(event, (() => scheduleInvalidate()) as any);
|
|
269
|
+
}
|
|
270
|
+
}
|
package/src/default-agents.ts
CHANGED
|
@@ -34,7 +34,6 @@ export const DEFAULT_AGENTS: Map<string, AgentConfig> = new Map([
|
|
|
34
34
|
builtinToolNames: READ_ONLY_TOOLS,
|
|
35
35
|
extensions: true,
|
|
36
36
|
skills: true,
|
|
37
|
-
model: "anthropic/claude-haiku-4-5-20251001",
|
|
38
37
|
systemPrompt: `# CRITICAL: READ-ONLY MODE - NO FILE MODIFICATIONS
|
|
39
38
|
You are a file search specialist. You excel at thoroughly navigating and exploring codebases.
|
|
40
39
|
Your role is EXCLUSIVELY to search and analyze existing code. You do NOT have access to file editing tools.
|
package/src/index.ts
CHANGED
|
@@ -24,8 +24,10 @@ import { getAgentConversation, getCurrentExtensionAgentId, getCurrentExtensionDe
|
|
|
24
24
|
import { buildAgentToolDescription, getModelLabelFromConfig } from "./agent-tool-description.js";
|
|
25
25
|
import { BUILTIN_TOOL_NAMES, getAgentConfig, getAllTypes, getAvailableTypes, isDefaultsDisabled, registerAgents, resolveType, setDefaultsDisabled } from "./agent-types.js";
|
|
26
26
|
import { formatOutputFileHint, limitText, MAX_RESULT_CHARS, MAX_VERBOSE_CHARS } from "./bounded-output.js";
|
|
27
|
+
import { extractText } from "./context.js";
|
|
27
28
|
import { registerRpcHandlers } from "./cross-extension-rpc.js";
|
|
28
29
|
import { loadCustomAgents } from "./custom-agents.js";
|
|
30
|
+
import { registerDashboardModules } from "./dashboard-ui.js";
|
|
29
31
|
import { isModelInScope, readEnabledModels, resolveEnabledModels } from "./enabled-models.js";
|
|
30
32
|
import { GroupJoinManager } from "./group-join.js";
|
|
31
33
|
import { resolveAgentInvocationConfig, resolveJoinMode } from "./invocation-config.js";
|
|
@@ -37,11 +39,11 @@ import { SubagentScheduler } from "./schedule.js";
|
|
|
37
39
|
import { resolveStorePath, ScheduleStore } from "./schedule-store.js";
|
|
38
40
|
import { applyAndEmitLoaded, DEFAULT_WAIT_TIMEOUT_SECONDS, type SubagentsSettings, saveAndEmitChanged, type ToolDescriptionMode } from "./settings.js";
|
|
39
41
|
import { getStatusNote } from "./status-note.js";
|
|
42
|
+
import { registerSubagentListClearTools } from "./subagent-list-clear.js";
|
|
40
43
|
import { type AgentConfig, type AgentInvocation, type AgentRecord, type JoinMode, MAX_RECURSIVE_DEPTH, type NotificationDetails, type SubagentType } from "./types.js";
|
|
41
|
-
import { renderAgentCall, renderAgentResult, renderSteerCall, tailPreview } from "./ui/agent-tool-rendering.js";
|
|
44
|
+
import { renderAgentCall, renderAgentResult, renderSteerCall, snipMiddleLines, tailPreview } from "./ui/agent-tool-rendering.js";
|
|
42
45
|
import {
|
|
43
46
|
type AgentActivity,
|
|
44
|
-
type AgentDetails,
|
|
45
47
|
AgentWidget,
|
|
46
48
|
buildInvocationTags,
|
|
47
49
|
describeActivity,
|
|
@@ -60,10 +62,21 @@ import { formatWaitTimeout, pollPendingMessages, raceWait, type WaitOutcome, wai
|
|
|
60
62
|
// ---- Shared helpers ----
|
|
61
63
|
|
|
62
64
|
/** Tool execute return value for a text response. */
|
|
63
|
-
function textResult(msg: string, details?:
|
|
65
|
+
function textResult(msg: string, details?: unknown) {
|
|
64
66
|
return { content: [{ type: "text" as const, text: msg }], details: details as any };
|
|
65
67
|
}
|
|
66
68
|
|
|
69
|
+
/** Metadata attached to get_subagent_result for compact UI rendering. */
|
|
70
|
+
interface GetResultDetails {
|
|
71
|
+
status: AgentRecord["status"];
|
|
72
|
+
description: string;
|
|
73
|
+
toolUses: number;
|
|
74
|
+
tokens: string | null;
|
|
75
|
+
contextPercent: number | null;
|
|
76
|
+
duration: string;
|
|
77
|
+
outputFile?: string;
|
|
78
|
+
}
|
|
79
|
+
|
|
67
80
|
/**
|
|
68
81
|
* Create an AgentActivity state and spawn callbacks for tracking tool usage.
|
|
69
82
|
* Used by the background spawn path to track tool usage.
|
|
@@ -443,6 +456,8 @@ export default function (pi: ExtensionAPI) {
|
|
|
443
456
|
// Capture ctx from session_start for RPC spawn handler + start the scheduler.
|
|
444
457
|
pi.on("session_start", async (_event, ctx) => {
|
|
445
458
|
currentCtx = ctx;
|
|
459
|
+
clearBatchState();
|
|
460
|
+
groupJoin.dispose();
|
|
446
461
|
manager.clearCompleted();
|
|
447
462
|
widget.clearSnapshots();
|
|
448
463
|
retryStash.clear();
|
|
@@ -450,8 +465,10 @@ export default function (pi: ExtensionAPI) {
|
|
|
450
465
|
});
|
|
451
466
|
|
|
452
467
|
pi.on("session_before_switch", () => {
|
|
468
|
+
clearBatchState();
|
|
469
|
+
groupJoin.dispose();
|
|
453
470
|
manager.clearCompleted();
|
|
454
|
-
widget.
|
|
471
|
+
widget.dispose();
|
|
455
472
|
retryStash.clear();
|
|
456
473
|
scheduler.stop();
|
|
457
474
|
});
|
|
@@ -472,13 +489,17 @@ export default function (pi: ExtensionAPI) {
|
|
|
472
489
|
unsubSpawnRpc();
|
|
473
490
|
unsubStopRpc();
|
|
474
491
|
unsubPingRpc();
|
|
492
|
+
unsubWidgetCreated?.();
|
|
475
493
|
unsubWidgetStarted?.();
|
|
476
494
|
unsubWidgetCompleted?.();
|
|
477
495
|
unsubWidgetFailed?.();
|
|
478
496
|
currentCtx = undefined;
|
|
479
497
|
delete (globalThis as any)[MANAGER_KEY];
|
|
480
498
|
scheduler.stop();
|
|
499
|
+
clearBatchState();
|
|
500
|
+
groupJoin.dispose();
|
|
481
501
|
manager.abortAll();
|
|
502
|
+
widget.dispose();
|
|
482
503
|
for (const timer of pendingNudges.values()) clearTimeout(timer);
|
|
483
504
|
pendingNudges.clear();
|
|
484
505
|
retryStash.clear();
|
|
@@ -491,6 +512,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
491
512
|
const snapshot = widgetSnapshotFromEvent(payload);
|
|
492
513
|
if (snapshot) widget.upsertSnapshot(snapshot);
|
|
493
514
|
};
|
|
515
|
+
const unsubWidgetCreated = pi.events.on("subagents:created", upsertWidgetEventSnapshot);
|
|
494
516
|
const unsubWidgetStarted = pi.events.on("subagents:started", upsertWidgetEventSnapshot);
|
|
495
517
|
const unsubWidgetCompleted = pi.events.on("subagents:completed", upsertWidgetEventSnapshot);
|
|
496
518
|
const unsubWidgetFailed = pi.events.on("subagents:failed", upsertWidgetEventSnapshot);
|
|
@@ -567,6 +589,14 @@ export default function (pi: ExtensionAPI) {
|
|
|
567
589
|
let batchFinalizeTimer: ReturnType<typeof setTimeout> | undefined;
|
|
568
590
|
let batchCounter = 0;
|
|
569
591
|
|
|
592
|
+
function clearBatchState() {
|
|
593
|
+
if (batchFinalizeTimer) {
|
|
594
|
+
clearTimeout(batchFinalizeTimer);
|
|
595
|
+
batchFinalizeTimer = undefined;
|
|
596
|
+
}
|
|
597
|
+
currentBatchAgents = [];
|
|
598
|
+
}
|
|
599
|
+
|
|
570
600
|
/** Finalize the current batch: if 2+ smart-mode agents, register as a group. */
|
|
571
601
|
function finalizeBatch() {
|
|
572
602
|
batchFinalizeTimer = undefined;
|
|
@@ -679,7 +709,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
679
709
|
}),
|
|
680
710
|
subagent_type: Type.Optional(
|
|
681
711
|
Type.String({
|
|
682
|
-
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.`,
|
|
712
|
+
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.`,
|
|
683
713
|
}),
|
|
684
714
|
),
|
|
685
715
|
model: Type.Optional(
|
|
@@ -782,18 +812,16 @@ export default function (pi: ExtensionAPI) {
|
|
|
782
812
|
const { retry: _omit, ...overrides } = params;
|
|
783
813
|
P = { ...stashed.params, ...overrides } as typeof params;
|
|
784
814
|
}
|
|
785
|
-
|
|
815
|
+
const requestedSubagentType = (P.subagent_type ?? "general-purpose") as SubagentType;
|
|
816
|
+
emitPromptPreview(P.prompt, P.description, requestedSubagentType);
|
|
786
817
|
|
|
787
|
-
// Retry supplied the prompt
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
[!P.prompt && "prompt", !P.subagent_type && "subagent_type"].filter(Boolean).join(", ") +
|
|
792
|
-
".",
|
|
793
|
-
);
|
|
818
|
+
// Retry supplied the prompt from the stash; otherwise prompt is required.
|
|
819
|
+
// subagent_type defaults to general-purpose when omitted.
|
|
820
|
+
if (!retryHandle && !P.prompt) {
|
|
821
|
+
return textResult("Missing required argument: prompt.");
|
|
794
822
|
}
|
|
795
823
|
|
|
796
|
-
const rawType =
|
|
824
|
+
const rawType = requestedSubagentType;
|
|
797
825
|
const resolved = resolveType(rawType);
|
|
798
826
|
if (!resolved) {
|
|
799
827
|
// Unknown agent type — recoverable. List valid types so the orchestrator
|
|
@@ -1032,6 +1060,10 @@ export default function (pi: ExtensionAPI) {
|
|
|
1032
1060
|
isBackground: true,
|
|
1033
1061
|
depth: record?.depth ?? nextSubagentDepth,
|
|
1034
1062
|
parentAgentId: extensionAgentId,
|
|
1063
|
+
status: record?.status ?? "running",
|
|
1064
|
+
startedAt: record?.startedAt,
|
|
1065
|
+
toolUses: record?.toolUses ?? 0,
|
|
1066
|
+
invocation: record?.invocation,
|
|
1035
1067
|
});
|
|
1036
1068
|
|
|
1037
1069
|
const isQueued = record?.status === "queued";
|
|
@@ -1077,6 +1109,57 @@ export default function (pi: ExtensionAPI) {
|
|
|
1077
1109
|
}),
|
|
1078
1110
|
),
|
|
1079
1111
|
}),
|
|
1112
|
+
renderResult(result, { expanded }, theme) {
|
|
1113
|
+
const details = result.details as GetResultDetails | undefined;
|
|
1114
|
+
const text = extractText(result.content);
|
|
1115
|
+
|
|
1116
|
+
// Header: status + stats + description
|
|
1117
|
+
let line = "";
|
|
1118
|
+
if (details) {
|
|
1119
|
+
const icon = details.status === "error" || details.status === "stopped" || details.status === "aborted"
|
|
1120
|
+
? theme.fg("error", "✗")
|
|
1121
|
+
: details.status === "running" || details.status === "queued"
|
|
1122
|
+
? theme.fg("accent", "◌")
|
|
1123
|
+
: theme.fg("success", "✓");
|
|
1124
|
+
|
|
1125
|
+
const parts: string[] = [];
|
|
1126
|
+
if (details.toolUses > 0) parts.push(`${details.toolUses} tool use${details.toolUses === 1 ? "" : "s"}`);
|
|
1127
|
+
if (details.tokens) parts.push(details.tokens);
|
|
1128
|
+
if (details.contextPercent !== null) parts.push(`ctx ${Math.round(details.contextPercent)}%`);
|
|
1129
|
+
if (details.duration) parts.push(details.duration);
|
|
1130
|
+
const stats = parts.map(p => theme.fg("dim", p)).join(" " + theme.fg("dim", "·") + " ");
|
|
1131
|
+
|
|
1132
|
+
line = `${icon} ${theme.bold(details.description)} ${theme.fg("dim", details.status)}`;
|
|
1133
|
+
if (stats) line += "\n " + stats;
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1136
|
+
// Body: snip when collapsed, full when expanded
|
|
1137
|
+
// Extract the body portion (after the first blank line) to keep the
|
|
1138
|
+
// tool-output header always visible and only snip the actual result.
|
|
1139
|
+
if (text.trim()) {
|
|
1140
|
+
const firstBlank = text.indexOf("\n\n");
|
|
1141
|
+
const body = firstBlank >= 0 ? text.slice(firstBlank + 2) : text;
|
|
1142
|
+
|
|
1143
|
+
if (expanded) {
|
|
1144
|
+
for (const l of text.split("\n")) {
|
|
1145
|
+
line += "\n" + theme.fg("dim", ` ${l}`);
|
|
1146
|
+
}
|
|
1147
|
+
} else {
|
|
1148
|
+
// Show the tool-output header verbatim, then snip only the body
|
|
1149
|
+
if (firstBlank >= 0) {
|
|
1150
|
+
for (const l of text.slice(0, firstBlank).split("\n")) {
|
|
1151
|
+
line += "\n" + theme.fg("dim", ` ${l}`);
|
|
1152
|
+
}
|
|
1153
|
+
}
|
|
1154
|
+
for (const l of snipMiddleLines(body, 20)) {
|
|
1155
|
+
line += "\n" + theme.fg("dim", ` ${l}`);
|
|
1156
|
+
}
|
|
1157
|
+
}
|
|
1158
|
+
}
|
|
1159
|
+
|
|
1160
|
+
return new Text(line, 0, 0);
|
|
1161
|
+
},
|
|
1162
|
+
|
|
1080
1163
|
execute: async (_toolCallId, params, signal, _onUpdate, ctx) => {
|
|
1081
1164
|
const record = manager.getRecord(params.agent_id);
|
|
1082
1165
|
if (!record) {
|
|
@@ -1125,6 +1208,16 @@ export default function (pi: ExtensionAPI) {
|
|
|
1125
1208
|
if (record.compactionCount) statsParts.push(`Compactions: ${record.compactionCount}`);
|
|
1126
1209
|
statsParts.push(`Duration: ${duration}`);
|
|
1127
1210
|
|
|
1211
|
+
const details: GetResultDetails = {
|
|
1212
|
+
status: record.status,
|
|
1213
|
+
description: record.description,
|
|
1214
|
+
toolUses: record.toolUses,
|
|
1215
|
+
tokens: tokens || null,
|
|
1216
|
+
contextPercent,
|
|
1217
|
+
duration,
|
|
1218
|
+
outputFile: record.outputFile,
|
|
1219
|
+
};
|
|
1220
|
+
|
|
1128
1221
|
let output =
|
|
1129
1222
|
`Agent: ${record.id}\n` +
|
|
1130
1223
|
`Type: ${displayName} | Status: ${record.status}${getStatusNote(record.status)} | ${statsParts.join(" | ")}\n` +
|
|
@@ -1168,7 +1261,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
1168
1261
|
}
|
|
1169
1262
|
}
|
|
1170
1263
|
|
|
1171
|
-
return textResult(output);
|
|
1264
|
+
return textResult(output, details);
|
|
1172
1265
|
},
|
|
1173
1266
|
}));
|
|
1174
1267
|
|
|
@@ -1228,6 +1321,11 @@ export default function (pi: ExtensionAPI) {
|
|
|
1228
1321
|
},
|
|
1229
1322
|
}));
|
|
1230
1323
|
|
|
1324
|
+
// ---- list_subagents / clear_subagents tools ----
|
|
1325
|
+
|
|
1326
|
+
registerSubagentListClearTools(pi, manager);
|
|
1327
|
+
registerDashboardModules(pi, manager);
|
|
1328
|
+
|
|
1231
1329
|
// ---- list_models tool ----
|
|
1232
1330
|
|
|
1233
1331
|
pi.registerTool(defineTool({
|
package/src/peek.ts
CHANGED
|
@@ -112,6 +112,11 @@ function parseOutputFileLines(path: string): string[] {
|
|
|
112
112
|
return [];
|
|
113
113
|
}
|
|
114
114
|
const out: string[] = [];
|
|
115
|
+
const pushRenderedLines = (text: string) => {
|
|
116
|
+
for (const renderedLine of text.trimEnd().split("\n")) {
|
|
117
|
+
if (renderedLine.trim()) out.push(renderedLine);
|
|
118
|
+
}
|
|
119
|
+
};
|
|
115
120
|
for (const line of raw.split("\n")) {
|
|
116
121
|
const trimmed = line.trim();
|
|
117
122
|
if (!trimmed) continue;
|
|
@@ -124,12 +129,12 @@ function parseOutputFileLines(path: string): string[] {
|
|
|
124
129
|
const content = entry?.message?.content;
|
|
125
130
|
if (!Array.isArray(content)) {
|
|
126
131
|
// Some entries may carry a plain string content.
|
|
127
|
-
if (typeof content === "string" && content.trim())
|
|
132
|
+
if (typeof content === "string" && content.trim()) pushRenderedLines(content);
|
|
128
133
|
continue;
|
|
129
134
|
}
|
|
130
135
|
for (const block of content) {
|
|
131
136
|
if (block?.type === "text" && typeof block.text === "string" && block.text.trim()) {
|
|
132
|
-
|
|
137
|
+
pushRenderedLines(block.text);
|
|
133
138
|
}
|
|
134
139
|
}
|
|
135
140
|
}
|