pi-extended-teams 2.1.16
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 +105 -0
- package/assets/pi-extended-teams-agent-navigation.png +0 -0
- package/assets/pi-extended-teams-in-action.png +0 -0
- package/extensions/agents/read-agent-report.ts +181 -0
- package/extensions/agents/read-agent-session-lifecycle.test.ts +605 -0
- package/extensions/agents/read-agent-session-lifecycle.ts +676 -0
- package/extensions/agents/read-agent.test.ts +3077 -0
- package/extensions/agents/read-agent.ts +1119 -0
- package/extensions/agents/write-agent.test.ts +513 -0
- package/extensions/agents/write-agent.ts +392 -0
- package/extensions/events/register-events.test.ts +465 -0
- package/extensions/events/register-events.ts +409 -0
- package/extensions/index.test.ts +1659 -0
- package/extensions/index.ts +1228 -0
- package/extensions/internal/agent-session-files.test.ts +164 -0
- package/extensions/internal/agent-session-files.ts +320 -0
- package/extensions/internal/debug.ts +44 -0
- package/extensions/internal/model-selection.ts +82 -0
- package/extensions/internal/pi-command.test.ts +191 -0
- package/extensions/internal/pi-command.ts +224 -0
- package/extensions/internal/pi-runtime-api.test.ts +43 -0
- package/extensions/internal/pi-runtime-api.ts +78 -0
- package/extensions/internal/schema.ts +19 -0
- package/extensions/internal/session-context-reference.test.ts +290 -0
- package/extensions/internal/session-context-reference.ts +430 -0
- package/extensions/internal/session-files.test.ts +225 -0
- package/extensions/internal/session-files.ts +273 -0
- package/extensions/internal/session-usage.ts +59 -0
- package/extensions/resources/spawn-resource-plan.test.ts +233 -0
- package/extensions/resources/spawn-resource-plan.ts +247 -0
- package/extensions/runtime/active-agent-sleep.test.ts +157 -0
- package/extensions/runtime/active-agent-sleep.ts +117 -0
- package/extensions/runtime/nested-read-agents.ts +22 -0
- package/extensions/runtime/pending-child-controller.test.ts +169 -0
- package/extensions/runtime/pending-child-controller.ts +280 -0
- package/extensions/runtime/types.ts +68 -0
- package/extensions/team/contracts.test.ts +111 -0
- package/extensions/team/lifecycle.test.ts +1095 -0
- package/extensions/team/lifecycle.ts +502 -0
- package/extensions/team/recipient-closure.test.ts +97 -0
- package/extensions/team/recipient-closure.ts +120 -0
- package/extensions/team/roster.test.ts +129 -0
- package/extensions/team/roster.ts +165 -0
- package/extensions/team/team-contracts.json +43 -0
- package/extensions/team/writer-screens.test.ts +50 -0
- package/extensions/team/writer-screens.ts +156 -0
- package/extensions/tools/agent-communication-tools.test.ts +306 -0
- package/extensions/tools/agent-communication-tools.ts +183 -0
- package/extensions/tools/coordination-tools.test.ts +670 -0
- package/extensions/tools/coordination-tools.ts +304 -0
- package/extensions/tools/delegation-guard.test.ts +95 -0
- package/extensions/tools/delegation-guard.ts +65 -0
- package/extensions/tools/file-claim-tools.test.ts +104 -0
- package/extensions/tools/file-claim-tools.ts +68 -0
- package/extensions/tools/model-tools.ts +53 -0
- package/extensions/tools/predefined-tools.test.ts +647 -0
- package/extensions/tools/predefined-tools.ts +331 -0
- package/extensions/tools/read-helper.test.ts +35 -0
- package/extensions/tools/task-runtime-tools.test.ts +334 -0
- package/extensions/tools/task-runtime-tools.ts +227 -0
- package/extensions/tools/team-tools.read-agent.test.ts +1924 -0
- package/extensions/tools/team-tools.ts +1270 -0
- package/extensions/ui/agent-follow-view.test.ts +520 -0
- package/extensions/ui/agent-follow-view.ts +779 -0
- package/extensions/ui/agent-navigation.test.ts +52 -0
- package/extensions/ui/agent-navigation.ts +55 -0
- package/extensions/ui/ansi.ts +16 -0
- package/extensions/ui/extensions-command.test.ts +336 -0
- package/extensions/ui/extensions-command.ts +282 -0
- package/extensions/ui/favorite-models-command.test.ts +280 -0
- package/extensions/ui/favorite-models-command.ts +423 -0
- package/extensions/ui/frame.ts +77 -0
- package/extensions/ui/input.ts +17 -0
- package/extensions/ui/read-agent-status.test.ts +117 -0
- package/extensions/ui/read-agent-status.ts +125 -0
- package/extensions/ui/renderers.ts +243 -0
- package/extensions/ui/status-widget.test.ts +143 -0
- package/extensions/ui/status-widget.ts +440 -0
- package/extensions/ui-frame.test.ts +69 -0
- package/package.json +75 -0
- package/skills/teams.md +256 -0
- package/src/adapters/terminal-registry.ts +78 -0
- package/src/adapters/tmux-adapter.test.ts +276 -0
- package/src/adapters/tmux-adapter.ts +200 -0
- package/src/orchestration/index.ts +437 -0
- package/src/orchestration/orchestrator.test.ts +396 -0
- package/src/orchestration/types.ts +125 -0
- package/src/utils/atomic-json.ts +21 -0
- package/src/utils/claims.test.ts +137 -0
- package/src/utils/claims.ts +169 -0
- package/src/utils/hooks.test.ts +75 -0
- package/src/utils/hooks.ts +35 -0
- package/src/utils/lifecycle-tombstone.test.ts +105 -0
- package/src/utils/lifecycle-tombstone.ts +268 -0
- package/src/utils/lock.race.child.ts +44 -0
- package/src/utils/lock.race.test.ts +198 -0
- package/src/utils/lock.test.ts +90 -0
- package/src/utils/lock.ts +186 -0
- package/src/utils/messaging.test.ts +337 -0
- package/src/utils/messaging.ts +441 -0
- package/src/utils/model-resolution.test.ts +231 -0
- package/src/utils/model-resolution.ts +322 -0
- package/src/utils/models.test.ts +8 -0
- package/src/utils/models.ts +115 -0
- package/src/utils/paths.ts +81 -0
- package/src/utils/predefined-teams/types.ts +48 -0
- package/src/utils/predefined-teams.save-template.test.ts +74 -0
- package/src/utils/predefined-teams.test.ts +441 -0
- package/src/utils/predefined-teams.ts +471 -0
- package/src/utils/read-helper-queue.ts +99 -0
- package/src/utils/report-events.test.ts +154 -0
- package/src/utils/report-events.ts +222 -0
- package/src/utils/runtime.test.ts +314 -0
- package/src/utils/runtime.ts +261 -0
- package/src/utils/security.test.ts +43 -0
- package/src/utils/settings.test.ts +480 -0
- package/src/utils/settings.ts +645 -0
- package/src/utils/shared-memory.test.ts +80 -0
- package/src/utils/shared-memory.ts +81 -0
- package/src/utils/tasks.race.test.ts +44 -0
- package/src/utils/tasks.test.ts +229 -0
- package/src/utils/tasks.ts +396 -0
- package/src/utils/teams.ts +231 -0
- package/src/utils/terminal-adapter.ts +103 -0
- package/src/utils/thinking-levels.test.ts +56 -0
- package/src/utils/thinking-levels.ts +47 -0
- package/src/utils/workflow-metadata.test.ts +11 -0
- package/src/utils/workflow-metadata.ts +88 -0
- package/src/utils/write-queue.test.ts +251 -0
- package/src/utils/write-queue.ts +202 -0
|
@@ -0,0 +1,441 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from "vitest";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import os from "node:os";
|
|
5
|
+
import {
|
|
6
|
+
parseAgentFrontmatter,
|
|
7
|
+
parseTeamsYaml,
|
|
8
|
+
discoverAgents,
|
|
9
|
+
discoverTeams,
|
|
10
|
+
getAllAgentDefinitions,
|
|
11
|
+
getAllPredefinedTeams,
|
|
12
|
+
getAgentDefinition,
|
|
13
|
+
getPredefinedTeam,
|
|
14
|
+
} from "./predefined-teams";
|
|
15
|
+
|
|
16
|
+
describe("parseAgentFrontmatter", () => {
|
|
17
|
+
it("parses a valid agent definition with all fields", () => {
|
|
18
|
+
const content = `---
|
|
19
|
+
name: scout
|
|
20
|
+
description: Fast recon and codebase exploration
|
|
21
|
+
tools: read,grep,find,ls
|
|
22
|
+
model: claude-sonnet-4
|
|
23
|
+
thinking: high
|
|
24
|
+
---
|
|
25
|
+
You are a scout agent. Investigate the codebase quickly.`;
|
|
26
|
+
|
|
27
|
+
const result = parseAgentFrontmatter(content, "/test/scout.md");
|
|
28
|
+
|
|
29
|
+
expect(result).not.toBeNull();
|
|
30
|
+
expect(result?.name).toBe("scout");
|
|
31
|
+
expect(result?.description).toBe("Fast recon and codebase exploration");
|
|
32
|
+
expect(result?.tools).toEqual(["read", "grep", "find", "ls"]);
|
|
33
|
+
expect(result?.model).toBe("claude-sonnet-4");
|
|
34
|
+
expect(result?.thinking).toBe("high");
|
|
35
|
+
expect(result?.prompt).toBe("You are a scout agent. Investigate the codebase quickly.");
|
|
36
|
+
expect(result?.filePath).toBe("/test/scout.md");
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it("parses an agent definition with space-separated tools", () => {
|
|
40
|
+
const content = `---
|
|
41
|
+
name: builder
|
|
42
|
+
description: Code builder
|
|
43
|
+
tools: read write edit bash
|
|
44
|
+
---
|
|
45
|
+
You build things.`;
|
|
46
|
+
|
|
47
|
+
const result = parseAgentFrontmatter(content, "/test/builder.md");
|
|
48
|
+
|
|
49
|
+
expect(result).not.toBeNull();
|
|
50
|
+
expect(result?.tools).toEqual(["read", "write", "edit", "bash"]);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it("parses an agent definition without optional fields", () => {
|
|
54
|
+
const content = `---
|
|
55
|
+
name: simple
|
|
56
|
+
description: Simple agent
|
|
57
|
+
---
|
|
58
|
+
You are simple.`;
|
|
59
|
+
|
|
60
|
+
const result = parseAgentFrontmatter(content, "/test/simple.md");
|
|
61
|
+
|
|
62
|
+
expect(result).not.toBeNull();
|
|
63
|
+
expect(result?.name).toBe("simple");
|
|
64
|
+
expect(result?.description).toBe("Simple agent");
|
|
65
|
+
expect(result?.tools).toBeUndefined();
|
|
66
|
+
expect(result?.model).toBeUndefined();
|
|
67
|
+
expect(result?.thinking).toBeUndefined();
|
|
68
|
+
expect(result?.prompt).toBe("You are simple.");
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it("returns null for content without frontmatter", () => {
|
|
72
|
+
const content = "This is just regular markdown without frontmatter.";
|
|
73
|
+
const result = parseAgentFrontmatter(content, "/test/no-frontmatter.md");
|
|
74
|
+
expect(result).toBeNull();
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it("returns null for frontmatter without name", () => {
|
|
78
|
+
const content = `---
|
|
79
|
+
description: Missing name field
|
|
80
|
+
---
|
|
81
|
+
Some prompt`;
|
|
82
|
+
const result = parseAgentFrontmatter(content, "/test/no-name.md");
|
|
83
|
+
expect(result).toBeNull();
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
describe("parseTeamsYaml", () => {
|
|
88
|
+
it("parses a valid teams.yaml content", () => {
|
|
89
|
+
const content = `
|
|
90
|
+
full:
|
|
91
|
+
- scout
|
|
92
|
+
- planner
|
|
93
|
+
- builder
|
|
94
|
+
|
|
95
|
+
plan-build:
|
|
96
|
+
- planner
|
|
97
|
+
- builder
|
|
98
|
+
- reviewer
|
|
99
|
+
`;
|
|
100
|
+
|
|
101
|
+
const result = parseTeamsYaml(content);
|
|
102
|
+
|
|
103
|
+
expect(result).toHaveLength(2);
|
|
104
|
+
expect(result[0].name).toBe("full");
|
|
105
|
+
expect(result[0].agents).toEqual(["scout", "planner", "builder"]);
|
|
106
|
+
expect(result[1].name).toBe("plan-build");
|
|
107
|
+
expect(result[1].agents).toEqual(["planner", "builder", "reviewer"]);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it("handles comments and empty lines", () => {
|
|
111
|
+
const content = `
|
|
112
|
+
# This is a comment
|
|
113
|
+
full:
|
|
114
|
+
- scout
|
|
115
|
+
# Another comment
|
|
116
|
+
- planner
|
|
117
|
+
|
|
118
|
+
# Empty line above
|
|
119
|
+
minimal:
|
|
120
|
+
- scout
|
|
121
|
+
`;
|
|
122
|
+
|
|
123
|
+
const result = parseTeamsYaml(content);
|
|
124
|
+
|
|
125
|
+
expect(result).toHaveLength(2);
|
|
126
|
+
expect(result[0].agents).toEqual(["scout", "planner"]);
|
|
127
|
+
expect(result[1].agents).toEqual(["scout"]);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it("returns empty array for empty content", () => {
|
|
131
|
+
expect(parseTeamsYaml("")).toEqual([]);
|
|
132
|
+
expect(parseTeamsYaml("# Just comments\n\n")).toEqual([]);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it("handles tab indentation", () => {
|
|
136
|
+
const content = `
|
|
137
|
+
full:
|
|
138
|
+
\t- scout
|
|
139
|
+
\t- planner
|
|
140
|
+
`;
|
|
141
|
+
|
|
142
|
+
const result = parseTeamsYaml(content);
|
|
143
|
+
|
|
144
|
+
expect(result).toHaveLength(1);
|
|
145
|
+
expect(result[0].agents).toEqual(["scout", "planner"]);
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
describe("discoverAgents", () => {
|
|
150
|
+
const testDir = path.join(os.tmpdir(), "pi-extended-teams-test-agents-" + Date.now());
|
|
151
|
+
|
|
152
|
+
beforeEach(() => {
|
|
153
|
+
if (fs.existsSync(testDir)) {
|
|
154
|
+
fs.rmSync(testDir, { recursive: true });
|
|
155
|
+
}
|
|
156
|
+
fs.mkdirSync(testDir, { recursive: true });
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
afterEach(() => {
|
|
160
|
+
if (fs.existsSync(testDir)) {
|
|
161
|
+
fs.rmSync(testDir, { recursive: true });
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
it("discovers agent definitions from markdown files", () => {
|
|
166
|
+
fs.writeFileSync(path.join(testDir, "scout.md"), `---
|
|
167
|
+
name: scout
|
|
168
|
+
description: Scout agent
|
|
169
|
+
---
|
|
170
|
+
Scout prompt`);
|
|
171
|
+
|
|
172
|
+
fs.writeFileSync(path.join(testDir, "builder.md"), `---
|
|
173
|
+
name: builder
|
|
174
|
+
description: Builder agent
|
|
175
|
+
---
|
|
176
|
+
Builder prompt`);
|
|
177
|
+
|
|
178
|
+
const result = discoverAgents(testDir);
|
|
179
|
+
|
|
180
|
+
expect(result).toHaveLength(2);
|
|
181
|
+
expect(result.find(a => a.name === "scout")).toBeDefined();
|
|
182
|
+
expect(result.find(a => a.name === "builder")).toBeDefined();
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
it("discovers agents from SKILL.md in subdirectories", () => {
|
|
186
|
+
const subDir = path.join(testDir, "special-agent");
|
|
187
|
+
fs.mkdirSync(subDir, { recursive: true });
|
|
188
|
+
|
|
189
|
+
fs.writeFileSync(path.join(subDir, "SKILL.md"), `---
|
|
190
|
+
name: special
|
|
191
|
+
description: Special agent
|
|
192
|
+
---
|
|
193
|
+
Special prompt`);
|
|
194
|
+
|
|
195
|
+
const result = discoverAgents(testDir);
|
|
196
|
+
|
|
197
|
+
expect(result).toHaveLength(1);
|
|
198
|
+
expect(result[0].name).toBe("special");
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
it("returns empty array for non-existent directory", () => {
|
|
202
|
+
const result = discoverAgents("/non/existent/path");
|
|
203
|
+
expect(result).toEqual([]);
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
it("ignores files without valid frontmatter", () => {
|
|
207
|
+
fs.writeFileSync(path.join(testDir, "invalid.md"), "No frontmatter here");
|
|
208
|
+
fs.writeFileSync(path.join(testDir, "valid.md"), `---
|
|
209
|
+
name: valid
|
|
210
|
+
description: Valid agent
|
|
211
|
+
---
|
|
212
|
+
Valid prompt`);
|
|
213
|
+
|
|
214
|
+
const result = discoverAgents(testDir);
|
|
215
|
+
|
|
216
|
+
expect(result).toHaveLength(1);
|
|
217
|
+
expect(result[0].name).toBe("valid");
|
|
218
|
+
});
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
describe("discoverTeams", () => {
|
|
222
|
+
const testDir = path.join(os.tmpdir(), "pi-extended-teams-test-teams-" + Date.now());
|
|
223
|
+
|
|
224
|
+
beforeEach(() => {
|
|
225
|
+
if (fs.existsSync(testDir)) {
|
|
226
|
+
fs.rmSync(testDir, { recursive: true });
|
|
227
|
+
}
|
|
228
|
+
fs.mkdirSync(testDir, { recursive: true });
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
afterEach(() => {
|
|
232
|
+
if (fs.existsSync(testDir)) {
|
|
233
|
+
fs.rmSync(testDir, { recursive: true });
|
|
234
|
+
}
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
it("discovers teams from teams.yaml", () => {
|
|
238
|
+
fs.writeFileSync(path.join(testDir, "teams.yaml"), `
|
|
239
|
+
full:
|
|
240
|
+
- scout
|
|
241
|
+
- planner
|
|
242
|
+
`);
|
|
243
|
+
|
|
244
|
+
const result = discoverTeams(testDir);
|
|
245
|
+
|
|
246
|
+
expect(result).toHaveLength(1);
|
|
247
|
+
expect(result[0].name).toBe("full");
|
|
248
|
+
expect(result[0].agents).toEqual(["scout", "planner"]);
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
it("returns empty array when teams.yaml does not exist", () => {
|
|
252
|
+
const result = discoverTeams(testDir);
|
|
253
|
+
expect(result).toEqual([]);
|
|
254
|
+
});
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
describe("getAllAgentDefinitions and getAllPredefinedTeams", () => {
|
|
258
|
+
const globalDir = path.join(os.homedir(), ".pi", "agent", "agents");
|
|
259
|
+
const globalTeamsDir = path.join(os.homedir(), ".pi");
|
|
260
|
+
const legacyGlobalTeamsDir = path.join(os.homedir(), ".pi", "agent");
|
|
261
|
+
const projectDir = path.join(os.tmpdir(), "pi-extended-teams-test-project-" + Date.now());
|
|
262
|
+
const projectAgentsDir = path.join(projectDir, ".pi", "agents");
|
|
263
|
+
const projectTeamsDir = path.join(projectDir, ".pi");
|
|
264
|
+
|
|
265
|
+
// Store original files to restore later
|
|
266
|
+
let originalGlobalAgents: string[] = [];
|
|
267
|
+
let originalGlobalTeams: string | null = null;
|
|
268
|
+
let originalLegacyGlobalTeams: string | null = null;
|
|
269
|
+
|
|
270
|
+
beforeEach(() => {
|
|
271
|
+
// Create project directory
|
|
272
|
+
if (fs.existsSync(projectDir)) {
|
|
273
|
+
fs.rmSync(projectDir, { recursive: true });
|
|
274
|
+
}
|
|
275
|
+
fs.mkdirSync(projectAgentsDir, { recursive: true });
|
|
276
|
+
|
|
277
|
+
// Backup global files if they exist
|
|
278
|
+
if (fs.existsSync(globalDir)) {
|
|
279
|
+
originalGlobalAgents = fs.readdirSync(globalDir);
|
|
280
|
+
}
|
|
281
|
+
if (fs.existsSync(path.join(globalTeamsDir, "teams.yaml"))) {
|
|
282
|
+
originalGlobalTeams = fs.readFileSync(path.join(globalTeamsDir, "teams.yaml"), "utf-8");
|
|
283
|
+
}
|
|
284
|
+
if (fs.existsSync(path.join(legacyGlobalTeamsDir, "teams.yaml"))) {
|
|
285
|
+
originalLegacyGlobalTeams = fs.readFileSync(path.join(legacyGlobalTeamsDir, "teams.yaml"), "utf-8");
|
|
286
|
+
}
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
afterEach(() => {
|
|
290
|
+
if (fs.existsSync(projectDir)) {
|
|
291
|
+
fs.rmSync(projectDir, { recursive: true });
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
const globalTeamsPath = path.join(globalTeamsDir, "teams.yaml");
|
|
295
|
+
if (originalGlobalTeams === null) {
|
|
296
|
+
if (fs.existsSync(globalTeamsPath)) fs.rmSync(globalTeamsPath);
|
|
297
|
+
} else {
|
|
298
|
+
fs.mkdirSync(globalTeamsDir, { recursive: true });
|
|
299
|
+
fs.writeFileSync(globalTeamsPath, originalGlobalTeams);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
const legacyGlobalTeamsPath = path.join(legacyGlobalTeamsDir, "teams.yaml");
|
|
303
|
+
if (originalLegacyGlobalTeams === null) {
|
|
304
|
+
if (fs.existsSync(legacyGlobalTeamsPath)) fs.rmSync(legacyGlobalTeamsPath);
|
|
305
|
+
} else {
|
|
306
|
+
fs.mkdirSync(legacyGlobalTeamsDir, { recursive: true });
|
|
307
|
+
fs.writeFileSync(legacyGlobalTeamsPath, originalLegacyGlobalTeams);
|
|
308
|
+
}
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
it("combines global and project-local agents", () => {
|
|
312
|
+
// Create project-local agent
|
|
313
|
+
fs.writeFileSync(path.join(projectAgentsDir, "project-agent.md"), `---
|
|
314
|
+
name: project-agent
|
|
315
|
+
description: Project local agent
|
|
316
|
+
---
|
|
317
|
+
Project prompt`);
|
|
318
|
+
|
|
319
|
+
const result = getAllAgentDefinitions(projectDir);
|
|
320
|
+
|
|
321
|
+
// Should include project-local agent
|
|
322
|
+
expect(result.find(a => a.name === "project-agent")).toBeDefined();
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
it("project-local agents override global agents", () => {
|
|
326
|
+
// Create project-local agent with same name as global
|
|
327
|
+
fs.writeFileSync(path.join(projectAgentsDir, "scout.md"), `---
|
|
328
|
+
name: scout
|
|
329
|
+
description: Project override scout
|
|
330
|
+
---
|
|
331
|
+
Project scout prompt`);
|
|
332
|
+
|
|
333
|
+
const result = getAllAgentDefinitions(projectDir);
|
|
334
|
+
const scout = result.find(a => a.name === "scout");
|
|
335
|
+
|
|
336
|
+
expect(scout).toBeDefined();
|
|
337
|
+
expect(scout?.description).toBe("Project override scout");
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
it("combines global and project-local teams", () => {
|
|
341
|
+
// Create project-local teams.yaml
|
|
342
|
+
fs.writeFileSync(path.join(projectTeamsDir, "teams.yaml"), `
|
|
343
|
+
custom:
|
|
344
|
+
- agent1
|
|
345
|
+
- agent2
|
|
346
|
+
`);
|
|
347
|
+
|
|
348
|
+
const result = getAllPredefinedTeams(projectDir);
|
|
349
|
+
|
|
350
|
+
// Should include project-local team
|
|
351
|
+
expect(result.find(t => t.name === "custom")).toBeDefined();
|
|
352
|
+
expect(result.find(t => t.name === "custom")?.agents).toEqual(["agent1", "agent2"]);
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
it("reads global predefined teams from ~/.pi/teams.yaml", () => {
|
|
356
|
+
fs.mkdirSync(globalTeamsDir, { recursive: true });
|
|
357
|
+
fs.writeFileSync(path.join(globalTeamsDir, "teams.yaml"), `
|
|
358
|
+
root-global:
|
|
359
|
+
- scout
|
|
360
|
+
`);
|
|
361
|
+
|
|
362
|
+
const result = getAllPredefinedTeams();
|
|
363
|
+
|
|
364
|
+
expect(result.find(t => t.name === "root-global")).toBeDefined();
|
|
365
|
+
expect(result.find(t => t.name === "root-global")?.agents).toEqual(["scout"]);
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
it("falls back to legacy ~/.pi/agent/teams.yaml when needed", () => {
|
|
369
|
+
const globalTeamsPath = path.join(globalTeamsDir, "teams.yaml");
|
|
370
|
+
if (fs.existsSync(globalTeamsPath)) {
|
|
371
|
+
fs.rmSync(globalTeamsPath);
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
fs.mkdirSync(legacyGlobalTeamsDir, { recursive: true });
|
|
375
|
+
fs.writeFileSync(path.join(legacyGlobalTeamsDir, "teams.yaml"), `
|
|
376
|
+
legacy-global:
|
|
377
|
+
- scout
|
|
378
|
+
`);
|
|
379
|
+
|
|
380
|
+
const result = getAllPredefinedTeams();
|
|
381
|
+
|
|
382
|
+
expect(result.find(t => t.name === "legacy-global")).toBeDefined();
|
|
383
|
+
expect(result.find(t => t.name === "legacy-global")?.agents).toEqual(["scout"]);
|
|
384
|
+
});
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
describe("getAgentDefinition and getPredefinedTeam", () => {
|
|
388
|
+
const projectDir = path.join(os.tmpdir(), "pi-extended-teams-test-get-" + Date.now());
|
|
389
|
+
const projectAgentsDir = path.join(projectDir, ".pi", "agents");
|
|
390
|
+
const projectTeamsDir = path.join(projectDir, ".pi");
|
|
391
|
+
|
|
392
|
+
beforeEach(() => {
|
|
393
|
+
if (fs.existsSync(projectDir)) {
|
|
394
|
+
fs.rmSync(projectDir, { recursive: true });
|
|
395
|
+
}
|
|
396
|
+
fs.mkdirSync(projectAgentsDir, { recursive: true });
|
|
397
|
+
|
|
398
|
+
fs.writeFileSync(path.join(projectAgentsDir, "test-agent.md"), `---
|
|
399
|
+
name: test-agent
|
|
400
|
+
description: Test agent
|
|
401
|
+
---
|
|
402
|
+
Test prompt`);
|
|
403
|
+
|
|
404
|
+
fs.writeFileSync(path.join(projectTeamsDir, "teams.yaml"), `
|
|
405
|
+
test-team:
|
|
406
|
+
- test-agent
|
|
407
|
+
`);
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
afterEach(() => {
|
|
411
|
+
if (fs.existsSync(projectDir)) {
|
|
412
|
+
fs.rmSync(projectDir, { recursive: true });
|
|
413
|
+
}
|
|
414
|
+
});
|
|
415
|
+
|
|
416
|
+
it("gets a specific agent definition by name", () => {
|
|
417
|
+
const result = getAgentDefinition("test-agent", projectDir);
|
|
418
|
+
|
|
419
|
+
expect(result).toBeDefined();
|
|
420
|
+
expect(result?.name).toBe("test-agent");
|
|
421
|
+
expect(result?.description).toBe("Test agent");
|
|
422
|
+
});
|
|
423
|
+
|
|
424
|
+
it("returns undefined for non-existent agent", () => {
|
|
425
|
+
const result = getAgentDefinition("non-existent", projectDir);
|
|
426
|
+
expect(result).toBeUndefined();
|
|
427
|
+
});
|
|
428
|
+
|
|
429
|
+
it("gets a specific predefined team by name", () => {
|
|
430
|
+
const result = getPredefinedTeam("test-team", projectDir);
|
|
431
|
+
|
|
432
|
+
expect(result).toBeDefined();
|
|
433
|
+
expect(result?.name).toBe("test-team");
|
|
434
|
+
expect(result?.agents).toEqual(["test-agent"]);
|
|
435
|
+
});
|
|
436
|
+
|
|
437
|
+
it("returns undefined for non-existent team", () => {
|
|
438
|
+
const result = getPredefinedTeam("non-existent", projectDir);
|
|
439
|
+
expect(result).toBeUndefined();
|
|
440
|
+
});
|
|
441
|
+
});
|