@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/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
+ }