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 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 intercom is unavailable in this run, continue the task normally.
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: claude-sonnet-4-6
5
+ model: openai-codex/gpt-5.4
6
6
  systemPromptMode: replace
7
7
  inheritProjectContext: true
8
8
  inheritSkills: false
package/agents/planner.md CHANGED
@@ -2,7 +2,7 @@
2
2
  name: planner
3
3
  description: Creates implementation plans from context and requirements
4
4
  tools: read, grep, find, ls, write
5
- model: claude-opus-4-6
5
+ model: openai-codex/gpt-5.4
6
6
  thinking: high
7
7
  systemPromptMode: replace
8
8
  inheritProjectContext: true
@@ -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: anthropic/claude-sonnet-4-6
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: anthropic/claude-haiku-4-5
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
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: worker
3
3
  description: General-purpose subagent with full capabilities
4
- model: claude-sonnet-4-6
4
+ model: openai-codex/gpt-5.4
5
5
  systemPromptMode: replace
6
6
  inheritProjectContext: true
7
7
  inheritSkills: false
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 fireUpdate = () => {
210
+ const emitUpdateSnapshot = (text: string) => {
179
211
  if (!options.onUpdate || processClosed) return;
180
- progress.durationMs = Date.now() - startTime;
212
+ const progressSnapshot = snapshotProgress(progress);
213
+ const resultSnapshot = snapshotResult(result, progressSnapshot);
181
214
  options.onUpdate({
182
- content: [{ type: "text", text: getFinalOutput(result.messages) || "(running...)" }],
183
- details: { mode: "single", results: [result], progress: [progress] },
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
  });
@@ -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 = `Use intercom only for coordination with the orchestrator session:
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
- - Completion/update: intercom({ action: "send", to: "{orchestratorTarget}", message: "DONE: <summary>" })
15
- If intercom is unavailable in this run, continue the task normally.`;
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-subagents",
3
- "version": "0.17.0",
3
+ "version": "0.17.1",
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",
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
- if (r.progress.currentTool) {
230
- const maxToolArgsLen = Math.max(50, w - 20);
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 (r.progress.currentTool || r.progress.recentTools?.length || r.progress.recentOutput?.length) {
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 ${i + 1}: ${theme.bold(theme.fg("warning", r.agent))}${modelDisplay}${stats}`
407
- : `${statusIcon} Step ${i + 1}: ${theme.bold(r.agent)}${modelDisplay}${stats}`;
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
- if (rProg.currentTool) {
437
- const maxToolArgsLen = Math.max(50, w - 20);
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
@@ -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 with access to the parent session's context for reference. " +
388
- "Your sole job is to execute the task below. Do not continue or respond to the prior conversation " +
389
- " focus exclusively on completing this task using your tools.";
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.length > 60 ? `${value.slice(0, 57)}...` : 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.length > 50 ? `${value.slice(0, 47)}...` : value;
412
+ const preview = truncatePreview(value, 50);
384
413
  return `${key}=${preview}`;
385
414
  }
386
415
  }