pi-subagents 0.9.2 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +33 -0
- package/README.md +56 -6
- package/async-execution.ts +64 -30
- package/chain-clarify.ts +16 -4
- package/chain-execution.ts +31 -1
- package/execution.ts +16 -1
- package/index.ts +234 -25
- package/package.json +11 -2
- package/parallel-utils.ts +93 -0
- package/render.ts +78 -8
- package/schemas.ts +1 -1
- package/settings.ts +16 -14
- package/skills.ts +25 -1
- package/subagent-runner.ts +360 -176
- package/utils.ts +23 -7
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,39 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [0.11.0] - 2026-02-23
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
- **Background mode toggle in clarify TUI**: Press `b` to toggle background/async execution for any mode (single, parallel, chain). Shows `[b]g:ON` in footer when enabled. Previously async execution required programmatic `clarify: false, async: true` — now users can interactively choose background mode after previewing/editing parameters.
|
|
9
|
+
- **`--bg` flag for slash commands**: `/run scout "task" --bg`, `/chain scout "task" -> planner --bg`, `/parallel scout "a" -> scout "b" --bg` now run in background without needing the TUI.
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
- Task edits in clarify TUI were lost when launching in background mode if no other behavior (model, output, reads) was modified. The async handoff now always applies the edited template.
|
|
13
|
+
|
|
14
|
+
## [0.10.0] - 2026-02-23
|
|
15
|
+
|
|
16
|
+
### Added
|
|
17
|
+
- **Async parallel chain support**: Chains with `{ parallel: [...] }` steps now work in async mode. Previously they were rejected with "Async mode doesn't support chains with parallel steps." The async runner now spawns concurrent pi processes for parallel step groups with configurable `concurrency` and `failFast` options. Inspired by PR #31 from @marcfargas.
|
|
18
|
+
- **Comprehensive test suite**: 85 integration tests and 12 E2E tests covering all execution modes (single, parallel, chain, async), error handling, template resolution, and tool validation. Uses `@marcfargas/pi-test-harness` for subprocess mocking and in-process session testing. Thanks @marcfargas for PR #32.
|
|
19
|
+
- GitHub Actions CI workflow running tests on both Ubuntu and Windows with Node.js 24.
|
|
20
|
+
|
|
21
|
+
### Changed
|
|
22
|
+
- **BREAKING:** `share` parameter now defaults to `false`. Previously, sessions were silently uploaded to GitHub Gists without user consent. Users who want session sharing must now explicitly pass `share: true`. Added documentation explaining what the feature does and its privacy implications.
|
|
23
|
+
|
|
24
|
+
### Fixed
|
|
25
|
+
- `mapConcurrent` with `limit=0` returned array of undefined values instead of processing items sequentially. Now clamps limit to at least 1.
|
|
26
|
+
- ANSI background color bleed in truncated text. The `truncLine` function now properly tracks and re-applies all active ANSI styles (bold, colors, etc.) before the ellipsis, preventing style leakage. Also uses `Intl.Segmenter` for correct Unicode/emoji handling. Thanks @monotykamary for identifying the issue.
|
|
27
|
+
- `detectSubagentError` no longer produces false positives when the agent recovers from tool errors. Previously, any error in the last tool result would override exitCode 0→1, even if the agent had already produced complete output. Now only errors AFTER the agent's final text response are flagged. Thanks @marcfargas for the fix and comprehensive test coverage.
|
|
28
|
+
- Parallel mode (`tasks: [...]`) now returns aggregated output from all tasks instead of just a success count. Previously only returned "3/3 succeeded" with actual task outputs lost.
|
|
29
|
+
- Session sharing fallback no longer fails with `ERR_PACKAGE_PATH_NOT_EXPORTED`. The fallback now resolves the main entry point and walks up to find the package root instead of trying to resolve `package.json` directly.
|
|
30
|
+
- Skills from globally-installed npm packages (via `pi install npm:...`) are now discoverable by subagents. Previously only scanned local `.pi/npm/node_modules/` paths, missing the global npm root where pi actually installs packages.
|
|
31
|
+
- **Windows compatibility**: Fixed `ENAMETOOLONG` errors when tasks exceed command-line length limits by writing long tasks to temp files using pi's `@file` syntax. Thanks @marcfargas.
|
|
32
|
+
- **Windows compatibility**: Suppressed flashing console windows when spawning async runner processes (`windowsHide: true`).
|
|
33
|
+
- **Windows compatibility**: Fixed pi CLI resolution in async runner by passing `piPackageRoot` through to `getPiSpawnCommand`.
|
|
34
|
+
- **Cross-platform paths**: Replaced `startsWith("/")` checks with `path.isAbsolute()` for correct Windows absolute path detection. Replaced template string path concatenation with `path.join()` for consistent path separators.
|
|
35
|
+
- **Resilience**: Added error handling and auto-restart for the results directory watcher. Previously, if the directory was deleted or became inaccessible, the watcher would die silently.
|
|
36
|
+
- **Resilience**: Added `ensureAccessibleDir` helper that verifies directory accessibility after creation and attempts recovery if the directory has broken ACLs (can happen on Windows with Azure AD/Entra ID after wake-from-sleep).
|
|
37
|
+
|
|
5
38
|
## [0.9.2] - 2026-02-19
|
|
6
39
|
|
|
7
40
|
### Fixed
|
package/README.md
CHANGED
|
@@ -162,6 +162,18 @@ Append `[key=value,...]` to any agent name to override its defaults:
|
|
|
162
162
|
|
|
163
163
|
Set `output=false`, `reads=false`, or `skills=false` to explicitly disable.
|
|
164
164
|
|
|
165
|
+
### Background Execution
|
|
166
|
+
|
|
167
|
+
Add `--bg` at the end of any slash command to run in the background:
|
|
168
|
+
|
|
169
|
+
```
|
|
170
|
+
/run scout "full security audit of the codebase" --bg
|
|
171
|
+
/chain scout "analyze auth system" -> planner "design refactor plan" -> worker --bg
|
|
172
|
+
/parallel scout "scan frontend" -> scout "scan backend" -> scout "scan infra" --bg
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
Background tasks run asynchronously and notify you when complete. Check status with `subagent_status`.
|
|
176
|
+
|
|
165
177
|
## Agents Manager
|
|
166
178
|
|
|
167
179
|
Press **Ctrl+Shift+A** or type `/agents` to open the Agents Manager overlay — a TUI for browsing, viewing, editing, creating, and launching agents and chains.
|
|
@@ -270,10 +282,10 @@ Chains can be created from the Agents Manager template picker ("Blank Chain"), o
|
|
|
270
282
|
| Mode | Async Support | Notes |
|
|
271
283
|
|------|---------------|-------|
|
|
272
284
|
| Single | Yes | `{ agent, task }` - agents with `output` write to temp dir |
|
|
273
|
-
| Chain | Yes
|
|
274
|
-
| Parallel |
|
|
285
|
+
| Chain | Yes | `{ chain: [{agent, task}...] }` with `{task}`, `{previous}`, `{chain_dir}` variables |
|
|
286
|
+
| Parallel | Yes | `{ tasks: [{agent, task}...] }` - via TUI toggle or converted to chain for async |
|
|
275
287
|
|
|
276
|
-
|
|
288
|
+
All modes support background/async execution. For programmatic async, use `clarify: false, async: true`. For interactive async, use `clarify: true` and press `b` in the TUI to toggle background mode before running. Chains with parallel steps (`{ parallel: [...] }`) run concurrently with configurable `concurrency` and `failFast` options.
|
|
277
289
|
|
|
278
290
|
**Clarify TUI for single/parallel:**
|
|
279
291
|
|
|
@@ -290,13 +302,14 @@ Single and parallel modes also support the clarify TUI for previewing/editing pa
|
|
|
290
302
|
**Clarification TUI keybindings:**
|
|
291
303
|
|
|
292
304
|
*Navigation mode:*
|
|
293
|
-
- `Enter` - Run
|
|
305
|
+
- `Enter` - Run (foreground) or launch in background if `b` is toggled on
|
|
294
306
|
- `Esc` - Cancel
|
|
295
307
|
- `↑↓` - Navigate between steps/tasks (parallel, chain)
|
|
296
308
|
- `e` - Edit task/template (all modes)
|
|
297
309
|
- `m` - Select model (all modes)
|
|
298
310
|
- `t` - Select thinking level (all modes)
|
|
299
311
|
- `s` - Select skills (all modes)
|
|
312
|
+
- `b` - Toggle background/async execution (all modes) — shows `[b]g:ON` when enabled
|
|
300
313
|
- `w` - Edit writes/output file (single, chain only)
|
|
301
314
|
- `r` - Edit reads list (chain only)
|
|
302
315
|
- `p` - Toggle progress tracking (chain only)
|
|
@@ -423,6 +436,16 @@ Skills are specialized instructions loaded from SKILL.md files and injected into
|
|
|
423
436
|
{ agent: "worker", task: "Refactor module C" }
|
|
424
437
|
], concurrency: 2, failFast: true } // limit concurrency, stop on first failure
|
|
425
438
|
]}
|
|
439
|
+
|
|
440
|
+
// Async chain with parallel step (runs in background)
|
|
441
|
+
{ chain: [
|
|
442
|
+
{ agent: "scout", task: "Gather context" },
|
|
443
|
+
{ parallel: [
|
|
444
|
+
{ agent: "worker", task: "Implement feature A based on {previous}" },
|
|
445
|
+
{ agent: "worker", task: "Implement feature B based on {previous}" }
|
|
446
|
+
]},
|
|
447
|
+
{ agent: "reviewer", task: "Review all changes from {previous}" }
|
|
448
|
+
], clarify: false, async: true }
|
|
426
449
|
```
|
|
427
450
|
|
|
428
451
|
**subagent_status tool:**
|
|
@@ -514,7 +537,7 @@ Notes:
|
|
|
514
537
|
| `maxOutput` | `{bytes?, lines?}` | 200KB, 5000 lines | Truncation limits for final output |
|
|
515
538
|
| `artifacts` | boolean | true | Write debug artifacts |
|
|
516
539
|
| `includeProgress` | boolean | false | Include full progress in result |
|
|
517
|
-
| `share` | boolean |
|
|
540
|
+
| `share` | boolean | false | Upload session to GitHub Gist (see [Session Sharing](#session-sharing)) |
|
|
518
541
|
| `sessionDir` | string | temp | Directory to store session logs |
|
|
519
542
|
|
|
520
543
|
**ChainItem** can be either a sequential step or a parallel step:
|
|
@@ -608,6 +631,25 @@ Files per task:
|
|
|
608
631
|
|
|
609
632
|
Session files (JSONL) are stored under a per-run session dir (temp by default). The session file path is shown in output. Set `sessionDir` to keep session logs outside `<tmpdir>`.
|
|
610
633
|
|
|
634
|
+
## Session Sharing
|
|
635
|
+
|
|
636
|
+
When `share: true` is passed, the extension will:
|
|
637
|
+
|
|
638
|
+
1. Export the full session (all tool calls, file contents, outputs) to an HTML file
|
|
639
|
+
2. Upload it to a GitHub Gist using your `gh` CLI credentials
|
|
640
|
+
3. Return a shareable URL (`https://shittycodingagent.ai/session/?<gistId>`)
|
|
641
|
+
|
|
642
|
+
**This is disabled by default.** Session data may contain sensitive information like source code, file paths, environment variables, or credentials that appear in tool outputs.
|
|
643
|
+
|
|
644
|
+
To enable sharing for a specific run:
|
|
645
|
+
```typescript
|
|
646
|
+
{ agent: "scout", task: "...", share: true }
|
|
647
|
+
```
|
|
648
|
+
|
|
649
|
+
Requirements:
|
|
650
|
+
- GitHub CLI (`gh`) must be installed and authenticated (`gh auth login`)
|
|
651
|
+
- Gists are created as "secret" (unlisted but accessible to anyone with the URL)
|
|
652
|
+
|
|
611
653
|
## Live progress (sync mode)
|
|
612
654
|
|
|
613
655
|
During sync execution, the collapsed view shows real-time progress for single, chain, and parallel modes.
|
|
@@ -686,10 +728,16 @@ Async events:
|
|
|
686
728
|
├── artifacts.ts # Artifact management
|
|
687
729
|
├── formatters.ts # Output formatting utilities
|
|
688
730
|
├── schemas.ts # TypeBox parameter schemas
|
|
689
|
-
├── utils.ts # Shared utility functions
|
|
731
|
+
├── utils.ts # Shared utility functions (mapConcurrent, readStatus, etc.)
|
|
690
732
|
├── types.ts # Shared types and constants
|
|
691
733
|
├── subagent-runner.ts # Async runner (detached process)
|
|
734
|
+
├── parallel-utils.ts # Parallel execution utilities for async runner
|
|
735
|
+
├── pi-spawn.ts # Cross-platform pi CLI spawning
|
|
736
|
+
├── single-output.ts # Solo agent output file handling
|
|
692
737
|
├── notify.ts # Async completion notifications
|
|
738
|
+
├── completion-dedupe.ts # Completion deduplication for notifications
|
|
739
|
+
├── file-coalescer.ts # Debounced file write coalescing
|
|
740
|
+
├── jsonl-writer.ts # JSONL event stream writer
|
|
693
741
|
├── agent-manager.ts # Overlay orchestrator, screen routing, CRUD
|
|
694
742
|
├── agent-manager-list.ts # List screen (search, multi-select, progressive footer)
|
|
695
743
|
├── agent-manager-detail.ts # Detail screen (resolved prompt, runs, fields)
|
|
@@ -698,6 +746,8 @@ Async events:
|
|
|
698
746
|
├── agent-manager-chain-detail.ts # Chain detail screen (flow visualization)
|
|
699
747
|
├── agent-management.ts # Management action handlers (list, get, create, update, delete)
|
|
700
748
|
├── agent-serializer.ts # Serialize agents to markdown frontmatter
|
|
749
|
+
├── agent-scope.ts # Agent scope resolution utilities
|
|
750
|
+
├── agent-selection.ts # Agent selection state management
|
|
701
751
|
├── agent-templates.ts # Agent/chain creation templates
|
|
702
752
|
├── render-helpers.ts # Shared pad/row/header/footer helpers
|
|
703
753
|
├── run-history.ts # Per-agent run recording (JSONL)
|
package/async-execution.ts
CHANGED
|
@@ -12,7 +12,8 @@ import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|
|
12
12
|
import type { AgentConfig } from "./agents.js";
|
|
13
13
|
import { applyThinkingSuffix } from "./execution.js";
|
|
14
14
|
import { injectSingleOutputInstruction, resolveSingleOutputPath } from "./single-output.js";
|
|
15
|
-
import { isParallelStep, resolveStepBehavior, type ChainStep, type SequentialStep, type StepOverrides } from "./settings.js";
|
|
15
|
+
import { isParallelStep, resolveStepBehavior, type ChainStep, type ParallelStep, type SequentialStep, type StepOverrides } from "./settings.js";
|
|
16
|
+
import type { RunnerStep } from "./parallel-utils.js";
|
|
16
17
|
import { resolvePiPackageRoot } from "./pi-spawn.js";
|
|
17
18
|
import { buildSkillInjection, normalizeSkillInput, resolveSkills } from "./skills.js";
|
|
18
19
|
import {
|
|
@@ -105,6 +106,7 @@ function spawnRunner(cfg: object, suffix: string, cwd: string): number | undefin
|
|
|
105
106
|
cwd,
|
|
106
107
|
detached: true,
|
|
107
108
|
stdio: "ignore",
|
|
109
|
+
windowsHide: true,
|
|
108
110
|
});
|
|
109
111
|
proc.unref();
|
|
110
112
|
return proc.pid;
|
|
@@ -119,28 +121,20 @@ export function executeAsyncChain(
|
|
|
119
121
|
): AsyncExecutionResult {
|
|
120
122
|
const { chain, agents, ctx, cwd, maxOutput, artifactsDir, artifactConfig, shareEnabled, sessionRoot } = params;
|
|
121
123
|
const chainSkills = params.chainSkills ?? [];
|
|
122
|
-
|
|
123
|
-
// Async mode doesn't support parallel steps (v1 limitation)
|
|
124
|
-
const hasParallelInChain = chain.some(isParallelStep);
|
|
125
|
-
if (hasParallelInChain) {
|
|
126
|
-
return {
|
|
127
|
-
content: [{ type: "text", text: "Async mode doesn't support chains with parallel steps. Use clarify: true (sync mode) for parallel-in-chain." }],
|
|
128
|
-
isError: true,
|
|
129
|
-
details: { mode: "chain" as const, results: [] },
|
|
130
|
-
};
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
// At this point, all steps are sequential
|
|
134
|
-
const seqSteps = chain as SequentialStep[];
|
|
135
124
|
|
|
136
125
|
// Validate all agents exist before building steps
|
|
137
|
-
for (const s of
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
126
|
+
for (const s of chain) {
|
|
127
|
+
const stepAgents = isParallelStep(s)
|
|
128
|
+
? s.parallel.map((t) => t.agent)
|
|
129
|
+
: [(s as SequentialStep).agent];
|
|
130
|
+
for (const agentName of stepAgents) {
|
|
131
|
+
if (!agents.find((x) => x.name === agentName)) {
|
|
132
|
+
return {
|
|
133
|
+
content: [{ type: "text", text: `Unknown agent: ${agentName}` }],
|
|
134
|
+
isError: true,
|
|
135
|
+
details: { mode: "chain" as const, results: [] },
|
|
136
|
+
};
|
|
137
|
+
}
|
|
144
138
|
}
|
|
145
139
|
}
|
|
146
140
|
|
|
@@ -149,7 +143,8 @@ export function executeAsyncChain(
|
|
|
149
143
|
fs.mkdirSync(asyncDir, { recursive: true });
|
|
150
144
|
} catch {}
|
|
151
145
|
|
|
152
|
-
|
|
146
|
+
/** Build a resolved runner step from a SequentialStep */
|
|
147
|
+
const buildSeqStep = (s: SequentialStep) => {
|
|
153
148
|
const a = agents.find((x) => x.name === s.agent)!;
|
|
154
149
|
const stepSkillInput = normalizeSkillInput(s.skill);
|
|
155
150
|
const stepOverrides: StepOverrides = { skills: stepSkillInput };
|
|
@@ -162,9 +157,15 @@ export function executeAsyncChain(
|
|
|
162
157
|
const injection = buildSkillInjection(resolvedSkills);
|
|
163
158
|
systemPrompt = systemPrompt ? `${systemPrompt}\n\n${injection}` : injection;
|
|
164
159
|
}
|
|
160
|
+
|
|
161
|
+
// Resolve output path and inject instruction into task
|
|
162
|
+
// Use step's cwd if specified, otherwise fall back to chain-level cwd
|
|
163
|
+
const outputPath = resolveSingleOutputPath(s.output, ctx.cwd, s.cwd ?? cwd);
|
|
164
|
+
const task = injectSingleOutputInstruction(s.task ?? "{previous}", outputPath);
|
|
165
|
+
|
|
165
166
|
return {
|
|
166
167
|
agent: s.agent,
|
|
167
|
-
task
|
|
168
|
+
task,
|
|
168
169
|
cwd: s.cwd,
|
|
169
170
|
model: applyThinkingSuffix(s.model ?? a.model, a.thinking),
|
|
170
171
|
tools: a.tools,
|
|
@@ -172,7 +173,28 @@ export function executeAsyncChain(
|
|
|
172
173
|
mcpDirectTools: a.mcpDirectTools,
|
|
173
174
|
systemPrompt,
|
|
174
175
|
skills: resolvedSkills.map((r) => r.name),
|
|
176
|
+
outputPath,
|
|
175
177
|
};
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
// Build runner steps — sequential steps become flat objects,
|
|
181
|
+
// parallel steps become { parallel: [...], concurrency?, failFast? }
|
|
182
|
+
const steps: RunnerStep[] = chain.map((s) => {
|
|
183
|
+
if (isParallelStep(s)) {
|
|
184
|
+
return {
|
|
185
|
+
parallel: s.parallel.map((t) => buildSeqStep({
|
|
186
|
+
agent: t.agent,
|
|
187
|
+
task: t.task,
|
|
188
|
+
cwd: t.cwd,
|
|
189
|
+
skill: t.skill,
|
|
190
|
+
model: t.model,
|
|
191
|
+
output: t.output,
|
|
192
|
+
})),
|
|
193
|
+
concurrency: s.concurrency,
|
|
194
|
+
failFast: s.failFast,
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
return buildSeqStep(s as SequentialStep);
|
|
176
198
|
});
|
|
177
199
|
|
|
178
200
|
const runnerCwd = cwd ?? ctx.cwd;
|
|
@@ -197,22 +219,34 @@ export function executeAsyncChain(
|
|
|
197
219
|
);
|
|
198
220
|
|
|
199
221
|
if (pid) {
|
|
200
|
-
const
|
|
222
|
+
const firstStep = chain[0];
|
|
223
|
+
const firstAgents = isParallelStep(firstStep)
|
|
224
|
+
? firstStep.parallel.map((t) => t.agent)
|
|
225
|
+
: [(firstStep as SequentialStep).agent];
|
|
201
226
|
ctx.pi.events.emit("subagent:started", {
|
|
202
227
|
id,
|
|
203
228
|
pid,
|
|
204
|
-
agent:
|
|
205
|
-
task:
|
|
206
|
-
|
|
229
|
+
agent: firstAgents[0],
|
|
230
|
+
task: isParallelStep(firstStep)
|
|
231
|
+
? firstStep.parallel[0]?.task?.slice(0, 50)
|
|
232
|
+
: (firstStep as SequentialStep).task?.slice(0, 50),
|
|
233
|
+
chain: chain.map((s) =>
|
|
234
|
+
isParallelStep(s) ? `[${s.parallel.map((t) => t.agent).join("+")}]` : (s as SequentialStep).agent,
|
|
235
|
+
),
|
|
207
236
|
cwd: runnerCwd,
|
|
208
237
|
asyncDir,
|
|
209
238
|
});
|
|
210
239
|
}
|
|
211
240
|
|
|
241
|
+
// Build chain description with parallel groups shown as [agent1+agent2]
|
|
242
|
+
const chainDesc = chain
|
|
243
|
+
.map((s) =>
|
|
244
|
+
isParallelStep(s) ? `[${s.parallel.map((t) => t.agent).join("+")}]` : (s as SequentialStep).agent,
|
|
245
|
+
)
|
|
246
|
+
.join(" -> ");
|
|
247
|
+
|
|
212
248
|
return {
|
|
213
|
-
content: [
|
|
214
|
-
{ type: "text", text: `Async chain: ${chain.map((s) => (s as SequentialStep).agent).join(" -> ")} [${id}]` },
|
|
215
|
-
],
|
|
249
|
+
content: [{ type: "text", text: `Async chain: ${chainDesc} [${id}]` }],
|
|
216
250
|
details: { mode: "chain", results: [], asyncId: id, asyncDir },
|
|
217
251
|
};
|
|
218
252
|
}
|
package/chain-clarify.ts
CHANGED
|
@@ -42,6 +42,8 @@ export interface ChainClarifyResult {
|
|
|
42
42
|
templates: string[];
|
|
43
43
|
/** User-modified behavior overrides per step (undefined = no changes) */
|
|
44
44
|
behaviorOverrides: (BehaviorOverride | undefined)[];
|
|
45
|
+
/** User requested background/async execution */
|
|
46
|
+
runInBackground?: boolean;
|
|
45
47
|
}
|
|
46
48
|
|
|
47
49
|
type EditMode = "template" | "output" | "reads" | "model" | "thinking" | "skills";
|
|
@@ -88,6 +90,8 @@ export class ChainClarifyComponent implements Component {
|
|
|
88
90
|
private saveMessageTimer: ReturnType<typeof setTimeout> | null = null;
|
|
89
91
|
private saveChainNameState: TextEditorState = createEditorState();
|
|
90
92
|
private savingChain = false;
|
|
93
|
+
/** Run in background (async) mode */
|
|
94
|
+
private runInBackground = false;
|
|
91
95
|
|
|
92
96
|
constructor(
|
|
93
97
|
private tui: TUI,
|
|
@@ -463,7 +467,7 @@ export class ChainClarifyComponent implements Component {
|
|
|
463
467
|
for (let i = 0; i < this.agentConfigs.length; i++) {
|
|
464
468
|
overrides.push(this.behaviorOverrides.get(i));
|
|
465
469
|
}
|
|
466
|
-
this.done({ confirmed: true, templates: this.templates, behaviorOverrides: overrides });
|
|
470
|
+
this.done({ confirmed: true, templates: this.templates, behaviorOverrides: overrides, runInBackground: this.runInBackground });
|
|
467
471
|
return;
|
|
468
472
|
}
|
|
469
473
|
|
|
@@ -539,6 +543,13 @@ export class ChainClarifyComponent implements Component {
|
|
|
539
543
|
return;
|
|
540
544
|
}
|
|
541
545
|
|
|
546
|
+
// 'b' to toggle background/async execution (all modes)
|
|
547
|
+
if (data === "b") {
|
|
548
|
+
this.runInBackground = !this.runInBackground;
|
|
549
|
+
this.tui.requestRender();
|
|
550
|
+
return;
|
|
551
|
+
}
|
|
552
|
+
|
|
542
553
|
if (data === "S") {
|
|
543
554
|
this.saveOverridesToAgent();
|
|
544
555
|
return;
|
|
@@ -1179,13 +1190,14 @@ export class ChainClarifyComponent implements Component {
|
|
|
1179
1190
|
|
|
1180
1191
|
/** Get footer text based on mode */
|
|
1181
1192
|
private getFooterText(): string {
|
|
1193
|
+
const bgLabel = this.runInBackground ? '[b]g:ON' : '[b]g';
|
|
1182
1194
|
switch (this.mode) {
|
|
1183
1195
|
case 'single':
|
|
1184
|
-
return
|
|
1196
|
+
return ` [Enter] Run • [Esc] Cancel • e m t w s ${bgLabel} S `;
|
|
1185
1197
|
case 'parallel':
|
|
1186
|
-
return
|
|
1198
|
+
return ` [Enter] Run • [Esc] Cancel • e m t s ${bgLabel} S • ↑↓ Nav `;
|
|
1187
1199
|
case 'chain':
|
|
1188
|
-
return
|
|
1200
|
+
return ` [Enter] Run • [Esc] Cancel • e m t w r p s ${bgLabel} S W • ↑↓ Nav `;
|
|
1189
1201
|
}
|
|
1190
1202
|
}
|
|
1191
1203
|
|
package/chain-execution.ts
CHANGED
|
@@ -83,6 +83,11 @@ export interface ChainExecutionResult {
|
|
|
83
83
|
content: Array<{ type: "text"; text: string }>;
|
|
84
84
|
details: Details;
|
|
85
85
|
isError?: boolean;
|
|
86
|
+
/** User requested async execution via TUI - caller should dispatch to executeAsyncChain */
|
|
87
|
+
requestedAsync?: {
|
|
88
|
+
chain: ChainStep[];
|
|
89
|
+
chainSkills: string[];
|
|
90
|
+
};
|
|
86
91
|
}
|
|
87
92
|
|
|
88
93
|
/**
|
|
@@ -210,6 +215,31 @@ export async function executeChain(params: ChainExecutionParams): Promise<ChainE
|
|
|
210
215
|
details: { mode: "chain", results: [] },
|
|
211
216
|
};
|
|
212
217
|
}
|
|
218
|
+
|
|
219
|
+
// User requested background execution - return early so caller can dispatch to async
|
|
220
|
+
if (result.runInBackground) {
|
|
221
|
+
removeChainDir(chainDir); // Will be recreated by async runner
|
|
222
|
+
// Apply TUI edits (templates + behavior overrides) to chain steps
|
|
223
|
+
const updatedChain = chainSteps.map((step, i) => {
|
|
224
|
+
if (isParallelStep(step)) return step; // Parallel steps unchanged (TUI skipped for parallel chains)
|
|
225
|
+
const override = result.behaviorOverrides[i];
|
|
226
|
+
return {
|
|
227
|
+
...step,
|
|
228
|
+
task: result.templates[i] as string, // Always use edited template
|
|
229
|
+
...(override?.model ? { model: override.model } : {}),
|
|
230
|
+
...(override?.output !== undefined ? { output: override.output } : {}),
|
|
231
|
+
...(override?.reads !== undefined ? { reads: override.reads } : {}),
|
|
232
|
+
...(override?.progress !== undefined ? { progress: override.progress } : {}),
|
|
233
|
+
...(override?.skills !== undefined ? { skill: override.skills } : {}),
|
|
234
|
+
};
|
|
235
|
+
});
|
|
236
|
+
return {
|
|
237
|
+
content: [{ type: "text", text: "Launching in background..." }],
|
|
238
|
+
details: { mode: "chain", results: [] },
|
|
239
|
+
requestedAsync: { chain: updatedChain as ChainStep[], chainSkills },
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
|
|
213
243
|
// Update templates from TUI result
|
|
214
244
|
templates = result.templates;
|
|
215
245
|
// Store behavior overrides from TUI (used below in sequential step execution)
|
|
@@ -493,7 +523,7 @@ export async function executeChain(params: ChainExecutionParams): Promise<ChainE
|
|
|
493
523
|
// Validate expected output file was created
|
|
494
524
|
if (behavior.output && r.exitCode === 0) {
|
|
495
525
|
try {
|
|
496
|
-
const expectedPath = behavior.output
|
|
526
|
+
const expectedPath = path.isAbsolute(behavior.output)
|
|
497
527
|
? behavior.output
|
|
498
528
|
: path.join(chainDir, behavior.output);
|
|
499
529
|
if (!fs.existsSync(expectedPath)) {
|
package/execution.ts
CHANGED
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
|
|
5
5
|
import { spawn } from "node:child_process";
|
|
6
6
|
import * as fs from "node:fs";
|
|
7
|
+
import * as os from "node:os";
|
|
8
|
+
import * as path from "node:path";
|
|
7
9
|
import type { Message } from "@mariozechner/pi-ai";
|
|
8
10
|
import type { AgentConfig } from "./agents.js";
|
|
9
11
|
import {
|
|
@@ -123,7 +125,20 @@ export async function runSync(
|
|
|
123
125
|
tmpDir = tmp.dir;
|
|
124
126
|
args.push("--append-system-prompt", tmp.path);
|
|
125
127
|
}
|
|
126
|
-
|
|
128
|
+
|
|
129
|
+
// When the task is too long for a CLI argument (Windows ENAMETOOLONG),
|
|
130
|
+
// write it to a temp file and use pi's @file syntax instead.
|
|
131
|
+
const TASK_ARG_LIMIT = 8000;
|
|
132
|
+
if (task.length > TASK_ARG_LIMIT) {
|
|
133
|
+
if (!tmpDir) {
|
|
134
|
+
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "pi-subagent-"));
|
|
135
|
+
}
|
|
136
|
+
const taskFilePath = path.join(tmpDir, "task.md");
|
|
137
|
+
fs.writeFileSync(taskFilePath, `Task: ${task}`, { mode: 0o600 });
|
|
138
|
+
args.push(`@${taskFilePath}`);
|
|
139
|
+
} else {
|
|
140
|
+
args.push(`Task: ${task}`);
|
|
141
|
+
}
|
|
127
142
|
|
|
128
143
|
const result: SingleResult = {
|
|
129
144
|
agent: agentName,
|