@tintinweb/pi-subagents 0.10.3 → 0.10.4
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 +8 -0
- package/README.md +3 -3
- package/dist/agent-manager.d.ts +17 -2
- package/dist/agent-manager.js +49 -8
- package/dist/index.js +23 -5
- package/package.json +1 -1
- package/src/agent-manager.ts +45 -8
- package/src/index.ts +23 -5
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.10.4] - 2026-06-23
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
- **Background agent records lost before result is read** ([#108](https://github.com/tintinweb/pi-subagents/issues/108) — thanks [@philipmw](https://github.com/philipmw)). On session switch or `/new`/`/resume`, `clearCompleted()` removed completed agent records regardless of whether the LLM had retrieved the result, causing `get_subagent_result` to return "Agent not found" for agents that had finished but hadn't been checked yet. `clearCompleted()` now accepts a `skipUnconsumed` flag; session event handlers pass `true`, so records with `resultConsumed=false` are preserved across session transitions. The 10-minute cleanup timer handles eventual eviction. Note: a full session shutdown (`session_shutdown`) calls `dispose()` which clears all records unconditionally — that path is not affected by this fix.
|
|
14
|
+
|
|
15
|
+
### Added
|
|
16
|
+
- **Foreground agent lifecycle completion and conversation logging** ([#105](https://github.com/tintinweb/pi-subagents/pull/105) — thanks [@benrhodeland](https://github.com/benrhodeland)). Two gaps closed: (1) **`onComplete` now fires for foreground agents**, emitting `subagents:completed` / `subagents:failed` lifecycle events and writing a `subagents:record` entry to the parent JSONL — previously only background agents emitted these, leaving cross-extension observers with an orphaned `subagents:started` event and no matching completion. `resultConsumed` is pre-set so the callback skips notifications (the result is returned inline); no change to the tool's return value. (2) **Foreground agent conversations are now streamed to `.output` files** (same `.pi/output/agent-<id>.jsonl` path as background agents) — inline subagent transcripts were previously permanently lost after `spawnAndWait` returned.
|
|
17
|
+
|
|
10
18
|
## [0.10.3] - 2026-06-12
|
|
11
19
|
|
|
12
20
|
### Added
|
package/README.md
CHANGED
|
@@ -124,7 +124,7 @@ Individual agent results render Claude Code-style in the conversation:
|
|
|
124
124
|
|
|
125
125
|
Completed results can be expanded (ctrl+o in pi) to show the full agent output inline.
|
|
126
126
|
|
|
127
|
-
Background agent completion notifications render as styled boxes:
|
|
127
|
+
Both foreground and background agents stream their full conversation to a `.pi/output/agent-<id>.jsonl` transcript file. Background agent completion notifications render as styled boxes:
|
|
128
128
|
|
|
129
129
|
```
|
|
130
130
|
✓ Find auth files completed
|
|
@@ -418,8 +418,8 @@ Agent lifecycle events are emitted via `pi.events.emit()` so other extensions ca
|
|
|
418
418
|
|-------|------|------------|
|
|
419
419
|
| `subagents:created` | Background agent registered | `id`, `type`, `description`, `isBackground` |
|
|
420
420
|
| `subagents:started` | Agent transitions to running (including queued→running) | `id`, `type`, `description` |
|
|
421
|
-
| `subagents:completed` | Agent finished successfully | `id`, `type`, `durationMs`, `tokens` (lifetime `{ input, output, total }`), `toolUses`, `result` |
|
|
422
|
-
| `subagents:failed` | Agent errored, stopped, or aborted | same as completed + `error`, `status` |
|
|
421
|
+
| `subagents:completed` | Agent finished successfully (background and foreground) | `id`, `type`, `durationMs`, `tokens` (lifetime `{ input, output, total }`), `toolUses`, `result` |
|
|
422
|
+
| `subagents:failed` | Agent errored, stopped, or aborted (background and foreground) | same as completed + `error`, `status` |
|
|
423
423
|
| `subagents:steered` | Steering message sent | `id`, `message` |
|
|
424
424
|
| `subagents:compacted` | Agent's session successfully compacted | `id`, `type`, `description`, `reason` (`"manual"` / `"threshold"` / `"overflow"`), `tokensBefore`, `compactionCount` |
|
|
425
425
|
| `subagents:scheduled` | Schedule lifecycle change | `{ type: "added" \| "removed" \| "updated" \| "fired" \| "error", … }` (job/agentId/error fields per type) |
|
package/dist/agent-manager.d.ts
CHANGED
|
@@ -89,11 +89,24 @@ export declare class AgentManager {
|
|
|
89
89
|
private startAgent;
|
|
90
90
|
/** Start queued agents up to the concurrency limit. */
|
|
91
91
|
private drainQueue;
|
|
92
|
+
/**
|
|
93
|
+
* Called synchronously right after spawn, before onSessionCreated fires.
|
|
94
|
+
* Lets the caller set up the output file path on the record.
|
|
95
|
+
* The record is guaranteed to be in this.agents at this point.
|
|
96
|
+
*/
|
|
97
|
+
private onSpawned?;
|
|
92
98
|
/**
|
|
93
99
|
* Spawn an agent and wait for completion (foreground use).
|
|
94
100
|
* Foreground agents bypass the concurrency queue.
|
|
101
|
+
* Returns { id, record } so callers can access the agent ID.
|
|
102
|
+
*
|
|
103
|
+
* @param onSpawned - Called synchronously after spawn(), before onSessionCreated fires.
|
|
104
|
+
* Use this to set record.outputFile so streamToOutputFile can pick it up.
|
|
95
105
|
*/
|
|
96
|
-
spawnAndWait(pi: ExtensionAPI, ctx: ExtensionContext, type: SubagentType, prompt: string, options: Omit<SpawnOptions, "isBackground"
|
|
106
|
+
spawnAndWait(pi: ExtensionAPI, ctx: ExtensionContext, type: SubagentType, prompt: string, options: Omit<SpawnOptions, "isBackground">, onSpawned?: (id: string) => void): Promise<{
|
|
107
|
+
id: string;
|
|
108
|
+
record: AgentRecord;
|
|
109
|
+
}>;
|
|
97
110
|
/**
|
|
98
111
|
* Resume an existing agent session with a new prompt.
|
|
99
112
|
*/
|
|
@@ -107,8 +120,10 @@ export declare class AgentManager {
|
|
|
107
120
|
/**
|
|
108
121
|
* Remove all completed/stopped/errored records immediately.
|
|
109
122
|
* Called on session start/switch so tasks from a prior session don't persist.
|
|
123
|
+
* Pass skipUnconsumed=true to preserve records the LLM hasn't read yet
|
|
124
|
+
* (resultConsumed=false) — they will be evicted by the 10-minute cleanup timer instead.
|
|
110
125
|
*/
|
|
111
|
-
clearCompleted(): void;
|
|
126
|
+
clearCompleted(skipUnconsumed?: boolean): void;
|
|
112
127
|
/** Whether any agents are still running or queued. */
|
|
113
128
|
hasRunning(): boolean;
|
|
114
129
|
/** Abort all running and queued agents immediately. */
|
package/dist/agent-manager.js
CHANGED
|
@@ -225,7 +225,16 @@ export class AgentManager {
|
|
|
225
225
|
`\n\n---\nChanges saved to branch \`${wtResult.branch}\`${repoNote}. Merge with: \`git merge ${wtResult.branch}\`${customCwd !== undefined ? ` (run in \`${baseCwd}\`)` : ""}`;
|
|
226
226
|
}
|
|
227
227
|
}
|
|
228
|
-
|
|
228
|
+
// Fire onComplete for foreground agents too — lifecycle symmetry.
|
|
229
|
+
// Mark resultConsumed so the callback skips notifications (result returned inline).
|
|
230
|
+
if (!options.isBackground) {
|
|
231
|
+
record.resultConsumed = true;
|
|
232
|
+
try {
|
|
233
|
+
this.onComplete?.(record);
|
|
234
|
+
}
|
|
235
|
+
catch { /* ignore completion side-effect errors */ }
|
|
236
|
+
}
|
|
237
|
+
else {
|
|
229
238
|
this.runningBackground--;
|
|
230
239
|
try {
|
|
231
240
|
this.onComplete?.(record);
|
|
@@ -259,7 +268,13 @@ export class AgentManager {
|
|
|
259
268
|
}
|
|
260
269
|
catch { /* ignore cleanup errors */ }
|
|
261
270
|
}
|
|
262
|
-
|
|
271
|
+
// Fire onComplete for foreground agents too — lifecycle symmetry.
|
|
272
|
+
// Mark resultConsumed so the callback skips notifications (result returned inline).
|
|
273
|
+
if (!options.isBackground) {
|
|
274
|
+
record.resultConsumed = true;
|
|
275
|
+
this.onComplete?.(record);
|
|
276
|
+
}
|
|
277
|
+
else {
|
|
263
278
|
this.runningBackground--;
|
|
264
279
|
this.onComplete?.(record);
|
|
265
280
|
this.drainQueue();
|
|
@@ -267,6 +282,10 @@ export class AgentManager {
|
|
|
267
282
|
return "";
|
|
268
283
|
});
|
|
269
284
|
record.promise = promise;
|
|
285
|
+
// Notify caller that spawn is complete (record is in the map, promise is set).
|
|
286
|
+
// Called synchronously — onSessionCreated fires asynchronously inside runAgent.
|
|
287
|
+
// Used by spawnAndWait to let the caller set up output files before streaming starts.
|
|
288
|
+
this.onSpawned?.(id);
|
|
270
289
|
}
|
|
271
290
|
/** Start queued agents up to the concurrency limit. */
|
|
272
291
|
drainQueue() {
|
|
@@ -288,15 +307,33 @@ export class AgentManager {
|
|
|
288
307
|
}
|
|
289
308
|
}
|
|
290
309
|
}
|
|
310
|
+
/**
|
|
311
|
+
* Called synchronously right after spawn, before onSessionCreated fires.
|
|
312
|
+
* Lets the caller set up the output file path on the record.
|
|
313
|
+
* The record is guaranteed to be in this.agents at this point.
|
|
314
|
+
*/
|
|
315
|
+
onSpawned;
|
|
291
316
|
/**
|
|
292
317
|
* Spawn an agent and wait for completion (foreground use).
|
|
293
318
|
* Foreground agents bypass the concurrency queue.
|
|
319
|
+
* Returns { id, record } so callers can access the agent ID.
|
|
320
|
+
*
|
|
321
|
+
* @param onSpawned - Called synchronously after spawn(), before onSessionCreated fires.
|
|
322
|
+
* Use this to set record.outputFile so streamToOutputFile can pick it up.
|
|
294
323
|
*/
|
|
295
|
-
async spawnAndWait(pi, ctx, type, prompt, options) {
|
|
296
|
-
|
|
297
|
-
const
|
|
298
|
-
|
|
299
|
-
|
|
324
|
+
async spawnAndWait(pi, ctx, type, prompt, options, onSpawned) {
|
|
325
|
+
// Temporarily register the onSpawned hook so startAgent can call it.
|
|
326
|
+
const prevOnSpawned = this.onSpawned;
|
|
327
|
+
this.onSpawned = onSpawned;
|
|
328
|
+
try {
|
|
329
|
+
const id = this.spawn(pi, ctx, type, prompt, { ...options, isBackground: false });
|
|
330
|
+
const record = this.agents.get(id);
|
|
331
|
+
await record.promise;
|
|
332
|
+
return { id, record };
|
|
333
|
+
}
|
|
334
|
+
finally {
|
|
335
|
+
this.onSpawned = prevOnSpawned;
|
|
336
|
+
}
|
|
300
337
|
}
|
|
301
338
|
/**
|
|
302
339
|
* Resume an existing agent session with a new prompt.
|
|
@@ -379,11 +416,15 @@ export class AgentManager {
|
|
|
379
416
|
/**
|
|
380
417
|
* Remove all completed/stopped/errored records immediately.
|
|
381
418
|
* Called on session start/switch so tasks from a prior session don't persist.
|
|
419
|
+
* Pass skipUnconsumed=true to preserve records the LLM hasn't read yet
|
|
420
|
+
* (resultConsumed=false) — they will be evicted by the 10-minute cleanup timer instead.
|
|
382
421
|
*/
|
|
383
|
-
clearCompleted() {
|
|
422
|
+
clearCompleted(skipUnconsumed = false) {
|
|
384
423
|
for (const [id, record] of this.agents) {
|
|
385
424
|
if (record.status === "running" || record.status === "queued")
|
|
386
425
|
continue;
|
|
426
|
+
if (skipUnconsumed && !record.resultConsumed)
|
|
427
|
+
continue;
|
|
387
428
|
this.removeRecord(id, record);
|
|
388
429
|
}
|
|
389
430
|
}
|
package/dist/index.js
CHANGED
|
@@ -406,12 +406,12 @@ export default function (pi) {
|
|
|
406
406
|
// Capture ctx from session_start for RPC spawn handler + start the scheduler.
|
|
407
407
|
pi.on("session_start", async (_event, ctx) => {
|
|
408
408
|
currentCtx = ctx;
|
|
409
|
-
manager.clearCompleted();
|
|
409
|
+
manager.clearCompleted(true);
|
|
410
410
|
if (isSchedulingEnabled() && !scheduler.isActive())
|
|
411
411
|
startScheduler(ctx);
|
|
412
412
|
});
|
|
413
413
|
pi.on("session_before_switch", () => {
|
|
414
|
-
manager.clearCompleted();
|
|
414
|
+
manager.clearCompleted(true);
|
|
415
415
|
scheduler.stop();
|
|
416
416
|
});
|
|
417
417
|
const { unsubPing: unsubPingRpc, unsubSpawn: unsubSpawnRpc, unsubStop: unsubStopRpc } = registerRpcHandlers({
|
|
@@ -1065,7 +1065,9 @@ Terse command-style prompts produce shallow, generic work.
|
|
|
1065
1065
|
});
|
|
1066
1066
|
};
|
|
1067
1067
|
const { state: fgState, callbacks: fgCallbacks } = createActivityTracker(effectiveMaxTurns, streamUpdate);
|
|
1068
|
-
// Wire session creation
|
|
1068
|
+
// Wire session creation: register in widget + stream to output file.
|
|
1069
|
+
// The output file path is set synchronously after spawn (below),
|
|
1070
|
+
// before onSessionCreated fires — same pattern as background agents.
|
|
1069
1071
|
const origOnSession = fgCallbacks.onSessionCreated;
|
|
1070
1072
|
fgCallbacks.onSessionCreated = (session) => {
|
|
1071
1073
|
origOnSession(session);
|
|
@@ -1077,6 +1079,13 @@ Terse command-style prompts produce shallow, generic work.
|
|
|
1077
1079
|
break;
|
|
1078
1080
|
}
|
|
1079
1081
|
}
|
|
1082
|
+
// Stream conversation to output file (foreground agent logging)
|
|
1083
|
+
if (fgId) {
|
|
1084
|
+
const rec = manager.getRecord(fgId);
|
|
1085
|
+
if (rec?.outputFile) {
|
|
1086
|
+
rec.outputCleanup = streamToOutputFile(session, rec.outputFile, fgId, ctx.cwd);
|
|
1087
|
+
}
|
|
1088
|
+
}
|
|
1080
1089
|
};
|
|
1081
1090
|
// Animate spinner at ~80ms (smooth rotation through 10 braille frames)
|
|
1082
1091
|
const spinnerInterval = setInterval(() => {
|
|
@@ -1086,7 +1095,7 @@ Terse command-style prompts produce shallow, generic work.
|
|
|
1086
1095
|
streamUpdate();
|
|
1087
1096
|
let record;
|
|
1088
1097
|
try {
|
|
1089
|
-
|
|
1098
|
+
const fgResult = await manager.spawnAndWait(pi, ctx, subagentType, params.prompt, {
|
|
1090
1099
|
description: params.description,
|
|
1091
1100
|
model,
|
|
1092
1101
|
maxTurns: effectiveMaxTurns,
|
|
@@ -1097,7 +1106,16 @@ Terse command-style prompts produce shallow, generic work.
|
|
|
1097
1106
|
invocation: agentInvocation,
|
|
1098
1107
|
signal,
|
|
1099
1108
|
...fgCallbacks,
|
|
1109
|
+
}, (fgAgentId) => {
|
|
1110
|
+
// onSpawned: called synchronously after spawn, before onSessionCreated fires.
|
|
1111
|
+
// Set up the output file so streamToOutputFile can pick it up.
|
|
1112
|
+
const fgRec = manager.getRecord(fgAgentId);
|
|
1113
|
+
if (fgRec) {
|
|
1114
|
+
fgRec.outputFile = createOutputFilePath(ctx.cwd, fgAgentId, ctx.sessionManager.getSessionId());
|
|
1115
|
+
writeInitialEntry(fgRec.outputFile, fgAgentId, params.prompt, ctx.cwd);
|
|
1116
|
+
}
|
|
1100
1117
|
});
|
|
1118
|
+
record = fgResult.record;
|
|
1101
1119
|
}
|
|
1102
1120
|
catch (err) {
|
|
1103
1121
|
clearInterval(spinnerInterval);
|
|
@@ -1671,7 +1689,7 @@ Guidelines for choosing settings:
|
|
|
1671
1689
|
- Only include frontmatter fields that differ from defaults — omit fields where the default is fine
|
|
1672
1690
|
|
|
1673
1691
|
Write the file using the write tool. Only write the file, nothing else.`;
|
|
1674
|
-
const record = await manager.spawnAndWait(pi, ctx, "general-purpose", generatePrompt, {
|
|
1692
|
+
const { record } = await manager.spawnAndWait(pi, ctx, "general-purpose", generatePrompt, {
|
|
1675
1693
|
description: `Generate ${name} agent`,
|
|
1676
1694
|
maxTurns: 5,
|
|
1677
1695
|
});
|
package/package.json
CHANGED
package/src/agent-manager.ts
CHANGED
|
@@ -311,7 +311,12 @@ export class AgentManager {
|
|
|
311
311
|
}
|
|
312
312
|
}
|
|
313
313
|
|
|
314
|
-
|
|
314
|
+
// Fire onComplete for foreground agents too — lifecycle symmetry.
|
|
315
|
+
// Mark resultConsumed so the callback skips notifications (result returned inline).
|
|
316
|
+
if (!options.isBackground) {
|
|
317
|
+
record.resultConsumed = true;
|
|
318
|
+
try { this.onComplete?.(record); } catch { /* ignore completion side-effect errors */ }
|
|
319
|
+
} else {
|
|
315
320
|
this.runningBackground--;
|
|
316
321
|
try { this.onComplete?.(record); } catch { /* ignore completion side-effect errors */ }
|
|
317
322
|
this.drainQueue();
|
|
@@ -342,7 +347,12 @@ export class AgentManager {
|
|
|
342
347
|
} catch { /* ignore cleanup errors */ }
|
|
343
348
|
}
|
|
344
349
|
|
|
345
|
-
|
|
350
|
+
// Fire onComplete for foreground agents too — lifecycle symmetry.
|
|
351
|
+
// Mark resultConsumed so the callback skips notifications (result returned inline).
|
|
352
|
+
if (!options.isBackground) {
|
|
353
|
+
record.resultConsumed = true;
|
|
354
|
+
this.onComplete?.(record);
|
|
355
|
+
} else {
|
|
346
356
|
this.runningBackground--;
|
|
347
357
|
this.onComplete?.(record);
|
|
348
358
|
this.drainQueue();
|
|
@@ -351,6 +361,11 @@ export class AgentManager {
|
|
|
351
361
|
});
|
|
352
362
|
|
|
353
363
|
record.promise = promise;
|
|
364
|
+
|
|
365
|
+
// Notify caller that spawn is complete (record is in the map, promise is set).
|
|
366
|
+
// Called synchronously — onSessionCreated fires asynchronously inside runAgent.
|
|
367
|
+
// Used by spawnAndWait to let the caller set up output files before streaming starts.
|
|
368
|
+
this.onSpawned?.(id);
|
|
354
369
|
}
|
|
355
370
|
|
|
356
371
|
/** Start queued agents up to the concurrency limit. */
|
|
@@ -372,9 +387,20 @@ export class AgentManager {
|
|
|
372
387
|
}
|
|
373
388
|
}
|
|
374
389
|
|
|
390
|
+
/**
|
|
391
|
+
* Called synchronously right after spawn, before onSessionCreated fires.
|
|
392
|
+
* Lets the caller set up the output file path on the record.
|
|
393
|
+
* The record is guaranteed to be in this.agents at this point.
|
|
394
|
+
*/
|
|
395
|
+
private onSpawned?: (id: string) => void;
|
|
396
|
+
|
|
375
397
|
/**
|
|
376
398
|
* Spawn an agent and wait for completion (foreground use).
|
|
377
399
|
* Foreground agents bypass the concurrency queue.
|
|
400
|
+
* Returns { id, record } so callers can access the agent ID.
|
|
401
|
+
*
|
|
402
|
+
* @param onSpawned - Called synchronously after spawn(), before onSessionCreated fires.
|
|
403
|
+
* Use this to set record.outputFile so streamToOutputFile can pick it up.
|
|
378
404
|
*/
|
|
379
405
|
async spawnAndWait(
|
|
380
406
|
pi: ExtensionAPI,
|
|
@@ -382,11 +408,19 @@ export class AgentManager {
|
|
|
382
408
|
type: SubagentType,
|
|
383
409
|
prompt: string,
|
|
384
410
|
options: Omit<SpawnOptions, "isBackground">,
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
411
|
+
onSpawned?: (id: string) => void,
|
|
412
|
+
): Promise<{ id: string; record: AgentRecord }> {
|
|
413
|
+
// Temporarily register the onSpawned hook so startAgent can call it.
|
|
414
|
+
const prevOnSpawned = this.onSpawned;
|
|
415
|
+
this.onSpawned = onSpawned;
|
|
416
|
+
try {
|
|
417
|
+
const id = this.spawn(pi, ctx, type, prompt, { ...options, isBackground: false });
|
|
418
|
+
const record = this.agents.get(id)!;
|
|
419
|
+
await record.promise;
|
|
420
|
+
return { id, record };
|
|
421
|
+
} finally {
|
|
422
|
+
this.onSpawned = prevOnSpawned;
|
|
423
|
+
}
|
|
390
424
|
}
|
|
391
425
|
|
|
392
426
|
/**
|
|
@@ -480,10 +514,13 @@ export class AgentManager {
|
|
|
480
514
|
/**
|
|
481
515
|
* Remove all completed/stopped/errored records immediately.
|
|
482
516
|
* Called on session start/switch so tasks from a prior session don't persist.
|
|
517
|
+
* Pass skipUnconsumed=true to preserve records the LLM hasn't read yet
|
|
518
|
+
* (resultConsumed=false) — they will be evicted by the 10-minute cleanup timer instead.
|
|
483
519
|
*/
|
|
484
|
-
clearCompleted(): void {
|
|
520
|
+
clearCompleted(skipUnconsumed = false): void {
|
|
485
521
|
for (const [id, record] of this.agents) {
|
|
486
522
|
if (record.status === "running" || record.status === "queued") continue;
|
|
523
|
+
if (skipUnconsumed && !record.resultConsumed) continue;
|
|
487
524
|
this.removeRecord(id, record);
|
|
488
525
|
}
|
|
489
526
|
}
|
package/src/index.ts
CHANGED
|
@@ -458,12 +458,12 @@ export default function (pi: ExtensionAPI) {
|
|
|
458
458
|
// Capture ctx from session_start for RPC spawn handler + start the scheduler.
|
|
459
459
|
pi.on("session_start", async (_event, ctx) => {
|
|
460
460
|
currentCtx = ctx;
|
|
461
|
-
manager.clearCompleted();
|
|
461
|
+
manager.clearCompleted(true);
|
|
462
462
|
if (isSchedulingEnabled() && !scheduler.isActive()) startScheduler(ctx);
|
|
463
463
|
});
|
|
464
464
|
|
|
465
465
|
pi.on("session_before_switch", () => {
|
|
466
|
-
manager.clearCompleted();
|
|
466
|
+
manager.clearCompleted(true);
|
|
467
467
|
scheduler.stop();
|
|
468
468
|
});
|
|
469
469
|
|
|
@@ -1200,7 +1200,9 @@ Terse command-style prompts produce shallow, generic work.
|
|
|
1200
1200
|
|
|
1201
1201
|
const { state: fgState, callbacks: fgCallbacks } = createActivityTracker(effectiveMaxTurns, streamUpdate);
|
|
1202
1202
|
|
|
1203
|
-
// Wire session creation
|
|
1203
|
+
// Wire session creation: register in widget + stream to output file.
|
|
1204
|
+
// The output file path is set synchronously after spawn (below),
|
|
1205
|
+
// before onSessionCreated fires — same pattern as background agents.
|
|
1204
1206
|
const origOnSession = fgCallbacks.onSessionCreated;
|
|
1205
1207
|
fgCallbacks.onSessionCreated = (session: any) => {
|
|
1206
1208
|
origOnSession(session);
|
|
@@ -1212,6 +1214,13 @@ Terse command-style prompts produce shallow, generic work.
|
|
|
1212
1214
|
break;
|
|
1213
1215
|
}
|
|
1214
1216
|
}
|
|
1217
|
+
// Stream conversation to output file (foreground agent logging)
|
|
1218
|
+
if (fgId) {
|
|
1219
|
+
const rec = manager.getRecord(fgId);
|
|
1220
|
+
if (rec?.outputFile) {
|
|
1221
|
+
rec.outputCleanup = streamToOutputFile(session, rec.outputFile, fgId, ctx.cwd);
|
|
1222
|
+
}
|
|
1223
|
+
}
|
|
1215
1224
|
};
|
|
1216
1225
|
|
|
1217
1226
|
// Animate spinner at ~80ms (smooth rotation through 10 braille frames)
|
|
@@ -1224,7 +1233,7 @@ Terse command-style prompts produce shallow, generic work.
|
|
|
1224
1233
|
|
|
1225
1234
|
let record: AgentRecord;
|
|
1226
1235
|
try {
|
|
1227
|
-
|
|
1236
|
+
const fgResult = await manager.spawnAndWait(pi, ctx, subagentType, params.prompt, {
|
|
1228
1237
|
description: params.description,
|
|
1229
1238
|
model,
|
|
1230
1239
|
maxTurns: effectiveMaxTurns,
|
|
@@ -1235,7 +1244,16 @@ Terse command-style prompts produce shallow, generic work.
|
|
|
1235
1244
|
invocation: agentInvocation,
|
|
1236
1245
|
signal,
|
|
1237
1246
|
...fgCallbacks,
|
|
1247
|
+
}, (fgAgentId) => {
|
|
1248
|
+
// onSpawned: called synchronously after spawn, before onSessionCreated fires.
|
|
1249
|
+
// Set up the output file so streamToOutputFile can pick it up.
|
|
1250
|
+
const fgRec = manager.getRecord(fgAgentId);
|
|
1251
|
+
if (fgRec) {
|
|
1252
|
+
fgRec.outputFile = createOutputFilePath(ctx.cwd, fgAgentId, ctx.sessionManager.getSessionId());
|
|
1253
|
+
writeInitialEntry(fgRec.outputFile, fgAgentId, params.prompt, ctx.cwd);
|
|
1254
|
+
}
|
|
1238
1255
|
});
|
|
1256
|
+
record = fgResult.record;
|
|
1239
1257
|
} catch (err) {
|
|
1240
1258
|
clearInterval(spinnerInterval);
|
|
1241
1259
|
return textResult(err instanceof Error ? err.message : String(err));
|
|
@@ -1838,7 +1856,7 @@ Guidelines for choosing settings:
|
|
|
1838
1856
|
|
|
1839
1857
|
Write the file using the write tool. Only write the file, nothing else.`;
|
|
1840
1858
|
|
|
1841
|
-
const record = await manager.spawnAndWait(pi, ctx, "general-purpose", generatePrompt, {
|
|
1859
|
+
const { record } = await manager.spawnAndWait(pi, ctx, "general-purpose", generatePrompt, {
|
|
1842
1860
|
description: `Generate ${name} agent`,
|
|
1843
1861
|
maxTurns: 5,
|
|
1844
1862
|
});
|