@gotgenes/pi-subagents 6.16.0 → 6.16.2

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.
@@ -7,35 +7,31 @@
7
7
 
8
8
  import { join } from "node:path";
9
9
 
10
- import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
11
10
  import type { AgentTypeRegistry } from "../agent-types.js";
12
11
  import type { AgentConfig } from "../types.js";
13
12
  import type { AgentFileOps } from "./agent-file-ops.js";
14
-
15
- // ---- Deps interface ----
16
-
17
- export interface AgentConfigEditorDeps {
18
- fileOps: AgentFileOps;
19
- registry: AgentTypeRegistry;
20
- personalAgentsDir: string;
21
- projectAgentsDir: string;
22
- }
13
+ import type { MenuUI } from "./agent-menu.js";
23
14
 
24
15
  // ---- Factory ----
25
16
 
26
- export function createAgentConfigEditor(deps: AgentConfigEditorDeps) {
17
+ export function createAgentConfigEditor(
18
+ fileOps: AgentFileOps,
19
+ registry: AgentTypeRegistry,
20
+ personalAgentsDir: string,
21
+ projectAgentsDir: string,
22
+ ) {
27
23
  function agentDirs(): string[] {
28
- return [deps.projectAgentsDir, deps.personalAgentsDir];
24
+ return [projectAgentsDir, personalAgentsDir];
29
25
  }
30
26
 
31
- async function showAgentDetail(ctx: ExtensionContext, name: string) {
32
- if (deps.registry.resolveType(name) == null) {
33
- ctx.ui.notify(`Agent config not found for "${name}".`, "warning");
27
+ async function showAgentDetail(ui: MenuUI, name: string) {
28
+ if (registry.resolveType(name) == null) {
29
+ ui.notify(`Agent config not found for "${name}".`, "warning");
34
30
  return;
35
31
  }
36
- const cfg = deps.registry.resolveAgentConfig(name);
32
+ const cfg = registry.resolveAgentConfig(name);
37
33
 
38
- const file = deps.fileOps.findAgentFile(name, agentDirs());
34
+ const file = fileOps.findAgentFile(name, agentDirs());
39
35
  const isDefault = cfg.isDefault === true;
40
36
  const disabled = cfg.enabled === false;
41
37
 
@@ -52,64 +48,64 @@ export function createAgentConfigEditor(deps: AgentConfigEditorDeps) {
52
48
  menuOptions = ["Edit", "Disable", "Delete", "Back"];
53
49
  }
54
50
 
55
- const choice = await ctx.ui.select(name, menuOptions);
51
+ const choice = await ui.select(name, menuOptions);
56
52
  if (!choice || choice === "Back") return;
57
53
 
58
54
  if (choice === "Edit" && file) {
59
- const content = deps.fileOps.read(file);
55
+ const content = fileOps.read(file);
60
56
  if (content !== undefined) {
61
- const edited = await ctx.ui.editor(`Edit ${name}`, content);
57
+ const edited = await ui.editor(`Edit ${name}`, content);
62
58
  if (edited !== undefined && edited !== content) {
63
- deps.fileOps.write(file, edited);
64
- deps.registry.reload();
65
- ctx.ui.notify(`Updated ${file}`, "info");
59
+ fileOps.write(file, edited);
60
+ registry.reload();
61
+ ui.notify(`Updated ${file}`, "info");
66
62
  }
67
63
  }
68
64
  } else if (choice === "Delete") {
69
65
  if (file) {
70
- const confirmed = await ctx.ui.confirm(
66
+ const confirmed = await ui.confirm(
71
67
  "Delete agent",
72
68
  `Delete ${name} (${file})?`,
73
69
  );
74
70
  if (confirmed) {
75
- deps.fileOps.remove(file);
76
- deps.registry.reload();
77
- ctx.ui.notify(`Deleted ${file}`, "info");
71
+ fileOps.remove(file);
72
+ registry.reload();
73
+ ui.notify(`Deleted ${file}`, "info");
78
74
  }
79
75
  }
80
76
  } else if (choice === "Reset to default" && file) {
81
- const confirmed = await ctx.ui.confirm(
77
+ const confirmed = await ui.confirm(
82
78
  "Reset to default",
83
79
  `Delete override ${file} and restore embedded default?`,
84
80
  );
85
81
  if (confirmed) {
86
- deps.fileOps.remove(file);
87
- deps.registry.reload();
88
- ctx.ui.notify(`Restored default ${name}`, "info");
82
+ fileOps.remove(file);
83
+ registry.reload();
84
+ ui.notify(`Restored default ${name}`, "info");
89
85
  }
90
86
  } else if (choice.startsWith("Eject")) {
91
- await ejectAgent(ctx, name, cfg);
87
+ await ejectAgent(ui, name, cfg);
92
88
  } else if (choice === "Disable") {
93
- await disableAgent(ctx, name);
89
+ await disableAgent(ui, name);
94
90
  } else if (choice === "Enable") {
95
- await enableAgent(ctx, name);
91
+ await enableAgent(ui, name);
96
92
  }
97
93
  }
98
94
 
99
- async function ejectAgent(ctx: ExtensionContext, name: string, cfg: AgentConfig) {
100
- const location = await ctx.ui.select("Choose location", [
95
+ async function ejectAgent(ui: MenuUI, name: string, cfg: AgentConfig) {
96
+ const location = await ui.select("Choose location", [
101
97
  "Project (.pi/agents/)",
102
- `Personal (${deps.personalAgentsDir})`,
98
+ `Personal (${personalAgentsDir})`,
103
99
  ]);
104
100
  if (!location) return;
105
101
 
106
102
  const targetDir = location.startsWith("Project")
107
- ? deps.projectAgentsDir
108
- : deps.personalAgentsDir;
103
+ ? projectAgentsDir
104
+ : personalAgentsDir;
109
105
 
110
106
  const targetPath = join(targetDir, `${name}.md`);
111
- if (deps.fileOps.exists(targetPath)) {
112
- const overwrite = await ctx.ui.confirm(
107
+ if (fileOps.exists(targetPath)) {
108
+ const overwrite = await ui.confirm(
113
109
  "Overwrite",
114
110
  `${targetPath} already exists. Overwrite?`,
115
111
  );
@@ -140,61 +136,61 @@ export function createAgentConfigEditor(deps: AgentConfigEditorDeps) {
140
136
 
141
137
  const content = `---\n${fmFields.join("\n")}\n---\n\n${cfg.systemPrompt}\n`;
142
138
 
143
- deps.fileOps.write(targetPath, content);
144
- deps.registry.reload();
145
- ctx.ui.notify(`Ejected ${name} to ${targetPath}`, "info");
139
+ fileOps.write(targetPath, content);
140
+ registry.reload();
141
+ ui.notify(`Ejected ${name} to ${targetPath}`, "info");
146
142
  }
147
143
 
148
- async function disableAgent(ctx: ExtensionContext, name: string) {
149
- const file = deps.fileOps.findAgentFile(name, agentDirs());
144
+ async function disableAgent(ui: MenuUI, name: string) {
145
+ const file = fileOps.findAgentFile(name, agentDirs());
150
146
  if (file) {
151
- const content = deps.fileOps.read(file);
147
+ const content = fileOps.read(file);
152
148
  if (content?.includes("\nenabled: false\n")) {
153
- ctx.ui.notify(`${name} is already disabled.`, "info");
149
+ ui.notify(`${name} is already disabled.`, "info");
154
150
  return;
155
151
  }
156
152
  if (content) {
157
153
  const updated = content.replace(/^---\n/, "---\nenabled: false\n");
158
- deps.fileOps.write(file, updated);
159
- deps.registry.reload();
160
- ctx.ui.notify(`Disabled ${name} (${file})`, "info");
154
+ fileOps.write(file, updated);
155
+ registry.reload();
156
+ ui.notify(`Disabled ${name} (${file})`, "info");
161
157
  }
162
158
  return;
163
159
  }
164
160
 
165
- const location = await ctx.ui.select("Choose location", [
161
+ const location = await ui.select("Choose location", [
166
162
  "Project (.pi/agents/)",
167
- `Personal (${deps.personalAgentsDir})`,
163
+ `Personal (${personalAgentsDir})`,
168
164
  ]);
169
165
  if (!location) return;
170
166
 
171
167
  const targetDir = location.startsWith("Project")
172
- ? deps.projectAgentsDir
173
- : deps.personalAgentsDir;
168
+ ? projectAgentsDir
169
+ : personalAgentsDir;
174
170
 
175
171
  const targetPath = join(targetDir, `${name}.md`);
176
- deps.fileOps.write(targetPath, "---\nenabled: false\n---\n");
177
- deps.registry.reload();
178
- ctx.ui.notify(`Disabled ${name} (${targetPath})`, "info");
172
+ fileOps.write(targetPath, "---\nenabled: false\n---\n");
173
+ registry.reload();
174
+ ui.notify(`Disabled ${name} (${targetPath})`, "info");
179
175
  }
180
176
 
181
- async function enableAgent(ctx: ExtensionContext, name: string) {
182
- const file = deps.fileOps.findAgentFile(name, agentDirs());
177
+ async function enableAgent(ui: MenuUI, name: string) {
178
+ const file = fileOps.findAgentFile(name, agentDirs());
183
179
  if (!file) return;
184
180
 
185
- const content = deps.fileOps.read(file);
181
+ const content = fileOps.read(file);
186
182
  if (!content) return;
187
183
 
188
184
  const updated = content.replace(/^(---\n)enabled: false\n/, "$1");
189
185
 
190
186
  if (updated.trim() === "---\n---" || updated.trim() === "---\n---\n") {
191
- deps.fileOps.remove(file);
192
- deps.registry.reload();
193
- ctx.ui.notify(`Enabled ${name} (removed ${file})`, "info");
187
+ fileOps.remove(file);
188
+ registry.reload();
189
+ ui.notify(`Enabled ${name} (removed ${file})`, "info");
194
190
  } else {
195
- deps.fileOps.write(file, updated);
196
- deps.registry.reload();
197
- ctx.ui.notify(`Enabled ${name} (${file})`, "info");
191
+ fileOps.write(file, updated);
192
+ registry.reload();
193
+ ui.notify(`Enabled ${name} (${file})`, "info");
198
194
  }
199
195
  }
200
196
 
@@ -7,17 +7,18 @@
7
7
 
8
8
  import { join } from "node:path";
9
9
 
10
- import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
11
10
  import { BUILTIN_TOOL_NAMES } from "../agent-types.js";
11
+ import type { ParentSnapshot } from "../parent-snapshot.js";
12
12
  import type { AgentRecord } from "../types.js";
13
13
  import type { AgentFileOps } from "./agent-file-ops.js";
14
+ import type { MenuUI } from "./agent-menu.js";
14
15
 
15
16
  // ---- Deps interface ----
16
17
 
17
18
  /** Narrow manager interface for agent spawning (generate wizard). */
18
19
  export interface WizardManager {
19
20
  spawnAndWait: (
20
- ctx: ExtensionContext,
21
+ parentSnapshot: ParentSnapshot,
21
22
  type: string,
22
23
  prompt: string,
23
24
  opts: { description: string; maxTurns: number },
@@ -39,50 +40,60 @@ export interface AgentCreationWizardDeps {
39
40
 
40
41
  // ---- Factory ----
41
42
 
42
- export function createAgentCreationWizard(deps: AgentCreationWizardDeps) {
43
- async function showCreateWizard(ctx: ExtensionContext) {
44
- const location = await ctx.ui.select("Choose location", [
43
+ export function createAgentCreationWizard({
44
+ fileOps,
45
+ manager,
46
+ registry,
47
+ personalAgentsDir,
48
+ projectAgentsDir,
49
+ }: AgentCreationWizardDeps) {
50
+ async function showCreateWizard(ui: MenuUI, parentSnapshot: ParentSnapshot) {
51
+ const location = await ui.select("Choose location", [
45
52
  "Project (.pi/agents/)",
46
- `Personal (${deps.personalAgentsDir})`,
53
+ `Personal (${personalAgentsDir})`,
47
54
  ]);
48
55
  if (!location) return;
49
56
 
50
57
  const targetDir = location.startsWith("Project")
51
- ? deps.projectAgentsDir
52
- : deps.personalAgentsDir;
58
+ ? projectAgentsDir
59
+ : personalAgentsDir;
53
60
 
54
- const method = await ctx.ui.select("Creation method", [
61
+ const method = await ui.select("Creation method", [
55
62
  "Generate with Claude (recommended)",
56
63
  "Manual configuration",
57
64
  ]);
58
65
  if (!method) return;
59
66
 
60
67
  if (method.startsWith("Generate")) {
61
- await showGenerateWizard(ctx, targetDir);
68
+ await showGenerateWizard(ui, parentSnapshot, targetDir);
62
69
  } else {
63
- await showManualWizard(ctx, targetDir);
70
+ await showManualWizard(ui, targetDir);
64
71
  }
65
72
  }
66
73
 
67
- async function showGenerateWizard(ctx: ExtensionContext, targetDir: string) {
68
- const description = await ctx.ui.input("Describe what this agent should do");
74
+ async function showGenerateWizard(
75
+ ui: MenuUI,
76
+ parentSnapshot: ParentSnapshot,
77
+ targetDir: string,
78
+ ) {
79
+ const description = await ui.input("Describe what this agent should do");
69
80
  if (!description) return;
70
81
 
71
- const name = await ctx.ui.input("Agent name (filename, no spaces)");
82
+ const name = await ui.input("Agent name (filename, no spaces)");
72
83
  if (!name) return;
73
84
 
74
- deps.fileOps.ensureDir(targetDir);
85
+ fileOps.ensureDir(targetDir);
75
86
 
76
87
  const targetPath = join(targetDir, `${name}.md`);
77
- if (deps.fileOps.exists(targetPath)) {
78
- const overwrite = await ctx.ui.confirm(
88
+ if (fileOps.exists(targetPath)) {
89
+ const overwrite = await ui.confirm(
79
90
  "Overwrite",
80
91
  `${targetPath} already exists. Overwrite?`,
81
92
  );
82
93
  if (!overwrite) return;
83
94
  }
84
95
 
85
- ctx.ui.notify("Generating agent definition...", "info");
96
+ ui.notify("Generating agent definition...", "info");
86
97
 
87
98
  const generatePrompt = `Create a custom pi sub-agent definition file based on this description: "${description}"
88
99
 
@@ -122,8 +133,8 @@ Guidelines for choosing settings:
122
133
 
123
134
  Write the file using the write tool. Only write the file, nothing else.`;
124
135
 
125
- const record = await deps.manager.spawnAndWait(
126
- ctx,
136
+ const record = await manager.spawnAndWait(
137
+ parentSnapshot,
127
138
  "general-purpose",
128
139
  generatePrompt,
129
140
  {
@@ -133,30 +144,30 @@ Write the file using the write tool. Only write the file, nothing else.`;
133
144
  );
134
145
 
135
146
  if (record.status === "error") {
136
- ctx.ui.notify(`Generation failed: ${record.error}`, "warning");
147
+ ui.notify(`Generation failed: ${record.error}`, "warning");
137
148
  return;
138
149
  }
139
150
 
140
- deps.registry.reload();
151
+ registry.reload();
141
152
 
142
- if (deps.fileOps.exists(targetPath)) {
143
- ctx.ui.notify(`Created ${targetPath}`, "info");
153
+ if (fileOps.exists(targetPath)) {
154
+ ui.notify(`Created ${targetPath}`, "info");
144
155
  } else {
145
- ctx.ui.notify(
156
+ ui.notify(
146
157
  "Agent generation completed but file was not created. Check the agent output.",
147
158
  "warning",
148
159
  );
149
160
  }
150
161
  }
151
162
 
152
- async function showManualWizard(ctx: ExtensionContext, targetDir: string) {
153
- const name = await ctx.ui.input("Agent name (filename, no spaces)");
163
+ async function showManualWizard(ui: MenuUI, targetDir: string) {
164
+ const name = await ui.input("Agent name (filename, no spaces)");
154
165
  if (!name) return;
155
166
 
156
- const description = await ctx.ui.input("Description (one line)");
167
+ const description = await ui.input("Description (one line)");
157
168
  if (!description) return;
158
169
 
159
- const toolChoice = await ctx.ui.select("Tools", [
170
+ const toolChoice = await ui.select("Tools", [
160
171
  "all",
161
172
  "none",
162
173
  "read-only (read, bash, grep, find, ls)",
@@ -172,7 +183,7 @@ Write the file using the write tool. Only write the file, nothing else.`;
172
183
  } else if (toolChoice.startsWith("read-only")) {
173
184
  tools = "read, bash, grep, find, ls";
174
185
  } else {
175
- const customTools = await ctx.ui.input(
186
+ const customTools = await ui.input(
176
187
  "Tools (comma-separated)",
177
188
  BUILTIN_TOOL_NAMES.join(", "),
178
189
  );
@@ -180,7 +191,7 @@ Write the file using the write tool. Only write the file, nothing else.`;
180
191
  tools = customTools;
181
192
  }
182
193
 
183
- const modelChoice = await ctx.ui.select("Model", [
194
+ const modelChoice = await ui.select("Model", [
184
195
  "inherit (parent model)",
185
196
  "haiku",
186
197
  "sonnet",
@@ -197,11 +208,11 @@ Write the file using the write tool. Only write the file, nothing else.`;
197
208
  else if (modelChoice === "opus")
198
209
  modelLine = "\nmodel: anthropic/claude-opus-4-6";
199
210
  else if (modelChoice === "custom...") {
200
- const customModel = await ctx.ui.input("Model (provider/modelId)");
211
+ const customModel = await ui.input("Model (provider/modelId)");
201
212
  if (customModel) modelLine = `\nmodel: ${customModel}`;
202
213
  }
203
214
 
204
- const thinkingChoice = await ctx.ui.select("Thinking level", [
215
+ const thinkingChoice = await ui.select("Thinking level", [
205
216
  "inherit",
206
217
  "off",
207
218
  "minimal",
@@ -215,7 +226,7 @@ Write the file using the write tool. Only write the file, nothing else.`;
215
226
  let thinkingLine = "";
216
227
  if (thinkingChoice !== "inherit") thinkingLine = `\nthinking: ${thinkingChoice}`;
217
228
 
218
- const systemPrompt = await ctx.ui.editor("System prompt", "");
229
+ const systemPrompt = await ui.editor("System prompt", "");
219
230
  if (systemPrompt === undefined) return;
220
231
 
221
232
  const content = `---
@@ -229,17 +240,17 @@ ${systemPrompt}
229
240
 
230
241
  const targetPath = join(targetDir, `${name}.md`);
231
242
 
232
- if (deps.fileOps.exists(targetPath)) {
233
- const overwrite = await ctx.ui.confirm(
243
+ if (fileOps.exists(targetPath)) {
244
+ const overwrite = await ui.confirm(
234
245
  "Overwrite",
235
246
  `${targetPath} already exists. Overwrite?`,
236
247
  );
237
248
  if (!overwrite) return;
238
249
  }
239
250
 
240
- deps.fileOps.write(targetPath, content);
241
- deps.registry.reload();
242
- ctx.ui.notify(`Created ${targetPath}`, "info");
251
+ fileOps.write(targetPath, content);
252
+ registry.reload();
253
+ ui.notify(`Created ${targetPath}`, "info");
243
254
  }
244
255
 
245
256
  return { showCreateWizard };