pi-subagents 0.11.10 → 0.11.12
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 +12 -0
- package/execution.ts +11 -56
- package/package.json +4 -4
- package/prompt-template-bridge.ts +62 -2
- package/render.ts +3 -3
- package/skills.ts +14 -22
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [0.11.12] - 2026-03-28
|
|
6
|
+
|
|
7
|
+
### Changed
|
|
8
|
+
- Tool history (`recentTools`) in execution progress is now chronological (oldest first) and uncapped, replacing the old newest-first order with a 5-entry cap. Affects all execution paths (tool, slash commands, chains, parallel, async, delegation). Both single-task and chain-step render paths in `render.ts` now consistently use `slice(-3)` for most-recent display.
|
|
9
|
+
- Removed 50ms throttle on execution progress updates. `onUpdate` now fires immediately on every tool start, tool end, message end, and tool result. Affects all execution paths.
|
|
10
|
+
- Delegation bridge now passes through full `recentOutputLines` arrays, `recentTools` history, and resolved `model` to prompt-template consumers, replacing the old stripped-down single-line updates.
|
|
11
|
+
|
|
12
|
+
## [0.11.11] - 2026-03-23
|
|
13
|
+
|
|
14
|
+
### Changed
|
|
15
|
+
- Updated for pi 0.62.0 compatibility. `Skill.source` replaced with `Skill.sourceInfo` for skill provenance, `Widget` type replaced with `Component`. Bumped devDependencies to `^0.62.0`.
|
|
16
|
+
|
|
5
17
|
## [0.11.10] - 2026-03-21
|
|
6
18
|
|
|
7
19
|
### Changed
|
package/execution.ts
CHANGED
|
@@ -139,48 +139,15 @@ export async function runSync(
|
|
|
139
139
|
closeJsonlWriter = () => jsonlWriter.close();
|
|
140
140
|
let buf = "";
|
|
141
141
|
|
|
142
|
-
// Throttled update mechanism - consolidates all updates
|
|
143
|
-
let lastUpdateTime = 0;
|
|
144
|
-
let updatePending = false;
|
|
145
|
-
let pendingTimer: ReturnType<typeof setTimeout> | null = null;
|
|
146
142
|
let processClosed = false;
|
|
147
|
-
const UPDATE_THROTTLE_MS = 50; // Reduced from 75ms for faster responsiveness
|
|
148
143
|
|
|
149
|
-
const
|
|
144
|
+
const fireUpdate = () => {
|
|
150
145
|
if (!onUpdate || processClosed) return;
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
// Clear any pending timer to avoid double-updates
|
|
157
|
-
if (pendingTimer) {
|
|
158
|
-
clearTimeout(pendingTimer);
|
|
159
|
-
pendingTimer = null;
|
|
160
|
-
}
|
|
161
|
-
lastUpdateTime = now;
|
|
162
|
-
updatePending = false;
|
|
163
|
-
progress.durationMs = now - startTime;
|
|
164
|
-
onUpdate({
|
|
165
|
-
content: [{ type: "text", text: getFinalOutput(result.messages) || "(running...)" }],
|
|
166
|
-
details: { mode: "single", results: [result], progress: [progress] },
|
|
167
|
-
});
|
|
168
|
-
} else if (!updatePending) {
|
|
169
|
-
// Schedule update for later
|
|
170
|
-
updatePending = true;
|
|
171
|
-
pendingTimer = setTimeout(() => {
|
|
172
|
-
pendingTimer = null;
|
|
173
|
-
if (updatePending && !processClosed) {
|
|
174
|
-
updatePending = false;
|
|
175
|
-
lastUpdateTime = Date.now();
|
|
176
|
-
progress.durationMs = Date.now() - startTime;
|
|
177
|
-
onUpdate({
|
|
178
|
-
content: [{ type: "text", text: getFinalOutput(result.messages) || "(running...)" }],
|
|
179
|
-
details: { mode: "single", results: [result], progress: [progress] },
|
|
180
|
-
});
|
|
181
|
-
}
|
|
182
|
-
}, UPDATE_THROTTLE_MS - elapsed);
|
|
183
|
-
}
|
|
146
|
+
progress.durationMs = Date.now() - startTime;
|
|
147
|
+
onUpdate({
|
|
148
|
+
content: [{ type: "text", text: getFinalOutput(result.messages) || "(running...)" }],
|
|
149
|
+
details: { mode: "single", results: [result], progress: [progress] },
|
|
150
|
+
});
|
|
184
151
|
};
|
|
185
152
|
|
|
186
153
|
const processLine = (line: string) => {
|
|
@@ -195,25 +162,20 @@ export async function runSync(
|
|
|
195
162
|
progress.toolCount++;
|
|
196
163
|
progress.currentTool = evt.toolName;
|
|
197
164
|
progress.currentToolArgs = extractToolArgsPreview((evt.args || {}) as Record<string, unknown>);
|
|
198
|
-
|
|
199
|
-
lastUpdateTime = 0;
|
|
200
|
-
scheduleUpdate();
|
|
165
|
+
fireUpdate();
|
|
201
166
|
}
|
|
202
167
|
|
|
203
168
|
if (evt.type === "tool_execution_end") {
|
|
204
169
|
if (progress.currentTool) {
|
|
205
|
-
progress.recentTools.
|
|
170
|
+
progress.recentTools.push({
|
|
206
171
|
tool: progress.currentTool,
|
|
207
172
|
args: progress.currentToolArgs || "",
|
|
208
173
|
endMs: now,
|
|
209
174
|
});
|
|
210
|
-
if (progress.recentTools.length > 5) {
|
|
211
|
-
progress.recentTools.pop();
|
|
212
|
-
}
|
|
213
175
|
}
|
|
214
176
|
progress.currentTool = undefined;
|
|
215
177
|
progress.currentToolArgs = undefined;
|
|
216
|
-
|
|
178
|
+
fireUpdate();
|
|
217
179
|
}
|
|
218
180
|
|
|
219
181
|
if (evt.type === "message_end" && evt.message) {
|
|
@@ -245,7 +207,7 @@ export async function runSync(
|
|
|
245
207
|
}
|
|
246
208
|
}
|
|
247
209
|
}
|
|
248
|
-
|
|
210
|
+
fireUpdate();
|
|
249
211
|
}
|
|
250
212
|
if (evt.type === "tool_result_end" && evt.message) {
|
|
251
213
|
result.messages.push(evt.message);
|
|
@@ -262,7 +224,7 @@ export async function runSync(
|
|
|
262
224
|
progress.recentOutput.splice(0, progress.recentOutput.length - 50);
|
|
263
225
|
}
|
|
264
226
|
}
|
|
265
|
-
|
|
227
|
+
fireUpdate();
|
|
266
228
|
}
|
|
267
229
|
} catch {
|
|
268
230
|
// Non-JSON stdout lines are expected; only structured events are parsed.
|
|
@@ -276,19 +238,12 @@ export async function runSync(
|
|
|
276
238
|
const lines = buf.split("\n");
|
|
277
239
|
buf = lines.pop() || "";
|
|
278
240
|
lines.forEach(processLine);
|
|
279
|
-
|
|
280
|
-
// Also schedule an update on data received (handles streaming output)
|
|
281
|
-
scheduleUpdate();
|
|
282
241
|
});
|
|
283
242
|
proc.stderr.on("data", (d) => {
|
|
284
243
|
stderrBuf += d.toString();
|
|
285
244
|
});
|
|
286
245
|
proc.on("close", (code) => {
|
|
287
246
|
processClosed = true;
|
|
288
|
-
if (pendingTimer) {
|
|
289
|
-
clearTimeout(pendingTimer);
|
|
290
|
-
pendingTimer = null;
|
|
291
|
-
}
|
|
292
247
|
if (buf.trim()) processLine(buf);
|
|
293
248
|
if (code !== 0 && stderrBuf.trim() && !result.error) {
|
|
294
249
|
result.error = stderrBuf.trim();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-subagents",
|
|
3
|
-
"version": "0.11.
|
|
3
|
+
"version": "0.11.12",
|
|
4
4
|
"description": "Pi extension for delegating tasks to subagents with chains, parallel execution, and TUI clarification",
|
|
5
5
|
"author": "Nico Bailon",
|
|
6
6
|
"license": "MIT",
|
|
@@ -47,8 +47,8 @@
|
|
|
47
47
|
},
|
|
48
48
|
"devDependencies": {
|
|
49
49
|
"@marcfargas/pi-test-harness": "^0.5.0",
|
|
50
|
-
"@mariozechner/pi-agent-core": "^0.
|
|
51
|
-
"@mariozechner/pi-ai": "^0.
|
|
52
|
-
"@mariozechner/pi-coding-agent": "^0.
|
|
50
|
+
"@mariozechner/pi-agent-core": "^0.62.0",
|
|
51
|
+
"@mariozechner/pi-ai": "^0.62.0",
|
|
52
|
+
"@mariozechner/pi-coding-agent": "^0.62.0"
|
|
53
53
|
}
|
|
54
54
|
}
|
|
@@ -41,6 +41,9 @@ export interface PromptTemplateDelegationTaskProgress {
|
|
|
41
41
|
currentTool?: string;
|
|
42
42
|
currentToolArgs?: string;
|
|
43
43
|
recentOutput?: string;
|
|
44
|
+
recentOutputLines?: string[];
|
|
45
|
+
recentTools?: Array<{ tool: string; args: string }>;
|
|
46
|
+
model?: string;
|
|
44
47
|
toolCount?: number;
|
|
45
48
|
durationMs?: number;
|
|
46
49
|
tokens?: number;
|
|
@@ -51,6 +54,9 @@ export interface PromptTemplateDelegationUpdate {
|
|
|
51
54
|
currentTool?: string;
|
|
52
55
|
currentToolArgs?: string;
|
|
53
56
|
recentOutput?: string;
|
|
57
|
+
recentOutputLines?: string[];
|
|
58
|
+
recentTools?: Array<{ tool: string; args: string }>;
|
|
59
|
+
model?: string;
|
|
54
60
|
toolCount?: number;
|
|
55
61
|
durationMs?: number;
|
|
56
62
|
tokens?: number;
|
|
@@ -71,6 +77,7 @@ interface PromptTemplateBridgeResult {
|
|
|
71
77
|
messages?: unknown[];
|
|
72
78
|
exitCode?: number;
|
|
73
79
|
error?: string;
|
|
80
|
+
model?: string;
|
|
74
81
|
}>;
|
|
75
82
|
progress?: Array<{
|
|
76
83
|
index?: number;
|
|
@@ -79,6 +86,7 @@ interface PromptTemplateBridgeResult {
|
|
|
79
86
|
currentTool?: string;
|
|
80
87
|
currentToolArgs?: string;
|
|
81
88
|
recentOutput?: string[];
|
|
89
|
+
recentTools?: Array<{ tool?: string; args?: string }>;
|
|
82
90
|
toolCount?: number;
|
|
83
91
|
durationMs?: number;
|
|
84
92
|
tokens?: number;
|
|
@@ -154,17 +162,62 @@ export function firstTextContent(content: unknown): string | undefined {
|
|
|
154
162
|
return undefined;
|
|
155
163
|
}
|
|
156
164
|
|
|
165
|
+
function filterRecentOutput(lines: string[] | undefined): string[] | undefined {
|
|
166
|
+
if (!lines || lines.length === 0) return undefined;
|
|
167
|
+
const filtered = lines.filter((line) => typeof line === "string" && line.trim() && line.trim() !== "(running...)");
|
|
168
|
+
if (filtered.length === 0) return undefined;
|
|
169
|
+
return filtered;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function sanitizeRecentTools(
|
|
173
|
+
tools: Array<{ tool?: string; args?: string }> | undefined,
|
|
174
|
+
): Array<{ tool: string; args: string }> | undefined {
|
|
175
|
+
if (!tools || tools.length === 0) return undefined;
|
|
176
|
+
const sanitized = tools
|
|
177
|
+
.filter((entry) => typeof entry.tool === "string" && entry.tool.trim().length > 0)
|
|
178
|
+
.map((entry) => ({
|
|
179
|
+
tool: entry.tool as string,
|
|
180
|
+
args: typeof entry.args === "string" ? entry.args : String(entry.args ?? ""),
|
|
181
|
+
}));
|
|
182
|
+
return sanitized.length > 0 ? sanitized : undefined;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function resolveProgressModel(
|
|
186
|
+
update: PromptTemplateBridgeResult,
|
|
187
|
+
entry: { index?: number; agent?: string },
|
|
188
|
+
): string | undefined {
|
|
189
|
+
const results = update.details?.results;
|
|
190
|
+
if (!results || results.length === 0) return undefined;
|
|
191
|
+
if (typeof entry.index === "number" && entry.index >= 0) {
|
|
192
|
+
const byIndex = results[entry.index];
|
|
193
|
+
if (typeof byIndex?.model === "string") return byIndex.model;
|
|
194
|
+
}
|
|
195
|
+
if (entry.agent) {
|
|
196
|
+
const byAgent = results.find((result) => result.agent === entry.agent && typeof result.model === "string");
|
|
197
|
+
if (byAgent?.model) return byAgent.model;
|
|
198
|
+
}
|
|
199
|
+
const firstWithModel = results.find((result) => typeof result.model === "string");
|
|
200
|
+
return firstWithModel?.model;
|
|
201
|
+
}
|
|
202
|
+
|
|
157
203
|
function toDelegationUpdate(requestId: string, update: PromptTemplateBridgeResult): PromptTemplateDelegationUpdate | undefined {
|
|
158
204
|
const progress = update.details?.progress?.[0];
|
|
159
205
|
const taskProgress = update.details?.progress?.map((entry) => {
|
|
160
206
|
const lastOutput = entry.recentOutput?.[entry.recentOutput.length - 1];
|
|
207
|
+
const safeLastOutput =
|
|
208
|
+
typeof lastOutput === "string" && lastOutput.trim() && lastOutput !== "(running...)"
|
|
209
|
+
? lastOutput
|
|
210
|
+
: undefined;
|
|
161
211
|
return {
|
|
162
212
|
index: entry.index,
|
|
163
213
|
agent: entry.agent ?? "delegate",
|
|
164
214
|
status: entry.status,
|
|
165
215
|
currentTool: entry.currentTool,
|
|
166
216
|
currentToolArgs: entry.currentToolArgs,
|
|
167
|
-
recentOutput:
|
|
217
|
+
recentOutput: safeLastOutput,
|
|
218
|
+
recentOutputLines: filterRecentOutput(entry.recentOutput),
|
|
219
|
+
recentTools: sanitizeRecentTools(entry.recentTools),
|
|
220
|
+
model: resolveProgressModel(update, entry),
|
|
168
221
|
toolCount: entry.toolCount,
|
|
169
222
|
durationMs: entry.durationMs,
|
|
170
223
|
tokens: entry.tokens,
|
|
@@ -172,11 +225,18 @@ function toDelegationUpdate(requestId: string, update: PromptTemplateBridgeResul
|
|
|
172
225
|
});
|
|
173
226
|
if (!progress && (!taskProgress || taskProgress.length === 0)) return undefined;
|
|
174
227
|
const lastOutput = progress?.recentOutput?.[progress.recentOutput.length - 1];
|
|
228
|
+
const safeLastOutput =
|
|
229
|
+
typeof lastOutput === "string" && lastOutput.trim() && lastOutput !== "(running...)"
|
|
230
|
+
? lastOutput
|
|
231
|
+
: undefined;
|
|
175
232
|
return {
|
|
176
233
|
requestId,
|
|
177
234
|
currentTool: progress?.currentTool,
|
|
178
235
|
currentToolArgs: progress?.currentToolArgs,
|
|
179
|
-
recentOutput:
|
|
236
|
+
recentOutput: safeLastOutput,
|
|
237
|
+
recentOutputLines: filterRecentOutput(progress?.recentOutput),
|
|
238
|
+
recentTools: sanitizeRecentTools(progress?.recentTools),
|
|
239
|
+
model: progress ? resolveProgressModel(update, progress) : undefined,
|
|
180
240
|
toolCount: progress?.toolCount,
|
|
181
241
|
durationMs: progress?.durationMs,
|
|
182
242
|
tokens: progress?.tokens,
|
package/render.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
import type { AgentToolResult } from "@mariozechner/pi-agent-core";
|
|
6
6
|
import { getMarkdownTheme, type ExtensionContext } from "@mariozechner/pi-coding-agent";
|
|
7
|
-
import { Container, Markdown, Spacer, Text,
|
|
7
|
+
import { Container, Markdown, Spacer, Text, visibleWidth, type Component } from "@mariozechner/pi-tui";
|
|
8
8
|
import {
|
|
9
9
|
type AsyncJobState,
|
|
10
10
|
type Details,
|
|
@@ -179,7 +179,7 @@ export function renderSubagentResult(
|
|
|
179
179
|
result: AgentToolResult<Details>,
|
|
180
180
|
_options: { expanded: boolean },
|
|
181
181
|
theme: Theme,
|
|
182
|
-
):
|
|
182
|
+
): Component {
|
|
183
183
|
const d = result.details;
|
|
184
184
|
if (!d || !d.results.length) {
|
|
185
185
|
const t = result.content[0];
|
|
@@ -447,7 +447,7 @@ export function renderSubagentResult(
|
|
|
447
447
|
}
|
|
448
448
|
// Recent tools
|
|
449
449
|
if (rProg.recentTools?.length) {
|
|
450
|
-
for (const t of rProg.recentTools.slice(
|
|
450
|
+
for (const t of rProg.recentTools.slice(-3)) {
|
|
451
451
|
const maxArgsLen = Math.max(40, w - 30);
|
|
452
452
|
const argsPreview = t.args.length > maxArgsLen
|
|
453
453
|
? `${t.args.slice(0, maxArgsLen)}...`
|
package/skills.ts
CHANGED
|
@@ -194,32 +194,24 @@ function buildSkillPaths(cwd: string): string[] {
|
|
|
194
194
|
return [...new Set([...defaultSkillPaths, ...packagePaths, ...settingsPaths])];
|
|
195
195
|
}
|
|
196
196
|
|
|
197
|
-
function inferSkillSource(
|
|
198
|
-
const
|
|
197
|
+
function inferSkillSource(sourceInfo: { source: string; scope: string }, filePath: string, cwd: string): SkillSource {
|
|
198
|
+
const { scope, source } = sourceInfo;
|
|
199
|
+
|
|
200
|
+
if (scope === "project" && source === "local") return "project";
|
|
201
|
+
if (scope === "user" && source === "local") return "user";
|
|
202
|
+
|
|
203
|
+
// Fallback: infer from file path when sourceInfo isn't specific enough
|
|
204
|
+
// (e.g. scope === "temporary" for skills loaded via explicit skillPaths)
|
|
199
205
|
const projectRoot = path.resolve(cwd, CONFIG_DIR);
|
|
200
206
|
const isProjectScoped = isWithinPath(filePath, projectRoot);
|
|
207
|
+
if (isProjectScoped) return "project";
|
|
208
|
+
|
|
201
209
|
const isUserScoped = isWithinPath(filePath, AGENT_DIR);
|
|
210
|
+
if (isUserScoped) return "user";
|
|
211
|
+
|
|
202
212
|
const globalRoot = getGlobalNpmRoot();
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
if (source === "project") return "project";
|
|
206
|
-
if (source === "user") return "user";
|
|
207
|
-
if (source === "settings") {
|
|
208
|
-
if (isProjectScoped) return "project-settings";
|
|
209
|
-
if (isUserScoped) return "user-settings";
|
|
210
|
-
return "unknown";
|
|
211
|
-
}
|
|
212
|
-
if (source === "package") {
|
|
213
|
-
if (isProjectScoped) return "project-package";
|
|
214
|
-
if (isUserScoped || isGlobalPackage) return "user-package";
|
|
215
|
-
return "unknown";
|
|
216
|
-
}
|
|
217
|
-
if (source === "extension") return "extension";
|
|
218
|
-
if (source === "builtin") return "builtin";
|
|
213
|
+
if (globalRoot && isWithinPath(filePath, globalRoot)) return "user-package";
|
|
219
214
|
|
|
220
|
-
if (isProjectScoped) return "project";
|
|
221
|
-
if (isUserScoped) return "user";
|
|
222
|
-
if (isGlobalPackage) return "user-package";
|
|
223
215
|
return "unknown";
|
|
224
216
|
}
|
|
225
217
|
|
|
@@ -247,7 +239,7 @@ function getCachedSkills(cwd: string): CachedSkillEntry[] {
|
|
|
247
239
|
const entry: CachedSkillEntry = {
|
|
248
240
|
name: skill.name,
|
|
249
241
|
filePath: skill.filePath,
|
|
250
|
-
source: inferSkillSource(
|
|
242
|
+
source: inferSkillSource(skill.sourceInfo, skill.filePath, cwd),
|
|
251
243
|
description: skill.description,
|
|
252
244
|
order: i,
|
|
253
245
|
};
|