pi-mono-all 1.0.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.
Files changed (161) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/LICENCE.md +7 -0
  3. package/node_modules/pi-common/package.json +22 -0
  4. package/node_modules/pi-common/src/auth-config.ts +290 -0
  5. package/node_modules/pi-common/src/auth.ts +63 -0
  6. package/node_modules/pi-common/src/cache.ts +60 -0
  7. package/node_modules/pi-common/src/errors.ts +47 -0
  8. package/node_modules/pi-common/src/http-client.ts +118 -0
  9. package/node_modules/pi-common/src/index.ts +7 -0
  10. package/node_modules/pi-common/src/rate-limiter.ts +32 -0
  11. package/node_modules/pi-common/src/tool-result.ts +27 -0
  12. package/node_modules/pi-mono-ask-user-question/CHANGELOG.md +185 -0
  13. package/node_modules/pi-mono-ask-user-question/README.md +226 -0
  14. package/node_modules/pi-mono-ask-user-question/index.ts +923 -0
  15. package/node_modules/pi-mono-ask-user-question/package.json +29 -0
  16. package/node_modules/pi-mono-auto-fix/CHANGELOG.md +59 -0
  17. package/node_modules/pi-mono-auto-fix/README.md +77 -0
  18. package/node_modules/pi-mono-auto-fix/index.ts +488 -0
  19. package/node_modules/pi-mono-auto-fix/package.json +23 -0
  20. package/node_modules/pi-mono-btw/CHANGELOG.md +180 -0
  21. package/node_modules/pi-mono-btw/README.md +24 -0
  22. package/node_modules/pi-mono-btw/index.ts +499 -0
  23. package/node_modules/pi-mono-btw/package.json +29 -0
  24. package/node_modules/pi-mono-clear/CHANGELOG.md +180 -0
  25. package/node_modules/pi-mono-clear/README.md +40 -0
  26. package/node_modules/pi-mono-clear/index.ts +45 -0
  27. package/node_modules/pi-mono-clear/package.json +29 -0
  28. package/node_modules/pi-mono-context/CHANGELOG.md +12 -0
  29. package/node_modules/pi-mono-context/README.md +74 -0
  30. package/node_modules/pi-mono-context/index.ts +641 -0
  31. package/node_modules/pi-mono-context/package.json +29 -0
  32. package/node_modules/pi-mono-context-guard/CHANGELOG.md +195 -0
  33. package/node_modules/pi-mono-context-guard/README.md +81 -0
  34. package/node_modules/pi-mono-context-guard/index.ts +212 -0
  35. package/node_modules/pi-mono-context-guard/package.json +23 -0
  36. package/node_modules/pi-mono-figma/CHANGELOG.md +59 -0
  37. package/node_modules/pi-mono-figma/README.md +236 -0
  38. package/node_modules/pi-mono-figma/__tests__/code-connect.test.ts +32 -0
  39. package/node_modules/pi-mono-figma/__tests__/figma-assets.test.ts +38 -0
  40. package/node_modules/pi-mono-figma/__tests__/figma-component-hints.test.ts +23 -0
  41. package/node_modules/pi-mono-figma/__tests__/figma-implementation-layout.test.ts +47 -0
  42. package/node_modules/pi-mono-figma/__tests__/figma-search.test.ts +51 -0
  43. package/node_modules/pi-mono-figma/__tests__/figma-summarizer.test.ts +65 -0
  44. package/node_modules/pi-mono-figma/__tests__/fixtures/complex-auto-layout.json +115 -0
  45. package/node_modules/pi-mono-figma/__tests__/fixtures/component-instance.json +50 -0
  46. package/node_modules/pi-mono-figma/__tests__/fixtures/hidden-and-vectors.json +28 -0
  47. package/node_modules/pi-mono-figma/__tests__/fixtures/variables-and-styles.json +40 -0
  48. package/node_modules/pi-mono-figma/docs/live-selection-bridge.md +16 -0
  49. package/node_modules/pi-mono-figma/index.ts +6 -0
  50. package/node_modules/pi-mono-figma/package.json +33 -0
  51. package/node_modules/pi-mono-figma/skills/figma/SKILL.md +143 -0
  52. package/node_modules/pi-mono-figma/src/code-connect.ts +110 -0
  53. package/node_modules/pi-mono-figma/src/figma-assets.ts +146 -0
  54. package/node_modules/pi-mono-figma/src/figma-cache.ts +6 -0
  55. package/node_modules/pi-mono-figma/src/figma-client.ts +471 -0
  56. package/node_modules/pi-mono-figma/src/figma-component-hints.ts +87 -0
  57. package/node_modules/pi-mono-figma/src/figma-implementation.ts +264 -0
  58. package/node_modules/pi-mono-figma/src/figma-schemas.ts +139 -0
  59. package/node_modules/pi-mono-figma/src/figma-search.ts +195 -0
  60. package/node_modules/pi-mono-figma/src/figma-summarizer.ts +673 -0
  61. package/node_modules/pi-mono-figma/src/figma-tokens.ts +57 -0
  62. package/node_modules/pi-mono-figma/src/figma-tools.ts +352 -0
  63. package/node_modules/pi-mono-linear/CHANGELOG.md +44 -0
  64. package/node_modules/pi-mono-linear/README.md +159 -0
  65. package/node_modules/pi-mono-linear/index.ts +6 -0
  66. package/node_modules/pi-mono-linear/package.json +30 -0
  67. package/node_modules/pi-mono-linear/skills/linear/SKILL.md +107 -0
  68. package/node_modules/pi-mono-linear/src/linear-client.ts +339 -0
  69. package/node_modules/pi-mono-linear/src/linear-queries.ts +101 -0
  70. package/node_modules/pi-mono-linear/src/linear-schemas.ts +90 -0
  71. package/node_modules/pi-mono-linear/src/linear-tools.ts +362 -0
  72. package/node_modules/pi-mono-loop/CHANGELOG.md +163 -0
  73. package/node_modules/pi-mono-loop/README.md +54 -0
  74. package/node_modules/pi-mono-loop/index.ts +291 -0
  75. package/node_modules/pi-mono-loop/package.json +26 -0
  76. package/node_modules/pi-mono-multi-edit/CHANGELOG.md +232 -0
  77. package/node_modules/pi-mono-multi-edit/README.md +244 -0
  78. package/node_modules/pi-mono-multi-edit/__tests__/classic.test.ts +277 -0
  79. package/node_modules/pi-mono-multi-edit/__tests__/diff.test.ts +77 -0
  80. package/node_modules/pi-mono-multi-edit/__tests__/patch.test.ts +287 -0
  81. package/node_modules/pi-mono-multi-edit/benchmark-edits.ts +966 -0
  82. package/node_modules/pi-mono-multi-edit/classic.ts +435 -0
  83. package/node_modules/pi-mono-multi-edit/diff.ts +143 -0
  84. package/node_modules/pi-mono-multi-edit/index.ts +266 -0
  85. package/node_modules/pi-mono-multi-edit/package.json +37 -0
  86. package/node_modules/pi-mono-multi-edit/patch.ts +463 -0
  87. package/node_modules/pi-mono-multi-edit/types.ts +53 -0
  88. package/node_modules/pi-mono-multi-edit/workspace.ts +85 -0
  89. package/node_modules/pi-mono-review/CHANGELOG.md +190 -0
  90. package/node_modules/pi-mono-review/README.md +30 -0
  91. package/node_modules/pi-mono-review/common.ts +930 -0
  92. package/node_modules/pi-mono-review/index.ts +8 -0
  93. package/node_modules/pi-mono-review/package.json +29 -0
  94. package/node_modules/pi-mono-review/review-tui.ts +194 -0
  95. package/node_modules/pi-mono-review/review.ts +119 -0
  96. package/node_modules/pi-mono-review/reviewer.ts +339 -0
  97. package/node_modules/pi-mono-sentinel/CHANGELOG.md +158 -0
  98. package/node_modules/pi-mono-sentinel/README.md +87 -0
  99. package/node_modules/pi-mono-sentinel/__tests__/output-scanner.test.ts +109 -0
  100. package/node_modules/pi-mono-sentinel/__tests__/permissions.test.ts +202 -0
  101. package/node_modules/pi-mono-sentinel/__tests__/whitelist.test.ts +59 -0
  102. package/node_modules/pi-mono-sentinel/guards/execution-tracker.ts +281 -0
  103. package/node_modules/pi-mono-sentinel/guards/output-scanner.ts +232 -0
  104. package/node_modules/pi-mono-sentinel/guards/permission-gate.ts +170 -0
  105. package/node_modules/pi-mono-sentinel/index.ts +43 -0
  106. package/node_modules/pi-mono-sentinel/package.json +26 -0
  107. package/node_modules/pi-mono-sentinel/patterns/permissions.ts +175 -0
  108. package/node_modules/pi-mono-sentinel/patterns/read-targets.ts +104 -0
  109. package/node_modules/pi-mono-sentinel/patterns/secrets.ts +143 -0
  110. package/node_modules/pi-mono-sentinel/session.ts +95 -0
  111. package/node_modules/pi-mono-sentinel/specs/2026/04/sentinel/001-permission-gate.md +145 -0
  112. package/node_modules/pi-mono-sentinel/types.ts +39 -0
  113. package/node_modules/pi-mono-sentinel/whitelist.ts +86 -0
  114. package/node_modules/pi-mono-simplify/CHANGELOG.md +163 -0
  115. package/node_modules/pi-mono-simplify/README.md +56 -0
  116. package/node_modules/pi-mono-simplify/index.ts +78 -0
  117. package/node_modules/pi-mono-simplify/package.json +29 -0
  118. package/node_modules/pi-mono-status-line/CHANGELOG.md +180 -0
  119. package/node_modules/pi-mono-status-line/README.md +96 -0
  120. package/node_modules/pi-mono-status-line/basic.ts +89 -0
  121. package/node_modules/pi-mono-status-line/expert.ts +689 -0
  122. package/node_modules/pi-mono-status-line/index.ts +54 -0
  123. package/node_modules/pi-mono-status-line/package.json +29 -0
  124. package/node_modules/pi-mono-team-mode/CHANGELOG.md +278 -0
  125. package/node_modules/pi-mono-team-mode/README.md +246 -0
  126. package/node_modules/pi-mono-team-mode/__tests__/agent-manager-transient.test.ts +75 -0
  127. package/node_modules/pi-mono-team-mode/__tests__/delegation-manager.test.ts +118 -0
  128. package/node_modules/pi-mono-team-mode/__tests__/formatters.test.ts +104 -0
  129. package/node_modules/pi-mono-team-mode/__tests__/model-config.test.ts +272 -0
  130. package/node_modules/pi-mono-team-mode/__tests__/notification-box.test.ts +34 -0
  131. package/node_modules/pi-mono-team-mode/__tests__/parallel-utils.test.ts +32 -0
  132. package/node_modules/pi-mono-team-mode/__tests__/pi-stream-parser.test.ts +64 -0
  133. package/node_modules/pi-mono-team-mode/__tests__/prompts.test.ts +106 -0
  134. package/node_modules/pi-mono-team-mode/__tests__/store.test.ts +164 -0
  135. package/node_modules/pi-mono-team-mode/__tests__/tasks.test.ts +267 -0
  136. package/node_modules/pi-mono-team-mode/__tests__/teammate-specs.test.ts +114 -0
  137. package/node_modules/pi-mono-team-mode/__tests__/widget.test.ts +41 -0
  138. package/node_modules/pi-mono-team-mode/__tests__/worktree.test.ts +78 -0
  139. package/node_modules/pi-mono-team-mode/core/chain-utils.ts +90 -0
  140. package/node_modules/pi-mono-team-mode/core/fs-utils.ts +44 -0
  141. package/node_modules/pi-mono-team-mode/core/model-config.ts +432 -0
  142. package/node_modules/pi-mono-team-mode/core/parallel-utils.ts +48 -0
  143. package/node_modules/pi-mono-team-mode/core/prompts.ts +158 -0
  144. package/node_modules/pi-mono-team-mode/core/store.ts +156 -0
  145. package/node_modules/pi-mono-team-mode/core/tasks.ts +99 -0
  146. package/node_modules/pi-mono-team-mode/core/teammate-specs.ts +124 -0
  147. package/node_modules/pi-mono-team-mode/core/types.ts +160 -0
  148. package/node_modules/pi-mono-team-mode/index.ts +825 -0
  149. package/node_modules/pi-mono-team-mode/managers/agent-manager.ts +654 -0
  150. package/node_modules/pi-mono-team-mode/managers/delegation-manager.ts +211 -0
  151. package/node_modules/pi-mono-team-mode/managers/task-manager.ts +238 -0
  152. package/node_modules/pi-mono-team-mode/managers/team-manager.ts +59 -0
  153. package/node_modules/pi-mono-team-mode/package.json +33 -0
  154. package/node_modules/pi-mono-team-mode/runtime/pi-stream-parser.ts +194 -0
  155. package/node_modules/pi-mono-team-mode/runtime/subprocess.ts +183 -0
  156. package/node_modules/pi-mono-team-mode/runtime/transient-session.ts +196 -0
  157. package/node_modules/pi-mono-team-mode/runtime/worktree.ts +90 -0
  158. package/node_modules/pi-mono-team-mode/ui/formatters.ts +149 -0
  159. package/node_modules/pi-mono-team-mode/ui/notification-box.ts +55 -0
  160. package/node_modules/pi-mono-team-mode/ui/widget.ts +94 -0
  161. package/package.json +76 -0
