@compilr-dev/cli 0.8.10 → 0.9.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/CHANGELOG.md +35 -0
- package/dist/agent.d.ts +9 -0
- package/dist/agent.js +1 -0
- package/dist/commands-v2/handlers/project.js +88 -229
- package/dist/commands-v2/handlers/skill.js +10 -6
- package/dist/commands-v2/handlers/team.d.ts +19 -1
- package/dist/commands-v2/handlers/team.js +178 -3
- package/dist/compilr-diff-companion.vsix +0 -0
- package/dist/index.js +55 -3
- package/dist/multi-agent/agent-edit.d.ts +75 -0
- package/dist/multi-agent/agent-edit.js +84 -0
- package/dist/multi-agent/capability-loader.d.ts +15 -5
- package/dist/multi-agent/capability-loader.js +18 -8
- package/dist/multi-agent/custom-agents.d.ts +12 -0
- package/dist/multi-agent/custom-agents.js +22 -1
- package/dist/repl-helpers.d.ts +6 -1
- package/dist/repl-helpers.js +14 -7
- package/dist/repl-v2.d.ts +10 -0
- package/dist/repl-v2.js +43 -2
- package/dist/ui/overlay/impl/agent-edit-overlay-v2.d.ts +105 -0
- package/dist/ui/overlay/impl/agent-edit-overlay-v2.js +364 -0
- package/dist/ui/overlay/impl/custom-agent-form-overlay-v2.d.ts +1 -8
- package/dist/ui/overlay/impl/custom-agent-form-overlay-v2.js +16 -120
- package/dist/ui/overlay/impl/project-edit-overlay-v2.d.ts +2 -1
- package/dist/ui/overlay/impl/project-edit-overlay-v2.js +26 -19
- package/dist/ui/overlay/impl/skills-overlay-v2.d.ts +42 -2
- package/dist/ui/overlay/impl/skills-overlay-v2.js +238 -185
- package/dist/ui/overlay/impl/team-overlay-v2.d.ts +1 -2
- package/dist/ui/overlay/impl/team-overlay-v2.js +6 -9
- package/dist/ui/terminal-types.d.ts +8 -0
- package/dist/utils/model-tiers.js +5 -0
- package/package.json +2 -2
|
@@ -6,9 +6,11 @@
|
|
|
6
6
|
import pc from 'picocolors';
|
|
7
7
|
import { TeamOverlayV2 } from '../../ui/overlay/impl/team-overlay-v2.js';
|
|
8
8
|
import { CustomAgentFormOverlayV2 } from '../../ui/overlay/impl/custom-agent-form-overlay-v2.js';
|
|
9
|
+
import { AgentEditOverlayV2 } from '../../ui/overlay/impl/agent-edit-overlay-v2.js';
|
|
9
10
|
import { ArtifactOverlayV2, ArtifactDetailOverlayV2 } from '../../ui/overlay/index.js';
|
|
10
11
|
import { ROLE_METADATA, PREDEFINED_ROLE_IDS } from '../../multi-agent/index.js';
|
|
11
|
-
import { loadCustomAgents, createCustomAgentDefinition, addCustomAgent, removeCustomAgent, } from '../../multi-agent/custom-agents.js';
|
|
12
|
+
import { loadCustomAgents, createCustomAgentDefinition, addCustomAgent, removeCustomAgent, updateCustomAgent, } from '../../multi-agent/custom-agents.js';
|
|
13
|
+
import { readEditableInstructions, buildAgentPatch, } from '../../multi-agent/agent-edit.js';
|
|
12
14
|
import { getCurrentProject } from '../../tools/project-db.js';
|
|
13
15
|
import { listTemplates, saveTemplate } from '@compilr-dev/sdk';
|
|
14
16
|
import { getSessionRegistry } from '../../multi-agent/session-registry.js';
|
|
@@ -276,8 +278,182 @@ async function openTeamOverlay(ctx, team) {
|
|
|
276
278
|
});
|
|
277
279
|
}
|
|
278
280
|
}
|
|
281
|
+
else if (result?.action === 'edit' && result.agentId) {
|
|
282
|
+
await handleEditAgent(ctx, team, result.agentId);
|
|
283
|
+
}
|
|
279
284
|
return true;
|
|
280
285
|
}
|
|
286
|
+
/**
|
|
287
|
+
* Edit an existing team agent.
|
|
288
|
+
*
|
|
289
|
+
* ⚠️ THE INSTRUCTIONS FIELD IS NOT THE SAME FIELD FOR BOTH AGENT KINDS, and reading the
|
|
290
|
+
* wrong one moves the user's data. A custom agent's live `systemPromptAddition` is the
|
|
291
|
+
* whole GENERATED prompt ("You are a X specialized in Y. …"), not the instructions the
|
|
292
|
+
* user typed — those live on the `CustomAgentDefinition` on disk. So:
|
|
293
|
+
*
|
|
294
|
+
* custom agent → read/write `CustomAgentDefinition.systemPromptAddition`, then
|
|
295
|
+
* regenerate the live prompt around it
|
|
296
|
+
* role agent → read/write the live `TeamAgent.systemPromptAddition`, which for that
|
|
297
|
+
* kind genuinely IS free text
|
|
298
|
+
*
|
|
299
|
+
* Loading one and saving the other is an asymmetric round trip: it does not look like a
|
|
300
|
+
* bug until the data has already moved.
|
|
301
|
+
*/
|
|
302
|
+
// Exported for tests: the restoreAgent step below is the one failure in this flow that
|
|
303
|
+
// cannot be seen from the outside, so it is worth exercising directly.
|
|
304
|
+
export async function handleEditAgent(ctx, team, agentId) {
|
|
305
|
+
const teamAgent = team.get(agentId);
|
|
306
|
+
if (!teamAgent) {
|
|
307
|
+
ctx.ui.print({ type: 'error', message: `Agent '${agentId}' not found` });
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
const isCustom = teamAgent.role === 'custom';
|
|
311
|
+
const projectPath = getCurrentProject()?.path;
|
|
312
|
+
/*
|
|
313
|
+
A custom agent's editable text lives in its definition; without the project we cannot
|
|
314
|
+
read it, and editing the generated prompt instead is the trap above. Path and definition
|
|
315
|
+
are kept together in one value so "this is a custom agent we can actually write" is a
|
|
316
|
+
single check later, rather than two that could drift apart.
|
|
317
|
+
*/
|
|
318
|
+
let custom = null;
|
|
319
|
+
if (isCustom) {
|
|
320
|
+
if (!projectPath) {
|
|
321
|
+
ctx.ui.print({
|
|
322
|
+
type: 'warning',
|
|
323
|
+
message: 'Select a project first to edit custom agents',
|
|
324
|
+
});
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
327
|
+
const definition = loadCustomAgents(projectPath).find((a) => a.id === agentId);
|
|
328
|
+
if (!definition) {
|
|
329
|
+
ctx.ui.print({
|
|
330
|
+
type: 'error',
|
|
331
|
+
message: `No stored definition for '${agentId}' — it cannot be edited from here.`,
|
|
332
|
+
});
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
custom = { path: projectPath, definition };
|
|
336
|
+
}
|
|
337
|
+
const originals = {
|
|
338
|
+
displayName: teamAgent.displayName,
|
|
339
|
+
specialty: teamAgent.description ?? '',
|
|
340
|
+
instructions: readEditableInstructions(teamAgent.systemPromptAddition, custom?.definition),
|
|
341
|
+
mascot: teamAgent.mascot,
|
|
342
|
+
personality: teamAgent.personality ?? '',
|
|
343
|
+
modelTier: teamAgent.modelTier ?? 'balanced',
|
|
344
|
+
toolProfile: teamAgent.toolProfile ?? 'full',
|
|
345
|
+
systemPromptAddition: teamAgent.systemPromptAddition ?? '',
|
|
346
|
+
};
|
|
347
|
+
const overlay = new AgentEditOverlayV2({
|
|
348
|
+
agentId,
|
|
349
|
+
displayName: originals.displayName,
|
|
350
|
+
mascot: originals.mascot,
|
|
351
|
+
specialty: originals.specialty,
|
|
352
|
+
personality: originals.personality,
|
|
353
|
+
instructions: originals.instructions,
|
|
354
|
+
modelTier: originals.modelTier,
|
|
355
|
+
toolProfile: originals.toolProfile,
|
|
356
|
+
isCustom,
|
|
357
|
+
takenMascots: team
|
|
358
|
+
.getAll()
|
|
359
|
+
.filter((a) => a.id !== agentId)
|
|
360
|
+
.map((a) => a.mascot),
|
|
361
|
+
});
|
|
362
|
+
const result = await ctx.ui.showOverlay(overlay);
|
|
363
|
+
if (!result?.saved || !result.values) {
|
|
364
|
+
return;
|
|
365
|
+
}
|
|
366
|
+
const values = result.values;
|
|
367
|
+
/*
|
|
368
|
+
What should end up on the LIVE agent's prompt. For a custom agent that is the prompt
|
|
369
|
+
regenerated from the updated definition; for a role agent it is the edited free text.
|
|
370
|
+
*/
|
|
371
|
+
let newSystemPromptAddition;
|
|
372
|
+
if (custom) {
|
|
373
|
+
/*
|
|
374
|
+
Write the definition first, then send the user's OWN words to the live agent.
|
|
375
|
+
SDK 0.23 composes the role scaffold, capability blurb, delegation guidance and
|
|
376
|
+
personality at `initialize()` from current state — so `systemPromptAddition` holds
|
|
377
|
+
user-authored text only. Regenerating a composite here and sending THAT would be
|
|
378
|
+
composed a second time on top.
|
|
379
|
+
*/
|
|
380
|
+
const updatedDefinition = updateCustomAgent(custom.path, agentId, {
|
|
381
|
+
displayName: values.displayName,
|
|
382
|
+
specialty: values.specialty,
|
|
383
|
+
// Kept on the definition too, so the disk copy and the live agent do not drift.
|
|
384
|
+
personality: values.personality || undefined,
|
|
385
|
+
systemPromptAddition: values.instructions || undefined,
|
|
386
|
+
mascot: values.mascot,
|
|
387
|
+
modelTier: values.modelTier,
|
|
388
|
+
toolConfig: { ...custom.definition.toolConfig, profile: values.toolProfile },
|
|
389
|
+
});
|
|
390
|
+
if (!updatedDefinition) {
|
|
391
|
+
ctx.ui.print({
|
|
392
|
+
type: 'error',
|
|
393
|
+
message: `Could not write '${agentId}' to custom-agents.json — nothing was changed.`,
|
|
394
|
+
});
|
|
395
|
+
return;
|
|
396
|
+
}
|
|
397
|
+
/*
|
|
398
|
+
`?? ''` rather than leaving it undefined: clearing the instructions is a real edit,
|
|
399
|
+
and `buildAgentPatch` reads undefined as "unchanged" — so an emptied box would
|
|
400
|
+
silently keep the old text.
|
|
401
|
+
*/
|
|
402
|
+
newSystemPromptAddition = updatedDefinition.systemPromptAddition ?? '';
|
|
403
|
+
}
|
|
404
|
+
else if (values.instructions !== originals.instructions) {
|
|
405
|
+
newSystemPromptAddition = values.instructions;
|
|
406
|
+
}
|
|
407
|
+
const patch = buildAgentPatch(originals, values, newSystemPromptAddition);
|
|
408
|
+
if (Object.keys(patch).length === 0) {
|
|
409
|
+
ctx.ui.print({ type: 'info', message: 'No changes.' });
|
|
410
|
+
return;
|
|
411
|
+
}
|
|
412
|
+
try {
|
|
413
|
+
const update = await team.updateAgent(agentId, patch);
|
|
414
|
+
syncTeamRoster(team);
|
|
415
|
+
/*
|
|
416
|
+
⚠️ AN EDIT THAT DID NOT REACH DISK IS NOT A SAVE. The change is live in memory either
|
|
417
|
+
way, so the agent behaves as edited now and the loss only shows up on restart.
|
|
418
|
+
*/
|
|
419
|
+
saveTeamAndSession(team, ctx);
|
|
420
|
+
ctx.ui.print({
|
|
421
|
+
type: 'success',
|
|
422
|
+
message: `Updated ${teamAgent.getFullLabel()}`,
|
|
423
|
+
});
|
|
424
|
+
/*
|
|
425
|
+
⚠️ THE REPL HOLDS A DIRECT REFERENCE TO THE LIVE AGENT. A rebuild replaces the object
|
|
426
|
+
behind it, and without this the REPL keeps talking to the disposed one — so the edit
|
|
427
|
+
appears to do nothing, but only for the agent that happens to be active, which reads
|
|
428
|
+
as intermittent rather than broken. `updateAgent` rebuilds eagerly, so `.agent` is
|
|
429
|
+
already the new instance here.
|
|
430
|
+
*/
|
|
431
|
+
if (update.rebuilt && agentId === team.activeAgentId && teamAgent.agent && ctx.restoreAgent) {
|
|
432
|
+
ctx.restoreAgent(teamAgent.agent);
|
|
433
|
+
}
|
|
434
|
+
// Capability changes the agent cannot observe have to reach the conversation: its own
|
|
435
|
+
// history has it planning things it may no longer be able to do.
|
|
436
|
+
if (update.lostWriteAccess) {
|
|
437
|
+
ctx.ui.print({
|
|
438
|
+
type: 'warning',
|
|
439
|
+
message: `${teamAgent.getFullLabel()} can no longer change files (${update.previousProfile ?? 'unrestricted'} → ${update.newProfile ?? 'unrestricted'}).`,
|
|
440
|
+
});
|
|
441
|
+
}
|
|
442
|
+
else if (update.toolsNarrowed) {
|
|
443
|
+
ctx.ui.print({
|
|
444
|
+
type: 'warning',
|
|
445
|
+
message: `${teamAgent.getFullLabel()} can now reach fewer tools (${update.previousProfile ?? 'unrestricted'} → ${update.newProfile ?? 'unrestricted'}).`,
|
|
446
|
+
});
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
catch (err) {
|
|
450
|
+
// The SDK throws on an unknown agent, an unknown profile, and any attempt to change id.
|
|
451
|
+
ctx.ui.print({
|
|
452
|
+
type: 'error',
|
|
453
|
+
message: `Failed to update agent: ${err instanceof Error ? err.message : 'Unknown error'}`,
|
|
454
|
+
});
|
|
455
|
+
}
|
|
456
|
+
}
|
|
281
457
|
async function handleCreateCustomAgent(ctx, team) {
|
|
282
458
|
// Check for project context
|
|
283
459
|
const currentProject = getCurrentProject();
|
|
@@ -317,13 +493,12 @@ async function handleCreateCustomAgent(ctx, team) {
|
|
|
317
493
|
mascot: '',
|
|
318
494
|
createdAt: new Date().toISOString(),
|
|
319
495
|
toolConfig: agentData.toolConfig,
|
|
320
|
-
enabledSkills: agentData.enabledSkills,
|
|
321
496
|
modelTier: agentData.modelTier,
|
|
322
497
|
});
|
|
323
498
|
ctx.ui.print({ type: 'success', message: `Template "${formResult.saveAsTemplate}" saved.` });
|
|
324
499
|
}
|
|
325
500
|
// Create the custom agent definition
|
|
326
|
-
const agentDef = createCustomAgentDefinition(agentData.id, agentData.displayName, agentData.specialty, agentData.personality, existingCustomAgents, agentData.toolConfig, agentData.
|
|
501
|
+
const agentDef = createCustomAgentDefinition(agentData.id, agentData.displayName, agentData.specialty, agentData.personality, existingCustomAgents, agentData.toolConfig, agentData.modelTier, agentData.systemPromptAddition);
|
|
327
502
|
// Save to project
|
|
328
503
|
try {
|
|
329
504
|
addCustomAgent(projectPath, agentDef);
|
|
Binary file
|
package/dist/index.js
CHANGED
|
@@ -15,6 +15,8 @@ import { perf, flushStartupPerf } from './utils/startup-perf.js';
|
|
|
15
15
|
perf('imports-start');
|
|
16
16
|
import { createAgent } from './agent.js';
|
|
17
17
|
import { getModelForTier, getProviderMetadata } from './models/index.js';
|
|
18
|
+
import { filterMcpToolsByGrant, loadInstalledSkills, resolveSkillsForTeamAgent } from '@compilr-dev/sdk';
|
|
19
|
+
import { getProjectDir } from './repl-helpers.js';
|
|
18
20
|
import { ReplV2 } from './repl-v2.js';
|
|
19
21
|
import { AgentTeam, setActiveSharedContext } from './multi-agent/index.js';
|
|
20
22
|
import { getStyles } from './themes/index.js';
|
|
@@ -657,7 +659,35 @@ async function main() {
|
|
|
657
659
|
// ==========================================================================
|
|
658
660
|
perf('team-setup-start');
|
|
659
661
|
// Define agent factory for creating team agents
|
|
660
|
-
|
|
662
|
+
/**
|
|
663
|
+
* The skills an agent may CHOOSE — distinct from the slash commands a user types.
|
|
664
|
+
*
|
|
665
|
+
* ⚠️ THE HOST FILTERS, THE SDK PRESENTS, and `resolveSkillsForTeamAgent` holds every judgement
|
|
666
|
+
* so the CLI and Desktop cannot drift about what an absent grant means or which filters apply.
|
|
667
|
+
* `loadInstalledSkills` excludes the 42 platform prompts on purpose: those are Commands — modes
|
|
668
|
+
* a user forces — not capabilities a model selects.
|
|
669
|
+
*/
|
|
670
|
+
const skillsForAgent = (teamAgent) => {
|
|
671
|
+
// No agent identity means no eligibility context, so no catalogue.
|
|
672
|
+
if (!teamAgent)
|
|
673
|
+
return [];
|
|
674
|
+
const pool = loadInstalledSkills(getProjectDir());
|
|
675
|
+
if (pool.length === 0)
|
|
676
|
+
return [];
|
|
677
|
+
return resolveSkillsForTeamAgent(pool, teamAgent, { toolNames: teamAgent.toolFilter });
|
|
678
|
+
};
|
|
679
|
+
/** The MCP tools an agent may be built with. See the note at the `mcpTools` call site. */
|
|
680
|
+
const grantedMcpTools = (tools, teamAgent) => {
|
|
681
|
+
if (tools.length === 0)
|
|
682
|
+
return undefined;
|
|
683
|
+
// No agent identity means no grant to honour — nothing is filtered, as before.
|
|
684
|
+
if (!teamAgent)
|
|
685
|
+
return tools;
|
|
686
|
+
const servers = mcpManagerInstance?.getServerNames() ?? [];
|
|
687
|
+
const granted = filterMcpToolsByGrant(tools, servers, (s) => teamAgent.grantsMcpServer(s));
|
|
688
|
+
return granted.length > 0 ? granted : undefined;
|
|
689
|
+
};
|
|
690
|
+
const agentFactory = (_config, systemPromptAddition, useMinimalSystemPrompt, noTools, toolFilter, modelTier, teamAgent) => {
|
|
661
691
|
// Resolve model from tier if provided, otherwise use global model
|
|
662
692
|
const agentModel = modelTier ? getModelForTier(provider, modelTier) : model;
|
|
663
693
|
// Create new agent with same base options plus role-specific system prompt
|
|
@@ -701,7 +731,18 @@ async function main() {
|
|
|
701
731
|
// Tool filter - limit which tools this agent can use
|
|
702
732
|
toolFilter,
|
|
703
733
|
// MCP tools from external servers (reads mutable ref — includes tools once MCP init completes)
|
|
704
|
-
|
|
734
|
+
/*
|
|
735
|
+
⚠️ MCP REACH IS PER AGENT, AND THE TOOL PROFILE NEVER GATED IT. MCP tools are in no
|
|
736
|
+
capability pack — discovered at runtime, they never could be — and the SDK allows
|
|
737
|
+
unpacked "orphan" tools, so a read-only agent could run an MCP WRITE tool. Filtering at
|
|
738
|
+
construction is the enforcement: a tool the agent is not built with cannot be called.
|
|
739
|
+
|
|
740
|
+
`grantsMcpServer` owns what an absent grant means (undefined = an agent from before
|
|
741
|
+
grants existed, which still reaches everything), and `filterMcpToolsByGrant` owns the
|
|
742
|
+
tool→server mapping, so the CLI cannot drift from Desktop on either.
|
|
743
|
+
*/
|
|
744
|
+
mcpTools: grantedMcpTools(mcpToolsRef, teamAgent),
|
|
745
|
+
skills: skillsForAgent(teamAgent),
|
|
705
746
|
// Suppress startup logs for team agents (avoid UI interference)
|
|
706
747
|
quiet: true,
|
|
707
748
|
// Episode recorder for work history tracking (team agents share project store)
|
|
@@ -982,9 +1023,20 @@ async function main() {
|
|
|
982
1023
|
.then((tools) => {
|
|
983
1024
|
perf('mcp-background-done');
|
|
984
1025
|
replV2.setMCPLoading(false, tools.length);
|
|
1026
|
+
/*
|
|
1027
|
+
⚠️ THE SECOND INJECTION PATH. `mcpToolsRef` feeds the factory, which filters by grant —
|
|
1028
|
+
but the main agent already exists by now and gets its tools here instead, straight onto
|
|
1029
|
+
its registry where no capability filter looks. Desktop had the same pair; filtering only
|
|
1030
|
+
the factory leaves grants working except for the agent you are already talking to.
|
|
1031
|
+
|
|
1032
|
+
`mcpToolsRef` stays UNFILTERED on purpose: it is the pool every agent is filtered FROM,
|
|
1033
|
+
and narrowing it here would apply the default agent's grant to the whole team.
|
|
1034
|
+
*/
|
|
985
1035
|
if (tools.length > 0) {
|
|
986
|
-
agent?.registerTools(tools);
|
|
987
1036
|
mcpToolsRef = tools;
|
|
1037
|
+
const forDefault = grantedMcpTools(tools, sharedState.team?.get('default'));
|
|
1038
|
+
if (forDefault && forDefault.length > 0)
|
|
1039
|
+
agent?.registerTools(forDefault);
|
|
988
1040
|
}
|
|
989
1041
|
})
|
|
990
1042
|
.catch(() => {
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent edit — the decisions an edit makes, separated from the UI that collects it.
|
|
3
|
+
*
|
|
4
|
+
* These are pure so they can be tested. The two things here are the two things most
|
|
5
|
+
* likely to be silently wrong: WHICH prompt field an edit reads and writes, and WHAT
|
|
6
|
+
* ends up in the patch.
|
|
7
|
+
*/
|
|
8
|
+
import type { TeamAgentUpdate, MascotExpression, ToolProfile } from '@compilr-dev/sdk';
|
|
9
|
+
import type { CustomAgentDefinition } from './custom-agents.js';
|
|
10
|
+
import type { ModelTier } from '../models/index.js';
|
|
11
|
+
/** The agent's values as they were when the editor opened. */
|
|
12
|
+
export interface AgentEditOriginals {
|
|
13
|
+
displayName: string;
|
|
14
|
+
specialty: string;
|
|
15
|
+
instructions: string;
|
|
16
|
+
mascot: MascotExpression;
|
|
17
|
+
modelTier: ModelTier;
|
|
18
|
+
toolProfile: ToolProfile;
|
|
19
|
+
/** Tone. Composed into the prompt at initialize() as "Your manner: …". */
|
|
20
|
+
personality: string;
|
|
21
|
+
/** The live agent's current prompt addition — user-authored text since SDK 0.23. */
|
|
22
|
+
systemPromptAddition: string;
|
|
23
|
+
}
|
|
24
|
+
/** The values the editor handed back. */
|
|
25
|
+
export interface AgentEditedValues {
|
|
26
|
+
displayName: string;
|
|
27
|
+
specialty: string;
|
|
28
|
+
instructions: string;
|
|
29
|
+
mascot: MascotExpression;
|
|
30
|
+
modelTier: ModelTier;
|
|
31
|
+
toolProfile: ToolProfile;
|
|
32
|
+
personality: string;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Which text is the user's to edit.
|
|
36
|
+
*
|
|
37
|
+
* ⚠️ THE TWO FIELDS SHARE A NAME AND DO NOT SHARE A MEANING.
|
|
38
|
+
* `CustomAgentDefinition.systemPromptAddition` is what the user typed
|
|
39
|
+
* ("Always cite the ADR."). `TeamAgent.systemPromptAddition` is the whole GENERATED
|
|
40
|
+
* prompt ("You are a Solution Architect specialized in …"), because
|
|
41
|
+
* `fromCustomDefinition` stores the generated composite there.
|
|
42
|
+
*
|
|
43
|
+
* So a custom agent's editable text comes from its definition, and a role agent's — which
|
|
44
|
+
* has no definition — comes from the live agent.
|
|
45
|
+
*
|
|
46
|
+
* ⚠️ Corrected: before SDK 0.23 the live field was NOT free text for a role agent either.
|
|
47
|
+
* `fromRole` composed the role scaffold, tool awareness and delegation guidance into it —
|
|
48
|
+
* 6,321 characters for `pm`. An earlier note here claimed role agents were unaffected; that
|
|
49
|
+
* was asserted from reading the code rather than running it, and it was wrong. Since 0.23
|
|
50
|
+
* the field holds user text for both kinds, so this fallback is now correct for both — but
|
|
51
|
+
* the definition stays authoritative for custom agents, because it is what is on disk.
|
|
52
|
+
*/
|
|
53
|
+
export declare function readEditableInstructions(liveSystemPromptAddition: string | undefined, definition: CustomAgentDefinition | undefined): string;
|
|
54
|
+
/**
|
|
55
|
+
* Build the minimal patch for an edit.
|
|
56
|
+
*
|
|
57
|
+
* Only changed fields are included, so an untouched field never triggers a rebuild —
|
|
58
|
+
* `applyUpdate` compares each present key against the live value and rebuilds on any
|
|
59
|
+
* difference in the prompt, personality, profile, filter or skills.
|
|
60
|
+
*
|
|
61
|
+
* `newSystemPromptAddition` is the USER'S OWN WORDS, for both agent kinds, or undefined
|
|
62
|
+
* when the text was not touched.
|
|
63
|
+
*
|
|
64
|
+
* ⚠️ This changed in SDK 0.23. `systemPromptAddition` now holds user-authored text ONLY:
|
|
65
|
+
* the role scaffold, capability blurb, delegation guidance and personality are composed at
|
|
66
|
+
* `initialize()` from current state. Sending a pre-composed prompt here would be composed a
|
|
67
|
+
* second time on top of itself — and was previously the reason a tool-profile change could
|
|
68
|
+
* leave the prompt still claiming tools the filter had already removed.
|
|
69
|
+
*
|
|
70
|
+
* ⚠️ `toolProfile` is sent WITHOUT a `toolFilter`. The profile is a label; the filter is
|
|
71
|
+
* the enforcement, and `applyUpdate` derives the filter from the profile precisely when
|
|
72
|
+
* the filter is absent. Supplying our own would override that with a second source of
|
|
73
|
+
* truth for the same fact.
|
|
74
|
+
*/
|
|
75
|
+
export declare function buildAgentPatch(originals: AgentEditOriginals, values: AgentEditedValues, newSystemPromptAddition: string | undefined): TeamAgentUpdate;
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent edit — the decisions an edit makes, separated from the UI that collects it.
|
|
3
|
+
*
|
|
4
|
+
* These are pure so they can be tested. The two things here are the two things most
|
|
5
|
+
* likely to be silently wrong: WHICH prompt field an edit reads and writes, and WHAT
|
|
6
|
+
* ends up in the patch.
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Which text is the user's to edit.
|
|
10
|
+
*
|
|
11
|
+
* ⚠️ THE TWO FIELDS SHARE A NAME AND DO NOT SHARE A MEANING.
|
|
12
|
+
* `CustomAgentDefinition.systemPromptAddition` is what the user typed
|
|
13
|
+
* ("Always cite the ADR."). `TeamAgent.systemPromptAddition` is the whole GENERATED
|
|
14
|
+
* prompt ("You are a Solution Architect specialized in …"), because
|
|
15
|
+
* `fromCustomDefinition` stores the generated composite there.
|
|
16
|
+
*
|
|
17
|
+
* So a custom agent's editable text comes from its definition, and a role agent's — which
|
|
18
|
+
* has no definition — comes from the live agent.
|
|
19
|
+
*
|
|
20
|
+
* ⚠️ Corrected: before SDK 0.23 the live field was NOT free text for a role agent either.
|
|
21
|
+
* `fromRole` composed the role scaffold, tool awareness and delegation guidance into it —
|
|
22
|
+
* 6,321 characters for `pm`. An earlier note here claimed role agents were unaffected; that
|
|
23
|
+
* was asserted from reading the code rather than running it, and it was wrong. Since 0.23
|
|
24
|
+
* the field holds user text for both kinds, so this fallback is now correct for both — but
|
|
25
|
+
* the definition stays authoritative for custom agents, because it is what is on disk.
|
|
26
|
+
*/
|
|
27
|
+
export function readEditableInstructions(liveSystemPromptAddition, definition) {
|
|
28
|
+
if (definition) {
|
|
29
|
+
return definition.systemPromptAddition ?? '';
|
|
30
|
+
}
|
|
31
|
+
return liveSystemPromptAddition ?? '';
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Build the minimal patch for an edit.
|
|
35
|
+
*
|
|
36
|
+
* Only changed fields are included, so an untouched field never triggers a rebuild —
|
|
37
|
+
* `applyUpdate` compares each present key against the live value and rebuilds on any
|
|
38
|
+
* difference in the prompt, personality, profile, filter or skills.
|
|
39
|
+
*
|
|
40
|
+
* `newSystemPromptAddition` is the USER'S OWN WORDS, for both agent kinds, or undefined
|
|
41
|
+
* when the text was not touched.
|
|
42
|
+
*
|
|
43
|
+
* ⚠️ This changed in SDK 0.23. `systemPromptAddition` now holds user-authored text ONLY:
|
|
44
|
+
* the role scaffold, capability blurb, delegation guidance and personality are composed at
|
|
45
|
+
* `initialize()` from current state. Sending a pre-composed prompt here would be composed a
|
|
46
|
+
* second time on top of itself — and was previously the reason a tool-profile change could
|
|
47
|
+
* leave the prompt still claiming tools the filter had already removed.
|
|
48
|
+
*
|
|
49
|
+
* ⚠️ `toolProfile` is sent WITHOUT a `toolFilter`. The profile is a label; the filter is
|
|
50
|
+
* the enforcement, and `applyUpdate` derives the filter from the profile precisely when
|
|
51
|
+
* the filter is absent. Supplying our own would override that with a second source of
|
|
52
|
+
* truth for the same fact.
|
|
53
|
+
*/
|
|
54
|
+
export function buildAgentPatch(originals, values, newSystemPromptAddition) {
|
|
55
|
+
const patch = {};
|
|
56
|
+
if (values.displayName !== originals.displayName) {
|
|
57
|
+
patch.displayName = values.displayName;
|
|
58
|
+
}
|
|
59
|
+
if (values.specialty !== originals.specialty) {
|
|
60
|
+
patch.description = values.specialty;
|
|
61
|
+
}
|
|
62
|
+
if (values.mascot !== originals.mascot) {
|
|
63
|
+
patch.mascot = values.mascot;
|
|
64
|
+
}
|
|
65
|
+
if (values.modelTier !== originals.modelTier) {
|
|
66
|
+
patch.modelTier = values.modelTier;
|
|
67
|
+
}
|
|
68
|
+
if (values.toolProfile !== originals.toolProfile) {
|
|
69
|
+
patch.toolProfile = values.toolProfile;
|
|
70
|
+
}
|
|
71
|
+
/*
|
|
72
|
+
Unblocked in SDK 0.23. `fromCustomDefinition` now passes `def.personality` through, so
|
|
73
|
+
this REPLACES the agent's tone rather than adding a second, contradicting one beside a
|
|
74
|
+
`Your approach:` line baked into the base prompt.
|
|
75
|
+
*/
|
|
76
|
+
if (values.personality !== originals.personality) {
|
|
77
|
+
patch.personality = values.personality;
|
|
78
|
+
}
|
|
79
|
+
if (newSystemPromptAddition !== undefined &&
|
|
80
|
+
newSystemPromptAddition !== originals.systemPromptAddition) {
|
|
81
|
+
patch.systemPromptAddition = newSystemPromptAddition;
|
|
82
|
+
}
|
|
83
|
+
return patch;
|
|
84
|
+
}
|
|
@@ -1,9 +1,19 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Capability Loader —
|
|
2
|
+
* Capability Loader — bridge between MACROS and the CapabilityManager.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* A macro declares the tools its text assumes (MACRO_REQUIREMENTS); this maps those to
|
|
5
|
+
* capability packs and loads them before the macro's prompt is queued, so `/design` arrives
|
|
6
|
+
* with the tools it is about to ask for.
|
|
7
|
+
*
|
|
8
|
+
* ⚠️ THIS WHOLE PATH IS DEAD, AND HAS BEEN SINCE IT WAS WRITTEN. `setCapabilityManager` has NO
|
|
9
|
+
* CALLERS anywhere in the CLI — the header below says it is "set by agent.ts during
|
|
10
|
+
* createAgent()", and agent.ts does not call it. So `currentManager` is permanently null,
|
|
11
|
+
* `loadCapabilitiesForMacro` returns at its first guard every time, and the warning it exists
|
|
12
|
+
* to print has never printed. Verified by grep, not by reading.
|
|
13
|
+
*
|
|
14
|
+
* Left in place rather than deleted because the design is sound and the tier flip that made
|
|
15
|
+
* packs lazy is what it was waiting for — but it is a decision, not an oversight to inherit.
|
|
16
|
+
* Wire it or delete it; do not assume it works.
|
|
7
17
|
*/
|
|
8
18
|
import type { CapabilityManager, CapabilityLoadResult } from '@compilr-dev/sdk';
|
|
9
19
|
/**
|
|
@@ -26,4 +36,4 @@ export declare function getCapabilityManager(): CapabilityManager | null;
|
|
|
26
36
|
* @param skillName - Skill name (e.g., 'design', 'build', 'refactor')
|
|
27
37
|
* @returns Load result, or null if capability loading is not active or skill has no requirements
|
|
28
38
|
*/
|
|
29
|
-
export declare function
|
|
39
|
+
export declare function loadCapabilitiesForMacro(skillName: string): CapabilityLoadResult | null;
|
|
@@ -1,11 +1,21 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Capability Loader —
|
|
2
|
+
* Capability Loader — bridge between MACROS and the CapabilityManager.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* A macro declares the tools its text assumes (MACRO_REQUIREMENTS); this maps those to
|
|
5
|
+
* capability packs and loads them before the macro's prompt is queued, so `/design` arrives
|
|
6
|
+
* with the tools it is about to ask for.
|
|
7
|
+
*
|
|
8
|
+
* ⚠️ THIS WHOLE PATH IS DEAD, AND HAS BEEN SINCE IT WAS WRITTEN. `setCapabilityManager` has NO
|
|
9
|
+
* CALLERS anywhere in the CLI — the header below says it is "set by agent.ts during
|
|
10
|
+
* createAgent()", and agent.ts does not call it. So `currentManager` is permanently null,
|
|
11
|
+
* `loadCapabilitiesForMacro` returns at its first guard every time, and the warning it exists
|
|
12
|
+
* to print has never printed. Verified by grep, not by reading.
|
|
13
|
+
*
|
|
14
|
+
* Left in place rather than deleted because the design is sound and the tier flip that made
|
|
15
|
+
* packs lazy is what it was waiting for — but it is a decision, not an oversight to inherit.
|
|
16
|
+
* Wire it or delete it; do not assume it works.
|
|
7
17
|
*/
|
|
8
|
-
import {
|
|
18
|
+
import { MACRO_REQUIREMENTS } from '@compilr-dev/sdk';
|
|
9
19
|
// =============================================================================
|
|
10
20
|
// Module-level state
|
|
11
21
|
// =============================================================================
|
|
@@ -37,14 +47,14 @@ export function getCapabilityManager() {
|
|
|
37
47
|
* @param skillName - Skill name (e.g., 'design', 'build', 'refactor')
|
|
38
48
|
* @returns Load result, or null if capability loading is not active or skill has no requirements
|
|
39
49
|
*/
|
|
40
|
-
export function
|
|
50
|
+
export function loadCapabilitiesForMacro(skillName) {
|
|
41
51
|
if (!currentManager) {
|
|
42
52
|
return null;
|
|
43
53
|
}
|
|
44
|
-
if (!(skillName in
|
|
54
|
+
if (!(skillName in MACRO_REQUIREMENTS)) {
|
|
45
55
|
return null;
|
|
46
56
|
}
|
|
47
|
-
const requirements =
|
|
57
|
+
const requirements = MACRO_REQUIREMENTS[skillName];
|
|
48
58
|
// Combine required + optional tools
|
|
49
59
|
const allTools = [
|
|
50
60
|
...requirements.required,
|
|
@@ -24,3 +24,15 @@ export declare function addCustomAgent(projectPath: string, agent: CustomAgentDe
|
|
|
24
24
|
* Remove a custom agent by ID and save.
|
|
25
25
|
*/
|
|
26
26
|
export declare function removeCustomAgent(projectPath: string, agentId: string): boolean;
|
|
27
|
+
/**
|
|
28
|
+
* Update a custom agent's definition in place and save.
|
|
29
|
+
*
|
|
30
|
+
* Returns the updated definition (the caller needs it to regenerate the agent's
|
|
31
|
+
* system prompt), or `null` if no agent with that id is stored.
|
|
32
|
+
*
|
|
33
|
+
* ⚠️ `id` and `createdAt` are not patchable, by type rather than by a runtime throw.
|
|
34
|
+
* The id is persisted in message history as literal text — mentions resolve when
|
|
35
|
+
* typed, not when read — so renaming `$arch` would strand every prior reference.
|
|
36
|
+
* `AgentTeam.updateAgent` enforces the same constraint on the live agent.
|
|
37
|
+
*/
|
|
38
|
+
export declare function updateCustomAgent(projectPath: string, agentId: string, patch: Partial<Omit<CustomAgentDefinition, 'id' | 'createdAt'>>): CustomAgentDefinition | null;
|
|
@@ -37,7 +37,6 @@ function migrateAgentV1toV2(agent) {
|
|
|
37
37
|
return {
|
|
38
38
|
...agent,
|
|
39
39
|
toolConfig: agent.toolConfig ?? createDefaultToolConfig(),
|
|
40
|
-
enabledSkills: agent.enabledSkills ?? [],
|
|
41
40
|
};
|
|
42
41
|
}
|
|
43
42
|
/**
|
|
@@ -99,3 +98,25 @@ export function removeCustomAgent(projectPath, agentId) {
|
|
|
99
98
|
saveCustomAgents(projectPath, agents);
|
|
100
99
|
return true;
|
|
101
100
|
}
|
|
101
|
+
/**
|
|
102
|
+
* Update a custom agent's definition in place and save.
|
|
103
|
+
*
|
|
104
|
+
* Returns the updated definition (the caller needs it to regenerate the agent's
|
|
105
|
+
* system prompt), or `null` if no agent with that id is stored.
|
|
106
|
+
*
|
|
107
|
+
* ⚠️ `id` and `createdAt` are not patchable, by type rather than by a runtime throw.
|
|
108
|
+
* The id is persisted in message history as literal text — mentions resolve when
|
|
109
|
+
* typed, not when read — so renaming `$arch` would strand every prior reference.
|
|
110
|
+
* `AgentTeam.updateAgent` enforces the same constraint on the live agent.
|
|
111
|
+
*/
|
|
112
|
+
export function updateCustomAgent(projectPath, agentId, patch) {
|
|
113
|
+
const agents = loadCustomAgents(projectPath);
|
|
114
|
+
const index = agents.findIndex((a) => a.id === agentId);
|
|
115
|
+
if (index === -1) {
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
const updated = { ...agents[index], ...patch };
|
|
119
|
+
agents[index] = updated;
|
|
120
|
+
saveCustomAgents(projectPath, agents);
|
|
121
|
+
return updated;
|
|
122
|
+
}
|
package/dist/repl-helpers.d.ts
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* and improve maintainability. These functions support the REPL but don't
|
|
6
6
|
* depend on REPL instance state.
|
|
7
7
|
*/
|
|
8
|
+
import { type SkillSources } from '@compilr-dev/sdk';
|
|
8
9
|
import type { BacklogItem } from './tools/backlog.js';
|
|
9
10
|
/**
|
|
10
11
|
* Keyword patterns for tool selection based on user input.
|
|
@@ -38,9 +39,13 @@ export declare function detectCompilrProject(): CompilrProjectInfo;
|
|
|
38
39
|
* Check if backlog has any items using the shared parser.
|
|
39
40
|
*/
|
|
40
41
|
export declare function hasBacklogItems(backlogPath: string | null): boolean;
|
|
42
|
+
/** Get the current project directory (best effort). */
|
|
43
|
+
export declare function getProjectDir(): string | undefined;
|
|
44
|
+
/** Get CLI skill sources (factory + coding). */
|
|
45
|
+
export declare function getCliSkillSources(): SkillSources;
|
|
41
46
|
/**
|
|
42
47
|
* Get skill prompt by name. Delegates to SDK resolveSkillPrompt, minus any
|
|
43
|
-
* Desktop-only SDK skills (see
|
|
48
|
+
* Desktop-only SDK skills (see DESKTOP_ONLY_MACROS).
|
|
44
49
|
*/
|
|
45
50
|
export declare function getSkillPrompt(name: string): string | null;
|
|
46
51
|
/**
|