@compilr-dev/sdk 0.20.1 → 0.20.3

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/index.d.ts CHANGED
@@ -35,7 +35,7 @@
35
35
  */
36
36
  export { createCompilrAgent } from './agent.js';
37
37
  export type { CompilrAgentConfig, CompilrAgent, RunOptions, RunResult, ToolCallRecord, ToolConfig, UsageInfo, ProviderType, PermissionCallback, GuardrailConfig, ContextConfig, CapabilitiesConfig, } from './config.js';
38
- export { AgentTeam, TeamAgent, SharedContextManager, ArtifactStore, DelegationTracker, ContextResolver, createDelegationStatusTool, createHandoffTool, createDelegateTool, createDelegateBackgroundTool, buildHandoffTaskMessage, validateHandoffIntent, HandoffStash, createConsultTool, buildConsultQuestionMessage, ROLE_NAME_ALIASES, normalizeRoleName, isNarrowing, } from './team/index.js';
38
+ export { AgentTeam, TeamAgent, SharedContextManager, ArtifactStore, DelegationTracker, ContextResolver, createDelegationStatusTool, createHandoffTool, createDelegateTool, createDelegateBackgroundTool, buildHandoffTaskMessage, validateHandoffIntent, HandoffStash, createConsultTool, buildConsultQuestionMessage, ROLE_NAME_ALIASES, normalizeRoleName, isNarrowing, lostWriteAccess, } from './team/index.js';
39
39
  export type { AgentTeamConfig, TeamAgentConfig, TeamAgentUpdate, AgentUpdateResult, ITeamPersistence, IArtifactStorage, ISessionRegistry, CustomAgentDefinition, AgentTemplate, AgentWorkshopData, WorkshopRoleDef, WorkshopToolProfile, WorkshopModelTier, WorkshopSkillDef, PlanSubmitInfo, PlanSubmitResult, PlanModeExitInfo, PlanModeCallbacks, ToolConfig as TeamToolConfig, ToolTier, ToolGroup, ProfileInfo, } from './team/index.js';
40
40
  export type { AgentRole, RoleMetadata, ToolProfile, MascotExpression, BackgroundSessionInfo, SerializedTeam, SerializedTeamAgent, TeamMetadata, TeamEvent, TeamEventType, TeamEventHandler, Artifact, ArtifactType as TeamArtifactType, ArtifactSummary as TeamArtifactSummary, CreateArtifactOptions, UpdateArtifactOptions, SerializedArtifact, SharedContext, SharedProjectInfo, SharedTeamInfo, TeamRosterEntry, TeamActivity, TeamActivityType, SharedDecision, TokenBudget, SerializedSharedContext, ParsedMention, ParsedInput, ResolvedMention, ResolveOptions, ResolutionSource, Delegation, DelegationStatus, DelegationResult, CompletionEvent, CreateDelegationOptions, DelegationStats, DelegationTrackerEvents, HandoffResult, HandoffToolConfig, HandoffIntent, HandoffValidationResult, DelegateResult, DelegateToolConfig, DelegateBackgroundResult, DelegateBackgroundToolConfig, ConsultInput, ConsultResult, ConsultToolConfig, NormalizedRole, SkillToolRequirement, } from './team/index.js';
41
41
  export { ROLE_METADATA, ROLE_EXPERTISE, ROLE_GROUPS, PREDEFINED_ROLE_IDS, TOOL_GROUPS, TOOL_PROFILES, PROFILE_INFO, SKILL_REQUIREMENTS, CUSTOM_MASCOTS, buildAgentWorkshopData, buildSuggestedRolesMap, PLAN_MODE_BLOCKED_TOOLS, PLAN_MODE_DENIAL_MESSAGE, PLAN_MODE_PROMPT, isToolAllowedInPlanMode, getPlanModePrompt, } from './team/index.js';
package/dist/index.js CHANGED
@@ -41,7 +41,7 @@ export { createCompilrAgent } from './agent.js';
41
41
  // Multi-Agent Team Orchestration
42
42
  // =============================================================================
43
43
  // Core classes
44
- export { AgentTeam, TeamAgent, SharedContextManager, ArtifactStore, DelegationTracker, ContextResolver, createDelegationStatusTool, createHandoffTool, createDelegateTool, createDelegateBackgroundTool, buildHandoffTaskMessage, validateHandoffIntent, HandoffStash, createConsultTool, buildConsultQuestionMessage, ROLE_NAME_ALIASES, normalizeRoleName, isNarrowing, } from './team/index.js';
44
+ export { AgentTeam, TeamAgent, SharedContextManager, ArtifactStore, DelegationTracker, ContextResolver, createDelegationStatusTool, createHandoffTool, createDelegateTool, createDelegateBackgroundTool, buildHandoffTaskMessage, validateHandoffIntent, HandoffStash, createConsultTool, buildConsultQuestionMessage, ROLE_NAME_ALIASES, normalizeRoleName, isNarrowing, lostWriteAccess, } from './team/index.js';
45
45
  // Constants