@@ -0,0 +1,194 @@
1
+ // Pi Team-Mode — Parse pi JSON stdout into typed progress events
2
+
3
+ const MAX_PREVIEW_CHARS = 200;
4
+
5
+ export type PiUsage = {
6
+ inputTokens?: number;
7
+ outputTokens?: number;
8
+ totalTokens?: number;
9
+ cacheReadTokens?: number;
10
+ cacheWriteTokens?: number;
11
+ };
12
+
13
+ export type PiStreamEvent =
14
+ | { type: "assistant_message"; text: string; usage?: PiUsage; stopReason?: string }
15
+ | { type: "assistant_delta"; text: string }
16
+ | { type: "tool_start"; toolName: string; argsPreview?: string }
17
+ | { type: "tool_end"; toolName?: string; isError?: boolean; resultPreview?: string }
18
+ | { type: "turn_end" };
19
+
20
+ export class PiStreamParser {
21
+ private buffer = "";
22
+
23
+ push(chunk: string): PiStreamEvent[] {
24
+ this.buffer += chunk;
25
+ const lines = this.buffer.split("\n");
26
+ this.buffer = lines.pop() ?? "";
27
+ return this.parseLines(lines);
28
+ }
29
+
30
+ flush(): PiStreamEvent[] {
31
+ const tail = this.buffer.trim();
32
+ this.buffer = "";
33
+ if (!tail) return [];
34
+ return this.parseLines([tail]);
35
+ }
36
+
37
+ private parseLines(lines: string[]): PiStreamEvent[] {
38
+ const out: PiStreamEvent[] = [];
39
+ for (const line of lines) {
40
+ const trimmed = line.trim();
41
+ if (!trimmed) continue;
42
+ let event: Record<string, unknown>;
43
+ try {
44
+ event = JSON.parse(trimmed) as Record<string, unknown>;
45
+ } catch {
46
+ continue;
47
+ }
48
+ const mapped = mapEvent(event);
49
+ if (mapped) out.push(mapped);
50
+ }
51
+ return out;
52
+ }
53
+ }
54
+
55
+ function mapEvent(event: Record<string, unknown>): PiStreamEvent | undefined {
56
+ const eventType = str(event.type);
57
+ if (!eventType) return undefined;
58
+
59
+ if (eventType === "message_update") {
60
+ const delta = textDelta(event);
61
+ if (delta) return { type: "assistant_delta", text: delta };
62
+ return undefined;
63
+ }
64
+
65
+ if (eventType === "message_end") {
66
+ const assistant = assistantMessage(event);
67
+ if (!assistant) return undefined;
68
+ return {
69
+ type: "assistant_message",
70
+ text: assistant.text,
71
+ usage: assistant.usage,
72
+ stopReason: str(event.stopReason),
73
+ };
74
+ }
75
+
76
+ if (eventType === "tool_execution_start") {
77
+ const toolName = str(event.toolName);
78
+ if (!toolName) return undefined;
79
+ return {
80
+ type: "tool_start",
81
+ toolName,
82
+ argsPreview: preview(event.args),
83
+ };
84
+ }
85
+
86
+ if (eventType === "tool_execution_end") {
87
+ return {
88
+ type: "tool_end",
89
+ toolName: str(event.toolName),
90
+ isError: bool(event.isError),
91
+ resultPreview: preview(event.result),
92
+ };
93
+ }
94
+
95
+ if (eventType === "turn_end") return { type: "turn_end" };
96
+ return undefined;
97
+ }
98
+
99
+ function assistantMessage(event: Record<string, unknown>): { text: string; usage?: PiUsage } | undefined {
100
+ const message = obj(event.message);
101
+ if (!message) return undefined;
102
+ if (str(message.role) !== "assistant") return undefined;
103
+ const text = extractText(message);
104
+ return { text, usage: parseUsage(message.usage) };
105
+ }
106
+
107
+ function textDelta(event: Record<string, unknown>): string | undefined {
108
+ const message = obj(event.message);
109
+ if (message && str(message.role) && str(message.role) !== "assistant") return undefined;
110
+ const assistantEvent = obj(event.assistantMessageEvent);
111
+ if (!assistantEvent) return undefined;
112
+ if (str(assistantEvent.type) !== "text_delta") return undefined;
113
+ return str(assistantEvent.text) ?? str(assistantEvent.delta) ?? undefined;
114
+ }
115
+
116
+ function extractText(message: Record<string, unknown>): string {
117
+ const content = message.content;
118
+ if (!Array.isArray(content)) return "";
119
+ const parts: string[] = [];
120
+ for (const block of content) {
121
+ const rec = obj(block);
122
+ if (!rec) continue;
123
+ const text = str(rec.text);
124
+ if (text) parts.push(text);
125
+ }
126
+ return parts.join("\n").trim();
127
+ }
128
+
129
+ function parseUsage(value: unknown): PiUsage | undefined {
130
+ const usage = obj(value);
131
+ if (!usage) return undefined;
132
+ const mapped: PiUsage = {
133
+ inputTokens: num(usage.inputTokens) ?? num(usage.input_tokens) ?? num(usage.input),
134
+ outputTokens: num(usage.outputTokens) ?? num(usage.output_tokens) ?? num(usage.output),
135
+ totalTokens: num(usage.totalTokens) ?? num(usage.total_tokens),
136
+ cacheReadTokens: num(usage.cacheReadTokens) ?? num(usage.cache_read_tokens) ?? num(usage.cacheRead),
137
+ cacheWriteTokens: num(usage.cacheWriteTokens) ?? num(usage.cache_write_tokens) ?? num(usage.cacheWrite),
138
+ };
139
+ if (
140
+ mapped.totalTokens === undefined &&
141
+ (mapped.inputTokens !== undefined ||
142
+ mapped.outputTokens !== undefined ||
143
+ mapped.cacheReadTokens !== undefined ||
144
+ mapped.cacheWriteTokens !== undefined)
145
+ ) {
146
+ mapped.totalTokens =
147
+ (mapped.inputTokens ?? 0) +
148
+ (mapped.outputTokens ?? 0) +
149
+ (mapped.cacheReadTokens ?? 0) +
150
+ (mapped.cacheWriteTokens ?? 0);
151
+ }
152
+ if (
153
+ mapped.inputTokens === undefined &&
154
+ mapped.outputTokens === undefined &&
155
+ mapped.totalTokens === undefined &&
156
+ mapped.cacheReadTokens === undefined &&
157
+ mapped.cacheWriteTokens === undefined
158
+ ) {
159
+ return undefined;
160
+ }
161
+ return mapped;
162
+ }
163
+
164
+ function preview(value: unknown): string | undefined {
165
+ if (value === undefined) return undefined;
166
+ const raw =
167
+ typeof value === "string"
168
+ ? value
169
+ : (() => {
170
+ try {
171
+ return JSON.stringify(value);
172
+ } catch {
173
+ return String(value);
174
+ }
175
+ })();
176
+ if (!raw) return undefined;
177
+ return raw.length <= MAX_PREVIEW_CHARS ? raw : `${raw.slice(0, MAX_PREVIEW_CHARS)}…`;
178
+ }
179
+
180
+ function obj(value: unknown): Record<string, unknown> | undefined {
181
+ return typeof value === "object" && value !== null ? (value as Record<string, unknown>) : undefined;
182
+ }
183
+
184
+ function str(value: unknown): string | undefined {
185
+ return typeof value === "string" ? value : undefined;
186
+ }
187
+
188
+ function num(value: unknown): number | undefined {
189
+ return typeof value === "number" && Number.isFinite(value) ? value : undefined;
190
+ }
191
+
192
+ function bool(value: unknown): boolean | undefined {
193
+ return typeof value === "boolean" ? value : undefined;
194
+ }
@@ -0,0 +1,183 @@
1
+ // Pi Team-Mode — Pi Subprocess Runner
2
+
3
+ import { spawn, type ChildProcess } from "node:child_process";
4
+ import { createHash } from "node:crypto";
5
+ import { writeFile, mkdir } from "node:fs/promises";
6
+ import * as path from "node:path";
7
+ import * as os from "node:os";
8
+ import { PiStreamParser, type PiStreamEvent } from "./pi-stream-parser.js";
9
+ import type { ThinkingLevel } from "../core/types.js";
10
+
11
+ const STDERR_BUDGET = 8 * 1024;
12
+ const KILL_GRACE_MS = 3_000;
13
+
14
+ export type PiRunOptions = {
15
+ message: string;
16
+ cwd: string;
17
+ /** Absolute path to the pi session file. Pi creates it if missing, resumes if present. */
18
+ sessionPath: string;
19
+ /** Provider — maps to `--provider`. */
20
+ provider?: string;
21
+ /** Bare model id — maps to `--model`. */
22
+ model?: string;
23
+ /** Thinking level — maps to `--thinking`. */
24
+ thinkingLevel?: ThinkingLevel;
25
+ tools?: string[];
26
+ /** Raw markdown to append to the system prompt. Written to a content-hashed temp file. */
27
+ systemPromptBody?: string;
28
+ /** Propagated to the subprocess so it can scope its task board to this session. */
29
+ parentSessionId?: string;
30
+ /** Propagated to the subprocess as its task-owner identity. */
31
+ teammateName?: string;
32
+ onEvent?: (event: PiStreamEvent) => void;
33
+ signal?: AbortSignal;
34
+ };
35
+
36
+ export type PiRunResult = {
37
+ finalMessage: string;
38
+ exitCode: number | null;
39
+ exitSignal: NodeJS.Signals | null;
40
+ stderr: string;
41
+ };
42
+
43
+ export type PiRun = {
44
+ pid?: number;
45
+ promise: Promise<PiRunResult>;
46
+ abort: () => void;
47
+ };
48
+
49
+ export function runPi(opts: PiRunOptions): PiRun {
50
+ const controller = new AbortController();
51
+ if (opts.signal) {
52
+ if (opts.signal.aborted) controller.abort();
53
+ else opts.signal.addEventListener("abort", () => controller.abort(), { once: true });
54
+ }
55
+
56
+ const run: PiRun = {
57
+ promise: Promise.resolve({
58
+ finalMessage: "",
59
+ exitCode: null,
60
+ exitSignal: null,
61
+ stderr: "",
62
+ }),
63
+ abort: () => controller.abort(),
64
+ };
65
+
66
+ run.promise = (async (): Promise<PiRunResult> => {
67
+ const args = await buildArgs(opts);
68
+ const env: NodeJS.ProcessEnv = {
69
+ ...process.env,
70
+ PI_TEAM_MATE_SUBPROCESS: "1",
71
+ };
72
+ if (opts.parentSessionId) env.PI_TEAM_MATE_PARENT_SESSION_ID = opts.parentSessionId;
73
+ if (opts.teammateName) env.PI_TEAM_MATE_TEAMMATE_NAME = opts.teammateName;
74
+
75
+ const proc = spawn("pi", args, {
76
+ cwd: opts.cwd,
77
+ shell: false,
78
+ stdio: ["ignore", "pipe", "pipe"],
79
+ env,
80
+ });
81
+ run.pid = proc.pid;
82
+
83
+ const onAbort = () => terminate(proc);
84
+ controller.signal.addEventListener("abort", onAbort, { once: true });
85
+
86
+ try {
87
+ return await collect(proc, opts.onEvent);
88
+ } finally {
89
+ controller.signal.removeEventListener("abort", onAbort);
90
+ }
91
+ })();
92
+
93
+ return run;
94
+ }
95
+
96
+ // Separate `--provider` + `--model` flags avoid pi's `--model provider/id`
97
+ // resolver silently routing hyphenated provider names through amazon-bedrock.
98
+ async function buildArgs(opts: PiRunOptions): Promise<string[]> {
99
+ const args = ["--mode", "json", "-p", "--session", opts.sessionPath];
100
+ if (opts.provider) args.push("--provider", opts.provider);
101
+ if (opts.model) args.push("--model", opts.model);
102
+ if (opts.thinkingLevel) args.push("--thinking", opts.thinkingLevel);
103
+ if (opts.tools && opts.tools.length > 0) args.push("--tools", opts.tools.join(","));
104
+
105
+ if (opts.systemPromptBody && opts.systemPromptBody.trim().length > 0) {
106
+ const promptPath = await writeSystemPrompt(opts.systemPromptBody);
107
+ args.push("--append-system-prompt", promptPath);
108
+ }
109
+
110
+ args.push(opts.message);
111
+ return args;
112
+ }
113
+
114
+ // Content-hashed filename so identical specs reuse the same file and stale
115
+ // entries are bounded by distinct spec content rather than call count.
116
+ async function writeSystemPrompt(body: string): Promise<string> {
117
+ const dir = path.join(os.tmpdir(), "pi-team-mode");
118
+ await mkdir(dir, { recursive: true });
119
+ const hash = createHash("sha1").update(body).digest("hex").slice(0, 16);
120
+ const file = path.join(dir, `prompt-${hash}.md`);
121
+ await writeFile(file, body, "utf8");
122
+ return file;
123
+ }
124
+
125
+ function collect(proc: ChildProcess, onEvent?: (event: PiStreamEvent) => void): Promise<PiRunResult> {
126
+ const parser = new PiStreamParser();
127
+ let stderr = "";
128
+ let finalMessage = "";
129
+ const deltaBuffer: string[] = [];
130
+
131
+ const emit = (events: PiStreamEvent[]) => {
132
+ for (const event of events) {
133
+ onEvent?.(event);
134
+ if (event.type === "assistant_delta") {
135
+ deltaBuffer.push(event.text);
136
+ }
137
+ if (event.type === "assistant_message" && event.text) {
138
+ finalMessage = event.text;
139
+ }
140
+ }
141
+ };
142
+
143
+ proc.stdout?.on("data", (chunk: Buffer) => {
144
+ emit(parser.push(chunk.toString("utf8")));
145
+ });
146
+
147
+ proc.stderr?.on("data", (chunk: Buffer) => {
148
+ if (stderr.length >= STDERR_BUDGET) return;
149
+ stderr += chunk.toString("utf8").slice(0, STDERR_BUDGET - stderr.length);
150
+ });
151
+
152
+ return new Promise((resolve) => {
153
+ proc.on("close", (code, signal) => {
154
+ emit(parser.flush());
155
+ if (!finalMessage && deltaBuffer.length > 0) {
156
+ finalMessage = deltaBuffer.join("");
157
+ }
158
+ resolve({ finalMessage, exitCode: code, exitSignal: signal, stderr });
159
+ });
160
+ proc.on("error", (err) => {
161
+ stderr += `\n[spawn error] ${err.message}`;
162
+ resolve({ finalMessage, exitCode: null, exitSignal: null, stderr });
163
+ });
164
+ });
165
+ }
166
+
167
+ function terminate(proc: ChildProcess): void {
168
+ if (proc.exitCode !== null || proc.signalCode !== null) return;
169
+ try {
170
+ proc.kill("SIGTERM");
171
+ } catch {
172
+ /* ignore */
173
+ }
174
+ setTimeout(() => {
175
+ if (proc.exitCode === null && proc.signalCode === null) {
176
+ try {
177
+ proc.kill("SIGKILL");
178
+ } catch {
179
+ /* ignore */
180
+ }
181
+ }
182
+ }, KILL_GRACE_MS).unref();
183
+ }
@@ -0,0 +1,196 @@
1
+ // Pi Team-Mode — In-process transient AgentSession runner
2
+
3
+ import {
4
+ AuthStorage,
5
+ createAgentSession,
6
+ createBashTool,
7
+ createEditTool,
8
+ createFindTool,
9
+ createGrepTool,
10
+ createLsTool,
11
+ createReadTool,
12
+ createWriteTool,
13
+ DefaultResourceLoader,
14
+ getAgentDir,
15
+ ModelRegistry,
16
+ SessionManager,
17
+ type AgentSessionEvent,
18
+ } from "@mariozechner/pi-coding-agent";
19
+
20
+ import type {
21
+ LiveTeammateMetrics,
22
+ TeammateRunResult,
23
+ TeammateSpec,
24
+ ThinkingLevel,
25
+ } from "../core/types.js";
26
+
27
+ export type TransientSessionOpts = {
28
+ id: string;
29
+ name: string;
30
+ description: string;
31
+ message: string;
32
+ cwd: string;
33
+ provider?: string;
34
+ model?: string;
35
+ thinkingLevel?: ThinkingLevel;
36
+ modelRationale?: string;
37
+ spec?: TeammateSpec;
38
+ };
39
+
40
+ export async function runTransientSession(opts: TransientSessionOpts): Promise<TeammateRunResult> {
41
+ const startedAt = Date.now();
42
+ const metrics: LiveTeammateMetrics = {
43
+ turns: 0,
44
+ toolUses: 0,
45
+ tokens: 0,
46
+ startedAt,
47
+ };
48
+ let finalMessage = "";
49
+ const deltaBuffer: string[] = [];
50
+
51
+ try {
52
+ const agentDir = getAgentDir();
53
+ const authStorage = AuthStorage.create();
54
+ const modelRegistry = ModelRegistry.create(authStorage);
55
+ const selectedModel = opts.provider && opts.model
56
+ ? modelRegistry.find(opts.provider, opts.model)
57
+ : undefined;
58
+ if (opts.provider && opts.model && !selectedModel) {
59
+ throw new Error(`model not found in registry: ${opts.provider}/${opts.model}`);
60
+ }
61
+
62
+ const loader = new DefaultResourceLoader({
63
+ cwd: opts.cwd,
64
+ agentDir,
65
+ noContextFiles: true,
66
+ appendSystemPromptOverride: (base: string[]) => {
67
+ const specBody = opts.spec?.systemPrompt?.trim();
68
+ return specBody ? [...base, specBody] : base;
69
+ },
70
+ } as ConstructorParameters<typeof DefaultResourceLoader>[0] & { noContextFiles?: boolean });
71
+ await loader.reload();
72
+
73
+ const { session } = await createAgentSession({
74
+ cwd: opts.cwd,
75
+ authStorage,
76
+ modelRegistry,
77
+ model: selectedModel,
78
+ thinkingLevel: opts.thinkingLevel,
79
+ resourceLoader: loader,
80
+ sessionManager: SessionManager.inMemory(opts.cwd),
81
+ tools: opts.spec?.tools ? createAllowedTools(opts.cwd, opts.spec.tools) : undefined,
82
+ sessionStartEvent: { type: "session_start", reason: "startup" },
83
+ });
84
+
85
+ const unsubscribe = session.subscribe((event) => {
86
+ applySessionEvent(metrics, event);
87
+ if (event.type === "message_update" && event.assistantMessageEvent.type === "text_delta") {
88
+ deltaBuffer.push(event.assistantMessageEvent.delta);
89
+ }
90
+ if (event.type === "message_end" && event.message.role === "assistant") {
91
+ finalMessage = extractText(event.message.content);
92
+ }
93
+ });
94
+
95
+ try {
96
+ await session.prompt(opts.message);
97
+ } finally {
98
+ unsubscribe();
99
+ session.dispose();
100
+ }
101
+
102
+ if (!finalMessage && deltaBuffer.length > 0) finalMessage = deltaBuffer.join("");
103
+ metrics.finishedAt = Date.now();
104
+ metrics.exitReason = "completed";
105
+ return {
106
+ teammateId: opts.id,
107
+ name: opts.name,
108
+ description: opts.description,
109
+ status: "completed",
110
+ result: finalMessage,
111
+ exitCode: 0,
112
+ metrics,
113
+ provider: opts.provider,
114
+ model: opts.model,
115
+ thinkingLevel: opts.thinkingLevel,
116
+ modelRationale: opts.modelRationale,
117
+ durationMs: Date.now() - startedAt,
118
+ runtime: "transient",
119
+ };
120
+ } catch (err) {
121
+ metrics.finishedAt = Date.now();
122
+ metrics.exitReason = "failed";
123
+ return {
124
+ teammateId: opts.id,
125
+ name: opts.name,
126
+ description: opts.description,
127
+ status: "failed",
128
+ result: `[transient error] ${err instanceof Error ? err.message : String(err)}`,
129
+ exitCode: null,
130
+ metrics,
131
+ provider: opts.provider,
132
+ model: opts.model,
133
+ thinkingLevel: opts.thinkingLevel,
134
+ modelRationale: opts.modelRationale,
135
+ durationMs: Date.now() - startedAt,
136
+ runtime: "transient",
137
+ };
138
+ }
139
+ }
140
+
141
+ function createAllowedTools(cwd: string, names: string[]): any[] {
142
+ const all: Record<string, any> = {
143
+ read: createReadTool(cwd),
144
+ bash: createBashTool(cwd),
145
+ edit: createEditTool(cwd),
146
+ write: createWriteTool(cwd),
147
+ grep: createGrepTool(cwd),
148
+ find: createFindTool(cwd),
149
+ ls: createLsTool(cwd),
150
+ };
151
+ return names.map((name) => all[name]).filter(Boolean);
152
+ }
153
+
154
+ function applySessionEvent(metrics: LiveTeammateMetrics, event: AgentSessionEvent): void {
155
+ switch (event.type) {
156
+ case "message_update":
157
+ if (!metrics.activityHint) metrics.activityHint = "thinking…";
158
+ break;
159
+ case "message_end":
160
+ if (event.message.role === "assistant") {
161
+ metrics.turns += 1;
162
+ metrics.currentTool = undefined;
163
+ metrics.currentToolStartedAt = undefined;
164
+ metrics.activityHint = "responding…";
165
+ }
166
+ break;
167
+ case "tool_execution_start":
168
+ metrics.toolUses += 1;
169
+ metrics.currentTool = event.toolName;
170
+ metrics.currentToolStartedAt = Date.now();
171
+ metrics.activityHint = `${event.toolName}…`;
172
+ break;
173
+ case "tool_execution_end":
174
+ metrics.activityHint = event.isError ? "tool error…" : "processing result…";
175
+ if (metrics.currentTool === event.toolName) {
176
+ metrics.currentTool = undefined;
177
+ metrics.currentToolStartedAt = undefined;
178
+ }
179
+ break;
180
+ case "turn_end":
181
+ metrics.activityHint = "waiting…";
182
+ break;
183
+ }
184
+ }
185
+
186
+ function extractText(content: unknown): string {
187
+ if (!Array.isArray(content)) return "";
188
+ return content
189
+ .map((part) => {
190
+ if (part && typeof part === "object" && "type" in part && part.type === "text" && "text" in part) {
191
+ return typeof part.text === "string" ? part.text : "";
192
+ }
193
+ return "";
194
+ })
195
+ .join("");
196
+ }
@@ -0,0 +1,90 @@
1
+ /**
2
+ * Pi Team-Mode — Worktree Isolation
3
+ *
4
+ * Wraps `git worktree add/remove` with a no-diff auto-cleanup heuristic
5
+ * matching Claude Code's semantics:
6
+ *
7
+ * - If the teammate's worktree is clean on cleanup, the worktree and its
8
+ * branch are removed silently.
9
+ * - If the worktree has uncommitted changes, the path and branch are
10
+ * returned to the caller (so they can inspect or merge the work).
11
+ */
12
+
13
+ import { spawn } from "node:child_process";
14
+ import { mkdir } from "node:fs/promises";
15
+ import * as path from "node:path";
16
+ import * as os from "node:os";
17
+ import { randomUUID } from "node:crypto";
18
+
19
+ export type WorktreeHandle = {
20
+ path: string;
21
+ branch: string;
22
+ repoRoot: string;
23
+ /** Commit SHA the worktree was branched off of — used to detect new commits. */
24
+ baseSha: string;
25
+ };
26
+
27
+ export type WorktreeCleanupResult =
28
+ | { removed: true }
29
+ | { removed: false; path: string; branch: string };
30
+
31
+ /**
32
+ * Create a git worktree off HEAD in a temp dir (or `base` if supplied).
33
+ * Throws if the caller's cwd isn't inside a git repo.
34
+ */
35
+ export async function createWorktree(cwd: string, base?: string): Promise<WorktreeHandle> {
36
+ const repoRoot = await getRepoRoot(cwd);
37
+ const worktreeBase = base ?? path.join(os.tmpdir(), "pi-team-mode-worktrees");
38
+ await mkdir(worktreeBase, { recursive: true });
39
+ const suffix = randomUUID().slice(0, 8);
40
+ const worktreePath = path.join(worktreeBase, `teammate-${suffix}`);
41
+ const branch = `team-mode/teammate-${suffix}`;
42
+ const head = await git(repoRoot, ["rev-parse", "HEAD"]);
43
+ const baseSha = head.stdout.trim();
44
+ await git(repoRoot, ["worktree", "add", "-b", branch, worktreePath, "HEAD"]);
45
+ return { path: worktreePath, branch, repoRoot, baseSha };
46
+ }
47
+
48
+ /**
49
+ * Clean up a worktree. If the worktree has no uncommitted changes and no
50
+ * commits beyond the branch point, remove both the worktree and the branch
51
+ * and return `{ removed: true }`. Otherwise leave them in place and return
52
+ * the path and branch so the caller can surface them in the tool result.
53
+ */
54
+ export async function cleanupWorktree(handle: WorktreeHandle): Promise<WorktreeCleanupResult> {
55
+ const hasChanges = await worktreeHasChanges(handle);
56
+ if (hasChanges) {
57
+ return { removed: false, path: handle.path, branch: handle.branch };
58
+ }
59
+ await git(handle.repoRoot, ["worktree", "remove", "--force", handle.path]).catch(() => {});
60
+ await git(handle.repoRoot, ["branch", "-D", handle.branch]).catch(() => {});
61
+ return { removed: true };
62
+ }
63
+
64
+ /** Returns true if the worktree has uncommitted changes OR commits beyond baseSha. */
65
+ async function worktreeHasChanges(handle: WorktreeHandle): Promise<boolean> {
66
+ const dirty = await git(handle.path, ["status", "--porcelain"]);
67
+ if (dirty.stdout.trim().length > 0) return true;
68
+ const head = await git(handle.path, ["rev-parse", "HEAD"]);
69
+ return head.stdout.trim() !== handle.baseSha;
70
+ }
71
+
72
+ async function getRepoRoot(cwd: string): Promise<string> {
73
+ const { stdout, code } = await git(cwd, ["rev-parse", "--show-toplevel"]);
74
+ if (code !== 0) throw new Error(`not a git repo: ${cwd}`);
75
+ return stdout.trim();
76
+ }
77
+
78
+ type GitResult = { stdout: string; stderr: string; code: number };
79
+
80
+ function git(cwd: string, args: string[]): Promise<GitResult> {
81
+ return new Promise((resolve) => {
82
+ const proc = spawn("git", args, { cwd, stdio: ["ignore", "pipe", "pipe"] });
83
+ let stdout = "";
84
+ let stderr = "";
85
+ proc.stdout.on("data", (c: Buffer) => (stdout += c.toString("utf8")));
86
+ proc.stderr.on("data", (c: Buffer) => (stderr += c.toString("utf8")));
87
+ proc.on("close", (code) => resolve({ stdout, stderr, code: code ?? 1 }));
88
+ proc.on("error", (err) => resolve({ stdout, stderr: err.message, code: 1 }));
89
+ });
90
+ }