@compilr-dev/sdk 0.22.0 → 0.23.1
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/dist/team/custom-agents.d.ts +30 -0
- package/dist/team/custom-agents.js +51 -4
- package/dist/team/team-agent.d.ts +57 -0
- package/dist/team/team-agent.js +156 -49
- package/dist/team/types.d.ts +3 -0
- package/package.json +1 -1
|
@@ -33,6 +33,36 @@ export declare const CUSTOM_MASCOTS: string[];
|
|
|
33
33
|
* Avoids mascots already in use by other custom agents.
|
|
34
34
|
*/
|
|
35
35
|
export declare function assignMascot(existingAgents: CustomAgentDefinition[]): string;
|
|
36
|
+
/**
|
|
37
|
+
* The generated scaffold for a custom agent: who it is and what it sticks to.
|
|
38
|
+
*
|
|
39
|
+
* ⚠️ COMPOSED AT `TeamAgent.initialize()`, NEVER STORED in `systemPromptAddition`. That field
|
|
40
|
+
* is the user's own words and nothing else — a host that renders it in a textarea (both of ours
|
|
41
|
+
* do) must not be shown machine-written prose to hand-edit, and must not freeze it on save.
|
|
42
|
+
*/
|
|
43
|
+
export declare function generateCustomAgentScaffold(displayName: string, specialty: string): string;
|
|
44
|
+
/** What a legacy composite yielded up: the user's own words, and a personality to rescue. */
|
|
45
|
+
export interface LegacyCustomPromptParts {
|
|
46
|
+
/** The text the user actually typed, or undefined if they never typed any. */
|
|
47
|
+
userText?: string;
|
|
48
|
+
/** Personality recovered from a frozen `Your approach:` line — see below. */
|
|
49
|
+
personality?: string;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Split a pre-0.23 custom-agent composite back into the parts that belong to the user.
|
|
53
|
+
*
|
|
54
|
+
* Before this change `fromCustomDefinition` stored the whole generated composite in
|
|
55
|
+
* `systemPromptAddition`, so every agent already on disk has it frozen there. This reverses that
|
|
56
|
+
* for the fields we can recover without guessing.
|
|
57
|
+
*
|
|
58
|
+
* ⚠️ `personality` is recovered, not discarded. Legacy `fromCustomDefinition` never passed the
|
|
59
|
+
* definition's personality to the live agent, so for these agents the ONLY surviving copy is the
|
|
60
|
+
* `Your approach:` line inside this string. Dropping it would silently delete a value the user set.
|
|
61
|
+
*
|
|
62
|
+
* Returns `{}` when the string does not look like a composite, which is the signal to leave the
|
|
63
|
+
* stored value exactly as it is. Guessing is worse than migrating nothing.
|
|
64
|
+
*/
|
|
65
|
+
export declare function splitLegacyCustomPrompt(stored: string): LegacyCustomPromptParts;
|
|
36
66
|
/**
|
|
37
67
|
* Generate system prompt for a custom agent.
|
|
38
68
|
* Uses a template-based approach (no LLM call).
|
|
@@ -49,6 +49,57 @@ export function assignMascot(existingAgents) {
|
|
|
49
49
|
// =============================================================================
|
|
50
50
|
// System Prompt Generation
|
|
51
51
|
// =============================================================================
|
|
52
|
+
/**
|
|
53
|
+
* The generated scaffold for a custom agent: who it is and what it sticks to.
|
|
54
|
+
*
|
|
55
|
+
* ⚠️ COMPOSED AT `TeamAgent.initialize()`, NEVER STORED in `systemPromptAddition`. That field
|
|
56
|
+
* is the user's own words and nothing else — a host that renders it in a textarea (both of ours
|
|
57
|
+
* do) must not be shown machine-written prose to hand-edit, and must not freeze it on save.
|
|
58
|
+
*/
|
|
59
|
+
export function generateCustomAgentScaffold(displayName, specialty) {
|
|
60
|
+
return [
|
|
61
|
+
`You are a ${displayName} specialized in ${specialty}.`,
|
|
62
|
+
'',
|
|
63
|
+
'Focus on your area of expertise. When questions fall outside your specialty, suggest which team member might be better suited to help.',
|
|
64
|
+
].join('\n');
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Split a pre-0.23 custom-agent composite back into the parts that belong to the user.
|
|
68
|
+
*
|
|
69
|
+
* Before this change `fromCustomDefinition` stored the whole generated composite in
|
|
70
|
+
* `systemPromptAddition`, so every agent already on disk has it frozen there. This reverses that
|
|
71
|
+
* for the fields we can recover without guessing.
|
|
72
|
+
*
|
|
73
|
+
* ⚠️ `personality` is recovered, not discarded. Legacy `fromCustomDefinition` never passed the
|
|
74
|
+
* definition's personality to the live agent, so for these agents the ONLY surviving copy is the
|
|
75
|
+
* `Your approach:` line inside this string. Dropping it would silently delete a value the user set.
|
|
76
|
+
*
|
|
77
|
+
* Returns `{}` when the string does not look like a composite, which is the signal to leave the
|
|
78
|
+
* stored value exactly as it is. Guessing is worse than migrating nothing.
|
|
79
|
+
*/
|
|
80
|
+
export function splitLegacyCustomPrompt(stored) {
|
|
81
|
+
// The identity line is what makes this recognisable as generated. No line, no migration.
|
|
82
|
+
if (!/^You are a .+ specialized in .+\.$/m.test(stored))
|
|
83
|
+
return {};
|
|
84
|
+
const parts = {};
|
|
85
|
+
const approach = /^Your approach: (.+)$/m.exec(stored);
|
|
86
|
+
if (approach)
|
|
87
|
+
parts.personality = approach[1].trim();
|
|
88
|
+
const marker = '## Custom Instructions\n\n';
|
|
89
|
+
const at = stored.indexOf(marker);
|
|
90
|
+
if (at !== -1) {
|
|
91
|
+
let body = stored.slice(at + marker.length);
|
|
92
|
+
// Tool awareness, when present, was appended after a `---` rule. Everything from there on
|
|
93
|
+
// is generated too.
|
|
94
|
+
const rule = body.indexOf('\n\n---\n\n');
|
|
95
|
+
if (rule !== -1)
|
|
96
|
+
body = body.slice(0, rule);
|
|
97
|
+
const trimmed = body.trim();
|
|
98
|
+
if (trimmed)
|
|
99
|
+
parts.userText = trimmed;
|
|
100
|
+
}
|
|
101
|
+
return parts;
|
|
102
|
+
}
|
|
52
103
|
/**
|
|
53
104
|
* Generate system prompt for a custom agent.
|
|
54
105
|
* Uses a template-based approach (no LLM call).
|
|
@@ -56,10 +107,6 @@ export function assignMascot(existingAgents) {
|
|
|
56
107
|
*/
|
|
57
108
|
export function generateCustomAgentSystemPrompt(agent) {
|
|
58
109
|
const lines = [`You are a ${agent.displayName} specialized in ${agent.specialty}.`];
|
|
59
|
-
if (agent.personality) {
|
|
60
|
-
lines.push('');
|
|
61
|
-
lines.push(`Your approach: ${agent.personality}`);
|
|
62
|
-
}
|
|
63
110
|
lines.push('');
|
|
64
111
|
lines.push('Focus on your area of expertise. When questions fall outside your specialty, suggest which team member might be better suited to help.');
|
|
65
112
|
// Append custom instructions if provided
|
|
@@ -13,6 +13,38 @@ import type { ModelTier } from '../models/index.js';
|
|
|
13
13
|
import type { ProviderType } from '../config.js';
|
|
14
14
|
import type { SharedContextManager } from './shared-context.js';
|
|
15
15
|
import type { CustomAgentDefinition } from './custom-agents.js';
|
|
16
|
+
/**
|
|
17
|
+
* Strip the composite a pre-0.23 `fromRole()` stored, so it is not composed a second time.
|
|
18
|
+
*
|
|
19
|
+
* ⚠️ WITHOUT THIS, EVERY ROLE AGENT CREATED BEFORE 0.23 SENDS ITS ROLE PROMPT TWICE. The first
|
|
20
|
+
* cut of this migration skipped role agents, reasoning that an edited stored prompt is
|
|
21
|
+
* indistinguishable from an unedited one so removing anything risks the user's text. True, and
|
|
22
|
+
* beside the point: the generated sections now compose on top of whatever is stored, so leaving
|
|
23
|
+
* the legacy copy in place duplicates it. Measured on a real `$arch`: 13,024 characters instead
|
|
24
|
+
* of ~6,900, with the role header and capability blurb appearing twice each.
|
|
25
|
+
*
|
|
26
|
+
* ⚠️ AND IT CANNOT WORK BY RECONSTRUCTION. The second cut rebuilt the old composite exactly and
|
|
27
|
+
* stripped it on an exact match. That matched 6 of 16 real agents. The capability blurb lists
|
|
28
|
+
* the profile's tool GROUPS as they stood on the day the agent was created — a `qa` agent saved
|
|
29
|
+
* before the profile gained `consult` carries a blurb nine characters shorter than today's, and
|
|
30
|
+
* no amount of care reproduces a list that has since changed.
|
|
31
|
+
*
|
|
32
|
+
* So this anchors on the two ends, which are stable, and ignores the drifting middle entirely:
|
|
33
|
+
*
|
|
34
|
+
* - the FIRST LINE must be the first line of generated content (the role header, or the
|
|
35
|
+
* guidance heading for roles with no role prompt of their own), and
|
|
36
|
+
* - the CURRENT guidance block must appear — it is always last, and unlike the tool blurb it
|
|
37
|
+
* is not derived from mutable configuration.
|
|
38
|
+
*
|
|
39
|
+
* Everything up to the end of that guidance is generated; whatever follows is the user's and is
|
|
40
|
+
* kept. If either anchor is missing the value is returned untouched, so hand-written text and a
|
|
41
|
+
* future rewording both fail safe: the agent keeps its duplication until someone clears the box,
|
|
42
|
+
* and nobody loses text they wrote.
|
|
43
|
+
*
|
|
44
|
+
* @returns the user's own remainder, `undefined` if the stored value was entirely generated, or
|
|
45
|
+
* `stored` unchanged when it is not recognisably a legacy composite.
|
|
46
|
+
*/
|
|
47
|
+
export declare function splitLegacyRolePrompt(stored: string, role: AgentRole): string | undefined;
|
|
16
48
|
/**
|
|
17
49
|
* The editable subset of an agent.
|
|
18
50
|
*
|
|
@@ -147,6 +179,12 @@ export declare class TeamAgent {
|
|
|
147
179
|
* Tool profile name (for display)
|
|
148
180
|
*/
|
|
149
181
|
toolProfile?: TeamAgentConfig['toolProfile'];
|
|
182
|
+
/**
|
|
183
|
+
* Groups behind a `custom` profile. Needed because the capability blurb is now composed on
|
|
184
|
+
* every build rather than baked once, and a custom profile's blurb cannot be regenerated
|
|
185
|
+
* from the profile name alone.
|
|
186
|
+
*/
|
|
187
|
+
customGroups?: string[];
|
|
150
188
|
/**
|
|
151
189
|
* Enabled skills (empty = all skills)
|
|
152
190
|
*/
|
|
@@ -248,6 +286,25 @@ export declare class TeamAgent {
|
|
|
248
286
|
applyUpdate(patch: TeamAgentUpdate): {
|
|
249
287
|
rebuilt: boolean;
|
|
250
288
|
};
|
|
289
|
+
/**
|
|
290
|
+
* The generated half of the prompt, rebuilt from CURRENT state on every build.
|
|
291
|
+
*
|
|
292
|
+
* ⚠️ THIS IS WHY IT IS A METHOD AND NOT A STORED STRING. All three constructors used to bake
|
|
293
|
+
* these sections into `systemPromptAddition`, which broke three ways at once:
|
|
294
|
+
*
|
|
295
|
+
* 1. hosts render that field in a textarea, so users were shown generated prose to hand-edit
|
|
296
|
+
* (`fromRole('pm')` alone put 6,321 characters of role prompt in there) and their save
|
|
297
|
+
* froze it as if they had written it;
|
|
298
|
+
* 2. a custom agent's personality was baked in as `Your approach:` while an edit appended
|
|
299
|
+
* `Your manner:` — so editing it added a contradiction instead of changing it;
|
|
300
|
+
* 3. the capability blurb described whatever profile the agent had AT CREATION, so changing
|
|
301
|
+
* the tool profile updated the filter but left the prompt telling the agent it still had
|
|
302
|
+
* tools it no longer had.
|
|
303
|
+
*
|
|
304
|
+
* Composing here fixes all three: nothing generated is ever stored, and every section reflects
|
|
305
|
+
* the state at the moment the agent is built.
|
|
306
|
+
*/
|
|
307
|
+
private generatedPromptSections;
|
|
251
308
|
/**
|
|
252
309
|
* Initialize the agent with the given factory
|
|
253
310
|
* This allows the team to control agent creation
|
package/dist/team/team-agent.js
CHANGED
|
@@ -9,8 +9,60 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import { ROLE_METADATA } from './types.js';
|
|
11
11
|
import { getModelForTier, getModelContextWindow } from '../models/index.js';
|
|
12
|
-
import {
|
|
12
|
+
import { generateCustomAgentScaffold, getCustomAgentToolFilter, splitLegacyCustomPrompt, } from './custom-agents.js';
|
|
13
13
|
import { getToolsForProfile, PROFILE_INFO, generateToolAwarenessPrompt, generateCoordinatorGuidance, generateSpecialistGuidance, } from './tool-config.js';
|
|
14
|
+
/**
|
|
15
|
+
* Strip the composite a pre-0.23 `fromRole()` stored, so it is not composed a second time.
|
|
16
|
+
*
|
|
17
|
+
* ⚠️ WITHOUT THIS, EVERY ROLE AGENT CREATED BEFORE 0.23 SENDS ITS ROLE PROMPT TWICE. The first
|
|
18
|
+
* cut of this migration skipped role agents, reasoning that an edited stored prompt is
|
|
19
|
+
* indistinguishable from an unedited one so removing anything risks the user's text. True, and
|
|
20
|
+
* beside the point: the generated sections now compose on top of whatever is stored, so leaving
|
|
21
|
+
* the legacy copy in place duplicates it. Measured on a real `$arch`: 13,024 characters instead
|
|
22
|
+
* of ~6,900, with the role header and capability blurb appearing twice each.
|
|
23
|
+
*
|
|
24
|
+
* ⚠️ AND IT CANNOT WORK BY RECONSTRUCTION. The second cut rebuilt the old composite exactly and
|
|
25
|
+
* stripped it on an exact match. That matched 6 of 16 real agents. The capability blurb lists
|
|
26
|
+
* the profile's tool GROUPS as they stood on the day the agent was created — a `qa` agent saved
|
|
27
|
+
* before the profile gained `consult` carries a blurb nine characters shorter than today's, and
|
|
28
|
+
* no amount of care reproduces a list that has since changed.
|
|
29
|
+
*
|
|
30
|
+
* So this anchors on the two ends, which are stable, and ignores the drifting middle entirely:
|
|
31
|
+
*
|
|
32
|
+
* - the FIRST LINE must be the first line of generated content (the role header, or the
|
|
33
|
+
* guidance heading for roles with no role prompt of their own), and
|
|
34
|
+
* - the CURRENT guidance block must appear — it is always last, and unlike the tool blurb it
|
|
35
|
+
* is not derived from mutable configuration.
|
|
36
|
+
*
|
|
37
|
+
* Everything up to the end of that guidance is generated; whatever follows is the user's and is
|
|
38
|
+
* kept. If either anchor is missing the value is returned untouched, so hand-written text and a
|
|
39
|
+
* future rewording both fail safe: the agent keeps its duplication until someone clears the box,
|
|
40
|
+
* and nobody loses text they wrote.
|
|
41
|
+
*
|
|
42
|
+
* @returns the user's own remainder, `undefined` if the stored value was entirely generated, or
|
|
43
|
+
* `stored` unchanged when it is not recognisably a legacy composite.
|
|
44
|
+
*/
|
|
45
|
+
export function splitLegacyRolePrompt(stored, role) {
|
|
46
|
+
/*
|
|
47
|
+
⚠️ TYPED AS TOTAL, NOT TOTAL AT RUNTIME. `role` is `AgentRole` to the compiler but arrives
|
|
48
|
+
from a JSON file on disk, so it can be any string — a role removed in a later version, or one
|
|
49
|
+
a fixture invented. The explicit `| undefined` is what makes the guard below legitimate
|
|
50
|
+
rather than "always falsy": lint reasons from the type, and the type is a claim about data
|
|
51
|
+
we do not control.
|
|
52
|
+
*/
|
|
53
|
+
const byRole = ROLE_METADATA;
|
|
54
|
+
const metadata = byRole[role];
|
|
55
|
+
if (!metadata)
|
|
56
|
+
return stored;
|
|
57
|
+
const guidance = role === 'default' ? generateCoordinatorGuidance() : generateSpecialistGuidance(role);
|
|
58
|
+
const generatedFirstLine = (metadata.defaultSystemPromptAddition || guidance).split('\n')[0];
|
|
59
|
+
if (stored.split('\n')[0] !== generatedFirstLine)
|
|
60
|
+
return stored;
|
|
61
|
+
const at = stored.lastIndexOf(guidance);
|
|
62
|
+
if (at === -1)
|
|
63
|
+
return stored;
|
|
64
|
+
return stored.slice(at + guidance.length).trim() || undefined;
|
|
65
|
+
}
|
|
14
66
|
/**
|
|
15
67
|
* Estimate token count from text (approximate: ~4 chars per token)
|
|
16
68
|
*/
|
|
@@ -145,6 +197,12 @@ export class TeamAgent {
|
|
|
145
197
|
* Tool profile name (for display)
|
|
146
198
|
*/
|
|
147
199
|
toolProfile;
|
|
200
|
+
/**
|
|
201
|
+
* Groups behind a `custom` profile. Needed because the capability blurb is now composed on
|
|
202
|
+
* every build rather than baked once, and a custom profile's blurb cannot be regenerated
|
|
203
|
+
* from the profile name alone.
|
|
204
|
+
*/
|
|
205
|
+
customGroups;
|
|
148
206
|
/**
|
|
149
207
|
* Enabled skills (empty = all skills)
|
|
150
208
|
*/
|
|
@@ -202,6 +260,7 @@ export class TeamAgent {
|
|
|
202
260
|
this.noTools = config.noTools;
|
|
203
261
|
this.toolFilter = config.toolFilter;
|
|
204
262
|
this.toolProfile = config.toolProfile;
|
|
263
|
+
this.customGroups = config.customGroups;
|
|
205
264
|
this.enabledSkills = config.enabledSkills;
|
|
206
265
|
this.personality = config.personality;
|
|
207
266
|
this.autoApproveHandoff = config.autoApproveHandoff;
|
|
@@ -341,6 +400,48 @@ export class TeamAgent {
|
|
|
341
400
|
this.stashForRebuild();
|
|
342
401
|
return { rebuilt: needsRebuild || tierChanged };
|
|
343
402
|
}
|
|
403
|
+
/**
|
|
404
|
+
* The generated half of the prompt, rebuilt from CURRENT state on every build.
|
|
405
|
+
*
|
|
406
|
+
* ⚠️ THIS IS WHY IT IS A METHOD AND NOT A STORED STRING. All three constructors used to bake
|
|
407
|
+
* these sections into `systemPromptAddition`, which broke three ways at once:
|
|
408
|
+
*
|
|
409
|
+
* 1. hosts render that field in a textarea, so users were shown generated prose to hand-edit
|
|
410
|
+
* (`fromRole('pm')` alone put 6,321 characters of role prompt in there) and their save
|
|
411
|
+
* froze it as if they had written it;
|
|
412
|
+
* 2. a custom agent's personality was baked in as `Your approach:` while an edit appended
|
|
413
|
+
* `Your manner:` — so editing it added a contradiction instead of changing it;
|
|
414
|
+
* 3. the capability blurb described whatever profile the agent had AT CREATION, so changing
|
|
415
|
+
* the tool profile updated the filter but left the prompt telling the agent it still had
|
|
416
|
+
* tools it no longer had.
|
|
417
|
+
*
|
|
418
|
+
* Composing here fixes all three: nothing generated is ever stored, and every section reflects
|
|
419
|
+
* the state at the moment the agent is built.
|
|
420
|
+
*/
|
|
421
|
+
generatedPromptSections() {
|
|
422
|
+
const sections = [];
|
|
423
|
+
if (this.role === 'custom') {
|
|
424
|
+
sections.push(generateCustomAgentScaffold(this.displayName, this.description ?? ''));
|
|
425
|
+
}
|
|
426
|
+
else {
|
|
427
|
+
const roleScaffold = ROLE_METADATA[this.role].defaultSystemPromptAddition;
|
|
428
|
+
if (roleScaffold)
|
|
429
|
+
sections.push(roleScaffold);
|
|
430
|
+
}
|
|
431
|
+
// Capability blurb — from the live profile, never a baked copy (defect 3 above).
|
|
432
|
+
const profile = this.toolProfile;
|
|
433
|
+
if (profile && profile !== 'full') {
|
|
434
|
+
sections.push(generateToolAwarenessPrompt({ profile, customGroups: this.customGroups }));
|
|
435
|
+
}
|
|
436
|
+
// Delegation guidance. Custom agents never had it and still do not — this method reproduces
|
|
437
|
+
// what each constructor built, it does not redistribute behaviour between roles.
|
|
438
|
+
if (this.role !== 'custom') {
|
|
439
|
+
sections.push(this.role === 'default'
|
|
440
|
+
? generateCoordinatorGuidance()
|
|
441
|
+
: generateSpecialistGuidance(this.role));
|
|
442
|
+
}
|
|
443
|
+
return sections;
|
|
444
|
+
}
|
|
344
445
|
/**
|
|
345
446
|
* Initialize the agent with the given factory
|
|
346
447
|
* This allows the team to control agent creation
|
|
@@ -352,8 +453,13 @@ export class TeamAgent {
|
|
|
352
453
|
if (this._agent) {
|
|
353
454
|
return; // Already initialized
|
|
354
455
|
}
|
|
355
|
-
|
|
356
|
-
|
|
456
|
+
/*
|
|
457
|
+
Generated sections first, then the user's own words. `systemPromptAddition` holds ONLY what
|
|
458
|
+
the user typed — see `generatedPromptSections()` for why nothing generated is stored.
|
|
459
|
+
*/
|
|
460
|
+
let finalSystemPromptAddition = [...this.generatedPromptSections(), this.systemPromptAddition]
|
|
461
|
+
.filter((section) => Boolean(section?.trim()))
|
|
462
|
+
.join('\n\n');
|
|
357
463
|
// Identity preamble — every team agent gets a stable self-reference
|
|
358
464
|
// block at the top of its prompt addition. Without this, the agent
|
|
359
465
|
// knows it's "the PM" from the role prompt but doesn't connect that
|
|
@@ -575,6 +681,7 @@ export class TeamAgent {
|
|
|
575
681
|
noTools: this.noTools,
|
|
576
682
|
toolFilter: this.toolFilter,
|
|
577
683
|
toolProfile: this.toolProfile,
|
|
684
|
+
customGroups: this.customGroups,
|
|
578
685
|
enabledSkills: this.enabledSkills,
|
|
579
686
|
modelTier: this.modelTier,
|
|
580
687
|
autoApproveHandoff: this.autoApproveHandoff,
|
|
@@ -588,18 +695,49 @@ export class TeamAgent {
|
|
|
588
695
|
// Refresh tool filter from profile if available (ensures new tools are picked up)
|
|
589
696
|
// This handles the case where tools were added after the agent was created
|
|
590
697
|
const toolFilter = data.toolProfile ? getToolsForProfile(data.toolProfile) : data.toolFilter;
|
|
698
|
+
/*
|
|
699
|
+
MIGRATION — agents serialized before generated text was separated out.
|
|
700
|
+
Their `systemPromptAddition` holds the whole generated composite, because that is what the
|
|
701
|
+
constructors used to store. Recover the parts that are genuinely the user's.
|
|
702
|
+
|
|
703
|
+
⚠️ THE FAILURE MODE IS "CHANGE NOTHING", NEVER "GUESS". `splitLegacyCustomPrompt` returns an
|
|
704
|
+
empty result for anything it does not positively recognise, and we then keep the stored
|
|
705
|
+
string exactly as it is — which is the pre-migration behaviour. A generator that has since
|
|
706
|
+
changed wording costs one agent a stale prompt until its next edit; a clever guess would
|
|
707
|
+
cost the user the instructions they wrote.
|
|
708
|
+
|
|
709
|
+
Role agents are deliberately NOT migrated. Their stored string is generated in full, but an
|
|
710
|
+
edited one is indistinguishable from an unedited one, so anything removed might be the
|
|
711
|
+
user's. Their generated sections now compose on top of whatever is stored: a legacy role
|
|
712
|
+
agent shows its old prompt in the editor until someone clears it, and nothing is lost.
|
|
713
|
+
*/
|
|
714
|
+
let systemPromptAddition = data.systemPromptAddition;
|
|
715
|
+
let personality = data.personality;
|
|
716
|
+
if (data.role === 'custom' && systemPromptAddition) {
|
|
717
|
+
const legacy = splitLegacyCustomPrompt(systemPromptAddition);
|
|
718
|
+
if (legacy.userText !== undefined || legacy.personality !== undefined) {
|
|
719
|
+
systemPromptAddition = legacy.userText;
|
|
720
|
+
// Only rescue the baked personality when the live field never held one — a value set
|
|
721
|
+
// through the editor is newer than anything frozen in the prompt.
|
|
722
|
+
personality = personality ?? legacy.personality;
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
else if (data.role !== 'custom' && systemPromptAddition) {
|
|
726
|
+
systemPromptAddition = splitLegacyRolePrompt(systemPromptAddition, data.role);
|
|
727
|
+
}
|
|
591
728
|
const agent = new TeamAgent({
|
|
592
729
|
id: data.id,
|
|
593
730
|
displayName: data.displayName,
|
|
594
731
|
mascot: data.mascot,
|
|
595
732
|
role: data.role,
|
|
596
733
|
description: data.description,
|
|
597
|
-
systemPromptAddition
|
|
598
|
-
personality
|
|
734
|
+
systemPromptAddition,
|
|
735
|
+
personality,
|
|
599
736
|
useMinimalSystemPrompt: data.useMinimalSystemPrompt,
|
|
600
737
|
noTools: data.noTools,
|
|
601
738
|
toolFilter,
|
|
602
739
|
toolProfile: data.toolProfile,
|
|
740
|
+
customGroups: data.customGroups,
|
|
603
741
|
enabledSkills: data.enabledSkills,
|
|
604
742
|
modelTier: data.modelTier,
|
|
605
743
|
autoApproveHandoff: data.autoApproveHandoff,
|
|
@@ -619,36 +757,14 @@ export class TeamAgent {
|
|
|
619
757
|
// Get tool filter from default profile (if specified)
|
|
620
758
|
const profile = metadata.defaultToolProfile;
|
|
621
759
|
const toolFilter = profile ? getToolsForProfile(profile) : undefined;
|
|
622
|
-
//
|
|
623
|
-
|
|
624
|
-
if (profile && profile !== 'full') {
|
|
625
|
-
const toolAwareness = generateToolAwarenessPrompt({ profile });
|
|
626
|
-
systemPromptAddition = systemPromptAddition
|
|
627
|
-
? `${systemPromptAddition}\n\n${toolAwareness}`
|
|
628
|
-
: toolAwareness;
|
|
629
|
-
}
|
|
630
|
-
// Add delegation guidance based on role
|
|
631
|
-
if (role === 'default') {
|
|
632
|
-
// Coordinator gets delegation tool guidance
|
|
633
|
-
const coordinatorGuidance = generateCoordinatorGuidance();
|
|
634
|
-
systemPromptAddition = systemPromptAddition
|
|
635
|
-
? `${systemPromptAddition}\n\n${coordinatorGuidance}`
|
|
636
|
-
: coordinatorGuidance;
|
|
637
|
-
}
|
|
638
|
-
else {
|
|
639
|
-
// Specialists get collaboration guidance
|
|
640
|
-
const specialistGuidance = generateSpecialistGuidance(role);
|
|
641
|
-
systemPromptAddition = systemPromptAddition
|
|
642
|
-
? `${systemPromptAddition}\n\n${specialistGuidance}`
|
|
643
|
-
: specialistGuidance;
|
|
644
|
-
}
|
|
760
|
+
// The role prompt, capability blurb and delegation guidance are all generated, so they are
|
|
761
|
+
// composed at initialize() rather than stored. A fresh role agent has no user text at all.
|
|
645
762
|
return new TeamAgent({
|
|
646
763
|
id: id ?? role,
|
|
647
764
|
displayName: metadata.displayName,
|
|
648
765
|
mascot: metadata.mascot,
|
|
649
766
|
role,
|
|
650
767
|
description: metadata.description,
|
|
651
|
-
systemPromptAddition: systemPromptAddition || undefined,
|
|
652
768
|
useMinimalSystemPrompt: metadata.useMinimalSystemPrompt,
|
|
653
769
|
noTools: metadata.noTools,
|
|
654
770
|
toolFilter,
|
|
@@ -667,31 +783,15 @@ export class TeamAgent {
|
|
|
667
783
|
const metadata = ROLE_METADATA[baseRole];
|
|
668
784
|
const profile = opts.toolProfile ?? metadata.defaultToolProfile;
|
|
669
785
|
const toolFilter = profile ? getToolsForProfile(profile) : undefined;
|
|
670
|
-
//
|
|
671
|
-
|
|
672
|
-
if (opts.systemPromptAddition?.trim()) {
|
|
673
|
-
systemPromptAddition = systemPromptAddition
|
|
674
|
-
? `${systemPromptAddition}\n\n${opts.systemPromptAddition.trim()}`
|
|
675
|
-
: opts.systemPromptAddition.trim();
|
|
676
|
-
}
|
|
677
|
-
if (profile && profile !== 'full') {
|
|
678
|
-
const toolAwareness = generateToolAwarenessPrompt({ profile });
|
|
679
|
-
systemPromptAddition = systemPromptAddition
|
|
680
|
-
? `${systemPromptAddition}\n\n${toolAwareness}`
|
|
681
|
-
: toolAwareness;
|
|
682
|
-
}
|
|
683
|
-
// Specialists always get collaboration guidance (never the coordinator path).
|
|
684
|
-
const specialistGuidance = generateSpecialistGuidance(baseRole);
|
|
685
|
-
systemPromptAddition = systemPromptAddition
|
|
686
|
-
? `${systemPromptAddition}\n\n${specialistGuidance}`
|
|
687
|
-
: specialistGuidance;
|
|
786
|
+
// Only the specialist's own addition is authored text; the role prompt, capability blurb and
|
|
787
|
+
// collaboration guidance are generated and composed at initialize().
|
|
688
788
|
return new TeamAgent({
|
|
689
789
|
id: opts.id ?? baseRole,
|
|
690
790
|
displayName: opts.displayName ?? metadata.displayName,
|
|
691
791
|
mascot: metadata.mascot,
|
|
692
792
|
role: baseRole,
|
|
693
793
|
description: metadata.description,
|
|
694
|
-
systemPromptAddition: systemPromptAddition || undefined,
|
|
794
|
+
systemPromptAddition: opts.systemPromptAddition?.trim() || undefined,
|
|
695
795
|
useMinimalSystemPrompt: metadata.useMinimalSystemPrompt,
|
|
696
796
|
noTools: metadata.noTools,
|
|
697
797
|
toolFilter,
|
|
@@ -711,9 +811,16 @@ export class TeamAgent {
|
|
|
711
811
|
mascot: def.mascot,
|
|
712
812
|
role: 'custom',
|
|
713
813
|
description: def.specialty,
|
|
714
|
-
|
|
814
|
+
// The user's own words only — the identity and focus lines are composed at initialize().
|
|
815
|
+
systemPromptAddition: def.systemPromptAddition,
|
|
816
|
+
/* ⚠️ WITHOUT THIS an edit ADDS a second personality instead of changing the one in force:
|
|
817
|
+
the definition's value was baked into the prompt as `Your approach:` and never reached
|
|
818
|
+
the live field, so `updateAgent({ personality })` appended a contradicting `Your manner:`
|
|
819
|
+
while the original stayed active. */
|
|
820
|
+
personality: def.personality,
|
|
715
821
|
toolFilter, // Pass tool filter from custom agent config
|
|
716
822
|
toolProfile: def.toolConfig?.profile, // Store profile for display
|
|
823
|
+
customGroups: def.toolConfig?.customGroups,
|
|
717
824
|
enabledSkills: def.enabledSkills, // Store skills for display
|
|
718
825
|
modelTier: def.modelTier, // Store model tier
|
|
719
826
|
});
|
package/dist/team/types.d.ts
CHANGED
|
@@ -135,6 +135,8 @@ export interface TeamAgentConfig {
|
|
|
135
135
|
* Shows which profile was selected when creating the agent
|
|
136
136
|
*/
|
|
137
137
|
toolProfile?: ToolProfile;
|
|
138
|
+
/** Groups behind a `custom` profile — see TeamAgent.customGroups. */
|
|
139
|
+
customGroups?: string[];
|
|
138
140
|
/**
|
|
139
141
|
* Enabled skills for this agent
|
|
140
142
|
* Empty array means all skills, non-empty means filtered
|
|
@@ -173,6 +175,7 @@ export interface SerializedTeamAgent {
|
|
|
173
175
|
noTools?: boolean;
|
|
174
176
|
toolFilter?: string[];
|
|
175
177
|
toolProfile?: ToolProfile;
|
|
178
|
+
customGroups?: string[];
|
|
176
179
|
enabledSkills?: string[];
|
|
177
180
|
modelTier?: ModelTier;
|
|
178
181
|
autoApproveHandoff?: boolean;
|