@gotgenes/pi-subagents 7.3.1 → 7.4.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/CHANGELOG.md +26 -0
- package/docs/architecture/architecture.md +148 -60
- package/docs/architecture/history/phase-12-complexity-test-fixtures.md +55 -0
- package/docs/plans/0214-convert-remaining-closure-factories-to-classes.md +261 -0
- package/docs/retro/0208-extract-shared-test-fixtures.md +57 -0
- package/docs/retro/0214-convert-remaining-closure-factories-to-classes.md +66 -0
- package/package.json +1 -1
- package/src/index.ts +2 -2
- package/src/lifecycle/agent-runner.ts +10 -1
- package/src/service/service-adapter.ts +76 -76
- package/src/ui/agent-config-editor.ts +56 -56
- package/src/ui/agent-creation-wizard.ts +28 -36
- package/src/ui/agent-menu.ts +7 -7
- package/src/ui/message-formatters.ts +26 -1
|
@@ -30,33 +30,27 @@ export interface WizardRegistry {
|
|
|
30
30
|
reload(): void;
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
manager,
|
|
46
|
-
registry,
|
|
47
|
-
personalAgentsDir,
|
|
48
|
-
projectAgentsDir,
|
|
49
|
-
}: AgentCreationWizardDeps) {
|
|
50
|
-
async function showCreateWizard(ui: MenuUI, parentSnapshot: ParentSnapshot) {
|
|
33
|
+
// ---- Class ----
|
|
34
|
+
|
|
35
|
+
export class AgentCreationWizard {
|
|
36
|
+
constructor(
|
|
37
|
+
private readonly fileOps: AgentFileOps,
|
|
38
|
+
private readonly manager: WizardManager,
|
|
39
|
+
private readonly registry: WizardRegistry,
|
|
40
|
+
private readonly personalAgentsDir: string,
|
|
41
|
+
private readonly projectAgentsDir: string,
|
|
42
|
+
) {}
|
|
43
|
+
|
|
44
|
+
async showCreateWizard(ui: MenuUI, parentSnapshot: ParentSnapshot): Promise<void> {
|
|
51
45
|
const location = await ui.select("Choose location", [
|
|
52
46
|
"Project (.pi/agents/)",
|
|
53
|
-
`Personal (${personalAgentsDir})`,
|
|
47
|
+
`Personal (${this.personalAgentsDir})`,
|
|
54
48
|
]);
|
|
55
49
|
if (!location) return;
|
|
56
50
|
|
|
57
51
|
const targetDir = location.startsWith("Project")
|
|
58
|
-
? projectAgentsDir
|
|
59
|
-
: personalAgentsDir;
|
|
52
|
+
? this.projectAgentsDir
|
|
53
|
+
: this.personalAgentsDir;
|
|
60
54
|
|
|
61
55
|
const method = await ui.select("Creation method", [
|
|
62
56
|
"Generate with Claude (recommended)",
|
|
@@ -65,27 +59,27 @@ export function createAgentCreationWizard({
|
|
|
65
59
|
if (!method) return;
|
|
66
60
|
|
|
67
61
|
if (method.startsWith("Generate")) {
|
|
68
|
-
await showGenerateWizard(ui, parentSnapshot, targetDir);
|
|
62
|
+
await this.showGenerateWizard(ui, parentSnapshot, targetDir);
|
|
69
63
|
} else {
|
|
70
|
-
await showManualWizard(ui, targetDir);
|
|
64
|
+
await this.showManualWizard(ui, targetDir);
|
|
71
65
|
}
|
|
72
66
|
}
|
|
73
67
|
|
|
74
|
-
async
|
|
68
|
+
private async showGenerateWizard(
|
|
75
69
|
ui: MenuUI,
|
|
76
70
|
parentSnapshot: ParentSnapshot,
|
|
77
71
|
targetDir: string,
|
|
78
|
-
) {
|
|
72
|
+
): Promise<void> {
|
|
79
73
|
const description = await ui.input("Describe what this agent should do");
|
|
80
74
|
if (!description) return;
|
|
81
75
|
|
|
82
76
|
const name = await ui.input("Agent name (filename, no spaces)");
|
|
83
77
|
if (!name) return;
|
|
84
78
|
|
|
85
|
-
fileOps.ensureDir(targetDir);
|
|
79
|
+
this.fileOps.ensureDir(targetDir);
|
|
86
80
|
|
|
87
81
|
const targetPath = join(targetDir, `${name}.md`);
|
|
88
|
-
if (fileOps.exists(targetPath)) {
|
|
82
|
+
if (this.fileOps.exists(targetPath)) {
|
|
89
83
|
const overwrite = await ui.confirm(
|
|
90
84
|
"Overwrite",
|
|
91
85
|
`${targetPath} already exists. Overwrite?`,
|
|
@@ -132,7 +126,7 @@ Guidelines for choosing settings:
|
|
|
132
126
|
|
|
133
127
|
Write the file using the write tool. Only write the file, nothing else.`;
|
|
134
128
|
|
|
135
|
-
const record = await manager.spawnAndWait(
|
|
129
|
+
const record = await this.manager.spawnAndWait(
|
|
136
130
|
parentSnapshot,
|
|
137
131
|
"general-purpose",
|
|
138
132
|
generatePrompt,
|
|
@@ -147,9 +141,9 @@ Write the file using the write tool. Only write the file, nothing else.`;
|
|
|
147
141
|
return;
|
|
148
142
|
}
|
|
149
143
|
|
|
150
|
-
registry.reload();
|
|
144
|
+
this.registry.reload();
|
|
151
145
|
|
|
152
|
-
if (fileOps.exists(targetPath)) {
|
|
146
|
+
if (this.fileOps.exists(targetPath)) {
|
|
153
147
|
ui.notify(`Created ${targetPath}`, "info");
|
|
154
148
|
} else {
|
|
155
149
|
ui.notify(
|
|
@@ -159,7 +153,7 @@ Write the file using the write tool. Only write the file, nothing else.`;
|
|
|
159
153
|
}
|
|
160
154
|
}
|
|
161
155
|
|
|
162
|
-
async
|
|
156
|
+
private async showManualWizard(ui: MenuUI, targetDir: string): Promise<void> {
|
|
163
157
|
const name = await ui.input("Agent name (filename, no spaces)");
|
|
164
158
|
if (!name) return;
|
|
165
159
|
|
|
@@ -239,7 +233,7 @@ ${systemPrompt}
|
|
|
239
233
|
|
|
240
234
|
const targetPath = join(targetDir, `${name}.md`);
|
|
241
235
|
|
|
242
|
-
if (fileOps.exists(targetPath)) {
|
|
236
|
+
if (this.fileOps.exists(targetPath)) {
|
|
243
237
|
const overwrite = await ui.confirm(
|
|
244
238
|
"Overwrite",
|
|
245
239
|
`${targetPath} already exists. Overwrite?`,
|
|
@@ -247,10 +241,8 @@ ${systemPrompt}
|
|
|
247
241
|
if (!overwrite) return;
|
|
248
242
|
}
|
|
249
243
|
|
|
250
|
-
fileOps.write(targetPath, content);
|
|
251
|
-
registry.reload();
|
|
244
|
+
this.fileOps.write(targetPath, content);
|
|
245
|
+
this.registry.reload();
|
|
252
246
|
ui.notify(`Created ${targetPath}`, "info");
|
|
253
247
|
}
|
|
254
|
-
|
|
255
|
-
return { showCreateWizard };
|
|
256
248
|
}
|
package/src/ui/agent-menu.ts
CHANGED
|
@@ -6,8 +6,8 @@ import { type ModelRegistry, resolveModel } from "#src/session/model-resolver";
|
|
|
6
6
|
import { getModelLabelFromConfig } from "#src/tools/helpers";
|
|
7
7
|
import type { AgentConfig, AgentRecord } from "#src/types";
|
|
8
8
|
import type { AgentActivityTracker } from "#src/ui/agent-activity-tracker";
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
9
|
+
import { AgentConfigEditor } from "#src/ui/agent-config-editor";
|
|
10
|
+
import { AgentCreationWizard } from "#src/ui/agent-creation-wizard";
|
|
11
11
|
import type { AgentFileOps } from "#src/ui/agent-file-ops";
|
|
12
12
|
import { formatDuration, getDisplayName } from "#src/ui/display";
|
|
13
13
|
|
|
@@ -64,8 +64,8 @@ export interface MenuUI {
|
|
|
64
64
|
* Call `handle(ctx)` from the Pi command registration to open the interactive menu.
|
|
65
65
|
*/
|
|
66
66
|
export class AgentsMenuHandler {
|
|
67
|
-
private readonly editor:
|
|
68
|
-
private readonly wizard:
|
|
67
|
+
private readonly editor: AgentConfigEditor;
|
|
68
|
+
private readonly wizard: AgentCreationWizard;
|
|
69
69
|
|
|
70
70
|
constructor(
|
|
71
71
|
private readonly manager: AgentMenuManager,
|
|
@@ -76,19 +76,19 @@ export class AgentsMenuHandler {
|
|
|
76
76
|
private readonly personalAgentsDir: string,
|
|
77
77
|
private readonly projectAgentsDir: string,
|
|
78
78
|
) {
|
|
79
|
-
this.editor =
|
|
79
|
+
this.editor = new AgentConfigEditor(
|
|
80
80
|
fileOps,
|
|
81
81
|
registry,
|
|
82
82
|
personalAgentsDir,
|
|
83
83
|
projectAgentsDir,
|
|
84
84
|
);
|
|
85
|
-
this.wizard =
|
|
85
|
+
this.wizard = new AgentCreationWizard(
|
|
86
86
|
fileOps,
|
|
87
87
|
manager,
|
|
88
88
|
registry,
|
|
89
89
|
personalAgentsDir,
|
|
90
90
|
projectAgentsDir,
|
|
91
|
-
|
|
91
|
+
);
|
|
92
92
|
}
|
|
93
93
|
|
|
94
94
|
async handle({
|
|
@@ -116,20 +116,31 @@ export function formatToolResult(
|
|
|
116
116
|
];
|
|
117
117
|
}
|
|
118
118
|
|
|
119
|
+
// ── Types ─────────────────────────────────────────────────────────────────────
|
|
120
|
+
|
|
121
|
+
/** Model/provider attribution for assistant messages. */
|
|
122
|
+
export interface MessageAttribution {
|
|
123
|
+
provider?: string;
|
|
124
|
+
model?: string;
|
|
125
|
+
}
|
|
126
|
+
|
|
119
127
|
// ── formatAssistantMessage ───────────────────────────────────────────────────
|
|
120
128
|
|
|
121
129
|
/**
|
|
122
130
|
* Format an assistant message into display lines.
|
|
123
131
|
* Always returns at least the [Assistant] header line.
|
|
132
|
+
* When attribution is provided, renders as `[Assistant (provider/model)]`.
|
|
124
133
|
*/
|
|
125
134
|
export function formatAssistantMessage(
|
|
126
135
|
content: { type: string; [key: string]: unknown }[],
|
|
127
136
|
width: number,
|
|
128
137
|
ctx: FormatterContext,
|
|
138
|
+
attribution?: MessageAttribution,
|
|
129
139
|
): string[] {
|
|
130
140
|
const { theme, wrapText } = ctx;
|
|
131
141
|
const { textParts, toolNames } = extractAssistantContent(content);
|
|
132
|
-
const
|
|
142
|
+
const label = formatAttributionLabel(attribution);
|
|
143
|
+
const lines: string[] = [theme.bold(`[Assistant${label}]`)];
|
|
133
144
|
if (textParts.length > 0) {
|
|
134
145
|
lines.push(...wrapText(textParts.join("\n").trim(), width));
|
|
135
146
|
}
|
|
@@ -154,10 +165,15 @@ export function formatMessage(
|
|
|
154
165
|
return formatUserMessage(msg.content as string | unknown[], width, ctx);
|
|
155
166
|
}
|
|
156
167
|
if (msg.role === "assistant") {
|
|
168
|
+
const attribution: MessageAttribution = {
|
|
169
|
+
provider: msg.provider as string | undefined,
|
|
170
|
+
model: msg.model as string | undefined,
|
|
171
|
+
};
|
|
157
172
|
return formatAssistantMessage(
|
|
158
173
|
msg.content as { type: string; [key: string]: unknown }[],
|
|
159
174
|
width,
|
|
160
175
|
ctx,
|
|
176
|
+
attribution,
|
|
161
177
|
);
|
|
162
178
|
}
|
|
163
179
|
if (msg.role === "toolResult") {
|
|
@@ -168,3 +184,12 @@ export function formatMessage(
|
|
|
168
184
|
}
|
|
169
185
|
return null;
|
|
170
186
|
}
|
|
187
|
+
|
|
188
|
+
// ── Helpers ──────────────────────────────────────────────────────────────────
|
|
189
|
+
|
|
190
|
+
/** Build a `(provider/model)` attribution suffix, or empty string when absent. */
|
|
191
|
+
function formatAttributionLabel(attr?: MessageAttribution): string {
|
|
192
|
+
if (!attr?.provider && !attr?.model) return "";
|
|
193
|
+
if (attr.provider && attr.model) return ` (${attr.provider}/${attr.model})`;
|
|
194
|
+
return ` (${attr.provider ?? attr.model})`;
|
|
195
|
+
}
|