@tmustier/pi-agent-teams 0.2.0 → 0.3.1

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.
@@ -0,0 +1,329 @@
1
+ import type { ExtensionCommandContext, ExtensionContext } from "@mariozechner/pi-coding-agent";
2
+ import { getTeamDir } from "./paths.js";
3
+ import {
4
+ handleTeamEnvCommand,
5
+ handleTeamIdCommand,
6
+ handleTeamListCommand,
7
+ } from "./leader-info-commands.js";
8
+ import {
9
+ handleTeamCleanupCommand,
10
+ handleTeamDelegateCommand,
11
+ handleTeamKillCommand,
12
+ handleTeamPruneCommand,
13
+ handleTeamShutdownCommand,
14
+ handleTeamStopCommand,
15
+ handleTeamStyleCommand,
16
+ } from "./leader-lifecycle-commands.js";
17
+ import {
18
+ handleTeamBroadcastCommand,
19
+ handleTeamDmCommand,
20
+ handleTeamSendCommand,
21
+ handleTeamSteerCommand,
22
+ } from "./leader-messaging-commands.js";
23
+ import { handleTeamPlanCommand } from "./leader-plan-commands.js";
24
+ import { handleTeamSpawnCommand } from "./leader-spawn-command.js";
25
+ import { handleTeamTaskCommand } from "./leader-task-commands.js";
26
+ import type { SpawnTeammateFn } from "./spawn-types.js";
27
+ import type { TeamConfig } from "./team-config.js";
28
+ import type { TeamTask } from "./task-store.js";
29
+ import type { TeammateRpc } from "./teammate-rpc.js";
30
+ import type { TeamsStyle } from "./teams-style.js";
31
+
32
+ const TEAM_HELP_TEXT = [
33
+ "Usage:",
34
+ " /team id",
35
+ " /team env <name>",
36
+ " /team spawn <name> [fresh|branch] [shared|worktree] [plan]",
37
+ " /team panel",
38
+ " /team send <name> <msg...>",
39
+ " /team dm <name> <msg...>",
40
+ " /team broadcast <msg...>",
41
+ " /team steer <name> <msg...>",
42
+ " /team stop <name> [reason...]",
43
+ " /team kill <name>",
44
+ " /team shutdown",
45
+ " /team shutdown <name> [reason...]",
46
+ " /team delegate [on|off]",
47
+ " /team plan approve <name>",
48
+ " /team plan reject <name> [feedback...]",
49
+ " /team cleanup [--force]",
50
+ " /team prune [--all] # hide stale manual teammates (mark offline)",
51
+ " /team task add <text...>",
52
+ " /team task assign <id> <agent>",
53
+ " /team task unassign <id>",
54
+ " /team task list",
55
+ " /team task clear [completed|all] [--force]",
56
+ " /team task show <id>",
57
+ " /team task dep add <id> <depId>",
58
+ " /team task dep rm <id> <depId>",
59
+ " /team task dep ls <id>",
60
+ " /team task use <taskListId>",
61
+ ].join("\n");
62
+
63
+ export function getTeamHelpText(): string {
64
+ return TEAM_HELP_TEXT;
65
+ }
66
+
67
+ export async function handleTeamCommand(opts: {
68
+ args: string;
69
+ ctx: ExtensionCommandContext;
70
+ teammates: Map<string, TeammateRpc>;
71
+ getTeamConfig: () => TeamConfig | null;
72
+ getTasks: () => TeamTask[];
73
+ refreshTasks: () => Promise<void>;
74
+ renderWidget: () => void;
75
+ getTaskListId: () => string | null;
76
+ setTaskListId: (id: string) => void;
77
+ pendingPlanApprovals: Map<string, { requestId: string; name: string; taskId?: string }>;
78
+ getDelegateMode: () => boolean;
79
+ setDelegateMode: (next: boolean) => void;
80
+ getStyle: () => TeamsStyle;
81
+ setStyle: (next: TeamsStyle) => void;
82
+ spawnTeammate: SpawnTeammateFn;
83
+ openWidget: (ctx: ExtensionCommandContext) => Promise<void>;
84
+ getTeamsExtensionEntryPath: () => string | null;
85
+ shellQuote: (v: string) => string;
86
+ getCurrentCtx: () => ExtensionContext | null;
87
+ stopAllTeammates: (ctx: ExtensionContext, reason: string) => Promise<void>;
88
+ }): Promise<void> {
89
+ const {
90
+ args,
91
+ ctx,
92
+ teammates,
93
+ getTeamConfig,
94
+ getTasks,
95
+ refreshTasks,
96
+ renderWidget,
97
+ getTaskListId,
98
+ setTaskListId,
99
+ pendingPlanApprovals,
100
+ getDelegateMode,
101
+ setDelegateMode,
102
+ getStyle,
103
+ setStyle,
104
+ spawnTeammate,
105
+ openWidget,
106
+ getTeamsExtensionEntryPath,
107
+ shellQuote,
108
+ getCurrentCtx,
109
+ stopAllTeammates,
110
+ } = opts;
111
+
112
+ const style = getStyle();
113
+ const leadName = getTeamConfig()?.leadName ?? "team-lead";
114
+ const taskListId = getTaskListId();
115
+
116
+ const [sub, ...rest] = args.trim().split(" ");
117
+ if (!sub || sub === "help") {
118
+ ctx.ui.notify(getTeamHelpText(), "info");
119
+ return;
120
+ }
121
+
122
+ type TeamSubcommandHandler = () => Promise<void>;
123
+ const handlers: Record<string, TeamSubcommandHandler> = {
124
+ list: async () => {
125
+ await handleTeamListCommand({
126
+ ctx,
127
+ teammates,
128
+ getTeamConfig,
129
+ style,
130
+ refreshTasks,
131
+ renderWidget,
132
+ });
133
+ },
134
+
135
+ id: async () => {
136
+ await handleTeamIdCommand({
137
+ ctx,
138
+ taskListId,
139
+ leadName,
140
+ style,
141
+ });
142
+ },
143
+
144
+ env: async () => {
145
+ await handleTeamEnvCommand({
146
+ ctx,
147
+ rest,
148
+ taskListId,
149
+ leadName,
150
+ style,
151
+ getTeamsExtensionEntryPath,
152
+ shellQuote,
153
+ });
154
+ },
155
+
156
+ cleanup: async () => {
157
+ await handleTeamCleanupCommand({
158
+ ctx,
159
+ rest,
160
+ teammates,
161
+ refreshTasks,
162
+ getTasks,
163
+ renderWidget,
164
+ style,
165
+ });
166
+ },
167
+
168
+ prune: async () => {
169
+ await handleTeamPruneCommand({
170
+ ctx,
171
+ rest,
172
+ teammates,
173
+ getTeamConfig,
174
+ refreshTasks,
175
+ getTasks,
176
+ style,
177
+ renderWidget,
178
+ });
179
+ },
180
+
181
+ delegate: async () => {
182
+ await handleTeamDelegateCommand({
183
+ ctx,
184
+ rest,
185
+ getDelegateMode,
186
+ setDelegateMode,
187
+ renderWidget,
188
+ });
189
+ },
190
+
191
+ shutdown: async () => {
192
+ await handleTeamShutdownCommand({
193
+ ctx,
194
+ rest,
195
+ teammates,
196
+ getTeamConfig,
197
+ leadName,
198
+ style,
199
+ getCurrentCtx,
200
+ stopAllTeammates,
201
+ refreshTasks,
202
+ getTasks,
203
+ renderWidget,
204
+ });
205
+ },
206
+
207
+ spawn: async () => {
208
+ await handleTeamSpawnCommand({ ctx, rest, teammates, style, spawnTeammate });
209
+ },
210
+
211
+ style: async () => {
212
+ const teamId = ctx.sessionManager.getSessionId();
213
+ const teamDir = getTeamDir(teamId);
214
+ await handleTeamStyleCommand({
215
+ ctx,
216
+ rest,
217
+ teamDir,
218
+ getStyle,
219
+ setStyle,
220
+ refreshTasks,
221
+ renderWidget,
222
+ });
223
+ },
224
+
225
+ panel: async () => {
226
+ await openWidget(ctx);
227
+ },
228
+
229
+ send: async () => {
230
+ await handleTeamSendCommand({
231
+ ctx,
232
+ rest,
233
+ teammates,
234
+ style,
235
+ renderWidget,
236
+ });
237
+ },
238
+
239
+ steer: async () => {
240
+ await handleTeamSteerCommand({
241
+ ctx,
242
+ rest,
243
+ teammates,
244
+ style,
245
+ renderWidget,
246
+ });
247
+ },
248
+
249
+ stop: async () => {
250
+ await handleTeamStopCommand({
251
+ ctx,
252
+ rest,
253
+ teammates,
254
+ leadName,
255
+ style,
256
+ refreshTasks,
257
+ getTasks,
258
+ renderWidget,
259
+ });
260
+ },
261
+
262
+ kill: async () => {
263
+ await handleTeamKillCommand({
264
+ ctx,
265
+ rest,
266
+ teammates,
267
+ leadName,
268
+ style,
269
+ taskListId,
270
+ refreshTasks,
271
+ renderWidget,
272
+ });
273
+ },
274
+
275
+ dm: async () => {
276
+ await handleTeamDmCommand({
277
+ ctx,
278
+ rest,
279
+ leadName,
280
+ style,
281
+ });
282
+ },
283
+
284
+ broadcast: async () => {
285
+ await handleTeamBroadcastCommand({
286
+ ctx,
287
+ rest,
288
+ teammates,
289
+ leadName,
290
+ style,
291
+ refreshTasks,
292
+ getTasks,
293
+ getTaskListId,
294
+ });
295
+ },
296
+
297
+ task: async () => {
298
+ await handleTeamTaskCommand({
299
+ ctx,
300
+ rest,
301
+ leadName,
302
+ style,
303
+ getTaskListId,
304
+ setTaskListId,
305
+ getTasks,
306
+ refreshTasks,
307
+ renderWidget,
308
+ });
309
+ },
310
+
311
+ plan: async () => {
312
+ await handleTeamPlanCommand({
313
+ ctx,
314
+ rest,
315
+ leadName,
316
+ style,
317
+ pendingPlanApprovals,
318
+ });
319
+ },
320
+ };
321
+
322
+ const normalizedSub = sub === "widget" ? "panel" : sub;
323
+ const handler = handlers[normalizedSub];
324
+ if (!handler) {
325
+ ctx.ui.notify(`Unknown subcommand: ${sub}`, "error");
326
+ return;
327
+ }
328
+ await handler();
329
+ }
@@ -1,29 +1,19 @@
1
1
  import type { AgentToolResult } from "@mariozechner/pi-agent-core";
