@tintinweb/pi-subagents 0.3.1 → 0.4.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/CHANGELOG.md +29 -1
- package/README.md +17 -15
- package/dist/agent-manager.d.ts +70 -0
- package/dist/agent-manager.js +236 -0
- package/dist/agent-runner.d.ts +60 -0
- package/dist/agent-runner.js +265 -0
- package/dist/agent-types.d.ts +41 -0
- package/dist/agent-types.js +130 -0
- package/dist/context.d.ts +12 -0
- package/dist/context.js +56 -0
- package/dist/custom-agents.d.ts +14 -0
- package/dist/custom-agents.js +100 -0
- package/dist/default-agents.d.ts +7 -0
- package/dist/default-agents.js +126 -0
- package/dist/env.d.ts +6 -0
- package/dist/env.js +28 -0
- package/dist/group-join.d.ts +32 -0
- package/dist/group-join.js +116 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +1270 -0
- package/dist/model-resolver.d.ts +19 -0
- package/dist/model-resolver.js +62 -0
- package/dist/prompts.d.ts +14 -0
- package/dist/prompts.js +48 -0
- package/dist/types.d.ts +62 -0
- package/dist/types.js +5 -0
- package/dist/ui/agent-widget.d.ts +101 -0
- package/dist/ui/agent-widget.js +333 -0
- package/dist/ui/conversation-viewer.d.ts +31 -0
- package/dist/ui/conversation-viewer.js +236 -0
- package/package.json +1 -1
- package/src/agent-manager.ts +22 -4
- package/src/agent-runner.ts +11 -45
- package/src/agent-types.ts +4 -15
- package/src/default-agents.ts +2 -36
- package/src/index.ts +30 -24
- package/src/prompts.ts +35 -20
- package/src/ui/agent-widget.ts +100 -24
- package/src/ui/conversation-viewer.ts +4 -2
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* conversation-viewer.ts — Live conversation overlay for viewing agent sessions.
|
|
3
|
+
*
|
|
4
|
+
* Displays a scrollable, live-updating view of an agent's conversation.
|
|
5
|
+
* Subscribes to session events for real-time streaming updates.
|
|
6
|
+
*/
|
|
7
|
+
import { type Component, type TUI } from "@mariozechner/pi-tui";
|
|
8
|
+
import type { AgentSession } from "@mariozechner/pi-coding-agent";
|
|
9
|
+
import type { Theme } from "./agent-widget.js";
|
|
10
|
+
import { type AgentActivity } from "./agent-widget.js";
|
|
11
|
+
import type { AgentRecord } from "../types.js";
|
|
12
|
+
export declare class ConversationViewer implements Component {
|
|
13
|
+
private tui;
|
|
14
|
+
private session;
|
|
15
|
+
private record;
|
|
16
|
+
private activity;
|
|
17
|
+
private theme;
|
|
18
|
+
private done;
|
|
19
|
+
private scrollOffset;
|
|
20
|
+
private autoScroll;
|
|
21
|
+
private unsubscribe;
|
|
22
|
+
private lastInnerW;
|
|
23
|
+
private closed;
|
|
24
|
+
constructor(tui: TUI, session: AgentSession, record: AgentRecord, activity: AgentActivity | undefined, theme: Theme, done: (result: undefined) => void);
|
|
25
|
+
handleInput(data: string): void;
|
|
26
|
+
render(width: number): string[];
|
|
27
|
+
invalidate(): void;
|
|
28
|
+
dispose(): void;
|
|
29
|
+
private viewportHeight;
|
|
30
|
+
private buildContentLines;
|
|
31
|
+
}
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* conversation-viewer.ts — Live conversation overlay for viewing agent sessions.
|
|
3
|
+
*
|
|
4
|
+
* Displays a scrollable, live-updating view of an agent's conversation.
|
|
5
|
+
* Subscribes to session events for real-time streaming updates.
|
|
6
|
+
*/
|
|
7
|
+
import { matchesKey, truncateToWidth, visibleWidth, wrapTextWithAnsi } from "@mariozechner/pi-tui";
|
|
8
|
+
import { formatTokens, formatDuration, getDisplayName, getPromptModeLabel, describeActivity } from "./agent-widget.js";
|
|
9
|
+
import { extractText } from "../context.js";
|
|
10
|
+
/** Lines consumed by chrome: top border + header + header sep + footer sep + footer + bottom border. */
|
|
11
|
+
const CHROME_LINES = 6;
|
|
12
|
+
const MIN_VIEWPORT = 3;
|
|
13
|
+
export class ConversationViewer {
|
|
14
|
+
tui;
|
|
15
|
+
session;
|
|
16
|
+
record;
|
|
17
|
+
activity;
|
|
18
|
+
theme;
|
|
19
|
+
done;
|
|
20
|
+
scrollOffset = 0;
|
|
21
|
+
autoScroll = true;
|
|
22
|
+
unsubscribe;
|
|
23
|
+
lastInnerW = 0;
|
|
24
|
+
closed = false;
|
|
25
|
+
constructor(tui, session, record, activity, theme, done) {
|
|
26
|
+
this.tui = tui;
|
|
27
|
+
this.session = session;
|
|
28
|
+
this.record = record;
|
|
29
|
+
this.activity = activity;
|
|
30
|
+
this.theme = theme;
|
|
31
|
+
this.done = done;
|
|
32
|
+
this.unsubscribe = session.subscribe(() => {
|
|
33
|
+
if (this.closed)
|
|
34
|
+
return;
|
|
35
|
+
this.tui.requestRender();
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
handleInput(data) {
|
|
39
|
+
if (matchesKey(data, "escape") || matchesKey(data, "q")) {
|
|
40
|
+
this.closed = true;
|
|
41
|
+
this.done(undefined);
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
const totalLines = this.buildContentLines(this.lastInnerW).length;
|
|
45
|
+
const viewportHeight = this.viewportHeight();
|
|
46
|
+
const maxScroll = Math.max(0, totalLines - viewportHeight);
|
|
47
|
+
if (matchesKey(data, "up") || matchesKey(data, "k")) {
|
|
48
|
+
this.scrollOffset = Math.max(0, this.scrollOffset - 1);
|
|
49
|
+
this.autoScroll = this.scrollOffset >= maxScroll;
|
|
50
|
+
}
|
|
51
|
+
else if (matchesKey(data, "down") || matchesKey(data, "j")) {
|
|
52
|
+
this.scrollOffset = Math.min(maxScroll, this.scrollOffset + 1);
|
|
53
|
+
this.autoScroll = this.scrollOffset >= maxScroll;
|
|
54
|
+
}
|
|
55
|
+
else if (matchesKey(data, "pageUp")) {
|
|
56
|
+
this.scrollOffset = Math.max(0, this.scrollOffset - viewportHeight);
|
|
57
|
+
this.autoScroll = false;
|
|
58
|
+
}
|
|
59
|
+
else if (matchesKey(data, "pageDown")) {
|
|
60
|
+
this.scrollOffset = Math.min(maxScroll, this.scrollOffset + viewportHeight);
|
|
61
|
+
this.autoScroll = this.scrollOffset >= maxScroll;
|
|
62
|
+
}
|
|
63
|
+
else if (matchesKey(data, "home")) {
|
|
64
|
+
this.scrollOffset = 0;
|
|
65
|
+
this.autoScroll = false;
|
|
66
|
+
}
|
|
67
|
+
else if (matchesKey(data, "end")) {
|
|
68
|
+
this.scrollOffset = maxScroll;
|
|
69
|
+
this.autoScroll = true;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
render(width) {
|
|
73
|
+
if (width < 6)
|
|
74
|
+
return []; // too narrow for any meaningful rendering
|
|
75
|
+
const th = this.theme;
|
|
76
|
+
const innerW = width - 4; // border + padding
|
|
77
|
+
this.lastInnerW = innerW;
|
|
78
|
+
const lines = [];
|
|
79
|
+
const pad = (s, len) => {
|
|
80
|
+
const vis = visibleWidth(s);
|
|
81
|
+
return s + " ".repeat(Math.max(0, len - vis));
|
|
82
|
+
};
|
|
83
|
+
const row = (content) => th.fg("border", "│") + " " + truncateToWidth(pad(content, innerW), innerW) + " " + th.fg("border", "│");
|
|
84
|
+
const hrTop = th.fg("border", `╭${"─".repeat(width - 2)}╮`);
|
|
85
|
+
const hrBot = th.fg("border", `╰${"─".repeat(width - 2)}╯`);
|
|
86
|
+
const hrMid = row(th.fg("dim", "─".repeat(innerW)));
|
|
87
|
+
// Header
|
|
88
|
+
lines.push(hrTop);
|
|
89
|
+
const name = getDisplayName(this.record.type);
|
|
90
|
+
const modeLabel = getPromptModeLabel(this.record.type);
|
|
91
|
+
const modeTag = modeLabel ? ` ${th.fg("dim", `(${modeLabel})`)}` : "";
|
|
92
|
+
const statusIcon = this.record.status === "running"
|
|
93
|
+
? th.fg("accent", "●")
|
|
94
|
+
: this.record.status === "completed"
|
|
95
|
+
? th.fg("success", "✓")
|
|
96
|
+
: this.record.status === "error"
|
|
97
|
+
? th.fg("error", "✗")
|
|
98
|
+
: th.fg("dim", "○");
|
|
99
|
+
const duration = formatDuration(this.record.startedAt, this.record.completedAt);
|
|
100
|
+
const headerParts = [duration];
|
|
101
|
+
const toolUses = this.activity?.toolUses ?? this.record.toolUses;
|
|
102
|
+
if (toolUses > 0)
|
|
103
|
+
headerParts.unshift(`${toolUses} tool${toolUses === 1 ? "" : "s"}`);
|
|
104
|
+
if (this.activity?.session) {
|
|
105
|
+
try {
|
|
106
|
+
const tokens = this.activity.session.getSessionStats().tokens.total;
|
|
107
|
+
if (tokens > 0)
|
|
108
|
+
headerParts.push(formatTokens(tokens));
|
|
109
|
+
}
|
|
110
|
+
catch { /* */ }
|
|
111
|
+
}
|
|
112
|
+
lines.push(row(`${statusIcon} ${th.bold(name)}${modeTag} ${th.fg("muted", this.record.description)} ${th.fg("dim", "·")} ${th.fg("dim", headerParts.join(" · "))}`));
|
|
113
|
+
lines.push(hrMid);
|
|
114
|
+
// Content area — rebuild every render (live data, no cache needed)
|
|
115
|
+
const contentLines = this.buildContentLines(innerW);
|
|
116
|
+
const viewportHeight = this.viewportHeight();
|
|
117
|
+
const maxScroll = Math.max(0, contentLines.length - viewportHeight);
|
|
118
|
+
if (this.autoScroll) {
|
|
119
|
+
this.scrollOffset = maxScroll;
|
|
120
|
+
}
|
|
121
|
+
const visibleStart = Math.min(this.scrollOffset, maxScroll);
|
|
122
|
+
const visible = contentLines.slice(visibleStart, visibleStart + viewportHeight);
|
|
123
|
+
for (let i = 0; i < viewportHeight; i++) {
|
|
124
|
+
lines.push(row(visible[i] ?? ""));
|
|
125
|
+
}
|
|
126
|
+
// Footer
|
|
127
|
+
lines.push(hrMid);
|
|
128
|
+
const scrollPct = contentLines.length <= viewportHeight
|
|
129
|
+
? "100%"
|
|
130
|
+
: `${Math.round(((visibleStart + viewportHeight) / contentLines.length) * 100)}%`;
|
|
131
|
+
const footerLeft = th.fg("dim", `${contentLines.length} lines · ${scrollPct}`);
|
|
132
|
+
const footerRight = th.fg("dim", "↑↓ scroll · PgUp/PgDn · Esc close");
|
|
133
|
+
const footerGap = Math.max(1, innerW - visibleWidth(footerLeft) - visibleWidth(footerRight));
|
|
134
|
+
lines.push(row(footerLeft + " ".repeat(footerGap) + footerRight));
|
|
135
|
+
lines.push(hrBot);
|
|
136
|
+
return lines;
|
|
137
|
+
}
|
|
138
|
+
invalidate() { }
|
|
139
|
+
dispose() {
|
|
140
|
+
this.closed = true;
|
|
141
|
+
if (this.unsubscribe) {
|
|
142
|
+
this.unsubscribe();
|
|
143
|
+
this.unsubscribe = undefined;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
// ---- Private ----
|
|
147
|
+
viewportHeight() {
|
|
148
|
+
return Math.max(MIN_VIEWPORT, this.tui.terminal.rows - CHROME_LINES);
|
|
149
|
+
}
|
|
150
|
+
buildContentLines(width) {
|
|
151
|
+
if (width <= 0)
|
|
152
|
+
return [];
|
|
153
|
+
const th = this.theme;
|
|
154
|
+
const messages = this.session.messages;
|
|
155
|
+
const lines = [];
|
|
156
|
+
if (messages.length === 0) {
|
|
157
|
+
lines.push(th.fg("dim", "(waiting for first message...)"));
|
|
158
|
+
return lines;
|
|
159
|
+
}
|
|
160
|
+
let needsSeparator = false;
|
|
161
|
+
for (const msg of messages) {
|
|
162
|
+
if (msg.role === "user") {
|
|
163
|
+
const text = typeof msg.content === "string"
|
|
164
|
+
? msg.content
|
|
165
|
+
: extractText(msg.content);
|
|
166
|
+
if (!text.trim())
|
|
167
|
+
continue;
|
|
168
|
+
if (needsSeparator)
|
|
169
|
+
lines.push(th.fg("dim", "───"));
|
|
170
|
+
lines.push(th.fg("accent", "[User]"));
|
|
171
|
+
for (const line of wrapTextWithAnsi(text.trim(), width)) {
|
|
172
|
+
lines.push(line);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
else if (msg.role === "assistant") {
|
|
176
|
+
const textParts = [];
|
|
177
|
+
const toolCalls = [];
|
|
178
|
+
for (const c of msg.content) {
|
|
179
|
+
if (c.type === "text" && c.text)
|
|
180
|
+
textParts.push(c.text);
|
|
181
|
+
else if (c.type === "toolCall") {
|
|
182
|
+
toolCalls.push(c.toolName ?? "unknown");
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
if (needsSeparator)
|
|
186
|
+
lines.push(th.fg("dim", "───"));
|
|
187
|
+
lines.push(th.bold("[Assistant]"));
|
|
188
|
+
if (textParts.length > 0) {
|
|
189
|
+
for (const line of wrapTextWithAnsi(textParts.join("\n").trim(), width)) {
|
|
190
|
+
lines.push(line);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
for (const name of toolCalls) {
|
|
194
|
+
lines.push(truncateToWidth(th.fg("muted", ` [Tool: ${name}]`), width));
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
else if (msg.role === "toolResult") {
|
|
198
|
+
const text = extractText(msg.content);
|
|
199
|
+
const truncated = text.length > 500 ? text.slice(0, 500) + "... (truncated)" : text;
|
|
200
|
+
if (!truncated.trim())
|
|
201
|
+
continue;
|
|
202
|
+
if (needsSeparator)
|
|
203
|
+
lines.push(th.fg("dim", "───"));
|
|
204
|
+
lines.push(th.fg("dim", "[Result]"));
|
|
205
|
+
for (const line of wrapTextWithAnsi(truncated.trim(), width)) {
|
|
206
|
+
lines.push(th.fg("dim", line));
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
else if (msg.role === "bashExecution") {
|
|
210
|
+
const bash = msg;
|
|
211
|
+
if (needsSeparator)
|
|
212
|
+
lines.push(th.fg("dim", "───"));
|
|
213
|
+
lines.push(truncateToWidth(th.fg("muted", ` $ ${bash.command}`), width));
|
|
214
|
+
if (bash.output?.trim()) {
|
|
215
|
+
const out = bash.output.length > 500
|
|
216
|
+
? bash.output.slice(0, 500) + "... (truncated)"
|
|
217
|
+
: bash.output;
|
|
218
|
+
for (const line of wrapTextWithAnsi(out.trim(), width)) {
|
|
219
|
+
lines.push(th.fg("dim", line));
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
else {
|
|
224
|
+
continue;
|
|
225
|
+
}
|
|
226
|
+
needsSeparator = true;
|
|
227
|
+
}
|
|
228
|
+
// Streaming indicator for running agents
|
|
229
|
+
if (this.record.status === "running" && this.activity) {
|
|
230
|
+
const act = describeActivity(this.activity.activeTools, this.activity.responseText);
|
|
231
|
+
lines.push("");
|
|
232
|
+
lines.push(truncateToWidth(th.fg("accent", "▍ ") + th.fg("dim", act), width));
|
|
233
|
+
}
|
|
234
|
+
return lines;
|
|
235
|
+
}
|
|
236
|
+
}
|
package/package.json
CHANGED
package/src/agent-manager.ts
CHANGED
|
@@ -33,8 +33,6 @@ interface SpawnOptions {
|
|
|
33
33
|
isolated?: boolean;
|
|
34
34
|
inheritContext?: boolean;
|
|
35
35
|
thinkingLevel?: ThinkingLevel;
|
|
36
|
-
systemPromptOverride?: string;
|
|
37
|
-
systemPromptAppend?: string;
|
|
38
36
|
isBackground?: boolean;
|
|
39
37
|
/** Called on tool start/end with activity info (for streaming progress to UI). */
|
|
40
38
|
onToolActivity?: (activity: ToolActivity) => void;
|
|
@@ -122,8 +120,6 @@ export class AgentManager {
|
|
|
122
120
|
isolated: options.isolated,
|
|
123
121
|
inheritContext: options.inheritContext,
|
|
124
122
|
thinkingLevel: options.thinkingLevel,
|
|
125
|
-
systemPromptOverride: options.systemPromptOverride,
|
|
126
|
-
systemPromptAppend: options.systemPromptAppend,
|
|
127
123
|
signal: record.abortController!.signal,
|
|
128
124
|
onToolActivity: (activity) => {
|
|
129
125
|
if (activity.type === "end") record.toolUses++;
|
|
@@ -275,6 +271,28 @@ export class AgentManager {
|
|
|
275
271
|
}
|
|
276
272
|
}
|
|
277
273
|
|
|
274
|
+
/** Whether any agents are still running or queued. */
|
|
275
|
+
hasRunning(): boolean {
|
|
276
|
+
return [...this.agents.values()].some(
|
|
277
|
+
r => r.status === "running" || r.status === "queued",
|
|
278
|
+
);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/** Wait for all running and queued agents to complete (including queued ones). */
|
|
282
|
+
async waitForAll(): Promise<void> {
|
|
283
|
+
// Loop because drainQueue respects the concurrency limit — as running
|
|
284
|
+
// agents finish they start queued ones, which need awaiting too.
|
|
285
|
+
while (true) {
|
|
286
|
+
this.drainQueue();
|
|
287
|
+
const pending = [...this.agents.values()]
|
|
288
|
+
.filter(r => r.status === "running" || r.status === "queued")
|
|
289
|
+
.map(r => r.promise)
|
|
290
|
+
.filter(Boolean);
|
|
291
|
+
if (pending.length === 0) break;
|
|
292
|
+
await Promise.allSettled(pending);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
278
296
|
dispose() {
|
|
279
297
|
clearInterval(this.cleanupInterval);
|
|
280
298
|
// Clear queue
|
package/src/agent-runner.ts
CHANGED
|
@@ -84,10 +84,6 @@ export interface RunOptions {
|
|
|
84
84
|
isolated?: boolean;
|
|
85
85
|
inheritContext?: boolean;
|
|
86
86
|
thinkingLevel?: ThinkingLevel;
|
|
87
|
-
/** Override system prompt entirely (for custom agents with promptMode: "replace"). */
|
|
88
|
-
systemPromptOverride?: string;
|
|
89
|
-
/** Append to default system prompt (for custom agents with promptMode: "append"). */
|
|
90
|
-
systemPromptAppend?: string;
|
|
91
87
|
/** Called on tool start/end with activity info. */
|
|
92
88
|
onToolActivity?: (activity: ToolActivity) => void;
|
|
93
89
|
/** Called on streaming text deltas from the assistant response. */
|
|
@@ -142,57 +138,27 @@ export async function runAgent(
|
|
|
142
138
|
const agentConfig = getAgentConfig(type);
|
|
143
139
|
const env = await detectEnv(options.pi, ctx.cwd);
|
|
144
140
|
|
|
145
|
-
//
|
|
141
|
+
// Get parent system prompt for append-mode agents
|
|
142
|
+
const parentSystemPrompt = ctx.getSystemPrompt();
|
|
143
|
+
|
|
144
|
+
// Build system prompt from agent config
|
|
146
145
|
let systemPrompt: string;
|
|
147
|
-
if (
|
|
148
|
-
systemPrompt =
|
|
149
|
-
} else if (options.systemPromptAppend) {
|
|
150
|
-
// Build a default prompt and append to it
|
|
151
|
-
const defaultConfig = agentConfig ?? {
|
|
152
|
-
name: type,
|
|
153
|
-
description: "",
|
|
154
|
-
builtinToolNames: [],
|
|
155
|
-
extensions: true,
|
|
156
|
-
skills: true,
|
|
157
|
-
systemPrompt: "",
|
|
158
|
-
promptMode: "replace" as const,
|
|
159
|
-
inheritContext: false,
|
|
160
|
-
runInBackground: false,
|
|
161
|
-
isolated: false,
|
|
162
|
-
};
|
|
163
|
-
systemPrompt = buildAgentPrompt(defaultConfig, ctx.cwd, env) + "\n\n" + options.systemPromptAppend;
|
|
164
|
-
} else if (agentConfig) {
|
|
165
|
-
systemPrompt = buildAgentPrompt(agentConfig, ctx.cwd, env);
|
|
146
|
+
if (agentConfig) {
|
|
147
|
+
systemPrompt = buildAgentPrompt(agentConfig, ctx.cwd, env, parentSystemPrompt);
|
|
166
148
|
} else {
|
|
167
|
-
// Unknown type
|
|
149
|
+
// Unknown type fallback: general-purpose (defensive — unreachable in practice
|
|
150
|
+
// since index.ts resolves unknown types to "general-purpose" before calling runAgent)
|
|
168
151
|
systemPrompt = buildAgentPrompt({
|
|
169
152
|
name: type,
|
|
170
153
|
description: "General-purpose agent",
|
|
171
|
-
|
|
154
|
+
systemPrompt: "",
|
|
155
|
+
promptMode: "append",
|
|
172
156
|
extensions: true,
|
|
173
157
|
skills: true,
|
|
174
|
-
systemPrompt: `# Role
|
|
175
|
-
You are a general-purpose coding agent for complex, multi-step tasks.
|
|
176
|
-
You have full access to read, write, edit files, and execute commands.
|
|
177
|
-
Do what has been asked; nothing more, nothing less.
|
|
178
|
-
|
|
179
|
-
# Tool Usage
|
|
180
|
-
- Use the read tool instead of cat/head/tail
|
|
181
|
-
- Use the edit tool instead of sed/awk
|
|
182
|
-
- Use the write tool instead of echo/heredoc
|
|
183
|
-
- Use the find tool instead of bash find/ls for file search
|
|
184
|
-
- Use the grep tool instead of bash grep/rg for content search
|
|
185
|
-
- Make independent tool calls in parallel
|
|
186
|
-
|
|
187
|
-
# Output
|
|
188
|
-
- Use absolute file paths
|
|
189
|
-
- Do not use emojis
|
|
190
|
-
- Be concise but complete`,
|
|
191
|
-
promptMode: "replace",
|
|
192
158
|
inheritContext: false,
|
|
193
159
|
runInBackground: false,
|
|
194
160
|
isolated: false,
|
|
195
|
-
}, ctx.cwd, env);
|
|
161
|
+
}, ctx.cwd, env, parentSystemPrompt);
|
|
196
162
|
}
|
|
197
163
|
|
|
198
164
|
const tools = getToolsForType(type, ctx.cwd);
|
package/src/agent-types.ts
CHANGED
|
@@ -125,6 +125,7 @@ export function getConfig(type: string): {
|
|
|
125
125
|
builtinToolNames: string[];
|
|
126
126
|
extensions: true | string[] | false;
|
|
127
127
|
skills: true | string[] | false;
|
|
128
|
+
promptMode: "replace" | "append";
|
|
128
129
|
} {
|
|
129
130
|
const key = resolveKey(type);
|
|
130
131
|
const config = key ? agents.get(key) : undefined;
|
|
@@ -135,6 +136,7 @@ export function getConfig(type: string): {
|
|
|
135
136
|
builtinToolNames: config.builtinToolNames ?? BUILTIN_TOOL_NAMES,
|
|
136
137
|
extensions: config.extensions,
|
|
137
138
|
skills: config.skills,
|
|
139
|
+
promptMode: config.promptMode,
|
|
138
140
|
};
|
|
139
141
|
}
|
|
140
142
|
|
|
@@ -147,6 +149,7 @@ export function getConfig(type: string): {
|
|
|
147
149
|
builtinToolNames: gp.builtinToolNames ?? BUILTIN_TOOL_NAMES,
|
|
148
150
|
extensions: gp.extensions,
|
|
149
151
|
skills: gp.skills,
|
|
152
|
+
promptMode: gp.promptMode,
|
|
150
153
|
};
|
|
151
154
|
}
|
|
152
155
|
|
|
@@ -157,21 +160,7 @@ export function getConfig(type: string): {
|
|
|
157
160
|
builtinToolNames: BUILTIN_TOOL_NAMES,
|
|
158
161
|
extensions: true,
|
|
159
162
|
skills: true,
|
|
163
|
+
promptMode: "append",
|
|
160
164
|
};
|
|
161
165
|
}
|
|
162
166
|
|
|
163
|
-
// ---- Backwards-compatible aliases ----
|
|
164
|
-
|
|
165
|
-
/** @deprecated Use registerAgents instead */
|
|
166
|
-
export const registerCustomAgents = registerAgents;
|
|
167
|
-
|
|
168
|
-
/** @deprecated Use getAgentConfig instead */
|
|
169
|
-
export function getCustomAgentConfig(name: string): AgentConfig | undefined {
|
|
170
|
-
const key = resolveKey(name);
|
|
171
|
-
return key ? agents.get(key) : undefined;
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
/** @deprecated Use getUserAgentNames instead */
|
|
175
|
-
export function getCustomAgentNames(): string[] {
|
|
176
|
-
return getUserAgentNames();
|
|
177
|
-
}
|
package/src/default-agents.ts
CHANGED
|
@@ -18,42 +18,8 @@ export const DEFAULT_AGENTS: Map<string, AgentConfig> = new Map([
|
|
|
18
18
|
// builtinToolNames omitted — means "all available tools" (resolved at lookup time)
|
|
19
19
|
extensions: true,
|
|
20
20
|
skills: true,
|
|
21
|
-
systemPrompt:
|
|
22
|
-
|
|
23
|
-
You have full access to read, write, edit files, and execute commands.
|
|
24
|
-
Do what has been asked; nothing more, nothing less.
|
|
25
|
-
|
|
26
|
-
# Tool Usage
|
|
27
|
-
- Use the read tool instead of cat/head/tail
|
|
28
|
-
- Use the edit tool instead of sed/awk
|
|
29
|
-
- Use the write tool instead of echo/heredoc
|
|
30
|
-
- Use the find tool instead of bash find/ls for file search
|
|
31
|
-
- Use the grep tool instead of bash grep/rg for content search
|
|
32
|
-
- Make independent tool calls in parallel
|
|
33
|
-
|
|
34
|
-
# File Operations
|
|
35
|
-
- NEVER create files unless absolutely necessary
|
|
36
|
-
- Prefer editing existing files over creating new ones
|
|
37
|
-
- NEVER create documentation files unless explicitly requested
|
|
38
|
-
|
|
39
|
-
# Git Safety
|
|
40
|
-
- NEVER update git config
|
|
41
|
-
- NEVER run destructive git commands (push --force, reset --hard, checkout ., restore ., clean -f, branch -D) without explicit request
|
|
42
|
-
- NEVER skip hooks (--no-verify, --no-gpg-sign) unless explicitly asked
|
|
43
|
-
- NEVER force push to main/master — warn the user if they request it
|
|
44
|
-
- Always create NEW commits, never amend existing ones. When a pre-commit hook fails, the commit did NOT happen — so --amend would modify the PREVIOUS commit. Fix the issue, re-stage, and create a NEW commit
|
|
45
|
-
- Stage specific files by name, not git add -A or git add .
|
|
46
|
-
- NEVER commit changes unless the user explicitly asks
|
|
47
|
-
- NEVER push unless the user explicitly asks
|
|
48
|
-
- NEVER use git commands with the -i flag (like git rebase -i or git add -i) — they require interactive input
|
|
49
|
-
- Do not use --no-edit with git rebase commands
|
|
50
|
-
- Do not commit files that likely contain secrets (.env, credentials.json, etc); warn the user if they request it
|
|
51
|
-
|
|
52
|
-
# Output
|
|
53
|
-
- Use absolute file paths
|
|
54
|
-
- Do not use emojis
|
|
55
|
-
- Be concise but complete`,
|
|
56
|
-
promptMode: "replace",
|
|
21
|
+
systemPrompt: "",
|
|
22
|
+
promptMode: "append",
|
|
57
23
|
inheritContext: false,
|
|
58
24
|
runInBackground: false,
|
|
59
25
|
isolated: false,
|
package/src/index.ts
CHANGED
|
@@ -30,6 +30,7 @@ import {
|
|
|
30
30
|
formatMs,
|
|
31
31
|
formatDuration,
|
|
32
32
|
getDisplayName,
|
|
33
|
+
getPromptModeLabel,
|
|
33
34
|
describeActivity,
|
|
34
35
|
type AgentDetails,
|
|
35
36
|
type AgentActivity,
|
|
@@ -120,20 +121,6 @@ function buildDetails(
|
|
|
120
121
|
};
|
|
121
122
|
}
|
|
122
123
|
|
|
123
|
-
/** Resolve system prompt overrides from an agent config. */
|
|
124
|
-
function resolveCustomPrompt(config: AgentConfig | undefined): {
|
|
125
|
-
systemPromptOverride?: string;
|
|
126
|
-
systemPromptAppend?: string;
|
|
127
|
-
} {
|
|
128
|
-
if (!config?.systemPrompt) return {};
|
|
129
|
-
// Default agents use their systemPrompt via buildAgentPrompt in agent-runner,
|
|
130
|
-
// not via override/append. Only non-default agents use this path.
|
|
131
|
-
if (config.isDefault) return {};
|
|
132
|
-
if (config.promptMode === "append") return { systemPromptAppend: config.systemPrompt };
|
|
133
|
-
return { systemPromptOverride: config.systemPrompt };
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
|
|
137
124
|
export default function (pi: ExtensionAPI) {
|
|
138
125
|
/** Reload agents from .pi/agents/*.md and merge with defaults (called on init and each Agent invocation). */
|
|
139
126
|
const reloadCustomAgents = () => {
|
|
@@ -161,9 +148,11 @@ export default function (pi: ExtensionAPI) {
|
|
|
161
148
|
agentActivity.delete(record.id);
|
|
162
149
|
widget.markFinished(record.id);
|
|
163
150
|
|
|
151
|
+
const tokens = safeFormatTokens(record.session);
|
|
152
|
+
const toolStats = tokens ? `Tool uses: ${record.toolUses} | ${tokens}` : `Tool uses: ${record.toolUses}`;
|
|
164
153
|
pi.sendUserMessage(
|
|
165
154
|
`Background agent completed: ${displayName} (${record.description})\n` +
|
|
166
|
-
`Agent ID: ${record.id} | Status: ${status} |
|
|
155
|
+
`Agent ID: ${record.id} | Status: ${status} | ${toolStats} | Duration: ${duration}\n\n` +
|
|
167
156
|
resultPreview,
|
|
168
157
|
{ deliverAs: "followUp" },
|
|
169
158
|
);
|
|
@@ -180,7 +169,9 @@ export default function (pi: ExtensionAPI) {
|
|
|
180
169
|
? record.result.slice(0, 300) + "\n...(truncated)"
|
|
181
170
|
: record.result
|
|
182
171
|
: "No output.";
|
|
183
|
-
|
|
172
|
+
const tokens = safeFormatTokens(record.session);
|
|
173
|
+
const toolStats = tokens ? `Tools: ${record.toolUses} | ${tokens}` : `Tools: ${record.toolUses}`;
|
|
174
|
+
return `- ${displayName} (${record.description})\n ID: ${record.id} | Status: ${status} | ${toolStats} | Duration: ${duration}\n ${resultPreview}`;
|
|
184
175
|
}
|
|
185
176
|
|
|
186
177
|
// ---- Group join manager ----
|
|
@@ -239,6 +230,21 @@ export default function (pi: ExtensionAPI) {
|
|
|
239
230
|
widget.update();
|
|
240
231
|
});
|
|
241
232
|
|
|
233
|
+
// Expose manager via Symbol.for() global registry for cross-package access.
|
|
234
|
+
// Standard Node.js pattern for cross-package singletons (used by OpenTelemetry, etc.).
|
|
235
|
+
const MANAGER_KEY = Symbol.for("pi-subagents:manager");
|
|
236
|
+
(globalThis as any)[MANAGER_KEY] = {
|
|
237
|
+
waitForAll: () => manager.waitForAll(),
|
|
238
|
+
hasRunning: () => manager.hasRunning(),
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
// Wait for all subagents on shutdown, then dispose the manager
|
|
242
|
+
pi.on("session_shutdown", async () => {
|
|
243
|
+
delete (globalThis as any)[MANAGER_KEY];
|
|
244
|
+
await manager.waitForAll();
|
|
245
|
+
manager.dispose();
|
|
246
|
+
});
|
|
247
|
+
|
|
242
248
|
// Live widget: show running agents above editor
|
|
243
249
|
const widget = new AgentWidget(manager, agentActivity);
|
|
244
250
|
|
|
@@ -539,8 +545,6 @@ Guidelines:
|
|
|
539
545
|
const runInBackground = params.run_in_background ?? customConfig?.runInBackground ?? false;
|
|
540
546
|
const isolated = params.isolated ?? customConfig?.isolated ?? false;
|
|
541
547
|
|
|
542
|
-
const { systemPromptOverride, systemPromptAppend } = resolveCustomPrompt(customConfig);
|
|
543
|
-
|
|
544
548
|
// Build display tags for non-default config
|
|
545
549
|
const parentModelId = ctx.model?.id;
|
|
546
550
|
const effectiveModelId = model?.id;
|
|
@@ -548,6 +552,8 @@ Guidelines:
|
|
|
548
552
|
? (model?.name ?? effectiveModelId).replace(/^Claude\s+/i, "").toLowerCase()
|
|
549
553
|
: undefined;
|
|
550
554
|
const agentTags: string[] = [];
|
|
555
|
+
const modeLabel = getPromptModeLabel(subagentType);
|
|
556
|
+
if (modeLabel) agentTags.push(modeLabel);
|
|
551
557
|
if (thinking) agentTags.push(`thinking: ${thinking}`);
|
|
552
558
|
if (isolated) agentTags.push("isolated");
|
|
553
559
|
// Shared base fields for all AgentDetails in this call
|
|
@@ -589,8 +595,6 @@ Guidelines:
|
|
|
589
595
|
isolated,
|
|
590
596
|
inheritContext,
|
|
591
597
|
thinkingLevel: thinking,
|
|
592
|
-
systemPromptOverride,
|
|
593
|
-
systemPromptAppend,
|
|
594
598
|
isBackground: true,
|
|
595
599
|
...bgCallbacks,
|
|
596
600
|
});
|
|
@@ -680,8 +684,6 @@ Guidelines:
|
|
|
680
684
|
isolated,
|
|
681
685
|
inheritContext,
|
|
682
686
|
thinkingLevel: thinking,
|
|
683
|
-
systemPromptOverride,
|
|
684
|
-
systemPromptAppend,
|
|
685
687
|
...fgCallbacks,
|
|
686
688
|
});
|
|
687
689
|
|
|
@@ -707,8 +709,10 @@ Guidelines:
|
|
|
707
709
|
}
|
|
708
710
|
|
|
709
711
|
const durationMs = (record.completedAt ?? Date.now()) - record.startedAt;
|
|
712
|
+
const statsParts = [`${record.toolUses} tool uses`];
|
|
713
|
+
if (tokenText) statsParts.push(tokenText);
|
|
710
714
|
return textResult(
|
|
711
|
-
`${fallbackNote}Agent completed in ${formatMs(durationMs)} (${
|
|
715
|
+
`${fallbackNote}Agent completed in ${formatMs(durationMs)} (${statsParts.join(", ")})${getStatusNote(record.status)}.\n\n` +
|
|
712
716
|
(record.result ?? "No output."),
|
|
713
717
|
details,
|
|
714
718
|
);
|
|
@@ -750,10 +754,12 @@ Guidelines:
|
|
|
750
754
|
|
|
751
755
|
const displayName = getDisplayName(record.type);
|
|
752
756
|
const duration = formatDuration(record.startedAt, record.completedAt);
|
|
757
|
+
const tokens = safeFormatTokens(record.session);
|
|
758
|
+
const toolStats = tokens ? `Tool uses: ${record.toolUses} | ${tokens}` : `Tool uses: ${record.toolUses}`;
|
|
753
759
|
|
|
754
760
|
let output =
|
|
755
761
|
`Agent: ${record.id}\n` +
|
|
756
|
-
`Type: ${displayName} | Status: ${record.status} |
|
|
762
|
+
`Type: ${displayName} | Status: ${record.status} | ${toolStats} | Duration: ${duration}\n` +
|
|
757
763
|
`Description: ${record.description}\n\n`;
|
|
758
764
|
|
|
759
765
|
if (record.status === "running") {
|