pi-subagents 0.17.0 → 0.17.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 +14 -0
- package/README.md +35 -4
- package/agents/context-builder.md +1 -1
- package/agents/planner.md +1 -1
- package/agents/researcher.md +1 -1
- package/agents/scout.md +1 -1
- package/agents/worker.md +1 -1
- package/execution.ts +56 -4
- package/intercom-bridge.ts +6 -3
- package/package.json +1 -1
- package/render.ts +58 -24
- package/slash-commands.ts +1 -1
- package/subagents-status.ts +7 -1
- package/types.ts +6 -3
- package/utils.ts +31 -2
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,20 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [0.17.1] - 2026-04-20
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
- Foreground subagent runs now make deeper live detail easier to discover. Running cards show an explicit `Ctrl+O` hint, lightweight live-state signals like recent activity, current-tool durations, and artifact output paths when available. Common array-heavy tool previews such as `web_search.queries` and `fetch_content.urls` are now summarized more clearly instead of collapsing into opaque fallback text.
|
|
9
|
+
|
|
10
|
+
### Changed
|
|
11
|
+
- Forked delegated runs now use stronger prompt-side guidance for `pi-intercom` coordination instead of runtime policing. The default fork preamble and intercom bridge instructions now explicitly treat inherited fork history as reference-only context, tell children not to continue the parent conversation in normal assistant text, and steer upstream questions or handoffs through `intercom` when needed.
|
|
12
|
+
- Documented an opt-in custom agent pattern for forked chat-back workflows so users can make that coordination contract explicit without changing builtin agents.
|
|
13
|
+
- Slash-run status text and `/subagents-status` summary output now use the same more explicit observability language, including clearer live-detail hints and surfaced output/session paths in the async status overlay.
|
|
14
|
+
- Builtin agent defaults now prefer `openai-codex` models for `planner`, `scout`, `researcher`, `context-builder`, and `worker`.
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
- Removed the short-lived foreground intercom enforcement/retry layer from delegated fork runs. Coordination behavior is now shaped by prompt and agent design only, avoiding hidden retries, heuristic output inspection, and failure paths based on guessed intent.
|
|
18
|
+
|
|
5
19
|
## [0.17.0] - 2026-04-16
|
|
6
20
|
|
|
7
21
|
### Added
|
package/README.md
CHANGED
|
@@ -925,12 +925,14 @@ Example `instructionFile`:
|
|
|
925
925
|
```md
|
|
926
926
|
Intercom orchestration channel:
|
|
927
927
|
|
|
928
|
+
The inherited thread is reference-only. Do not continue that conversation or send questions, status updates, or completion handoffs to the orchestrator in normal assistant text.
|
|
929
|
+
|
|
928
930
|
Use `intercom` only to coordinate with the orchestrator session `{orchestratorTarget}`.
|
|
929
931
|
|
|
930
932
|
- Need a decision or you're blocked: `intercom({ action: "ask", to: "{orchestratorTarget}", message: "<question>" })`
|
|
931
|
-
- Need to report progress or completion: `intercom({ action: "send", to: "{orchestratorTarget}", message: "DONE: <summary>" })`
|
|
933
|
+
- Need to report progress or a completion handoff: `intercom({ action: "send", to: "{orchestratorTarget}", message: "DONE: <summary>" })`
|
|
932
934
|
|
|
933
|
-
If
|
|
935
|
+
If no upstream coordination is needed, continue the task normally and return a focused task result.
|
|
934
936
|
```
|
|
935
937
|
|
|
936
938
|
Bridge activation also requires all of the following:
|
|
@@ -941,6 +943,33 @@ Bridge activation also requires all of the following:
|
|
|
941
943
|
|
|
942
944
|
When an unnamed session falls back to `subagent-chat-<id>`, that alias is used only for the live intercom broker. It is not persisted as the Pi session title, so `pi --resume` can still show the transcript snippet.
|
|
943
945
|
|
|
946
|
+
If you want a stronger prompt contract for forked chat-back runs without changing builtins, define a custom agent for it. Keeping that as an opt-in agent works better than teaching every delegated run to behave this way.
|
|
947
|
+
|
|
948
|
+
Example agent:
|
|
949
|
+
|
|
950
|
+
```md
|
|
951
|
+
---
|
|
952
|
+
name: fork-chatback
|
|
953
|
+
description: Forked worker that asks the orchestrator questions through intercom when needed
|
|
954
|
+
tools: read, bash, edit, write, intercom
|
|
955
|
+
systemPromptMode: replace
|
|
956
|
+
inheritProjectContext: true
|
|
957
|
+
inheritSkills: false
|
|
958
|
+
---
|
|
959
|
+
|
|
960
|
+
You are a delegated worker running from a fork of the orchestrator session.
|
|
961
|
+
|
|
962
|
+
Treat the inherited conversation as reference-only context. Do not continue that conversation in normal assistant text.
|
|
963
|
+
|
|
964
|
+
Your job is to do the task. If you need a decision, clarification, or unblock from the orchestrator, use `intercom` to ask the orchestrator session named in the runtime bridge instructions.
|
|
965
|
+
|
|
966
|
+
If you need to send a progress update or completion handoff upstream, use `intercom` to send it to that same orchestrator session.
|
|
967
|
+
|
|
968
|
+
If no upstream coordination is needed, just complete the work and return a focused task result.
|
|
969
|
+
```
|
|
970
|
+
|
|
971
|
+
Pair that with task wording that makes the contract explicit, like "Work from the forked context below. If you need anything from me, ask through `intercom`. Otherwise complete the task and return the result."
|
|
972
|
+
|
|
944
973
|
### `worktreeSetupHook`
|
|
945
974
|
|
|
946
975
|
`worktreeSetupHook` configures an optional setup hook for worktree-isolated parallel runs. The hook runs once per created worktree, after `git worktree add` succeeds and before the agent starts.
|
|
@@ -1005,7 +1034,7 @@ When fallback is used, metadata records both the ordered `attemptedModels` list
|
|
|
1005
1034
|
|
|
1006
1035
|
Session files (JSONL) are stored under a per-run session directory. Directory selection follows the same precedence as session root resolution: explicit `sessionDir` > `config.defaultSessionDir` > parent-session-derived path. The session file path is shown in output.
|
|
1007
1036
|
|
|
1008
|
-
When `context: "fork"` is used, each child run starts with `--session <branched-session-file>` produced from the parent's current leaf. This is a real session fork, not injected summary text.
|
|
1037
|
+
When `context: "fork"` is used, each child run starts with `--session <branched-session-file>` produced from the parent's current leaf. This is a real session fork, not injected summary text. The fork preamble explicitly tells the child to treat the inherited conversation as reference-only context rather than a live thread to continue.
|
|
1009
1038
|
|
|
1010
1039
|
## Session Sharing
|
|
1011
1040
|
|
|
@@ -1041,6 +1070,8 @@ During sync execution, the collapsed view shows real-time progress for single, c
|
|
|
1041
1070
|
- Per-task step cards showing status icon, agent name, model, tool count, and duration
|
|
1042
1071
|
- Current tool and recent output for each running task
|
|
1043
1072
|
|
|
1073
|
+
While a foreground run is active, the compact view also hints when richer detail is available and shows lightweight live-state signals like activity freshness and current-tool duration.
|
|
1074
|
+
|
|
1044
1075
|
Press **Ctrl+O** to expand the full streaming view with complete output per step.
|
|
1045
1076
|
|
|
1046
1077
|
> **Note:** Chain visualization (the `done scout → running planner` line) is only shown for sequential chains. Chains with parallel steps show per-step cards instead.
|
|
@@ -1095,7 +1126,7 @@ subagent_status({ id: "<id>" })
|
|
|
1095
1126
|
subagent_status({ dir: "<tmpdir>/pi-subagents-<scope>/async-subagent-runs/<id>" })
|
|
1096
1127
|
```
|
|
1097
1128
|
|
|
1098
|
-
For an interactive overview, run the `/subagents-status` slash command to open the overlay listing active runs and recent completed/failed runs. The overlay auto-refreshes every 2 seconds while it is open.
|
|
1129
|
+
For an interactive overview, run the `/subagents-status` slash command to open the overlay listing active runs and recent completed/failed runs. The overlay auto-refreshes every 2 seconds while it is open and focuses on summary/status information, including the current output/session paths when available.
|
|
1099
1130
|
|
|
1100
1131
|
## Events
|
|
1101
1132
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
name: context-builder
|
|
3
3
|
description: Analyzes requirements and codebase, generates context and meta-prompt
|
|
4
4
|
tools: read, grep, find, ls, bash, write, web_search
|
|
5
|
-
model:
|
|
5
|
+
model: openai-codex/gpt-5.4
|
|
6
6
|
systemPromptMode: replace
|
|
7
7
|
inheritProjectContext: true
|
|
8
8
|
inheritSkills: false
|
package/agents/planner.md
CHANGED
package/agents/researcher.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
name: researcher
|
|
3
3
|
description: Autonomous web researcher — searches, evaluates, and synthesizes a focused research brief
|
|
4
4
|
tools: read, write, web_search, fetch_content, get_search_content
|
|
5
|
-
model:
|
|
5
|
+
model: openai-codex/gpt-5.4
|
|
6
6
|
systemPromptMode: replace
|
|
7
7
|
inheritProjectContext: true
|
|
8
8
|
inheritSkills: false
|
package/agents/scout.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
name: scout
|
|
3
3
|
description: Fast codebase recon that returns compressed context for handoff
|
|
4
4
|
tools: read, grep, find, ls, bash, write
|
|
5
|
-
model:
|
|
5
|
+
model: openai-codex/gpt-5.4-mini
|
|
6
6
|
systemPromptMode: replace
|
|
7
7
|
inheritProjectContext: true
|
|
8
8
|
inheritSkills: false
|
package/agents/worker.md
CHANGED
package/execution.ts
CHANGED
|
@@ -63,6 +63,35 @@ function appendRecentOutput(progress: AgentProgress, lines: string[]): void {
|
|
|
63
63
|
}
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
+
function snapshotProgress(progress: AgentProgress): AgentProgress {
|
|
67
|
+
return {
|
|
68
|
+
...progress,
|
|
69
|
+
skills: progress.skills ? [...progress.skills] : undefined,
|
|
70
|
+
recentTools: progress.recentTools.map((tool) => ({ ...tool })),
|
|
71
|
+
recentOutput: [...progress.recentOutput],
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function snapshotResult(result: SingleResult, progress: AgentProgress): SingleResult {
|
|
76
|
+
return {
|
|
77
|
+
...result,
|
|
78
|
+
messages: result.messages ? [...result.messages] : undefined,
|
|
79
|
+
usage: { ...result.usage },
|
|
80
|
+
skills: result.skills ? [...result.skills] : undefined,
|
|
81
|
+
attemptedModels: result.attemptedModels ? [...result.attemptedModels] : undefined,
|
|
82
|
+
modelAttempts: result.modelAttempts
|
|
83
|
+
? result.modelAttempts.map((attempt) => ({
|
|
84
|
+
...attempt,
|
|
85
|
+
usage: attempt.usage ? { ...attempt.usage } : undefined,
|
|
86
|
+
}))
|
|
87
|
+
: undefined,
|
|
88
|
+
progress,
|
|
89
|
+
progressSummary: result.progressSummary ? { ...result.progressSummary } : undefined,
|
|
90
|
+
artifactPaths: result.artifactPaths ? { ...result.artifactPaths } : undefined,
|
|
91
|
+
truncation: result.truncation ? { ...result.truncation } : undefined,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
66
95
|
async function runSingleAttempt(
|
|
67
96
|
runtimeCwd: string,
|
|
68
97
|
agent: AgentConfig,
|
|
@@ -75,6 +104,7 @@ async function runSingleAttempt(
|
|
|
75
104
|
resolvedSkillNames?: string[];
|
|
76
105
|
skillsWarning?: string;
|
|
77
106
|
jsonlPath?: string;
|
|
107
|
+
artifactPaths?: ArtifactPaths;
|
|
78
108
|
attemptNotes: string[];
|
|
79
109
|
outputSnapshot?: SingleOutputSnapshot;
|
|
80
110
|
},
|
|
@@ -105,6 +135,7 @@ async function runSingleAttempt(
|
|
|
105
135
|
messages: [],
|
|
106
136
|
usage: emptyUsage(),
|
|
107
137
|
model: modelArg,
|
|
138
|
+
artifactPaths: shared.artifactPaths,
|
|
108
139
|
skills: shared.resolvedSkillNames,
|
|
109
140
|
skillsWarning: shared.skillsWarning,
|
|
110
141
|
};
|
|
@@ -120,6 +151,7 @@ async function runSingleAttempt(
|
|
|
120
151
|
toolCount: 0,
|
|
121
152
|
tokens: 0,
|
|
122
153
|
durationMs: 0,
|
|
154
|
+
lastActivityAt: Date.now(),
|
|
123
155
|
};
|
|
124
156
|
result.progress = progress;
|
|
125
157
|
|
|
@@ -175,15 +207,22 @@ async function runSingleAttempt(
|
|
|
175
207
|
resolve(code);
|
|
176
208
|
};
|
|
177
209
|
|
|
178
|
-
const
|
|
210
|
+
const emitUpdateSnapshot = (text: string) => {
|
|
179
211
|
if (!options.onUpdate || processClosed) return;
|
|
180
|
-
|
|
212
|
+
const progressSnapshot = snapshotProgress(progress);
|
|
213
|
+
const resultSnapshot = snapshotResult(result, progressSnapshot);
|
|
181
214
|
options.onUpdate({
|
|
182
|
-
content: [{ type: "text", text
|
|
183
|
-
details: { mode: "single", results: [
|
|
215
|
+
content: [{ type: "text", text }],
|
|
216
|
+
details: { mode: "single", results: [resultSnapshot], progress: [progressSnapshot] },
|
|
184
217
|
});
|
|
185
218
|
};
|
|
186
219
|
|
|
220
|
+
const fireUpdate = () => {
|
|
221
|
+
if (!options.onUpdate || processClosed) return;
|
|
222
|
+
progress.durationMs = Date.now() - startTime;
|
|
223
|
+
emitUpdateSnapshot(getFinalOutput(result.messages) || "(running...)");
|
|
224
|
+
};
|
|
225
|
+
|
|
187
226
|
const processLine = (line: string) => {
|
|
188
227
|
if (!line.trim()) return;
|
|
189
228
|
jsonlWriter.writeLine(line);
|
|
@@ -197,6 +236,7 @@ async function runSingleAttempt(
|
|
|
197
236
|
|
|
198
237
|
const now = Date.now();
|
|
199
238
|
progress.durationMs = now - startTime;
|
|
239
|
+
progress.lastActivityAt = now;
|
|
200
240
|
|
|
201
241
|
if (evt.type === "tool_execution_start") {
|
|
202
242
|
if (options.allowIntercomDetach && evt.toolName === "intercom") {
|
|
@@ -205,6 +245,7 @@ async function runSingleAttempt(
|
|
|
205
245
|
progress.toolCount++;
|
|
206
246
|
progress.currentTool = evt.toolName;
|
|
207
247
|
progress.currentToolArgs = extractToolArgsPreview((evt.args || {}) as Record<string, unknown>);
|
|
248
|
+
progress.currentToolStartedAt = now;
|
|
208
249
|
fireUpdate();
|
|
209
250
|
}
|
|
210
251
|
|
|
@@ -218,6 +259,7 @@ async function runSingleAttempt(
|
|
|
218
259
|
}
|
|
219
260
|
progress.currentTool = undefined;
|
|
220
261
|
progress.currentToolArgs = undefined;
|
|
262
|
+
progress.currentToolStartedAt = undefined;
|
|
221
263
|
fireUpdate();
|
|
222
264
|
}
|
|
223
265
|
|
|
@@ -343,6 +385,15 @@ async function runSingleAttempt(
|
|
|
343
385
|
result.outputSaveError = resolvedOutput.saveError;
|
|
344
386
|
}
|
|
345
387
|
result.finalOutput = fullOutput;
|
|
388
|
+
if (options.onUpdate) {
|
|
389
|
+
const finalText = result.finalOutput || result.error || "(no output)";
|
|
390
|
+
const progressSnapshot = snapshotProgress(progress);
|
|
391
|
+
const resultSnapshot = snapshotResult(result, progressSnapshot);
|
|
392
|
+
options.onUpdate({
|
|
393
|
+
content: [{ type: "text", text: finalText }],
|
|
394
|
+
details: { mode: "single", results: [resultSnapshot], progress: [progressSnapshot] },
|
|
395
|
+
});
|
|
396
|
+
}
|
|
346
397
|
return result;
|
|
347
398
|
}
|
|
348
399
|
|
|
@@ -417,6 +468,7 @@ export async function runSync(
|
|
|
417
468
|
resolvedSkillNames: resolvedSkills.length > 0 ? resolvedSkills.map((skill) => skill.name) : undefined,
|
|
418
469
|
skillsWarning: missingSkills.length > 0 ? `Skills not found: ${missingSkills.join(", ")}` : undefined,
|
|
419
470
|
jsonlPath,
|
|
471
|
+
artifactPaths: artifactPathsResult,
|
|
420
472
|
attemptNotes,
|
|
421
473
|
outputSnapshot,
|
|
422
474
|
});
|
package/intercom-bridge.ts
CHANGED
|
@@ -9,10 +9,13 @@ const DEFAULT_INTERCOM_CONFIG_PATH = path.join(os.homedir(), ".pi", "agent", "in
|
|
|
9
9
|
const DEFAULT_SUBAGENT_CONFIG_DIR = path.join(os.homedir(), ".pi", "agent", "extensions", "subagent");
|
|
10
10
|
const DEFAULT_INTERCOM_TARGET_PREFIX = "subagent-chat";
|
|
11
11
|
const INTERCOM_BRIDGE_MARKER = "Intercom orchestration channel:";
|
|
12
|
-
const DEFAULT_INTERCOM_BRIDGE_TEMPLATE = `
|
|
12
|
+
const DEFAULT_INTERCOM_BRIDGE_TEMPLATE = `The inherited thread is reference-only. Do not continue that conversation or send questions, status updates, or completion handoffs to the orchestrator in normal assistant text.
|
|
13
|
+
|
|
14
|
+
Use intercom only for coordination with the orchestrator session "{orchestratorTarget}".
|
|
13
15
|
- Need a decision or blocked: intercom({ action: "ask", to: "{orchestratorTarget}", message: "<question>" })
|
|
14
|
-
-
|
|
15
|
-
|
|
16
|
+
- Need to report progress or a completion handoff: intercom({ action: "send", to: "{orchestratorTarget}", message: "DONE: <summary>" })
|
|
17
|
+
|
|
18
|
+
If no upstream coordination is needed, continue the task normally and return a focused task result.`;
|
|
16
19
|
|
|
17
20
|
export interface IntercomBridgeState {
|
|
18
21
|
active: boolean;
|
package/package.json
CHANGED
package/render.ts
CHANGED
|
@@ -6,6 +6,7 @@ import type { AgentToolResult } from "@mariozechner/pi-agent-core";
|
|
|
6
6
|
import { getMarkdownTheme, type ExtensionContext } from "@mariozechner/pi-coding-agent";
|
|
7
7
|
import { Container, Markdown, Spacer, Text, visibleWidth, type Component } from "@mariozechner/pi-tui";
|
|
8
8
|
import {
|
|
9
|
+
type AgentProgress,
|
|
9
10
|
type AsyncJobState,
|
|
10
11
|
type Details,
|
|
11
12
|
MAX_WIDGET_JOBS,
|
|
@@ -113,6 +114,34 @@ function getToolCallLines(
|
|
|
113
114
|
return result.toolCalls?.map((toolCall) => expanded ? toolCall.expandedText : toolCall.text) ?? [];
|
|
114
115
|
}
|
|
115
116
|
|
|
117
|
+
function formatActivityLabel(lastActivityAt: number | undefined, now = Date.now()): string | undefined {
|
|
118
|
+
if (lastActivityAt === undefined) return undefined;
|
|
119
|
+
const ago = Math.max(0, now - lastActivityAt);
|
|
120
|
+
if (ago < 1000) return "active now";
|
|
121
|
+
if (ago < 60000) return `active ${Math.floor(ago / 1000)}s ago`;
|
|
122
|
+
return `active ${Math.floor(ago / 60000)}m ago`;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function formatCurrentToolLine(progress: Pick<AgentProgress, "currentTool" | "currentToolArgs" | "currentToolStartedAt">, availableWidth: number, expanded: boolean): string | undefined {
|
|
126
|
+
if (!progress.currentTool) return undefined;
|
|
127
|
+
const maxToolArgsLen = Math.max(50, availableWidth - 20);
|
|
128
|
+
const toolArgsPreview = progress.currentToolArgs
|
|
129
|
+
? (expanded || progress.currentToolArgs.length <= maxToolArgsLen
|
|
130
|
+
? progress.currentToolArgs
|
|
131
|
+
: `${progress.currentToolArgs.slice(0, maxToolArgsLen)}...`)
|
|
132
|
+
: "";
|
|
133
|
+
const durationSuffix = progress.currentToolStartedAt !== undefined
|
|
134
|
+
? ` | ${formatDuration(Math.max(0, Date.now() - progress.currentToolStartedAt))}`
|
|
135
|
+
: "";
|
|
136
|
+
return toolArgsPreview
|
|
137
|
+
? `${progress.currentTool}: ${toolArgsPreview}${durationSuffix}`
|
|
138
|
+
: `${progress.currentTool}${durationSuffix}`;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function buildLiveStatusLine(progress: Pick<AgentProgress, "lastActivityAt">): string | undefined {
|
|
142
|
+
return formatActivityLabel(progress.lastActivityAt);
|
|
143
|
+
}
|
|
144
|
+
|
|
116
145
|
/**
|
|
117
146
|
* Render the async jobs widget
|
|
118
147
|
*/
|
|
@@ -226,18 +255,18 @@ export function renderSubagentResult(
|
|
|
226
255
|
c.addChild(new Spacer(1));
|
|
227
256
|
|
|
228
257
|
if (isRunning && r.progress) {
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
const toolArgsPreview = r.progress.currentToolArgs
|
|
232
|
-
? (expanded || r.progress.currentToolArgs.length <= maxToolArgsLen
|
|
233
|
-
? r.progress.currentToolArgs
|
|
234
|
-
: `${r.progress.currentToolArgs.slice(0, maxToolArgsLen)}...`)
|
|
235
|
-
: "";
|
|
236
|
-
const toolLine = toolArgsPreview
|
|
237
|
-
? `${r.progress.currentTool}: ${toolArgsPreview}`
|
|
238
|
-
: r.progress.currentTool;
|
|
258
|
+
const toolLine = formatCurrentToolLine(r.progress, w, expanded);
|
|
259
|
+
if (toolLine) {
|
|
239
260
|
c.addChild(new Text(fit(theme.fg("warning", `> ${toolLine}`)), 0, 0));
|
|
240
261
|
}
|
|
262
|
+
const liveStatusLine = buildLiveStatusLine(r.progress);
|
|
263
|
+
if (liveStatusLine) {
|
|
264
|
+
c.addChild(new Text(fit(theme.fg("accent", liveStatusLine)), 0, 0));
|
|
265
|
+
}
|
|
266
|
+
c.addChild(new Text(fit(theme.fg("accent", "Press Ctrl+O for live detail")), 0, 0));
|
|
267
|
+
if (r.artifactPaths) {
|
|
268
|
+
c.addChild(new Text(fit(theme.fg("dim", `Artifacts: ${shortenPath(r.artifactPaths.outputPath)}`)), 0, 0));
|
|
269
|
+
}
|
|
241
270
|
if (r.progress.recentTools?.length) {
|
|
242
271
|
for (const t of r.progress.recentTools.slice(-3)) {
|
|
243
272
|
const maxArgsLen = Math.max(40, w - 24);
|
|
@@ -250,7 +279,7 @@ export function renderSubagentResult(
|
|
|
250
279
|
for (const line of (r.progress.recentOutput ?? []).slice(-5)) {
|
|
251
280
|
c.addChild(new Text(fit(theme.fg("dim", ` ${line}`)), 0, 0));
|
|
252
281
|
}
|
|
253
|
-
if (
|
|
282
|
+
if (toolLine || liveStatusLine || r.progress.recentTools?.length || r.progress.recentOutput?.length || r.artifactPaths) {
|
|
254
283
|
c.addChild(new Spacer(1));
|
|
255
284
|
}
|
|
256
285
|
}
|
|
@@ -278,7 +307,7 @@ export function renderSubagentResult(
|
|
|
278
307
|
c.addChild(new Text(fit(theme.fg("dim", `Session: ${shortenPath(r.sessionFile)}`)), 0, 0));
|
|
279
308
|
}
|
|
280
309
|
|
|
281
|
-
if (r.artifactPaths) {
|
|
310
|
+
if (!isRunning && r.artifactPaths) {
|
|
282
311
|
c.addChild(new Spacer(1));
|
|
283
312
|
c.addChild(new Text(fit(theme.fg("dim", `Artifacts: ${shortenPath(r.artifactPaths.outputPath)}`)), 0, 0));
|
|
284
313
|
}
|
|
@@ -391,6 +420,7 @@ export function renderSubagentResult(
|
|
|
391
420
|
|| d.progress?.find((p) => p.agent === r.agent && p.status === "running");
|
|
392
421
|
const rProg = r.progress || progressFromArray || r.progressSummary;
|
|
393
422
|
const rRunning = rProg?.status === "running";
|
|
423
|
+
const stepNumber = typeof rProg?.index === "number" ? rProg.index + 1 : i + 1;
|
|
394
424
|
|
|
395
425
|
const resultOutput = getSingleResultOutput(r);
|
|
396
426
|
const statusIcon = rRunning
|
|
@@ -403,8 +433,8 @@ export function renderSubagentResult(
|
|
|
403
433
|
const stats = rProg ? ` | ${rProg.toolCount} tools, ${formatDuration(rProg.durationMs)}` : "";
|
|
404
434
|
const modelDisplay = r.model ? theme.fg("dim", ` (${r.model})`) : "";
|
|
405
435
|
const stepHeader = rRunning
|
|
406
|
-
? `${statusIcon} Step ${
|
|
407
|
-
: `${statusIcon} Step ${
|
|
436
|
+
? `${statusIcon} Step ${stepNumber}: ${theme.bold(theme.fg("warning", r.agent))}${modelDisplay}${stats}`
|
|
437
|
+
: `${statusIcon} Step ${stepNumber}: ${theme.bold(r.agent)}${modelDisplay}${stats}`;
|
|
408
438
|
const toolCallLines = getToolCallLines(r, expanded);
|
|
409
439
|
c.addChild(new Text(fit(stepHeader), 0, 0));
|
|
410
440
|
|
|
@@ -433,18 +463,18 @@ export function renderSubagentResult(
|
|
|
433
463
|
if (rProg.skills?.length) {
|
|
434
464
|
c.addChild(new Text(fit(theme.fg("accent", ` skills: ${rProg.skills.join(", ")}`)), 0, 0));
|
|
435
465
|
}
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
const toolArgsPreview = rProg.currentToolArgs
|
|
439
|
-
? (expanded || rProg.currentToolArgs.length <= maxToolArgsLen
|
|
440
|
-
? rProg.currentToolArgs
|
|
441
|
-
: `${rProg.currentToolArgs.slice(0, maxToolArgsLen)}...`)
|
|
442
|
-
: "";
|
|
443
|
-
const toolLine = toolArgsPreview
|
|
444
|
-
? `${rProg.currentTool}: ${toolArgsPreview}`
|
|
445
|
-
: rProg.currentTool;
|
|
466
|
+
const toolLine = formatCurrentToolLine(rProg, w, expanded);
|
|
467
|
+
if (toolLine) {
|
|
446
468
|
c.addChild(new Text(fit(theme.fg("warning", ` > ${toolLine}`)), 0, 0));
|
|
447
469
|
}
|
|
470
|
+
const liveStatusLine = buildLiveStatusLine(rProg);
|
|
471
|
+
if (liveStatusLine) {
|
|
472
|
+
c.addChild(new Text(fit(theme.fg("accent", ` ${liveStatusLine}`)), 0, 0));
|
|
473
|
+
}
|
|
474
|
+
c.addChild(new Text(fit(theme.fg("accent", " Press Ctrl+O for live detail")), 0, 0));
|
|
475
|
+
if (r.artifactPaths) {
|
|
476
|
+
c.addChild(new Text(fit(theme.fg("dim", ` artifacts: ${shortenPath(r.artifactPaths.outputPath)}`)), 0, 0));
|
|
477
|
+
}
|
|
448
478
|
if (rProg.recentTools?.length) {
|
|
449
479
|
for (const t of rProg.recentTools.slice(-3)) {
|
|
450
480
|
const maxArgsLen = Math.max(40, w - 30);
|
|
@@ -460,6 +490,10 @@ export function renderSubagentResult(
|
|
|
460
490
|
}
|
|
461
491
|
}
|
|
462
492
|
|
|
493
|
+
if (!rRunning && r.artifactPaths) {
|
|
494
|
+
c.addChild(new Text(fit(theme.fg("dim", ` artifacts: ${shortenPath(r.artifactPaths.outputPath)}`)), 0, 0));
|
|
495
|
+
}
|
|
496
|
+
|
|
463
497
|
if (expanded && !rRunning) {
|
|
464
498
|
for (const line of toolCallLines) {
|
|
465
499
|
c.addChild(new Text(fit(theme.fg("muted", ` ${line}`)), 0, 0));
|
package/slash-commands.ts
CHANGED
|
@@ -145,7 +145,7 @@ async function requestSlashRun(
|
|
|
145
145
|
if (!ctx.hasUI) return;
|
|
146
146
|
const tool = update.currentTool ? ` ${update.currentTool}` : "";
|
|
147
147
|
const count = update.toolCount ?? 0;
|
|
148
|
-
ctx.ui.setStatus("subagent-slash", `${count} tools${tool}`);
|
|
148
|
+
ctx.ui.setStatus("subagent-slash", `${count} tools${tool} | Ctrl+O live detail`);
|
|
149
149
|
};
|
|
150
150
|
|
|
151
151
|
const onTerminalInput = ctx.hasUI
|
package/subagents-status.ts
CHANGED
|
@@ -157,6 +157,12 @@ export class SubagentsStatusComponent implements Component {
|
|
|
157
157
|
const lines = [
|
|
158
158
|
row(`cwd: ${truncateToWidth(shortenPath(run.cwd ?? run.asyncDir), innerW - 5)}`, width, this.theme),
|
|
159
159
|
];
|
|
160
|
+
if (run.outputFile) {
|
|
161
|
+
lines.push(row(`output: ${truncateToWidth(shortenPath(run.outputFile), innerW - 8)}`, width, this.theme));
|
|
162
|
+
}
|
|
163
|
+
if (run.sessionFile) {
|
|
164
|
+
lines.push(row(`session: ${truncateToWidth(shortenPath(run.sessionFile), innerW - 9)}`, width, this.theme));
|
|
165
|
+
}
|
|
160
166
|
for (const step of run.steps) {
|
|
161
167
|
const model = step.model ? ` | ${step.model}` : "";
|
|
162
168
|
const attempts = step.attemptedModels && step.attemptedModels.length > 1
|
|
@@ -226,7 +232,7 @@ export class SubagentsStatusComponent implements Component {
|
|
|
226
232
|
lines.push(row(this.theme.fg("dim", "No runs selected."), w, this.theme));
|
|
227
233
|
}
|
|
228
234
|
|
|
229
|
-
const footer = `↑↓ select esc close ${this.active.length} active / ${this.recent.length} recent`;
|
|
235
|
+
const footer = `↑↓ select esc close summary view ${this.active.length} active / ${this.recent.length} recent`;
|
|
230
236
|
lines.push(renderFooter(truncateToWidth(footer, innerW), w, this.theme));
|
|
231
237
|
return lines;
|
|
232
238
|
}
|
package/types.ts
CHANGED
|
@@ -50,8 +50,10 @@ export interface AgentProgress {
|
|
|
50
50
|
status: "pending" | "running" | "completed" | "failed" | "detached";
|
|
51
51
|
task: string;
|
|
52
52
|
skills?: string[];
|
|
53
|
+
lastActivityAt?: number;
|
|
53
54
|
currentTool?: string;
|
|
54
55
|
currentToolArgs?: string;
|
|
56
|
+
currentToolStartedAt?: number;
|
|
55
57
|
recentTools: Array<{ tool: string; args: string; endMs: number }>;
|
|
56
58
|
recentOutput: string[];
|
|
57
59
|
toolCount: number;
|
|
@@ -384,9 +386,10 @@ export const MAX_WIDGET_JOBS = 4;
|
|
|
384
386
|
export const DEFAULT_SUBAGENT_MAX_DEPTH = 2;
|
|
385
387
|
|
|
386
388
|
export const DEFAULT_FORK_PREAMBLE =
|
|
387
|
-
"You are a delegated subagent
|
|
388
|
-
"
|
|
389
|
-
"
|
|
389
|
+
"You are a delegated subagent running from a fork of the parent session. " +
|
|
390
|
+
"Treat the inherited conversation as reference-only context, not a live thread to continue. " +
|
|
391
|
+
"Do not continue or answer prior messages as if they are waiting for a reply. " +
|
|
392
|
+
"Your sole job is to execute the task below and return a focused result for that task using your tools.";
|
|
390
393
|
|
|
391
394
|
function normalizeTopLevelParallelValue(value: unknown): number | undefined {
|
|
392
395
|
const parsed = typeof value === "number" ? value : typeof value === "string" ? Number(value) : NaN;
|
package/utils.ts
CHANGED
|
@@ -362,25 +362,54 @@ export function detectSubagentError(messages: Message[]): ErrorInfo {
|
|
|
362
362
|
* Extract a preview of tool arguments for display
|
|
363
363
|
*/
|
|
364
364
|
export function extractToolArgsPreview(args: Record<string, unknown>): string {
|
|
365
|
+
const truncatePreview = (value: string, maxLength: number): string =>
|
|
366
|
+
value.length > maxLength ? `${value.slice(0, maxLength - 3)}...` : value;
|
|
367
|
+
|
|
368
|
+
const stringifyPreviewValue = (value: unknown): string | undefined => {
|
|
369
|
+
if (typeof value === "string" && value.trim().length > 0) return value;
|
|
370
|
+
if (typeof value === "number" || typeof value === "boolean") return String(value);
|
|
371
|
+
return undefined;
|
|
372
|
+
};
|
|
373
|
+
|
|
374
|
+
const previewArray = (value: unknown): string | undefined => {
|
|
375
|
+
if (!Array.isArray(value) || value.length === 0) return undefined;
|
|
376
|
+
const first = stringifyPreviewValue(value[0]);
|
|
377
|
+
if (!first) return undefined;
|
|
378
|
+
const suffix = value.length > 1 ? ` (+${value.length - 1} more)` : "";
|
|
379
|
+
return `${first}${suffix}`;
|
|
380
|
+
};
|
|
381
|
+
|
|
365
382
|
// Handle MCP tool calls - show server/tool info
|
|
366
383
|
if (args.tool && typeof args.tool === "string") {
|
|
367
384
|
const server = args.server && typeof args.server === "string" ? `${args.server}/` : "";
|
|
368
385
|
const toolArgs = args.args && typeof args.args === "string" ? ` ${args.args.slice(0, 40)}` : "";
|
|
369
386
|
return `${server}${args.tool}${toolArgs}`;
|
|
370
387
|
}
|
|
388
|
+
|
|
389
|
+
const queriesPreview = previewArray(args.queries);
|
|
390
|
+
if (queriesPreview) return truncatePreview(queriesPreview, 60);
|
|
391
|
+
if (typeof args.query === "string" && args.query.trim().length > 0) return truncatePreview(args.query, 60);
|
|
392
|
+
if (typeof args.workflow === "string" && args.workflow.trim().length > 0) return `workflow=${truncatePreview(args.workflow, 48)}`;
|
|
393
|
+
|
|
394
|
+
if (typeof args.url === "string" && args.url.trim().length > 0) return truncatePreview(args.url, 60);
|
|
395
|
+
const urlsPreview = previewArray(args.urls);
|
|
396
|
+
if (urlsPreview) return truncatePreview(urlsPreview, 60);
|
|
397
|
+
if (typeof args.prompt === "string" && args.prompt.trim().length > 0) return truncatePreview(args.prompt, 60);
|
|
371
398
|
|
|
372
399
|
const previewKeys = ["command", "path", "file_path", "pattern", "query", "url", "task", "describe", "search"];
|
|
373
400
|
for (const key of previewKeys) {
|
|
374
401
|
if (args[key] && typeof args[key] === "string") {
|
|
375
402
|
const value = args[key] as string;
|
|
376
|
-
return value
|
|
403
|
+
return truncatePreview(value, 60);
|
|
377
404
|
}
|
|
378
405
|
}
|
|
379
406
|
|
|
380
407
|
// Fallback: show first string value found
|
|
381
408
|
for (const [key, value] of Object.entries(args)) {
|
|
409
|
+
const arrayPreview = previewArray(value);
|
|
410
|
+
if (arrayPreview) return `${key}=${truncatePreview(arrayPreview, 50)}`;
|
|
382
411
|
if (typeof value === "string" && value.length > 0) {
|
|
383
|
-
const preview = value
|
|
412
|
+
const preview = truncatePreview(value, 50);
|
|
384
413
|
return `${key}=${preview}`;
|
|
385
414
|
}
|
|
386
415
|
}
|