@clanker-code/pi-subagents 0.10.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.
Files changed (130) hide show
  1. package/.plans/PLAN-next-changes.md +183 -0
  2. package/.plans/README.md +14 -0
  3. package/AGENTS.md +31 -0
  4. package/CHANGELOG.md +583 -0
  5. package/CLAUDE.md +1 -0
  6. package/LICENSE +21 -0
  7. package/README.md +630 -0
  8. package/RELEASE.md +39 -0
  9. package/dist/abort-resend.d.ts +35 -0
  10. package/dist/abort-resend.js +71 -0
  11. package/dist/agent-details.d.ts +17 -0
  12. package/dist/agent-details.js +22 -0
  13. package/dist/agent-manager.d.ts +132 -0
  14. package/dist/agent-manager.js +493 -0
  15. package/dist/agent-runner.d.ts +165 -0
  16. package/dist/agent-runner.js +732 -0
  17. package/dist/agent-tool-description.d.ts +9 -0
  18. package/dist/agent-tool-description.js +147 -0
  19. package/dist/agent-types.d.ts +60 -0
  20. package/dist/agent-types.js +157 -0
  21. package/dist/context.d.ts +12 -0
  22. package/dist/context.js +56 -0
  23. package/dist/cross-extension-rpc.d.ts +46 -0
  24. package/dist/cross-extension-rpc.js +76 -0
  25. package/dist/custom-agents.d.ts +14 -0
  26. package/dist/custom-agents.js +149 -0
  27. package/dist/default-agents.d.ts +7 -0
  28. package/dist/default-agents.js +119 -0
  29. package/dist/enabled-models.d.ts +49 -0
  30. package/dist/enabled-models.js +145 -0
  31. package/dist/env.d.ts +6 -0
  32. package/dist/env.js +28 -0
  33. package/dist/group-join.d.ts +32 -0
  34. package/dist/group-join.js +116 -0
  35. package/dist/index.d.ts +36 -0
  36. package/dist/index.js +1918 -0
  37. package/dist/invocation-config.d.ts +25 -0
  38. package/dist/invocation-config.js +19 -0
  39. package/dist/memory.d.ts +49 -0
  40. package/dist/memory.js +151 -0
  41. package/dist/model-resolver.d.ts +19 -0
  42. package/dist/model-resolver.js +62 -0
  43. package/dist/notifications.d.ts +6 -0
  44. package/dist/notifications.js +107 -0
  45. package/dist/output-file.d.ts +24 -0
  46. package/dist/output-file.js +86 -0
  47. package/dist/peek.d.ts +37 -0
  48. package/dist/peek.js +121 -0
  49. package/dist/prompts.d.ts +40 -0
  50. package/dist/prompts.js +95 -0
  51. package/dist/schedule-store.d.ts +38 -0
  52. package/dist/schedule-store.js +155 -0
  53. package/dist/schedule.d.ts +109 -0
  54. package/dist/schedule.js +338 -0
  55. package/dist/settings.d.ts +135 -0
  56. package/dist/settings.js +168 -0
  57. package/dist/skill-loader.d.ts +24 -0
  58. package/dist/skill-loader.js +93 -0
  59. package/dist/status-note.d.ts +13 -0
  60. package/dist/status-note.js +24 -0
  61. package/dist/types.d.ts +184 -0
  62. package/dist/types.js +7 -0
  63. package/dist/ui/agent-tool-rendering.d.ts +34 -0
  64. package/dist/ui/agent-tool-rendering.js +154 -0
  65. package/dist/ui/agent-widget-tree.d.ts +33 -0
  66. package/dist/ui/agent-widget-tree.js +130 -0
  67. package/dist/ui/agent-widget.d.ts +156 -0
  68. package/dist/ui/agent-widget.js +408 -0
  69. package/dist/ui/conversation-viewer.d.ts +47 -0
  70. package/dist/ui/conversation-viewer.js +290 -0
  71. package/dist/ui/menu-select.d.ts +20 -0
  72. package/dist/ui/menu-select.js +46 -0
  73. package/dist/ui/schedule-menu.d.ts +16 -0
  74. package/dist/ui/schedule-menu.js +99 -0
  75. package/dist/ui/viewer-keys.d.ts +20 -0
  76. package/dist/ui/viewer-keys.js +17 -0
  77. package/dist/usage.d.ts +50 -0
  78. package/dist/usage.js +49 -0
  79. package/dist/wait.d.ts +10 -0
  80. package/dist/wait.js +37 -0
  81. package/dist/worktree.d.ts +45 -0
  82. package/dist/worktree.js +160 -0
  83. package/docs/design/default-extension-tool-exposure.md +56 -0
  84. package/docs/superpowers/plans/2026-06-19-recursive-subagent-widget.md +600 -0
  85. package/docs/superpowers/specs/2026-06-19-recursive-subagent-widget-design.md +189 -0
  86. package/examples/agent-tool-description.md +45 -0
  87. package/package.json +56 -0
  88. package/reviews/proposal-structured-output-schema.md +135 -0
  89. package/reviews/recursive-subagent-widget-preview-rev2.png +0 -0
  90. package/reviews/recursive-subagent-widget-preview.html +137 -0
  91. package/reviews/recursive-subagent-widget-preview.png +0 -0
  92. package/reviews/subagent-features-comparison.md +350 -0
  93. package/src/abort-resend.ts +75 -0
  94. package/src/agent-details.ts +31 -0
  95. package/src/agent-manager.ts +596 -0
  96. package/src/agent-runner.ts +872 -0
  97. package/src/agent-tool-description.ts +163 -0
  98. package/src/agent-types.ts +189 -0
  99. package/src/context.ts +58 -0
  100. package/src/cross-extension-rpc.ts +122 -0
  101. package/src/custom-agents.ts +160 -0
  102. package/src/default-agents.ts +123 -0
  103. package/src/enabled-models.ts +180 -0
  104. package/src/env.ts +33 -0
  105. package/src/group-join.ts +141 -0
  106. package/src/index.ts +2115 -0
  107. package/src/invocation-config.ts +42 -0
  108. package/src/memory.ts +165 -0
  109. package/src/model-resolver.ts +81 -0
  110. package/src/notifications.ts +120 -0
  111. package/src/output-file.ts +96 -0
  112. package/src/peek.ts +155 -0
  113. package/src/prompts.ts +129 -0
  114. package/src/schedule-store.ts +153 -0
  115. package/src/schedule.ts +365 -0
  116. package/src/settings.ts +289 -0
  117. package/src/skill-loader.ts +102 -0
  118. package/src/status-note.ts +25 -0
  119. package/src/types.ts +195 -0
  120. package/src/ui/agent-tool-rendering.ts +175 -0
  121. package/src/ui/agent-widget-tree.ts +169 -0
  122. package/src/ui/agent-widget.ts +497 -0
  123. package/src/ui/conversation-viewer.ts +297 -0
  124. package/src/ui/menu-select.ts +68 -0
  125. package/src/ui/schedule-menu.ts +105 -0
  126. package/src/ui/viewer-keys.ts +39 -0
  127. package/src/usage.ts +60 -0
  128. package/src/wait.ts +44 -0
  129. package/src/worktree.ts +191 -0
  130. package/vitest.config.ts +25 -0