2
2
  import { StringEnum } from "@mariozechner/pi-ai";
3
3
  import { Type, type Static } from "@sinclair/typebox";
4
- import type { ExtensionAPI, ExtensionContext } from "@mariozechner/pi-coding-agent";
4
+ import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
5
5
  import { writeToMailbox } from "./mailbox.js";
6
6
  import { pickAgentNames, pickComradeNames, sanitizeName } from "./names.js";
7
7
  import { getTeamDir } from "./paths.js";
8
+ import { taskAssignmentPayload } from "./protocol.js";
8
9
  import { ensureTeamConfig } from "./team-config.js";
9
10
  import { getTeamsStyleFromEnv, type TeamsStyle, formatMemberDisplayName } from "./teams-style.js";
10
- import { createTask, type TeamTask } from "./task-store.js";
11
+ import { createTask } from "./task-store.js";
11
12
  import type { TeammateRpc } from "./teammate-rpc.js";
12
-
13
- export type ContextMode = "fresh" | "branch";
14
- export type WorkspaceMode = "shared" | "worktree";
13
+ import type { ContextMode, WorkspaceMode, SpawnTeammateFn } from "./spawn-types.js";
15
14
 
16
15
  type TeamsToolDelegateTask = { text: string; assignee?: string };
17
16
 
