pi-fast-subagent 0.6.1 → 0.7.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/README.md +20 -6
- package/agents/general.md +9 -0
- package/agents/scout.md +9 -1
- package/agents.ts +10 -8
- package/index.ts +204 -31
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -72,12 +72,13 @@ You are code exploration specialist. Read relevant files, trace data flow, summa
|
|
|
72
72
|
|
|
73
73
|
### `tools:` field
|
|
74
74
|
|
|
75
|
-
Controls which tools the subagent has access to.
|
|
75
|
+
Controls which tools the subagent has access to. The default is **all tools** — builtins plus parent extensions (web_search, fetch_content, mcp, playwright, …). Agents opt into lean mode with `tools: builtins` or an explicit built-in allowlist.
|
|
76
76
|
|
|
77
77
|
| Value | Behavior |
|
|
78
78
|
|-------|----------|
|
|
79
|
-
| *(omitted)* |
|
|
79
|
+
| *(omitted)* | Builtins + every parent extension (**default**) |
|
|
80
80
|
| `all` | Same as omitted — explicit "everything" |
|
|
81
|
+
| `builtins` | Builtins only — `read, bash, edit, write, grep, find, ls` |
|
|
81
82
|
| `none` | No tools at all — pure reasoning agent |
|
|
82
83
|
| comma list | Allowlist; extensions auto-load only if any listed tool is non-builtin |
|
|
83
84
|
|
|
@@ -95,9 +96,10 @@ tools: none
|
|
|
95
96
|
|
|
96
97
|
```md
|
|
97
98
|
---
|
|
98
|
-
name:
|
|
99
|
-
description:
|
|
100
|
-
|
|
99
|
+
name: scout
|
|
100
|
+
description: Read-only code explorer
|
|
101
|
+
# drop `edit` and `write` so the agent cannot mutate the codebase
|
|
102
|
+
tools: read, bash, grep, find, ls
|
|
101
103
|
---
|
|
102
104
|
```
|
|
103
105
|
|
|
@@ -105,11 +107,23 @@ tools: read, bash, edit, write, grep, find, ls
|
|
|
105
107
|
---
|
|
106
108
|
name: researcher
|
|
107
109
|
description: Web research agent
|
|
110
|
+
# listing `web_search` triggers extension loading; `read` + `write` keep the rest local
|
|
108
111
|
tools: read, write, web_search, fetch_content
|
|
109
112
|
---
|
|
110
113
|
```
|
|
111
114
|
|
|
112
|
-
|
|
115
|
+
```md
|
|
116
|
+
---
|
|
117
|
+
name: general
|
|
118
|
+
description: Do-anything helper
|
|
119
|
+
# `tools` omitted means all tools; `tools: all` is equivalent
|
|
120
|
+
tools: all
|
|
121
|
+
---
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
> **Performance note:** omitted `tools` / `tools: all` loads every installed pi extension into the subagent session. That adds startup cost (extension init, possibly MCP server spawn, playwright runtime, …) and token cost (bigger system prompt). Use `tools: builtins` or list specific tools for tight, focused agents.
|
|
125
|
+
|
|
126
|
+
**YAML comments** (`# …`) are allowed inside the frontmatter — handy for documenting *why* a particular tool set was chosen. See `agents/general.md` and `agents/scout.md` for examples.
|
|
113
127
|
|
|
114
128
|
## Background Agents
|
|
115
129
|
|
package/agents/general.md
CHANGED
|
@@ -2,6 +2,15 @@
|
|
|
2
2
|
name: general
|
|
3
3
|
description: General-purpose helper for coding, analysis, writing, debugging, and task execution
|
|
4
4
|
model: anthropic/claude-haiku-4-5
|
|
5
|
+
|
|
6
|
+
# tools: which tools this agent can use.
|
|
7
|
+
# (omit) → all tools: builtins + every parent extension (default)
|
|
8
|
+
# all → same as omitted — explicit "everything"
|
|
9
|
+
# builtins → read, bash, edit, write, grep, find, ls only (fast startup)
|
|
10
|
+
# none → no tools — pure reasoning
|
|
11
|
+
# comma-separated list → explicit allowlist, e.g. `read, grep, web_search`
|
|
12
|
+
# General is meant to be a do-anything fallback, so it keeps everything explicit.
|
|
13
|
+
tools: all
|
|
5
14
|
---
|
|
6
15
|
|
|
7
16
|
You are general-purpose subagent.
|
package/agents/scout.md
CHANGED
|
@@ -2,7 +2,15 @@
|
|
|
2
2
|
name: scout
|
|
3
3
|
description: Explores codebases, maps structure, traces data flow, answers how things work across many files
|
|
4
4
|
model: anthropic/claude-haiku-4-5
|
|
5
|
-
|
|
5
|
+
|
|
6
|
+
# tools: which tools this agent can use.
|
|
7
|
+
# (omit) → all tools: builtins + every parent extension (default)
|
|
8
|
+
# all → same as omitted — explicit "everything"
|
|
9
|
+
# builtins → read, bash, edit, write, grep, find, ls only (fast startup)
|
|
10
|
+
# none → no tools — pure reasoning
|
|
11
|
+
# comma-separated list → explicit allowlist
|
|
12
|
+
# Scout is read-only: no `edit`, no `write`, no extension tools. Keeps the agent from mutating the codebase.
|
|
13
|
+
tools: read, bash, grep, find, ls
|
|
6
14
|
---
|
|
7
15
|
|
|
8
16
|
You are code exploration specialist.
|
package/agents.ts
CHANGED
|
@@ -10,19 +10,19 @@ import { getAgentDir, parseFrontmatter } from "@mariozechner/pi-coding-agent";
|
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
12
|
* tools frontmatter semantics:
|
|
13
|
-
* unset →
|
|
14
|
-
* `all` → all builtins + all
|
|
13
|
+
* unset → all builtins + all parent extensions — DEFAULT
|
|
14
|
+
* `all` → all builtins + all parent extensions (web_search, fetch_content, mcp, …)
|
|
15
|
+
* `builtins` → built-in coding tools only (read, bash, edit, write, grep, find, ls)
|
|
15
16
|
* `none` → no tools at all
|
|
16
17
|
* comma list → allowlist; extensions auto-loaded if any listed tool is non-builtin
|
|
17
|
-
* for lean "builtins-only" mode, list them explicitly:
|
|
18
|
-
* tools: read, bash, edit, write, grep, find, ls
|
|
19
18
|
*
|
|
20
19
|
* Represented as:
|
|
21
|
-
* "
|
|
20
|
+
* "builtins" → only built-in coding tools
|
|
21
|
+
* "all" → everything (default)
|
|
22
22
|
* "none" → no tools
|
|
23
23
|
* string[] → allowlist
|
|
24
24
|
*/
|
|
25
|
-
export type AgentTools = "all" | "none" | string[];
|
|
25
|
+
export type AgentTools = "builtins" | "all" | "none" | string[];
|
|
26
26
|
|
|
27
27
|
export const BUILTIN_TOOL_NAMES = ["read", "bash", "edit", "write", "grep", "find", "ls"] as const;
|
|
28
28
|
|
|
@@ -40,11 +40,12 @@ const BUILTIN_TOOLS = new Set<string>(BUILTIN_TOOL_NAMES);
|
|
|
40
40
|
|
|
41
41
|
export function agentNeedsExtensions(tools: AgentTools): boolean {
|
|
42
42
|
if (tools === "all") return true;
|
|
43
|
-
if (tools === "none") return false;
|
|
43
|
+
if (tools === "builtins" || tools === "none") return false;
|
|
44
44
|
return tools.some((t) => !BUILTIN_TOOLS.has(t));
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
-
// Default:
|
|
47
|
+
// Default: all tools, matching pi-subagents behavior. Agents opt into lean mode
|
|
48
|
+
// with `tools: builtins` or explicit built-in allowlists.
|
|
48
49
|
function parseToolsField(raw: unknown): AgentTools {
|
|
49
50
|
if (raw === undefined || raw === null) return "all";
|
|
50
51
|
const str = String(raw).trim();
|
|
@@ -52,6 +53,7 @@ function parseToolsField(raw: unknown): AgentTools {
|
|
|
52
53
|
const lower = str.toLowerCase();
|
|
53
54
|
if (lower === "all") return "all";
|
|
54
55
|
if (lower === "none") return "none";
|
|
56
|
+
if (lower === "builtins" || lower === "builtin") return "builtins";
|
|
55
57
|
const list = str.split(",").map((t) => t.trim()).filter(Boolean);
|
|
56
58
|
return list.length ? list : "all";
|
|
57
59
|
}
|
package/index.ts
CHANGED
|
@@ -14,6 +14,7 @@ import type {
|
|
|
14
14
|
AgentToolUpdateCallback,
|
|
15
15
|
ExtensionAPI,
|
|
16
16
|
ExtensionContext,
|
|
17
|
+
ResourceLoader,
|
|
17
18
|
ToolRenderResultOptions,
|
|
18
19
|
} from "@mariozechner/pi-coding-agent";
|
|
19
20
|
import { BackgroundJobManager } from "./background-job-manager.js";
|
|
@@ -36,6 +37,7 @@ import { type AgentConfig, agentNeedsExtensions, discoverAgents } from "./agents
|
|
|
36
37
|
|
|
37
38
|
function formatTools(tools: AgentConfig["tools"]): string {
|
|
38
39
|
if (tools === "all") return "all";
|
|
40
|
+
if (tools === "builtins") return "builtins (default)";
|
|
39
41
|
if (tools === "none") return "none";
|
|
40
42
|
return tools.join(", ");
|
|
41
43
|
}
|
|
@@ -125,6 +127,125 @@ function refreshBgStatus(): void {
|
|
|
125
127
|
_setBgStatus?.(running.length > 0 ? `⧗ ${running.length} bg agent${running.length > 1 ? "s" : ""}` : undefined);
|
|
126
128
|
}
|
|
127
129
|
|
|
130
|
+
// ─── Resource loader pool ─────────────────────────────────────────────────────
|
|
131
|
+
|
|
132
|
+
interface LoaderPoolEntry {
|
|
133
|
+
idle: DefaultResourceLoader[];
|
|
134
|
+
active: Set<DefaultResourceLoader>;
|
|
135
|
+
warming: Set<Promise<void>>;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
interface LoaderLease {
|
|
139
|
+
loader: ResourceLoader;
|
|
140
|
+
release: () => void;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const _loaderPool = new Map<string, LoaderPoolEntry>();
|
|
144
|
+
|
|
145
|
+
function loaderPoolKey(cwd: string, agentDir: string, noExtensions: boolean): string {
|
|
146
|
+
return `${cwd}\0${agentDir}\0${noExtensions ? "noext" : "ext"}`;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function getLoaderPoolEntry(cwd: string, agentDir: string, noExtensions: boolean): LoaderPoolEntry {
|
|
150
|
+
const key = loaderPoolKey(cwd, agentDir, noExtensions);
|
|
151
|
+
let entry = _loaderPool.get(key);
|
|
152
|
+
if (!entry) {
|
|
153
|
+
entry = { idle: [], active: new Set(), warming: new Set() };
|
|
154
|
+
_loaderPool.set(key, entry);
|
|
155
|
+
}
|
|
156
|
+
return entry;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function makeLoaderOptions(cwd: string, agentDir: string, noExtensions: boolean): DefaultResourceLoaderOptions {
|
|
160
|
+
return {
|
|
161
|
+
cwd,
|
|
162
|
+
agentDir,
|
|
163
|
+
noExtensions,
|
|
164
|
+
noContextFiles: true,
|
|
165
|
+
noSkills: true,
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
class AgentPromptResourceLoader implements ResourceLoader {
|
|
170
|
+
constructor(
|
|
171
|
+
private readonly base: ResourceLoader,
|
|
172
|
+
private readonly systemPromptOverride: string | undefined,
|
|
173
|
+
) {}
|
|
174
|
+
|
|
175
|
+
getExtensions() { return this.base.getExtensions(); }
|
|
176
|
+
getSkills() { return this.base.getSkills(); }
|
|
177
|
+
getPrompts() { return this.base.getPrompts(); }
|
|
178
|
+
getThemes() { return this.base.getThemes(); }
|
|
179
|
+
getAgentsFiles() { return this.base.getAgentsFiles(); }
|
|
180
|
+
getSystemPrompt() { return this.systemPromptOverride ?? this.base.getSystemPrompt(); }
|
|
181
|
+
getAppendSystemPrompt() { return this.base.getAppendSystemPrompt(); }
|
|
182
|
+
extendResources(paths: Parameters<ResourceLoader["extendResources"]>[0]): void { this.base.extendResources(paths); }
|
|
183
|
+
reload(): Promise<void> { return this.base.reload(); }
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function isLoaderWarm(cwd: string, agentDir: string, noExtensions: boolean): boolean {
|
|
187
|
+
const entry = _loaderPool.get(loaderPoolKey(cwd, agentDir, noExtensions));
|
|
188
|
+
return !!entry && entry.idle.length > 0;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
async function allowUiPaint(coldLoader: boolean): Promise<void> {
|
|
192
|
+
await new Promise<void>((resolve) => setImmediate(resolve));
|
|
193
|
+
if (!coldLoader) return;
|
|
194
|
+
// Give pi's TUI render timer a real timers-phase turn before CPU-heavy extension loading.
|
|
195
|
+
await new Promise<void>((resolve) => setTimeout(resolve, 50));
|
|
196
|
+
await new Promise<void>((resolve) => setImmediate(resolve));
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
async function acquireResourceLoader(
|
|
200
|
+
cwd: string,
|
|
201
|
+
agentDir: string,
|
|
202
|
+
noExtensions: boolean,
|
|
203
|
+
systemPromptOverride: string | undefined,
|
|
204
|
+
): Promise<LoaderLease> {
|
|
205
|
+
const entry = getLoaderPoolEntry(cwd, agentDir, noExtensions);
|
|
206
|
+
|
|
207
|
+
while (true) {
|
|
208
|
+
const cached = entry.idle.pop();
|
|
209
|
+
if (cached) {
|
|
210
|
+
entry.active.add(cached);
|
|
211
|
+
let released = false;
|
|
212
|
+
return {
|
|
213
|
+
loader: new AgentPromptResourceLoader(cached, systemPromptOverride),
|
|
214
|
+
release: () => {
|
|
215
|
+
if (released) return;
|
|
216
|
+
released = true;
|
|
217
|
+
entry.active.delete(cached);
|
|
218
|
+
entry.idle.push(cached);
|
|
219
|
+
},
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const warming = entry.warming.values().next().value as Promise<void> | undefined;
|
|
224
|
+
if (warming) {
|
|
225
|
+
await warming;
|
|
226
|
+
continue;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const loader = new DefaultResourceLoader(makeLoaderOptions(cwd, agentDir, noExtensions));
|
|
230
|
+
const warmPromise = loader.reload()
|
|
231
|
+
.then(() => { entry.idle.push(loader); })
|
|
232
|
+
.finally(() => { entry.warming.delete(warmPromise); });
|
|
233
|
+
entry.warming.add(warmPromise);
|
|
234
|
+
await warmPromise;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function warmResourceLoader(cwd: string, agentDir: string, noExtensions: boolean): void {
|
|
239
|
+
const entry = getLoaderPoolEntry(cwd, agentDir, noExtensions);
|
|
240
|
+
if (entry.idle.length > 0 || entry.active.size > 0 || entry.warming.size > 0) return;
|
|
241
|
+
const loader = new DefaultResourceLoader(makeLoaderOptions(cwd, agentDir, noExtensions));
|
|
242
|
+
const warmPromise = loader.reload()
|
|
243
|
+
.then(() => { entry.idle.push(loader); })
|
|
244
|
+
.catch(() => { /* ignore warm failures; foreground call reports real error */ })
|
|
245
|
+
.finally(() => { entry.warming.delete(warmPromise); });
|
|
246
|
+
entry.warming.add(warmPromise);
|
|
247
|
+
}
|
|
248
|
+
|
|
128
249
|
// ─── Foreground detach registry ───────────────────────────────────────────────
|
|
129
250
|
|
|
130
251
|
interface ForegroundDetachEntry {
|
|
@@ -168,6 +289,7 @@ interface AgentRowStatus {
|
|
|
168
289
|
|
|
169
290
|
interface SubagentDetails {
|
|
170
291
|
mode?: "single" | "parallel";
|
|
292
|
+
agentName?: string;
|
|
171
293
|
task?: string;
|
|
172
294
|
// parallel
|
|
173
295
|
parallelAgents?: AgentRowStatus[];
|
|
@@ -231,36 +353,59 @@ async function runAgent(
|
|
|
231
353
|
};
|
|
232
354
|
}
|
|
233
355
|
|
|
356
|
+
const bootStartedAt = Date.now();
|
|
234
357
|
const { authStorage, modelRegistry } = getAuth();
|
|
235
358
|
const agentDir = getAgentDir();
|
|
359
|
+
const noExtensions = !agentNeedsExtensions(agent.tools);
|
|
360
|
+
const coldLoader = !isLoaderWarm(cwd, agentDir, noExtensions);
|
|
361
|
+
|
|
362
|
+
// Fire an immediate "running" emit so the UI draws the agent header + prompt
|
|
363
|
+
// before the (potentially slow) extension/session load. Without this, pi looks
|
|
364
|
+
// frozen while `loader.reload()` and `createAgentSession()` are in flight.
|
|
365
|
+
onUpdate?.({
|
|
366
|
+
content: [{ type: "text", text: "" }],
|
|
367
|
+
details: {
|
|
368
|
+
agentName: agent.name,
|
|
369
|
+
task,
|
|
370
|
+
usage: { input: 0, output: 0, cost: 0, turns: 0 },
|
|
371
|
+
running: true,
|
|
372
|
+
elapsedMs: 0,
|
|
373
|
+
model: modelOverride ?? agent.model,
|
|
374
|
+
toolCalls: [],
|
|
375
|
+
} satisfies SubagentDetails,
|
|
376
|
+
});
|
|
377
|
+
// Yield through timers when loader is cold so pi's render loop paints before
|
|
378
|
+
// CPU-heavy extension loading runs.
|
|
379
|
+
await allowUiPaint(coldLoader);
|
|
236
380
|
|
|
237
|
-
|
|
238
|
-
// Agents can opt in to extensions via `extensions: true` in frontmatter, which
|
|
239
|
-
// makes tools like web_search / fetch_content / mcp / etc. available to the
|
|
240
|
-
// subagent (subject to the optional `tools:` allowlist below).
|
|
241
|
-
const loaderOptions: DefaultResourceLoaderOptions = {
|
|
381
|
+
const loaderLease = await acquireResourceLoader(
|
|
242
382
|
cwd,
|
|
243
383
|
agentDir,
|
|
244
|
-
noExtensions
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
};
|
|
248
|
-
if (agent.systemPrompt) {
|
|
249
|
-
// Replace pi's base system prompt with the agent's own prompt
|
|
250
|
-
loaderOptions.systemPromptOverride = () => agent.systemPrompt;
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
const loader = new DefaultResourceLoader(loaderOptions);
|
|
254
|
-
await loader.reload();
|
|
384
|
+
noExtensions,
|
|
385
|
+
agent.systemPrompt || undefined,
|
|
386
|
+
);
|
|
255
387
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
388
|
+
let session: Awaited<ReturnType<typeof createAgentSession>>["session"];
|
|
389
|
+
try {
|
|
390
|
+
const created = await createAgentSession({
|
|
391
|
+
cwd,
|
|
392
|
+
agentDir,
|
|
393
|
+
sessionManager: SessionManager.inMemory(cwd),
|
|
394
|
+
authStorage,
|
|
395
|
+
modelRegistry,
|
|
396
|
+
resourceLoader: loaderLease.loader,
|
|
397
|
+
});
|
|
398
|
+
session = created.session;
|
|
399
|
+
} catch (e) {
|
|
400
|
+
loaderLease.release();
|
|
401
|
+
return {
|
|
402
|
+
output: "",
|
|
403
|
+
exitCode: 1,
|
|
404
|
+
error: e instanceof Error ? e.message : String(e),
|
|
405
|
+
toolCalls: [],
|
|
406
|
+
usage: { input: 0, output: 0, cost: 0, turns: 0 },
|
|
407
|
+
};
|
|
408
|
+
}
|
|
264
409
|
|
|
265
410
|
// Resolve and apply model
|
|
266
411
|
const modelStr = modelOverride ?? agent.model;
|
|
@@ -288,7 +433,7 @@ async function runAgent(
|
|
|
288
433
|
let lastOutput = "";
|
|
289
434
|
let currentDelta = "";
|
|
290
435
|
let detectedModel: string | undefined;
|
|
291
|
-
const startedAt =
|
|
436
|
+
const startedAt = bootStartedAt;
|
|
292
437
|
const configuredModel = modelOverride ?? agent.model;
|
|
293
438
|
const toolCalls: ToolCallEntry[] = [];
|
|
294
439
|
const toolStartTimes = new Map<string, number>();
|
|
@@ -300,6 +445,7 @@ async function runAgent(
|
|
|
300
445
|
onUpdate?.({
|
|
301
446
|
content: [{ type: "text", text: currentDelta || lastOutput || "" }],
|
|
302
447
|
details: {
|
|
448
|
+
agentName: agent.name,
|
|
303
449
|
task,
|
|
304
450
|
usage,
|
|
305
451
|
running: true,
|
|
@@ -423,6 +569,7 @@ async function runAgent(
|
|
|
423
569
|
clearInterval(heartbeat);
|
|
424
570
|
unsubscribe();
|
|
425
571
|
session.dispose();
|
|
572
|
+
loaderLease.release();
|
|
426
573
|
if (prevEnvDepth === undefined) delete process.env[DEPTH_ENV];
|
|
427
574
|
else process.env[DEPTH_ENV] = prevEnvDepth;
|
|
428
575
|
_currentDepth = depth;
|
|
@@ -558,12 +705,21 @@ export default function (pi: ExtensionAPI) {
|
|
|
558
705
|
|
|
559
706
|
pi.on("session_start", async (_event, ctx) => {
|
|
560
707
|
_setBgStatus = (text) => ctx.ui.setStatus(BG_STATUS_KEY, text);
|
|
708
|
+
|
|
709
|
+
// Warm one extension-capable loader after startup. First `tools: all` subagent
|
|
710
|
+
// call can then reuse loaded extensions instead of blocking before first stream.
|
|
711
|
+
if (process.env.PI_FAST_SUBAGENT_WARM !== "0") {
|
|
712
|
+
const warmCwd = ctx.cwd;
|
|
713
|
+
const warmAgentDir = getAgentDir();
|
|
714
|
+
setTimeout(() => warmResourceLoader(warmCwd, warmAgentDir, false), 1000);
|
|
715
|
+
}
|
|
561
716
|
});
|
|
562
717
|
|
|
563
718
|
pi.on("session_shutdown", async () => {
|
|
564
719
|
getBgManager().shutdown();
|
|
565
720
|
_bgManager = null;
|
|
566
721
|
_setBgStatus = null;
|
|
722
|
+
_loaderPool.clear();
|
|
567
723
|
});
|
|
568
724
|
|
|
569
725
|
// ─── Ctrl+Shift+B — move foreground subagent to background ─────────────────────────
|
|
@@ -866,14 +1022,15 @@ export default function (pi: ExtensionAPI) {
|
|
|
866
1022
|
|
|
867
1023
|
function statusLine(): string {
|
|
868
1024
|
if (details.backgroundJobId) return `moved to background · ${details.backgroundJobId}`;
|
|
1025
|
+
const prefix = details.agentName ? `${theme.fg("toolTitle", details.agentName)} · ` : "";
|
|
869
1026
|
if (details.running) {
|
|
870
1027
|
const parts: string[] = ["running"];
|
|
871
1028
|
if (details.usage?.turns) parts.push(`${details.usage.turns} turn${details.usage.turns > 1 ? "s" : ""}`);
|
|
872
1029
|
if (details.elapsedMs != null) parts.push(formatDuration(details.elapsedMs));
|
|
873
1030
|
if (details.model) parts.push(details.model);
|
|
874
|
-
return parts.join(" · ");
|
|
1031
|
+
return prefix + parts.join(" · ");
|
|
875
1032
|
}
|
|
876
|
-
return formatUsage(details.usage ?? { input: 0, output: 0, cost: 0, turns: 0 }, details.model);
|
|
1033
|
+
return prefix + formatUsage(details.usage ?? { input: 0, output: 0, cost: 0, turns: 0 }, details.model);
|
|
877
1034
|
}
|
|
878
1035
|
|
|
879
1036
|
// Name(arg) ✓ 0.3s or Name(arg) (dim, still running)
|
|
@@ -904,6 +1061,8 @@ export default function (pi: ExtensionAPI) {
|
|
|
904
1061
|
render(width: number): string[] {
|
|
905
1062
|
const out: string[] = [];
|
|
906
1063
|
const indent = " ";
|
|
1064
|
+
const ellipsisLine = (count: number) =>
|
|
1065
|
+
theme.fg("muted", `${indent}… (${count} more line${count === 1 ? "" : "s"})`);
|
|
907
1066
|
|
|
908
1067
|
// ── Prompt ────────────────────────────────────────────────────
|
|
909
1068
|
if (details.task) {
|
|
@@ -913,14 +1072,22 @@ export default function (pi: ExtensionAPI) {
|
|
|
913
1072
|
for (const w of wrapLine(indent + line, width)) out.push(w);
|
|
914
1073
|
}
|
|
915
1074
|
} else {
|
|
916
|
-
// Up to 8 visual lines
|
|
1075
|
+
// Up to 8 visual lines from the HEAD of the prompt (keep opening, not tail).
|
|
917
1076
|
const PROMPT_PREVIEW_LINES = 8;
|
|
918
1077
|
if (cache.width !== width || cache.promptLines === undefined) {
|
|
919
|
-
const
|
|
920
|
-
|
|
921
|
-
|
|
1078
|
+
const innerWidth = Math.max(1, width - indent.length);
|
|
1079
|
+
const allVisual: string[] = [];
|
|
1080
|
+
for (const raw of details.task.split("\n")) {
|
|
1081
|
+
for (const w of wrapLine(raw, innerWidth)) allVisual.push(w);
|
|
1082
|
+
}
|
|
1083
|
+
const head = allVisual.slice(0, PROMPT_PREVIEW_LINES);
|
|
1084
|
+
cache.promptLines = head.map((l) => truncateToWidth(indent + l, width, "..."));
|
|
1085
|
+
cache.promptSkipped = Math.max(0, allVisual.length - head.length);
|
|
922
1086
|
}
|
|
923
1087
|
out.push(...cache.promptLines);
|
|
1088
|
+
if ((cache.promptSkipped ?? 0) > 0) {
|
|
1089
|
+
out.push(truncateToWidth(ellipsisLine(cache.promptSkipped!), width, "..."));
|
|
1090
|
+
}
|
|
924
1091
|
}
|
|
925
1092
|
}
|
|
926
1093
|
|
|
@@ -950,6 +1117,10 @@ export default function (pi: ExtensionAPI) {
|
|
|
950
1117
|
cache.skipped = preview.skippedCount;
|
|
951
1118
|
cache.width = width;
|
|
952
1119
|
}
|
|
1120
|
+
// truncateToVisualLines keeps the tail — show ellipsis BEFORE the visible lines.
|
|
1121
|
+
if ((cache.skipped ?? 0) > 0) {
|
|
1122
|
+
out.push(truncateToWidth(ellipsisLine(cache.skipped!), width, "..."));
|
|
1123
|
+
}
|
|
953
1124
|
out.push(...(cache.responseLines ?? []));
|
|
954
1125
|
}
|
|
955
1126
|
}
|
|
@@ -1129,6 +1300,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
1129
1300
|
return {
|
|
1130
1301
|
content: [{ type: "text", text: `Moved to background: ${bgJobId}. Completion will be announced automatically.` }],
|
|
1131
1302
|
details: {
|
|
1303
|
+
agentName: params.agent,
|
|
1132
1304
|
task: params.task,
|
|
1133
1305
|
usage: { input: 0, output: 0, cost: 0, turns: 0 },
|
|
1134
1306
|
running: false,
|
|
@@ -1142,6 +1314,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
1142
1314
|
return {
|
|
1143
1315
|
content: [{ type: "text", text: getFinalText(result) }],
|
|
1144
1316
|
details: {
|
|
1317
|
+
agentName: params.agent,
|
|
1145
1318
|
task: params.task,
|
|
1146
1319
|
usage: result.usage,
|
|
1147
1320
|
running: false,
|