@clanker-code/pi-subagents 0.10.7 → 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 +22 -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 +110 -14
- 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/dist/wait.d.ts +18 -4
- package/dist/wait.js +48 -3
- 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 +126 -18
- 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
- package/src/wait.ts +55 -3
|
@@ -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,
|
|
@@ -55,15 +57,26 @@ import type { WidgetAgentSnapshot, WidgetDisplayMode } from "./ui/agent-widget-t
|
|
|
55
57
|
import { menuSelect } from "./ui/menu-select.js";
|
|
56
58
|
import { showSchedulesMenu } from "./ui/schedule-menu.js";
|
|
57
59
|
import { addUsage, getLifetimeTotal, getSessionContextPercent } from "./usage.js";
|
|
58
|
-
import { formatWaitTimeout, raceWait, type WaitOutcome, waitTimeoutMessage } from "./wait.js";
|
|
60
|
+
import { formatWaitTimeout, pollPendingMessages, raceWait, type WaitOutcome, waitTimeoutMessage } from "./wait.js";
|
|
59
61
|
|
|
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,7 +1109,58 @@ export default function (pi: ExtensionAPI) {
|
|
|
1077
1109
|
}),
|
|
1078
1110
|
),
|
|
1079
1111
|
}),
|
|
1080
|
-
|
|
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
|
+
|
|
1163
|
+
execute: async (_toolCallId, params, signal, _onUpdate, ctx) => {
|
|
1081
1164
|
const record = manager.getRecord(params.agent_id);
|
|
1082
1165
|
if (!record) {
|
|
1083
1166
|
return textResult(`Agent not found: "${params.agent_id}". It may have been cleaned up.`);
|
|
@@ -1099,7 +1182,17 @@ export default function (pi: ExtensionAPI) {
|
|
|
1099
1182
|
let waitOutcome: WaitOutcome = "completed";
|
|
1100
1183
|
if (params.wait && record.status === "running" && record.promise) {
|
|
1101
1184
|
cancelNudge(params.agent_id);
|
|
1102
|
-
|
|
1185
|
+
// Poll for queued user messages so we can return early and let the
|
|
1186
|
+
// parent LLM process them immediately instead of blocking for the
|
|
1187
|
+
// full wait timeout.
|
|
1188
|
+
const pending = typeof ctx?.hasPendingMessages === "function"
|
|
1189
|
+
? pollPendingMessages(() => ctx.hasPendingMessages())
|
|
1190
|
+
: undefined;
|
|
1191
|
+
try {
|
|
1192
|
+
waitOutcome = await raceWait(record.promise, signal, getWaitTimeoutSeconds(), pending?.promise);
|
|
1193
|
+
} finally {
|
|
1194
|
+
pending?.cancel();
|
|
1195
|
+
}
|
|
1103
1196
|
if (waitOutcome === "completed") {
|
|
1104
1197
|
record.resultConsumed = true;
|
|
1105
1198
|
}
|
|
@@ -1115,6 +1208,16 @@ export default function (pi: ExtensionAPI) {
|
|
|
1115
1208
|
if (record.compactionCount) statsParts.push(`Compactions: ${record.compactionCount}`);
|
|
1116
1209
|
statsParts.push(`Duration: ${duration}`);
|
|
1117
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
|
+
|
|
1118
1221
|
let output =
|
|
1119
1222
|
`Agent: ${record.id}\n` +
|
|
1120
1223
|
`Type: ${displayName} | Status: ${record.status}${getStatusNote(record.status)} | ${statsParts.join(" | ")}\n` +
|
|
@@ -1158,7 +1261,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
1158
1261
|
}
|
|
1159
1262
|
}
|
|
1160
1263
|
|
|
1161
|
-
return textResult(output);
|
|
1264
|
+
return textResult(output, details);
|
|
1162
1265
|
},
|
|
1163
1266
|
}));
|
|
1164
1267
|
|
|
@@ -1218,6 +1321,11 @@ export default function (pi: ExtensionAPI) {
|
|
|
1218
1321
|
},
|
|
1219
1322
|
}));
|
|
1220
1323
|
|
|
1324
|
+
// ---- list_subagents / clear_subagents tools ----
|
|
1325
|
+
|
|
1326
|
+
registerSubagentListClearTools(pi, manager);
|
|
1327
|
+
registerDashboardModules(pi, manager);
|
|
1328
|
+
|
|
1221
1329
|
// ---- list_models tool ----
|
|
1222
1330
|
|
|
1223
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
|
}
|