pi-fast-subagent 0.2.0 → 0.4.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/README.md +19 -0
- package/background-job-manager.ts +178 -0
- package/background-types.ts +36 -0
- package/index.ts +132 -2
- package/package.json +18 -3
package/README.md
CHANGED
|
@@ -8,6 +8,7 @@ Runs subagents with `createAgentSession()` in same process instead of spawning `
|
|
|
8
8
|
|
|
9
9
|
- Single mode: `{ agent, task }`
|
|
10
10
|
- Parallel mode: `{ tasks: [...] }`
|
|
11
|
+
- Background mode: `{ agent, task, background: true }` — fire-and-forget with poll/cancel
|
|
11
12
|
- Per-call model override
|
|
12
13
|
- User + project agent discovery
|
|
13
14
|
- Project agents override user agents
|
|
@@ -59,6 +60,24 @@ model: anthropic/claude-haiku-4-5
|
|
|
59
60
|
You are code exploration specialist. Read relevant files, trace data flow, summarize findings clearly.
|
|
60
61
|
```
|
|
61
62
|
|
|
63
|
+
## Slash Commands
|
|
64
|
+
|
|
65
|
+
### `/agent`
|
|
66
|
+
|
|
67
|
+
List all available agents:
|
|
68
|
+
|
|
69
|
+
```
|
|
70
|
+
/agent
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Show details for a specific agent (description, file path, model, tools, system prompt):
|
|
74
|
+
|
|
75
|
+
```
|
|
76
|
+
/agent scout
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Tab-completion is supported for agent names.
|
|
80
|
+
|
|
62
81
|
## Usage
|
|
63
82
|
|
|
64
83
|
### List agents
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import type {
|
|
3
|
+
BackgroundHandleLike,
|
|
4
|
+
BackgroundJobManagerOptions,
|
|
5
|
+
BackgroundJobResult,
|
|
6
|
+
BackgroundSubagentJob,
|
|
7
|
+
} from "./background-types.js";
|
|
8
|
+
|
|
9
|
+
export type { BackgroundSubagentJob } from "./background-types.js";
|
|
10
|
+
|
|
11
|
+
export class BackgroundJobManager {
|
|
12
|
+
private jobs = new Map<string, BackgroundSubagentJob>();
|
|
13
|
+
private evictionTimers = new Map<string, ReturnType<typeof setTimeout>>();
|
|
14
|
+
private maxRunning: number;
|
|
15
|
+
private maxTotal: number;
|
|
16
|
+
private evictionMs: number;
|
|
17
|
+
private onJobComplete?: (job: BackgroundSubagentJob) => void;
|
|
18
|
+
private isShutdown = false;
|
|
19
|
+
|
|
20
|
+
constructor(options: BackgroundJobManagerOptions = {}) {
|
|
21
|
+
this.maxRunning = options.maxRunning ?? 10;
|
|
22
|
+
this.maxTotal = options.maxTotal ?? 50;
|
|
23
|
+
this.evictionMs = options.evictionMs ?? 5 * 60 * 1000;
|
|
24
|
+
this.onJobComplete = options.onJobComplete;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
adoptHandle(
|
|
28
|
+
agentName: string,
|
|
29
|
+
task: string,
|
|
30
|
+
cwd: string,
|
|
31
|
+
handle: BackgroundHandleLike,
|
|
32
|
+
resultPromise: Promise<BackgroundJobResult>,
|
|
33
|
+
): string {
|
|
34
|
+
handle.detach?.();
|
|
35
|
+
|
|
36
|
+
const abortController = new AbortController();
|
|
37
|
+
const onAbort = () => handle.abort();
|
|
38
|
+
abortController.signal.addEventListener("abort", onAbort, { once: true });
|
|
39
|
+
|
|
40
|
+
return this.attachJob(agentName, task, cwd, abortController, resultPromise.finally(() => {
|
|
41
|
+
abortController.signal.removeEventListener("abort", onAbort);
|
|
42
|
+
}));
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
cancel(id: string): "cancelled" | "not_found" | "already_done" {
|
|
46
|
+
const job = this.jobs.get(id);
|
|
47
|
+
if (!job) return "not_found";
|
|
48
|
+
if (job.status !== "running") return "already_done";
|
|
49
|
+
|
|
50
|
+
job.status = "cancelled";
|
|
51
|
+
job.completedAt = Date.now();
|
|
52
|
+
job.abortController.abort();
|
|
53
|
+
this.scheduleEviction(id);
|
|
54
|
+
return "cancelled";
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
getJob(id: string): BackgroundSubagentJob | undefined {
|
|
58
|
+
return this.jobs.get(id);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
getRunningJobs(): BackgroundSubagentJob[] {
|
|
62
|
+
return [...this.jobs.values()].filter((job) => job.status === "running");
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
getAllJobs(): BackgroundSubagentJob[] {
|
|
66
|
+
return [...this.jobs.values()];
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
shutdown(): void {
|
|
70
|
+
this.isShutdown = true;
|
|
71
|
+
for (const timer of this.evictionTimers.values()) {
|
|
72
|
+
clearTimeout(timer);
|
|
73
|
+
}
|
|
74
|
+
this.evictionTimers.clear();
|
|
75
|
+
|
|
76
|
+
for (const job of this.jobs.values()) {
|
|
77
|
+
if (job.status === "running") {
|
|
78
|
+
job.status = "cancelled";
|
|
79
|
+
job.completedAt = Date.now();
|
|
80
|
+
job.abortController.abort();
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
private attachJob(
|
|
86
|
+
agentName: string,
|
|
87
|
+
task: string,
|
|
88
|
+
cwd: string,
|
|
89
|
+
abortController: AbortController,
|
|
90
|
+
resultPromise: Promise<BackgroundJobResult>,
|
|
91
|
+
): string {
|
|
92
|
+
if (this.getRunningJobs().length >= this.maxRunning) {
|
|
93
|
+
throw new Error(`Maximum concurrent background subagents reached (${this.maxRunning}).`);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (this.jobs.size >= this.maxTotal) {
|
|
97
|
+
this.evictOldestCompleted();
|
|
98
|
+
if (this.jobs.size >= this.maxTotal) {
|
|
99
|
+
throw new Error(`Maximum total background subagent jobs reached (${this.maxTotal}).`);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const id = `sa_${randomUUID().slice(0, 8)}`;
|
|
104
|
+
const job: BackgroundSubagentJob = {
|
|
105
|
+
id,
|
|
106
|
+
agentName,
|
|
107
|
+
task,
|
|
108
|
+
cwd,
|
|
109
|
+
status: "running",
|
|
110
|
+
startedAt: Date.now(),
|
|
111
|
+
abortController,
|
|
112
|
+
promise: undefined as unknown as Promise<void>,
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
job.promise = resultPromise
|
|
116
|
+
.then((result) => {
|
|
117
|
+
if (job.status === "cancelled") {
|
|
118
|
+
this.scheduleEviction(id);
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
job.status = result.exitCode === 0 ? "completed" : "failed";
|
|
122
|
+
job.completedAt = Date.now();
|
|
123
|
+
job.exitCode = result.exitCode;
|
|
124
|
+
job.resultSummary = result.summary;
|
|
125
|
+
job.error = result.error;
|
|
126
|
+
job.model = result.model;
|
|
127
|
+
this.scheduleEviction(id);
|
|
128
|
+
this.deliverResult(job);
|
|
129
|
+
})
|
|
130
|
+
.catch((error) => {
|
|
131
|
+
if (job.status === "cancelled") {
|
|
132
|
+
this.scheduleEviction(id);
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
job.status = "failed";
|
|
136
|
+
job.completedAt = Date.now();
|
|
137
|
+
job.exitCode = 1;
|
|
138
|
+
job.error = error instanceof Error ? error.message : String(error);
|
|
139
|
+
this.scheduleEviction(id);
|
|
140
|
+
this.deliverResult(job);
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
this.jobs.set(id, job);
|
|
144
|
+
return id;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
private deliverResult(job: BackgroundSubagentJob): void {
|
|
148
|
+
if (!this.onJobComplete) return;
|
|
149
|
+
queueMicrotask(() => this.onJobComplete?.(job));
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
private scheduleEviction(id: string): void {
|
|
153
|
+
if (this.isShutdown) return;
|
|
154
|
+
const existing = this.evictionTimers.get(id);
|
|
155
|
+
if (existing) clearTimeout(existing);
|
|
156
|
+
|
|
157
|
+
const timer = setTimeout(() => {
|
|
158
|
+
this.evictionTimers.delete(id);
|
|
159
|
+
this.jobs.delete(id);
|
|
160
|
+
}, this.evictionMs);
|
|
161
|
+
|
|
162
|
+
this.evictionTimers.set(id, timer);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
private evictOldestCompleted(): void {
|
|
166
|
+
let oldest: BackgroundSubagentJob | undefined;
|
|
167
|
+
for (const job of this.jobs.values()) {
|
|
168
|
+
if (job.status === "running") continue;
|
|
169
|
+
if (!oldest || job.startedAt < oldest.startedAt) oldest = job;
|
|
170
|
+
}
|
|
171
|
+
if (!oldest) return;
|
|
172
|
+
|
|
173
|
+
const timer = this.evictionTimers.get(oldest.id);
|
|
174
|
+
if (timer) clearTimeout(timer);
|
|
175
|
+
this.evictionTimers.delete(oldest.id);
|
|
176
|
+
this.jobs.delete(oldest.id);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export type BackgroundJobStatus = "running" | "completed" | "failed" | "cancelled";
|
|
2
|
+
|
|
3
|
+
export interface BackgroundSubagentJob {
|
|
4
|
+
id: string;
|
|
5
|
+
agentName: string;
|
|
6
|
+
task: string;
|
|
7
|
+
cwd: string;
|
|
8
|
+
status: BackgroundJobStatus;
|
|
9
|
+
startedAt: number;
|
|
10
|
+
completedAt?: number;
|
|
11
|
+
exitCode?: number;
|
|
12
|
+
resultSummary?: string;
|
|
13
|
+
error?: string;
|
|
14
|
+
model?: string;
|
|
15
|
+
abortController: AbortController;
|
|
16
|
+
promise: Promise<void>;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface BackgroundJobManagerOptions {
|
|
20
|
+
maxRunning?: number;
|
|
21
|
+
maxTotal?: number;
|
|
22
|
+
evictionMs?: number;
|
|
23
|
+
onJobComplete?: (job: BackgroundSubagentJob) => void;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface BackgroundJobResult {
|
|
27
|
+
summary: string;
|
|
28
|
+
exitCode: number;
|
|
29
|
+
error?: string;
|
|
30
|
+
model?: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface BackgroundHandleLike {
|
|
34
|
+
abort: () => void;
|
|
35
|
+
detach?: () => void;
|
|
36
|
+
}
|
package/index.ts
CHANGED
|
@@ -15,6 +15,8 @@ import type {
|
|
|
15
15
|
ExtensionContext,
|
|
16
16
|
ToolRenderResultOptions,
|
|
17
17
|
} from "@mariozechner/pi-coding-agent";
|
|
18
|
+
import { BackgroundJobManager } from "./background-job-manager.js";
|
|
19
|
+
import type { BackgroundHandleLike, BackgroundJobResult } from "./background-types.js";
|
|
18
20
|
import { Theme } from "@mariozechner/pi-coding-agent";
|
|
19
21
|
import { truncateToWidth, wrapTextWithAnsi } from "@mariozechner/pi-tui";
|
|
20
22
|
import { truncateToVisualLines, keyHint } from "@mariozechner/pi-coding-agent";
|
|
@@ -94,6 +96,7 @@ function summarizeToolArgs(toolName: unknown, toolInput: unknown): string {
|
|
|
94
96
|
|
|
95
97
|
let _authStorage: ReturnType<typeof AuthStorage.create> | null = null;
|
|
96
98
|
let _modelRegistry: ReturnType<typeof ModelRegistry.create> | null = null;
|
|
99
|
+
let _bgManager: BackgroundJobManager | null = null;
|
|
97
100
|
|
|
98
101
|
function getAuth() {
|
|
99
102
|
if (!_authStorage) _authStorage = AuthStorage.create();
|
|
@@ -101,6 +104,11 @@ function getAuth() {
|
|
|
101
104
|
return { authStorage: _authStorage, modelRegistry: _modelRegistry };
|
|
102
105
|
}
|
|
103
106
|
|
|
107
|
+
function getBgManager(): BackgroundJobManager {
|
|
108
|
+
if (!_bgManager) _bgManager = new BackgroundJobManager();
|
|
109
|
+
return _bgManager;
|
|
110
|
+
}
|
|
111
|
+
|
|
104
112
|
// ─── In-process runner ───────────────────────────────────────────────────────
|
|
105
113
|
|
|
106
114
|
const MAX_DEPTH = 2;
|
|
@@ -231,7 +239,10 @@ async function runAgent(
|
|
|
231
239
|
const toolCalls: ToolCallEntry[] = [];
|
|
232
240
|
const toolStartTimes = new Map<string, number>();
|
|
233
241
|
|
|
242
|
+
let done = false;
|
|
243
|
+
|
|
234
244
|
function emitUpdate(): void {
|
|
245
|
+
if (done) return;
|
|
235
246
|
onUpdate?.({
|
|
236
247
|
content: [{ type: "text", text: currentDelta || lastOutput || "" }],
|
|
237
248
|
details: {
|
|
@@ -354,6 +365,7 @@ async function runAgent(
|
|
|
354
365
|
exitCode = 1;
|
|
355
366
|
error = signal?.aborted ? "Aborted" : e instanceof Error ? e.message : String(e);
|
|
356
367
|
} finally {
|
|
368
|
+
done = true;
|
|
357
369
|
clearInterval(heartbeat);
|
|
358
370
|
unsubscribe();
|
|
359
371
|
session.dispose();
|
|
@@ -436,14 +448,21 @@ const SubagentParams = Type.Object({
|
|
|
436
448
|
Type.Number({ description: "Max parallel concurrency (default: 4)", default: 4 }),
|
|
437
449
|
),
|
|
438
450
|
|
|
451
|
+
// Background
|
|
452
|
+
background: Type.Optional(Type.Boolean({ description: "Run in background, returns job ID immediately" })),
|
|
453
|
+
jobId: Type.Optional(Type.String({ description: "Job ID for poll/cancel" })),
|
|
454
|
+
|
|
439
455
|
// Management
|
|
440
456
|
action: Type.Optional(
|
|
441
457
|
Type.Union(
|
|
442
458
|
[
|
|
443
459
|
Type.Literal("list"),
|
|
444
460
|
Type.Literal("get"),
|
|
461
|
+
Type.Literal("status"),
|
|
462
|
+
Type.Literal("poll"),
|
|
463
|
+
Type.Literal("cancel"),
|
|
445
464
|
],
|
|
446
|
-
{ description: "'list'
|
|
465
|
+
{ description: "'list'/'get' for agents, 'status' for bg jobs, 'poll'/'cancel' for a specific job" },
|
|
447
466
|
),
|
|
448
467
|
),
|
|
449
468
|
agentScope: Type.Optional(
|
|
@@ -457,6 +476,72 @@ const SubagentParams = Type.Object({
|
|
|
457
476
|
// ─── Extension entry point ────────────────────────────────────────────────────
|
|
458
477
|
|
|
459
478
|
export default function (pi: ExtensionAPI) {
|
|
479
|
+
// ─── /agent slash command ─────────────────────────────────────────────────
|
|
480
|
+
pi.registerCommand("agent", {
|
|
481
|
+
description: "List available subagents. Usage: /agent [name] — show details for a specific agent.",
|
|
482
|
+
getArgumentCompletions(prefix: string) {
|
|
483
|
+
const agents = discoverAgents(process.cwd());
|
|
484
|
+
return agents
|
|
485
|
+
.filter((a) => a.name.startsWith(prefix))
|
|
486
|
+
.map((a) => ({ value: a.name, label: a.name, description: a.description }));
|
|
487
|
+
},
|
|
488
|
+
async handler(args: string, ctx) {
|
|
489
|
+
const agents = discoverAgents(ctx.cwd);
|
|
490
|
+
const name = args.trim();
|
|
491
|
+
|
|
492
|
+
if (name) {
|
|
493
|
+
const agent = agents.find((a) => a.name === name);
|
|
494
|
+
if (!agent) {
|
|
495
|
+
const list = agents.map((a) => a.name).join(", ") || "none";
|
|
496
|
+
ctx.ui.notify(`Unknown agent "${name}". Available: ${list}`, "warning");
|
|
497
|
+
return;
|
|
498
|
+
}
|
|
499
|
+
const lines = [
|
|
500
|
+
`## ${agent.name} [${agent.source}]`,
|
|
501
|
+
`File: ${agent.filePath}`,
|
|
502
|
+
`Description: ${agent.description}`,
|
|
503
|
+
agent.model ? `Model: ${agent.model}` : "",
|
|
504
|
+
agent.tools ? `Tools: ${agent.tools.join(", ")}` : "",
|
|
505
|
+
agent.systemPrompt ? `\nSystem prompt:\n${agent.systemPrompt}` : "",
|
|
506
|
+
].filter(Boolean).join("\n");
|
|
507
|
+
ctx.ui.notify(lines, "info");
|
|
508
|
+
return;
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
if (agents.length === 0) {
|
|
512
|
+
ctx.ui.notify(
|
|
513
|
+
"No agents found.\n" +
|
|
514
|
+
"Add .md files to:\n" +
|
|
515
|
+
" ~/.pi/agent/agents/ (user-level)\n" +
|
|
516
|
+
" .pi/agents/ (project-level)\n" +
|
|
517
|
+
"\nFrontmatter required: name, description. Optional: model, tools.",
|
|
518
|
+
"info"
|
|
519
|
+
);
|
|
520
|
+
return;
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
const userAgents = agents.filter((a) => a.source === "user");
|
|
524
|
+
const projectAgents = agents.filter((a) => a.source === "project");
|
|
525
|
+
|
|
526
|
+
const lines: string[] = [`Agents (${agents.length}):`];
|
|
527
|
+
if (projectAgents.length) {
|
|
528
|
+
lines.push("\nProject (.pi/agents/):");
|
|
529
|
+
for (const a of projectAgents) {
|
|
530
|
+
lines.push(` ${a.name}${a.model ? ` [${a.model}]` : ""} — ${a.description}`);
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
if (userAgents.length) {
|
|
534
|
+
lines.push("\nUser (~/.pi/agent/agents/):");
|
|
535
|
+
for (const a of userAgents) {
|
|
536
|
+
lines.push(` ${a.name}${a.model ? ` [${a.model}]` : ""} — ${a.description}`);
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
lines.push("");
|
|
540
|
+
lines.push("Tip: /agent <name> for details · Add .md files to .pi/agents/ to create new agents");
|
|
541
|
+
ctx.ui.notify(lines.join("\n"), "info");
|
|
542
|
+
},
|
|
543
|
+
});
|
|
544
|
+
|
|
460
545
|
pi.registerTool({
|
|
461
546
|
name: "subagent",
|
|
462
547
|
label: "Subagent",
|
|
@@ -655,7 +740,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
655
740
|
};
|
|
656
741
|
|
|
657
742
|
// ── Management: list ──────────────────────────────────────────────────────
|
|
658
|
-
if (params.action === "list" || (!params.agent && !params.tasks)) {
|
|
743
|
+
if (params.action === "list" || (!params.action && !params.agent && !params.tasks)) {
|
|
659
744
|
if (agents.length === 0) {
|
|
660
745
|
return {
|
|
661
746
|
content: [{
|
|
@@ -684,11 +769,56 @@ export default function (pi: ExtensionAPI) {
|
|
|
684
769
|
return { content: [{ type: "text", text: info }] };
|
|
685
770
|
}
|
|
686
771
|
|
|
772
|
+
// ── Background status ───────────────────────────────────────────────────
|
|
773
|
+
if (params.action === "status") {
|
|
774
|
+
const jobs = getBgManager().getAllJobs();
|
|
775
|
+
if (jobs.length === 0) return { content: [{ type: "text", text: "No background jobs." }] };
|
|
776
|
+
const lines = jobs.map((j) => {
|
|
777
|
+
const dur = j.completedAt ? formatDuration(j.completedAt - j.startedAt) : formatDuration(Date.now() - j.startedAt);
|
|
778
|
+
return `${j.id} [${j.status}] ${j.agentName} · ${dur} · ${j.task.length > 50 ? j.task.slice(0, 47) + "..." : j.task}`;
|
|
779
|
+
});
|
|
780
|
+
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
// ── Background poll ────────────────────────────────────────────────────────
|
|
784
|
+
if (params.action === "poll") {
|
|
785
|
+
if (!params.jobId) return { content: [{ type: "text", text: "Provide jobId to poll." }] };
|
|
786
|
+
const job = getBgManager().getJob(params.jobId);
|
|
787
|
+
if (!job) return { content: [{ type: "text", text: `Job ${params.jobId} not found (completed and evicted, or invalid).` }] };
|
|
788
|
+
const dur = job.completedAt ? formatDuration(job.completedAt - job.startedAt) : formatDuration(Date.now() - job.startedAt);
|
|
789
|
+
const parts = [`${job.id} [${job.status}] ${job.agentName} · ${dur}`, `Task: ${job.task}`];
|
|
790
|
+
if (job.status === "completed") parts.push(`\nResult:\n${job.resultSummary ?? "(no output)"}`);
|
|
791
|
+
if (job.status === "failed") parts.push(`\nError: ${job.error ?? "(unknown)"}`);
|
|
792
|
+
if (job.status === "running") parts.push("Still running — poll again later.");
|
|
793
|
+
return { content: [{ type: "text", text: parts.join("\n") }] };
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
// ── Background cancel ──────────────────────────────────────────────────────
|
|
797
|
+
if (params.action === "cancel") {
|
|
798
|
+
if (!params.jobId) return { content: [{ type: "text", text: "Provide jobId to cancel." }] };
|
|
799
|
+
const result = getBgManager().cancel(params.jobId);
|
|
800
|
+
const msg = result === "cancelled" ? `Job ${params.jobId} cancelled.`
|
|
801
|
+
: result === "already_done" ? `Job ${params.jobId} already completed.`
|
|
802
|
+
: `Job ${params.jobId} not found.`;
|
|
803
|
+
return { content: [{ type: "text", text: msg }] };
|
|
804
|
+
}
|
|
805
|
+
|
|
687
806
|
// ── Single mode ───────────────────────────────────────────────────────────
|
|
688
807
|
if (params.agent && params.task) {
|
|
689
808
|
const { agent, error } = findAgent(params.agent);
|
|
690
809
|
if (error || !agent) return { content: [{ type: "text", text: error ?? "Not found" }] };
|
|
691
810
|
|
|
811
|
+
// Background dispatch — fire and forget
|
|
812
|
+
if (params.background) {
|
|
813
|
+
const bgAbort = new AbortController();
|
|
814
|
+
const handle: BackgroundHandleLike = { abort: () => bgAbort.abort() };
|
|
815
|
+
const resultPromise: Promise<BackgroundJobResult> = runAgent(
|
|
816
|
+
agent, params.task, cwd, params.model, bgAbort.signal, undefined
|
|
817
|
+
).then((r) => ({ summary: r.output, exitCode: r.exitCode, error: r.error, model: r.model }));
|
|
818
|
+
const jobId = getBgManager().adoptHandle(agent.name, params.task, cwd, handle, resultPromise);
|
|
819
|
+
return { content: [{ type: "text", text: `Background job started: ${jobId}\nCheck progress: subagent({ action: "poll", jobId: "${jobId}" })` }] };
|
|
820
|
+
}
|
|
821
|
+
|
|
692
822
|
const result = await runAgent(
|
|
693
823
|
agent,
|
|
694
824
|
params.task,
|
package/package.json
CHANGED
|
@@ -1,14 +1,27 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-fast-subagent",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "In-process subagent delegation for pi with single, parallel, and
|
|
3
|
+
"version": "0.4.0",
|
|
4
|
+
"description": "In-process subagent delegation for pi with single, parallel, and background modes",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"pi-package",
|
|
8
8
|
"pi",
|
|
9
|
+
"pi-extension",
|
|
9
10
|
"subagent",
|
|
10
11
|
"agents",
|
|
11
|
-
"extension"
|
|
12
|
+
"extension",
|
|
13
|
+
"delegation",
|
|
14
|
+
"parallel",
|
|
15
|
+
"concurrent",
|
|
16
|
+
"in-process",
|
|
17
|
+
"fast",
|
|
18
|
+
"coding-agent",
|
|
19
|
+
"ai-agent",
|
|
20
|
+
"llm",
|
|
21
|
+
"task-runner",
|
|
22
|
+
"scout",
|
|
23
|
+
"workflow",
|
|
24
|
+
"automation"
|
|
12
25
|
],
|
|
13
26
|
"homepage": "https://github.com/tuansondinh/pi-fast-subagent#readme",
|
|
14
27
|
"repository": {
|
|
@@ -21,6 +34,8 @@
|
|
|
21
34
|
"files": [
|
|
22
35
|
"index.ts",
|
|
23
36
|
"agents.ts",
|
|
37
|
+
"background-job-manager.ts",
|
|
38
|
+
"background-types.ts",
|
|
24
39
|
"agents/*.md",
|
|
25
40
|
"README.md"
|
|
26
41
|
],
|