@fosterg4/pi-subagent 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.
- package/README.md +170 -0
- package/agents/planner.md +71 -0
- package/agents/reviewer.md +81 -0
- package/agents/scout.md +66 -0
- package/agents/worker.md +48 -0
- package/agents.ts +196 -0
- package/index.ts +1436 -0
- package/package.json +47 -0
- package/prompts/implement-and-review.md +10 -0
- package/prompts/implement.md +10 -0
- package/prompts/scout-and-plan.md +9 -0
- package/validate.ts +168 -0
package/package.json
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@fosterg4/pi-subagent",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Delegate tasks to specialized subagents with isolated context windows, structured JSON handoff, contract schemas, and live TUI streaming",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"pi-package",
|
|
7
|
+
"pi-subagent",
|
|
8
|
+
"subagent",
|
|
9
|
+
"agent",
|
|
10
|
+
"ai"
|
|
11
|
+
],
|
|
12
|
+
"license": "MIT",
|
|
13
|
+
"author": "fosterg4",
|
|
14
|
+
"repository": {
|
|
15
|
+
"type": "git",
|
|
16
|
+
"url": "git+https://github.com/fosterg4/pi-subagent.git"
|
|
17
|
+
},
|
|
18
|
+
"homepage": "https://github.com/fosterg4/pi-subagent#readme",
|
|
19
|
+
"bugs": {
|
|
20
|
+
"url": "https://github.com/fosterg4/pi-subagent/issues"
|
|
21
|
+
},
|
|
22
|
+
"files": [
|
|
23
|
+
"*.ts",
|
|
24
|
+
"agents/*.md",
|
|
25
|
+
"prompts/*.md"
|
|
26
|
+
],
|
|
27
|
+
"peerDependencies": {
|
|
28
|
+
"@earendil-works/pi-agent-core": "^0.79.3",
|
|
29
|
+
"@earendil-works/pi-ai": "^0.79.3",
|
|
30
|
+
"@earendil-works/pi-coding-agent": "^0.79.3",
|
|
31
|
+
"@earendil-works/pi-tui": "^0.79.3",
|
|
32
|
+
"typebox": "^1.2.10"
|
|
33
|
+
},
|
|
34
|
+
"pi": {
|
|
35
|
+
"extensions": [
|
|
36
|
+
"./index.ts"
|
|
37
|
+
],
|
|
38
|
+
"prompts": [
|
|
39
|
+
"./prompts"
|
|
40
|
+
]
|
|
41
|
+
},
|
|
42
|
+
"scripts": {
|
|
43
|
+
"test": "npx tsx tests/run.ts",
|
|
44
|
+
"test:validate": "npx tsx -e \"import { runValidateTests } from './tests/validate.test.ts'; runValidateTests();\"",
|
|
45
|
+
"test:agents": "npx tsx -e \"import { runAgentsTests } from './tests/agents.test.ts'; runAgentsTests();\""
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Worker implements, reviewer reviews, worker applies feedback
|
|
3
|
+
---
|
|
4
|
+
Use the subagent tool with the chain parameter to execute this workflow:
|
|
5
|
+
|
|
6
|
+
1. First, use the "worker" agent to implement: $@ — return structured results
|
|
7
|
+
2. Then, use the "reviewer" agent to review the implementation from the previous step — return structured review findings (use {previous} placeholder)
|
|
8
|
+
3. Finally, use the "worker" agent to apply the feedback from the review (use {previous} placeholder)
|
|
9
|
+
|
|
10
|
+
Execute this as a chain, passing structured output between steps via {previous}.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Scout investigates, planner creates plan, worker implements
|
|
3
|
+
---
|
|
4
|
+
Use the subagent tool with the chain parameter to execute this workflow:
|
|
5
|
+
|
|
6
|
+
1. First, use the "scout" agent to investigate: $@ — return structured findings
|
|
7
|
+
2. Then, use the "planner" agent to create an implementation plan from the scout's structured findings and the original requirements (use {previous} placeholder)
|
|
8
|
+
3. Finally, use the "worker" agent to implement the plan from the previous step (use {previous} placeholder)
|
|
9
|
+
|
|
10
|
+
Execute this as a chain, passing structured output between steps via {previous}.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Scout investigates, planner creates plan (no implementation)
|
|
3
|
+
---
|
|
4
|
+
Use the subagent tool with the chain parameter to execute this workflow:
|
|
5
|
+
|
|
6
|
+
1. First, use the "scout" agent to investigate: $@ — return structured findings
|
|
7
|
+
2. Then, use the "planner" agent to create an implementation plan from the scout's structured findings (use {previous} placeholder)
|
|
8
|
+
|
|
9
|
+
Execute this as a chain, passing structured output between steps via {previous}. Do NOT implement — only scout and plan.
|
package/validate.ts
ADDED
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal JSON Schema validator for contract schemas.
|
|
3
|
+
*
|
|
4
|
+
* Validates structured data against inputSchema/outputSchema defined
|
|
5
|
+
* in agent frontmatter. Supports: required fields, property types,
|
|
6
|
+
* enums, nested objects, and arrays.
|
|
7
|
+
*
|
|
8
|
+
* No external dependencies — lightweight ~60 lines.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
export interface ValidationResult {
|
|
12
|
+
valid: boolean;
|
|
13
|
+
errors?: string[];
|
|
14
|
+
data?: Record<string, unknown>;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Validate a value against a JSON Schema-like object.
|
|
19
|
+
*/
|
|
20
|
+
export function validateSchema(
|
|
21
|
+
data: unknown,
|
|
22
|
+
schema: Record<string, unknown>,
|
|
23
|
+
): ValidationResult {
|
|
24
|
+
const errors: string[] = [];
|
|
25
|
+
|
|
26
|
+
if (typeof schema !== "object" || schema === null) {
|
|
27
|
+
return { valid: true };
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (typeof data !== "object" || data === null) {
|
|
31
|
+
errors.push("Expected an object, got " + typeof data);
|
|
32
|
+
return { valid: false, errors };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const input = data as Record<string, unknown>;
|
|
36
|
+
const schemaType = schema.type as string | undefined;
|
|
37
|
+
|
|
38
|
+
// Basic type check
|
|
39
|
+
if (schemaType && schemaType !== "object") {
|
|
40
|
+
errors.push(`Expected schema type "object", got "${schemaType}"`);
|
|
41
|
+
return { valid: false, errors };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const properties = schema.properties as Record<string, unknown> | undefined;
|
|
45
|
+
const required = (schema.required as string[]) || [];
|
|
46
|
+
const additionalProperties = schema.additionalProperties as boolean | undefined;
|
|
47
|
+
|
|
48
|
+
// Check required fields
|
|
49
|
+
for (const field of required) {
|
|
50
|
+
if (!(field in input) || input[field] === undefined || input[field] === null) {
|
|
51
|
+
errors.push(`Missing required field: "${field}"`);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Validate property types
|
|
56
|
+
if (properties) {
|
|
57
|
+
for (const [key, propSchema] of Object.entries(properties)) {
|
|
58
|
+
if (!(key in input) || input[key] === undefined || input[key] === null) continue;
|
|
59
|
+
|
|
60
|
+
const prop = propSchema as Record<string, unknown>;
|
|
61
|
+
const value = input[key];
|
|
62
|
+
const propType = prop.type as string | undefined;
|
|
63
|
+
|
|
64
|
+
if (propType) {
|
|
65
|
+
const typeError = validateType(value, propType, key);
|
|
66
|
+
if (typeError) {
|
|
67
|
+
errors.push(typeError);
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Enum validation
|
|
73
|
+
const enumValues = prop.enum as unknown[] | undefined;
|
|
74
|
+
if (enumValues && !enumValues.includes(value)) {
|
|
75
|
+
errors.push(
|
|
76
|
+
`Field "${key}": must be one of [${enumValues.map((e) => JSON.stringify(e)).join(", ")}], got ${JSON.stringify(value)}`,
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Nested objects
|
|
81
|
+
if (propType === "object" && prop.properties) {
|
|
82
|
+
const nested = validateSchema(value, prop as Record<string, unknown>);
|
|
83
|
+
if (!nested.valid && nested.errors) {
|
|
84
|
+
errors.push(...nested.errors.map((e) => `${key}.${e}`));
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Array items validation
|
|
89
|
+
if (propType === "array" && prop.items) {
|
|
90
|
+
if (!Array.isArray(value)) {
|
|
91
|
+
errors.push(`Field "${key}": expected array, got ${typeof value}`);
|
|
92
|
+
} else {
|
|
93
|
+
const itemsSchema = prop.items as Record<string, unknown>;
|
|
94
|
+
const itemType = itemsSchema.type as string | undefined;
|
|
95
|
+
|
|
96
|
+
for (let i = 0; i < (value as unknown[]).length; i++) {
|
|
97
|
+
const item = (value as unknown[])[i];
|
|
98
|
+
if (itemType) {
|
|
99
|
+
const itemError = validateType(item, itemType, `${key}[${i}]`);
|
|
100
|
+
if (itemError) errors.push(itemError);
|
|
101
|
+
}
|
|
102
|
+
// Nested object items
|
|
103
|
+
if (itemType === "object" && itemsSchema.properties) {
|
|
104
|
+
const nested = validateSchema(
|
|
105
|
+
item,
|
|
106
|
+
itemsSchema as Record<string, unknown>,
|
|
107
|
+
);
|
|
108
|
+
if (!nested.valid && nested.errors) {
|
|
109
|
+
errors.push(...nested.errors);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Check for unexpected properties when additionalProperties is false
|
|
119
|
+
if (additionalProperties === false && properties) {
|
|
120
|
+
const allowedKeys = new Set(Object.keys(properties));
|
|
121
|
+
for (const key of Object.keys(input)) {
|
|
122
|
+
if (!allowedKeys.has(key)) {
|
|
123
|
+
errors.push(`Unexpected field: "${key}"`);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (errors.length > 0) {
|
|
129
|
+
return { valid: false, errors };
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return { valid: true, data: input };
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function validateType(value: unknown, type: string, path: string): string | null {
|
|
136
|
+
switch (type) {
|
|
137
|
+
case "string":
|
|
138
|
+
if (typeof value !== "string") {
|
|
139
|
+
return `Field "${path}": expected string, got ${typeof value}`;
|
|
140
|
+
}
|
|
141
|
+
break;
|
|
142
|
+
case "number":
|
|
143
|
+
if (typeof value !== "number") {
|
|
144
|
+
// Coerce string to number
|
|
145
|
+
if (typeof value === "string" && !isNaN(Number(value))) {
|
|
146
|
+
return null; // coercion allowed
|
|
147
|
+
}
|
|
148
|
+
return `Field "${path}": expected number, got ${typeof value}`;
|
|
149
|
+
}
|
|
150
|
+
break;
|
|
151
|
+
case "boolean":
|
|
152
|
+
if (typeof value !== "boolean") {
|
|
153
|
+
return `Field "${path}": expected boolean, got ${typeof value}`;
|
|
154
|
+
}
|
|
155
|
+
break;
|
|
156
|
+
case "array":
|
|
157
|
+
if (!Array.isArray(value)) {
|
|
158
|
+
return `Field "${path}": expected array, got ${typeof value}`;
|
|
159
|
+
}
|
|
160
|
+
break;
|
|
161
|
+
case "object":
|
|
162
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
163
|
+
return `Field "${path}": expected object, got ${typeof value}`;
|
|
164
|
+
}
|
|
165
|
+
break;
|
|
166
|
+
}
|
|
167
|
+
return null;
|
|
168
|
+
}
|