@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,45 @@
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
+ export interface WorktreeInfo {
9
+ /** Absolute path to the worktree directory (the copied repo's root). */
10
+ path: string;
11
+ /** Branch name created for this worktree (if changes exist). */
12
+ branch: string;
13
+ /** Commit SHA that the worktree was created from. */
14
+ baseSha: string;
15
+ /**
16
+ * Where the agent should work inside the worktree: the equivalent of the
17
+ * cwd the worktree was created from. Equals `path` when that cwd was the
18
+ * repo root; points at the copied subdirectory when it was deeper (e.g. a
19
+ * monorepo package), so the requested scoping survives isolation.
20
+ */
21
+ workPath: string;
22
+ }
23
+ export interface WorktreeCleanupResult {
24
+ /** Whether changes were found in the worktree. */
25
+ hasChanges: boolean;
26
+ /** Branch name if changes were committed. */
27
+ branch?: string;
28
+ /** Worktree path if it was kept. */
29
+ path?: string;
30
+ }
31
+ /**
32
+ * Create a temporary git worktree for an agent.
33
+ * Returns the worktree path, or undefined if not in a git repo.
34
+ */
35
+ export declare function createWorktree(cwd: string, agentId: string): WorktreeInfo | undefined;
36
+ /**
37
+ * Clean up a worktree after agent completion.
38
+ * - If no changes: remove worktree entirely.
39
+ * - If changes exist: create a branch, commit changes, return branch info.
40
+ */
41
+ export declare function cleanupWorktree(cwd: string, worktree: WorktreeInfo, agentDescription: string): WorktreeCleanupResult;
42
+ /**
43
+ * Prune any orphaned worktrees (crash recovery).
44
+ */
45
+ export declare function pruneWorktrees(cwd: string): void;
@@ -0,0 +1,160 @@
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
+ import { execFileSync } from "node:child_process";
9
+ import { randomUUID } from "node:crypto";
10
+ import { existsSync, realpathSync } from "node:fs";
11
+ import { tmpdir } from "node:os";
12
+ import { join, relative } from "node:path";
13
+ /**
14
+ * Create a temporary git worktree for an agent.
15
+ * Returns the worktree path, or undefined if not in a git repo.
16
+ */
17
+ export function createWorktree(cwd, agentId) {
18
+ // Verify we're in a git repo with at least one commit (HEAD must exist)
19
+ let baseSha;
20
+ let subdir;
21
+ try {
22
+ execFileSync("git", ["rev-parse", "--is-inside-work-tree"], { cwd, stdio: "pipe", timeout: 5000 });
23
+ baseSha = execFileSync("git", ["rev-parse", "HEAD"], { cwd, stdio: "pipe", timeout: 5000 })
24
+ .toString()
25
+ .trim();
26
+ // Where cwd sits inside the repo ("" at the root): the agent must work at
27
+ // the same subdirectory inside the copy, or a monorepo-package cwd would
28
+ // silently widen to the whole repo. realpath both sides — git emits
29
+ // resolved paths while cwd may arrive through a symlink (macOS /tmp).
30
+ const topLevel = execFileSync("git", ["rev-parse", "--show-toplevel"], { cwd, stdio: "pipe", timeout: 5000 })
31
+ .toString()
32
+ .trim();
33
+ subdir = relative(realpathSync(topLevel), realpathSync(cwd));
34
+ }
35
+ catch {
36
+ return undefined;
37
+ }
38
+ const branch = `pi-agent-${agentId}`;
39
+ const suffix = randomUUID().slice(0, 8);
40
+ const worktreePath = join(tmpdir(), `pi-agent-${agentId}-${suffix}`);
41
+ try {
42
+ // Create detached worktree at HEAD
43
+ execFileSync("git", ["worktree", "add", "--detach", worktreePath, "HEAD"], {
44
+ cwd,
45
+ stdio: "pipe",
46
+ timeout: 30000,
47
+ });
48
+ return { path: worktreePath, branch, baseSha, workPath: subdir ? join(worktreePath, subdir) : worktreePath };
49
+ }
50
+ catch {
51
+ // If worktree creation fails, return undefined (agent runs in normal cwd)
52
+ return undefined;
53
+ }
54
+ }
55
+ /**
56
+ * Clean up a worktree after agent completion.
57
+ * - If no changes: remove worktree entirely.
58
+ * - If changes exist: create a branch, commit changes, return branch info.
59
+ */
60
+ export function cleanupWorktree(cwd, worktree, agentDescription) {
61
+ if (!existsSync(worktree.path)) {
62
+ return { hasChanges: false };
63
+ }
64
+ try {
65
+ // Check for uncommitted changes in the worktree
66
+ const status = execFileSync("git", ["status", "--porcelain"], {
67
+ cwd: worktree.path,
68
+ stdio: "pipe",
69
+ timeout: 10000,
70
+ }).toString().trim();
71
+ if (status) {
72
+ // Changes exist — stage, commit, and create a branch
73
+ execFileSync("git", ["add", "-A"], { cwd: worktree.path, stdio: "pipe", timeout: 10000 });
74
+ // Truncate description for commit message (no shell sanitization needed — execFileSync uses argv)
75
+ const safeDesc = agentDescription.slice(0, 200);
76
+ const commitMsg = `pi-agent: ${safeDesc}`;
77
+ execFileSync("git", ["commit", "--no-verify", "-m", commitMsg], {
78
+ cwd: worktree.path,
79
+ stdio: "pipe",
80
+ timeout: 10000,
81
+ });
82
+ }
83
+ else {
84
+ const currentSha = execFileSync("git", ["rev-parse", "HEAD"], {
85
+ cwd: worktree.path,
86
+ stdio: "pipe",
87
+ timeout: 5000,
88
+ }).toString().trim();
89
+ if (currentSha === worktree.baseSha) {
90
+ // No changes — remove worktree
91
+ removeWorktree(cwd, worktree.path);
92
+ return { hasChanges: false };
93
+ }
94
+ }
95
+ // Create a branch pointing to the worktree's HEAD.
96
+ // If the branch already exists, append a suffix to avoid overwriting previous work.
97
+ let branchName = worktree.branch;
98
+ try {
99
+ execFileSync("git", ["branch", branchName], {
100
+ cwd: worktree.path,
101
+ stdio: "pipe",
102
+ timeout: 5000,
103
+ });
104
+ }
105
+ catch {
106
+ // Branch already exists — use a unique suffix
107
+ branchName = `${worktree.branch}-${Date.now()}`;
108
+ execFileSync("git", ["branch", branchName], {
109
+ cwd: worktree.path,
110
+ stdio: "pipe",
111
+ timeout: 5000,
112
+ });
113
+ }
114
+ // Update branch name in worktree info for the caller
115
+ worktree.branch = branchName;
116
+ // Remove the worktree (branch persists in main repo)
117
+ removeWorktree(cwd, worktree.path);
118
+ return {
119
+ hasChanges: true,
120
+ branch: worktree.branch,
121
+ path: worktree.path,
122
+ };
123
+ }
124
+ catch {
125
+ // Best effort cleanup on error
126
+ try {
127
+ removeWorktree(cwd, worktree.path);
128
+ }
129
+ catch { /* ignore */ }
130
+ return { hasChanges: false };
131
+ }
132
+ }
133
+ /**
134
+ * Force-remove a worktree.
135
+ */
136
+ function removeWorktree(cwd, worktreePath) {
137
+ try {
138
+ execFileSync("git", ["worktree", "remove", "--force", worktreePath], {
139
+ cwd,
140
+ stdio: "pipe",
141
+ timeout: 10000,
142
+ });
143
+ }
144
+ catch {
145
+ // If git worktree remove fails, try pruning
146
+ try {
147
+ execFileSync("git", ["worktree", "prune"], { cwd, stdio: "pipe", timeout: 5000 });
148
+ }
149
+ catch { /* ignore */ }
150
+ }
151
+ }
152
+ /**
153
+ * Prune any orphaned worktrees (crash recovery).
154
+ */
155
+ export function pruneWorktrees(cwd) {
156
+ try {
157
+ execFileSync("git", ["worktree", "prune"], { cwd, stdio: "pipe", timeout: 5000 });
158
+ }
159
+ catch { /* ignore */ }
160
+ }
@@ -0,0 +1,56 @@
1
+ # Default extension tool exposure
2
+
3
+ ## Problem
4
+
5
+ Subagents have two separate controls:
6
+
7
+ - `extensions:` decides which extensions load.
8
+ - `tools:` decides which tools surface to the model.
9
+
10
+ That split is useful for least privilege, but some extensions provide
11
+ infrastructure tools that should usually be present whenever the extension is
12
+ loaded. `pi-c2c` is the first concrete example: a subagent that loads it needs
13
+ the c2c tools to identify itself and message its parent, even when the agent's
14
+ `tools:` field narrows other extension tools with `ext:` selectors.
15
+
16
+ ## Current v1 behavior
17
+
18
+ `pi-c2c` is special-cased in `agent-runner.ts` as an auto-exposed extension.
19
+ When a non-isolated subagent loads an extension whose canonical name is
20
+ `pi-c2c`, all tools registered by that extension are included in the session
21
+ allowlist.
22
+
23
+ The existing safety controls still apply:
24
+
25
+ - `isolated: true` forces `extensions: false`, so no pi-c2c tools surface.
26
+ - `extensions: false` loads no extension tools.
27
+ - `exclude_extensions: pi-c2c` removes pi-c2c before tool enumeration.
28
+ - `disallowed_tools` can remove individual pi-c2c tools.
29
+
30
+ All other extensions keep the existing `ext:` opt-in behavior.
31
+
32
+ ## Future generic convention
33
+
34
+ A future version could let extensions declare this behavior themselves through
35
+ metadata, for example:
36
+
37
+ ```ts
38
+ export const piExtensionMetadata = {
39
+ subagents: {
40
+ defaultExposeTools: true,
41
+ },
42
+ };
43
+ ```
44
+
45
+ Open questions before implementing a generic convention:
46
+
47
+ - Where should metadata live so both source-loaded and packaged extensions can
48
+ expose it without running arbitrary factory code first?
49
+ - Should the metadata expose all tools, a named subset, or tool tags?
50
+ - How should conflicts be reported when `tools:` explicitly narrows an
51
+ auto-exposed extension?
52
+ - Should this be limited to extensions that are already trusted by the parent
53
+ session, or can project extensions opt in too?
54
+
55
+ This design note intentionally does not implement generic metadata. The v1
56
+ implementation is limited to the known `pi-c2c` integration.