@@ -0,0 +1,191 @@
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
+
9
+ import { execFileSync } from "node:child_process";
10
+ import { randomUUID } from "node:crypto";
11
+ import { existsSync, realpathSync } from "node:fs";
12
+ import { tmpdir } from "node:os";
13
+ import { join, relative } from "node:path";
14
+
15
+ export interface WorktreeInfo {
16
+ /** Absolute path to the worktree directory (the copied repo's root). */
17
+ path: string;
18
+ /** Branch name created for this worktree (if changes exist). */
19
+ branch: string;
20
+ /** Commit SHA that the worktree was created from. */
21
+ baseSha: string;
22
+ /**
23
+ * Where the agent should work inside the worktree: the equivalent of the
24
+ * cwd the worktree was created from. Equals `path` when that cwd was the
25
+ * repo root; points at the copied subdirectory when it was deeper (e.g. a
26
+ * monorepo package), so the requested scoping survives isolation.
27
+ */
28
+ workPath: string;
29
+ }
30
+
31
+ export interface WorktreeCleanupResult {
32
+ /** Whether changes were found in the worktree. */
33
+ hasChanges: boolean;
34
+ /** Branch name if changes were committed. */
35
+ branch?: string;
36
+ /** Worktree path if it was kept. */
37
+ path?: string;
38
+ }
39
+
40
+ /**
41
+ * Create a temporary git worktree for an agent.
42
+ * Returns the worktree path, or undefined if not in a git repo.
43
+ */
44
+ export function createWorktree(cwd: string, agentId: string): WorktreeInfo | undefined {
45
+ // Verify we're in a git repo with at least one commit (HEAD must exist)
46
+ let baseSha: string;
47
+ let subdir: string;
48
+ try {
49
+ execFileSync("git", ["rev-parse", "--is-inside-work-tree"], { cwd, stdio: "pipe", timeout: 5000 });
50
+ baseSha = execFileSync("git", ["rev-parse", "HEAD"], { cwd, stdio: "pipe", timeout: 5000 })
51
+ .toString()
52
+ .trim();
53
+ // Where cwd sits inside the repo ("" at the root): the agent must work at
54
+ // the same subdirectory inside the copy, or a monorepo-package cwd would
55
+ // silently widen to the whole repo. realpath both sides — git emits
56
+ // resolved paths while cwd may arrive through a symlink (macOS /tmp).
57
+ const topLevel = execFileSync("git", ["rev-parse", "--show-toplevel"], { cwd, stdio: "pipe", timeout: 5000 })
58
+ .toString()
59
+ .trim();
60
+ subdir = relative(realpathSync(topLevel), realpathSync(cwd));
61
+ } catch {
62
+ return undefined;
63
+ }
64
+
65
+ const branch = `pi-agent-${agentId}`;
66
+ const suffix = randomUUID().slice(0, 8);
67
+ const worktreePath = join(tmpdir(), `pi-agent-${agentId}-${suffix}`);
68
+
69
+ try {
70
+ // Create detached worktree at HEAD
71
+ execFileSync("git", ["worktree", "add", "--detach", worktreePath, "HEAD"], {
72
+ cwd,
73
+ stdio: "pipe",
74
+ timeout: 30000,
75
+ });
76
+ return { path: worktreePath, branch, baseSha, workPath: subdir ? join(worktreePath, subdir) : worktreePath };
77
+ } catch {
78
+ // If worktree creation fails, return undefined (agent runs in normal cwd)
79
+ return undefined;
80
+ }
81
+ }
82
+
83
+ /**
84
+ * Clean up a worktree after agent completion.
85
+ * - If no changes: remove worktree entirely.
86
+ * - If changes exist: create a branch, commit changes, return branch info.
87
+ */
88
+ export function cleanupWorktree(
89
+ cwd: string,
90
+ worktree: WorktreeInfo,
91
+ agentDescription: string,
92
+ ): WorktreeCleanupResult {
93
+ if (!existsSync(worktree.path)) {
94
+ return { hasChanges: false };
95
+ }
96
+
97
+ try {
98
+ // Check for uncommitted changes in the worktree
99
+ const status = execFileSync("git", ["status", "--porcelain"], {
100
+ cwd: worktree.path,
101
+ stdio: "pipe",
102
+ timeout: 10000,
103
+ }).toString().trim();
104
+
105
+ if (status) {
106
+ // Changes exist — stage, commit, and create a branch
107
+ execFileSync("git", ["add", "-A"], { cwd: worktree.path, stdio: "pipe", timeout: 10000 });
108
+ // Truncate description for commit message (no shell sanitization needed — execFileSync uses argv)
109
+ const safeDesc = agentDescription.slice(0, 200);
110
+ const commitMsg = `pi-agent: ${safeDesc}`;
111
+ execFileSync("git", ["commit", "--no-verify", "-m", commitMsg], {
112
+ cwd: worktree.path,
113
+ stdio: "pipe",
114
+ timeout: 10000,
115
+ });
116
+ } else {
117
+ const currentSha = execFileSync("git", ["rev-parse", "HEAD"], {
118
+ cwd: worktree.path,
119
+ stdio: "pipe",
120
+ timeout: 5000,
121
+ }).toString().trim();
122
+
123
+ if (currentSha === worktree.baseSha) {
124
+ // No changes — remove worktree
125
+ removeWorktree(cwd, worktree.path);
126
+ return { hasChanges: false };
127
+ }
128
+ }
129
+
130
+ // Create a branch pointing to the worktree's HEAD.
131
+ // If the branch already exists, append a suffix to avoid overwriting previous work.
132
+ let branchName = worktree.branch;
133
+ try {
134
+ execFileSync("git", ["branch", branchName], {
135
+ cwd: worktree.path,
136
+ stdio: "pipe",
137
+ timeout: 5000,
138
+ });
139
+ } catch {
140
+ // Branch already exists — use a unique suffix
141
+ branchName = `${worktree.branch}-${Date.now()}`;
142
+ execFileSync("git", ["branch", branchName], {
143
+ cwd: worktree.path,
144
+ stdio: "pipe",
145
+ timeout: 5000,
146
+ });
147
+ }
148
+ // Update branch name in worktree info for the caller
149
+ worktree.branch = branchName;
150
+
151
+ // Remove the worktree (branch persists in main repo)
152
+ removeWorktree(cwd, worktree.path);
153
+
154
+ return {
155
+ hasChanges: true,
156
+ branch: worktree.branch,
157
+ path: worktree.path,
158
+ };
159
+ } catch {
160
+ // Best effort cleanup on error
161
+ try { removeWorktree(cwd, worktree.path); } catch { /* ignore */ }
162
+ return { hasChanges: false };
163
+ }
164
+ }
165
+
166
+ /**
167
+ * Force-remove a worktree.
168
+ */
169
+ function removeWorktree(cwd: string, worktreePath: string): void {
170
+ try {
171
+ execFileSync("git", ["worktree", "remove", "--force", worktreePath], {
172
+ cwd,
173
+ stdio: "pipe",
174
+ timeout: 10000,
175
+ });
176
+ } catch {
177
+ // If git worktree remove fails, try pruning
178
+ try {
179
+ execFileSync("git", ["worktree", "prune"], { cwd, stdio: "pipe", timeout: 5000 });
180
+ } catch { /* ignore */ }
181
+ }
182
+ }
183
+
184
+ /**
185
+ * Prune any orphaned worktrees (crash recovery).
186
+ */
187
+ export function pruneWorktrees(cwd: string): void {
188
+ try {
189
+ execFileSync("git", ["worktree", "prune"], { cwd, stdio: "pipe", timeout: 5000 });
190
+ } catch { /* ignore */ }
191
+ }
@@ -0,0 +1,25 @@
1
+ import { defineConfig } from "vitest/config";
2
+
3
+ export default defineConfig({
4
+ // The print-mode e2e suite (test/subagents-print-mode-e2e.test.ts) drives REAL
5
+ // faux-model turns through pi-coding-agent + pi-agent-core. That requires ONE
6
+ // shared @earendil-works/pi-ai instance so the faux provider the test registers
7
+ // lands in the same api-registry the session streams through. npm physically
8
+ // duplicates pi-ai (a top-level copy and one nested under pi-coding-agent), which
9
+ // otherwise yields two registries and "No API provider registered" errors.
10
+ // Inlining the @earendil-works packages routes them through Vite's resolver so
11
+ // dedupe can collapse pi-ai to a single instance — for the parent AND for every
12
+ // subagent session the extension spawns. dedupe alone is insufficient (it only
13
+ // affects modules Vite resolves; without inline the runtime stays externalized).
14
+ test: {
15
+ server: { deps: { inline: [/@earendil-works\/pi-/] } },
16
+ // Cap parallelism to 2 workers. Under heavy system load, vite's SSR
17
+ // transforms (the inline @earendil-works packages above) can OOM/timeout
18
+ // and surface as a misleading `Cannot find module '.../stream'` from
19
+ // vitest's module-evaluator. Limiting concurrency keeps memory + transform
20
+ // pressure low and matches the host's 2-thread testing budget.
21
+ minWorkers: 1,
22
+ maxWorkers: 2,
23
+ },
24
+ resolve: { dedupe: ["@earendil-works/pi-ai"] },
25
+ });