@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
package/RELEASE.md ADDED
@@ -0,0 +1,39 @@
1
+ # Release Process
2
+
3
+ 1. **Prepare changes**
4
+ - Ensure all work is on `master` and CI/tests pass:
5
+ ```bash
6
+ npm run lint
7
+ npm run typecheck
8
+ npm test
9
+ npm run build
10
+ ```
11
+
12
+ 2. **Update documentation**
13
+ - Bump `version` in `package.json`.
14
+ - Add a dated `[x.y.z]` section to `CHANGELOG.md` under `[Unreleased]`.
15
+ - **Update `README.md` to describe any new or changed user-facing features.**
16
+
17
+ 3. **Commit and push**
18
+ ```bash
19
+ git add package.json CHANGELOG.md README.md
20
+ git commit -m "chore(release): bump version to x.y.z"
21
+ git push
22
+ ```
23
+
24
+ 4. **Create a GitHub release**
25
+ - Tag the commit matching the version:
26
+ ```bash
27
+ git tag vX.Y.Z
28
+ git push origin vX.Y.Z
29
+ ```
30
+ - Open the [GitHub releases page](https://github.com/clankercode/pi-subagents/releases) and create a new release for the tag.
31
+ - Copy the relevant `[x.y.z]` section from `CHANGELOG.md` into the release notes.
32
+ - Highlight any breaking changes, fork-specific features, or upgrade notes.
33
+
34
+ 5. **Publish to npm**
35
+ ```bash
36
+ npm publish
37
+ ```
38
+
39
+ > Note: `prepublishOnly` already runs lint, typecheck, tests, and build before publishing.
@@ -0,0 +1,35 @@
1
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
+ /**
3
+ * Register a keyboard shortcut that aborts the current streaming turn and
4
+ * auto-sends any queued follow-up message(s) as the next turn — instead of the
5
+ * default Escape behavior (restore the queue into the editor for editing).
6
+ *
7
+ * Key precedence: PI_ABORT_RESEND_KEY env var > the `abortResendKey` setting >
8
+ * "f9" (the default — a distinct key on every terminal; shift+escape is
9
+ * indistinguishable from escape on terminals that don't negotiate the kitty
10
+ * keyboard protocol).
11
+ *
12
+ * This is a workaround for a core-harness behavior: Escape always calls
13
+ * `restoreQueuedMessagesToEditor({ abort: true })`, putting queued messages back
14
+ * in the editor. There is no extension API to flush the internal queue as a turn
15
+ * atomically. This shortcut reads the text that abort just restored into the
16
+ * editor, clears it, and re-injects it as a fresh turn.
17
+ *
18
+ * Mechanics:
19
+ * 1. `ctx.abort()` is synchronous: it calls restoreQueuedMessagesToEditor which
20
+ * sets the editor text, THEN agent.abort(). So getEditorText() right after
21
+ * captures the restored queue.
22
+ * 2. `pi.sendUserMessage(text, { deliverAs: "steer" })` injects into a fresh turn.
23
+ * MUST be awaited with try/catch — an unhandled rejection would discard the
24
+ * text silently after the editor has already been cleared, losing the message.
25
+ *
26
+ * State handling:
27
+ * - idle → no-op (preserves any editor draft)
28
+ * - streaming, nothing queued → plain abort (preserves editor draft)
29
+ * - streaming, queued messages → abort + clear restored text + resend as steer
30
+ *
31
+ * The shortcut is registered once at session start, so changing the setting
32
+ * applies on the next pi session (consistent with schedulingEnabled /
33
+ * toolDescriptionMode).
34
+ */
35
+ export declare function registerAbortResend(pi: ExtensionAPI, settingKey?: string): void;
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Register a keyboard shortcut that aborts the current streaming turn and
3
+ * auto-sends any queued follow-up message(s) as the next turn — instead of the
4
+ * default Escape behavior (restore the queue into the editor for editing).
5
+ *
6
+ * Key precedence: PI_ABORT_RESEND_KEY env var > the `abortResendKey` setting >
7
+ * "f9" (the default — a distinct key on every terminal; shift+escape is
8
+ * indistinguishable from escape on terminals that don't negotiate the kitty
9
+ * keyboard protocol).
10
+ *
11
+ * This is a workaround for a core-harness behavior: Escape always calls
12
+ * `restoreQueuedMessagesToEditor({ abort: true })`, putting queued messages back
13
+ * in the editor. There is no extension API to flush the internal queue as a turn
14
+ * atomically. This shortcut reads the text that abort just restored into the
15
+ * editor, clears it, and re-injects it as a fresh turn.
16
+ *
17
+ * Mechanics:
18
+ * 1. `ctx.abort()` is synchronous: it calls restoreQueuedMessagesToEditor which
19
+ * sets the editor text, THEN agent.abort(). So getEditorText() right after
20
+ * captures the restored queue.
21
+ * 2. `pi.sendUserMessage(text, { deliverAs: "steer" })` injects into a fresh turn.
22
+ * MUST be awaited with try/catch — an unhandled rejection would discard the
23
+ * text silently after the editor has already been cleared, losing the message.
24
+ *
25
+ * State handling:
26
+ * - idle → no-op (preserves any editor draft)
27
+ * - streaming, nothing queued → plain abort (preserves editor draft)
28
+ * - streaming, queued messages → abort + clear restored text + resend as steer
29
+ *
30
+ * The shortcut is registered once at session start, so changing the setting
31
+ * applies on the next pi session (consistent with schedulingEnabled /
32
+ * toolDescriptionMode).
33
+ */
34
+ export function registerAbortResend(pi, settingKey) {
35
+ const ABORT_RESEND_SHORTCUT = (process.env.PI_ABORT_RESEND_KEY ?? settingKey ?? "f9");
36
+ pi.registerShortcut(ABORT_RESEND_SHORTCUT, {
37
+ description: "Abort current turn and auto-send queued message(s) as the next turn",
38
+ handler: async (ctx) => {
39
+ // Idle: nothing to abort; leave any editor draft untouched.
40
+ if (ctx.isIdle())
41
+ return;
42
+ // Snapshot whether anything is queued BEFORE abort — abort clears the
43
+ // internal queue and restores it to the editor, so hasPendingMessages()
44
+ // would read false afterwards.
45
+ const hadQueued = ctx.hasPendingMessages();
46
+ // Abort restores queued messages to the editor synchronously, then aborts.
47
+ ctx.abort();
48
+ // No queued message → this was a plain abort; preserve the editor draft.
49
+ if (!hadQueued)
50
+ return;
51
+ // Read the text abort just restored into the editor, clear it, and re-inject
52
+ // as a fresh turn. MUST await + try/catch so errors surface rather than
53
+ // silently swallowing the queued text (an unhandled rejection from inside
54
+ // the non-awaited sendUserMessage call would discard the text after the
55
+ // editor has already been cleared by restoreQueuedMessagesToEditor).
56
+ const text = ctx.ui.getEditorText().trim();
57
+ ctx.ui.setEditorText("");
58
+ if (text) {
59
+ try {
60
+ await pi.sendUserMessage(text, { deliverAs: "steer" });
61
+ }
62
+ catch (err) {
63
+ console.error("[abort-resend] sendUserMessage failed:", err);
64
+ // Fall back to putting the text back in the editor so the user can
65
+ // re-submit manually.
66
+ ctx.ui.setEditorText(text);
67
+ }
68
+ }
69
+ },
70
+ });
71
+ }
@@ -0,0 +1,17 @@
1
+ import type { AgentActivity, AgentDetails } from "./ui/agent-widget.js";
2
+ import type { LifetimeUsage } from "./usage.js";
3
+ /** Format an agent's lifetime token total, or "" when zero. */
4
+ export declare function formatLifetimeTokens(o: {
5
+ lifetimeUsage: LifetimeUsage;
6
+ }): string;
7
+ /** Build AgentDetails from a base plus record-specific fields. */
8
+ export declare function buildDetails(base: Pick<AgentDetails, "displayName" | "description" | "subagentType" | "modelName" | "tags">, record: {
9
+ toolUses: number;
10
+ startedAt: number;
11
+ completedAt?: number;
12
+ status: string;
13
+ error?: string;
14
+ id?: string;
15
+ session?: any;
16
+ lifetimeUsage: LifetimeUsage;
17
+ }, activity?: AgentActivity, overrides?: Partial<AgentDetails>): AgentDetails;
@@ -0,0 +1,22 @@
1
+ import { formatTokens } from "./ui/agent-widget.js";
2
+ import { getLifetimeTotal } from "./usage.js";
3
+ /** Format an agent's lifetime token total, or "" when zero. */
4
+ export function formatLifetimeTokens(o) {
5
+ const t = getLifetimeTotal(o.lifetimeUsage);
6
+ return t > 0 ? formatTokens(t) : "";
7
+ }
8
+ /** Build AgentDetails from a base plus record-specific fields. */
9
+ export function buildDetails(base, record, activity, overrides) {
10
+ return {
11
+ ...base,
12
+ toolUses: record.toolUses,
13
+ tokens: formatLifetimeTokens(record),
14
+ turnCount: activity?.turnCount,
15
+ maxTurns: activity?.maxTurns,
16
+ durationMs: (record.completedAt ?? Date.now()) - record.startedAt,
17
+ status: record.status,
18
+ agentId: record.id,
19
+ error: record.error,
20
+ ...overrides,
21
+ };
22
+ }
@@ -0,0 +1,132 @@
1
+ /**
2
+ * agent-manager.ts — Tracks agents, background execution, resume support.
3
+ *
4
+ * Background agents are subject to a configurable concurrency limit (default: 4).
5
+ * Excess agents are queued and auto-started as running agents complete.
6
+ * Foreground agents bypass the queue (they block the parent anyway).
7
+ */
8
+ import type { Model } from "@earendil-works/pi-ai";
9
+ import type { AgentSession, ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
10
+ import { type ToolActivity } from "./agent-runner.js";
11
+ import { type AgentInvocation, type AgentRecord, type IsolationMode, type SubagentType, type ThinkingLevel } from "./types.js";
12
+ export type OnAgentComplete = (record: AgentRecord) => void;
13
+ export type OnAgentStart = (record: AgentRecord) => void;
14
+ export type OnAgentCompact = (record: AgentRecord, info: CompactionInfo) => void;
15
+ export type CompactionInfo = {
16
+ reason: "manual" | "threshold" | "overflow";
17
+ tokensBefore: number;
18
+ };
19
+ interface SpawnOptions {
20
+ description: string;
21
+ model?: Model<any>;
22
+ maxTurns?: number;
23
+ isolated?: boolean;
24
+ inheritContext?: boolean;
25
+ thinkingLevel?: ThinkingLevel;
26
+ isBackground?: boolean;
27
+ /**
28
+ * Skip the maxConcurrent queue check for this spawn — start immediately even
29
+ * if the configured concurrency limit would otherwise queue it. Used by the
30
+ * scheduler so a fired job can't be deferred past its trigger window.
31
+ */
32
+ bypassQueue?: boolean;
33
+ /** Isolation mode — "worktree" creates a temp git worktree for the agent. */
34
+ isolation?: IsolationMode;
35
+ /**
36
+ * Working directory for the agent (absolute path). Default: parent session
37
+ * cwd. The agent's tools operate here, but .pi config (extensions, skills,
38
+ * settings, memory) still loads from the parent session's project — the
39
+ * target directory's `.pi` extensions never execute. With isolation:
40
+ * "worktree", the worktree is created FROM this directory and the result
41
+ * branch lands in that repo.
42
+ */
43
+ cwd?: string;
44
+ /** Resolved invocation snapshot captured for UI display. */
45
+ invocation?: AgentInvocation;
46
+ /** Recursive subagent depth. Parent/orchestrator is 0; spawned agents are 1..4. */
47
+ depth?: number;
48
+ /** Parent subagent id when spawned recursively from another subagent. */
49
+ parentAgentId?: string;
50
+ /** Parent abort signal — when aborted, the subagent is also stopped. */
51
+ signal?: AbortSignal;
52
+ /** Called on tool start/end with activity info (for streaming progress to UI). */
53
+ onToolActivity?: (activity: ToolActivity) => void;
54
+ /** Called on streaming text deltas from the assistant response. */
55
+ onTextDelta?: (delta: string, fullText: string) => void;
56
+ /** Called when the agent session is created (for accessing session stats). */
57
+ onSessionCreated?: (session: AgentSession) => void;
58
+ /** Called at the end of each agentic turn with the cumulative count. */
59
+ onTurnEnd?: (turnCount: number) => void;
60
+ /** Called once per assistant message_end with that message's usage delta. */
61
+ onAssistantUsage?: (usage: {
62
+ input: number;
63
+ output: number;
64
+ cacheWrite: number;
65
+ }) => void;
66
+ /** Called when the session successfully compacts. */
67
+ onCompaction?: (info: CompactionInfo) => void;
68
+ }
69
+ interface ResumeOptions {
70
+ signal?: AbortSignal;
71
+ onToolActivity?: (activity: ToolActivity) => void;
72
+ onAssistantUsage?: (usage: {
73
+ input: number;
74
+ output: number;
75
+ cacheWrite: number;
76
+ }) => void;
77
+ onCompaction?: (info: CompactionInfo) => void;
78
+ }
79
+ export declare class AgentManager {
80
+ private agents;
81
+ private cleanupInterval;
82
+ private onComplete?;
83
+ private onStart?;
84
+ private onCompact?;
85
+ private maxConcurrent;
86
+ /** Base repos worktrees were created from — so dispose() can prune them all,
87
+ * not just the parent repo (caller-supplied cwd can target other repos). */
88
+ private worktreeRepos;
89
+ /** Queue of background agents waiting to start. */
90
+ private queue;
91
+ /** Number of currently running background agents. */
92
+ private runningBackground;
93
+ constructor(onComplete?: OnAgentComplete, maxConcurrent?: number, onStart?: OnAgentStart, onCompact?: OnAgentCompact);
94
+ /** Update the max concurrent background agents limit. */
95
+ setMaxConcurrent(n: number): void;
96
+ getMaxConcurrent(): number;
97
+ /**
98
+ * Spawn an agent and return its ID immediately (for background use).
99
+ * If the concurrency limit is reached, the agent is queued.
100
+ */
101
+ spawn(pi: ExtensionAPI, ctx: ExtensionContext, type: SubagentType, prompt: string, options: SpawnOptions): string;
102
+ /** Actually start an agent (called immediately or from queue drain). */
103
+ private startAgent;
104
+ /** Start queued agents up to the concurrency limit. */
105
+ private drainQueue;
106
+ /**
107
+ * Spawn an agent and wait for completion (foreground use).
108
+ * Foreground agents bypass the concurrency queue.
109
+ */
110
+ spawnAndWait(pi: ExtensionAPI, ctx: ExtensionContext, type: SubagentType, prompt: string, options: Omit<SpawnOptions, "isBackground">): Promise<AgentRecord>;
111
+ /** Resume an existing agent session in the background. */
112
+ resume(id: string, prompt: string, signalOrOptions?: AbortSignal | ResumeOptions): AgentRecord | undefined;
113
+ getRecord(id: string): AgentRecord | undefined;
114
+ listAgents(): AgentRecord[];
115
+ abort(id: string): boolean;
116
+ /** Dispose a record's session and remove it from the map. */
117
+ private removeRecord;
118
+ private cleanup;
119
+ /**
120
+ * Remove all completed/stopped/errored records immediately.
121
+ * Called on session start/switch so tasks from a prior session don't persist.
122
+ */
123
+ clearCompleted(): void;
124
+ /** Whether any agents are still running or queued. */
125
+ hasRunning(): boolean;
126
+ /** Abort all running and queued agents immediately. */
127
+ abortAll(): number;
128
+ /** Wait for all running and queued agents to complete (including queued ones). */
129
+ waitForAll(): Promise<void>;
130
+ dispose(): void;
131
+ }
132
+ export {};