18
- type SpawnTeammateResult =
19
- | { ok: true; name: string; warnings: string[] }
20
- | { ok: false; error: string };
21
-
22
- export type SpawnTeammateFn = (
23
- ctx: ExtensionContext,
24
- opts: { name: string; mode?: ContextMode; workspaceMode?: WorkspaceMode; planRequired?: boolean },
25
- ) => Promise<SpawnTeammateResult>;
26
-
27
17
  const TeamsActionSchema = StringEnum(["delegate"] as const, {
28
18
  description: "Teams tool action. Currently only 'delegate' is supported.",
29
19
  default: "delegate",
@@ -71,11 +61,10 @@ export function registerTeamsTool(opts: {
71
61
  teammates: Map<string, TeammateRpc>;
72
62
  spawnTeammate: SpawnTeammateFn;
73
63
  getTaskListId: () => string | null;
74
- taskAssignmentPayload: (task: TeamTask, assignedBy: string) => unknown;
75
64
  refreshTasks: () => Promise<void>;
76
65
  renderWidget: () => void;
77
66
  }): void {
78
- const { pi, teammates, spawnTeammate, getTaskListId, taskAssignmentPayload, refreshTasks, renderWidget } = opts;
67
+ const { pi, teammates, spawnTeammate, getTaskListId, refreshTasks, renderWidget } = opts;
79
68
 
80
69
  pi.registerTool({
81
70
  name: "teams",