46
46
  export { ROLE_METADATA, ROLE_EXPERTISE, ROLE_GROUPS, PREDEFINED_ROLE_IDS, TOOL_GROUPS, TOOL_PROFILES, PROFILE_INFO, SKILL_REQUIREMENTS, CUSTOM_MASCOTS, buildAgentWorkshopData, buildSuggestedRolesMap,
47
47
  // Plan mode
@@ -6,7 +6,7 @@
6
6
  */
7
7
  export { AgentTeam } from './team.js';
8
8
  export type { AgentTeamConfig } from './team.js';
9
- export { TeamAgent, isNarrowing } from './team-agent.js';
9
+ export { TeamAgent, isNarrowing, lostWriteAccess } from './team-agent.js';
10
10
  export type { TeamAgentUpdate, AgentUpdateResult } from './team-agent.js';
11
11
  export { SharedContextManager } from './shared-context.js';
12
12
  export type { SharedContext, SharedProjectInfo, SharedTeamInfo, TeamRosterEntry, TeamActivity, TeamActivityType, SharedDecision, TokenBudget, SerializedSharedContext, } from './shared-context.js';
@@ -6,7 +6,7 @@
6
6
  */
7
7
  // Core classes
8
8
  export { AgentTeam } from './team.js';
9
- export { TeamAgent, isNarrowing } from './team-agent.js';
9
+ export { TeamAgent, isNarrowing, lostWriteAccess } from './team-agent.js';
10
10
  export { SharedContextManager } from './shared-context.js';
11
11
  export { ArtifactStore } from './artifacts.js';
12
12
  export { ROLE_METADATA, ROLE_EXPERTISE, ROLE_GROUPS, PREDEFINED_ROLE_IDS } from './types.js';
@@ -49,17 +49,43 @@ export interface AgentUpdateResult {
49
49
  * ability to write files is worth more than the row it costs.
50
50
  */
51
51
  toolsNarrowed: boolean;
52
+ /**
53
+ * ⚠️ NARROWER THAN `toolsNarrowed`, and the host needs both. "Fewer tools" and "cannot
54
+ * write any more" are different facts; a transcript saying "it can no longer change files"
55
+ * when six tools were dropped is a false statement in the record.
56
+ */
57
+ lostWriteAccess: boolean;
52
58
  previousProfile?: TeamAgentConfig['toolProfile'];
53
59
  newProfile?: TeamAgentConfig['toolProfile'];
54
60
  }
55
61
  /**
56
- * Did the tool profile get strictly smaller?
62
+ * Did the agent's reach get strictly smaller?
63
+ *
64
+ * ⚠️ TWO TESTS, NOT ONE, and the first version had only half of the first.
65
+ *
66
+ * Losing the ability to WRITE is the narrowing that matters most, so a move into a read-only
67
+ * profile counts however the tool counts land. But it is not the only one: `full` → `custom`
68
+ * with no groups is total tool removal — the largest narrowing there is — and neither profile
69
+ * is read-only, so a read-only test alone called it no change at all.
57
70
  *
58
- * ⚠️ Decided by whether the profile is READ-ONLY, not by counting tools. A profile with more
59
- * tools that cannot write is a narrowing in every sense the user cares about, and tool counts
60
- * shift whenever the registry grows.
71
+ * ⚠️ `undefined` IS NOT "UNKNOWN". `initialize` passes `toolFilter` straight to the factory,
72
+ * and an absent filter means NO filter — every tool. So `undefined` → `read-only` is the
73
+ * maximal narrowing, and the original returned false for it. My own test asserted exactly
74
+ * that under the name "says nothing when either side is unknown", which is how it survived:
75
+ * the suite encoded the defect as intended behaviour.
61
76
  */
62
77
  export declare function isNarrowing(before: TeamAgentConfig['toolProfile'], after: TeamAgentConfig['toolProfile']): boolean;
78
+ /**
79
+ * Did the agent specifically lose the ability to change files?
80
+ *
81
+ * ⚠️ A NARROWER QUESTION THAN `isNarrowing`, and the host needs both. "Fewer tools" and
82
+ * "cannot write any more" are different facts, and a notice that says "it can no longer
83
+ * change files" when the agent merely dropped six tools is a false statement in the
84
+ * transcript — exactly the class of error this whole review found.
85
+ */
86
+ export declare function lostWriteAccess(before: TeamAgentConfig['toolProfile'], after: TeamAgentConfig['toolProfile']): boolean;
87
+ /** Is this a profile the registry actually knows? Used to refuse bad IPC data. */
88
+ export declare function isKnownProfile(p: unknown): p is NonNullable<TeamAgentConfig['toolProfile']>;
63
89
  /**
64
90
  * TeamAgent wraps an Agent instance with team-specific metadata
65
91
  */
@@ -10,7 +10,7 @@
10
10
  import { ROLE_METADATA } from './types.js';
11
11
  import { getModelForTier, getModelContextWindow } from '../models/index.js';
12
12
  import { generateCustomAgentSystemPrompt, getCustomAgentToolFilter } from './custom-agents.js';
13
- import { getToolsForProfile, generateToolAwarenessPrompt, generateCoordinatorGuidance, generateSpecialistGuidance, } from './tool-config.js';
13
+ import { getToolsForProfile, PROFILE_INFO, generateToolAwarenessPrompt, generateCoordinatorGuidance, generateSpecialistGuidance, } from './tool-config.js';
14
14
  /**
15
15
  * Estimate token count from text (approximate: ~4 chars per token)
16
16
  */
@@ -18,34 +18,64 @@ function estimateTokens(text) {
18
18
  return Math.ceil(text.length / 4);
19
19
  }
20
20
  /**
21
- * Did the tool profile get strictly smaller?
21
+ * Did the agent's reach get strictly smaller?
22
22
  *
23
- * ⚠️ Decided by whether the profile is READ-ONLY, not by counting tools. A profile with more
24
- * tools that cannot write is a narrowing in every sense the user cares about, and tool counts
25
- * shift whenever the registry grows.
23
+ * ⚠️ TWO TESTS, NOT ONE, and the first version had only half of the first.
24
+ *
25
+ * Losing the ability to WRITE is the narrowing that matters most, so a move into a read-only
26
+ * profile counts however the tool counts land. But it is not the only one: `full` → `custom`
27
+ * with no groups is total tool removal — the largest narrowing there is — and neither profile
28
+ * is read-only, so a read-only test alone called it no change at all.
29
+ *
30
+ * ⚠️ `undefined` IS NOT "UNKNOWN". `initialize` passes `toolFilter` straight to the factory,
31
+ * and an absent filter means NO filter — every tool. So `undefined` → `read-only` is the
32
+ * maximal narrowing, and the original returned false for it. My own test asserted exactly
33
+ * that under the name "says nothing when either side is unknown", which is how it survived:
34
+ * the suite encoded the defect as intended behaviour.
26
35
  */
27
36
  export function isNarrowing(before, after) {
28
37
  if (before === after)
29
38
  return false;
30
- if (before === undefined || after === undefined)
31
- return false;
32
- return !READ_ONLY_PROFILES.has(before) && READ_ONLY_PROFILES.has(after);
39
+ const couldWrite = before === undefined || !isReadOnlyProfile(before);
40
+ const canWrite = after === undefined || !isReadOnlyProfile(after);
41
+ if (couldWrite && !canWrite)
42
+ return true;
43
+ // Otherwise compare what it may actually reach — `undefined` is every tool.
44
+ return toolReach(after) < toolReach(before);
33
45
  }
34
46
  /**
35
- * The profiles that cannot change files.
47
+ * Did the agent specifically lose the ability to change files?
36
48
  *
37
- * ⚠️ Kept in step with `PROFILE_INFO.isReadOnly` by a test, not by discipline — this module
38
- * cannot import tool-config without a cycle.
49
+ * ⚠️ A NARROWER QUESTION THAN `isNarrowing`, and the host needs both. "Fewer tools" and
50
+ * "cannot write any more" are different facts, and a notice that says "it can no longer
51
+ * change files" when the agent merely dropped six tools is a false statement in the
52
+ * transcript — exactly the class of error this whole review found.
39
53
  */
40
- const READ_ONLY_PROFILES = new Set([
41
- 'read-only',
42
- 'security',
43
- 'qa',
44
- 'architect',
45
- 'planner',
46
- 'analyst',
47
- 'designer',
48
- ]);
54
+ export function lostWriteAccess(before, after) {
55
+ if (before === after)
56
+ return false;
57
+ const couldWrite = before === undefined || !isReadOnlyProfile(before);
58
+ const canWrite = after === undefined || !isReadOnlyProfile(after);
59
+ return couldWrite && !canWrite;
60
+ }
61
+ /*
62
+ ⚠️ NO DEFENSIVE LOOKUP HERE — the profile is validated at the boundary instead.
63
+ `PROFILE_INFO` is a total Record over `ToolProfile`, so a guard here is dead code by the
64
+ type system's reckoning and ESLint says so. The real risk is a bogus string arriving over
65
+ IPC, and the honest place to refuse that is once, on the way in, rather than to absorb it
66
+ silently at every reader. See `isKnownProfile` and its use in `updateAgent`.
67
+ */
68
+ const isReadOnlyProfile = (p) => PROFILE_INFO[p].isReadOnly;
69
+ /** Is this a profile the registry actually knows? Used to refuse bad IPC data. */
70
+ export function isKnownProfile(p) {
71
+ return typeof p === 'string' && Object.prototype.hasOwnProperty.call(PROFILE_INFO, p);
72
+ }
73
+ /** How many tools a profile grants. `undefined` means no filter, i.e. all of them. */
74
+ function toolReach(p) {
75
+ if (p === undefined)
76
+ return Number.POSITIVE_INFINITY;
77
+ return getToolsForProfile(p).length;
78
+ }
49
79
  /** Order-insensitive comparison — a reordered skill list is not a change. */
50
80
  function sameStrings(a, b) {
51
81
  if (a === undefined || b === undefined)
@@ -269,8 +299,26 @@ export class TeamAgent {
269
299
  this.description = patch.description;
270
300
  if (patch.systemPromptAddition !== undefined)
271
301
  this.systemPromptAddition = patch.systemPromptAddition;
272
- if (patch.toolProfile !== undefined)
302
+ /*
303
+ ⚠️ THE PROFILE IS A LABEL; `toolFilter` IS THE ENFORCEMENT. `initialize()` passes
304
+ `toolFilter` to the factory and never looks at `toolProfile`, so setting the profile
305
+ alone changed the word on screen and nothing else: an agent switched to `read-only`
306
+ kept `write_file` and 168 other tools, while `toolsNarrowed` truthfully reported that
307
+ its reach had shrunk. A security control that confirms and does not engage.
308
+
309
+ All four CONSTRUCTION sites already derive it — `fromSerialized`, `fromRole`,
310
+ `fromSpecialist`, `fromCustomDefinition` — which is why a restart applied the
311
+ restriction the running session had ignored, and why creating a read-only agent was
312
+ correct all along. Update was the only path that did not.
313
+
314
+ An explicit `toolFilter` still wins: `custom` profiles carry their own list.
315
+ */
316
+ if (patch.toolProfile !== undefined) {
273
317
  this.toolProfile = patch.toolProfile;
318
+ if (patch.toolFilter === undefined) {
319
+ this.toolFilter = getToolsForProfile(patch.toolProfile);
320
+ }
321
+ }
274
322
  if (patch.toolFilter !== undefined)
275
323
  this.toolFilter = patch.toolFilter;
276
324
  if (patch.enabledSkills !== undefined)
package/dist/team/team.js CHANGED
@@ -11,7 +11,7 @@ import { TeamAgent } from './team-agent.js';
11
11
  import { ROLE_EXPERTISE } from './types.js';
12
12
  import { SharedContextManager } from './shared-context.js';
13
13
  import { ArtifactStore } from './artifacts.js';
14
- import { isNarrowing } from './team-agent.js';
14
+ import { isNarrowing, isKnownProfile, lostWriteAccess } from './team-agent.js';
15
15
  import { resolveAgentIdCollision } from './collision-utils.js';
16
16
  /**
17
17
  * AgentTeam orchestrates multiple persistent agents
@@ -339,6 +339,14 @@ export class AgentTeam {
339
339
  throw new Error(`Agent id is immutable — '${id}' cannot be renamed. Duplicate the agent instead: ` +
340
340
  'mention handles are written into stored message text and cannot be migrated.');
341
341
  }
342
+ /*
343
+ ⚠️ An unknown profile would otherwise be absorbed silently: `getToolsForProfile` returns
344
+ nothing for it, so the agent would end up with an empty filter — every tool removed —
345
+ while the UI showed whatever string was sent. Refuse it here, once.
346
+ */
347
+ if (patch.toolProfile !== undefined && !isKnownProfile(patch.toolProfile)) {
348
+ throw new Error(`Unknown tool profile '${String(patch.toolProfile)}'`);
349
+ }
342
350
  const previousProfile = agent.toolProfile;
343
351
  const { rebuilt } = agent.applyUpdate(patch);
344
352
  if (rebuilt) {
@@ -352,6 +360,8 @@ export class AgentTeam {
352
360
  return {
353
361
  rebuilt,
354
362
  toolsNarrowed: isNarrowing(previousProfile, agent.toolProfile),
363
+ /* ⚠️ The narrower fact, so the host can word the notice truthfully. */
364
+ lostWriteAccess: lostWriteAccess(previousProfile, agent.toolProfile),
355
365
  previousProfile,
356
366
  newProfile: agent.toolProfile,
357
367
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@compilr-dev/sdk",
3
- "version": "0.20.1",
3
+ "version": "0.20.3",
4
4
  "description": "Universal agent runtime for building AI-powered applications",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",