@melihmucuk/pi-crew 1.0.12 → 1.0.14
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/docs/architecture.md +24 -6
- package/extension/agent-discovery.ts +791 -0
- package/extension/bootstrap-session.ts +131 -0
- package/extension/index.ts +65 -0
- package/extension/integration/register-command.ts +59 -0
- package/extension/integration/register-renderers.ts +77 -0
- package/extension/integration/register-tools.ts +39 -0
- package/extension/integration/tool-presentation.ts +50 -0
- package/extension/integration/tools/crew-abort.ts +121 -0
- package/extension/integration/tools/crew-done.ts +42 -0
- package/extension/integration/tools/crew-list.ts +91 -0
- package/extension/integration/tools/crew-respond.ts +57 -0
- package/extension/integration/tools/crew-spawn.ts +88 -0
- package/extension/integration/tools/tool-deps.ts +16 -0
- package/extension/integration.ts +15 -0
- package/extension/runtime/crew-runtime.ts +426 -0
- package/extension/runtime/delivery-coordinator.ts +131 -0
- package/extension/runtime/overflow-recovery.ts +211 -0
- package/extension/runtime/subagent-registry.ts +85 -0
- package/extension/runtime/subagent-state.ts +73 -0
- package/extension/status-widget.ts +107 -0
- package/extension/subagent-messages.ts +124 -0
- package/extension/tool-registry.ts +19 -0
- package/package.json +11 -14
- package/dist/agent-discovery.d.ts +0 -29
- package/dist/agent-discovery.js +0 -527
- package/dist/bootstrap-session.d.ts +0 -21
- package/dist/bootstrap-session.js +0 -74
- package/dist/index.d.ts +0 -2
- package/dist/index.js +0 -46
- package/dist/integration/register-command.d.ts +0 -3
- package/dist/integration/register-command.js +0 -51
- package/dist/integration/register-renderers.d.ts +0 -2
- package/dist/integration/register-renderers.js +0 -53
- package/dist/integration/register-tools.d.ts +0 -3
- package/dist/integration/register-tools.js +0 -25
- package/dist/integration/tool-presentation.d.ts +0 -29
- package/dist/integration/tool-presentation.js +0 -28
- package/dist/integration/tools/crew-abort.d.ts +0 -2
- package/dist/integration/tools/crew-abort.js +0 -79
- package/dist/integration/tools/crew-done.d.ts +0 -2
- package/dist/integration/tools/crew-done.js +0 -28
- package/dist/integration/tools/crew-list.d.ts +0 -2
- package/dist/integration/tools/crew-list.js +0 -72
- package/dist/integration/tools/crew-respond.d.ts +0 -2
- package/dist/integration/tools/crew-respond.js +0 -32
- package/dist/integration/tools/crew-spawn.d.ts +0 -2
- package/dist/integration/tools/crew-spawn.js +0 -48
- package/dist/integration/tools/tool-deps.d.ts +0 -9
- package/dist/integration/tools/tool-deps.js +0 -1
- package/dist/integration.d.ts +0 -3
- package/dist/integration.js +0 -8
- package/dist/runtime/crew-runtime.d.ts +0 -62
- package/dist/runtime/crew-runtime.js +0 -285
- package/dist/runtime/delivery-coordinator.d.ts +0 -26
- package/dist/runtime/delivery-coordinator.js +0 -86
- package/dist/runtime/overflow-recovery.d.ts +0 -3
- package/dist/runtime/overflow-recovery.js +0 -155
- package/dist/runtime/subagent-registry.d.ts +0 -14
- package/dist/runtime/subagent-registry.js +0 -58
- package/dist/runtime/subagent-state.d.ts +0 -36
- package/dist/runtime/subagent-state.js +0 -34
- package/dist/status-widget.d.ts +0 -3
- package/dist/status-widget.js +0 -84
- package/dist/subagent-messages.d.ts +0 -33
- package/dist/subagent-messages.js +0 -59
- package/dist/tool-registry.d.ts +0 -5
- package/dist/tool-registry.js +0 -13
package/dist/agent-discovery.js
DELETED
|
@@ -1,527 +0,0 @@
|
|
|
1
|
-
import * as fs from "node:fs";
|
|
2
|
-
import * as path from "node:path";
|
|
3
|
-
import { fileURLToPath } from "node:url";
|
|
4
|
-
import { getAgentDir, parseFrontmatter } from "@mariozechner/pi-coding-agent";
|
|
5
|
-
import { isSupportedToolName } from "./tool-registry.js";
|
|
6
|
-
const VALID_THINKING_LEVELS = [
|
|
7
|
-
"off",
|
|
8
|
-
"minimal",
|
|
9
|
-
"low",
|
|
10
|
-
"medium",
|
|
11
|
-
"high",
|
|
12
|
-
"xhigh",
|
|
13
|
-
];
|
|
14
|
-
const ALLOWED_OVERRIDE_FIELDS = new Set([
|
|
15
|
-
"model",
|
|
16
|
-
"thinking",
|
|
17
|
-
"tools",
|
|
18
|
-
"skills",
|
|
19
|
-
"compaction",
|
|
20
|
-
"interactive",
|
|
21
|
-
]);
|
|
22
|
-
function createDiscoveryWarning(filePath, message) {
|
|
23
|
-
return { filePath, message };
|
|
24
|
-
}
|
|
25
|
-
/**
|
|
26
|
-
* Converts a comma-separated string or YAML array to string[].
|
|
27
|
-
* Returns undefined for null/undefined input.
|
|
28
|
-
*/
|
|
29
|
-
function parseCommaSeparated(value) {
|
|
30
|
-
if (value == null)
|
|
31
|
-
return undefined;
|
|
32
|
-
if (Array.isArray(value)) {
|
|
33
|
-
return value.map((v) => String(v).trim()).filter(Boolean);
|
|
34
|
-
}
|
|
35
|
-
if (typeof value === "string") {
|
|
36
|
-
return value
|
|
37
|
-
.split(",")
|
|
38
|
-
.map((s) => s.trim())
|
|
39
|
-
.filter(Boolean);
|
|
40
|
-
}
|
|
41
|
-
return undefined;
|
|
42
|
-
}
|
|
43
|
-
function formatFieldWarning(subject, name, warning) {
|
|
44
|
-
const prefix = `${subject === "subagent" ? "Subagent" : "Subagent override"} "${name}"`;
|
|
45
|
-
switch (warning.code) {
|
|
46
|
-
case "invalid-list-format":
|
|
47
|
-
return `${prefix}: invalid ${warning.fieldName} field, expected a comma-separated string or YAML array`;
|
|
48
|
-
case "invalid-type":
|
|
49
|
-
return `${prefix}: field "${warning.fieldName}" must be a ${warning.expected}, ignoring`;
|
|
50
|
-
case "invalid-model-format":
|
|
51
|
-
return `${prefix}: invalid model format "${warning.model}" (expected "provider/model-id"), ignoring model field`;
|
|
52
|
-
case "invalid-thinking-level":
|
|
53
|
-
return `${prefix}: invalid thinking level "${warning.thinking}", ignoring`;
|
|
54
|
-
case "unknown-tools":
|
|
55
|
-
return `${prefix}: unknown tools ${warning.tools.map((toolName) => `"${toolName}"`).join(", ")}, ignoring`;
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
function toDiscoveryWarnings(filePath, subject, name, warnings) {
|
|
59
|
-
return warnings.map((warning) => createDiscoveryWarning(filePath, formatFieldWarning(subject, name, warning)));
|
|
60
|
-
}
|
|
61
|
-
function parseListField(value, fieldName) {
|
|
62
|
-
if (value == null)
|
|
63
|
-
return { values: [], warnings: [] };
|
|
64
|
-
const parsed = parseCommaSeparated(value);
|
|
65
|
-
if (parsed !== undefined)
|
|
66
|
-
return { values: parsed, warnings: [] };
|
|
67
|
-
return {
|
|
68
|
-
values: [],
|
|
69
|
-
warnings: [{ code: "invalid-list-format", fieldName }],
|
|
70
|
-
};
|
|
71
|
-
}
|
|
72
|
-
/**
|
|
73
|
-
* Parses "provider/model-id" format.
|
|
74
|
-
* Returns null if "/" is missing.
|
|
75
|
-
*/
|
|
76
|
-
function parseModel(value) {
|
|
77
|
-
if (typeof value !== "string" || !value.includes("/")) {
|
|
78
|
-
return null;
|
|
79
|
-
}
|
|
80
|
-
const slashIndex = value.indexOf("/");
|
|
81
|
-
const provider = value.slice(0, slashIndex).trim();
|
|
82
|
-
const modelId = value.slice(slashIndex + 1).trim();
|
|
83
|
-
if (!provider || !modelId)
|
|
84
|
-
return null;
|
|
85
|
-
return { provider, modelId };
|
|
86
|
-
}
|
|
87
|
-
function validateThinkingLevel(value) {
|
|
88
|
-
if (!value)
|
|
89
|
-
return undefined;
|
|
90
|
-
if (VALID_THINKING_LEVELS.includes(value))
|
|
91
|
-
return value;
|
|
92
|
-
return undefined;
|
|
93
|
-
}
|
|
94
|
-
function parseModelField(value, options) {
|
|
95
|
-
if (typeof value === "string") {
|
|
96
|
-
const parsedModel = parseModel(value);
|
|
97
|
-
if (!parsedModel) {
|
|
98
|
-
return {
|
|
99
|
-
...(options.setValueOnInvalidType ? { model: value } : {}),
|
|
100
|
-
warnings: [{ code: "invalid-model-format", model: value }],
|
|
101
|
-
};
|
|
102
|
-
}
|
|
103
|
-
return {
|
|
104
|
-
model: value,
|
|
105
|
-
parsedModel,
|
|
106
|
-
warnings: [],
|
|
107
|
-
};
|
|
108
|
-
}
|
|
109
|
-
if (value !== undefined && options.warnOnInvalidType) {
|
|
110
|
-
return {
|
|
111
|
-
warnings: [{ code: "invalid-type", fieldName: "model", expected: "string" }],
|
|
112
|
-
};
|
|
113
|
-
}
|
|
114
|
-
return { warnings: [] };
|
|
115
|
-
}
|
|
116
|
-
function parseThinkingField(value, options) {
|
|
117
|
-
if (typeof value === "string") {
|
|
118
|
-
const thinking = validateThinkingLevel(value);
|
|
119
|
-
if (!thinking) {
|
|
120
|
-
return {
|
|
121
|
-
warnings: [{ code: "invalid-thinking-level", thinking: value }],
|
|
122
|
-
};
|
|
123
|
-
}
|
|
124
|
-
return { thinking, warnings: [] };
|
|
125
|
-
}
|
|
126
|
-
if (value !== undefined && options.warnOnInvalidType) {
|
|
127
|
-
return {
|
|
128
|
-
warnings: [{ code: "invalid-type", fieldName: "thinking", expected: "string" }],
|
|
129
|
-
};
|
|
130
|
-
}
|
|
131
|
-
return { warnings: [] };
|
|
132
|
-
}
|
|
133
|
-
function parseToolsField(value, options) {
|
|
134
|
-
const parsedTools = parseListField(value, "tools");
|
|
135
|
-
const validTools = parsedTools.values.filter(isSupportedToolName);
|
|
136
|
-
const invalidTools = parsedTools.values.filter((toolName) => !isSupportedToolName(toolName));
|
|
137
|
-
const warnings = [...parsedTools.warnings];
|
|
138
|
-
if (invalidTools.length > 0) {
|
|
139
|
-
warnings.push({ code: "unknown-tools", tools: invalidTools });
|
|
140
|
-
}
|
|
141
|
-
if (invalidTools.length > 0 && validTools.length === 0 && !options.setValueOnInvalidType) {
|
|
142
|
-
return { warnings };
|
|
143
|
-
}
|
|
144
|
-
if (parsedTools.warnings.length > 0 && !options.setValueOnInvalidType) {
|
|
145
|
-
return { warnings };
|
|
146
|
-
}
|
|
147
|
-
return {
|
|
148
|
-
tools: validTools,
|
|
149
|
-
warnings,
|
|
150
|
-
};
|
|
151
|
-
}
|
|
152
|
-
function parseSkillsField(value, options) {
|
|
153
|
-
const parsedSkills = parseListField(value, "skills");
|
|
154
|
-
if (parsedSkills.warnings.length > 0 && !options.setValueOnInvalidType) {
|
|
155
|
-
return { warnings: parsedSkills.warnings };
|
|
156
|
-
}
|
|
157
|
-
return {
|
|
158
|
-
skills: parsedSkills.values,
|
|
159
|
-
warnings: parsedSkills.warnings,
|
|
160
|
-
};
|
|
161
|
-
}
|
|
162
|
-
function parseBooleanField(fieldName, value, options) {
|
|
163
|
-
if (typeof value === "boolean") {
|
|
164
|
-
return {
|
|
165
|
-
[fieldName]: value,
|
|
166
|
-
warnings: [],
|
|
167
|
-
};
|
|
168
|
-
}
|
|
169
|
-
if (value !== undefined && options.warnOnInvalidType) {
|
|
170
|
-
return {
|
|
171
|
-
warnings: [{ code: "invalid-type", fieldName, expected: "boolean" }],
|
|
172
|
-
};
|
|
173
|
-
}
|
|
174
|
-
return { warnings: [] };
|
|
175
|
-
}
|
|
176
|
-
function parseSharedFields(record, options) {
|
|
177
|
-
const model = parseModelField(record.model, options);
|
|
178
|
-
const thinking = parseThinkingField(record.thinking, options);
|
|
179
|
-
const tools = Object.prototype.hasOwnProperty.call(record, "tools")
|
|
180
|
-
? parseToolsField(record.tools, options)
|
|
181
|
-
: { warnings: [] };
|
|
182
|
-
const skills = Object.prototype.hasOwnProperty.call(record, "skills")
|
|
183
|
-
? parseSkillsField(record.skills, options)
|
|
184
|
-
: { warnings: [] };
|
|
185
|
-
const compaction = parseBooleanField("compaction", record.compaction, options);
|
|
186
|
-
const interactive = parseBooleanField("interactive", record.interactive, options);
|
|
187
|
-
return {
|
|
188
|
-
...("model" in model ? { model: model.model } : {}),
|
|
189
|
-
...("parsedModel" in model ? { parsedModel: model.parsedModel } : {}),
|
|
190
|
-
...(thinking.thinking !== undefined ? { thinking: thinking.thinking } : {}),
|
|
191
|
-
...(tools.tools !== undefined ? { tools: tools.tools } : {}),
|
|
192
|
-
...(skills.skills !== undefined ? { skills: skills.skills } : {}),
|
|
193
|
-
...(compaction.compaction !== undefined ? { compaction: compaction.compaction } : {}),
|
|
194
|
-
...(interactive.interactive !== undefined ? { interactive: interactive.interactive } : {}),
|
|
195
|
-
warnings: [
|
|
196
|
-
...model.warnings,
|
|
197
|
-
...thinking.warnings,
|
|
198
|
-
...tools.warnings,
|
|
199
|
-
...skills.warnings,
|
|
200
|
-
...compaction.warnings,
|
|
201
|
-
...interactive.warnings,
|
|
202
|
-
],
|
|
203
|
-
};
|
|
204
|
-
}
|
|
205
|
-
function parseAgentDefinition(content, filePath) {
|
|
206
|
-
const warnings = [];
|
|
207
|
-
let frontmatter;
|
|
208
|
-
let body;
|
|
209
|
-
try {
|
|
210
|
-
const parsed = parseFrontmatter(content);
|
|
211
|
-
frontmatter = parsed.frontmatter;
|
|
212
|
-
body = parsed.body;
|
|
213
|
-
}
|
|
214
|
-
catch (error) {
|
|
215
|
-
const reason = error instanceof Error ? error.message : String(error);
|
|
216
|
-
return {
|
|
217
|
-
agent: null,
|
|
218
|
-
warnings: [
|
|
219
|
-
createDiscoveryWarning(filePath, `Ignored invalid subagent definition. Frontmatter could not be parsed: ${reason}`),
|
|
220
|
-
],
|
|
221
|
-
};
|
|
222
|
-
}
|
|
223
|
-
const name = typeof frontmatter.name === "string" ? frontmatter.name.trim() : undefined;
|
|
224
|
-
const description = typeof frontmatter.description === "string" ? frontmatter.description.trim() : undefined;
|
|
225
|
-
if (!name || !description) {
|
|
226
|
-
return {
|
|
227
|
-
agent: null,
|
|
228
|
-
warnings: [
|
|
229
|
-
createDiscoveryWarning(filePath, 'Ignored invalid subagent definition. Required frontmatter fields "name" and "description" must be non-empty strings.'),
|
|
230
|
-
],
|
|
231
|
-
};
|
|
232
|
-
}
|
|
233
|
-
if (/\s/.test(name)) {
|
|
234
|
-
return {
|
|
235
|
-
agent: null,
|
|
236
|
-
warnings: [
|
|
237
|
-
createDiscoveryWarning(filePath, `Ignored subagent definition "${name}". Subagent names cannot contain whitespace. Use "-" instead.`),
|
|
238
|
-
],
|
|
239
|
-
};
|
|
240
|
-
}
|
|
241
|
-
const parsedFields = parseSharedFields(frontmatter, {
|
|
242
|
-
warnOnInvalidType: false,
|
|
243
|
-
setValueOnInvalidType: true,
|
|
244
|
-
});
|
|
245
|
-
warnings.push(...toDiscoveryWarnings(filePath, "subagent", name, parsedFields.warnings));
|
|
246
|
-
const { model, parsedModel, thinking, tools, skills, compaction, interactive } = parsedFields;
|
|
247
|
-
return {
|
|
248
|
-
agent: {
|
|
249
|
-
name,
|
|
250
|
-
description,
|
|
251
|
-
model,
|
|
252
|
-
parsedModel: parsedModel ?? undefined,
|
|
253
|
-
thinking,
|
|
254
|
-
tools,
|
|
255
|
-
skills,
|
|
256
|
-
compaction,
|
|
257
|
-
interactive,
|
|
258
|
-
systemPrompt: body,
|
|
259
|
-
filePath,
|
|
260
|
-
},
|
|
261
|
-
warnings,
|
|
262
|
-
};
|
|
263
|
-
}
|
|
264
|
-
function loadAgentFile(filePath) {
|
|
265
|
-
try {
|
|
266
|
-
return {
|
|
267
|
-
content: fs.readFileSync(filePath, "utf-8"),
|
|
268
|
-
warnings: [],
|
|
269
|
-
};
|
|
270
|
-
}
|
|
271
|
-
catch (error) {
|
|
272
|
-
const reason = error instanceof Error ? error.message : String(error);
|
|
273
|
-
return {
|
|
274
|
-
content: null,
|
|
275
|
-
warnings: [
|
|
276
|
-
createDiscoveryWarning(filePath, `Ignored subagent definition. File could not be read: ${reason}`),
|
|
277
|
-
],
|
|
278
|
-
};
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
function loadAgentDefinitionFromFile(filePath) {
|
|
282
|
-
const file = loadAgentFile(filePath);
|
|
283
|
-
if (!file.content) {
|
|
284
|
-
return { agent: null, warnings: file.warnings };
|
|
285
|
-
}
|
|
286
|
-
const parsed = parseAgentDefinition(file.content, filePath);
|
|
287
|
-
return {
|
|
288
|
-
agent: parsed.agent,
|
|
289
|
-
warnings: [...file.warnings, ...parsed.warnings],
|
|
290
|
-
};
|
|
291
|
-
}
|
|
292
|
-
function loadAgentDefinitionFiles(agentsDir) {
|
|
293
|
-
let entries;
|
|
294
|
-
try {
|
|
295
|
-
entries = fs.readdirSync(agentsDir, { withFileTypes: true });
|
|
296
|
-
}
|
|
297
|
-
catch (error) {
|
|
298
|
-
const reason = error instanceof Error ? error.message : String(error);
|
|
299
|
-
return {
|
|
300
|
-
filePaths: [],
|
|
301
|
-
warnings: [
|
|
302
|
-
createDiscoveryWarning(agentsDir, `Subagent directory could not be read: ${reason}`),
|
|
303
|
-
],
|
|
304
|
-
};
|
|
305
|
-
}
|
|
306
|
-
return {
|
|
307
|
-
filePaths: entries
|
|
308
|
-
.filter((entry) => entry.name.endsWith(".md"))
|
|
309
|
-
.filter((entry) => entry.isFile() || entry.isSymbolicLink())
|
|
310
|
-
.map((entry) => path.join(agentsDir, entry.name)),
|
|
311
|
-
warnings: [],
|
|
312
|
-
};
|
|
313
|
-
}
|
|
314
|
-
function parseOverrideFields(agentName, value, filePath) {
|
|
315
|
-
const warnings = [];
|
|
316
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
317
|
-
return {
|
|
318
|
-
override: null,
|
|
319
|
-
warnings: [
|
|
320
|
-
createDiscoveryWarning(filePath, `Subagent override "${agentName}" must be a JSON object, ignoring`),
|
|
321
|
-
],
|
|
322
|
-
};
|
|
323
|
-
}
|
|
324
|
-
const record = value;
|
|
325
|
-
for (const fieldName of Object.keys(record)) {
|
|
326
|
-
if (fieldName === "name" || fieldName === "description") {
|
|
327
|
-
warnings.push(createDiscoveryWarning(filePath, `Subagent override "${agentName}": field "${fieldName}" is not overridable, ignoring`));
|
|
328
|
-
continue;
|
|
329
|
-
}
|
|
330
|
-
if (!ALLOWED_OVERRIDE_FIELDS.has(fieldName)) {
|
|
331
|
-
warnings.push(createDiscoveryWarning(filePath, `Subagent override "${agentName}": unknown field "${fieldName}", ignoring`));
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
|
-
const parsedFields = parseSharedFields(record, {
|
|
335
|
-
warnOnInvalidType: true,
|
|
336
|
-
setValueOnInvalidType: false,
|
|
337
|
-
});
|
|
338
|
-
warnings.push(...toDiscoveryWarnings(filePath, "subagent override", agentName, parsedFields.warnings));
|
|
339
|
-
const override = {};
|
|
340
|
-
if (parsedFields.model !== undefined) {
|
|
341
|
-
override.model = parsedFields.model;
|
|
342
|
-
}
|
|
343
|
-
if (parsedFields.parsedModel !== undefined) {
|
|
344
|
-
override.parsedModel = parsedFields.parsedModel;
|
|
345
|
-
}
|
|
346
|
-
if (parsedFields.thinking !== undefined) {
|
|
347
|
-
override.thinking = parsedFields.thinking;
|
|
348
|
-
}
|
|
349
|
-
if (parsedFields.tools !== undefined) {
|
|
350
|
-
override.tools = parsedFields.tools;
|
|
351
|
-
}
|
|
352
|
-
if (parsedFields.skills !== undefined) {
|
|
353
|
-
override.skills = parsedFields.skills;
|
|
354
|
-
}
|
|
355
|
-
if (parsedFields.compaction !== undefined) {
|
|
356
|
-
override.compaction = parsedFields.compaction;
|
|
357
|
-
}
|
|
358
|
-
if (parsedFields.interactive !== undefined) {
|
|
359
|
-
override.interactive = parsedFields.interactive;
|
|
360
|
-
}
|
|
361
|
-
return { override, warnings };
|
|
362
|
-
}
|
|
363
|
-
function parseConfigFile(content, filePath) {
|
|
364
|
-
let parsed;
|
|
365
|
-
try {
|
|
366
|
-
parsed = JSON.parse(content);
|
|
367
|
-
}
|
|
368
|
-
catch (error) {
|
|
369
|
-
const reason = error instanceof Error ? error.message : String(error);
|
|
370
|
-
return {
|
|
371
|
-
overrides: {},
|
|
372
|
-
overrideSources: {},
|
|
373
|
-
warnings: [
|
|
374
|
-
createDiscoveryWarning(filePath, `Ignored pi-crew config. JSON could not be parsed: ${reason}`),
|
|
375
|
-
],
|
|
376
|
-
};
|
|
377
|
-
}
|
|
378
|
-
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
379
|
-
return {
|
|
380
|
-
overrides: {},
|
|
381
|
-
overrideSources: {},
|
|
382
|
-
warnings: [
|
|
383
|
-
createDiscoveryWarning(filePath, "Ignored pi-crew config. Root value must be a JSON object."),
|
|
384
|
-
],
|
|
385
|
-
};
|
|
386
|
-
}
|
|
387
|
-
const root = parsed;
|
|
388
|
-
if (root.agents === undefined) {
|
|
389
|
-
return { overrides: {}, overrideSources: {}, warnings: [] };
|
|
390
|
-
}
|
|
391
|
-
if (!root.agents || typeof root.agents !== "object" || Array.isArray(root.agents)) {
|
|
392
|
-
return {
|
|
393
|
-
overrides: {},
|
|
394
|
-
overrideSources: {},
|
|
395
|
-
warnings: [
|
|
396
|
-
createDiscoveryWarning(filePath, 'Ignored pi-crew config. Field "agents" must be a JSON object.'),
|
|
397
|
-
],
|
|
398
|
-
};
|
|
399
|
-
}
|
|
400
|
-
const overrides = {};
|
|
401
|
-
const overrideSources = {};
|
|
402
|
-
const warnings = [];
|
|
403
|
-
for (const [agentName, value] of Object.entries(root.agents)) {
|
|
404
|
-
if (!agentName.trim()) {
|
|
405
|
-
warnings.push(createDiscoveryWarning(filePath, "Ignored pi-crew config entry with empty subagent name."));
|
|
406
|
-
continue;
|
|
407
|
-
}
|
|
408
|
-
const parsedOverride = parseOverrideFields(agentName, value, filePath);
|
|
409
|
-
warnings.push(...parsedOverride.warnings);
|
|
410
|
-
if (parsedOverride.override) {
|
|
411
|
-
overrides[agentName] = parsedOverride.override;
|
|
412
|
-
overrideSources[agentName] = filePath;
|
|
413
|
-
}
|
|
414
|
-
}
|
|
415
|
-
return { overrides, overrideSources, warnings };
|
|
416
|
-
}
|
|
417
|
-
function loadConfigOverridesFromFile(filePath) {
|
|
418
|
-
if (!fs.existsSync(filePath)) {
|
|
419
|
-
return { overrides: {}, overrideSources: {}, warnings: [] };
|
|
420
|
-
}
|
|
421
|
-
try {
|
|
422
|
-
const content = fs.readFileSync(filePath, "utf-8");
|
|
423
|
-
return parseConfigFile(content, filePath);
|
|
424
|
-
}
|
|
425
|
-
catch (error) {
|
|
426
|
-
const reason = error instanceof Error ? error.message : String(error);
|
|
427
|
-
return {
|
|
428
|
-
overrides: {},
|
|
429
|
-
overrideSources: {},
|
|
430
|
-
warnings: [
|
|
431
|
-
createDiscoveryWarning(filePath, `Ignored pi-crew config. File could not be read: ${reason}`),
|
|
432
|
-
],
|
|
433
|
-
};
|
|
434
|
-
}
|
|
435
|
-
}
|
|
436
|
-
function mergeConfigOverrides(base, override) {
|
|
437
|
-
const merged = { ...base };
|
|
438
|
-
for (const [agentName, agentOverride] of Object.entries(override)) {
|
|
439
|
-
merged[agentName] = {
|
|
440
|
-
...(merged[agentName] ?? {}),
|
|
441
|
-
...agentOverride,
|
|
442
|
-
};
|
|
443
|
-
}
|
|
444
|
-
return merged;
|
|
445
|
-
}
|
|
446
|
-
function mergeOverrideSources(base, override) {
|
|
447
|
-
return {
|
|
448
|
-
...base,
|
|
449
|
-
...override,
|
|
450
|
-
};
|
|
451
|
-
}
|
|
452
|
-
function loadConfigOverrides(cwd) {
|
|
453
|
-
const globalPath = path.join(getAgentDir(), "pi-crew.json");
|
|
454
|
-
const projectPath = path.join(cwd, ".pi", "pi-crew.json");
|
|
455
|
-
const globalConfig = loadConfigOverridesFromFile(globalPath);
|
|
456
|
-
const projectConfig = loadConfigOverridesFromFile(projectPath);
|
|
457
|
-
return {
|
|
458
|
-
overrides: mergeConfigOverrides(globalConfig.overrides, projectConfig.overrides),
|
|
459
|
-
overrideSources: mergeOverrideSources(globalConfig.overrideSources, projectConfig.overrideSources),
|
|
460
|
-
warnings: [...globalConfig.warnings, ...projectConfig.warnings],
|
|
461
|
-
};
|
|
462
|
-
}
|
|
463
|
-
function applyAgentOverride(agent, override) {
|
|
464
|
-
return {
|
|
465
|
-
...agent,
|
|
466
|
-
...(override.model !== undefined ? { model: override.model, parsedModel: override.parsedModel } : {}),
|
|
467
|
-
...(override.thinking !== undefined ? { thinking: override.thinking } : {}),
|
|
468
|
-
...(override.tools !== undefined ? { tools: override.tools } : {}),
|
|
469
|
-
...(override.skills !== undefined ? { skills: override.skills } : {}),
|
|
470
|
-
...(override.compaction !== undefined ? { compaction: override.compaction } : {}),
|
|
471
|
-
...(override.interactive !== undefined ? { interactive: override.interactive } : {}),
|
|
472
|
-
};
|
|
473
|
-
}
|
|
474
|
-
const bundledAgentsDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..", "agents");
|
|
475
|
-
/**
|
|
476
|
-
* Loads agents from a single directory into the agents list.
|
|
477
|
-
* Skips agents whose name already exists in seenNames (higher-priority source wins).
|
|
478
|
-
* Within the same directory, duplicate names produce a warning.
|
|
479
|
-
*/
|
|
480
|
-
function loadAgentsFromDir(agentsDir, seenNames, agents, warnings) {
|
|
481
|
-
if (!fs.existsSync(agentsDir))
|
|
482
|
-
return;
|
|
483
|
-
const fileLoad = loadAgentDefinitionFiles(agentsDir);
|
|
484
|
-
warnings.push(...fileLoad.warnings);
|
|
485
|
-
const dirNames = new Set();
|
|
486
|
-
for (const filePath of fileLoad.filePaths) {
|
|
487
|
-
const loaded = loadAgentDefinitionFromFile(filePath);
|
|
488
|
-
warnings.push(...loaded.warnings);
|
|
489
|
-
if (!loaded.agent)
|
|
490
|
-
continue;
|
|
491
|
-
const { name } = loaded.agent;
|
|
492
|
-
// Higher-priority source already registered this name
|
|
493
|
-
if (seenNames.has(name))
|
|
494
|
-
continue;
|
|
495
|
-
// Duplicate within the same directory
|
|
496
|
-
if (dirNames.has(name)) {
|
|
497
|
-
warnings.push(createDiscoveryWarning(filePath, `Duplicate subagent name "${name}" in ${agentsDir}, skipping`));
|
|
498
|
-
continue;
|
|
499
|
-
}
|
|
500
|
-
dirNames.add(name);
|
|
501
|
-
seenNames.set(name, filePath);
|
|
502
|
-
agents.push(loaded.agent);
|
|
503
|
-
}
|
|
504
|
-
}
|
|
505
|
-
export function discoverAgents(cwd = process.cwd()) {
|
|
506
|
-
const agents = [];
|
|
507
|
-
const warnings = [];
|
|
508
|
-
const seenNames = new Map();
|
|
509
|
-
// Priority 1: project-level agents
|
|
510
|
-
loadAgentsFromDir(path.join(cwd, ".pi", "agents"), seenNames, agents, warnings);
|
|
511
|
-
// Priority 2: user global agents
|
|
512
|
-
loadAgentsFromDir(path.join(getAgentDir(), "agents"), seenNames, agents, warnings);
|
|
513
|
-
// Priority 3: bundled agents
|
|
514
|
-
loadAgentsFromDir(bundledAgentsDir, seenNames, agents, warnings);
|
|
515
|
-
const configOverrides = loadConfigOverrides(cwd);
|
|
516
|
-
warnings.push(...configOverrides.warnings);
|
|
517
|
-
const finalAgents = agents.map((agent) => {
|
|
518
|
-
const override = configOverrides.overrides[agent.name];
|
|
519
|
-
return override ? applyAgentOverride(agent, override) : agent;
|
|
520
|
-
});
|
|
521
|
-
for (const agentName of Object.keys(configOverrides.overrides)) {
|
|
522
|
-
if (!seenNames.has(agentName)) {
|
|
523
|
-
warnings.push(createDiscoveryWarning(configOverrides.overrideSources[agentName] ?? path.join(cwd, ".pi", "pi-crew.json"), `Subagent override "${agentName}" does not match any discovered subagent, ignoring`));
|
|
524
|
-
}
|
|
525
|
-
}
|
|
526
|
-
return { agents: finalAgents, warnings };
|
|
527
|
-
}
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import { type AgentSession, type ModelRegistry } from "@mariozechner/pi-coding-agent";
|
|
2
|
-
import type { Api, Model } from "@mariozechner/pi-ai";
|
|
3
|
-
import type { AgentConfig } from "./agent-discovery.js";
|
|
4
|
-
export interface BootstrapContext {
|
|
5
|
-
model: Model<Api> | undefined;
|
|
6
|
-
modelRegistry: ModelRegistry;
|
|
7
|
-
agentDir: string;
|
|
8
|
-
parentSessionFile?: string;
|
|
9
|
-
}
|
|
10
|
-
interface BootstrapOptions {
|
|
11
|
-
agentConfig: AgentConfig;
|
|
12
|
-
cwd: string;
|
|
13
|
-
ctx: BootstrapContext;
|
|
14
|
-
extensionResolvedPath: string;
|
|
15
|
-
}
|
|
16
|
-
export interface BootstrapResult {
|
|
17
|
-
session: AgentSession;
|
|
18
|
-
warnings: string[];
|
|
19
|
-
}
|
|
20
|
-
export declare function bootstrapSession(opts: BootstrapOptions): Promise<BootstrapResult>;
|
|
21
|
-
export {};
|
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
import { createAgentSession, DefaultResourceLoader, SessionManager, SettingsManager, } from "@mariozechner/pi-coding-agent";
|
|
2
|
-
import { SUPPORTED_TOOL_NAMES } from "./tool-registry.js";
|
|
3
|
-
function resolveTools(agentConfig) {
|
|
4
|
-
return [...(agentConfig.tools ?? SUPPORTED_TOOL_NAMES)];
|
|
5
|
-
}
|
|
6
|
-
function resolveModel(agentConfig, ctx) {
|
|
7
|
-
const warnings = [];
|
|
8
|
-
const model = ctx.model;
|
|
9
|
-
if (!agentConfig.parsedModel)
|
|
10
|
-
return { model, warnings };
|
|
11
|
-
const found = ctx.modelRegistry.find(agentConfig.parsedModel.provider, agentConfig.parsedModel.modelId);
|
|
12
|
-
if (found)
|
|
13
|
-
return { model: found, warnings };
|
|
14
|
-
warnings.push(`Model "${agentConfig.model}" not found, using current session model`);
|
|
15
|
-
return { model, warnings };
|
|
16
|
-
}
|
|
17
|
-
function getSkillWarnings(agentConfig, resourceLoader) {
|
|
18
|
-
const warnings = [];
|
|
19
|
-
if (!agentConfig.skills)
|
|
20
|
-
return warnings;
|
|
21
|
-
const availableSkillNames = new Set(resourceLoader.getSkills().skills.map((skill) => skill.name));
|
|
22
|
-
for (const skillName of agentConfig.skills) {
|
|
23
|
-
if (!availableSkillNames.has(skillName)) {
|
|
24
|
-
warnings.push(`Unknown skill "${skillName}" in subagent config, skipping`);
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
return warnings;
|
|
28
|
-
}
|
|
29
|
-
export async function bootstrapSession(opts) {
|
|
30
|
-
const warnings = [];
|
|
31
|
-
const { agentConfig, cwd, ctx, extensionResolvedPath } = opts;
|
|
32
|
-
const authStorage = ctx.modelRegistry.authStorage;
|
|
33
|
-
const modelRegistry = ctx.modelRegistry;
|
|
34
|
-
const { model, warnings: modelWarnings } = resolveModel(agentConfig, ctx);
|
|
35
|
-
warnings.push(...modelWarnings);
|
|
36
|
-
const tools = resolveTools(agentConfig);
|
|
37
|
-
const resourceLoader = new DefaultResourceLoader({
|
|
38
|
-
cwd,
|
|
39
|
-
agentDir: ctx.agentDir,
|
|
40
|
-
extensionsOverride: (base) => ({
|
|
41
|
-
...base,
|
|
42
|
-
extensions: base.extensions.filter((ext) => !ext.resolvedPath.startsWith(extensionResolvedPath)),
|
|
43
|
-
}),
|
|
44
|
-
skillsOverride: agentConfig.skills
|
|
45
|
-
? (base) => ({
|
|
46
|
-
skills: base.skills.filter((skill) => agentConfig.skills.includes(skill.name)),
|
|
47
|
-
diagnostics: base.diagnostics,
|
|
48
|
-
})
|
|
49
|
-
: undefined,
|
|
50
|
-
appendSystemPromptOverride: (base) => agentConfig.systemPrompt.trim()
|
|
51
|
-
? [...base, agentConfig.systemPrompt]
|
|
52
|
-
: base,
|
|
53
|
-
});
|
|
54
|
-
await resourceLoader.reload();
|
|
55
|
-
warnings.push(...getSkillWarnings(agentConfig, resourceLoader));
|
|
56
|
-
const settingsManager = SettingsManager.inMemory({
|
|
57
|
-
compaction: { enabled: agentConfig.compaction ?? true },
|
|
58
|
-
});
|
|
59
|
-
const sessionManager = SessionManager.create(cwd);
|
|
60
|
-
sessionManager.newSession({ parentSession: ctx.parentSessionFile });
|
|
61
|
-
const result = await createAgentSession({
|
|
62
|
-
cwd,
|
|
63
|
-
agentDir: ctx.agentDir,
|
|
64
|
-
model,
|
|
65
|
-
thinkingLevel: agentConfig.thinking,
|
|
66
|
-
tools,
|
|
67
|
-
resourceLoader,
|
|
68
|
-
sessionManager,
|
|
69
|
-
settingsManager,
|
|
70
|
-
authStorage,
|
|
71
|
-
modelRegistry,
|
|
72
|
-
});
|
|
73
|
-
return { session: result.session, warnings };
|
|
74
|
-
}
|
package/dist/index.d.ts
DELETED
package/dist/index.js
DELETED
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
import { dirname } from "node:path";
|
|
2
|
-
import { fileURLToPath } from "node:url";
|
|
3
|
-
import { crewRuntime, } from "./runtime/crew-runtime.js";
|
|
4
|
-
import { registerCrewIntegration } from "./integration.js";
|
|
5
|
-
import { updateWidget } from "./status-widget.js";
|
|
6
|
-
const extensionDir = dirname(fileURLToPath(import.meta.url));
|
|
7
|
-
// Process-level cleanup for subagents on exit
|
|
8
|
-
let processHooksSetup = false;
|
|
9
|
-
function setupProcessHooks() {
|
|
10
|
-
if (processHooksSetup)
|
|
11
|
-
return;
|
|
12
|
-
processHooksSetup = true;
|
|
13
|
-
process.once('SIGINT', () => {
|
|
14
|
-
crewRuntime.abortAll();
|
|
15
|
-
process.exit(130);
|
|
16
|
-
});
|
|
17
|
-
process.on('beforeExit', () => crewRuntime.abortAll());
|
|
18
|
-
}
|
|
19
|
-
export default function (pi) {
|
|
20
|
-
let currentCtx;
|
|
21
|
-
setupProcessHooks();
|
|
22
|
-
const refreshWidget = () => {
|
|
23
|
-
if (currentCtx)
|
|
24
|
-
updateWidget(currentCtx, crewRuntime);
|
|
25
|
-
};
|
|
26
|
-
const activateSession = (ctx) => {
|
|
27
|
-
currentCtx = ctx;
|
|
28
|
-
crewRuntime.activateSession({
|
|
29
|
-
sessionId: ctx.sessionManager.getSessionId(),
|
|
30
|
-
isIdle: () => ctx.isIdle(),
|
|
31
|
-
sendMessage: pi.sendMessage.bind(pi),
|
|
32
|
-
}, refreshWidget);
|
|
33
|
-
refreshWidget();
|
|
34
|
-
};
|
|
35
|
-
pi.on("session_start", (_event, ctx) => {
|
|
36
|
-
activateSession(ctx);
|
|
37
|
-
});
|
|
38
|
-
pi.on("session_shutdown", (event, ctx) => {
|
|
39
|
-
const sessionId = ctx.sessionManager.getSessionId();
|
|
40
|
-
crewRuntime.deactivateSession(sessionId);
|
|
41
|
-
if (event.reason === "quit") {
|
|
42
|
-
crewRuntime.abortAll();
|
|
43
|
-
}
|
|
44
|
-
});
|
|
45
|
-
registerCrewIntegration(pi, crewRuntime, extensionDir);
|
|
46
|
-
}
|