@harms-haus/pi-subagents 0.1.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/LICENSE +21 -0
- package/README.md +362 -0
- package/docs/architecture.md +554 -0
- package/docs/changelog.md +61 -0
- package/docs/profiles.md +546 -0
- package/docs/settings.md +52 -0
- package/docs/tools-reference.md +519 -0
- package/package.json +59 -0
- package/src/cache.ts +24 -0
- package/src/commands/profile.ts +176 -0
- package/src/format-tool-call.ts +597 -0
- package/src/format-transcript.ts +151 -0
- package/src/index.ts +117 -0
- package/src/profile-editor.ts +356 -0
- package/src/profile-formatting.ts +178 -0
- package/src/profile-types.ts +73 -0
- package/src/profiles.ts +577 -0
- package/src/schemas.ts +65 -0
- package/src/settings.ts +155 -0
- package/src/skill-discovery.ts +30 -0
- package/src/spawner.ts +523 -0
- package/src/tools/delegate-render.ts +285 -0
- package/src/tools/delegate.ts +867 -0
- package/src/tools/retrieval.ts +287 -0
- package/src/types.ts +232 -0
- package/src/utils.ts +168 -0
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import type { SubagentProfile } from "./profile-types";
|
|
2
|
+
|
|
3
|
+
/** Quote a string value for safe YAML output */
|
|
4
|
+
function yamlQuote(value: string): string {
|
|
5
|
+
// Quote if contains YAML-special characters
|
|
6
|
+
if (/:|#|'|"|\n|^\s|\s$|^[&*?|>!%@`{[~,]|^-(\s|$)/.test(value)) {
|
|
7
|
+
const escaped = value.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
8
|
+
return '"' + escaped + '"';
|
|
9
|
+
}
|
|
10
|
+
return value;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Get a human-readable summary of a profile for display in the TUI.
|
|
15
|
+
*/
|
|
16
|
+
export function profileSummary(name: string, profile: SubagentProfile): string {
|
|
17
|
+
const parts: string[] = [`profile: ${name}`];
|
|
18
|
+
if (profile.model) {
|
|
19
|
+
parts.push(`model=${profile.model}`);
|
|
20
|
+
} else if (profile.provider) {
|
|
21
|
+
parts.push(`provider=${profile.provider}`);
|
|
22
|
+
}
|
|
23
|
+
if (profile.thinkingLevel) {
|
|
24
|
+
parts.push(`thinking=${profile.thinkingLevel}`);
|
|
25
|
+
}
|
|
26
|
+
if (profile.systemPrompt) {
|
|
27
|
+
parts.push("custom-system-prompt");
|
|
28
|
+
}
|
|
29
|
+
if (profile.appendSystemPrompt) {
|
|
30
|
+
parts.push("appended-system-prompt");
|
|
31
|
+
}
|
|
32
|
+
if (profile.noTools) {
|
|
33
|
+
parts.push("no-tools");
|
|
34
|
+
} else if (profile.tools && profile.tools.length > 0) {
|
|
35
|
+
parts.push(`tools=[${profile.tools.join(",")}]`);
|
|
36
|
+
} else if (profile.excludeTools && profile.excludeTools.length > 0) {
|
|
37
|
+
parts.push(`excludeTools=[${profile.excludeTools.join(",")}]`);
|
|
38
|
+
}
|
|
39
|
+
if (profile.suggestedSkills && profile.suggestedSkills.length > 0) {
|
|
40
|
+
parts.push(`suggestedSkills=[${profile.suggestedSkills.join(",")}]`);
|
|
41
|
+
}
|
|
42
|
+
if (profile.loadSkills && profile.loadSkills.length > 0) {
|
|
43
|
+
parts.push(`loadSkills=[${profile.loadSkills.join(",")}]`);
|
|
44
|
+
}
|
|
45
|
+
return parts.join(", ");
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// ── Profile Serialization ────────────────────────────────────────────
|
|
49
|
+
|
|
50
|
+
/** String-valued profile fields for frontmatter serialization. */
|
|
51
|
+
const SERIALIZABLE_STRING_FIELDS = [
|
|
52
|
+
"provider",
|
|
53
|
+
"model",
|
|
54
|
+
"thinkingLevel",
|
|
55
|
+
"appendSystemPrompt",
|
|
56
|
+
"apiKey",
|
|
57
|
+
] as const;
|
|
58
|
+
|
|
59
|
+
/** Boolean-valued profile fields for frontmatter serialization. */
|
|
60
|
+
const SERIALIZABLE_BOOLEAN_FIELDS = [
|
|
61
|
+
"noTools",
|
|
62
|
+
"noExtensions",
|
|
63
|
+
"noSkills",
|
|
64
|
+
"noContextFiles",
|
|
65
|
+
] as const;
|
|
66
|
+
|
|
67
|
+
/** Array-valued profile fields serialized as comma-joined strings. */
|
|
68
|
+
const SERIALIZABLE_ARRAY_FIELDS = [
|
|
69
|
+
"tools",
|
|
70
|
+
"excludeTools",
|
|
71
|
+
"extensions",
|
|
72
|
+
"extraArgs",
|
|
73
|
+
"suggestedSkills",
|
|
74
|
+
"loadSkills",
|
|
75
|
+
] as const;
|
|
76
|
+
|
|
77
|
+
function pushFrontmatterLines(fmLines: string[], name: string, profile: SubagentProfile): void {
|
|
78
|
+
fmLines.push("---");
|
|
79
|
+
fmLines.push(`name: ${yamlQuote(name)}`);
|
|
80
|
+
|
|
81
|
+
for (const field of SERIALIZABLE_STRING_FIELDS) {
|
|
82
|
+
const value = profile[field];
|
|
83
|
+
if (value !== undefined) fmLines.push(`${field}: ${yamlQuote(value)}`);
|
|
84
|
+
}
|
|
85
|
+
for (const field of SERIALIZABLE_BOOLEAN_FIELDS) {
|
|
86
|
+
const value = profile[field];
|
|
87
|
+
if (value !== undefined) fmLines.push(`${field}: ${value}`);
|
|
88
|
+
}
|
|
89
|
+
for (const field of SERIALIZABLE_ARRAY_FIELDS) {
|
|
90
|
+
const value = profile[field];
|
|
91
|
+
if (value && value.length > 0) {
|
|
92
|
+
fmLines.push(`${field}: ${yamlQuote(value.join(","))}`);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export function serializeProfileToMarkdown(name: string, profile: SubagentProfile): string {
|
|
98
|
+
const fmLines: string[] = [];
|
|
99
|
+
pushFrontmatterLines(fmLines, name, profile);
|
|
100
|
+
fmLines.push("---");
|
|
101
|
+
|
|
102
|
+
// Body is the system prompt
|
|
103
|
+
if (profile.systemPrompt) {
|
|
104
|
+
fmLines.push("");
|
|
105
|
+
fmLines.push(profile.systemPrompt);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return `${fmLines.join("\n")}\n`;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** Fields displayed as simple string values in profile detail. */
|
|
112
|
+
const DETAIL_STRING_FIELDS: Array<{ key: keyof SubagentProfile; label: string }> = [
|
|
113
|
+
{ key: "provider", label: "provider:" },
|
|
114
|
+
{ key: "model", label: "model:" },
|
|
115
|
+
{ key: "thinkingLevel", label: "thinkingLevel:" },
|
|
116
|
+
{ key: "systemPrompt", label: "systemPrompt:" },
|
|
117
|
+
{ key: "appendSystemPrompt", label: "appendSystemPrompt:" },
|
|
118
|
+
];
|
|
119
|
+
|
|
120
|
+
/** Fields displayed as comma-separated array values in profile detail. */
|
|
121
|
+
const DETAIL_ARRAY_FIELDS: Array<{ key: keyof SubagentProfile; label: string }> = [
|
|
122
|
+
{ key: "extensions", label: "extensions:" },
|
|
123
|
+
{ key: "suggestedSkills", label: "suggestedSkills:" },
|
|
124
|
+
{ key: "loadSkills", label: "loadSkills:" },
|
|
125
|
+
];
|
|
126
|
+
|
|
127
|
+
function pushDetailToolLines(lines: string[], profile: SubagentProfile): void {
|
|
128
|
+
if (profile.noTools) {
|
|
129
|
+
lines.push(` noTools: true`);
|
|
130
|
+
} else if (profile.tools) {
|
|
131
|
+
lines.push(` tools: [${profile.tools.join(", ")}]`);
|
|
132
|
+
} else if (profile.excludeTools && profile.excludeTools.length > 0) {
|
|
133
|
+
lines.push(` excludeTools: [${profile.excludeTools.join(", ")}]`);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function pushDetailSpecialLines(lines: string[], profile: SubagentProfile): void {
|
|
138
|
+
if (profile.noExtensions) lines.push(` noExtensions: true`);
|
|
139
|
+
if (profile.noSkills) lines.push(` noSkills: true`);
|
|
140
|
+
if (profile.noContextFiles) lines.push(` noContextFiles: true`);
|
|
141
|
+
if (profile.apiKey) {
|
|
142
|
+
const masked =
|
|
143
|
+
profile.apiKey.length > 8
|
|
144
|
+
? `${profile.apiKey.slice(0, 4)}****${profile.apiKey.slice(-4)}`
|
|
145
|
+
: "****";
|
|
146
|
+
lines.push(` apiKey: ${masked}`);
|
|
147
|
+
}
|
|
148
|
+
if (profile.extraArgs) {
|
|
149
|
+
lines.push(` extraArgs: ${JSON.stringify(profile.extraArgs)}`);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Format a profile as a human-readable multi-line string for display.
|
|
155
|
+
*/
|
|
156
|
+
export function formatProfileDetail(name: string, profile: SubagentProfile): string {
|
|
157
|
+
const lines: string[] = [];
|
|
158
|
+
lines.push(`Profile: ${name}`);
|
|
159
|
+
|
|
160
|
+
for (const { key, label } of DETAIL_STRING_FIELDS) {
|
|
161
|
+
const value = profile[key];
|
|
162
|
+
if (typeof value === "string") {
|
|
163
|
+
lines.push(` ${label.padEnd(label === "appendSystemPrompt:" ? 20 : 19)}${value}`);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
pushDetailToolLines(lines, profile);
|
|
168
|
+
|
|
169
|
+
for (const { key, label } of DETAIL_ARRAY_FIELDS) {
|
|
170
|
+
const value = profile[key];
|
|
171
|
+
if (Array.isArray(value) && value.length > 0) {
|
|
172
|
+
lines.push(` ${label.padEnd(19)}[${value.join(", ")}]`);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
pushDetailSpecialLines(lines, profile);
|
|
177
|
+
return lines.join("\n");
|
|
178
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Subagent Profile Type Definitions
|
|
3
|
+
*
|
|
4
|
+
* Shared types for the profile system, used by both profiles.ts and profile-formatting.ts
|
|
5
|
+
* to avoid circular dependencies.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
// ── Profile Types ────────────────────────────────────────────────────
|
|
9
|
+
|
|
10
|
+
export type ThinkingLevel = "off" | "minimal" | "low" | "medium" | "high" | "xhigh";
|
|
11
|
+
|
|
12
|
+
export interface SubagentProfile {
|
|
13
|
+
/** Provider name (e.g. "anthropic", "openai", "dashscope") */
|
|
14
|
+
provider?: string;
|
|
15
|
+
|
|
16
|
+
/** Model pattern or ID (supports "provider/id" and ":thinking" shorthand) */
|
|
17
|
+
model?: string;
|
|
18
|
+
|
|
19
|
+
/** Replace the default system prompt entirely */
|
|
20
|
+
systemPrompt?: string;
|
|
21
|
+
|
|
22
|
+
/** Append text to the default system prompt */
|
|
23
|
+
appendSystemPrompt?: string;
|
|
24
|
+
|
|
25
|
+
/** Explicit thinking level: off, minimal, low, medium, high, xhigh */
|
|
26
|
+
thinkingLevel?: ThinkingLevel;
|
|
27
|
+
|
|
28
|
+
/** Disable all tools */
|
|
29
|
+
noTools?: boolean;
|
|
30
|
+
|
|
31
|
+
/** Comma-separated allowlist of tool names to enable */
|
|
32
|
+
tools?: string[];
|
|
33
|
+
|
|
34
|
+
/** Blacklist of tool names to exclude from the full set */
|
|
35
|
+
excludeTools?: string[];
|
|
36
|
+
|
|
37
|
+
/** Disable all extensions */
|
|
38
|
+
noExtensions?: boolean;
|
|
39
|
+
|
|
40
|
+
/** Extension paths to load (can be used multiple times) */
|
|
41
|
+
extensions?: string[];
|
|
42
|
+
|
|
43
|
+
/** Disable skills */
|
|
44
|
+
noSkills?: boolean;
|
|
45
|
+
|
|
46
|
+
/** Skill names to suggest to the subagent via --skill (model chooses to load) */
|
|
47
|
+
suggestedSkills?: string[];
|
|
48
|
+
|
|
49
|
+
/** Skill names to pre-load (content injected into system prompt) */
|
|
50
|
+
loadSkills?: string[];
|
|
51
|
+
|
|
52
|
+
/** Disable context files (AGENTS.md, CLAUDE.md) */
|
|
53
|
+
noContextFiles?: boolean;
|
|
54
|
+
|
|
55
|
+
/** Custom API key */
|
|
56
|
+
apiKey?: string;
|
|
57
|
+
|
|
58
|
+
/** Additional CLI arguments to pass verbatim */
|
|
59
|
+
extraArgs?: string[];
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export interface SubagentProfiles {
|
|
63
|
+
[name: string]: SubagentProfile;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Result of converting a profile to invocation parameters.
|
|
68
|
+
* Contains both CLI arguments and environment variables.
|
|
69
|
+
*/
|
|
70
|
+
export interface ProfileInvocation {
|
|
71
|
+
args: string[];
|
|
72
|
+
env: Record<string, string>;
|
|
73
|
+
}
|