@tmustier/pi-agent-teams 0.2.0 → 0.3.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.
- package/README.md +14 -3
- package/docs/claude-parity.md +7 -5
- package/docs/smoke-test-plan.md +13 -4
- package/eslint.config.js +74 -0
- package/extensions/teams/activity-tracker.ts +2 -2
- package/extensions/teams/leader-lifecycle-commands.ts +26 -13
- package/extensions/teams/leader-messaging-commands.ts +0 -1
- package/extensions/teams/leader-plan-commands.ts +1 -1
- package/extensions/teams/leader-spawn-command.ts +3 -19
- package/extensions/teams/leader-task-commands.ts +11 -7
- package/extensions/teams/leader-team-command.ts +312 -0
- package/extensions/teams/leader-teams-tool.ts +5 -16
- package/extensions/teams/leader.ts +34 -310
- package/extensions/teams/protocol.ts +20 -0
- package/extensions/teams/spawn-types.ts +21 -0
- package/extensions/teams/task-store.ts +4 -0
- package/extensions/teams/teammate-rpc.ts +26 -2
- package/extensions/teams/teams-panel.ts +41 -95
- package/extensions/teams/teams-ui-shared.ts +89 -0
- package/extensions/teams/teams-widget.ts +15 -68
- package/extensions/teams/worker.ts +1 -1
- package/package.json +9 -4
- package/scripts/integration-claim-test.mts +16 -86
- package/scripts/integration-todo-test.mts +14 -65
- package/scripts/lib/pi-workers.ts +105 -0
- package/skills/agent-teams/SKILL.md +7 -4
- package/.github/workflows/ci.yml +0 -32
- package/scripts/smoke-test.mjs +0 -199
package/README.md
CHANGED
|
@@ -79,6 +79,7 @@ Or drive it manually:
|
|
|
79
79
|
/tw # open the interactive widget panel
|
|
80
80
|
|
|
81
81
|
/team shutdown alice # graceful shutdown (handshake)
|
|
82
|
+
/team shutdown # stop all teammates (leader session remains active)
|
|
82
83
|
/team cleanup # remove team artifacts when done
|
|
83
84
|
```
|
|
84
85
|
|
|
@@ -105,6 +106,7 @@ Or let the model drive it with the delegate tool:
|
|
|
105
106
|
| --- | --- |
|
|
106
107
|
| `/swarm [task]` | Tell the agent to spawn a team and work on a task |
|
|
107
108
|
| `/tw` | Open the interactive widget panel |
|
|
109
|
+
| `/team-widget` | Open the interactive widget panel (alias for `/tw`) |
|
|
108
110
|
|
|
109
111
|
### Team management
|
|
110
112
|
|
|
@@ -122,7 +124,7 @@ All management commands live under `/team`.
|
|
|
122
124
|
| `/team broadcast <msg>` | Message all teammates |
|
|
123
125
|
| `/team stop <name> [reason]` | Abort current work (resets task to pending) |
|
|
124
126
|
| `/team shutdown <name> [reason]` | Graceful shutdown (handshake) |
|
|
125
|
-
| `/team shutdown` |
|
|
127
|
+
| `/team shutdown` | Stop all teammates (leader session remains active) |
|
|
126
128
|
| `/team kill <name>` | Force-terminate |
|
|
127
129
|
| `/team cleanup [--force]` | Delete team artifacts |
|
|
128
130
|
| `/team id` | Print team/task-list IDs and paths |
|
|
@@ -166,13 +168,22 @@ All management commands live under `/team`.
|
|
|
166
168
|
|
|
167
169
|
## Development
|
|
168
170
|
|
|
171
|
+
### Quality gate
|
|
172
|
+
|
|
173
|
+
```bash
|
|
174
|
+
npm run check
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
Runs strict TypeScript typechecking (`npm run typecheck`) and ESLint (`npm run lint`).
|
|
178
|
+
|
|
169
179
|
### Smoke test (no API keys)
|
|
170
180
|
|
|
171
181
|
```bash
|
|
172
|
-
|
|
182
|
+
npm run smoke-test
|
|
183
|
+
# or: npx tsx scripts/smoke-test.mts
|
|
173
184
|
```
|
|
174
185
|
|
|
175
|
-
Filesystem-level test of the task store, mailbox, and
|
|
186
|
+
Filesystem-level smoke test of the task store, mailbox, team config, and protocol parsers.
|
|
176
187
|
|
|
177
188
|
### E2E RPC test (spawns pi + one teammate)
|
|
178
189
|
|
package/docs/claude-parity.md
CHANGED
|
@@ -40,11 +40,11 @@ Legend: ✅ implemented • 🟡 partial • ❌ missing
|
|
|
40
40
|
| Display modes | In-process selection (Shift+Up/Down); split panes (tmux/iTerm) | ❌ | Pi has a widget + commands, but no terminal-level comrade navigation/panes. | P2 |
|
|
41
41
|
| Delegate mode | Lead restricted to coordination-only tools | ✅ | `/team delegate [on|off]` toggles; `pi.on("tool_call")` blocks `bash/edit/write`; `PI_TEAMS_DELEGATE_MODE=1` env. Widget shows `[delegate]`. | P1 |
|
|
42
42
|
| Plan approval | Comrade can be "plan required" and needs lead approval to implement | ✅ | `/team spawn <name> plan` sets `PI_TEAMS_PLAN_REQUIRED=1`; worker restricted to read-only tools; submits plan via `plan_approval_request`; `/team plan approve|reject <name>`. | P1 |
|
|
43
|
-
| Shutdown handshake | Lead requests shutdown; comrade can approve/reject | ✅ | Full protocol: `shutdown_request` → `shutdown_approved` or `shutdown_rejected` (when worker is busy). `/team shutdown <name>` (graceful) + `/team kill
|
|
43
|
+
| Shutdown handshake | Lead requests shutdown; comrade can approve/reject | ✅ | Full protocol: `shutdown_request` → `shutdown_approved` or `shutdown_rejected` (when worker is busy). `/team shutdown <name>` (graceful) + `/team kill <name>` (force). | P1 |
|
|
44
44
|
| Cleanup team | “Clean up the team” removes shared resources after comrades stopped | ✅ | `/team cleanup [--force]` deletes only `<teamsRoot>/<teamId>` after safety checks. | P1 |
|
|
45
45
|
| Hooks / quality gates | `ComradeIdle`, `TaskCompleted` hooks | ❌ | Add optional hook runner in leader on idle/task-complete events (script execution + exit-code gating). | P2 |
|
|
46
46
|
| Task list UX | Ctrl+T toggle; show all/clear tasks by asking | 🟡 | Widget + `/team task list` show blocked/deps; `/team task show <id>`; `/team task clear [completed|all]`. No Ctrl+T toggle yet. | P0 |
|
|
47
|
-
| Shared task list across sessions | `CLAUDE_CODE_TASK_LIST_ID=...` | ✅ | `PI_TEAMS_TASK_LIST_ID` env
|
|
47
|
+
| Shared task list across sessions | `CLAUDE_CODE_TASK_LIST_ID=...` | ✅ | `PI_TEAMS_TASK_LIST_ID` env is **worker-side** (for manually started workers). The leader switches task lists via `/team task use <taskListId>` (persisted in `config.json`). Newly spawned workers inherit the new task list ID; existing workers need a restart to pick up changes. | P1 |
|
|
48
48
|
|
|
49
49
|
## Prioritized roadmap
|
|
50
50
|
|
|
@@ -66,7 +66,7 @@ Legend: ✅ implemented • 🟡 partial • ❌ missing
|
|
|
66
66
|
4) **Shutdown handshake** ✅
|
|
67
67
|
- Full protocol: `shutdown_request` → `shutdown_approved` / `shutdown_rejected`
|
|
68
68
|
- Worker rejects when busy (streaming + active task), auto-approves when idle
|
|
69
|
-
- Leader command: `/team shutdown <name> [reason...]` (graceful), `/team kill
|
|
69
|
+
- Leader command: `/team shutdown <name> [reason...]` (graceful), `/team kill <name>` as force
|
|
70
70
|
|
|
71
71
|
5) **Plan approval** ✅
|
|
72
72
|
- `/team spawn <name> [fresh|branch] [shared|worktree] plan` sets `PI_TEAMS_PLAN_REQUIRED=1`
|
|
@@ -109,7 +109,9 @@ Legend: ✅ implemented • 🟡 partial • ❌ missing
|
|
|
109
109
|
|
|
110
110
|
## Where changes would land (code map)
|
|
111
111
|
|
|
112
|
-
- Leader orchestration
|
|
112
|
+
- Leader orchestration: `extensions/teams/leader.ts`
|
|
113
|
+
- Leader `/team` command dispatch: `extensions/teams/leader-team-command.ts`
|
|
114
|
+
- Leader LLM tool (`teams`): `extensions/teams/leader-teams-tool.ts`
|
|
113
115
|
- Worker mailbox polling + self-claim + protocols: `extensions/teams/worker.ts`
|
|
114
116
|
- Task store + locking: `extensions/teams/task-store.ts`, `extensions/teams/fs-lock.ts`
|
|
115
117
|
- Mailbox store + locking: `extensions/teams/mailbox.ts`
|
|
@@ -120,5 +122,5 @@ Legend: ✅ implemented • 🟡 partial • ❌ missing
|
|
|
120
122
|
|
|
121
123
|
- Keep tests hermetic by setting `PI_TEAMS_ROOT_DIR` to a temp directory.
|
|
122
124
|
- Extend:
|
|
123
|
-
- `scripts/smoke-test.
|
|
125
|
+
- `scripts/smoke-test.mts` (run via `npm run smoke-test`) for filesystem-only behaviors (deps, claiming, locking)
|
|
124
126
|
- `scripts/e2e-rpc-test.mjs` for protocol flows (shutdown handshake, plan approval)
|
package/docs/smoke-test-plan.md
CHANGED
|
@@ -12,9 +12,10 @@ Exercises all core primitives directly via `tsx`:
|
|
|
12
12
|
|
|
13
13
|
```bash
|
|
14
14
|
npx tsx scripts/smoke-test.mts
|
|
15
|
+
# or: npm run smoke-test
|
|
15
16
|
```
|
|
16
17
|
|
|
17
|
-
**What it tests** (
|
|
18
|
+
**What it tests** (overview):
|
|
18
19
|
|
|
19
20
|
| Module | Coverage |
|
|
20
21
|
|------------------|-----------------------------------------------------------------|
|
|
@@ -24,10 +25,10 @@ npx tsx scripts/smoke-test.mts
|
|
|
24
25
|
| `task-store.ts` | CRUD, `startAssignedTask`, `completeTask`, `claimNextAvailable`,|
|
|
25
26
|
| | `unassignTasksForAgent`, dependencies, `clearTasks` |
|
|
26
27
|
| `team-config.ts` | `ensureTeamConfig` (idempotent), `upsertMember`, `setMemberStatus`, `loadTeamConfig` |
|
|
27
|
-
| `protocol.ts` |
|
|
28
|
-
| Pi CLI | `pi --version` executes
|
|
28
|
+
| `protocol.ts` | Structured message parsers (valid + invalid JSON + wrong type) |
|
|
29
|
+
| Pi CLI | `pi --version` executes (skipped in CI if `pi` not on PATH) |
|
|
29
30
|
|
|
30
|
-
**Expected result:** `PASSED:
|
|
31
|
+
**Expected result:** `PASSED: <n> FAILED: 0`
|
|
31
32
|
|
|
32
33
|
## 2. Extension Loading Test
|
|
33
34
|
|
|
@@ -103,6 +104,14 @@ Ask the model:
|
|
|
103
104
|
|
|
104
105
|
**Expected:** agent1 goes offline, widget updates.
|
|
105
106
|
|
|
107
|
+
Optional: stop all teammates without ending the leader session:
|
|
108
|
+
|
|
109
|
+
```
|
|
110
|
+
/team shutdown
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
**Expected:** all teammates stop; leader remains active until you exit it (e.g. ctrl+d).
|
|
114
|
+
|
|
106
115
|
## 4. Worker-side Smoke (verifying child process)
|
|
107
116
|
|
|
108
117
|
To test the worker role directly:
|
package/eslint.config.js
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
import eslint from "@eslint/js";
|
|
3
|
+
import tseslint from "typescript-eslint";
|
|
4
|
+
|
|
5
|
+
export default tseslint.config(
|
|
6
|
+
{
|
|
7
|
+
ignores: [
|
|
8
|
+
"node_modules/**",
|
|
9
|
+
"dist/**",
|
|
10
|
+
"build/**",
|
|
11
|
+
"coverage/**",
|
|
12
|
+
".artifacts/**",
|
|
13
|
+
".research/**",
|
|
14
|
+
".resume-sessions/**",
|
|
15
|
+
],
|
|
16
|
+
},
|
|
17
|
+
|
|
18
|
+
eslint.configs.recommended,
|
|
19
|
+
...tseslint.configs.recommended,
|
|
20
|
+
|
|
21
|
+
{
|
|
22
|
+
files: ["extensions/**/*.ts", "scripts/**/*.ts", "scripts/**/*.mts"],
|
|
23
|
+
rules: {
|
|
24
|
+
// ━━ Project invariants (AGENTS.md) ━━
|
|
25
|
+
"@typescript-eslint/no-explicit-any": "error",
|
|
26
|
+
"@typescript-eslint/no-non-null-assertion": "error",
|
|
27
|
+
"@typescript-eslint/ban-ts-comment": [
|
|
28
|
+
"error",
|
|
29
|
+
{
|
|
30
|
+
"ts-ignore": true,
|
|
31
|
+
"ts-expect-error": true,
|
|
32
|
+
"ts-nocheck": true,
|
|
33
|
+
"ts-check": false,
|
|
34
|
+
},
|
|
35
|
+
],
|
|
36
|
+
|
|
37
|
+
// Imports/types
|
|
38
|
+
"@typescript-eslint/consistent-type-imports": [
|
|
39
|
+
"error",
|
|
40
|
+
{ prefer: "type-imports", disallowTypeAnnotations: false },
|
|
41
|
+
],
|
|
42
|
+
|
|
43
|
+
// General correctness
|
|
44
|
+
eqeqeq: ["error", "always"],
|
|
45
|
+
|
|
46
|
+
// Prefer TS-aware unused-vars
|
|
47
|
+
"no-unused-vars": "off",
|
|
48
|
+
"@typescript-eslint/no-unused-vars": [
|
|
49
|
+
"warn",
|
|
50
|
+
{
|
|
51
|
+
argsIgnorePattern: "^_",
|
|
52
|
+
varsIgnorePattern: "^_",
|
|
53
|
+
caughtErrorsIgnorePattern: "^_",
|
|
54
|
+
},
|
|
55
|
+
],
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
|
|
59
|
+
// Scripts/tests: console is expected
|
|
60
|
+
{
|
|
61
|
+
files: ["scripts/**/*.ts", "scripts/**/*.mts"],
|
|
62
|
+
rules: {
|
|
63
|
+
"no-console": "off",
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
|
|
67
|
+
// Extension source: discourage stray console.log
|
|
68
|
+
{
|
|
69
|
+
files: ["extensions/**/*.ts"],
|
|
70
|
+
rules: {
|
|
71
|
+
"no-console": "warn",
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
);
|
|
@@ -68,7 +68,7 @@ export class TranscriptTracker {
|
|
|
68
68
|
if (ev.type === "tool_execution_end") {
|
|
69
69
|
const starts = this.toolStarts.get(name);
|
|
70
70
|
const startTs = starts?.get(ev.toolCallId);
|
|
71
|
-
const durationMs = startTs
|
|
71
|
+
const durationMs = startTs === undefined ? 0 : now - startTs;
|
|
72
72
|
starts?.delete(ev.toolCallId);
|
|
73
73
|
log.push({ kind: "tool_end", toolName: ev.toolName, durationMs, timestamp: now });
|
|
74
74
|
return;
|
|
@@ -135,7 +135,7 @@ export class TranscriptTracker {
|
|
|
135
135
|
// Flush all complete lines, keep the last (potentially incomplete) part
|
|
136
136
|
for (let i = 0; i < parts.length - 1; i++) {
|
|
137
137
|
const part = parts[i];
|
|
138
|
-
if (part
|
|
138
|
+
if (part === undefined) continue;
|
|
139
139
|
const trimmed = part.trimEnd();
|
|
140
140
|
if (trimmed) log.push({ kind: "text", text: trimmed, timestamp });
|
|
141
141
|
}
|
|
@@ -144,8 +144,11 @@ export async function handleTeamShutdownCommand(opts: {
|
|
|
144
144
|
leadName: string;
|
|
145
145
|
style: TeamsStyle;
|
|
146
146
|
getCurrentCtx: () => ExtensionContext | null;
|
|
147
|
+
stopAllTeammates: (ctx: ExtensionContext, reason: string) => Promise<void>;
|
|
148
|
+
refreshTasks: () => Promise<void>;
|
|
149
|
+
renderWidget: () => void;
|
|
147
150
|
}): Promise<void> {
|
|
148
|
-
const { ctx, rest, teammates, leadName, style, getCurrentCtx } = opts;
|
|
151
|
+
const { ctx, rest, teammates, leadName, style, getCurrentCtx, stopAllTeammates, refreshTasks, renderWidget } = opts;
|
|
149
152
|
const strings = getTeamsStrings(style);
|
|
150
153
|
const nameRaw = rest[0];
|
|
151
154
|
|
|
@@ -216,21 +219,32 @@ export async function handleTeamShutdownCommand(opts: {
|
|
|
216
219
|
return;
|
|
217
220
|
}
|
|
218
221
|
|
|
219
|
-
// /team shutdown (no args) =
|
|
220
|
-
|
|
221
|
-
|
|
222
|
+
// /team shutdown (no args) = stop all teammates but keep the leader session alive
|
|
223
|
+
if (teammates.size === 0) {
|
|
224
|
+
ctx.ui.notify(`No ${strings.memberTitle.toLowerCase()}s to shut down`, "info");
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
|
|
222
228
|
if (process.stdout.isTTY && process.stdin.isTTY) {
|
|
223
229
|
const msg =
|
|
224
230
|
style === "soviet"
|
|
225
|
-
? `
|
|
226
|
-
:
|
|
227
|
-
const ok = await ctx.ui.confirm("Shutdown", msg);
|
|
231
|
+
? `Dismiss all ${strings.memberTitle.toLowerCase()}s from the ${strings.teamNoun}?`
|
|
232
|
+
: `Stop all ${String(teammates.size)} teammate${teammates.size === 1 ? "" : "s"}?`;
|
|
233
|
+
const ok = await ctx.ui.confirm("Shutdown team", msg);
|
|
228
234
|
if (!ok) return;
|
|
229
235
|
}
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
236
|
+
|
|
237
|
+
const reason =
|
|
238
|
+
style === "soviet"
|
|
239
|
+
? `The ${strings.teamNoun} is dissolved by the chairman`
|
|
240
|
+
: "Stopped by /team shutdown";
|
|
241
|
+
await stopAllTeammates(ctx, reason);
|
|
242
|
+
await refreshTasks();
|
|
243
|
+
renderWidget();
|
|
244
|
+
ctx.ui.notify(
|
|
245
|
+
`Team ended: all ${strings.memberTitle.toLowerCase()}s stopped (leader session remains active)`,
|
|
246
|
+
"info",
|
|
247
|
+
);
|
|
234
248
|
}
|
|
235
249
|
|
|
236
250
|
export async function handleTeamStopCommand(opts: {
|
|
@@ -244,7 +258,6 @@ export async function handleTeamStopCommand(opts: {
|
|
|
244
258
|
renderWidget: () => void;
|
|
245
259
|
}): Promise<void> {
|
|
246
260
|
const { ctx, rest, teammates, leadName, style, refreshTasks, getTasks, renderWidget } = opts;
|
|
247
|
-
const strings = getTeamsStrings(style);
|
|
248
261
|
|
|
249
262
|
const nameRaw = rest[0];
|
|
250
263
|
const reason = rest.slice(1).join(" ").trim();
|
|
@@ -300,7 +313,7 @@ export async function handleTeamKillCommand(opts: {
|
|
|
300
313
|
refreshTasks: () => Promise<void>;
|
|
301
314
|
renderWidget: () => void;
|
|
302
315
|
}): Promise<void> {
|
|
303
|
-
const { ctx, rest, teammates, taskListId, leadName, style, refreshTasks, renderWidget } = opts;
|
|
316
|
+
const { ctx, rest, teammates, taskListId, leadName: _leadName, style, refreshTasks, renderWidget } = opts;
|
|
304
317
|
const strings = getTeamsStrings(style);
|
|
305
318
|
|
|
306
319
|
const nameRaw = rest[0];
|
|
@@ -71,7 +71,6 @@ export async function handleTeamDmCommand(opts: {
|
|
|
71
71
|
style: TeamsStyle;
|
|
72
72
|
}): Promise<void> {
|
|
73
73
|
const { ctx, rest, leadName, style } = opts;
|
|
74
|
-
const strings = getTeamsStrings(style);
|
|
75
74
|
|
|
76
75
|
const nameRaw = rest[0];
|
|
77
76
|
const msg = rest.slice(1).join(" ").trim();
|
|
@@ -4,7 +4,7 @@ import { sanitizeName } from "./names.js";
|
|
|
4
4
|
import { getTeamDir } from "./paths.js";
|
|
5
5
|
import { TEAM_MAILBOX_NS } from "./protocol.js";
|
|
6
6
|
import type { TeamsStyle } from "./teams-style.js";
|
|
7
|
-
import { formatMemberDisplayName
|
|
7
|
+
import { formatMemberDisplayName } from "./teams-style.js";
|
|
8
8
|
|
|
9
9
|
export async function handleTeamPlanCommand(opts: {
|
|
10
10
|
ctx: ExtensionCommandContext;
|
|
@@ -1,32 +1,16 @@
|
|
|
1
|
-
import type { ExtensionCommandContext
|
|
1
|
+
import type { ExtensionCommandContext } from "@mariozechner/pi-coding-agent";
|
|
2
2
|
import { pickComradeNames } from "./names.js";
|
|
3
3
|
import type { TeammateRpc } from "./teammate-rpc.js";
|
|
4
4
|
import type { TeamsStyle } from "./teams-style.js";
|
|
5
5
|
import { formatMemberDisplayName, getTeamsStrings, isSovietStyle } from "./teams-style.js";
|
|
6
|
-
|
|
7
|
-
export type ContextMode = "fresh" | "branch";
|
|
8
|
-
export type WorkspaceMode = "shared" | "worktree";
|
|
9
|
-
|
|
10
|
-
export type SpawnTeammateResult =
|
|
11
|
-
| {
|
|
12
|
-
ok: true;
|
|
13
|
-
name: string;
|
|
14
|
-
mode: ContextMode;
|
|
15
|
-
workspaceMode: WorkspaceMode;
|
|
16
|
-
note?: string;
|
|
17
|
-
warnings: string[];
|
|
18
|
-
}
|
|
19
|
-
| { ok: false; error: string };
|
|
6
|
+
import type { ContextMode, WorkspaceMode, SpawnTeammateFn } from "./spawn-types.js";
|
|
20
7
|
|
|
21
8
|
export async function handleTeamSpawnCommand(opts: {
|
|
22
9
|
ctx: ExtensionCommandContext;
|
|
23
10
|
rest: string[];
|
|
24
11
|
teammates: Map<string, TeammateRpc>;
|
|
25
12
|
style: TeamsStyle;
|
|
26
|
-
spawnTeammate:
|
|
27
|
-
ctx: ExtensionContext,
|
|
28
|
-
opts: { name: string; mode?: ContextMode; workspaceMode?: WorkspaceMode; planRequired?: boolean },
|
|
29
|
-
) => Promise<SpawnTeammateResult>;
|
|
13
|
+
spawnTeammate: SpawnTeammateFn;
|
|
30
14
|
}): Promise<void> {
|
|
31
15
|
const { ctx, rest, teammates, style, spawnTeammate } = opts;
|
|
32
16
|
const strings = getTeamsStrings(style);
|
|
@@ -3,7 +3,7 @@ import type { ExtensionCommandContext } from "@mariozechner/pi-coding-agent";
|
|
|
3
3
|
import { writeToMailbox } from "./mailbox.js";
|
|
4
4
|
import { sanitizeName } from "./names.js";
|
|
5
5
|
import { getTeamDir } from "./paths.js";
|
|
6
|
-
import {
|
|
6
|
+
import { taskAssignmentPayload } from "./protocol.js";
|
|
7
7
|
import {
|
|
8
8
|
addTaskDependency,
|
|
9
9
|
clearTasks,
|
|
@@ -17,7 +17,16 @@ import {
|
|
|
17
17
|
} from "./task-store.js";
|
|
18
18
|
import { ensureTeamConfig } from "./team-config.js";
|
|
19
19
|
import type { TeamsStyle } from "./teams-style.js";
|
|
20
|
-
import { formatMemberDisplayName
|
|
20
|
+
import { formatMemberDisplayName } from "./teams-style.js";
|
|
21
|
+
|
|
22
|
+
function parseAssigneePrefix(text: string): { assignee?: string; text: string } {
|
|
23
|
+
const m = text.match(/^([a-zA-Z0-9_-]+):\s*(.+)$/);
|
|
24
|
+
if (!m) return { text };
|
|
25
|
+
const assignee = m[1];
|
|
26
|
+
const rest = m[2];
|
|
27
|
+
if (!assignee || !rest) return { text };
|
|
28
|
+
return { assignee, text: rest };
|
|
29
|
+
}
|
|
21
30
|
|
|
22
31
|
export async function handleTeamTaskCommand(opts: {
|
|
23
32
|
ctx: ExtensionCommandContext;
|
|
@@ -29,8 +38,6 @@ export async function handleTeamTaskCommand(opts: {
|
|
|
29
38
|
getTasks: () => TeamTask[];
|
|
30
39
|
refreshTasks: () => Promise<void>;
|
|
31
40
|
renderWidget: () => void;
|
|
32
|
-
parseAssigneePrefix: (text: string) => { assignee?: string; text: string };
|
|
33
|
-
taskAssignmentPayload: (task: TeamTask, assignedBy: string) => unknown;
|
|
34
41
|
}): Promise<void> {
|
|
35
42
|
const {
|
|
36
43
|
ctx,
|
|
@@ -42,10 +49,7 @@ export async function handleTeamTaskCommand(opts: {
|
|
|
42
49
|
getTasks,
|
|
43
50
|
refreshTasks,
|
|
44
51
|
renderWidget,
|
|
45
|
-
parseAssigneePrefix,
|
|
46
|
-
taskAssignmentPayload,
|
|
47
52
|
} = opts;
|
|
48
|
-
const strings = getTeamsStrings(style);
|
|
49
53
|
|
|
50
54
|
const [taskSub, ...taskRest] = rest;
|
|
51
55
|
const teamId = ctx.sessionManager.getSessionId();
|