@bacnh85/pi-subagent 0.5.0 → 0.6.1
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/CHANGELOG.md +56 -0
- package/README.md +127 -4
- package/agent-format.md +12 -2
- package/extensions/agents.ts +90 -10
- package/extensions/index.ts +264 -215
- package/extensions/render.ts +7 -7
- package/extensions/runner.ts +88 -45
- package/extensions/security.ts +504 -0
- package/extensions/service.ts +29 -18
- package/extensions/thread-viewer.ts +2 -126
- package/extensions/threads.ts +1 -11
- package/package.json +26 -11
|
@@ -7,134 +7,10 @@
|
|
|
7
7
|
|
|
8
8
|
import { getMarkdownTheme } from "@earendil-works/pi-coding-agent";
|
|
9
9
|
import { matchesKey, Key, truncateToWidth, Markdown } from "@earendil-works/pi-tui";
|
|
10
|
-
import type { Message } from "@earendil-works/pi-ai";
|
|
11
10
|
|
|
12
|
-
import {
|
|
13
|
-
import { formatUsageStats } from "./render.ts";
|
|
11
|
+
import { isFailedResult, getFinalOutput } from "./runner.ts";
|
|
12
|
+
import { formatToolCall, getDisplayItems, formatUsageStats } from "./render.ts";
|
|
14
13
|
import type { SubagentThread } from "./threads.ts";
|
|
15
|
-
import * as os from "node:os";
|
|
16
|
-
|
|
17
|
-
// ---------------------------------------------------------------------------
|
|
18
|
-
// Safe type guards for tool-call arguments
|
|
19
|
-
// ---------------------------------------------------------------------------
|
|
20
|
-
|
|
21
|
-
function asString(value: unknown, fallback = "..."): string {
|
|
22
|
-
return typeof value === "string" ? value : fallback;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
function asNumber(value: unknown): number | undefined {
|
|
26
|
-
return typeof value === "number" && Number.isFinite(value) ? value : undefined;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
function asRecord(value: unknown): Record<string, unknown> {
|
|
30
|
-
return value && typeof value === "object" && !Array.isArray(value)
|
|
31
|
-
? (value as Record<string, unknown>)
|
|
32
|
-
: {};
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
// ---------------------------------------------------------------------------
|
|
36
|
-
// Tool-call formatting (same as render.ts)
|
|
37
|
-
// ---------------------------------------------------------------------------
|
|
38
|
-
|
|
39
|
-
function formatToolCall(
|
|
40
|
-
toolName: string,
|
|
41
|
-
args: Record<string, unknown>,
|
|
42
|
-
themeFg: (color: string, text: string) => string,
|
|
43
|
-
): string {
|
|
44
|
-
const shortenPath = (p: string) => {
|
|
45
|
-
const home = os.homedir();
|
|
46
|
-
return p.startsWith(home) ? `~${p.slice(home.length)}` : p;
|
|
47
|
-
};
|
|
48
|
-
|
|
49
|
-
switch (toolName) {
|
|
50
|
-
case "bash": {
|
|
51
|
-
const command = asString(args.command);
|
|
52
|
-
const preview = command.length > 60 ? `${command.slice(0, 60)}...` : command;
|
|
53
|
-
return themeFg("muted", "$ ") + themeFg("toolOutput", preview);
|
|
54
|
-
}
|
|
55
|
-
case "read": {
|
|
56
|
-
const rawPath = asString(args.file_path ?? args.path);
|
|
57
|
-
const filePath = shortenPath(rawPath);
|
|
58
|
-
const offset = asNumber(args.offset);
|
|
59
|
-
const limit = asNumber(args.limit);
|
|
60
|
-
let text = themeFg("accent", filePath);
|
|
61
|
-
if (offset !== undefined || limit !== undefined) {
|
|
62
|
-
const startLine = offset ?? 1;
|
|
63
|
-
const endLine = limit !== undefined ? startLine + limit - 1 : "";
|
|
64
|
-
text += themeFg("warning", `:${startLine}${endLine ? `-${endLine}` : ""}`);
|
|
65
|
-
}
|
|
66
|
-
return themeFg("muted", "read ") + text;
|
|
67
|
-
}
|
|
68
|
-
case "write": {
|
|
69
|
-
const rawPath = asString(args.file_path ?? args.path);
|
|
70
|
-
const filePath = shortenPath(rawPath);
|
|
71
|
-
const content = asString(args.content, "");
|
|
72
|
-
const lines = content.split("\n").length;
|
|
73
|
-
let text = themeFg("muted", "write ") + themeFg("accent", filePath);
|
|
74
|
-
if (lines > 1) text += themeFg("dim", ` (${lines} lines)`);
|
|
75
|
-
return text;
|
|
76
|
-
}
|
|
77
|
-
case "edit": {
|
|
78
|
-
const rawPath = asString(args.file_path ?? args.path);
|
|
79
|
-
return themeFg("muted", "edit ") + themeFg("accent", shortenPath(rawPath));
|
|
80
|
-
}
|
|
81
|
-
case "ls": {
|
|
82
|
-
const rawPath = asString(args.path, ".");
|
|
83
|
-
return themeFg("muted", "ls ") + themeFg("accent", shortenPath(rawPath));
|
|
84
|
-
}
|
|
85
|
-
case "find": {
|
|
86
|
-
const pattern = asString(args.pattern, "*");
|
|
87
|
-
const rawPath = asString(args.path, ".");
|
|
88
|
-
return (
|
|
89
|
-
themeFg("muted", "find ") +
|
|
90
|
-
themeFg("accent", pattern) +
|
|
91
|
-
themeFg("dim", ` in ${shortenPath(rawPath)}`)
|
|
92
|
-
);
|
|
93
|
-
}
|
|
94
|
-
case "grep": {
|
|
95
|
-
const pattern = asString(args.pattern);
|
|
96
|
-
const rawPath = asString(args.path, ".");
|
|
97
|
-
return (
|
|
98
|
-
themeFg("muted", "grep ") +
|
|
99
|
-
themeFg("accent", `/${pattern}/`) +
|
|
100
|
-
themeFg("dim", ` in ${shortenPath(rawPath)}`)
|
|
101
|
-
);
|
|
102
|
-
}
|
|
103
|
-
default: {
|
|
104
|
-
const argsStr = JSON.stringify(args);
|
|
105
|
-
const preview = argsStr.length > 50 ? `${argsStr.slice(0, 50)}...` : argsStr;
|
|
106
|
-
return themeFg("accent", toolName) + themeFg("dim", ` ${preview}`);
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
// ---------------------------------------------------------------------------
|
|
112
|
-
// Display helpers
|
|
113
|
-
// ---------------------------------------------------------------------------
|
|
114
|
-
|
|
115
|
-
type DisplayItem =
|
|
116
|
-
| { type: "text"; text: string }
|
|
117
|
-
| { type: "toolCall"; name: string; args: Record<string, unknown> };
|
|
118
|
-
|
|
119
|
-
function getDisplayItems(messages: Message[]): DisplayItem[] {
|
|
120
|
-
const items: DisplayItem[] = [];
|
|
121
|
-
for (const msg of messages) {
|
|
122
|
-
if (msg.role === "assistant") {
|
|
123
|
-
for (const part of msg.content) {
|
|
124
|
-
if (part.type === "text" && part.text.trim()) {
|
|
125
|
-
items.push({ type: "text", text: part.text });
|
|
126
|
-
} else if (part.type === "toolCall") {
|
|
127
|
-
items.push({
|
|
128
|
-
type: "toolCall",
|
|
129
|
-
name: part.name,
|
|
130
|
-
args: asRecord(part.arguments),
|
|
131
|
-
});
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
return items;
|
|
137
|
-
}
|
|
138
14
|
|
|
139
15
|
// ---------------------------------------------------------------------------
|
|
140
16
|
// Theme types
|
package/extensions/threads.ts
CHANGED
|
@@ -103,14 +103,4 @@ export const threadStore = new ThreadStore();
|
|
|
103
103
|
// ID generation (UUID v4 style, good enough for in-memory use)
|
|
104
104
|
// ---------------------------------------------------------------------------
|
|
105
105
|
|
|
106
|
-
|
|
107
|
-
if (typeof crypto !== "undefined" && crypto.randomUUID) {
|
|
108
|
-
return crypto.randomUUID();
|
|
109
|
-
}
|
|
110
|
-
// Fallback for environments without crypto (very unlikely in Node)
|
|
111
|
-
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
|
|
112
|
-
const r = (Math.random() * 16) | 0;
|
|
113
|
-
const v = c === "x" ? r : (r & 0x3) | 0x8;
|
|
114
|
-
return v.toString(16);
|
|
115
|
-
});
|
|
116
|
-
}
|
|
106
|
+
const cryptoGenId = () => crypto.randomUUID();
|
package/package.json
CHANGED
|
@@ -1,21 +1,31 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bacnh85/pi-subagent",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.6.1",
|
|
4
|
+
"description": "In-process subagents for Pi with isolated SDK sessions, parallel and chained delegation, and inspectable threads.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"publishConfig": { "access": "public" },
|
|
8
|
-
"homepage": "https://github.com/bacnh85/pi-extensions
|
|
8
|
+
"homepage": "https://github.com/bacnh85/pi-extensions/tree/main/pi-subagent",
|
|
9
9
|
"repository": {
|
|
10
10
|
"type": "git",
|
|
11
11
|
"url": "git+https://github.com/bacnh85/pi-extensions.git",
|
|
12
12
|
"directory": "pi-subagent"
|
|
13
13
|
},
|
|
14
14
|
"bugs": { "url": "https://github.com/bacnh85/pi-extensions/issues" },
|
|
15
|
-
"keywords": [
|
|
15
|
+
"keywords": [
|
|
16
|
+
"pi-package",
|
|
17
|
+
"pi-extension",
|
|
18
|
+
"subagent",
|
|
19
|
+
"sub-agent",
|
|
20
|
+
"delegation",
|
|
21
|
+
"parallel",
|
|
22
|
+
"chain",
|
|
23
|
+
"security"
|
|
24
|
+
],
|
|
16
25
|
"files": [
|
|
17
26
|
"README.md",
|
|
18
27
|
"agent-format.md",
|
|
28
|
+
"CHANGELOG.md",
|
|
19
29
|
"agents/",
|
|
20
30
|
"extensions/index.ts",
|
|
21
31
|
"extensions/agents.ts",
|
|
@@ -25,19 +35,24 @@
|
|
|
25
35
|
"extensions/render.ts",
|
|
26
36
|
"extensions/threads.ts",
|
|
27
37
|
"extensions/thread-viewer.ts",
|
|
38
|
+
"extensions/security.ts",
|
|
28
39
|
"extensions/package.json"
|
|
29
40
|
],
|
|
30
41
|
"pi": { "extensions": ["./extensions/index.ts"] },
|
|
42
|
+
"engines": {
|
|
43
|
+
"node": ">=20.18"
|
|
44
|
+
},
|
|
31
45
|
"scripts": {
|
|
32
|
-
"test": "cd extensions &&
|
|
33
|
-
"typecheck": "tsc --noEmit"
|
|
46
|
+
"test": "cd extensions && mocha",
|
|
47
|
+
"typecheck": "tsc --noEmit",
|
|
48
|
+
"check": "npm run typecheck && npm test"
|
|
34
49
|
},
|
|
35
50
|
"peerDependencies": {
|
|
36
|
-
"@earendil-works/pi-coding-agent": "
|
|
37
|
-
"@earendil-works/pi-ai": "
|
|
38
|
-
"@earendil-works/pi-agent-core": "
|
|
39
|
-
"@earendil-works/pi-tui": "
|
|
40
|
-
"typebox": "
|
|
51
|
+
"@earendil-works/pi-coding-agent": ">=0.80.0 <0.81.0",
|
|
52
|
+
"@earendil-works/pi-ai": ">=0.80.0 <0.81.0",
|
|
53
|
+
"@earendil-works/pi-agent-core": ">=0.80.0 <0.81.0",
|
|
54
|
+
"@earendil-works/pi-tui": ">=0.80.0 <0.81.0",
|
|
55
|
+
"typebox": ">=1.3.0 <2.0.0"
|
|
41
56
|
},
|
|
42
57
|
"devDependencies": {
|
|
43
58
|
"@earendil-works/pi-agent-core": "^0.80.2",
|