@matyah00/openpi 0.1.4 → 0.1.5
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/extensions/agent-chain.ts +45 -47
- package/extensions/agent-team.ts +32 -28
- package/extensions/damage-control-continue.ts +2 -2
- package/extensions/minimal.ts +3 -3
- package/extensions/openpi.ts +1 -1
- package/extensions/pure-focus.ts +1 -1
- package/extensions/purpose-gate.ts +2 -2
- package/extensions/theme-cycler.ts +2 -2
- package/extensions/themeMap.ts +1 -1
- package/extensions/tool-counter-widget.ts +2 -2
- package/extensions/tool-counter.ts +3 -3
- package/package.json +8 -1
- package/scripts/validate-package.mjs +100 -0
- package/types/pi-shims.d.ts +0 -48
|
@@ -1,10 +1,13 @@
|
|
|
1
|
-
import type { ExtensionAPI } from "@
|
|
2
|
-
import { Type } from "
|
|
3
|
-
import { Text } from "@
|
|
1
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import { Type } from "typebox";
|
|
3
|
+
import { Text } from "@earendil-works/pi-tui";
|
|
4
4
|
import { spawn } from "child_process";
|
|
5
|
-
import { existsSync, mkdirSync, readdirSync, readFileSync, unlinkSync } from "fs";
|
|
5
|
+
import { existsSync, mkdirSync, mkdtempSync, readdirSync, readFileSync, rmSync, unlinkSync, writeFileSync } from "fs";
|
|
6
|
+
import { tmpdir } from "os";
|
|
6
7
|
import { join, resolve } from "path";
|
|
8
|
+
import { parse as parseYaml } from "yaml";
|
|
7
9
|
import { bundledAgentsDir } from "./lib/packagePaths.ts";
|
|
10
|
+
import { arrayField, parseMarkdownFrontmatter, stringField } from "./lib/markdown.ts";
|
|
8
11
|
|
|
9
12
|
type ChainStep = { agent: string; prompt: string };
|
|
10
13
|
type ChainDef = { name: string; description: string; steps: ChainStep[] };
|
|
@@ -16,57 +19,48 @@ function displayName(name: string): string {
|
|
|
16
19
|
}
|
|
17
20
|
|
|
18
21
|
function parseChainYaml(raw: string): ChainDef[] {
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
let currentStep: ChainStep | null = null;
|
|
22
|
+
const parsed = parseYaml(raw);
|
|
23
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return [];
|
|
22
24
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
if (
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
currentStep = { agent: agentMatch[1].trim(), prompt: "" };
|
|
41
|
-
continue;
|
|
42
|
-
}
|
|
43
|
-
const promptMatch = line.match(/^\s+prompt:\s+(.+)$/);
|
|
44
|
-
if (promptMatch && currentStep) {
|
|
45
|
-
currentStep.prompt = promptMatch[1].trim().replace(/^["']|["']$/g, "").replace(/\\n/g, "\n");
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
if (current && currentStep) current.steps.push(currentStep);
|
|
49
|
-
return chains;
|
|
25
|
+
return Object.entries(parsed as Record<string, unknown>).flatMap(([name, value]) => {
|
|
26
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return [];
|
|
27
|
+
const chain = value as { description?: unknown; steps?: unknown };
|
|
28
|
+
if (!Array.isArray(chain.steps)) return [];
|
|
29
|
+
const steps = chain.steps.flatMap((step): ChainStep[] => {
|
|
30
|
+
if (!step || typeof step !== "object" || Array.isArray(step)) return [];
|
|
31
|
+
const item = step as { agent?: unknown; prompt?: unknown };
|
|
32
|
+
if (typeof item.agent !== "string" || typeof item.prompt !== "string") return [];
|
|
33
|
+
return [{ agent: item.agent, prompt: item.prompt }];
|
|
34
|
+
});
|
|
35
|
+
if (!steps.length) return [];
|
|
36
|
+
return [{
|
|
37
|
+
name,
|
|
38
|
+
description: typeof chain.description === "string" ? chain.description : "",
|
|
39
|
+
steps,
|
|
40
|
+
}];
|
|
41
|
+
});
|
|
50
42
|
}
|
|
51
43
|
|
|
52
44
|
function parseAgentFile(filePath: string): AgentDef | null {
|
|
53
45
|
const raw = readFileSync(filePath, "utf-8");
|
|
54
|
-
const
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
for (const line of match[1].split("\n")) {
|
|
58
|
-
const idx = line.indexOf(":");
|
|
59
|
-
if (idx > 0) frontmatter[line.slice(0, idx).trim()] = line.slice(idx + 1).trim();
|
|
60
|
-
}
|
|
61
|
-
if (!frontmatter.name) return null;
|
|
46
|
+
const { frontmatter, body } = parseMarkdownFrontmatter(raw);
|
|
47
|
+
const name = stringField(frontmatter.name);
|
|
48
|
+
if (!name) return null;
|
|
62
49
|
return {
|
|
63
|
-
name
|
|
64
|
-
description: frontmatter.description
|
|
65
|
-
tools: frontmatter.tools || "read,grep,find,ls",
|
|
66
|
-
systemPrompt:
|
|
50
|
+
name,
|
|
51
|
+
description: stringField(frontmatter.description),
|
|
52
|
+
tools: arrayField(frontmatter.tools).join(",") || "read,grep,find,ls",
|
|
53
|
+
systemPrompt: body,
|
|
67
54
|
};
|
|
68
55
|
}
|
|
69
56
|
|
|
57
|
+
function writeSystemPromptFile(agentName: string, systemPrompt: string): { dir: string; filePath: string } {
|
|
58
|
+
const dir = mkdtempSync(join(tmpdir(), "openpi-chain-"));
|
|
59
|
+
const filePath = join(dir, `system-${agentName.replace(/[^\w.-]+/g, "_")}.md`);
|
|
60
|
+
writeFileSync(filePath, systemPrompt, { encoding: "utf-8", mode: 0o600 });
|
|
61
|
+
return { dir, filePath };
|
|
62
|
+
}
|
|
63
|
+
|
|
70
64
|
function scanAgentDirs(cwd: string): Map<string, AgentDef> {
|
|
71
65
|
const dirs = [join(cwd, ".pi", "agents"), join(cwd, "agents"), bundledAgentsDir];
|
|
72
66
|
const agents = new Map<string, AgentDef>();
|
|
@@ -125,13 +119,14 @@ export default function (pi: ExtensionAPI) {
|
|
|
125
119
|
const model = ctx.model ? `${ctx.model.provider}/${ctx.model.id}` : "";
|
|
126
120
|
const agentKey = agentDef.name.toLowerCase().replace(/\s+/g, "-");
|
|
127
121
|
const agentSessionFile = join(sessionDir, `chain-${agentKey}.json`);
|
|
122
|
+
const systemPrompt = writeSystemPromptFile(agentKey, agentDef.systemPrompt);
|
|
128
123
|
const args = [
|
|
129
124
|
"--mode", "json",
|
|
130
125
|
"-p",
|
|
131
126
|
"--no-extensions",
|
|
132
127
|
"--tools", agentDef.tools,
|
|
133
128
|
"--thinking", "off",
|
|
134
|
-
"--append-system-prompt",
|
|
129
|
+
"--append-system-prompt", systemPrompt.filePath,
|
|
135
130
|
"--session", agentSessionFile,
|
|
136
131
|
];
|
|
137
132
|
if (model) args.splice(4, 0, "--model", model);
|
|
@@ -143,6 +138,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
143
138
|
const chunks: string[] = [];
|
|
144
139
|
|
|
145
140
|
return new Promise((resolveDone) => {
|
|
141
|
+
const cleanupPrompt = () => rmSync(systemPrompt.dir, { recursive: true, force: true });
|
|
146
142
|
const proc = spawn("pi", args, { stdio: ["ignore", "pipe", "pipe"], env: { ...process.env } });
|
|
147
143
|
const timer = setInterval(() => {
|
|
148
144
|
state.elapsed = Date.now() - start;
|
|
@@ -171,12 +167,14 @@ export default function (pi: ExtensionAPI) {
|
|
|
171
167
|
proc.stderr!.on("data", () => {});
|
|
172
168
|
proc.on("close", (code) => {
|
|
173
169
|
clearInterval(timer);
|
|
170
|
+
cleanupPrompt();
|
|
174
171
|
state.elapsed = Date.now() - start;
|
|
175
172
|
if (code === 0) agentSessions.set(agentKey, agentSessionFile);
|
|
176
173
|
resolveDone({ output: chunks.join(""), exitCode: code ?? 1, elapsed: state.elapsed });
|
|
177
174
|
});
|
|
178
175
|
proc.on("error", (error) => {
|
|
179
176
|
clearInterval(timer);
|
|
177
|
+
cleanupPrompt();
|
|
180
178
|
resolveDone({ output: `Error spawning agent: ${error.message}`, exitCode: 1, elapsed: Date.now() - start });
|
|
181
179
|
});
|
|
182
180
|
});
|
package/extensions/agent-team.ts
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
|
-
import type { ExtensionAPI } from "@
|
|
2
|
-
import { Type } from "
|
|
3
|
-
import { Text } from "@
|
|
1
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import { Type } from "typebox";
|
|
3
|
+
import { Text } from "@earendil-works/pi-tui";
|
|
4
4
|
import { spawn } from "child_process";
|
|
5
|
-
import { existsSync, mkdirSync, readdirSync, readFileSync, unlinkSync } from "fs";
|
|
5
|
+
import { existsSync, mkdirSync, mkdtempSync, readdirSync, readFileSync, rmSync, unlinkSync, writeFileSync } from "fs";
|
|
6
|
+
import { tmpdir } from "os";
|
|
6
7
|
import { join, resolve } from "path";
|
|
8
|
+
import { parse as parseYaml } from "yaml";
|
|
7
9
|
import { bundledAgentsDir } from "./lib/packagePaths.ts";
|
|
10
|
+
import { arrayField, parseMarkdownFrontmatter, stringField } from "./lib/markdown.ts";
|
|
8
11
|
|
|
9
12
|
type AgentDef = {
|
|
10
13
|
name: string;
|
|
@@ -28,42 +31,39 @@ function displayName(name: string): string {
|
|
|
28
31
|
}
|
|
29
32
|
|
|
30
33
|
function parseTeamsYaml(raw: string): Record<string, string[]> {
|
|
34
|
+
const parsed = parseYaml(raw);
|
|
35
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return {};
|
|
36
|
+
|
|
31
37
|
const teams: Record<string, string[]> = {};
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
if (teamMatch) {
|
|
36
|
-
current = teamMatch[1].trim();
|
|
37
|
-
teams[current] = [];
|
|
38
|
-
continue;
|
|
39
|
-
}
|
|
40
|
-
const itemMatch = line.match(/^\s+-\s+(.+)$/);
|
|
41
|
-
if (itemMatch && current) teams[current].push(itemMatch[1].trim());
|
|
38
|
+
for (const [name, members] of Object.entries(parsed as Record<string, unknown>)) {
|
|
39
|
+
if (!Array.isArray(members)) continue;
|
|
40
|
+
teams[name] = members.filter((member): member is string => typeof member === "string");
|
|
42
41
|
}
|
|
43
42
|
return teams;
|
|
44
43
|
}
|
|
45
44
|
|
|
46
45
|
function parseAgentFile(filePath: string): AgentDef | null {
|
|
47
46
|
const raw = readFileSync(filePath, "utf-8");
|
|
48
|
-
const
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
const frontmatter: Record<string, string> = {};
|
|
52
|
-
for (const line of match[1].split("\n")) {
|
|
53
|
-
const idx = line.indexOf(":");
|
|
54
|
-
if (idx > 0) frontmatter[line.slice(0, idx).trim()] = line.slice(idx + 1).trim();
|
|
55
|
-
}
|
|
56
|
-
if (!frontmatter.name) return null;
|
|
47
|
+
const { frontmatter, body } = parseMarkdownFrontmatter(raw);
|
|
48
|
+
const name = stringField(frontmatter.name);
|
|
49
|
+
if (!name) return null;
|
|
57
50
|
|
|
58
51
|
return {
|
|
59
|
-
name
|
|
60
|
-
description: frontmatter.description
|
|
61
|
-
tools: frontmatter.tools || "read,grep,find,ls",
|
|
62
|
-
systemPrompt:
|
|
52
|
+
name,
|
|
53
|
+
description: stringField(frontmatter.description),
|
|
54
|
+
tools: arrayField(frontmatter.tools).join(",") || "read,grep,find,ls",
|
|
55
|
+
systemPrompt: body,
|
|
63
56
|
file: filePath,
|
|
64
57
|
};
|
|
65
58
|
}
|
|
66
59
|
|
|
60
|
+
function writeSystemPromptFile(agentKey: string, systemPrompt: string): { dir: string; filePath: string } {
|
|
61
|
+
const dir = mkdtempSync(join(tmpdir(), "openpi-team-"));
|
|
62
|
+
const filePath = join(dir, `system-${agentKey.replace(/[^\w.-]+/g, "_")}.md`);
|
|
63
|
+
writeFileSync(filePath, systemPrompt, { encoding: "utf-8", mode: 0o600 });
|
|
64
|
+
return { dir, filePath };
|
|
65
|
+
}
|
|
66
|
+
|
|
67
67
|
function scanAgentDirs(cwd: string): AgentDef[] {
|
|
68
68
|
const dirs = [join(cwd, ".pi", "agents"), join(cwd, "agents"), bundledAgentsDir];
|
|
69
69
|
const agents: AgentDef[] = [];
|
|
@@ -161,13 +161,14 @@ export default function (pi: ExtensionAPI) {
|
|
|
161
161
|
const model = ctx.model ? `${ctx.model.provider}/${ctx.model.id}` : "";
|
|
162
162
|
const agentKey = state.def.name.toLowerCase().replace(/\s+/g, "-");
|
|
163
163
|
const agentSessionFile = join(sessionDir, `${agentKey}.json`);
|
|
164
|
+
const systemPrompt = writeSystemPromptFile(agentKey, state.def.systemPrompt);
|
|
164
165
|
const args = [
|
|
165
166
|
"--mode", "json",
|
|
166
167
|
"-p",
|
|
167
168
|
"--no-extensions",
|
|
168
169
|
"--tools", state.def.tools,
|
|
169
170
|
"--thinking", "off",
|
|
170
|
-
"--append-system-prompt",
|
|
171
|
+
"--append-system-prompt", systemPrompt.filePath,
|
|
171
172
|
"--session", agentSessionFile,
|
|
172
173
|
];
|
|
173
174
|
if (model) args.splice(4, 0, "--model", model);
|
|
@@ -178,6 +179,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
178
179
|
const chunks: string[] = [];
|
|
179
180
|
|
|
180
181
|
return new Promise((resolveDone) => {
|
|
182
|
+
const cleanupPrompt = () => rmSync(systemPrompt.dir, { recursive: true, force: true });
|
|
181
183
|
const proc = spawn("pi", args, { stdio: ["ignore", "pipe", "pipe"], env: { ...process.env } });
|
|
182
184
|
const timer = setInterval(() => {
|
|
183
185
|
state.elapsed = Date.now() - start;
|
|
@@ -206,6 +208,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
206
208
|
proc.stderr!.on("data", () => {});
|
|
207
209
|
proc.on("close", (code) => {
|
|
208
210
|
clearInterval(timer);
|
|
211
|
+
cleanupPrompt();
|
|
209
212
|
state.elapsed = Date.now() - start;
|
|
210
213
|
state.status = code === 0 ? "done" : "error";
|
|
211
214
|
if (code === 0) state.sessionFile = agentSessionFile;
|
|
@@ -214,6 +217,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
214
217
|
});
|
|
215
218
|
proc.on("error", (error) => {
|
|
216
219
|
clearInterval(timer);
|
|
220
|
+
cleanupPrompt();
|
|
217
221
|
state.status = "error";
|
|
218
222
|
state.lastWork = error.message;
|
|
219
223
|
updateWidget();
|
|
@@ -12,8 +12,8 @@
|
|
|
12
12
|
* Usage: pi -e extensions/damage-control-continue.ts
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
|
-
import type { ExtensionAPI } from "@
|
|
16
|
-
import { isToolCallEventType } from "@
|
|
15
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
16
|
+
import { isToolCallEventType } from "@earendil-works/pi-coding-agent";
|
|
17
17
|
import { parse as yamlParse } from "yaml";
|
|
18
18
|
import * as fs from "fs";
|
|
19
19
|
import * as path from "path";
|
package/extensions/minimal.ts
CHANGED
|
@@ -6,9 +6,9 @@
|
|
|
6
6
|
* Usage: pi -e extensions/minimal.ts
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import type { ExtensionAPI } from "@
|
|
9
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
10
10
|
import { applyExtensionDefaults } from "./themeMap.ts";
|
|
11
|
-
import { truncateToWidth, visibleWidth } from "@
|
|
11
|
+
import { truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
|
|
12
12
|
|
|
13
13
|
export default function (pi: ExtensionAPI) {
|
|
14
14
|
pi.on("session_start", async (_event, ctx) => {
|
|
@@ -31,4 +31,4 @@ export default function (pi: ExtensionAPI) {
|
|
|
31
31
|
},
|
|
32
32
|
}));
|
|
33
33
|
});
|
|
34
|
-
}
|
|
34
|
+
}
|
package/extensions/openpi.ts
CHANGED
package/extensions/pure-focus.ts
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* Usage: pi -e examples/extensions/pure-focus.ts
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import type { ExtensionAPI } from "@
|
|
10
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
11
11
|
import { applyExtensionDefaults } from "./themeMap.ts";
|
|
12
12
|
|
|
13
13
|
export default function (pi: ExtensionAPI) {
|
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
* Usage: pi -e extensions/purpose-gate.ts
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
import type { ExtensionAPI } from "@
|
|
12
|
-
import { Text, truncateToWidth } from "@
|
|
11
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
12
|
+
import { Text, truncateToWidth } from "@earendil-works/pi-tui";
|
|
13
13
|
import { applyExtensionDefaults } from "./themeMap.ts";
|
|
14
14
|
|
|
15
15
|
// synthwave: bgWarm #4a1e6a → rgb(74,30,106)
|
|
@@ -17,8 +17,8 @@
|
|
|
17
17
|
* Usage: pi -e extensions/theme-cycler.ts -e extensions/minimal.ts
|
|
18
18
|
*/
|
|
19
19
|
|
|
20
|
-
import type { ExtensionAPI, ExtensionContext } from "@
|
|
21
|
-
import { truncateToWidth } from "@
|
|
20
|
+
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
21
|
+
import { truncateToWidth } from "@earendil-works/pi-tui";
|
|
22
22
|
import { applyExtensionDefaults } from "./themeMap.ts";
|
|
23
23
|
|
|
24
24
|
export default function (pi: ExtensionAPI) {
|
package/extensions/themeMap.ts
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* synthwave · tokyo-night
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
|
-
import type { ExtensionContext } from "@
|
|
14
|
+
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
15
15
|
import { basename } from "path";
|
|
16
16
|
import { fileURLToPath } from "url";
|
|
17
17
|
|
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
* Usage: pi -e extensions/tool-counter-widget.ts
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import type { ExtensionAPI } from "@
|
|
11
|
-
import { Box, Text } from "@
|
|
10
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
11
|
+
import { Box, Text } from "@earendil-works/pi-tui";
|
|
12
12
|
import { applyExtensionDefaults } from "./themeMap.ts";
|
|
13
13
|
|
|
14
14
|
const palette = [
|
|
@@ -10,9 +10,9 @@
|
|
|
10
10
|
* Usage: pi -e extensions/tool-counter.ts
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
import type { AssistantMessage } from "@
|
|
14
|
-
import type { ExtensionAPI } from "@
|
|
15
|
-
import { truncateToWidth, visibleWidth } from "@
|
|
13
|
+
import type { AssistantMessage } from "@earendil-works/pi-ai";
|
|
14
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
15
|
+
import { truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
|
|
16
16
|
import { basename } from "node:path";
|
|
17
17
|
import { applyExtensionDefaults } from "./themeMap.ts";
|
|
18
18
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@matyah00/openpi",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Pi-native commands, skills, agents, and workflows.",
|
|
6
6
|
"keywords": [
|
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
"agents",
|
|
25
25
|
"extensions",
|
|
26
26
|
"prompts",
|
|
27
|
+
"scripts",
|
|
27
28
|
"skills",
|
|
28
29
|
"themes",
|
|
29
30
|
"types",
|
|
@@ -35,6 +36,12 @@
|
|
|
35
36
|
"publishConfig": {
|
|
36
37
|
"access": "public"
|
|
37
38
|
},
|
|
39
|
+
"scripts": {
|
|
40
|
+
"check": "npm run validate && npm run typecheck && npm run pack:check",
|
|
41
|
+
"pack:check": "npm pack --dry-run",
|
|
42
|
+
"typecheck": "tsc --noEmit",
|
|
43
|
+
"validate": "node scripts/validate-package.mjs"
|
|
44
|
+
},
|
|
38
45
|
"dependencies": {
|
|
39
46
|
"yaml": "^2.8.0"
|
|
40
47
|
},
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { existsSync, readdirSync, readFileSync } from "node:fs";
|
|
2
|
+
import { basename, join, relative, resolve } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { parse as parseYaml } from "yaml";
|
|
5
|
+
|
|
6
|
+
const root = resolve(fileURLToPath(new URL("..", import.meta.url)));
|
|
7
|
+
const failures = [];
|
|
8
|
+
|
|
9
|
+
function fail(message) {
|
|
10
|
+
failures.push(message);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function read(path) {
|
|
14
|
+
return readFileSync(join(root, path), "utf-8");
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function walk(dir, predicate = () => true) {
|
|
18
|
+
const absolute = join(root, dir);
|
|
19
|
+
if (!existsSync(absolute)) return [];
|
|
20
|
+
const files = [];
|
|
21
|
+
for (const entry of readdirSync(absolute, { withFileTypes: true })) {
|
|
22
|
+
const full = join(absolute, entry.name);
|
|
23
|
+
if (entry.isDirectory()) files.push(...walk(relative(root, full), predicate));
|
|
24
|
+
else if (predicate(entry.name)) files.push(relative(root, full).replace(/\\/g, "/"));
|
|
25
|
+
}
|
|
26
|
+
return files;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function parseMarkdown(file) {
|
|
30
|
+
const raw = read(file);
|
|
31
|
+
const match = raw.match(/^---\s*\r?\n([\s\S]*?)\r?\n---\s*\r?\n([\s\S]*)$/);
|
|
32
|
+
if (!match) return { frontmatter: {}, body: raw };
|
|
33
|
+
const frontmatter = parseYaml(match[1]) ?? {};
|
|
34
|
+
if (!frontmatter || typeof frontmatter !== "object" || Array.isArray(frontmatter)) {
|
|
35
|
+
fail(`${file}: frontmatter must be a mapping`);
|
|
36
|
+
return { frontmatter: {}, body: match[2] };
|
|
37
|
+
}
|
|
38
|
+
return { frontmatter, body: match[2] };
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
for (const file of walk("extensions", (name) => name.endsWith(".ts"))) {
|
|
42
|
+
const content = read(file);
|
|
43
|
+
if (content.includes("@mariozechner/")) fail(`${file}: uses old @mariozechner namespace`);
|
|
44
|
+
if (content.includes("@sinclair/typebox")) fail(`${file}: uses @sinclair/typebox instead of typebox`);
|
|
45
|
+
if (/--append-system-prompt",\s*(state\.def|agentDef)\.systemPrompt/.test(content)) {
|
|
46
|
+
fail(`${file}: passes raw system prompt instead of a temp file`);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const promptNames = new Map();
|
|
51
|
+
for (const file of walk("prompts", (name) => name.endsWith(".md"))) {
|
|
52
|
+
const { frontmatter, body } = parseMarkdown(file);
|
|
53
|
+
const name = typeof frontmatter.name === "string" ? frontmatter.name : basename(file, ".md");
|
|
54
|
+
if (!body.trim()) fail(`${file}: prompt body is empty`);
|
|
55
|
+
if (promptNames.has(name)) fail(`${file}: duplicate prompt name "${name}" also used by ${promptNames.get(name)}`);
|
|
56
|
+
promptNames.set(name, file);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const agentNames = new Set();
|
|
60
|
+
for (const file of walk("agents", (name) => name.endsWith(".md"))) {
|
|
61
|
+
const { frontmatter, body } = parseMarkdown(file);
|
|
62
|
+
if (typeof frontmatter.name !== "string" || !frontmatter.name.trim()) fail(`${file}: missing agent name`);
|
|
63
|
+
if (typeof frontmatter.description !== "string" || !frontmatter.description.trim()) fail(`${file}: missing agent description`);
|
|
64
|
+
if (!body.trim()) fail(`${file}: agent body is empty`);
|
|
65
|
+
if (typeof frontmatter.name === "string") agentNames.add(frontmatter.name);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const teams = parseYaml(read("agents/teams.yaml")) ?? {};
|
|
69
|
+
for (const [team, members] of Object.entries(teams)) {
|
|
70
|
+
if (!Array.isArray(members)) {
|
|
71
|
+
fail(`agents/teams.yaml: ${team} must be a list`);
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
for (const member of members) {
|
|
75
|
+
if (!agentNames.has(member)) fail(`agents/teams.yaml: ${team} references unknown agent ${member}`);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const chains = parseYaml(read("agents/agent-chain.yaml")) ?? {};
|
|
80
|
+
for (const [chainName, chain] of Object.entries(chains)) {
|
|
81
|
+
if (!chain || typeof chain !== "object" || Array.isArray(chain) || !Array.isArray(chain.steps)) {
|
|
82
|
+
fail(`agents/agent-chain.yaml: ${chainName} must have steps`);
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
for (const [index, step] of chain.steps.entries()) {
|
|
86
|
+
if (!step || typeof step !== "object" || Array.isArray(step)) {
|
|
87
|
+
fail(`agents/agent-chain.yaml: ${chainName} step ${index + 1} must be a mapping`);
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
if (!agentNames.has(step.agent)) fail(`agents/agent-chain.yaml: ${chainName} references unknown agent ${step.agent}`);
|
|
91
|
+
if (typeof step.prompt !== "string" || !step.prompt.trim()) fail(`agents/agent-chain.yaml: ${chainName} step ${index + 1} missing prompt`);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (failures.length) {
|
|
96
|
+
console.error(failures.map((item) => `- ${item}`).join("\n"));
|
|
97
|
+
process.exit(1);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
console.log("openpi package validation passed");
|
package/types/pi-shims.d.ts
CHANGED
|
@@ -1,11 +1,3 @@
|
|
|
1
|
-
declare module "@mariozechner/pi-coding-agent" {
|
|
2
|
-
export type ExtensionContext = any;
|
|
3
|
-
export type ExtensionAPI = any;
|
|
4
|
-
export const DynamicBorder: any;
|
|
5
|
-
export function isToolCallEventType(...args: any[]): boolean;
|
|
6
|
-
export function getMarkdownTheme(...args: any[]): any;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
1
|
declare module "@earendil-works/pi-coding-agent" {
|
|
10
2
|
export type ExtensionContext = any;
|
|
11
3
|
export type ExtensionAPI = any;
|
|
@@ -17,36 +9,6 @@ declare module "@earendil-works/pi-coding-agent" {
|
|
|
17
9
|
export function withFileMutationQueue<T = any>(...args: any[]): Promise<T>;
|
|
18
10
|
}
|
|
19
11
|
|
|
20
|
-
declare module "@mariozechner/pi-tui" {
|
|
21
|
-
export class Text {
|
|
22
|
-
constructor(...args: any[]);
|
|
23
|
-
setText(...args: any[]): any;
|
|
24
|
-
render(...args: any[]): any;
|
|
25
|
-
invalidate(...args: any[]): any;
|
|
26
|
-
}
|
|
27
|
-
export class Box {
|
|
28
|
-
constructor(...args: any[]);
|
|
29
|
-
}
|
|
30
|
-
export class Markdown {
|
|
31
|
-
constructor(...args: any[]);
|
|
32
|
-
}
|
|
33
|
-
export class Container {
|
|
34
|
-
constructor(...args: any[]);
|
|
35
|
-
addChild(...args: any[]): any;
|
|
36
|
-
render(...args: any[]): any;
|
|
37
|
-
invalidate(...args: any[]): any;
|
|
38
|
-
}
|
|
39
|
-
export class Spacer {
|
|
40
|
-
constructor(...args: any[]);
|
|
41
|
-
}
|
|
42
|
-
export type AutocompleteItem = any;
|
|
43
|
-
export const Key: any;
|
|
44
|
-
export function matchesKey(...args: any[]): any;
|
|
45
|
-
export function truncateToWidth(...args: any[]): any;
|
|
46
|
-
export function visibleWidth(...args: any[]): any;
|
|
47
|
-
export function getMarkdownTheme(...args: any[]): any;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
12
|
declare module "@earendil-works/pi-tui" {
|
|
51
13
|
export class Text {
|
|
52
14
|
constructor(...args: any[]);
|
|
@@ -77,16 +39,6 @@ declare module "@earendil-works/pi-tui" {
|
|
|
77
39
|
export function getMarkdownTheme(...args: any[]): any;
|
|
78
40
|
}
|
|
79
41
|
|
|
80
|
-
declare module "@sinclair/typebox" {
|
|
81
|
-
export const Type: any;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
declare module "@mariozechner/pi-ai" {
|
|
85
|
-
export type AssistantMessage = any;
|
|
86
|
-
export type Message = any;
|
|
87
|
-
export function StringEnum(...args: any[]): any;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
42
|
declare module "@earendil-works/pi-ai" {
|
|
91
43
|
export type AssistantMessage = any;
|
|
92
44
|
export type Message = any;
|