@tintinweb/pi-subagents 0.3.1 → 0.4.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/CHANGELOG.md +19 -1
- package/README.md +17 -15
- package/package.json +1 -1
- package/src/agent-manager.ts +0 -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 +15 -24
- package/src/prompts.ts +35 -20
- package/src/ui/agent-widget.ts +100 -24
- package/src/ui/conversation-viewer.ts +4 -2
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,22 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.4.0] - 2026-03-11
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- **XML-delimited prompt sections** — append-mode agents now wrap inherited content in `<inherited_system_prompt>`, `<sub_agent_context>`, and `<agent_instructions>` XML tags, giving the model explicit structure to distinguish inherited rules from sub-agent-specific instructions. Replace mode is unchanged.
|
|
12
|
+
- **Token count in agent results** — foreground agent results, background completion notifications, and `get_subagent_result` now include the token count alongside tool uses and duration (e.g. `Agent completed in 4.2s (12 tool uses, 33.8k token)`).
|
|
13
|
+
- **Widget overflow cap** — the running agents widget now caps at 12 lines. When exceeded, running agents are prioritized over finished ones and an overflow summary line shows hidden counts (e.g. `+3 more (1 running, 2 finished)`).
|
|
14
|
+
|
|
15
|
+
### Changed - **changing behavior**
|
|
16
|
+
- **General-purpose agent inherits parent prompt** — the default `general-purpose` agent now uses `promptMode: "append"` with an empty system prompt, making it a "parent twin" that inherits the full parent system prompt (including CLAUDE.md rules, project conventions, and safety guardrails). Previously it used a standalone prompt that duplicated a subset of the parent's rules. Explore and Plan are unchanged (standalone prompts). To customize: eject via `/agents` → select `general-purpose` → Eject, then edit the resulting `.md` file. Set `prompt_mode: replace` to go back to a standalone prompt, or keep `prompt_mode: append` and add extra instructions in the body.
|
|
17
|
+
- **Append-mode agents receive parent system prompt** — `buildAgentPrompt` now accepts the parent's system prompt and threads it into append-mode agents (env header + parent prompt + sub-agent context bridge + optional custom instructions). Replace-mode agents are unchanged.
|
|
18
|
+
- **Prompt pipeline simplified** — removed `systemPromptOverride`/`systemPromptAppend` from `SpawnOptions` and `RunOptions`. These were a separate code path where `index.ts` pre-resolved the prompt mode and passed raw strings into the runner, bypassing `buildAgentPrompt`. Now all prompt assembly flows through `buildAgentPrompt` using the agent's `promptMode` config — one code path, no special cases.
|
|
19
|
+
|
|
20
|
+
### Removed
|
|
21
|
+
- Deprecated backwards-compat aliases: `registerCustomAgents`, `getCustomAgentConfig`, `getCustomAgentNames` (use `registerAgents`, `getAgentConfig`, `getUserAgentNames`).
|
|
22
|
+
- `resolveCustomPrompt()` helper in index.ts — no longer needed now that prompt routing is config-driven.
|
|
23
|
+
|
|
8
24
|
## [0.3.1] - 2026-03-09
|
|
9
25
|
|
|
10
26
|
### Added
|
|
@@ -139,7 +155,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
139
155
|
### Added
|
|
140
156
|
- **Claude Code-style UI rendering** — `renderCall`/`renderResult`/`onUpdate` for live streaming progress
|
|
141
157
|
- Live activity descriptions: "searching, reading 3 files…"
|
|
142
|
-
- Token count display: "33.8k
|
|
158
|
+
- Token count display: "33.8k token"
|
|
143
159
|
- Per-agent tool use counter
|
|
144
160
|
- Expandable completed results (ctrl+o)
|
|
145
161
|
- Distinct states: running, background, completed, error, aborted
|
|
@@ -172,6 +188,8 @@ Initial release.
|
|
|
172
188
|
- **Thinking level** — per-agent extended thinking control
|
|
173
189
|
- **`/agent` and `/agents` commands**
|
|
174
190
|
|
|
191
|
+
[0.4.0]: https://github.com/tintinweb/pi-subagents/compare/v0.3.1...v0.4.0
|
|
192
|
+
[0.3.1]: https://github.com/tintinweb/pi-subagents/compare/v0.3.0...v0.3.1
|
|
175
193
|
[0.3.0]: https://github.com/tintinweb/pi-subagents/compare/v0.2.7...v0.3.0
|
|
176
194
|
[0.2.7]: https://github.com/tintinweb/pi-subagents/compare/v0.2.6...v0.2.7
|
|
177
195
|
[0.2.6]: https://github.com/tintinweb/pi-subagents/compare/v0.2.5...v0.2.6
|
package/README.md
CHANGED
|
@@ -57,9 +57,9 @@ The extension renders a persistent widget above the editor showing all active ag
|
|
|
57
57
|
|
|
58
58
|
```
|
|
59
59
|
● Agents
|
|
60
|
-
├─ ⠹ Agent Refactor auth module · 5 tool uses · 33.8k
|
|
60
|
+
├─ ⠹ Agent Refactor auth module · 5 tool uses · 33.8k token · 12.3s
|
|
61
61
|
│ ⎿ editing 2 files…
|
|
62
|
-
├─ ⠹ Explore Find auth files · 3 tool uses · 12.4k
|
|
62
|
+
├─ ⠹ Explore Find auth files · 3 tool uses · 12.4k token · 4.1s
|
|
63
63
|
│ ⎿ searching…
|
|
64
64
|
└─ 2 queued
|
|
65
65
|
```
|
|
@@ -68,24 +68,26 @@ Individual agent results render Claude Code-style in the conversation:
|
|
|
68
68
|
|
|
69
69
|
| State | Example |
|
|
70
70
|
|-------|---------|
|
|
71
|
-
| **Running** | `⠹ 3 tool uses · 12.4k
|
|
72
|
-
| **Completed** | `✓ 5 tool uses · 33.8k
|
|
73
|
-
| **Wrapped up** | `✓ 50 tool uses · 89.1k
|
|
74
|
-
| **Stopped** | `■ 3 tool uses · 12.4k
|
|
75
|
-
| **Error** | `✗ 3 tool uses · 12.4k
|
|
76
|
-
| **Aborted** | `✗ 55 tool uses · 102.3k
|
|
71
|
+
| **Running** | `⠹ 3 tool uses · 12.4k token` / `⎿ searching, reading 3 files…` |
|
|
72
|
+
| **Completed** | `✓ 5 tool uses · 33.8k token · 12.3s` / `⎿ Done` |
|
|
73
|
+
| **Wrapped up** | `✓ 50 tool uses · 89.1k token · 45.2s` / `⎿ Wrapped up (turn limit)` |
|
|
74
|
+
| **Stopped** | `■ 3 tool uses · 12.4k token` / `⎿ Stopped` |
|
|
75
|
+
| **Error** | `✗ 3 tool uses · 12.4k token` / `⎿ Error: timeout` |
|
|
76
|
+
| **Aborted** | `✗ 55 tool uses · 102.3k token` / `⎿ Aborted (max turns exceeded)` |
|
|
77
77
|
|
|
78
78
|
Completed results can be expanded (ctrl+o in pi) to show the full agent output inline.
|
|
79
79
|
|
|
80
80
|
## Default Agent Types
|
|
81
81
|
|
|
82
|
-
| Type | Tools | Model | Description |
|
|
83
|
-
|
|
84
|
-
| `general-purpose` | all 7 | inherit |
|
|
85
|
-
| `Explore` | read, bash, grep, find, ls | haiku (falls back to inherit) | Fast codebase exploration (read-only) |
|
|
86
|
-
| `Plan` | read, bash, grep, find, ls | inherit | Software architect for implementation planning (read-only) |
|
|
82
|
+
| Type | Tools | Model | Prompt Mode | Description |
|
|
83
|
+
|------|-------|-------|-------------|-------------|
|
|
84
|
+
| `general-purpose` | all 7 | inherit | `append` (parent twin) | Inherits the parent's full system prompt — same rules, CLAUDE.md, project conventions |
|
|
85
|
+
| `Explore` | read, bash, grep, find, ls | haiku (falls back to inherit) | `replace` (standalone) | Fast codebase exploration (read-only) |
|
|
86
|
+
| `Plan` | read, bash, grep, find, ls | inherit | `replace` (standalone) | Software architect for implementation planning (read-only) |
|
|
87
87
|
|
|
88
|
-
|
|
88
|
+
The `general-purpose` agent is a **parent twin** — it receives the parent's entire system prompt plus a sub-agent context bridge, so it follows the same rules the parent does. Explore and Plan use standalone prompts tailored to their read-only roles.
|
|
89
|
+
|
|
90
|
+
Default agents can be **ejected** (`/agents` → select agent → Eject) to export them as `.md` files for customization, **overridden** by creating a `.md` file with the same name (e.g. `.pi/agents/general-purpose.md`), or **disabled** per-project with `enabled: false` frontmatter.
|
|
89
91
|
|
|
90
92
|
## Custom Agents
|
|
91
93
|
|
|
@@ -140,7 +142,7 @@ All fields are optional — sensible defaults for everything.
|
|
|
140
142
|
| `model` | inherit parent | Model — `provider/modelId` or fuzzy name (`"haiku"`, `"sonnet"`) |
|
|
141
143
|
| `thinking` | inherit | off, minimal, low, medium, high, xhigh |
|
|
142
144
|
| `max_turns` | 50 | Max agentic turns before graceful shutdown |
|
|
143
|
-
| `prompt_mode` | `replace` | `replace`: body is the full system prompt. `append`: body appended to
|
|
145
|
+
| `prompt_mode` | `replace` | `replace`: body is the full system prompt. `append`: body appended to parent's prompt (agent acts as a "parent twin" with optional extra instructions) |
|
|
144
146
|
| `inherit_context` | `false` | Fork parent conversation into agent |
|
|
145
147
|
| `run_in_background` | `false` | Run in background by default |
|
|
146
148
|
| `isolated` | `false` | No extension/MCP tools, only built-in |
|
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++;
|
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 ----
|
|
@@ -539,8 +530,6 @@ Guidelines:
|
|
|
539
530
|
const runInBackground = params.run_in_background ?? customConfig?.runInBackground ?? false;
|
|
540
531
|
const isolated = params.isolated ?? customConfig?.isolated ?? false;
|
|
541
532
|
|
|
542
|
-
const { systemPromptOverride, systemPromptAppend } = resolveCustomPrompt(customConfig);
|
|
543
|
-
|
|
544
533
|
// Build display tags for non-default config
|
|
545
534
|
const parentModelId = ctx.model?.id;
|
|
546
535
|
const effectiveModelId = model?.id;
|
|
@@ -548,6 +537,8 @@ Guidelines:
|
|
|
548
537
|
? (model?.name ?? effectiveModelId).replace(/^Claude\s+/i, "").toLowerCase()
|
|
549
538
|
: undefined;
|
|
550
539
|
const agentTags: string[] = [];
|
|
540
|
+
const modeLabel = getPromptModeLabel(subagentType);
|
|
541
|
+
if (modeLabel) agentTags.push(modeLabel);
|
|
551
542
|
if (thinking) agentTags.push(`thinking: ${thinking}`);
|
|
552
543
|
if (isolated) agentTags.push("isolated");
|
|
553
544
|
// Shared base fields for all AgentDetails in this call
|
|
@@ -589,8 +580,6 @@ Guidelines:
|
|
|
589
580
|
isolated,
|
|
590
581
|
inheritContext,
|
|
591
582
|
thinkingLevel: thinking,
|
|
592
|
-
systemPromptOverride,
|
|
593
|
-
systemPromptAppend,
|
|
594
583
|
isBackground: true,
|
|
595
584
|
...bgCallbacks,
|
|
596
585
|
});
|
|
@@ -680,8 +669,6 @@ Guidelines:
|
|
|
680
669
|
isolated,
|
|
681
670
|
inheritContext,
|
|
682
671
|
thinkingLevel: thinking,
|
|
683
|
-
systemPromptOverride,
|
|
684
|
-
systemPromptAppend,
|
|
685
672
|
...fgCallbacks,
|
|
686
673
|
});
|
|
687
674
|
|
|
@@ -707,8 +694,10 @@ Guidelines:
|
|
|
707
694
|
}
|
|
708
695
|
|
|
709
696
|
const durationMs = (record.completedAt ?? Date.now()) - record.startedAt;
|
|
697
|
+
const statsParts = [`${record.toolUses} tool uses`];
|
|
698
|
+
if (tokenText) statsParts.push(tokenText);
|
|
710
699
|
return textResult(
|
|
711
|
-
`${fallbackNote}Agent completed in ${formatMs(durationMs)} (${
|
|
700
|
+
`${fallbackNote}Agent completed in ${formatMs(durationMs)} (${statsParts.join(", ")})${getStatusNote(record.status)}.\n\n` +
|
|
712
701
|
(record.result ?? "No output."),
|
|
713
702
|
details,
|
|
714
703
|
);
|
|
@@ -750,10 +739,12 @@ Guidelines:
|
|
|
750
739
|
|
|
751
740
|
const displayName = getDisplayName(record.type);
|
|
752
741
|
const duration = formatDuration(record.startedAt, record.completedAt);
|
|
742
|
+
const tokens = safeFormatTokens(record.session);
|
|
743
|
+
const toolStats = tokens ? `Tool uses: ${record.toolUses} | ${tokens}` : `Tool uses: ${record.toolUses}`;
|
|
753
744
|
|
|
754
745
|
let output =
|
|
755
746
|
`Agent: ${record.id}\n` +
|
|
756
|
-
`Type: ${displayName} | Status: ${record.status} |
|
|
747
|
+
`Type: ${displayName} | Status: ${record.status} | ${toolStats} | Duration: ${duration}\n` +
|
|
757
748
|
`Description: ${record.description}\n\n`;
|
|
758
749
|
|
|
759
750
|
if (record.status === "running") {
|
package/src/prompts.ts
CHANGED
|
@@ -7,42 +7,57 @@ import type { AgentConfig, EnvInfo } from "./types.js";
|
|
|
7
7
|
/**
|
|
8
8
|
* Build the system prompt for an agent from its config.
|
|
9
9
|
*
|
|
10
|
-
* - "replace" mode:
|
|
11
|
-
* - "append" mode:
|
|
10
|
+
* - "replace" mode: env header + config.systemPrompt (full control, no parent identity)
|
|
11
|
+
* - "append" mode: env header + parent system prompt + sub-agent context + config.systemPrompt
|
|
12
|
+
* - "append" with empty systemPrompt: pure parent clone
|
|
13
|
+
*
|
|
14
|
+
* @param parentSystemPrompt The parent agent's effective system prompt (for append mode).
|
|
12
15
|
*/
|
|
13
|
-
export function buildAgentPrompt(
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
export function buildAgentPrompt(
|
|
17
|
+
config: AgentConfig,
|
|
18
|
+
cwd: string,
|
|
19
|
+
env: EnvInfo,
|
|
20
|
+
parentSystemPrompt?: string,
|
|
21
|
+
): string {
|
|
22
|
+
const envBlock = `# Environment
|
|
18
23
|
Working directory: ${cwd}
|
|
19
24
|
${env.isGitRepo ? `Git repository: yes\nBranch: ${env.branch}` : "Not a git repository"}
|
|
20
25
|
Platform: ${env.platform}`;
|
|
21
26
|
|
|
22
27
|
if (config.promptMode === "append") {
|
|
23
|
-
const
|
|
28
|
+
const identity = parentSystemPrompt || genericBase;
|
|
24
29
|
|
|
25
|
-
|
|
26
|
-
You are a
|
|
27
|
-
You have full access to read, write, edit files, and execute commands.
|
|
28
|
-
Do what has been asked; nothing more, nothing less.
|
|
29
|
-
|
|
30
|
-
# Tool Usage
|
|
30
|
+
const bridge = `<sub_agent_context>
|
|
31
|
+
You are operating as a sub-agent invoked to handle a specific task.
|
|
31
32
|
- Use the read tool instead of cat/head/tail
|
|
32
33
|
- Use the edit tool instead of sed/awk
|
|
33
34
|
- Use the write tool instead of echo/heredoc
|
|
34
35
|
- Use the find tool instead of bash find/ls for file search
|
|
35
36
|
- Use the grep tool instead of bash grep/rg for content search
|
|
36
37
|
- Make independent tool calls in parallel
|
|
37
|
-
|
|
38
|
-
# Output
|
|
39
38
|
- Use absolute file paths
|
|
40
39
|
- Do not use emojis
|
|
41
|
-
- Be concise but complete
|
|
40
|
+
- Be concise but complete
|
|
41
|
+
</sub_agent_context>`;
|
|
42
42
|
|
|
43
|
-
|
|
43
|
+
const customSection = config.systemPrompt?.trim()
|
|
44
|
+
? `\n\n<agent_instructions>\n${config.systemPrompt}\n</agent_instructions>`
|
|
45
|
+
: "";
|
|
46
|
+
|
|
47
|
+
return envBlock + "\n\n<inherited_system_prompt>\n" + identity + "\n</inherited_system_prompt>\n\n" + bridge + customSection;
|
|
44
48
|
}
|
|
45
49
|
|
|
46
|
-
// "replace" mode — header + the config's full system prompt
|
|
47
|
-
|
|
50
|
+
// "replace" mode — env header + the config's full system prompt
|
|
51
|
+
const replaceHeader = `You are a pi coding agent sub-agent.
|
|
52
|
+
You have been invoked to handle a specific task autonomously.
|
|
53
|
+
|
|
54
|
+
${envBlock}`;
|
|
55
|
+
|
|
56
|
+
return replaceHeader + "\n\n" + config.systemPrompt;
|
|
48
57
|
}
|
|
58
|
+
|
|
59
|
+
/** Fallback base prompt when parent system prompt is unavailable in append mode. */
|
|
60
|
+
const genericBase = `# Role
|
|
61
|
+
You are a general-purpose coding agent for complex, multi-step tasks.
|
|
62
|
+
You have full access to read, write, edit files, and execute commands.
|
|
63
|
+
Do what has been asked; nothing more, nothing less.`;
|
package/src/ui/agent-widget.ts
CHANGED
|
@@ -12,6 +12,9 @@ import { getConfig } from "../agent-types.js";
|
|
|
12
12
|
|
|
13
13
|
// ---- Constants ----
|
|
14
14
|
|
|
15
|
+
/** Maximum number of rendered lines before overflow collapse kicks in. */
|
|
16
|
+
const MAX_WIDGET_LINES = 12;
|
|
17
|
+
|
|
15
18
|
/** Braille spinner frames for animated running indicator. */
|
|
16
19
|
export const SPINNER = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
|
17
20
|
|
|
@@ -77,11 +80,11 @@ export interface AgentDetails {
|
|
|
77
80
|
|
|
78
81
|
// ---- Formatting helpers ----
|
|
79
82
|
|
|
80
|
-
/** Format a token count
|
|
83
|
+
/** Format a token count compactly: "33.8k token", "1.2M token". */
|
|
81
84
|
export function formatTokens(count: number): string {
|
|
82
|
-
if (count >= 1_000_000) return `${(count / 1_000_000).toFixed(1)}M
|
|
83
|
-
if (count >= 1_000) return `${(count / 1_000).toFixed(1)}k
|
|
84
|
-
return `${count}
|
|
85
|
+
if (count >= 1_000_000) return `${(count / 1_000_000).toFixed(1)}M token`;
|
|
86
|
+
if (count >= 1_000) return `${(count / 1_000).toFixed(1)}k token`;
|
|
87
|
+
return `${count} token`;
|
|
85
88
|
}
|
|
86
89
|
|
|
87
90
|
/** Format milliseconds as human-readable duration. */
|
|
@@ -100,6 +103,12 @@ export function getDisplayName(type: SubagentType): string {
|
|
|
100
103
|
return getConfig(type).displayName;
|
|
101
104
|
}
|
|
102
105
|
|
|
106
|
+
/** Short label for prompt mode: "twin" for append, nothing for replace (the default). */
|
|
107
|
+
export function getPromptModeLabel(type: SubagentType): string | undefined {
|
|
108
|
+
const config = getConfig(type);
|
|
109
|
+
return config.promptMode === "append" ? "twin" : undefined;
|
|
110
|
+
}
|
|
111
|
+
|
|
103
112
|
/** Truncate text to a single line, max `len` chars. */
|
|
104
113
|
function truncateLine(text: string, len = 60): string {
|
|
105
114
|
const line = text.split("\n").find(l => l.trim())?.trim() ?? "";
|
|
@@ -193,6 +202,7 @@ export class AgentWidget {
|
|
|
193
202
|
/** Render a finished agent line. */
|
|
194
203
|
private renderFinishedLine(a: { type: SubagentType; status: string; description: string; toolUses: number; startedAt: number; completedAt?: number; error?: string }, theme: Theme): string {
|
|
195
204
|
const name = getDisplayName(a.type);
|
|
205
|
+
const modeLabel = getPromptModeLabel(a.type);
|
|
196
206
|
const duration = formatMs((a.completedAt ?? Date.now()) - a.startedAt);
|
|
197
207
|
|
|
198
208
|
let icon: string;
|
|
@@ -220,7 +230,8 @@ export class AgentWidget {
|
|
|
220
230
|
if (a.toolUses > 0) parts.push(`${a.toolUses} tool use${a.toolUses === 1 ? "" : "s"}`);
|
|
221
231
|
parts.push(duration);
|
|
222
232
|
|
|
223
|
-
|
|
233
|
+
const modeTag = modeLabel ? ` ${theme.fg("dim", `(${modeLabel})`)}` : "";
|
|
234
|
+
return `${icon} ${theme.fg("dim", name)}${modeTag} ${theme.fg("dim", a.description)} ${theme.fg("dim", "·")} ${theme.fg("dim", parts.join(" · "))}${statusText}`;
|
|
224
235
|
}
|
|
225
236
|
|
|
226
237
|
/** Force an immediate widget update. */
|
|
@@ -268,23 +279,20 @@ export class AgentWidget {
|
|
|
268
279
|
const truncate = (line: string) => truncateToWidth(line, w);
|
|
269
280
|
const headingColor = hasActive ? "accent" : "dim";
|
|
270
281
|
const headingIcon = hasActive ? "●" : "○";
|
|
271
|
-
const lines: string[] = [truncate(theme.fg(headingColor, headingIcon) + " " + theme.fg(headingColor, "Agents"))];
|
|
272
282
|
|
|
273
|
-
//
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
283
|
+
// Build sections separately for overflow-aware assembly.
|
|
284
|
+
// Each running agent = 2 lines (header + activity), finished = 1 line, queued = 1 line.
|
|
285
|
+
|
|
286
|
+
const finishedLines: string[] = [];
|
|
287
|
+
for (const a of finished) {
|
|
288
|
+
finishedLines.push(truncate(theme.fg("dim", "├─") + " " + this.renderFinishedLine(a, theme)));
|
|
279
289
|
}
|
|
280
290
|
|
|
281
|
-
//
|
|
282
|
-
const
|
|
283
|
-
for (let i = 0; i < running.length; i++) {
|
|
284
|
-
const a = running[i];
|
|
285
|
-
const isLast = isLastSection && i === running.length - 1;
|
|
286
|
-
const connector = isLast ? "└─" : "├─";
|
|
291
|
+
const runningLines: string[][] = []; // each entry is [header, activity]
|
|
292
|
+
for (const a of running) {
|
|
287
293
|
const name = getDisplayName(a.type);
|
|
294
|
+
const modeLabel = getPromptModeLabel(a.type);
|
|
295
|
+
const modeTag = modeLabel ? ` ${theme.fg("dim", `(${modeLabel})`)}` : "";
|
|
288
296
|
const elapsed = formatMs(Date.now() - a.startedAt);
|
|
289
297
|
|
|
290
298
|
const bg = this.agentActivity.get(a.id);
|
|
@@ -302,14 +310,82 @@ export class AgentWidget {
|
|
|
302
310
|
|
|
303
311
|
const activity = bg ? describeActivity(bg.activeTools, bg.responseText) : "thinking…";
|
|
304
312
|
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
313
|
+
runningLines.push([
|
|
314
|
+
truncate(theme.fg("dim", "├─") + ` ${theme.fg("accent", frame)} ${theme.bold(name)}${modeTag} ${theme.fg("muted", a.description)} ${theme.fg("dim", "·")} ${theme.fg("dim", statsText)}`),
|
|
315
|
+
truncate(theme.fg("dim", "│ ") + theme.fg("dim", ` ⎿ ${activity}`)),
|
|
316
|
+
]);
|
|
308
317
|
}
|
|
309
318
|
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
319
|
+
const queuedLine = queued.length > 0
|
|
320
|
+
? truncate(theme.fg("dim", "├─") + ` ${theme.fg("muted", "◦")} ${theme.fg("dim", `${queued.length} queued`)}`)
|
|
321
|
+
: undefined;
|
|
322
|
+
|
|
323
|
+
// Assemble with overflow cap (heading + overflow indicator = 2 reserved lines).
|
|
324
|
+
const maxBody = MAX_WIDGET_LINES - 1; // heading takes 1 line
|
|
325
|
+
const totalBody = finishedLines.length + runningLines.length * 2 + (queuedLine ? 1 : 0);
|
|
326
|
+
|
|
327
|
+
const lines: string[] = [truncate(theme.fg(headingColor, headingIcon) + " " + theme.fg(headingColor, "Agents"))];
|
|
328
|
+
|
|
329
|
+
if (totalBody <= maxBody) {
|
|
330
|
+
// Everything fits — add all lines and fix up connectors for the last item.
|
|
331
|
+
lines.push(...finishedLines);
|
|
332
|
+
for (const pair of runningLines) lines.push(...pair);
|
|
333
|
+
if (queuedLine) lines.push(queuedLine);
|
|
334
|
+
|
|
335
|
+
// Fix last connector: swap ├─ → └─ and │ → space for activity lines.
|
|
336
|
+
if (lines.length > 1) {
|
|
337
|
+
const last = lines.length - 1;
|
|
338
|
+
lines[last] = lines[last].replace("├─", "└─");
|
|
339
|
+
// If last item is a running agent activity line, fix indent of that line
|
|
340
|
+
// and fix the header line above it.
|
|
341
|
+
if (runningLines.length > 0 && !queuedLine) {
|
|
342
|
+
// The last two lines are the last running agent's header + activity.
|
|
343
|
+
if (last >= 2) {
|
|
344
|
+
lines[last - 1] = lines[last - 1].replace("├─", "└─");
|
|
345
|
+
lines[last] = lines[last].replace("│ ", " ");
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
} else {
|
|
350
|
+
// Overflow — prioritize: running > queued > finished.
|
|
351
|
+
// Reserve 1 line for overflow indicator.
|
|
352
|
+
let budget = maxBody - 1;
|
|
353
|
+
let hiddenRunning = 0;
|
|
354
|
+
let hiddenFinished = 0;
|
|
355
|
+
|
|
356
|
+
// 1. Running agents (2 lines each)
|
|
357
|
+
for (const pair of runningLines) {
|
|
358
|
+
if (budget >= 2) {
|
|
359
|
+
lines.push(...pair);
|
|
360
|
+
budget -= 2;
|
|
361
|
+
} else {
|
|
362
|
+
hiddenRunning++;
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// 2. Queued line
|
|
367
|
+
if (queuedLine && budget >= 1) {
|
|
368
|
+
lines.push(queuedLine);
|
|
369
|
+
budget--;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
// 3. Finished agents
|
|
373
|
+
for (const fl of finishedLines) {
|
|
374
|
+
if (budget >= 1) {
|
|
375
|
+
lines.push(fl);
|
|
376
|
+
budget--;
|
|
377
|
+
} else {
|
|
378
|
+
hiddenFinished++;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// Overflow summary
|
|
383
|
+
const overflowParts: string[] = [];
|
|
384
|
+
if (hiddenRunning > 0) overflowParts.push(`${hiddenRunning} running`);
|
|
385
|
+
if (hiddenFinished > 0) overflowParts.push(`${hiddenFinished} finished`);
|
|
386
|
+
const overflowText = overflowParts.join(", ");
|
|
387
|
+
lines.push(truncate(theme.fg("dim", "└─") + ` ${theme.fg("dim", `+${hiddenRunning + hiddenFinished} more (${overflowText})`)}`)
|
|
388
|
+
);
|
|
313
389
|
}
|
|
314
390
|
|
|
315
391
|
return { render: () => lines, invalidate: () => {} };
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
import { matchesKey, truncateToWidth, visibleWidth, wrapTextWithAnsi, type Component, type TUI } from "@mariozechner/pi-tui";
|
|
9
9
|
import type { AgentSession } from "@mariozechner/pi-coding-agent";
|
|
10
10
|
import type { Theme } from "./agent-widget.js";
|
|
11
|
-
import { formatTokens, formatDuration, getDisplayName, describeActivity, type AgentActivity } from "./agent-widget.js";
|
|
11
|
+
import { formatTokens, formatDuration, getDisplayName, getPromptModeLabel, describeActivity, type AgentActivity } from "./agent-widget.js";
|
|
12
12
|
import type { AgentRecord } from "../types.js";
|
|
13
13
|
import { extractText } from "../context.js";
|
|
14
14
|
|
|
@@ -89,6 +89,8 @@ export class ConversationViewer implements Component {
|
|
|
89
89
|
// Header
|
|
90
90
|
lines.push(hrTop);
|
|
91
91
|
const name = getDisplayName(this.record.type);
|
|
92
|
+
const modeLabel = getPromptModeLabel(this.record.type);
|
|
93
|
+
const modeTag = modeLabel ? ` ${th.fg("dim", `(${modeLabel})`)}` : "";
|
|
92
94
|
const statusIcon = this.record.status === "running"
|
|
93
95
|
? th.fg("accent", "●")
|
|
94
96
|
: this.record.status === "completed"
|
|
@@ -109,7 +111,7 @@ export class ConversationViewer implements Component {
|
|
|
109
111
|
}
|
|
110
112
|
|
|
111
113
|
lines.push(row(
|
|
112
|
-
`${statusIcon} ${th.bold(name)} ${th.fg("muted", this.record.description)} ${th.fg("dim", "·")} ${th.fg("dim", headerParts.join(" · "))}`,
|
|
114
|
+
`${statusIcon} ${th.bold(name)}${modeTag} ${th.fg("muted", this.record.description)} ${th.fg("dim", "·")} ${th.fg("dim", headerParts.join(" · "))}`,
|
|
113
115
|
));
|
|
114
116
|
lines.push(hrMid);
|
|
115
117
|
|