@diousk/pi-subagents-fast 0.20.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 +808 -0
- package/CONTRIBUTING.md +72 -0
- package/LICENSE +21 -0
- package/README.md +1034 -0
- package/SECURITY.md +95 -0
- package/dist/abortable.d.ts +12 -0
- package/dist/abortable.js +42 -0
- package/dist/agent-color.d.ts +35 -0
- package/dist/agent-color.js +123 -0
- package/dist/agent-file-toggle.d.ts +125 -0
- package/dist/agent-file-toggle.js +260 -0
- package/dist/agent-manager.d.ts +472 -0
- package/dist/agent-manager.js +1338 -0
- package/dist/agent-runner.d.ts +312 -0
- package/dist/agent-runner.js +1034 -0
- package/dist/agent-types.d.ts +119 -0
- package/dist/agent-types.js +286 -0
- package/dist/child-context.d.ts +2 -0
- package/dist/child-context.js +12 -0
- package/dist/context.d.ts +12 -0
- package/dist/context.js +56 -0
- package/dist/cross-extension-rpc.d.ts +66 -0
- package/dist/cross-extension-rpc.js +138 -0
- package/dist/custom-agents.d.ts +54 -0
- package/dist/custom-agents.js +316 -0
- package/dist/default-agents.d.ts +7 -0
- package/dist/default-agents.js +122 -0
- package/dist/enabled-models.d.ts +49 -0
- package/dist/enabled-models.js +145 -0
- package/dist/env.d.ts +6 -0
- package/dist/env.js +28 -0
- package/dist/group-join.d.ts +32 -0
- package/dist/group-join.js +116 -0
- package/dist/index.d.ts +50 -0
- package/dist/index.js +3682 -0
- package/dist/invocation-config.d.ts +107 -0
- package/dist/invocation-config.js +83 -0
- package/dist/memory.d.ts +53 -0
- package/dist/memory.js +165 -0
- package/dist/mention-clone.d.ts +87 -0
- package/dist/mention-clone.js +153 -0
- package/dist/mention.d.ts +81 -0
- package/dist/mention.js +131 -0
- package/dist/model-resolver.d.ts +36 -0
- package/dist/model-resolver.js +95 -0
- package/dist/model-scope.d.ts +49 -0
- package/dist/model-scope.js +48 -0
- package/dist/nested-tools.d.ts +55 -0
- package/dist/nested-tools.js +299 -0
- package/dist/output-file.d.ts +43 -0
- package/dist/output-file.js +142 -0
- package/dist/prompts.d.ts +55 -0
- package/dist/prompts.js +91 -0
- package/dist/schedule-store.d.ts +38 -0
- package/dist/schedule-store.js +155 -0
- package/dist/schedule.d.ts +109 -0
- package/dist/schedule.js +359 -0
- package/dist/settings.d.ts +360 -0
- package/dist/settings.js +251 -0
- package/dist/skill-loader.d.ts +24 -0
- package/dist/skill-loader.js +93 -0
- package/dist/status-note.d.ts +61 -0
- package/dist/status-note.js +85 -0
- package/dist/structured-output.d.ts +61 -0
- package/dist/structured-output.js +112 -0
- package/dist/types.d.ts +371 -0
- package/dist/types.js +5 -0
- package/dist/ui/agent-mention.d.ts +82 -0
- package/dist/ui/agent-mention.js +187 -0
- package/dist/ui/agent-widget.d.ts +219 -0
- package/dist/ui/agent-widget.js +592 -0
- package/dist/ui/conversation-viewer.d.ts +120 -0
- package/dist/ui/conversation-viewer.js +578 -0
- package/dist/ui/fleet-list.d.ts +195 -0
- package/dist/ui/fleet-list.js +471 -0
- package/dist/ui/schedule-menu.d.ts +16 -0
- package/dist/ui/schedule-menu.js +94 -0
- package/dist/ui/select-item.d.ts +27 -0
- package/dist/ui/select-item.js +34 -0
- package/dist/ui/viewer-keys.d.ts +20 -0
- package/dist/ui/viewer-keys.js +17 -0
- package/dist/ui/workflow-card.d.ts +175 -0
- package/dist/ui/workflow-card.js +332 -0
- package/dist/ui/workflow-dialog.d.ts +305 -0
- package/dist/ui/workflow-dialog.js +843 -0
- package/dist/ui/workflow-menu.d.ts +60 -0
- package/dist/ui/workflow-menu.js +147 -0
- package/dist/usage.d.ts +135 -0
- package/dist/usage.js +120 -0
- package/dist/workflow/collisions.d.ts +95 -0
- package/dist/workflow/collisions.js +88 -0
- package/dist/workflow/entry.d.ts +32 -0
- package/dist/workflow/entry.js +29 -0
- package/dist/workflow/host.d.ts +62 -0
- package/dist/workflow/host.js +362 -0
- package/dist/workflow/journal.d.ts +97 -0
- package/dist/workflow/journal.js +120 -0
- package/dist/workflow/json-schema.d.ts +51 -0
- package/dist/workflow/json-schema.js +111 -0
- package/dist/workflow/meta.d.ts +67 -0
- package/dist/workflow/meta.js +317 -0
- package/dist/workflow/progress.d.ts +224 -0
- package/dist/workflow/progress.js +361 -0
- package/dist/workflow/runtime.d.ts +334 -0
- package/dist/workflow/runtime.js +830 -0
- package/dist/workflow/saved.d.ts +90 -0
- package/dist/workflow/saved.js +203 -0
- package/dist/workflow/task.d.ts +136 -0
- package/dist/workflow/task.js +207 -0
- package/dist/workflow/tool-description.d.ts +38 -0
- package/dist/workflow/tool-description.js +199 -0
- package/dist/workflow/worker-source.d.ts +47 -0
- package/dist/workflow/worker-source.js +778 -0
- package/dist/worktree.d.ts +52 -0
- package/dist/worktree.js +164 -0
- package/dist/xml.d.ts +10 -0
- package/dist/xml.js +12 -0
- package/docs/rpc.md +183 -0
- package/docs/workflows.md +437 -0
- package/examples/agent-tool-description.md +42 -0
- package/examples/workflows/compose.js +51 -0
- package/examples/workflows/fan-out-audit.js +47 -0
- package/examples/workflows/gated-fix.js +60 -0
- package/examples/workflows/lib/count-child.js +27 -0
- package/examples/workflows/review-panel.js +63 -0
- package/examples/workflows/structured-findings.js +78 -0
- package/package.json +68 -0
- package/src/abortable.ts +43 -0
- package/src/agent-color.ts +161 -0
- package/src/agent-file-toggle.ts +270 -0
- package/src/agent-manager.ts +1581 -0
- package/src/agent-runner.ts +1286 -0
- package/src/agent-types.ts +346 -0
- package/src/child-context.ts +15 -0
- package/src/context.ts +58 -0
- package/src/cross-extension-rpc.ts +198 -0
- package/src/custom-agents.ts +333 -0
- package/src/default-agents.ts +126 -0
- package/src/enabled-models.ts +180 -0
- package/src/env.ts +33 -0
- package/src/group-join.ts +141 -0
- package/src/index.ts +3991 -0
- package/src/invocation-config.ts +155 -0
- package/src/memory.ts +179 -0
- package/src/mention-clone.ts +196 -0
- package/src/mention.ts +141 -0
- package/src/model-resolver.ts +118 -0
- package/src/model-scope.ts +70 -0
- package/src/nested-tools.ts +422 -0
- package/src/output-file.ts +155 -0
- package/src/prompts.ts +142 -0
- package/src/schedule-store.ts +153 -0
- package/src/schedule.ts +386 -0
- package/src/settings.ts +587 -0
- package/src/skill-loader.ts +102 -0
- package/src/status-note.ts +90 -0
- package/src/structured-output.ts +130 -0
- package/src/types.ts +384 -0
- package/src/ui/agent-mention.ts +216 -0
- package/src/ui/agent-widget.ts +664 -0
- package/src/ui/conversation-viewer.ts +589 -0
- package/src/ui/fleet-list.ts +543 -0
- package/src/ui/schedule-menu.ts +105 -0
- package/src/ui/select-item.ts +45 -0
- package/src/ui/viewer-keys.ts +39 -0
- package/src/ui/workflow-card.ts +470 -0
- package/src/ui/workflow-dialog.ts +1115 -0
- package/src/ui/workflow-menu.ts +193 -0
- package/src/usage.ts +167 -0
- package/src/workflow/collisions.ts +123 -0
- package/src/workflow/entry.ts +47 -0
- package/src/workflow/host.ts +403 -0
- package/src/workflow/journal.ts +164 -0
- package/src/workflow/json-schema.ts +128 -0
- package/src/workflow/meta.ts +325 -0
- package/src/workflow/progress.ts +550 -0
- package/src/workflow/runtime.ts +1219 -0
- package/src/workflow/saved.ts +217 -0
- package/src/workflow/task.ts +302 -0
- package/src/workflow/tool-description.ts +200 -0
- package/src/workflow/worker-source.ts +781 -0
- package/src/worktree.ts +205 -0
- package/src/xml.ts +13 -0
package/src/worktree.ts
ADDED
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* worktree.ts — Git worktree isolation for agents.
|
|
3
|
+
*
|
|
4
|
+
* Creates a temporary git worktree so the agent works on an isolated copy of the repo.
|
|
5
|
+
* On completion, if no changes were made, the worktree is cleaned up.
|
|
6
|
+
* If changes exist, a branch is created and returned in the result.
|
|
7
|
+
*
|
|
8
|
+
* Every git call goes through `pi.exec` (async) rather than `execFileSync`: a
|
|
9
|
+
* worktree copy can take seconds, and a session that spawns several isolated
|
|
10
|
+
* agents at once would otherwise serialize them all on the TUI's event loop.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { randomUUID } from "node:crypto";
|
|
14
|
+
import { existsSync, realpathSync } from "node:fs";
|
|
15
|
+
import { tmpdir } from "node:os";
|
|
16
|
+
import { join, relative } from "node:path";
|
|
17
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
18
|
+
|
|
19
|
+
export interface WorktreeInfo {
|
|
20
|
+
/** Absolute path to the worktree directory (the copied repo's root). */
|
|
21
|
+
path: string;
|
|
22
|
+
/** Branch name created for this worktree (if changes exist). */
|
|
23
|
+
branch: string;
|
|
24
|
+
/** Commit SHA that the worktree was created from. */
|
|
25
|
+
baseSha: string;
|
|
26
|
+
/**
|
|
27
|
+
* Where the agent should work inside the worktree: the equivalent of the
|
|
28
|
+
* cwd the worktree was created from. Equals `path` when that cwd was the
|
|
29
|
+
* repo root; points at the copied subdirectory when it was deeper (e.g. a
|
|
30
|
+
* monorepo package), so the requested scoping survives isolation.
|
|
31
|
+
*/
|
|
32
|
+
workPath: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Project-wide switch for worktree isolation (`worktreeIsolation` in
|
|
37
|
+
* subagents.json). Default `true` — unchanged behaviour.
|
|
38
|
+
*
|
|
39
|
+
* The `"off"` isolation value gives a model a legal way to decline a worktree,
|
|
40
|
+
* but it still depends on the model choosing it. This is the deterministic half
|
|
41
|
+
* of the same fix: on a large repo where every worktree costs real time and
|
|
42
|
+
* disk (#184), turning it off means no caller can create one, whatever it
|
|
43
|
+
* passes.
|
|
44
|
+
*/
|
|
45
|
+
let worktreeIsolationEnabled = true;
|
|
46
|
+
|
|
47
|
+
export function setWorktreeIsolationEnabled(enabled: boolean): void {
|
|
48
|
+
worktreeIsolationEnabled = enabled;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function isWorktreeIsolationEnabled(): boolean {
|
|
52
|
+
return worktreeIsolationEnabled;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export interface WorktreeCleanupResult {
|
|
56
|
+
/** Whether changes were found in the worktree. */
|
|
57
|
+
hasChanges: boolean;
|
|
58
|
+
/** Branch name if changes were committed. */
|
|
59
|
+
branch?: string;
|
|
60
|
+
/** Worktree path if it was kept. */
|
|
61
|
+
path?: string;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Run git and return its trimmed stdout, throwing on failure so callers keep
|
|
66
|
+
* the try/catch control flow `execFileSync` gave them.
|
|
67
|
+
*
|
|
68
|
+
* `pi.exec` never rejects — it reports failure in the result — and a command
|
|
69
|
+
* killed by its timeout comes back as `killed` with an exit code of 0, so both
|
|
70
|
+
* have to be checked to reproduce `execFileSync`'s "throws on anything but a
|
|
71
|
+
* clean exit".
|
|
72
|
+
*/
|
|
73
|
+
async function git(pi: ExtensionAPI, cwd: string, args: string[], timeout: number): Promise<string> {
|
|
74
|
+
const result = await pi.exec("git", args, { cwd, timeout });
|
|
75
|
+
if (result.killed || result.code !== 0) {
|
|
76
|
+
throw new Error(result.stderr.trim() || `git ${args.join(" ")} failed (exit ${result.code})`);
|
|
77
|
+
}
|
|
78
|
+
return result.stdout.trim();
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Create a temporary git worktree for an agent.
|
|
83
|
+
* Returns the worktree path, or undefined if not in a git repo.
|
|
84
|
+
*/
|
|
85
|
+
export async function createWorktree(
|
|
86
|
+
pi: ExtensionAPI,
|
|
87
|
+
cwd: string,
|
|
88
|
+
agentId: string,
|
|
89
|
+
): Promise<WorktreeInfo | undefined> {
|
|
90
|
+
// Verify we're in a git repo with at least one commit (HEAD must exist)
|
|
91
|
+
let baseSha: string;
|
|
92
|
+
let subdir: string;
|
|
93
|
+
try {
|
|
94
|
+
await git(pi, cwd, ["rev-parse", "--is-inside-work-tree"], 5000);
|
|
95
|
+
baseSha = await git(pi, cwd, ["rev-parse", "HEAD"], 5000);
|
|
96
|
+
// Where cwd sits inside the repo ("" at the root): the agent must work at
|
|
97
|
+
// the same subdirectory inside the copy, or a monorepo-package cwd would
|
|
98
|
+
// silently widen to the whole repo. realpath both sides — git emits
|
|
99
|
+
// resolved paths while cwd may arrive through a symlink (macOS /tmp).
|
|
100
|
+
const topLevel = await git(pi, cwd, ["rev-parse", "--show-toplevel"], 5000);
|
|
101
|
+
subdir = relative(realpathSync(topLevel), realpathSync(cwd));
|
|
102
|
+
} catch {
|
|
103
|
+
return undefined;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const branch = `pi-agent-${agentId}`;
|
|
107
|
+
const suffix = randomUUID().slice(0, 8);
|
|
108
|
+
const worktreePath = join(tmpdir(), `pi-agent-${agentId}-${suffix}`);
|
|
109
|
+
|
|
110
|
+
try {
|
|
111
|
+
// Create detached worktree at HEAD
|
|
112
|
+
await git(pi, cwd, ["worktree", "add", "--detach", worktreePath, "HEAD"], 30000);
|
|
113
|
+
return { path: worktreePath, branch, baseSha, workPath: subdir ? join(worktreePath, subdir) : worktreePath };
|
|
114
|
+
} catch {
|
|
115
|
+
// If worktree creation fails, return undefined (agent runs in normal cwd)
|
|
116
|
+
return undefined;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Clean up a worktree after agent completion.
|
|
122
|
+
* - If no changes: remove worktree entirely.
|
|
123
|
+
* - If changes exist: create a branch, commit changes, return branch info.
|
|
124
|
+
*/
|
|
125
|
+
export async function cleanupWorktree(
|
|
126
|
+
pi: ExtensionAPI,
|
|
127
|
+
cwd: string,
|
|
128
|
+
worktree: WorktreeInfo,
|
|
129
|
+
agentDescription: string,
|
|
130
|
+
): Promise<WorktreeCleanupResult> {
|
|
131
|
+
if (!existsSync(worktree.path)) {
|
|
132
|
+
return { hasChanges: false };
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
try {
|
|
136
|
+
// Check for uncommitted changes in the worktree
|
|
137
|
+
const status = await git(pi, worktree.path, ["status", "--porcelain"], 10000);
|
|
138
|
+
|
|
139
|
+
if (status) {
|
|
140
|
+
// Changes exist — stage, commit, and create a branch
|
|
141
|
+
await git(pi, worktree.path, ["add", "-A"], 10000);
|
|
142
|
+
// Truncate description for commit message (no shell sanitization needed — pi.exec uses argv)
|
|
143
|
+
const safeDesc = agentDescription.slice(0, 200);
|
|
144
|
+
const commitMsg = `pi-agent: ${safeDesc}`;
|
|
145
|
+
await git(pi, worktree.path, ["commit", "--no-verify", "-m", commitMsg], 10000);
|
|
146
|
+
} else {
|
|
147
|
+
const currentSha = await git(pi, worktree.path, ["rev-parse", "HEAD"], 5000);
|
|
148
|
+
|
|
149
|
+
if (currentSha === worktree.baseSha) {
|
|
150
|
+
// No changes — remove worktree
|
|
151
|
+
await removeWorktree(pi, cwd, worktree.path);
|
|
152
|
+
return { hasChanges: false };
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Create a branch pointing to the worktree's HEAD.
|
|
157
|
+
// If the branch already exists, append a suffix to avoid overwriting previous work.
|
|
158
|
+
let branchName = worktree.branch;
|
|
159
|
+
try {
|
|
160
|
+
await git(pi, worktree.path, ["branch", branchName], 5000);
|
|
161
|
+
} catch {
|
|
162
|
+
// Branch already exists — use a unique suffix
|
|
163
|
+
branchName = `${worktree.branch}-${Date.now()}`;
|
|
164
|
+
await git(pi, worktree.path, ["branch", branchName], 5000);
|
|
165
|
+
}
|
|
166
|
+
// Update branch name in worktree info for the caller
|
|
167
|
+
worktree.branch = branchName;
|
|
168
|
+
|
|
169
|
+
// Remove the worktree (branch persists in main repo)
|
|
170
|
+
await removeWorktree(pi, cwd, worktree.path);
|
|
171
|
+
|
|
172
|
+
return {
|
|
173
|
+
hasChanges: true,
|
|
174
|
+
branch: worktree.branch,
|
|
175
|
+
path: worktree.path,
|
|
176
|
+
};
|
|
177
|
+
} catch {
|
|
178
|
+
// Best effort cleanup on error
|
|
179
|
+
try { await removeWorktree(pi, cwd, worktree.path); } catch { /* ignore */ }
|
|
180
|
+
return { hasChanges: false };
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Force-remove a worktree.
|
|
186
|
+
*/
|
|
187
|
+
async function removeWorktree(pi: ExtensionAPI, cwd: string, worktreePath: string): Promise<void> {
|
|
188
|
+
try {
|
|
189
|
+
await git(pi, cwd, ["worktree", "remove", "--force", worktreePath], 10000);
|
|
190
|
+
} catch {
|
|
191
|
+
// If git worktree remove fails, try pruning
|
|
192
|
+
try {
|
|
193
|
+
await git(pi, cwd, ["worktree", "prune"], 5000);
|
|
194
|
+
} catch { /* ignore */ }
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Prune any orphaned worktrees (crash recovery).
|
|
200
|
+
*/
|
|
201
|
+
export async function pruneWorktrees(pi: ExtensionAPI, cwd: string): Promise<void> {
|
|
202
|
+
try {
|
|
203
|
+
await git(pi, cwd, ["worktree", "prune"], 5000);
|
|
204
|
+
} catch { /* ignore */ }
|
|
205
|
+
}
|
package/src/xml.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* xml.ts — escaping for the `<task-notification>` payloads.
|
|
3
|
+
*
|
|
4
|
+
* A module of its own because both notification builders need it and they sit
|
|
5
|
+
* on opposite sides of a dependency edge: the agent one lives in `index.ts`,
|
|
6
|
+
* which imports `workflow/task.ts`, so the workflow one cannot reach back for
|
|
7
|
+
* it without a cycle.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/** Escape XML special characters to prevent injection in structured notifications. */
|
|
11
|
+
export function escapeXml(s: string): string {
|
|
12
|
+
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
13
|
+
}
|