@pi-unipi/subagents 2.6.0 → 2.6.2
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 +89 -78
- package/agents/delegate.md +14 -0
- package/agents/oracle.md +78 -0
- package/agents/researcher.md +52 -0
- package/agents/reviewer.md +79 -0
- package/agents/scout.md +50 -0
- package/agents/worker.md +59 -0
- package/package.json +19 -17
- package/prompts/council.md +48 -0
- package/prompts/gather-context-and-clarify.md +13 -0
- package/prompts/parallel-cleanup.md +59 -0
- package/prompts/parallel-research.md +50 -0
- package/prompts/parallel-review.md +54 -0
- package/prompts/review-loop.md +43 -0
- package/skills/council-mode/SKILL.md +230 -0
- package/skills/subagents/SKILL.md +49 -0
- package/skills/subagents/references/constraints-and-recipes.md +259 -0
- package/skills/subagents/references/execution-controls.md +454 -0
- package/skills/subagents/references/management-authoring-rpc.md +161 -0
- package/skills/subagents/references/multi-lane-orchestration.md +39 -0
- package/skills/subagents/references/prompting-and-roles.md +267 -0
- package/src/acceptance.ts +331 -0
- package/src/agent-manager.ts +405 -0
- package/src/agent-memory.ts +300 -0
- package/src/agent-overrides.ts +183 -0
- package/src/agent-runner.ts +265 -0
- package/src/async-runner.ts +417 -0
- package/src/authority-policy.ts +47 -0
- package/src/budgets.ts +285 -0
- package/src/child-safety.ts +140 -0
- package/src/config.ts +226 -0
- package/src/conversation-viewer.ts +281 -0
- package/src/core-compat.ts +94 -0
- package/src/custom-agents.ts +323 -0
- package/src/file-system-retry.ts +50 -0
- package/src/fleet-data.ts +61 -0
- package/src/fleet-view.ts +316 -0
- package/src/foreground-detach.ts +59 -0
- package/src/fork-context.ts +285 -0
- package/src/global.d.ts +7 -0
- package/src/guide.ts +129 -0
- package/src/index.ts +1149 -0
- package/src/mission-state.ts +133 -0
- package/src/mission-store.ts +434 -0
- package/src/model-resolver.ts +79 -0
- package/src/output-limits.ts +142 -0
- package/src/parity-types.ts +315 -0
- package/src/pi-args.ts +161 -0
- package/src/pi-spawn.ts +147 -0
- package/src/result-files.ts +232 -0
- package/src/result-watcher.ts +221 -0
- package/src/retained-children.ts +176 -0
- package/src/run-fanout-budget.ts +356 -0
- package/src/scheduled-runs.ts +351 -0
- package/src/schemas.ts +263 -0
- package/src/slash-commands.ts +77 -0
- package/src/supervisor-channel.ts +249 -0
- package/src/tool-handler.ts +1144 -0
- package/src/types.ts +238 -0
- package/src/widget.ts +447 -0
- package/src/workflow-script.ts +737 -0
- package/src/workflow-worker.ts +384 -0
- package/src/worktree.ts +614 -0
- package/dist/agent-manager.d.ts +0 -81
- package/dist/agent-manager.d.ts.map +0 -1
- package/dist/agent-manager.js +0 -295
- package/dist/agent-manager.js.map +0 -1
- package/dist/agent-runner.d.ts +0 -51
- package/dist/agent-runner.d.ts.map +0 -1
- package/dist/agent-runner.js +0 -262
- package/dist/agent-runner.js.map +0 -1
- package/dist/config.d.ts +0 -24
- package/dist/config.d.ts.map +0 -1
- package/dist/config.js +0 -132
- package/dist/config.js.map +0 -1
- package/dist/conversation-viewer.d.ts +0 -40
- package/dist/conversation-viewer.d.ts.map +0 -1
- package/dist/conversation-viewer.js +0 -276
- package/dist/conversation-viewer.js.map +0 -1
- package/dist/core-compat.d.ts +0 -21
- package/dist/core-compat.d.ts.map +0 -1
- package/dist/core-compat.js +0 -86
- package/dist/core-compat.js.map +0 -1
- package/dist/custom-agents.d.ts +0 -14
- package/dist/custom-agents.d.ts.map +0 -1
- package/dist/custom-agents.js +0 -113
- package/dist/custom-agents.js.map +0 -1
- package/dist/file-lock.d.ts +0 -42
- package/dist/file-lock.d.ts.map +0 -1
- package/dist/file-lock.js +0 -91
- package/dist/file-lock.js.map +0 -1
- package/dist/index.d.ts +0 -10
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -758
- package/dist/index.js.map +0 -1
- package/dist/model-resolver.d.ts +0 -19
- package/dist/model-resolver.d.ts.map +0 -1
- package/dist/model-resolver.js +0 -61
- package/dist/model-resolver.js.map +0 -1
- package/dist/types.d.ts +0 -98
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js +0 -47
- package/dist/types.js.map +0 -1
- package/dist/widget.d.ts +0 -56
- package/dist/widget.d.ts.map +0 -1
- package/dist/widget.js +0 -396
- package/dist/widget.js.map +0 -1
package/src/worktree.ts
ADDED
|
@@ -0,0 +1,614 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @pi-unipi/subagents — Worktree isolation
|
|
3
|
+
*
|
|
4
|
+
* Ported from pi-subagents src/runs/shared/worktree.ts. Managed git worktrees
|
|
5
|
+
* per child (branch unipi-parallel-<runId>-<index> under a base dir; default
|
|
6
|
+
* OS temp, UNIPI_SUBAGENTS_WORKTREE_DIR override), node_modules symlink,
|
|
7
|
+
* optional setup hooks (JSON-in/JSON-out, syntheticPaths validation), diff
|
|
8
|
+
* capture as handoff patches, and cleanup with safety checks: dirty worktrees
|
|
9
|
+
* are preserved unless a captured patch represents the work; discard requires
|
|
10
|
+
* the authorityPolicy to authorize.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { spawnSync } from "node:child_process";
|
|
14
|
+
import * as fs from "node:fs";
|
|
15
|
+
import * as os from "node:os";
|
|
16
|
+
import * as path from "node:path";
|
|
17
|
+
|
|
18
|
+
export interface WorktreeInfo {
|
|
19
|
+
index: number;
|
|
20
|
+
path: string;
|
|
21
|
+
branch: string;
|
|
22
|
+
agentCwd: string;
|
|
23
|
+
syntheticPaths: string[];
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface WorktreeSetup {
|
|
27
|
+
cwd: string;
|
|
28
|
+
worktrees: WorktreeInfo[];
|
|
29
|
+
baseCommit: string;
|
|
30
|
+
capturedDiffs?: WorktreeDiff[];
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface WorktreeDiff {
|
|
34
|
+
index: number;
|
|
35
|
+
agent: string;
|
|
36
|
+
branch: string;
|
|
37
|
+
patchPath: string;
|
|
38
|
+
filesChanged: number;
|
|
39
|
+
insertions: number;
|
|
40
|
+
deletions: number;
|
|
41
|
+
diffStat: string;
|
|
42
|
+
error?: string;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface WorktreeCleanupTask {
|
|
46
|
+
index: number;
|
|
47
|
+
path: string;
|
|
48
|
+
branch: string;
|
|
49
|
+
worktreeRemoved: boolean;
|
|
50
|
+
branchRemoved: boolean;
|
|
51
|
+
preserved?: boolean;
|
|
52
|
+
reason?: string;
|
|
53
|
+
errors?: string[];
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export type WorktreeCleanupIntent =
|
|
57
|
+
| { kind: "preserve"; capturedDiffs?: WorktreeDiff[]; handoffManifestPath?: string }
|
|
58
|
+
| { kind: "discard"; authorization: { policy: "confirm" | "auto"; kind?: "confirmed" } }
|
|
59
|
+
| { kind: "setup-rollback" };
|
|
60
|
+
|
|
61
|
+
export interface WorktreeCleanupReport {
|
|
62
|
+
state: "complete" | "partial";
|
|
63
|
+
tasks: WorktreeCleanupTask[];
|
|
64
|
+
pruned: boolean;
|
|
65
|
+
errors?: string[];
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
interface GitResult {
|
|
69
|
+
stdout: string;
|
|
70
|
+
stderr: string;
|
|
71
|
+
status: number | null;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
interface RepoState {
|
|
75
|
+
toplevel: string;
|
|
76
|
+
cwdRelative: string;
|
|
77
|
+
baseCommit: string;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const DEFAULT_WORKTREE_SETUP_HOOK_TIMEOUT_MS = 30000;
|
|
81
|
+
export const UNIPI_SUBAGENTS_WORKTREE_DIR_ENV = "UNIPI_SUBAGENTS_WORKTREE_DIR";
|
|
82
|
+
|
|
83
|
+
function runGit(cwd: string, args: string[]): GitResult {
|
|
84
|
+
const result = spawnSync("git", ["-C", cwd, ...args], { encoding: "utf-8", windowsHide: true });
|
|
85
|
+
return { stdout: result.stdout ?? "", stderr: result.stderr ?? "", status: result.status };
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function runGitChecked(cwd: string, args: string[]): string {
|
|
89
|
+
const result = runGit(cwd, args);
|
|
90
|
+
if (result.status !== 0) {
|
|
91
|
+
const command = `git -C ${cwd} ${args.join(" ")}`;
|
|
92
|
+
const message = result.stderr.trim() || result.stdout.trim() || `${command} failed`;
|
|
93
|
+
throw new Error(message);
|
|
94
|
+
}
|
|
95
|
+
return result.stdout;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function resolveRepoCwdRelative(cwd: string): string {
|
|
99
|
+
const repoCheck = runGit(cwd, ["rev-parse", "--is-inside-work-tree"]);
|
|
100
|
+
if (repoCheck.status !== 0 || repoCheck.stdout.trim() !== "true") {
|
|
101
|
+
throw new Error("worktree isolation requires a git repository");
|
|
102
|
+
}
|
|
103
|
+
const rawPrefix = runGitChecked(cwd, ["rev-parse", "--show-prefix"]).trim();
|
|
104
|
+
const normalizedPrefix = rawPrefix ? path.normalize(rawPrefix.replace(/[\\/]+$/, "")) : "";
|
|
105
|
+
return normalizedPrefix === "." ? "" : normalizedPrefix;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function resolveRepoState(cwd: string): RepoState {
|
|
109
|
+
const cwdRelative = resolveRepoCwdRelative(cwd);
|
|
110
|
+
const toplevel = runGitChecked(cwd, ["rev-parse", "--show-toplevel"]).trim();
|
|
111
|
+
const status = runGitChecked(toplevel, ["status", "--porcelain"]);
|
|
112
|
+
if (status.trim().length > 0) {
|
|
113
|
+
throw new Error("worktree isolation requires a clean git working tree. Commit or stash changes first.");
|
|
114
|
+
}
|
|
115
|
+
const baseCommit = runGitChecked(toplevel, ["rev-parse", "HEAD"]).trim();
|
|
116
|
+
return { toplevel, cwdRelative, baseCommit };
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function normalizeComparableCwd(cwd: string): string {
|
|
120
|
+
const resolved = path.resolve(cwd);
|
|
121
|
+
let existing = resolved;
|
|
122
|
+
const missingSegments: string[] = [];
|
|
123
|
+
for (;;) {
|
|
124
|
+
try {
|
|
125
|
+
return path.join(fs.realpathSync(existing), ...missingSegments.reverse());
|
|
126
|
+
} catch {
|
|
127
|
+
const parent = path.dirname(existing);
|
|
128
|
+
if (parent === existing) return resolved;
|
|
129
|
+
missingSegments.push(path.basename(existing));
|
|
130
|
+
existing = parent;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export function findWorktreeTaskCwdConflict(
|
|
136
|
+
tasks: ReadonlyArray<{ agent: string; cwd?: string }>,
|
|
137
|
+
sharedCwd: string,
|
|
138
|
+
): { index: number; agent: string; cwd: string } | undefined {
|
|
139
|
+
const normalizedSharedCwd = normalizeComparableCwd(sharedCwd);
|
|
140
|
+
for (let index = 0; index < tasks.length; index++) {
|
|
141
|
+
const task = tasks[index]!;
|
|
142
|
+
if (!task.cwd) continue;
|
|
143
|
+
const taskCwd = path.isAbsolute(task.cwd) ? task.cwd : path.resolve(sharedCwd, task.cwd);
|
|
144
|
+
if (normalizeComparableCwd(taskCwd) === normalizedSharedCwd) continue;
|
|
145
|
+
return { index, agent: task.agent, cwd: task.cwd };
|
|
146
|
+
}
|
|
147
|
+
return undefined;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export function formatWorktreeTaskCwdConflict(
|
|
151
|
+
conflict: { index: number; agent: string; cwd: string },
|
|
152
|
+
sharedCwd: string,
|
|
153
|
+
): string {
|
|
154
|
+
return `worktree isolation uses the shared cwd (${sharedCwd}); task ${conflict.index + 1} (${conflict.agent}) sets cwd to ${conflict.cwd}. Remove task-level cwd overrides or disable worktree.`;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function safePatchAgentName(agent: string): string {
|
|
158
|
+
return agent.replace(/[^\w.-]/g, "_");
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function buildWorktreeBranch(runId: string, index: number): string {
|
|
162
|
+
return `unipi-parallel-${runId}-${index}`;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function resolveWorktreeBaseDir(configuredBaseDir: string | undefined, repoRoot: string): string {
|
|
166
|
+
const rawBaseDir = configuredBaseDir ?? process.env[UNIPI_SUBAGENTS_WORKTREE_DIR_ENV];
|
|
167
|
+
if (rawBaseDir === undefined) return os.tmpdir();
|
|
168
|
+
const trimmed = rawBaseDir.trim();
|
|
169
|
+
if (!trimmed) throw new Error("worktree base directory cannot be empty");
|
|
170
|
+
const expanded = trimmed.startsWith("~/") ? path.join(os.homedir(), trimmed.slice(2)) : trimmed;
|
|
171
|
+
const resolved = path.isAbsolute(expanded) ? expanded : path.resolve(repoRoot, expanded);
|
|
172
|
+
try {
|
|
173
|
+
fs.mkdirSync(resolved, { recursive: true });
|
|
174
|
+
} catch (error) {
|
|
175
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
176
|
+
throw new Error(`failed to create worktree base directory ${resolved}: ${message}`);
|
|
177
|
+
}
|
|
178
|
+
return resolved;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function buildWorktreePath(baseDir: string, runId: string, index: number): string {
|
|
182
|
+
return path.join(baseDir, `unipi-worktree-${runId}-${index}`);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
export function resolveExpectedWorktreeAgentCwd(cwd: string, runId: string, index: number, baseDir?: string): string {
|
|
186
|
+
const cwdRelative = resolveRepoCwdRelative(cwd);
|
|
187
|
+
const repoRoot = runGitChecked(cwd, ["rev-parse", "--show-toplevel"]).trim();
|
|
188
|
+
const worktreePath = buildWorktreePath(resolveWorktreeBaseDir(baseDir, repoRoot), runId, index);
|
|
189
|
+
return cwdRelative ? path.join(worktreePath, cwdRelative) : worktreePath;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function linkNodeModulesIfPresent(toplevel: string, worktreePath: string): boolean {
|
|
193
|
+
const nodeModulesPath = path.join(toplevel, "node_modules");
|
|
194
|
+
const nodeModulesLinkPath = path.join(worktreePath, "node_modules");
|
|
195
|
+
if (!fs.existsSync(nodeModulesPath) || fs.existsSync(nodeModulesLinkPath)) return false;
|
|
196
|
+
try {
|
|
197
|
+
fs.symlinkSync(nodeModulesPath, nodeModulesLinkPath);
|
|
198
|
+
return true;
|
|
199
|
+
} catch {
|
|
200
|
+
return false;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function parseHookTimeout(timeoutMs: number | undefined): number {
|
|
205
|
+
if (timeoutMs === undefined) return DEFAULT_WORKTREE_SETUP_HOOK_TIMEOUT_MS;
|
|
206
|
+
if (!Number.isInteger(timeoutMs) || timeoutMs <= 0) {
|
|
207
|
+
throw new Error("worktree setup hook timeout must be an integer greater than 0");
|
|
208
|
+
}
|
|
209
|
+
return timeoutMs;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
export interface WorktreeSetupHookConfig {
|
|
213
|
+
hookPath: string;
|
|
214
|
+
timeoutMs?: number;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
interface ResolvedWorktreeSetupHook {
|
|
218
|
+
hookPath: string;
|
|
219
|
+
timeoutMs: number;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function resolveWorktreeSetupHook(
|
|
223
|
+
repoRoot: string,
|
|
224
|
+
config: WorktreeSetupHookConfig | undefined,
|
|
225
|
+
): ResolvedWorktreeSetupHook | undefined {
|
|
226
|
+
if (!config) return undefined;
|
|
227
|
+
const hookPath = config.hookPath.trim();
|
|
228
|
+
if (!hookPath) throw new Error("worktree setup hook path cannot be empty");
|
|
229
|
+
const expandedHookPath = hookPath.startsWith("~/") ? path.join(os.homedir(), hookPath.slice(2)) : hookPath;
|
|
230
|
+
let resolvedPath: string;
|
|
231
|
+
if (path.isAbsolute(expandedHookPath)) resolvedPath = expandedHookPath;
|
|
232
|
+
else if (expandedHookPath.includes("/") || expandedHookPath.includes("\\")) resolvedPath = path.resolve(repoRoot, expandedHookPath);
|
|
233
|
+
else throw new Error("worktree setup hook must be an absolute path or a repo-relative path");
|
|
234
|
+
if (!fs.existsSync(resolvedPath)) throw new Error(`worktree setup hook not found: ${resolvedPath}`);
|
|
235
|
+
if (fs.statSync(resolvedPath).isDirectory()) {
|
|
236
|
+
throw new Error(`worktree setup hook must be a file, got directory: ${resolvedPath}`);
|
|
237
|
+
}
|
|
238
|
+
return { hookPath: resolvedPath, timeoutMs: parseHookTimeout(config.timeoutMs) };
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function normalizeSyntheticPath(worktreePath: string, rawPath: string): string {
|
|
242
|
+
const trimmed = rawPath.trim();
|
|
243
|
+
if (!trimmed) throw new Error("synthetic path cannot be empty");
|
|
244
|
+
if (path.isAbsolute(trimmed)) throw new Error(`synthetic path must be relative: ${rawPath}`);
|
|
245
|
+
const resolved = path.resolve(worktreePath, trimmed);
|
|
246
|
+
const relative = path.relative(worktreePath, resolved);
|
|
247
|
+
if (!relative || relative === ".") throw new Error(`synthetic path cannot target the worktree root: ${rawPath}`);
|
|
248
|
+
if (relative === ".." || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative)) {
|
|
249
|
+
throw new Error(`synthetic path escapes the worktree root: ${rawPath}`);
|
|
250
|
+
}
|
|
251
|
+
return path.normalize(relative);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
function hasTrackedEntries(worktreePath: string, relativePath: string): boolean {
|
|
255
|
+
const result = runGit(worktreePath, ["ls-files", "--", relativePath]);
|
|
256
|
+
return result.status === 0 && result.stdout.trim().length > 0;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function runWorktreeSetupHook(hook: ResolvedWorktreeSetupHook, input: Record<string, unknown>): string[] {
|
|
260
|
+
const result = spawnSync(hook.hookPath, [], {
|
|
261
|
+
windowsHide: true,
|
|
262
|
+
cwd: input.worktreePath as string,
|
|
263
|
+
encoding: "utf-8",
|
|
264
|
+
input: JSON.stringify(input),
|
|
265
|
+
timeout: hook.timeoutMs,
|
|
266
|
+
shell: false,
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
if (result.error) {
|
|
270
|
+
const code = "code" in result.error ? (result.error as { code?: string }).code : undefined;
|
|
271
|
+
if (code === "ETIMEDOUT") throw new Error(`worktree setup hook timed out after ${hook.timeoutMs}ms`);
|
|
272
|
+
throw new Error(`worktree setup hook failed: ${result.error.message}`);
|
|
273
|
+
}
|
|
274
|
+
if (result.status !== 0) {
|
|
275
|
+
const details = result.stderr?.trim() || result.stdout?.trim() || "no output";
|
|
276
|
+
throw new Error(`worktree setup hook failed with exit code ${result.status}: ${details}`);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
const trimmed = (result.stdout ?? "").trim();
|
|
280
|
+
if (!trimmed) throw new Error("worktree setup hook returned empty stdout; expected JSON object");
|
|
281
|
+
let parsed: unknown;
|
|
282
|
+
try {
|
|
283
|
+
parsed = JSON.parse(trimmed);
|
|
284
|
+
} catch (error) {
|
|
285
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
286
|
+
throw new Error(`worktree setup hook returned invalid JSON: ${message}`);
|
|
287
|
+
}
|
|
288
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
289
|
+
throw new Error("worktree setup hook stdout must be a JSON object");
|
|
290
|
+
}
|
|
291
|
+
const output = parsed as { syntheticPaths?: unknown };
|
|
292
|
+
if (output.syntheticPaths === undefined) return [];
|
|
293
|
+
if (!Array.isArray(output.syntheticPaths)) {
|
|
294
|
+
throw new Error("worktree setup hook output field 'syntheticPaths' must be an array of relative paths");
|
|
295
|
+
}
|
|
296
|
+
const uniquePaths = new Set<string>();
|
|
297
|
+
for (const candidate of output.syntheticPaths) {
|
|
298
|
+
if (typeof candidate !== "string") {
|
|
299
|
+
throw new Error("worktree setup hook output field 'syntheticPaths' must contain only strings");
|
|
300
|
+
}
|
|
301
|
+
const normalizedPath = normalizeSyntheticPath(input.worktreePath as string, candidate);
|
|
302
|
+
if (hasTrackedEntries(input.worktreePath as string, normalizedPath)) {
|
|
303
|
+
throw new Error(`worktree setup hook cannot mark tracked paths as synthetic: ${normalizedPath}`);
|
|
304
|
+
}
|
|
305
|
+
uniquePaths.add(normalizedPath);
|
|
306
|
+
}
|
|
307
|
+
return [...uniquePaths];
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
function createSingleWorktree(
|
|
311
|
+
toplevel: string,
|
|
312
|
+
cwdRelative: string,
|
|
313
|
+
runId: string,
|
|
314
|
+
index: number,
|
|
315
|
+
baseCommit: string,
|
|
316
|
+
setupHook: ResolvedWorktreeSetupHook | undefined,
|
|
317
|
+
agent: string | undefined,
|
|
318
|
+
baseDir: string,
|
|
319
|
+
): WorktreeInfo {
|
|
320
|
+
const branch = buildWorktreeBranch(runId, index);
|
|
321
|
+
const worktreePath = buildWorktreePath(baseDir, runId, index);
|
|
322
|
+
const add = runGit(toplevel, ["worktree", "add", worktreePath, "-b", branch, "HEAD"]);
|
|
323
|
+
if (add.status !== 0) {
|
|
324
|
+
const message = add.stderr.trim() || add.stdout.trim() || `failed to create worktree ${worktreePath}`;
|
|
325
|
+
throw new Error(message);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
const agentCwd = cwdRelative ? path.join(worktreePath, cwdRelative) : worktreePath;
|
|
329
|
+
try {
|
|
330
|
+
const nodeModulesLinked = linkNodeModulesIfPresent(toplevel, worktreePath);
|
|
331
|
+
const syntheticPaths = nodeModulesLinked ? ["node_modules"] : [];
|
|
332
|
+
|
|
333
|
+
if (setupHook) {
|
|
334
|
+
const hookSyntheticPaths = runWorktreeSetupHook(setupHook, {
|
|
335
|
+
version: 1,
|
|
336
|
+
repoRoot: toplevel,
|
|
337
|
+
worktreePath,
|
|
338
|
+
agentCwd,
|
|
339
|
+
branch,
|
|
340
|
+
index,
|
|
341
|
+
runId,
|
|
342
|
+
baseCommit,
|
|
343
|
+
agent,
|
|
344
|
+
});
|
|
345
|
+
syntheticPaths.push(...hookSyntheticPaths);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
return { index, path: worktreePath, branch, agentCwd, syntheticPaths };
|
|
349
|
+
} catch (error) {
|
|
350
|
+
// Roll back this worktree on setup failure.
|
|
351
|
+
try {
|
|
352
|
+
runGitChecked(toplevel, ["worktree", "remove", "--force", worktreePath]);
|
|
353
|
+
runGitChecked(toplevel, ["branch", "-D", branch]);
|
|
354
|
+
} catch {
|
|
355
|
+
// best effort
|
|
356
|
+
}
|
|
357
|
+
throw error;
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
export interface CreateWorktreesOptions {
|
|
362
|
+
agents?: string[];
|
|
363
|
+
setupHook?: WorktreeSetupHookConfig;
|
|
364
|
+
baseDir?: string;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
export function createWorktrees(cwd: string, runId: string, count: number, options?: CreateWorktreesOptions): WorktreeSetup {
|
|
368
|
+
const repo = resolveRepoState(cwd);
|
|
369
|
+
const setupHook = resolveWorktreeSetupHook(repo.toplevel, options?.setupHook);
|
|
370
|
+
const baseDir = resolveWorktreeBaseDir(options?.baseDir, repo.toplevel);
|
|
371
|
+
const worktrees: WorktreeInfo[] = [];
|
|
372
|
+
|
|
373
|
+
try {
|
|
374
|
+
for (let index = 0; index < count; index++) {
|
|
375
|
+
worktrees.push(
|
|
376
|
+
createSingleWorktree(repo.toplevel, repo.cwdRelative, runId, index, repo.baseCommit, setupHook, options?.agents?.[index], baseDir),
|
|
377
|
+
);
|
|
378
|
+
}
|
|
379
|
+
} catch (error) {
|
|
380
|
+
cleanupWorktrees({ cwd: repo.toplevel, worktrees, baseCommit: repo.baseCommit }, { kind: "setup-rollback" });
|
|
381
|
+
throw error;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
return { cwd: repo.toplevel, worktrees, baseCommit: repo.baseCommit };
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
function writeEmptyPatch(patchPath: string): void {
|
|
388
|
+
fs.mkdirSync(path.dirname(patchPath), { recursive: true });
|
|
389
|
+
fs.writeFileSync(patchPath, "");
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
function captureWorktreeDiff(
|
|
393
|
+
setup: WorktreeSetup,
|
|
394
|
+
worktree: WorktreeInfo,
|
|
395
|
+
agent: string,
|
|
396
|
+
patchPath: string,
|
|
397
|
+
): WorktreeDiff {
|
|
398
|
+
// Diff ALL work on the branch: committed (vs the setup base commit) and
|
|
399
|
+
// uncommitted, excluding synthetic paths so helper files don't pollute patches.
|
|
400
|
+
const excludeArgs = worktree.syntheticPaths.flatMap((p) => ["--", `:!${p}`]);
|
|
401
|
+
const diffStat = runGitChecked(worktree.path, ["diff", "--stat", setup.baseCommit, ...excludeArgs]);
|
|
402
|
+
const patch = runGit(worktree.path, ["diff", setup.baseCommit, ...excludeArgs]);
|
|
403
|
+
|
|
404
|
+
fs.mkdirSync(path.dirname(patchPath), { recursive: true });
|
|
405
|
+
fs.writeFileSync(patchPath, patch.stdout);
|
|
406
|
+
|
|
407
|
+
let filesChanged = 0;
|
|
408
|
+
let insertions = 0;
|
|
409
|
+
let deletions = 0;
|
|
410
|
+
for (const line of diffStat.split("\n")) {
|
|
411
|
+
const match = line.match(/(\d+) files? changed(?:, (\d+) insertions?\(\+\))?(?:, (\d+) deletions?\(-\))?/);
|
|
412
|
+
if (match) {
|
|
413
|
+
filesChanged = Number(match[1]);
|
|
414
|
+
insertions = Number(match[2] ?? 0);
|
|
415
|
+
deletions = Number(match[3] ?? 0);
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
// Untracked files count as changes.
|
|
419
|
+
const untracked = runGit(worktree.path, ["status", "--porcelain"]);
|
|
420
|
+
const hasUntracked = untracked.stdout
|
|
421
|
+
.split("\n")
|
|
422
|
+
.some((line) => line.startsWith("??") && !worktree.syntheticPaths.some((p) => line.includes(p)));
|
|
423
|
+
|
|
424
|
+
return {
|
|
425
|
+
index: worktree.index,
|
|
426
|
+
agent,
|
|
427
|
+
branch: worktree.branch,
|
|
428
|
+
patchPath,
|
|
429
|
+
filesChanged: filesChanged + (hasUntracked ? 1 : 0),
|
|
430
|
+
insertions,
|
|
431
|
+
deletions,
|
|
432
|
+
diffStat,
|
|
433
|
+
};
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
export function diffWorktrees(setup: WorktreeSetup, agents: string[], diffsDir: string): WorktreeDiff[] {
|
|
437
|
+
try {
|
|
438
|
+
fs.mkdirSync(diffsDir, { recursive: true });
|
|
439
|
+
} catch {
|
|
440
|
+
return [];
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
const diffs: WorktreeDiff[] = [];
|
|
444
|
+
for (let index = 0; index < setup.worktrees.length; index++) {
|
|
445
|
+
const worktree = setup.worktrees[index]!;
|
|
446
|
+
const agent = agents[index] ?? `task-${index + 1}`;
|
|
447
|
+
const patchPath = path.join(diffsDir, `task-${index}-${safePatchAgentName(agent)}.patch`);
|
|
448
|
+
try {
|
|
449
|
+
diffs.push(captureWorktreeDiff(setup, worktree, agent, patchPath));
|
|
450
|
+
} catch (error) {
|
|
451
|
+
writeEmptyPatch(patchPath);
|
|
452
|
+
diffs.push({
|
|
453
|
+
index,
|
|
454
|
+
agent,
|
|
455
|
+
branch: worktree.branch,
|
|
456
|
+
patchPath,
|
|
457
|
+
filesChanged: 0,
|
|
458
|
+
insertions: 0,
|
|
459
|
+
deletions: 0,
|
|
460
|
+
diffStat: "",
|
|
461
|
+
error: error instanceof Error ? error.message : String(error),
|
|
462
|
+
});
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
setup.capturedDiffs = diffs;
|
|
467
|
+
return diffs;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
function handoffRecordsPatch(handoffManifestPath: string | undefined, patchPath: string): boolean {
|
|
471
|
+
if (!handoffManifestPath) return false;
|
|
472
|
+
try {
|
|
473
|
+
const manifest = JSON.parse(fs.readFileSync(handoffManifestPath, "utf8")) as { patches?: string[] };
|
|
474
|
+
return Array.isArray(manifest.patches) && manifest.patches.includes(patchPath);
|
|
475
|
+
} catch {
|
|
476
|
+
return false;
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
function cleanupSingleWorktree(setup: WorktreeSetup, worktree: WorktreeInfo, intent: WorktreeCleanupIntent): WorktreeCleanupTask {
|
|
481
|
+
const errors: string[] = [];
|
|
482
|
+
let worktreeRemoved = false;
|
|
483
|
+
let branchRemoved = false;
|
|
484
|
+
|
|
485
|
+
// Safety: is there unrepresented work?
|
|
486
|
+
const status = runGit(worktree.path, ["status", "--porcelain"]);
|
|
487
|
+
if (status.status !== 0) {
|
|
488
|
+
return {
|
|
489
|
+
index: worktree.index,
|
|
490
|
+
path: worktree.path,
|
|
491
|
+
branch: worktree.branch,
|
|
492
|
+
worktreeRemoved: false,
|
|
493
|
+
branchRemoved: false,
|
|
494
|
+
preserved: true,
|
|
495
|
+
reason: "cleanup safety check failed",
|
|
496
|
+
errors: [...errors, `cleanup refused: git status failed`],
|
|
497
|
+
};
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
// Work = uncommitted changes OR committed changes on the branch that the
|
|
501
|
+
// base commit does not contain (diff against the setup base commit).
|
|
502
|
+
const baseDiff = runGit(worktree.path, ["diff", "--stat", setup.baseCommit, "HEAD"]);
|
|
503
|
+
const hasWork =
|
|
504
|
+
status.stdout.trim().length > 0 ||
|
|
505
|
+
baseDiff.status === 0
|
|
506
|
+
? status.stdout.trim().length > 0 || (baseDiff.status === 0 && baseDiff.stdout.trim().length > 0)
|
|
507
|
+
: true;
|
|
508
|
+
|
|
509
|
+
if (hasWork && intent.kind === "preserve") {
|
|
510
|
+
const captured = (intent.capturedDiffs ?? setup.capturedDiffs)?.find((diff) => diff.index === worktree.index);
|
|
511
|
+
const patchCaptured =
|
|
512
|
+
captured !== undefined &&
|
|
513
|
+
captured.error === undefined &&
|
|
514
|
+
fs.existsSync(captured.patchPath) &&
|
|
515
|
+
fs.statSync(captured.patchPath).size > 0 &&
|
|
516
|
+
handoffRecordsPatch(intent.handoffManifestPath, captured.patchPath);
|
|
517
|
+
if (!patchCaptured) {
|
|
518
|
+
const reason = "worktree contains changes that are not represented by a captured handoff patch";
|
|
519
|
+
return {
|
|
520
|
+
index: worktree.index,
|
|
521
|
+
path: worktree.path,
|
|
522
|
+
branch: worktree.branch,
|
|
523
|
+
worktreeRemoved: false,
|
|
524
|
+
branchRemoved: false,
|
|
525
|
+
preserved: true,
|
|
526
|
+
reason,
|
|
527
|
+
errors: [...errors, `cleanup refused: ${reason}; preserved ${worktree.path}`],
|
|
528
|
+
};
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
if (hasWork && intent.kind === "discard") {
|
|
533
|
+
const authorized = intent.authorization.policy === "auto" || intent.authorization.kind === "confirmed";
|
|
534
|
+
if (!authorized) {
|
|
535
|
+
const reason = "worktree discard requires explicit user confirmation";
|
|
536
|
+
return {
|
|
537
|
+
index: worktree.index,
|
|
538
|
+
path: worktree.path,
|
|
539
|
+
branch: worktree.branch,
|
|
540
|
+
worktreeRemoved: false,
|
|
541
|
+
branchRemoved: false,
|
|
542
|
+
preserved: true,
|
|
543
|
+
reason,
|
|
544
|
+
errors: [...errors, `cleanup refused: ${reason}; preserved ${worktree.path}`],
|
|
545
|
+
};
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
// setup-rollback removes unconditionally (setup failed before any work).
|
|
550
|
+
|
|
551
|
+
try {
|
|
552
|
+
runGitChecked(setup.cwd, ["worktree", "remove", "--force", worktree.path]);
|
|
553
|
+
worktreeRemoved = true;
|
|
554
|
+
} catch (error) {
|
|
555
|
+
errors.push(`worktree removal failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
556
|
+
}
|
|
557
|
+
if (worktreeRemoved) {
|
|
558
|
+
try {
|
|
559
|
+
runGitChecked(setup.cwd, ["branch", "-D", worktree.branch]);
|
|
560
|
+
branchRemoved = true;
|
|
561
|
+
} catch (error) {
|
|
562
|
+
errors.push(`branch removal failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
return {
|
|
566
|
+
index: worktree.index,
|
|
567
|
+
path: worktree.path,
|
|
568
|
+
branch: worktree.branch,
|
|
569
|
+
worktreeRemoved,
|
|
570
|
+
branchRemoved,
|
|
571
|
+
...(errors.length ? { errors } : {}),
|
|
572
|
+
};
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
function hasWorktreeChanges(diff: WorktreeDiff): boolean {
|
|
576
|
+
return diff.filesChanged > 0 || diff.insertions > 0 || diff.deletions > 0 || diff.diffStat.trim().length > 0;
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
export function cleanupWorktrees(
|
|
580
|
+
setup: WorktreeSetup,
|
|
581
|
+
intent: WorktreeCleanupIntent = { kind: "preserve", ...(setup.capturedDiffs ? { capturedDiffs: setup.capturedDiffs } : {}) },
|
|
582
|
+
): WorktreeCleanupReport {
|
|
583
|
+
const tasks: WorktreeCleanupTask[] = [];
|
|
584
|
+
for (let index = setup.worktrees.length - 1; index >= 0; index--) {
|
|
585
|
+
tasks.push(cleanupSingleWorktree(setup, setup.worktrees[index]!, intent));
|
|
586
|
+
}
|
|
587
|
+
tasks.sort((left, right) => left.index - right.index);
|
|
588
|
+
const errors: string[] = [];
|
|
589
|
+
let pruned = false;
|
|
590
|
+
try {
|
|
591
|
+
runGitChecked(setup.cwd, ["worktree", "prune"]);
|
|
592
|
+
pruned = true;
|
|
593
|
+
} catch (error) {
|
|
594
|
+
errors.push(`worktree prune failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
595
|
+
}
|
|
596
|
+
const state = tasks.every((task) => task.worktreeRemoved && task.branchRemoved) && pruned ? "complete" : "partial";
|
|
597
|
+
return { state, tasks, pruned, ...(errors.length ? { errors } : {}) };
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
export function formatWorktreeDiffSummary(diffs: WorktreeDiff[]): string {
|
|
601
|
+
const changed = diffs.filter(hasWorktreeChanges);
|
|
602
|
+
if (changed.length === 0) return "";
|
|
603
|
+
|
|
604
|
+
const lines: string[] = ["=== Worktree Changes ===", ""];
|
|
605
|
+
for (const diff of changed) {
|
|
606
|
+
lines.push(`--- Task ${diff.index + 1} (${diff.agent}): ${diff.filesChanged} files changed, +${diff.insertions} -${diff.deletions} ---`);
|
|
607
|
+
if (diff.diffStat.trim().length > 0) lines.push(diff.diffStat);
|
|
608
|
+
lines.push("");
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
const patchesDir = path.dirname(changed[0]!.patchPath);
|
|
612
|
+
lines.push(`Full patches: ${patchesDir}`);
|
|
613
|
+
return lines.join("\n").trimEnd();
|
|
614
|
+
}
|
package/dist/agent-manager.d.ts
DELETED
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @pi-unipi/subagents — Agent manager
|
|
3
|
-
*
|
|
4
|
-
* Tracks agents, manages concurrency queue, handles spawn/resume/abort.
|
|
5
|
-
* Background agents subject to concurrency limit. Foreground bypass queue.
|
|
6
|
-
*/
|
|
7
|
-
import type { Model } from "@earendil-works/pi-ai";
|
|
8
|
-
import type { AgentSession, ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
9
|
-
import { type ToolActivity } from "./agent-runner.js";
|
|
10
|
-
import { type ModelRegistry } from "./model-resolver.js";
|
|
11
|
-
import type { AgentRecord, AgentConfig, AgentType, ThinkingLevel, SubagentsConfig } from "./types.js";
|
|
12
|
-
import { FileLock } from "./file-lock.js";
|
|
13
|
-
export type OnAgentComplete = (record: AgentRecord) => void;
|
|
14
|
-
export type OnAgentStart = (record: AgentRecord) => void;
|
|
15
|
-
interface SpawnOptions {
|
|
16
|
-
description: string;
|
|
17
|
-
model?: Model<any>;
|
|
18
|
-
modelInput?: string;
|
|
19
|
-
modelRegistry?: ModelRegistry;
|
|
20
|
-
thinkingLevel?: ThinkingLevel;
|
|
21
|
-
maxTurns?: number;
|
|
22
|
-
isolated?: boolean;
|
|
23
|
-
inheritContext?: boolean;
|
|
24
|
-
isBackground?: boolean;
|
|
25
|
-
onToolActivity?: (activity: ToolActivity) => void;
|
|
26
|
-
onTextDelta?: (delta: string, fullText: string) => void;
|
|
27
|
-
onSessionCreated?: (session: AgentSession) => void;
|
|
28
|
-
onTurnEnd?: (turnCount: number) => void;
|
|
29
|
-
}
|
|
30
|
-
export declare class AgentManager {
|
|
31
|
-
private agents;
|
|
32
|
-
private cleanupInterval;
|
|
33
|
-
private onComplete?;
|
|
34
|
-
private onStart?;
|
|
35
|
-
private maxConcurrent;
|
|
36
|
-
private customAgents;
|
|
37
|
-
private typeSettings;
|
|
38
|
-
/** Per-file transparent locking for write agents. */
|
|
39
|
-
readonly fileLock: FileLock;
|
|
40
|
-
/** Queue of background agents waiting to start. */
|
|
41
|
-
private queue;
|
|
42
|
-
/** Number of currently running background agents. */
|
|
43
|
-
private runningBackground;
|
|
44
|
-
constructor(onComplete?: OnAgentComplete, maxConcurrent?: number, onStart?: OnAgentStart, typeSettings?: SubagentsConfig["types"], agentsCwd?: string);
|
|
45
|
-
/** Get resolved agent config for a type. */
|
|
46
|
-
getAgentConfig(type: AgentType): AgentConfig | undefined;
|
|
47
|
-
/** Public agent types known from built-ins, custom files, or JSON config. */
|
|
48
|
-
getKnownTypes(): string[];
|
|
49
|
-
/** A type is enabled only when both JSON config and agent frontmatter allow it. */
|
|
50
|
-
isTypeEnabled(type: AgentType): boolean;
|
|
51
|
-
private assertTypeEnabled;
|
|
52
|
-
setMaxConcurrent(n: number): void;
|
|
53
|
-
getMaxConcurrent(): number;
|
|
54
|
-
/**
|
|
55
|
-
* Spawn an agent. Returns ID immediately for background, waits for foreground.
|
|
56
|
-
*/
|
|
57
|
-
spawn(pi: ExtensionAPI, ctx: ExtensionContext, type: AgentType, prompt: string, options: SpawnOptions): string;
|
|
58
|
-
/** Actually start an agent. */
|
|
59
|
-
private startAgent;
|
|
60
|
-
/** Start queued agents up to concurrency limit. */
|
|
61
|
-
private drainQueue;
|
|
62
|
-
/**
|
|
63
|
-
* Spawn and wait (foreground).
|
|
64
|
-
*/
|
|
65
|
-
spawnAndWait(pi: ExtensionAPI, ctx: ExtensionContext, type: AgentType, prompt: string, options: Omit<SpawnOptions, "isBackground">): Promise<AgentRecord>;
|
|
66
|
-
getRecord(id: string): AgentRecord | undefined;
|
|
67
|
-
listAgents(): AgentRecord[];
|
|
68
|
-
abort(id: string): boolean;
|
|
69
|
-
/** Abort all agents (for ESC propagation). */
|
|
70
|
-
abortAll(): number;
|
|
71
|
-
/** Wait for all agents. */
|
|
72
|
-
waitForAll(): Promise<void>;
|
|
73
|
-
/** Whether any agents running or queued. */
|
|
74
|
-
hasRunning(): boolean;
|
|
75
|
-
/** Remove completed records. */
|
|
76
|
-
clearCompleted(): void;
|
|
77
|
-
private cleanup;
|
|
78
|
-
dispose(): void;
|
|
79
|
-
}
|
|
80
|
-
export {};
|
|
81
|
-
//# sourceMappingURL=agent-manager.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"agent-manager.d.ts","sourceRoot":"","sources":["../src/agent-manager.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,uBAAuB,CAAC;AACnD,OAAO,KAAK,EAAE,YAAY,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,iCAAiC,CAAC;AACpG,OAAO,EAAY,KAAK,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAChE,OAAO,EAAgB,KAAK,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACvE,OAAO,KAAK,EAAE,WAAW,EAAE,WAAW,EAAE,SAAS,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAOtG,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAE1C,MAAM,MAAM,eAAe,GAAG,CAAC,MAAM,EAAE,WAAW,KAAK,IAAI,CAAC;AAC5D,MAAM,MAAM,YAAY,GAAG,CAAC,MAAM,EAAE,WAAW,KAAK,IAAI,CAAC;AAazD,UAAU,YAAY;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,cAAc,CAAC,EAAE,CAAC,QAAQ,EAAE,YAAY,KAAK,IAAI,CAAC;IAClD,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IACxD,gBAAgB,CAAC,EAAE,CAAC,OAAO,EAAE,YAAY,KAAK,IAAI,CAAC;IACnD,SAAS,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;CACzC;AAED,qBAAa,YAAY;IACvB,OAAO,CAAC,MAAM,CAAkC;IAChD,OAAO,CAAC,eAAe,CAAiC;IACxD,OAAO,CAAC,UAAU,CAAC,CAAkB;IACrC,OAAO,CAAC,OAAO,CAAC,CAAe;IAC/B,OAAO,CAAC,aAAa,CAAS;IAC9B,OAAO,CAAC,YAAY,CAA2B;IAC/C,OAAO,CAAC,YAAY,CAA2B;IAE/C,qDAAqD;IACrD,QAAQ,CAAC,QAAQ,WAAkB;IAEnC,mDAAmD;IACnD,OAAO,CAAC,KAAK,CAAyC;IACtD,qDAAqD;IACrD,OAAO,CAAC,iBAAiB,CAAK;gBAG5B,UAAU,CAAC,EAAE,eAAe,EAC5B,aAAa,SAAyB,EACtC,OAAO,CAAC,EAAE,YAAY,EACtB,YAAY,GAAE,eAAe,CAAC,OAAO,CAAM,EAC3C,SAAS,SAAgB;IAU3B,4CAA4C;IAC5C,cAAc,CAAC,IAAI,EAAE,SAAS,GAAG,WAAW,GAAG,SAAS;IAIxD,6EAA6E;IAC7E,aAAa,IAAI,MAAM,EAAE;IAQzB,mFAAmF;IACnF,aAAa,CAAC,IAAI,EAAE,SAAS,GAAG,OAAO;IAKvC,OAAO,CAAC,iBAAiB;IAMzB,gBAAgB,CAAC,CAAC,EAAE,MAAM;IAK1B,gBAAgB,IAAI,MAAM;IAI1B;;OAEG;IACH,KAAK,CACH,EAAE,EAAE,YAAY,EAChB,GAAG,EAAE,gBAAgB,EACrB,IAAI,EAAE,SAAS,EACf,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,YAAY,GACpB,MAAM;IA8BT,+BAA+B;IAC/B,OAAO,CAAC,UAAU;IAwFlB,mDAAmD;IACnD,OAAO,CAAC,UAAU;IASlB;;OAEG;IACG,YAAY,CAChB,EAAE,EAAE,YAAY,EAChB,GAAG,EAAE,gBAAgB,EACrB,IAAI,EAAE,SAAS,EACf,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,IAAI,CAAC,YAAY,EAAE,cAAc,CAAC,GAC1C,OAAO,CAAC,WAAW,CAAC;IAOvB,SAAS,CAAC,EAAE,EAAE,MAAM,GAAG,WAAW,GAAG,SAAS;IAI9C,UAAU,IAAI,WAAW,EAAE;IAI3B,KAAK,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO;IAoB1B,8CAA8C;IAC9C,QAAQ,IAAI,MAAM;IAuBlB,2BAA2B;IACrB,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAYjC,4CAA4C;IAC5C,UAAU,IAAI,OAAO;IAIrB,gCAAgC;IAChC,cAAc,IAAI,IAAI;IAStB,OAAO,CAAC,OAAO;IAWf,OAAO;CASR"}
|