@pi-archimedes/subagent 1.0.0 → 1.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/package.json +2 -2
- package/src/agent-manager.ts +49 -14
- package/src/agents.ts +38 -6
- package/src/index.ts +2 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pi-archimedes/subagent",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi-package"
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
],
|
|
12
12
|
"main": "./src/index.ts",
|
|
13
13
|
"dependencies": {
|
|
14
|
-
"@pi-archimedes/core": "1.
|
|
14
|
+
"@pi-archimedes/core": "1.1.0"
|
|
15
15
|
},
|
|
16
16
|
"peerDependencies": {
|
|
17
17
|
"@earendil-works/pi-coding-agent": ">=0.1.0",
|
package/src/agent-manager.ts
CHANGED
|
@@ -54,8 +54,10 @@ interface ToolInfo {
|
|
|
54
54
|
interface ManagerState {
|
|
55
55
|
screen: "list" | "detail" | "edit" | "name-input" | "confirm-delete";
|
|
56
56
|
agents: AgentConfig[];
|
|
57
|
+
globalAgents: AgentConfig[];
|
|
57
58
|
userAgents: AgentConfig[];
|
|
58
59
|
projectAgents: AgentConfig[];
|
|
60
|
+
globalDir: string | null;
|
|
59
61
|
userDir: string;
|
|
60
62
|
projectDir: string | null;
|
|
61
63
|
|
|
@@ -86,7 +88,7 @@ interface ManagerState {
|
|
|
86
88
|
// Name input state
|
|
87
89
|
nameInputBuffer: string;
|
|
88
90
|
nameInputCursor: number;
|
|
89
|
-
nameInputScope: "user" | "project";
|
|
91
|
+
nameInputScope: "global" | "user" | "project";
|
|
90
92
|
nameInputMode: "new" | "clone";
|
|
91
93
|
nameInputSource: AgentConfig | null;
|
|
92
94
|
nameInputError: string | null;
|
|
@@ -188,7 +190,8 @@ function renderFooter(text: string, width: number, theme: Theme): string {
|
|
|
188
190
|
return theme.fg("dim", padEnd(text, width));
|
|
189
191
|
}
|
|
190
192
|
|
|
191
|
-
function scopeLabel(source: "user" | "project"): string {
|
|
193
|
+
function scopeLabel(source: "global" | "user" | "project"): string {
|
|
194
|
+
if (source === "global") return "home";
|
|
192
195
|
return source === "user" ? "user" : "proj";
|
|
193
196
|
}
|
|
194
197
|
|
|
@@ -1225,7 +1228,9 @@ function saveAgent(state: ManagerState, requestRender: () => void): void {
|
|
|
1225
1228
|
}
|
|
1226
1229
|
|
|
1227
1230
|
// Determine target directory
|
|
1228
|
-
const dir = agent.source === "
|
|
1231
|
+
const dir = agent.source === "global" ? state.globalDir
|
|
1232
|
+
: agent.source === "user" ? state.userDir
|
|
1233
|
+
: state.projectDir;
|
|
1229
1234
|
if (!dir) {
|
|
1230
1235
|
state.editError = "Target directory not available";
|
|
1231
1236
|
requestRender();
|
|
@@ -1259,9 +1264,11 @@ function saveAgent(state: ManagerState, requestRender: () => void): void {
|
|
|
1259
1264
|
// Refresh agents list
|
|
1260
1265
|
const cwd = process.cwd();
|
|
1261
1266
|
const discovery = discoverAgentsAll(cwd);
|
|
1267
|
+
state.globalAgents = discovery.global;
|
|
1262
1268
|
state.userAgents = discovery.user;
|
|
1263
1269
|
state.projectAgents = discovery.project;
|
|
1264
|
-
state.
|
|
1270
|
+
state.globalDir = discovery.globalDir;
|
|
1271
|
+
state.agents = [...discovery.global, ...discovery.user, ...discovery.project];
|
|
1265
1272
|
|
|
1266
1273
|
// Find the saved agent and switch to detail
|
|
1267
1274
|
const savedAgent = state.agents.find((a) => a.name === agent.name && a.source === agent.source);
|
|
@@ -1305,12 +1312,22 @@ function renderNameInput(state: ManagerState, width: number, theme: Theme): stri
|
|
|
1305
1312
|
const scopeText = `Scope: [${state.nameInputScope}] [tab] toggle`;
|
|
1306
1313
|
lines.push(padEnd(theme.fg("dim", scopeText), width));
|
|
1307
1314
|
|
|
1308
|
-
// Cross-scope collision warning
|
|
1309
|
-
const
|
|
1310
|
-
const
|
|
1311
|
-
|
|
1315
|
+
// Cross-scope collision warning — check higher-precedence scopes
|
|
1316
|
+
const scopeOrder = ["global", "user", "project"];
|
|
1317
|
+
const currentIdx = scopeOrder.indexOf(state.nameInputScope);
|
|
1318
|
+
let collisionAgent: AgentConfig | undefined;
|
|
1319
|
+
let collisionScope: string | undefined;
|
|
1320
|
+
for (const scope of scopeOrder.slice(currentIdx + 1)) {
|
|
1321
|
+
const scopeAgents = scope === "global" ? state.globalAgents : scope === "user" ? state.userAgents : state.projectAgents;
|
|
1322
|
+
const found = scopeAgents.find((a) => a.name === state.nameInputBuffer.trim());
|
|
1323
|
+
if (found) {
|
|
1324
|
+
collisionAgent = found;
|
|
1325
|
+
collisionScope = scope;
|
|
1326
|
+
break;
|
|
1327
|
+
}
|
|
1328
|
+
}
|
|
1312
1329
|
if (collisionAgent) {
|
|
1313
|
-
lines.push(padEnd(theme.fg("warning", `Warning: a ${
|
|
1330
|
+
lines.push(padEnd(theme.fg("warning", `Warning: a ${collisionScope} agent "${collisionAgent.name}" exists and will take precedence`), width));
|
|
1314
1331
|
} else if (state.nameInputError) {
|
|
1315
1332
|
lines.push(padEnd(theme.fg("error", ` ${state.nameInputError}`), width));
|
|
1316
1333
|
} else {
|
|
@@ -1329,8 +1346,13 @@ function handleNameInput(
|
|
|
1329
1346
|
requestRender: () => void,
|
|
1330
1347
|
): void {
|
|
1331
1348
|
if (matchesKey(data, Key.tab)) {
|
|
1332
|
-
|
|
1333
|
-
|
|
1349
|
+
const scopes = ["global", "user", "project"];
|
|
1350
|
+
const currentIdx = scopes.indexOf(state.nameInputScope);
|
|
1351
|
+
const nextIdx = (currentIdx + 1) % scopes.length;
|
|
1352
|
+
state.nameInputScope = scopes[nextIdx] as "global" | "user" | "project";
|
|
1353
|
+
if (state.nameInputScope === "global" && !state.globalDir) {
|
|
1354
|
+
state.nameInputError = "No global agents directory found";
|
|
1355
|
+
} else if (state.nameInputScope === "project" && !state.projectDir) {
|
|
1334
1356
|
state.nameInputError = "No project agents directory found";
|
|
1335
1357
|
} else {
|
|
1336
1358
|
state.nameInputError = null;
|
|
@@ -1363,6 +1385,11 @@ function handleNameInput(
|
|
|
1363
1385
|
requestRender();
|
|
1364
1386
|
return;
|
|
1365
1387
|
}
|
|
1388
|
+
if (state.nameInputScope === "global" && !state.globalDir) {
|
|
1389
|
+
state.nameInputError = "No global agents directory found";
|
|
1390
|
+
requestRender();
|
|
1391
|
+
return;
|
|
1392
|
+
}
|
|
1366
1393
|
if (state.nameInputScope === "project" && !state.projectDir) {
|
|
1367
1394
|
state.nameInputError = "No project agents directory found";
|
|
1368
1395
|
requestRender();
|
|
@@ -1379,7 +1406,9 @@ function handleNameInput(
|
|
|
1379
1406
|
return;
|
|
1380
1407
|
}
|
|
1381
1408
|
|
|
1382
|
-
const dir = state.nameInputScope === "
|
|
1409
|
+
const dir = state.nameInputScope === "global" ? state.globalDir
|
|
1410
|
+
: state.nameInputScope === "user" ? state.userDir
|
|
1411
|
+
: state.projectDir;
|
|
1383
1412
|
if (!dir) {
|
|
1384
1413
|
state.nameInputError = "Target directory not available";
|
|
1385
1414
|
requestRender();
|
|
@@ -1486,9 +1515,11 @@ function handleConfirmDelete(
|
|
|
1486
1515
|
// Refresh agents list
|
|
1487
1516
|
const cwd = process.cwd();
|
|
1488
1517
|
const discovery = discoverAgentsAll(cwd);
|
|
1518
|
+
state.globalAgents = discovery.global;
|
|
1489
1519
|
state.userAgents = discovery.user;
|
|
1490
1520
|
state.projectAgents = discovery.project;
|
|
1491
|
-
state.
|
|
1521
|
+
state.globalDir = discovery.globalDir;
|
|
1522
|
+
state.agents = [...discovery.global, ...discovery.user, ...discovery.project];
|
|
1492
1523
|
|
|
1493
1524
|
state.listCursor = 0;
|
|
1494
1525
|
state.listScroll = 0;
|
|
@@ -1506,8 +1537,10 @@ function handleConfirmDelete(
|
|
|
1506
1537
|
// ── Main factory ────────────────────────────────────────────────────────────
|
|
1507
1538
|
|
|
1508
1539
|
export function createAgentManager(
|
|
1540
|
+
globalAgents: AgentConfig[],
|
|
1509
1541
|
userAgents: AgentConfig[],
|
|
1510
1542
|
projectAgents: AgentConfig[],
|
|
1543
|
+
globalDir: string | null,
|
|
1511
1544
|
userDir: string,
|
|
1512
1545
|
projectDir: string | null,
|
|
1513
1546
|
tui: TUIContext,
|
|
@@ -1518,9 +1551,11 @@ export function createAgentManager(
|
|
|
1518
1551
|
): Component {
|
|
1519
1552
|
const state: ManagerState = {
|
|
1520
1553
|
screen: "list",
|
|
1521
|
-
agents: [...userAgents, ...projectAgents],
|
|
1554
|
+
agents: [...globalAgents, ...userAgents, ...projectAgents],
|
|
1555
|
+
globalAgents,
|
|
1522
1556
|
userAgents,
|
|
1523
1557
|
projectAgents,
|
|
1558
|
+
globalDir,
|
|
1524
1559
|
userDir,
|
|
1525
1560
|
projectDir,
|
|
1526
1561
|
|
package/src/agents.ts
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import * as fs from "node:fs";
|
|
7
|
+
import * as os from "node:os";
|
|
7
8
|
import * as path from "node:path";
|
|
8
9
|
import { getAgentDir, parseFrontmatter } from "@earendil-works/pi-coding-agent";
|
|
9
10
|
|
|
@@ -14,13 +15,13 @@ export interface AgentConfig {
|
|
|
14
15
|
model?: string;
|
|
15
16
|
thinking?: string;
|
|
16
17
|
systemPrompt: string;
|
|
17
|
-
source: "user" | "project";
|
|
18
|
+
source: "global" | "user" | "project";
|
|
18
19
|
filePath: string;
|
|
19
20
|
// Extra fields preserved from frontmatter but not editable in TUI
|
|
20
21
|
extraFields?: Record<string, unknown>;
|
|
21
22
|
}
|
|
22
23
|
|
|
23
|
-
function loadAgentsFromDir(dir: string, source: "user" | "project"): AgentConfig[] {
|
|
24
|
+
function loadAgentsFromDir(dir: string, source: "global" | "user" | "project"): AgentConfig[] {
|
|
24
25
|
const agents: AgentConfig[] = [];
|
|
25
26
|
|
|
26
27
|
if (!fs.existsSync(dir)) return agents;
|
|
@@ -102,28 +103,55 @@ function findNearestProjectAgentsDir(cwd: string): string | null {
|
|
|
102
103
|
}
|
|
103
104
|
}
|
|
104
105
|
|
|
106
|
+
function findNearestAgentsDir(cwd: string): string | null {
|
|
107
|
+
// Walk up looking for .agents/agents inside a git repo
|
|
108
|
+
let currentDir = cwd;
|
|
109
|
+
while (true) {
|
|
110
|
+
const gitPath = path.join(currentDir, ".git");
|
|
111
|
+
if (fs.existsSync(gitPath)) {
|
|
112
|
+
const projectAgentsDir = path.join(currentDir, ".agents", "agents");
|
|
113
|
+
if (fs.existsSync(projectAgentsDir)) return projectAgentsDir;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const parentDir = path.dirname(currentDir);
|
|
117
|
+
if (parentDir === currentDir) break;
|
|
118
|
+
currentDir = parentDir;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Fallback: ~/.agents/agents
|
|
122
|
+
const homeAgentsDir = path.join(os.homedir(), ".agents", "agents");
|
|
123
|
+
if (fs.existsSync(homeAgentsDir)) return homeAgentsDir;
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
|
|
105
127
|
/**
|
|
106
128
|
* Result of discovering agents — separated by source with directory paths.
|
|
107
129
|
*/
|
|
108
130
|
export interface AgentsDiscoveryResult {
|
|
131
|
+
global: AgentConfig[];
|
|
109
132
|
user: AgentConfig[];
|
|
110
133
|
project: AgentConfig[];
|
|
111
|
-
|
|
112
|
-
|
|
134
|
+
globalDir: string | null; // e.g., .agents/agents or null if not found
|
|
135
|
+
userDir: string; // e.g., ~/.pi/agent/agents
|
|
136
|
+
projectDir: string | null; // e.g., .pi/agents or null if not found
|
|
113
137
|
}
|
|
114
138
|
|
|
115
139
|
/**
|
|
116
|
-
* Discover all available agents from user and/or project directories.
|
|
140
|
+
* Discover all available agents from global, user and/or project directories.
|
|
141
|
+
* Precedence (highest last): global < user < project
|
|
117
142
|
*/
|
|
118
143
|
export function discoverAgents(cwd: string): AgentConfig[] {
|
|
144
|
+
const globalDir = findNearestAgentsDir(cwd);
|
|
119
145
|
const userDir = path.join(getAgentDir(), "agents");
|
|
120
146
|
const projectDir = findNearestProjectAgentsDir(cwd);
|
|
121
147
|
|
|
148
|
+
const globalAgents = globalDir ? loadAgentsFromDir(globalDir, "global") : [];
|
|
122
149
|
const userAgents = loadAgentsFromDir(userDir, "user");
|
|
123
150
|
const projectAgents = projectDir ? loadAgentsFromDir(projectDir, "project") : [];
|
|
124
151
|
|
|
125
|
-
//
|
|
152
|
+
// Later sources override earlier: global < user < project
|
|
126
153
|
const agentMap = new Map<string, AgentConfig>();
|
|
154
|
+
for (const agent of globalAgents) agentMap.set(agent.name, agent);
|
|
127
155
|
for (const agent of userAgents) agentMap.set(agent.name, agent);
|
|
128
156
|
for (const agent of projectAgents) agentMap.set(agent.name, agent);
|
|
129
157
|
|
|
@@ -134,15 +162,19 @@ export function discoverAgents(cwd: string): AgentConfig[] {
|
|
|
134
162
|
* Discover agents and return them separated by source with directory paths.
|
|
135
163
|
*/
|
|
136
164
|
export function discoverAgentsAll(cwd: string): AgentsDiscoveryResult {
|
|
165
|
+
const globalDir = findNearestAgentsDir(cwd);
|
|
137
166
|
const userDir = path.join(getAgentDir(), "agents");
|
|
138
167
|
const projectDir = findNearestProjectAgentsDir(cwd);
|
|
139
168
|
|
|
169
|
+
const globalAgents = globalDir ? loadAgentsFromDir(globalDir, "global") : [];
|
|
140
170
|
const userAgents = loadAgentsFromDir(userDir, "user");
|
|
141
171
|
const projectAgents = projectDir ? loadAgentsFromDir(projectDir, "project") : [];
|
|
142
172
|
|
|
143
173
|
return {
|
|
174
|
+
global: globalAgents,
|
|
144
175
|
user: userAgents,
|
|
145
176
|
project: projectAgents,
|
|
177
|
+
globalDir,
|
|
146
178
|
userDir,
|
|
147
179
|
projectDir,
|
|
148
180
|
};
|
package/src/index.ts
CHANGED
|
@@ -266,7 +266,7 @@ export function registerAgentsCommand(pi: ExtensionAPI): void {
|
|
|
266
266
|
pi.registerCommand("agents", {
|
|
267
267
|
description: "Open the Agents Manager",
|
|
268
268
|
handler: async (_args: string, ctx: ExtensionCommandContext) => {
|
|
269
|
-
const { user, project, userDir, projectDir } = discoverAgentsAll(ctx.cwd);
|
|
269
|
+
const { global: globalAgents, user, project, globalDir, userDir, projectDir } = discoverAgentsAll(ctx.cwd);
|
|
270
270
|
|
|
271
271
|
const availableModels = ctx.modelRegistry.getAvailable().map((m) => ({
|
|
272
272
|
id: m.id,
|
|
@@ -281,7 +281,7 @@ export function registerAgentsCommand(pi: ExtensionAPI): void {
|
|
|
281
281
|
|
|
282
282
|
await ctx.ui.custom<void>(
|
|
283
283
|
(tui: TUI, theme: Theme, _keybindings, done: () => void) => {
|
|
284
|
-
return createAgentManager(user, project, userDir, projectDir, tui, theme, done, availableModels, availableTools);
|
|
284
|
+
return createAgentManager(globalAgents, user, project, globalDir, userDir, projectDir, tui, theme, done, availableModels, availableTools);
|
|
285
285
|
},
|
|
286
286
|
{ overlay: true, overlayOptions: { anchor: "center", width: 84, maxHeight: "80%" } },
|
|
287
287
|
);
|