@pi-unipi/subagents 0.2.3 → 0.2.5
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 +1 -1
- package/src/__tests__/badge-generation.test.ts +73 -2
- package/src/agent-runner.ts +19 -11
- package/src/index.ts +18 -14
- package/src/types.ts +11 -0
package/package.json
CHANGED
|
@@ -60,9 +60,14 @@ describe("Badge generation — tool availability", () => {
|
|
|
60
60
|
// ─── Test: Prompt no longer references non-existent tool ───────────
|
|
61
61
|
|
|
62
62
|
describe("Badge generation — prompt fix", () => {
|
|
63
|
-
it("prompt
|
|
63
|
+
it("prompt includes conversation context inline", () => {
|
|
64
64
|
const src = readSource("packages/subagents/src/index.ts");
|
|
65
65
|
|
|
66
|
+
assert.ok(
|
|
67
|
+
src.includes("Conversation:"),
|
|
68
|
+
"Prompt should include conversation context inline",
|
|
69
|
+
);
|
|
70
|
+
|
|
66
71
|
assert.ok(
|
|
67
72
|
src.includes("Reply with ONLY the title"),
|
|
68
73
|
"Prompt should ask agent to reply with only the title",
|
|
@@ -93,6 +98,56 @@ describe("Badge generation — onComplete callback", () => {
|
|
|
93
98
|
});
|
|
94
99
|
});
|
|
95
100
|
|
|
101
|
+
// ─── Test: Agent configuration ────────────────────────────────────
|
|
102
|
+
|
|
103
|
+
describe("Badge generation — agent configuration", () => {
|
|
104
|
+
it("badge generation uses 'name-gen' agent type (not 'explore')", () => {
|
|
105
|
+
const src = readSource("packages/subagents/src/index.ts");
|
|
106
|
+
|
|
107
|
+
assert.ok(
|
|
108
|
+
src.includes('manager.spawn(pi, sessionCtx, "name-gen", prompt'),
|
|
109
|
+
"Badge generation should spawn a 'name-gen' agent",
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
assert.ok(
|
|
113
|
+
!src.includes('manager.spawn(pi, sessionCtx, "explore"'),
|
|
114
|
+
"Should NOT use 'explore' — that type can be overridden by user custom agents",
|
|
115
|
+
);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it("'name-gen' type is defined in BUILTIN_CONFIGS with empty tools", () => {
|
|
119
|
+
const src = readSource("packages/subagents/src/types.ts");
|
|
120
|
+
|
|
121
|
+
assert.ok(
|
|
122
|
+
src.includes('"name-gen"'),
|
|
123
|
+
"types.ts should define name-gen agent config",
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
assert.ok(
|
|
127
|
+
src.includes('builtinToolNames: []'),
|
|
128
|
+
"name-gen should have empty tool list",
|
|
129
|
+
);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it("background agent is isolated (no extensions, no skills, minimal system prompt)", () => {
|
|
133
|
+
const src = readSource("packages/subagents/src/index.ts");
|
|
134
|
+
|
|
135
|
+
assert.ok(
|
|
136
|
+
src.includes("isolated: true"),
|
|
137
|
+
"Should spawn with isolated: true to avoid loading extensions/skills",
|
|
138
|
+
);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it("background agent maxTurns is 1 (single response)", () => {
|
|
142
|
+
const src = readSource("packages/subagents/src/index.ts");
|
|
143
|
+
|
|
144
|
+
assert.ok(
|
|
145
|
+
src.includes("maxTurns: 1"),
|
|
146
|
+
"Badge generation agent should have maxTurns: 1",
|
|
147
|
+
);
|
|
148
|
+
});
|
|
149
|
+
});
|
|
150
|
+
|
|
96
151
|
// ─── Test: Cross-module event bus — the critical fix ───────────────
|
|
97
152
|
|
|
98
153
|
describe("Badge generation — event bus (CRITICAL FIX)", () => {
|
|
@@ -150,6 +205,7 @@ describe("Badge generation — event bus (CRITICAL FIX)", () => {
|
|
|
150
205
|
const validLifecycleEvents = [
|
|
151
206
|
"session_start", "session_shutdown", "input",
|
|
152
207
|
"tool_call", "tool_execution_start",
|
|
208
|
+
"agent_end", "before_agent_start",
|
|
153
209
|
];
|
|
154
210
|
|
|
155
211
|
// Check that pi.on() is only used with lifecycle events
|
|
@@ -173,11 +229,26 @@ describe("Badge generation — event bus (CRITICAL FIX)", () => {
|
|
|
173
229
|
// ─── Test: Event flow ──────────────────────────────────────────────
|
|
174
230
|
|
|
175
231
|
describe("Badge generation — event flow", () => {
|
|
176
|
-
it("utility emits BADGE_GENERATE_REQUEST
|
|
232
|
+
it("utility emits BADGE_GENERATE_REQUEST after agent responds (deferred from input)", () => {
|
|
177
233
|
const src = readSource("packages/utility/src/index.ts");
|
|
178
234
|
|
|
235
|
+
// BADGE_GENERATE_REQUEST should be emitted in agent_end handler, not input
|
|
179
236
|
assert.ok(src.includes("BADGE_GENERATE_REQUEST"));
|
|
180
237
|
assert.ok(src.includes('source: "input-hook"'));
|
|
238
|
+
|
|
239
|
+
// input handler should NOT emit BADGE_GENERATE_REQUEST directly
|
|
240
|
+
const inputBlock = src.match(/pi\.on\("input"[\s\S]*?(?=pi\.on\(|$)/)?.[0] ?? "";
|
|
241
|
+
assert.ok(
|
|
242
|
+
!inputBlock.includes("BADGE_GENERATE_REQUEST"),
|
|
243
|
+
"input handler should NOT emit BADGE_GENERATE_REQUEST — deferred to agent_end",
|
|
244
|
+
);
|
|
245
|
+
|
|
246
|
+
// agent_end handler should emit BADGE_GENERATE_REQUEST
|
|
247
|
+
const agentEndBlock = src.match(/pi\.on\("agent_end"[\s\S]*?(?=pi\.on\(|$)/)?.[0] ?? "";
|
|
248
|
+
assert.ok(
|
|
249
|
+
agentEndBlock.includes("BADGE_GENERATE_REQUEST"),
|
|
250
|
+
"agent_end handler should emit BADGE_GENERATE_REQUEST with full conversation context",
|
|
251
|
+
);
|
|
181
252
|
});
|
|
182
253
|
|
|
183
254
|
it("BADGE_GENERATE_REQUEST event is defined in core", () => {
|
package/src/agent-runner.ts
CHANGED
|
@@ -172,12 +172,16 @@ export async function runAgent(
|
|
|
172
172
|
let toolNames = getToolNamesForType(type, agentConfig);
|
|
173
173
|
|
|
174
174
|
// Create resource loader
|
|
175
|
+
// Respect agentConfig.extensions/skills flags: if explicitly false, skip loading.
|
|
176
|
+
// This prevents explore/work agents from loading all parent extensions.
|
|
175
177
|
const agentDir = getAgentDir();
|
|
178
|
+
const skipExtensions = options.isolated || agentConfig?.extensions === false;
|
|
179
|
+
const skipSkills = options.isolated || agentConfig?.skills === false;
|
|
176
180
|
const loader = new DefaultResourceLoader({
|
|
177
181
|
cwd: effectiveCwd,
|
|
178
182
|
agentDir,
|
|
179
|
-
noExtensions:
|
|
180
|
-
noSkills:
|
|
183
|
+
noExtensions: skipExtensions,
|
|
184
|
+
noSkills: skipSkills,
|
|
181
185
|
noPromptTemplates: true,
|
|
182
186
|
noThemes: true,
|
|
183
187
|
noContextFiles: true,
|
|
@@ -213,15 +217,19 @@ export async function runAgent(
|
|
|
213
217
|
});
|
|
214
218
|
session.setActiveToolsByName(activeTools);
|
|
215
219
|
|
|
216
|
-
// Bind extensions
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
220
|
+
// Bind extensions — only if extensions were loaded.
|
|
221
|
+
// Skipping for agents with extensions: false avoids firing session_start
|
|
222
|
+
// on an empty extension set, preventing unnecessary MODULE_READY cascade.
|
|
223
|
+
if (!skipExtensions) {
|
|
224
|
+
await session.bindExtensions({
|
|
225
|
+
onError: (err) => {
|
|
226
|
+
options.onToolActivity?.({
|
|
227
|
+
type: "end",
|
|
228
|
+
toolName: `extension-error:${err.extensionPath}`,
|
|
229
|
+
});
|
|
230
|
+
},
|
|
231
|
+
});
|
|
232
|
+
}
|
|
225
233
|
|
|
226
234
|
options.onSessionCreated?.(session);
|
|
227
235
|
|
package/src/index.ts
CHANGED
|
@@ -148,6 +148,19 @@ export default function (pi: ExtensionAPI) {
|
|
|
148
148
|
// Build notification details
|
|
149
149
|
const details = buildNotificationDetails(record, agentActivity.get(record.id));
|
|
150
150
|
|
|
151
|
+
// Badge generation: extract name from agent result and set directly.
|
|
152
|
+
// Mark resultConsumed BEFORE the notification check so the main agent
|
|
153
|
+
// never sees this subagent.
|
|
154
|
+
if (record.description === "Generate session name" && record.result && record.status === "completed") {
|
|
155
|
+
const name = record.result.split("\n")[0]?.trim().slice(0, 50) ?? "";
|
|
156
|
+
if (name && !name.startsWith("Error") && !name.includes("error")) {
|
|
157
|
+
try {
|
|
158
|
+
pi.setSessionName(name);
|
|
159
|
+
} catch { /* best effort */ }
|
|
160
|
+
}
|
|
161
|
+
record.resultConsumed = true;
|
|
162
|
+
}
|
|
163
|
+
|
|
151
164
|
// Send styled notification via message renderer
|
|
152
165
|
const status = getStatusLabel(record.status, record.error);
|
|
153
166
|
const durationMs = record.completedAt ? record.completedAt - record.startedAt : 0;
|
|
@@ -179,16 +192,6 @@ export default function (pi: ExtensionAPI) {
|
|
|
179
192
|
);
|
|
180
193
|
}
|
|
181
194
|
|
|
182
|
-
// Badge generation: extract name from agent result and set directly
|
|
183
|
-
if (record.description === "Generate session name" && record.result && record.status === "completed") {
|
|
184
|
-
const name = record.result.split("\n")[0]?.trim().slice(0, 50) ?? "";
|
|
185
|
-
if (name && !name.startsWith("Error") && !name.includes("error")) {
|
|
186
|
-
try {
|
|
187
|
-
pi.setSessionName(name);
|
|
188
|
-
} catch { /* best effort */ }
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
|
|
192
195
|
pi.events.emit("subagents:completed", {
|
|
193
196
|
id: record.id,
|
|
194
197
|
type: record.type,
|
|
@@ -354,8 +357,8 @@ export default function (pi: ExtensionAPI) {
|
|
|
354
357
|
|
|
355
358
|
const summary = event?.conversationSummary ?? "";
|
|
356
359
|
const prompt = summary
|
|
357
|
-
? `
|
|
358
|
-
: `Generate a concise session title (MAX 5 WORDS) for
|
|
360
|
+
? `Based on this conversation, generate a concise session title (MAX 5 WORDS). Reply with ONLY the title. No quotes, no explanation, no punctuation.\n\nConversation:\n${summary}`
|
|
361
|
+
: `Generate a concise session title (MAX 5 WORDS) for this session. Reply with ONLY the title. No quotes, no explanation, no punctuation.`;
|
|
359
362
|
|
|
360
363
|
// Try with configured model, fallback to inherit
|
|
361
364
|
let modelInput: string | undefined = undefined;
|
|
@@ -382,11 +385,12 @@ export default function (pi: ExtensionAPI) {
|
|
|
382
385
|
// If result is a string (error), resolvedModel stays undefined → inherit parent
|
|
383
386
|
}
|
|
384
387
|
|
|
385
|
-
manager.spawn(pi, sessionCtx, "
|
|
388
|
+
manager.spawn(pi, sessionCtx, "name-gen", prompt, {
|
|
386
389
|
description: "Generate session name",
|
|
387
390
|
model: resolvedModel,
|
|
388
391
|
isBackground: true,
|
|
389
|
-
|
|
392
|
+
isolated: true,
|
|
393
|
+
maxTurns: 1,
|
|
390
394
|
});
|
|
391
395
|
});
|
|
392
396
|
|
package/src/types.ts
CHANGED
|
@@ -44,6 +44,17 @@ export const BUILTIN_CONFIGS: Record<string, AgentConfig> = {
|
|
|
44
44
|
promptMode: "append",
|
|
45
45
|
source: "builtin",
|
|
46
46
|
},
|
|
47
|
+
"name-gen": {
|
|
48
|
+
name: "name-gen",
|
|
49
|
+
displayName: "Name Generator",
|
|
50
|
+
description: "Minimal agent for generating session names from conversation context.",
|
|
51
|
+
builtinToolNames: [],
|
|
52
|
+
extensions: false,
|
|
53
|
+
skills: false,
|
|
54
|
+
systemPrompt: "You are a session name generator. Generate concise titles from conversation context. Reply with ONLY the title.",
|
|
55
|
+
promptMode: "replace",
|
|
56
|
+
source: "builtin",
|
|
57
|
+
},
|
|
47
58
|
} as const;
|
|
48
59
|
|
|
49
60
|
/** Memory scope for persistent agent memory. */
|