pi-subagents 0.65.1 → 0.66.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 +55 -0
- package/README.md +1 -1
- package/agents/researcher.md +23 -13
- package/docs/agents.md +16 -2
- package/docs/configuration.md +18 -0
- package/docs/extension-api.md +91 -0
- package/docs/models.md +58 -1
- package/docs/observability.md +42 -2
- package/docs/tool-reference.md +3 -3
- package/docs/workflows.md +14 -7
- package/package.json +2 -1
- package/skills/pi-subagents/references/execution-controls.md +14 -1
- package/skills/pi-subagents/references/management-authoring-rpc.md +2 -1
- package/src/agents/advertised-agent-prompt.ts +63 -0
- package/src/agents/agent-management.ts +14 -1
- package/src/agents/agent-serializer.ts +2 -0
- package/src/agents/agents.ts +8 -0
- package/src/api/shared-types.ts +1 -1
- package/src/api/workflow-resources.ts +6 -0
- package/src/extension/index.ts +40 -2
- package/src/extension/public-execution.ts +0 -1
- package/src/extension/rpc.ts +4 -21
- package/src/extension/schemas.ts +8 -6
- package/src/extension/tool-description.ts +6 -5
- package/src/intercom/native-supervisor-channel.ts +82 -54
- package/src/runs/background/active-async-capacity.ts +18 -18
- package/src/runs/background/async-job-tracker.ts +35 -3
- package/src/runs/background/async-status-snapshot.ts +10 -12
- package/src/runs/background/async-status.ts +17 -9
- package/src/runs/background/auto-drain.ts +40 -29
- package/src/runs/background/chain-root-attachment.ts +8 -0
- package/src/runs/background/control-channel.ts +78 -44
- package/src/runs/background/notify.ts +86 -12
- package/src/runs/background/owned-process-tree.ts +6 -6
- package/src/runs/background/process-terminal.ts +23 -23
- package/src/runs/background/run-child-session.ts +60 -32
- package/src/runs/background/run-status.ts +75 -5
- package/src/runs/background/runner-aliases.ts +18 -7
- package/src/runs/background/runner-child-launch.ts +86 -0
- package/src/runs/background/stale-run-reconciler.ts +3 -1
- package/src/runs/background/subagent-runner.ts +412 -209
- package/src/runs/background/subagent-wait.ts +3 -0
- package/src/runs/background/wait-completions.ts +4 -0
- package/src/runs/foreground/async-steering-action.ts +19 -0
- package/src/runs/foreground/execution.ts +101 -25
- package/src/runs/foreground/subagent-executor.ts +474 -197
- package/src/runs/foreground/workflow-detach-reconcile.ts +8 -5
- package/src/runs/foreground/workflow-foreground-steering.ts +56 -2
- package/src/runs/shared/acceptance.ts +2 -2
- package/src/runs/shared/agent-contract.ts +1 -1
- package/src/runs/shared/async-status-projection.ts +47 -47
- package/src/runs/shared/child-hooks.ts +151 -2
- package/src/runs/shared/child-launch.ts +18 -13
- package/src/runs/shared/child-session.ts +42 -6
- package/src/runs/shared/child-tool-plan.ts +2 -2
- package/src/runs/shared/completion-evidence.ts +2 -2
- package/src/runs/shared/completion-guard.ts +1 -0
- package/src/runs/shared/host-step-status.ts +11 -11
- package/src/runs/shared/llm-intent-arbiter.ts +10 -9
- package/src/runs/shared/model-fallback.ts +10 -6
- package/src/runs/shared/nested-events.ts +5 -5
- package/src/runs/shared/orca-progress-tabs.ts +6 -0
- package/src/runs/shared/parallel-handoff.ts +57 -12
- package/src/runs/shared/parallel-utils.ts +2 -2
- package/src/runs/shared/readonly-drain-observation.ts +42 -0
- package/src/runs/shared/readonly-model-continuation.ts +69 -0
- package/src/runs/shared/readonly-session-evidence.ts +307 -0
- package/src/runs/shared/run-fanout-budget.ts +8 -8
- package/src/runs/shared/runtime-acknowledged-extensions.ts +3 -3
- package/src/runs/shared/subagent-prompt-runtime.ts +13 -3
- package/src/runs/shared/worktree-setup-command.ts +190 -0
- package/src/runs/shared/worktree.ts +332 -204
- package/src/shared/types.ts +81 -60
- package/src/shared/utils.ts +7 -2
- package/src/shared/workflow-child-permit.ts +18 -13
- package/src/tui/fleet.ts +11 -5
- package/src/tui/render.ts +23 -5
- package/src/workflows/chat-progress.ts +3 -3
- package/src/workflows/scripted-workflow.ts +38 -10
- package/src/workflows/workflow-checklist.ts +11 -15
- package/src/workflows/workflow-child-summary.ts +57 -8
- package/src/workflows/workflow-preflight.ts +19 -19
- package/src/workflows/workflow-receipt.ts +3 -3
- package/src/workflows/workflow-resources.ts +96 -21
- package/src/workflows/workflow-settlement.ts +3 -0
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { createOwnedProcessTreeController } from "../background/owned-process-tree.ts";
|
|
3
|
+
import type { ProcessTreeTerminal } from "../../shared/types.ts";
|
|
4
|
+
|
|
5
|
+
export interface SetupCommandOptions {
|
|
6
|
+
cwd?: string;
|
|
7
|
+
env?: NodeJS.ProcessEnv;
|
|
8
|
+
input?: string;
|
|
9
|
+
/** Worktrunk uses 128 KiB; Git and hooks use spawnSync's 1 MiB default. */
|
|
10
|
+
maxBuffer?: number;
|
|
11
|
+
signal?: AbortSignal;
|
|
12
|
+
deadlineAt?: number;
|
|
13
|
+
/** Only the existing configured setup-hook timeout, not a new Git budget. */
|
|
14
|
+
hookTimeoutMs?: number;
|
|
15
|
+
/** Read-only probes may define negative answers as completed commands. */
|
|
16
|
+
acceptedExitCodes?: readonly number[];
|
|
17
|
+
onSpawn?: (process: { pid: number; processGroupId?: number }) => void;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface SetupCommandResult {
|
|
21
|
+
stdout: string;
|
|
22
|
+
stderr: string;
|
|
23
|
+
status: number | null;
|
|
24
|
+
signal: NodeJS.Signals | null;
|
|
25
|
+
error?: Error;
|
|
26
|
+
pid?: number;
|
|
27
|
+
processGroupId?: number;
|
|
28
|
+
/** Absent on normal completion: trusted command completion is NOT tree proof. */
|
|
29
|
+
processTree?: ProcessTreeTerminal;
|
|
30
|
+
outputIncomplete: boolean;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function commandError(message: string, code: string): NodeJS.ErrnoException {
|
|
34
|
+
return Object.assign(new Error(message), { code });
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Setup I/O only. Commands must await their descendants before reporting success.
|
|
39
|
+
* Exit 0 + natural close + complete output still requires caller JSON/path validation.
|
|
40
|
+
* Invalid output is NOT accepted completion: retain unknown ownership, never signal
|
|
41
|
+
* this completed command's PID later. Callers own compensation and admission.
|
|
42
|
+
*/
|
|
43
|
+
export async function runSetupCommand(
|
|
44
|
+
command: string,
|
|
45
|
+
args: string[],
|
|
46
|
+
options: SetupCommandOptions,
|
|
47
|
+
): Promise<SetupCommandResult> {
|
|
48
|
+
const result: SetupCommandResult = {
|
|
49
|
+
stdout: "", stderr: "", status: null, signal: null, outputIncomplete: false,
|
|
50
|
+
};
|
|
51
|
+
const maxBuffer = options.maxBuffer ?? 1024 * 1024;
|
|
52
|
+
if (!Number.isSafeInteger(maxBuffer) || maxBuffer <= 0) throw new Error("Invalid setup command maxBuffer");
|
|
53
|
+
if (options.deadlineAt !== undefined && !Number.isFinite(options.deadlineAt)) throw new Error("Invalid setup deadline");
|
|
54
|
+
if (options.hookTimeoutMs !== undefined && (!Number.isSafeInteger(options.hookTimeoutMs) || options.hookTimeoutMs <= 0)) {
|
|
55
|
+
throw new Error("Invalid setup hook timeout");
|
|
56
|
+
}
|
|
57
|
+
const cancellation = (): Error | undefined => {
|
|
58
|
+
if (options.signal?.aborted) return commandError("Worktree setup aborted", "ABORT_ERR");
|
|
59
|
+
if (options.deadlineAt !== undefined && Date.now() >= options.deadlineAt) {
|
|
60
|
+
return commandError("Worktree setup deadline exceeded", "ETIMEDOUT");
|
|
61
|
+
}
|
|
62
|
+
return undefined;
|
|
63
|
+
};
|
|
64
|
+
result.error = cancellation();
|
|
65
|
+
if (result.error) return result;
|
|
66
|
+
|
|
67
|
+
let child;
|
|
68
|
+
try {
|
|
69
|
+
child = spawn(command, args, {
|
|
70
|
+
cwd: options.cwd,
|
|
71
|
+
env: options.env ? { ...process.env, ...options.env } : process.env,
|
|
72
|
+
stdio: "pipe", shell: false, windowsHide: true,
|
|
73
|
+
// Isolated owned POSIX group, never unref'd or fire-and-forget.
|
|
74
|
+
detached: process.platform !== "win32",
|
|
75
|
+
});
|
|
76
|
+
} catch (error) {
|
|
77
|
+
result.error = error instanceof Error ? error : new Error(String(error));
|
|
78
|
+
return result;
|
|
79
|
+
}
|
|
80
|
+
result.pid = child.pid;
|
|
81
|
+
if (child.pid !== undefined && process.platform !== "win32") result.processGroupId = child.pid;
|
|
82
|
+
const tree = child.pid === undefined ? undefined : createOwnedProcessTreeController(child.pid);
|
|
83
|
+
let termination: Promise<ProcessTreeTerminal> | undefined;
|
|
84
|
+
let directSettled = false;
|
|
85
|
+
const releaseUnknownIO = () => {
|
|
86
|
+
if (!directSettled || result.processTree?.state !== "unknown") return;
|
|
87
|
+
result.error ??= commandError("Worktree setup process tree settlement is unverified", "PROCESS_TREE_UNVERIFIED");
|
|
88
|
+
result.outputIncomplete = true;
|
|
89
|
+
// Only local I/O is released; unknown descendant ownership remains retained.
|
|
90
|
+
child.stdin.destroy();
|
|
91
|
+
child.stdout.destroy();
|
|
92
|
+
child.stderr.destroy();
|
|
93
|
+
};
|
|
94
|
+
const terminate = () => {
|
|
95
|
+
if (termination || !tree) return;
|
|
96
|
+
if (directSettled) {
|
|
97
|
+
result.processTree = {
|
|
98
|
+
state: "unknown", reason: "verification-failed",
|
|
99
|
+
diagnostic: "Command exited before termination began; no longer safe to signal its recorded PID.",
|
|
100
|
+
};
|
|
101
|
+
releaseUnknownIO();
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
termination = tree.terminate().then((proof) => {
|
|
105
|
+
result.processTree = proof;
|
|
106
|
+
releaseUnknownIO();
|
|
107
|
+
return proof;
|
|
108
|
+
});
|
|
109
|
+
};
|
|
110
|
+
const fail = (error: Error) => {
|
|
111
|
+
result.error ??= error;
|
|
112
|
+
terminate();
|
|
113
|
+
};
|
|
114
|
+
const onAbort = () => fail(commandError("Worktree setup aborted", "ABORT_ERR"));
|
|
115
|
+
const stdout: Buffer[] = [];
|
|
116
|
+
const stderr: Buffer[] = [];
|
|
117
|
+
let capturedBytes = 0;
|
|
118
|
+
const capture = (chunks: Buffer[], chunk: Buffer) => {
|
|
119
|
+
const remaining = maxBuffer - capturedBytes;
|
|
120
|
+
if (remaining > 0) {
|
|
121
|
+
const saved = Buffer.from(chunk.subarray(0, remaining));
|
|
122
|
+
chunks.push(saved);
|
|
123
|
+
capturedBytes += saved.length;
|
|
124
|
+
}
|
|
125
|
+
if (chunk.length > remaining) {
|
|
126
|
+
result.outputIncomplete = true;
|
|
127
|
+
fail(commandError("Worktree setup command output exceeds maxBuffer", "ENOBUFS"));
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
const onStdout = (chunk: Buffer) => capture(stdout, chunk);
|
|
131
|
+
const onStderr = (chunk: Buffer) => capture(stderr, chunk);
|
|
132
|
+
child.stdout.on("data", onStdout);
|
|
133
|
+
child.stderr.on("data", onStderr);
|
|
134
|
+
child.stdin.on("error", fail);
|
|
135
|
+
const close = new Promise<void>((resolve) => child.once("close", () => resolve()));
|
|
136
|
+
const directExit = new Promise<void>((resolve) => {
|
|
137
|
+
child.once("exit", (status, signal) => {
|
|
138
|
+
directSettled = true;
|
|
139
|
+
result.status = status;
|
|
140
|
+
result.signal = signal;
|
|
141
|
+
if (status === null || !(options.acceptedExitCodes ?? [0]).includes(status)) terminate();
|
|
142
|
+
releaseUnknownIO();
|
|
143
|
+
resolve();
|
|
144
|
+
});
|
|
145
|
+
child.once("error", (error) => {
|
|
146
|
+
fail(error);
|
|
147
|
+
// A failed spawn has no direct process to await. Other errors do not prove exit.
|
|
148
|
+
if (child.pid === undefined) {
|
|
149
|
+
directSettled = true;
|
|
150
|
+
resolve();
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
155
|
+
const deadlineAt = Math.min(options.deadlineAt ?? Infinity,
|
|
156
|
+
options.hookTimeoutMs === undefined ? Infinity : Date.now() + options.hookTimeoutMs);
|
|
157
|
+
const armDeadline = () => {
|
|
158
|
+
if (deadlineAt === Infinity) return;
|
|
159
|
+
const remaining = deadlineAt - Date.now();
|
|
160
|
+
if (remaining <= 0) fail(commandError("Worktree setup command timed out", "ETIMEDOUT"));
|
|
161
|
+
else timer = setTimeout(armDeadline, Math.min(remaining, 2 ** 31 - 1));
|
|
162
|
+
};
|
|
163
|
+
options.signal?.addEventListener("abort", onAbort, { once: true });
|
|
164
|
+
try {
|
|
165
|
+
armDeadline();
|
|
166
|
+
if (options.signal?.aborted) onAbort();
|
|
167
|
+
if (child.pid !== undefined) {
|
|
168
|
+
try { options.onSpawn?.({ pid: child.pid, processGroupId: result.processGroupId }); }
|
|
169
|
+
catch (error) { fail(error instanceof Error ? error : new Error(String(error))); }
|
|
170
|
+
}
|
|
171
|
+
child.stdin.end(options.input);
|
|
172
|
+
await directExit;
|
|
173
|
+
if (termination) result.processTree = await termination;
|
|
174
|
+
releaseUnknownIO();
|
|
175
|
+
await close;
|
|
176
|
+
// Abort/overflow may arrive while draining stdio after direct exit.
|
|
177
|
+
if (termination) result.processTree = await termination;
|
|
178
|
+
const cancelled = cancellation();
|
|
179
|
+
if (cancelled) fail(cancelled);
|
|
180
|
+
result.stdout = Buffer.concat(stdout).toString("utf8");
|
|
181
|
+
result.stderr = Buffer.concat(stderr).toString("utf8");
|
|
182
|
+
return result;
|
|
183
|
+
} finally {
|
|
184
|
+
if (timer) clearTimeout(timer);
|
|
185
|
+
options.signal?.removeEventListener("abort", onAbort);
|
|
186
|
+
child.stdout.removeListener("data", onStdout);
|
|
187
|
+
child.stderr.removeListener("data", onStderr);
|
|
188
|
+
child.stdin.removeListener("error", fail);
|
|
189
|
+
}
|
|
190
|
+
}
|