@ferris1225/pi-subagents 4.3.4 → 4.3.5
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 +15 -0
- package/README.md +59 -66
- package/agents/artisan.md +0 -1
- package/agents/steward.md +1 -2
- package/{src/index.ts → index.ts} +19 -19
- package/package.json +4 -3
- package/src/{config.ts → configuration/config.ts} +19 -24
- package/src/configuration/setup.ts +375 -0
- package/src/configuration/ui.ts +245 -0
- package/src/{agents.ts → delegation/agents.ts} +3 -3
- package/src/{dispatch.ts → delegation/dispatch.ts} +12 -18
- package/src/{prompt.ts → delegation/prompt.ts} +3 -8
- package/src/{background.ts → execution/background.ts} +3 -6
- package/src/execution/rpc-control.ts +200 -0
- package/src/{rpc-run.ts → execution/rpc-run.ts} +11 -199
- package/src/{session-fork.ts → execution/session-fork.ts} +1 -1
- package/src/{spawn.ts → execution/spawn.ts} +10 -8
- package/src/isolation/git-command.ts +147 -0
- package/src/{recovery.ts → isolation/recovery.ts} +1 -1
- package/src/{worktree.ts → isolation/worktree.ts} +10 -147
- package/src/{completion.ts → lifecycle/completion.ts} +2 -2
- package/src/{durable.ts → lifecycle/durable.ts} +3 -3
- package/src/{runtime.ts → lifecycle/runtime.ts} +7 -7
- package/src/{thread-lifecycle.ts → lifecycle/thread-lifecycle.ts} +25 -519
- package/src/lifecycle/thread-restore.ts +250 -0
- package/src/lifecycle/thread-shared.ts +269 -0
- package/src/{tools.ts → lifecycle/tools.ts} +8 -8
- package/src/{announcements.ts → presentation/announcements.ts} +4 -4
- package/src/{format.ts → presentation/format.ts} +3 -3
- package/src/{monitor.ts → presentation/monitor.ts} +2 -2
- package/src/{widget.ts → presentation/widget.ts} +1 -1
- package/agents/sentinel.md +0 -16
- package/src/setup.ts +0 -344
- package/src/ui.ts +0 -160
- /package/src/{models.ts → configuration/models.ts} +0 -0
- /package/src/{temp-hygiene.ts → isolation/temp-hygiene.ts} +0 -0
- /package/src/{status.ts → presentation/status.ts} +0 -0
|
@@ -8,10 +8,17 @@
|
|
|
8
8
|
* Failed integration deliberately retains both the worktree and patch.
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
import { spawn, type ChildProcess } from "node:child_process";
|
|
12
11
|
import { existsSync, symlinkSync } from "node:fs";
|
|
13
12
|
import { copyFile, mkdir, mkdtemp, realpath, rm, stat, writeFile } from "node:fs/promises";
|
|
14
13
|
import { basename, dirname, isAbsolute, join, relative, resolve } from "node:path";
|
|
14
|
+
import {
|
|
15
|
+
GIT_COMMAND_TIMEOUT_MS,
|
|
16
|
+
GIT_OUTPUT_MAX_BYTES,
|
|
17
|
+
runCommand,
|
|
18
|
+
type CommandResult,
|
|
19
|
+
type CommandRunner,
|
|
20
|
+
WORKTREE_PATCH_MAX_BYTES,
|
|
21
|
+
} from "./git-command.ts";
|
|
15
22
|
import { writeTempOwnerMarker } from "./temp-hygiene.ts";
|
|
16
23
|
|
|
17
24
|
export type IsolationMode = "shared" | "worktree";
|
|
@@ -28,151 +35,6 @@ export function worktreeGroupId(worktree: Pick<WorktreeIsolation, "tempDir">): s
|
|
|
28
35
|
: base;
|
|
29
36
|
}
|
|
30
37
|
|
|
31
|
-
export interface CommandRunOptions {
|
|
32
|
-
cwd: string;
|
|
33
|
-
input?: Buffer;
|
|
34
|
-
signal?: AbortSignal;
|
|
35
|
-
timeoutMs?: number;
|
|
36
|
-
maxOutputBytes?: number;
|
|
37
|
-
/** Extra environment entries merged over the inherited environment. */
|
|
38
|
-
env?: Record<string, string>;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
export interface CommandResult {
|
|
42
|
-
code: number;
|
|
43
|
-
stdout: Buffer;
|
|
44
|
-
stderr: Buffer;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/** Injectable, shell-free command runner used by every Git operation. */
|
|
48
|
-
export type CommandRunner = (
|
|
49
|
-
command: string,
|
|
50
|
-
args: readonly string[],
|
|
51
|
-
options: CommandRunOptions,
|
|
52
|
-
) => Promise<CommandResult>;
|
|
53
|
-
|
|
54
|
-
export const GIT_COMMAND_TIMEOUT_MS = 120_000;
|
|
55
|
-
export const GIT_COMMAND_KILL_GRACE_MS = 2_000;
|
|
56
|
-
export const GIT_OUTPUT_MAX_BYTES = 64 * 1024 * 1024;
|
|
57
|
-
export const WORKTREE_PATCH_MAX_BYTES = GIT_OUTPUT_MAX_BYTES;
|
|
58
|
-
|
|
59
|
-
/** Git apply validates and writes in one process. The repository lane
|
|
60
|
-
* (thread-lifecycle) already serializes every finalize against all writers of
|
|
61
|
-
* the same canonical checkout, so applies never race each other here. */
|
|
62
|
-
function terminateCommandTree(child: ChildProcess, force: boolean, processGroup: boolean): void {
|
|
63
|
-
if (process.platform === "win32" && child.pid !== undefined) {
|
|
64
|
-
const fallback = (): void => {
|
|
65
|
-
try {
|
|
66
|
-
child.kill(force ? "SIGKILL" : "SIGTERM");
|
|
67
|
-
} catch {
|
|
68
|
-
/* process may already be gone */
|
|
69
|
-
}
|
|
70
|
-
};
|
|
71
|
-
const killer = spawn("taskkill", ["/pid", String(child.pid), "/t", "/f"], {
|
|
72
|
-
stdio: "ignore",
|
|
73
|
-
windowsHide: true,
|
|
74
|
-
});
|
|
75
|
-
killer.once("error", fallback);
|
|
76
|
-
killer.once("close", (code) => {
|
|
77
|
-
if (code !== 0) fallback();
|
|
78
|
-
});
|
|
79
|
-
return;
|
|
80
|
-
}
|
|
81
|
-
try {
|
|
82
|
-
if (processGroup && child.pid !== undefined) {
|
|
83
|
-
process.kill(-child.pid, force ? "SIGKILL" : "SIGTERM");
|
|
84
|
-
} else {
|
|
85
|
-
child.kill(force ? "SIGKILL" : "SIGTERM");
|
|
86
|
-
}
|
|
87
|
-
} catch {
|
|
88
|
-
/* process may already be gone */
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
/** Default argument-safe runner. Output is bounded before binary patches enter
|
|
93
|
-
* memory, and timeout/abort terminates the complete checkout-filter process tree. */
|
|
94
|
-
export const runCommand: CommandRunner = (command, args, options) =>
|
|
95
|
-
new Promise<CommandResult>((resolveResult, reject) => {
|
|
96
|
-
if (options.signal?.aborted) {
|
|
97
|
-
reject(new Error(`Command aborted before start: ${command}`));
|
|
98
|
-
return;
|
|
99
|
-
}
|
|
100
|
-
const usePosixProcessGroup = process.platform !== "win32";
|
|
101
|
-
const child = spawn(command, [...args], {
|
|
102
|
-
cwd: options.cwd,
|
|
103
|
-
shell: false,
|
|
104
|
-
windowsHide: true,
|
|
105
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
106
|
-
detached: usePosixProcessGroup,
|
|
107
|
-
...(options.env ? { env: { ...process.env, ...options.env } } : {}),
|
|
108
|
-
});
|
|
109
|
-
const stdout: Buffer[] = [];
|
|
110
|
-
const stderr: Buffer[] = [];
|
|
111
|
-
const maxOutputBytes = options.maxOutputBytes ?? GIT_OUTPUT_MAX_BYTES;
|
|
112
|
-
let outputBytes = 0;
|
|
113
|
-
let finished = false;
|
|
114
|
-
let failure: Error | undefined;
|
|
115
|
-
let timeout: ReturnType<typeof setTimeout> | undefined;
|
|
116
|
-
let forceKillTimer: ReturnType<typeof setTimeout> | undefined;
|
|
117
|
-
|
|
118
|
-
const terminate = (): void => {
|
|
119
|
-
terminateCommandTree(child, false, usePosixProcessGroup);
|
|
120
|
-
if (!forceKillTimer) {
|
|
121
|
-
forceKillTimer = setTimeout(
|
|
122
|
-
() => terminateCommandTree(child, true, usePosixProcessGroup),
|
|
123
|
-
GIT_COMMAND_KILL_GRACE_MS,
|
|
124
|
-
);
|
|
125
|
-
if (typeof forceKillTimer.unref === "function") forceKillTimer.unref();
|
|
126
|
-
}
|
|
127
|
-
};
|
|
128
|
-
const fail = (error: Error): void => {
|
|
129
|
-
if (failure || finished) return;
|
|
130
|
-
failure = error;
|
|
131
|
-
terminate();
|
|
132
|
-
};
|
|
133
|
-
const append = (target: Buffer[], chunk: Buffer | string): void => {
|
|
134
|
-
if (failure || finished) return;
|
|
135
|
-
const value = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
136
|
-
outputBytes += value.length;
|
|
137
|
-
if (outputBytes > maxOutputBytes) {
|
|
138
|
-
fail(new Error(`Command output exceeded ${maxOutputBytes} bytes: ${command}`));
|
|
139
|
-
return;
|
|
140
|
-
}
|
|
141
|
-
target.push(value);
|
|
142
|
-
};
|
|
143
|
-
const onAbort = (): void => fail(new Error(`Command aborted: ${command}`));
|
|
144
|
-
options.signal?.addEventListener("abort", onAbort, { once: true });
|
|
145
|
-
if (options.timeoutMs !== undefined && options.timeoutMs > 0) {
|
|
146
|
-
timeout = setTimeout(
|
|
147
|
-
() => fail(new Error(`Command timed out after ${options.timeoutMs}ms: ${command}`)),
|
|
148
|
-
options.timeoutMs,
|
|
149
|
-
);
|
|
150
|
-
if (typeof timeout.unref === "function") timeout.unref();
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
child.stdout?.on("data", (chunk: Buffer | string) => append(stdout, chunk));
|
|
154
|
-
child.stderr?.on("data", (chunk: Buffer | string) => append(stderr, chunk));
|
|
155
|
-
child.once("error", (error) => fail(error));
|
|
156
|
-
child.once("close", (code) => {
|
|
157
|
-
if (finished) return;
|
|
158
|
-
finished = true;
|
|
159
|
-
if (timeout) clearTimeout(timeout);
|
|
160
|
-
if (forceKillTimer) clearTimeout(forceKillTimer);
|
|
161
|
-
options.signal?.removeEventListener("abort", onAbort);
|
|
162
|
-
if (failure) {
|
|
163
|
-
reject(failure);
|
|
164
|
-
return;
|
|
165
|
-
}
|
|
166
|
-
resolveResult({
|
|
167
|
-
code: code ?? 1,
|
|
168
|
-
stdout: Buffer.concat(stdout),
|
|
169
|
-
stderr: Buffer.concat(stderr),
|
|
170
|
-
});
|
|
171
|
-
});
|
|
172
|
-
child.stdin?.once("error", () => undefined);
|
|
173
|
-
child.stdin?.end(options.input);
|
|
174
|
-
});
|
|
175
|
-
|
|
176
38
|
export interface WorktreeTarget {
|
|
177
39
|
/** Canonical cwd requested by the caller. */
|
|
178
40
|
originalCwd: string;
|
|
@@ -663,7 +525,8 @@ class GitWorktreeIsolation implements WorktreeIsolation {
|
|
|
663
525
|
* that index, so everything runs against a private copy of the checkout's
|
|
664
526
|
* index: the copy first absorbs the current unstaged state (`add -A`),
|
|
665
527
|
* making the working tree "ours" of the merge, and the user's real staged
|
|
666
|
-
* state is never touched.
|
|
528
|
+
* state is never touched. The repository lane serializes finalization, and
|
|
529
|
+
* each `git apply` validates and writes in one process. */
|
|
667
530
|
private async applyPatchThreeWay(): Promise<boolean> {
|
|
668
531
|
const indexCopy = join(this.tempDir, "apply-index");
|
|
669
532
|
try {
|
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
* failure directly so it is never delayed.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import { formatUsageCompact, sumUsage, type RunWaitReason } from "
|
|
11
|
-
import type { UsageStats } from "
|
|
10
|
+
import { formatUsageCompact, sumUsage, type RunWaitReason } from "../presentation/monitor.ts";
|
|
11
|
+
import type { UsageStats } from "../execution/rpc-control.ts";
|
|
12
12
|
|
|
13
13
|
export interface CompletionBatchTimings {
|
|
14
14
|
debounceMs: number;
|
|
@@ -18,9 +18,9 @@ import { existsSync, type Dirent, readdirSync, statSync } from "node:fs";
|
|
|
18
18
|
import { mkdir, readFile, rename, rm, writeFile } from "node:fs/promises";
|
|
19
19
|
import { uptime } from "node:os";
|
|
20
20
|
import { dirname, join } from "node:path";
|
|
21
|
-
import type { UsageStats } from "
|
|
21
|
+
import type { UsageStats } from "../execution/rpc-control.ts";
|
|
22
22
|
import type { SubagentThread } from "./runtime.ts";
|
|
23
|
-
import { getResultOutput, isFailedResult, getProjectRoot, getSubagentsRoot, type SingleResult } from "
|
|
23
|
+
import { getResultOutput, isFailedResult, getProjectRoot, getSubagentsRoot, type SingleResult } from "../execution/spawn.ts";
|
|
24
24
|
import {
|
|
25
25
|
isPathInside,
|
|
26
26
|
restoreWorktreeIsolation,
|
|
@@ -28,7 +28,7 @@ import {
|
|
|
28
28
|
normalizeWorktreeSnapshot,
|
|
29
29
|
worktreeSnapshot,
|
|
30
30
|
type WorktreeSnapshot,
|
|
31
|
-
} from "
|
|
31
|
+
} from "../isolation/worktree.ts";
|
|
32
32
|
|
|
33
33
|
export const THREADS_MANIFEST_FILE_NAME = "pi-subagents-threads.json";
|
|
34
34
|
const THREADS_MANIFEST_VERSION = 1;
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
12
12
|
import { rmSync } from "node:fs";
|
|
13
|
-
import { resolveSubagentConcurrency, BackgroundTaskQueue } from "
|
|
13
|
+
import { resolveSubagentConcurrency, BackgroundTaskQueue } from "../execution/background.ts";
|
|
14
14
|
import {
|
|
15
15
|
createCompletionBatcher,
|
|
16
16
|
formatActiveRunsFooter,
|
|
@@ -18,13 +18,13 @@ import {
|
|
|
18
18
|
type CompletionBatcher,
|
|
19
19
|
type CompletionMessageItem,
|
|
20
20
|
} from "./completion.ts";
|
|
21
|
-
import { type ThinkingLevel } from "
|
|
21
|
+
import { type ThinkingLevel } from "../configuration/config.ts";
|
|
22
22
|
import { removeThreadRecord, threadRecordFromThread, upsertThreadRecord, type ThreadRecord } from "./durable.ts";
|
|
23
|
-
import { isRunActiveStatus, monitor } from "
|
|
24
|
-
import type { RpcRunControl } from "
|
|
25
|
-
import type { StartBackgroundInternal } from "./thread-
|
|
26
|
-
import { isFailedResult, type SingleResult } from "
|
|
27
|
-
import type { IsolationMode, WorktreeFinalization, WorktreeIsolation } from "
|
|
23
|
+
import { isRunActiveStatus, monitor } from "../presentation/monitor.ts";
|
|
24
|
+
import type { RpcRunControl } from "../execution/rpc-control.ts";
|
|
25
|
+
import type { StartBackgroundInternal } from "./thread-shared.ts";
|
|
26
|
+
import { isFailedResult, type SingleResult } from "../execution/spawn.ts";
|
|
27
|
+
import type { IsolationMode, WorktreeFinalization, WorktreeIsolation } from "../isolation/worktree.ts";
|
|
28
28
|
|
|
29
29
|
export type ThreadState =
|
|
30
30
|
| "queued"
|