@compilr-dev/sdk 0.2.15 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/agent.js CHANGED
@@ -6,6 +6,15 @@ import { resolveProvider } from './provider.js';
6
6
  import { resolvePreset } from './presets/index.js';
7
7
  import { assembleTools, deduplicateTools } from './tools.js';
8
8
  import { getContextWindow } from './models.js';
9
+ // Capability loading imports
10
+ import { TOOL_GROUPS } from './team/tool-config.js';
11
+ import { CAPABILITY_PACKS } from './capabilities/packs.js';
12
+ import { CapabilityManager } from './capabilities/manager.js';
13
+ import { CapabilityContext } from './capabilities/context.js';
14
+ import { createCapabilityHook } from './capabilities/hook.js';
15
+ import { createLoadCapabilityTool } from './capabilities/load-tool.js';
16
+ import { resolveProfileGroups, resolveUpfrontGroups } from './capabilities/profile-resolver.js';
17
+ import { MetaToolsRegistry, createMetaTools } from './meta-tools/registry.js';
9
18
  /**
10
19
  * Convert an AgentRunResult to our simplified RunResult
11
20
  */
@@ -77,6 +86,8 @@ class CompilrAgentImpl {
77
86
  agent;
78
87
  abortController;
79
88
  totalUsage = { inputTokens: 0, outputTokens: 0, totalTokens: 0 };
89
+ /** Meta-tools fallback handler (set when capabilities.enabled) */
90
+ _metaToolsFallback;
80
91
  constructor(config) {
81
92
  this.abortController = new AbortController();
82
93
  // Resolve provider
@@ -98,8 +109,8 @@ class CompilrAgentImpl {
98
109
  else {
99
110
  systemPrompt = preset.systemPrompt;
100
111
  }
101
- // Assemble tools
102
- const tools = deduplicateTools(assembleTools(preset, config?.tools));
112
+ // Assemble all tools from preset + config
113
+ const allTools = deduplicateTools(assembleTools(preset, config?.tools));
103
114
  // Build context manager if configured
104
115
  let contextManager;
105
116
  if (config?.context) {
@@ -114,6 +125,110 @@ class CompilrAgentImpl {
114
125
  // Build agent config
115
126
  const permissionsConfig = buildPermissions(config?.permissions, preset.defaultPermissions, config?.permissionRules, config?.includeDefaultRules);
116
127
  const guardrailsConfig = buildGuardrails(config?.guardrails);
128
+ // Merge hooks: user-provided + capability hook (if enabled)
129
+ const capabilityHooks = [];
130
+ let finalTools = allTools;
131
+ // --- Dynamic Capability Loading ---
132
+ if (config?.capabilities?.enabled) {
133
+ const capConfig = config.capabilities;
134
+ // 1. Resolve profile → groups → upfront groups
135
+ const profileGroups = resolveProfileGroups(capConfig.profile ?? 'full');
136
+ const upfrontGroups = resolveUpfrontGroups(profileGroups);
137
+ // 2. Create capability manager
138
+ const manager = new CapabilityManager({
139
+ profileGroups,
140
+ packs: CAPABILITY_PACKS,
141
+ upfrontGroups,
142
+ });
143
+ // 3. Build direct-tier tool name set (tools in upfront groups)
144
+ const directToolNames = new Set();
145
+ for (const groupId of upfrontGroups) {
146
+ const group = TOOL_GROUPS[groupId];
147
+ for (const toolName of group.tools) {
148
+ directToolNames.add(toolName);
149
+ }
150
+ }
151
+ // 4. Split tools into direct (registered on Agent) and meta (registry)
152
+ const directTools = [];
153
+ const metaRegistryTools = [];
154
+ for (const tool of allTools) {
155
+ const t = tool;
156
+ if (directToolNames.has(t.definition.name)) {
157
+ directTools.push(t);
158
+ }
159
+ else {
160
+ metaRegistryTools.push(t);
161
+ }
162
+ }
163
+ // 5. Initialize meta-tools registry
164
+ const registry = new MetaToolsRegistry();
165
+ const additionalTools = capConfig.additionalTools ?? [];
166
+ registry.initialize([...metaRegistryTools, ...additionalTools]);
167
+ // 6. Compute orphan tools (in registry but not in any capability pack)
168
+ const packedToolNames = new Set();
169
+ for (const pack of Object.values(CAPABILITY_PACKS)) {
170
+ for (const toolName of pack.tools) {
171
+ packedToolNames.add(toolName);
172
+ }
173
+ }
174
+ const orphanTools = registry
175
+ .getRegisteredTools()
176
+ .map((t) => t.definition.name)
177
+ .filter((name) => !packedToolNames.has(name));
178
+ // 7. Create capability context with filter sync
179
+ const capCtx = new CapabilityContext({
180
+ manager,
181
+ orphanTools,
182
+ onFilterUpdate: (allowed) => {
183
+ registry.setFilter(allowed);
184
+ },
185
+ });
186
+ capCtx.syncFilter();
187
+ // 8. Create meta-tools (get_tool_info) + fallback handler
188
+ const metaTools = createMetaTools(registry, {
189
+ onFilteredTool: (name) => capCtx.onFilteredTool(name),
190
+ });
191
+ // 9. Assemble final tool list: direct + get_tool_info + load_capability
192
+ finalTools = [...directTools, metaTools.getToolInfoTool];
193
+ if (manager.hasLoadablePacks()) {
194
+ finalTools.push(createLoadCapabilityTool(manager));
195
+ }
196
+ // 10. Create BeforeLLM hook for dynamic prompt assembly
197
+ const basePrompt = systemPrompt ?? '';
198
+ const capHook = createCapabilityHook(capCtx, basePrompt, {
199
+ maxAutoLoadsPerTurn: capConfig.maxAutoLoadsPerTurn,
200
+ conditionalModules: capConfig.conditionalModules,
201
+ staticSections: capConfig.staticSections,
202
+ getToolIndex: (allowedNames) => {
203
+ registry.setFilter(allowedNames);
204
+ return registry.getToolIndexForSystemPrompt();
205
+ },
206
+ });
207
+ capabilityHooks.push(capHook);
208
+ // Store registry for fallback handler setup (after Agent creation)
209
+ this._metaToolsFallback = metaTools.createFallback({
210
+ onFilteredTool: (name) => capCtx.onFilteredTool(name),
211
+ });
212
+ }
213
+ else {
214
+ // Legacy mode: register all tools directly (no capability loading)
215
+ finalTools = allTools;
216
+ // Include additional tools if provided (even without capabilities)
217
+ if (config?.capabilities?.additionalTools) {
218
+ finalTools = [...finalTools, ...config.capabilities.additionalTools];
219
+ }
220
+ }
221
+ // Merge hooks: capability hook + user-provided hooks
222
+ const mergedHooks = { ...config?.hooks };
223
+ if (capabilityHooks.length > 0) {
224
+ const existingBeforeLLM = config?.hooks?.beforeLLM;
225
+ const existingHooks = existingBeforeLLM
226
+ ? Array.isArray(existingBeforeLLM)
227
+ ? existingBeforeLLM
228
+ : [existingBeforeLLM]
229
+ : [];
230
+ mergedHooks.beforeLLM = [...existingHooks, ...capabilityHooks];
231
+ }
117
232
  this.agent = new Agent({
118
233
  provider,
119
234
  systemPrompt,
@@ -121,7 +236,7 @@ class CompilrAgentImpl {
121
236
  toolTimeoutMs: config?.toolTimeoutMs,
122
237
  contextManager,
123
238
  autoContextManagement: contextManager !== undefined,
124
- hooks: config?.hooks,
239
+ hooks: mergedHooks,
125
240
  pins: {},
126
241
  permissions: {
127
242
  defaultLevel: permissionsConfig.defaultLevel,
@@ -142,8 +257,12 @@ class CompilrAgentImpl {
142
257
  }
143
258
  },
144
259
  });
145
- // Register tools (cast AnyTool[] → Tool[] for the Agent API)
146
- this.agent.registerTools(tools);
260
+ // Register tools
261
+ this.agent.registerTools(finalTools);
262
+ // Set fallback handler for meta-tools (if capabilities enabled)
263
+ if (this._metaToolsFallback) {
264
+ this.agent.getToolRegistry().setFallbackHandler(this._metaToolsFallback);
265
+ }
147
266
  }
148
267
  async run(message, options) {
149
268
  const result = await this.agent.run(message, {
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Auto-Detect Capabilities — scans agent messages for "Tool not found"
3
+ * errors and auto-loads the corresponding capability packs.
4
+ */
5
+ import type { Message } from '@compilr-dev/agents';
6
+ import type { CapabilityManager } from './manager.js';
7
+ export interface AutoDetectResult {
8
+ /** Pack IDs that were auto-loaded */
9
+ loaded: string[];
10
+ /** Forbidden tool names with suggested agents */
11
+ forbidden: Array<{
12
+ toolName: string;
13
+ packId: string;
14
+ suggestedAgent?: string;
15
+ }>;
16
+ }
17
+ /**
18
+ * Scan recent messages for "Tool not found: X" errors and auto-load
19
+ * the corresponding capability packs (max N per turn, loadable only).
20
+ * Also detects forbidden tool access for handoff suggestions.
21
+ */
22
+ export declare function autoDetectCapabilities(manager: CapabilityManager, messages: Message[], maxLoads?: number): AutoDetectResult;
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Auto-Detect Capabilities — scans agent messages for "Tool not found"
3
+ * errors and auto-loads the corresponding capability packs.
4
+ */
5
+ /**
6
+ * Scan recent messages for "Tool not found: X" errors and auto-load
7
+ * the corresponding capability packs (max N per turn, loadable only).
8
+ * Also detects forbidden tool access for handoff suggestions.
9
+ */
10
+ export function autoDetectCapabilities(manager, messages, maxLoads = 2) {
11
+ const result = { loaded: [], forbidden: [] };
12
+ const toolNotFoundPattern = /Tool not found: (\w+)/;
13
+ // Find the most recent user message (contains the last turn's tool results)
14
+ for (let i = messages.length - 1; i >= 0; i--) {
15
+ const msg = messages[i];
16
+ if (msg.role !== 'user' || typeof msg.content === 'string')
17
+ continue;
18
+ // Scan this message's tool_result blocks for "Tool not found" errors
19
+ for (const block of msg.content) {
20
+ if (block.type !== 'tool_result' || !block.isError || !block.content)
21
+ continue;
22
+ // Extract text from tool_result content (may be string or structured)
23
+ const rawContent = block.content;
24
+ const content = typeof rawContent === 'string' ? rawContent : String(rawContent);
25
+ const match = toolNotFoundPattern.exec(content);
26
+ if (!match)
27
+ continue;
28
+ const toolName = match[1];
29
+ const packId = manager.findPackForTool(toolName);
30
+ if (!packId || manager.isLoaded(packId))
31
+ continue;
32
+ const tier = manager.getTier(packId);
33
+ if (tier === 'loadable' && result.loaded.length < maxLoads) {
34
+ manager.load(packId, 'auto-detect');
35
+ result.loaded.push(packId);
36
+ }
37
+ else if (tier === 'forbidden') {
38
+ result.forbidden.push({
39
+ toolName,
40
+ packId,
41
+ suggestedAgent: manager.getSuggestedAgent(packId),
42
+ });
43
+ }
44
+ }
45
+ // Only scan the most recent user message
46
+ break;
47
+ }
48
+ return result;
49
+ }
@@ -0,0 +1,46 @@
1
+ /**
2
+ * CapabilityContext — encapsulates the CapabilityManager + meta-tool
3
+ * filter synchronization for a single agent.
4
+ *
5
+ * Replaces the CLI's global setCapabilityManager/getCapabilityManager
6
+ * pattern with a per-agent instance.
7
+ */
8
+ import type { CapabilityManager } from './manager.js';
9
+ export interface CapabilityContextConfig {
10
+ manager: CapabilityManager;
11
+ /**
12
+ * Tools in the meta-registry that aren't in any capability pack.
13
+ * These are always visible regardless of which packs are loaded.
14
+ */
15
+ orphanTools: string[];
16
+ /**
17
+ * Callback to update the meta-tool filter with allowed tool names.
18
+ * Called whenever loaded packs change (auto-detect, auto-load, manual load).
19
+ */
20
+ onFilterUpdate: (allowedTools: string[]) => void;
21
+ }
22
+ export declare class CapabilityContext {
23
+ readonly manager: CapabilityManager;
24
+ private readonly orphanTools;
25
+ private readonly onFilterUpdate;
26
+ constructor(config: CapabilityContextConfig);
27
+ /**
28
+ * Update the meta-tool filter to match current loaded state.
29
+ * Call after loading packs or during BeforeLLM hook.
30
+ */
31
+ syncFilter(): void;
32
+ /**
33
+ * Get all currently allowed tool names (loaded packs + orphans).
34
+ */
35
+ getAllowedToolNames(): string[];
36
+ /**
37
+ * Auto-load callback for meta-registry fallback.
38
+ *
39
+ * When the agent tries to call a tool that's in the meta-registry
40
+ * but filtered out, this callback loads the corresponding pack
41
+ * and updates the filter so the tool can be retried.
42
+ *
43
+ * @returns true if the tool was successfully loaded (retry the call)
44
+ */
45
+ onFilteredTool(toolName: string): boolean;
46
+ }
@@ -0,0 +1,50 @@
1
+ /**
2
+ * CapabilityContext — encapsulates the CapabilityManager + meta-tool
3
+ * filter synchronization for a single agent.
4
+ *
5
+ * Replaces the CLI's global setCapabilityManager/getCapabilityManager
6
+ * pattern with a per-agent instance.
7
+ */
8
+ export class CapabilityContext {
9
+ manager;
10
+ orphanTools;
11
+ onFilterUpdate;
12
+ constructor(config) {
13
+ this.manager = config.manager;
14
+ this.orphanTools = config.orphanTools;
15
+ this.onFilterUpdate = config.onFilterUpdate;
16
+ }
17
+ /**
18
+ * Update the meta-tool filter to match current loaded state.
19
+ * Call after loading packs or during BeforeLLM hook.
20
+ */
21
+ syncFilter() {
22
+ const activeTools = this.manager.getActiveToolNames();
23
+ this.onFilterUpdate([...activeTools, ...this.orphanTools]);
24
+ }
25
+ /**
26
+ * Get all currently allowed tool names (loaded packs + orphans).
27
+ */
28
+ getAllowedToolNames() {
29
+ return [...this.manager.getActiveToolNames(), ...this.orphanTools];
30
+ }
31
+ /**
32
+ * Auto-load callback for meta-registry fallback.
33
+ *
34
+ * When the agent tries to call a tool that's in the meta-registry
35
+ * but filtered out, this callback loads the corresponding pack
36
+ * and updates the filter so the tool can be retried.
37
+ *
38
+ * @returns true if the tool was successfully loaded (retry the call)
39
+ */
40
+ onFilteredTool(toolName) {
41
+ const packId = this.manager.findPackForTool(toolName);
42
+ if (!packId || this.manager.isLoaded(packId))
43
+ return false;
44
+ if (this.manager.getTier(packId) !== 'loadable')
45
+ return false;
46
+ this.manager.load(packId, 'auto-load');
47
+ this.syncFilter();
48
+ return true;
49
+ }
50
+ }
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Capability Hook — BeforeLLM hook that dynamically rebuilds the
3
+ * system prompt based on loaded capability packs.
4
+ *
5
+ * Responsibilities:
6
+ * 1. Auto-detect "Tool not found" in recent messages → auto-load packs
7
+ * 2. Sync meta-tool filter with loaded state
8
+ * 3. Rebuild system prompt with dynamic sections (modules, snippets, catalog)
9
+ * 4. Preserve anchor blocks from agent library injection
10
+ * 5. Inject forbidden tool boundary hints as messages
11
+ */
12
+ import type { Message, BeforeLLMHookResult } from '@compilr-dev/agents';
13
+ import type { CapabilityContext } from './context.js';
14
+ /**
15
+ * A conditional system prompt module that gets included
16
+ * when certain capability packs are loaded.
17
+ */
18
+ export interface ConditionalModule {
19
+ /** Module content to append to the system prompt */
20
+ content: string;
21
+ /** Include when ANY of these prompt module IDs are active */
22
+ whenModuleActive: string[];
23
+ }
24
+ export interface CapabilityHookConfig {
25
+ /** Max packs to auto-load per turn from "Tool not found" detection */
26
+ maxAutoLoadsPerTurn?: number;
27
+ /** Conditional prompt modules to include based on loaded packs */
28
+ conditionalModules?: ConditionalModule[];
29
+ /**
30
+ * Callback to generate the filtered tool index section.
31
+ * Receives the list of currently allowed tool names.
32
+ * Should return a string to append to the system prompt (e.g., meta-tool index).
33
+ */
34
+ getToolIndex?: (allowedToolNames: string[]) => string;
35
+ /**
36
+ * Static prompt section to always include (e.g., meta-tools protocol).
37
+ * Appended before conditional modules.
38
+ */
39
+ staticSections?: string[];
40
+ }
41
+ /**
42
+ * Create a BeforeLLM hook that dynamically manages the system prompt
43
+ * based on loaded capabilities.
44
+ *
45
+ * @param ctx - CapabilityContext for this agent
46
+ * @param basePrompt - Base system prompt (without dynamic sections)
47
+ * @param config - Optional configuration
48
+ */
49
+ export declare function createCapabilityHook(ctx: CapabilityContext, basePrompt: string, config?: CapabilityHookConfig): (hookCtx: {
50
+ messages: Message[];
51
+ systemPrompt: string;
52
+ }) => BeforeLLMHookResult;
@@ -0,0 +1,110 @@
1
+ /**
2
+ * Capability Hook — BeforeLLM hook that dynamically rebuilds the
3
+ * system prompt based on loaded capability packs.
4
+ *
5
+ * Responsibilities:
6
+ * 1. Auto-detect "Tool not found" in recent messages → auto-load packs
7
+ * 2. Sync meta-tool filter with loaded state
8
+ * 3. Rebuild system prompt with dynamic sections (modules, snippets, catalog)
9
+ * 4. Preserve anchor blocks from agent library injection
10
+ * 5. Inject forbidden tool boundary hints as messages
11
+ */
12
+ import { autoDetectCapabilities } from './auto-detect.js';
13
+ import { generateCapabilityCatalog } from './catalog.js';
14
+ /**
15
+ * Create a BeforeLLM hook that dynamically manages the system prompt
16
+ * based on loaded capabilities.
17
+ *
18
+ * @param ctx - CapabilityContext for this agent
19
+ * @param basePrompt - Base system prompt (without dynamic sections)
20
+ * @param config - Optional configuration
21
+ */
22
+ export function createCapabilityHook(ctx, basePrompt, config) {
23
+ const maxAutoLoads = config?.maxAutoLoadsPerTurn ?? 2;
24
+ const conditionalModules = config?.conditionalModules ?? [];
25
+ const staticSections = config?.staticSections ?? [];
26
+ const getToolIndex = config?.getToolIndex;
27
+ return (hookCtx) => {
28
+ const manager = ctx.manager;
29
+ // 1. Auto-detect capability needs from tool failures (safety net)
30
+ let forbiddenHints = [];
31
+ if (manager.hasLoadablePacks() || hookCtx.messages.length > 1) {
32
+ const detected = autoDetectCapabilities(manager, hookCtx.messages, maxAutoLoads);
33
+ if (detected.forbidden.length > 0) {
34
+ forbiddenHints = detected.forbidden.map((f) => {
35
+ const agentHint = f.suggestedAgent
36
+ ? ` Consider using handoff("${f.suggestedAgent}", "Need ${f.packId} access for ${f.toolName}").`
37
+ : '';
38
+ return `[System] Tool "${f.toolName}" requires ${f.packId} capability which is outside your tool profile.${agentHint}`;
39
+ });
40
+ }
41
+ }
42
+ // 2. Sync meta-tool filter with current loaded state
43
+ ctx.syncFilter();
44
+ // 3. Build dynamic system prompt sections
45
+ const dynamicSections = [];
46
+ // Static sections (e.g., meta-tools protocol)
47
+ for (const section of staticSections) {
48
+ dynamicSections.push(section);
49
+ }
50
+ // Conditional modules based on loaded packs
51
+ const activeModuleIds = manager.getActivePromptModuleIds();
52
+ for (const mod of conditionalModules) {
53
+ if (mod.whenModuleActive.some((id) => activeModuleIds.has(id))) {
54
+ dynamicSections.push(mod.content);
55
+ }
56
+ }
57
+ // Prompt snippets from loaded packs
58
+ const snippets = manager.getActivePromptSnippets();
59
+ if (snippets.length > 0) {
60
+ dynamicSections.push('## Active Tool Capabilities\n' + snippets.join('\n'));
61
+ }
62
+ // Filtered tool index
63
+ if (getToolIndex) {
64
+ const allowedTools = ctx.getAllowedToolNames();
65
+ dynamicSections.push(getToolIndex(allowedTools));
66
+ }
67
+ // Capability catalog (if loadable packs remain)
68
+ if (manager.hasLoadablePacks()) {
69
+ const catalog = manager.getCatalog();
70
+ const catalogSection = generateCapabilityCatalog(catalog, manager.getLoadedPackIds());
71
+ if (catalogSection) {
72
+ dynamicSections.push(catalogSection);
73
+ }
74
+ }
75
+ // 4. Preserve anchors from the current system prompt.
76
+ // The agents library injects anchors into systemContent BEFORE this hook runs.
77
+ // We must extract and re-append them since we rebuild from basePrompt.
78
+ const anchorsBlock = extractAnchorsBlock(hookCtx.systemPrompt);
79
+ const hookResult = {
80
+ systemPrompt: basePrompt + '\n\n' + dynamicSections.join('\n\n') + anchorsBlock,
81
+ };
82
+ // 5. Inject forbidden boundary messages (handoff suggestions)
83
+ if (forbiddenHints.length > 0) {
84
+ const hintContent = forbiddenHints.join('\n');
85
+ const hintMessage = { role: 'user', content: hintContent };
86
+ hookResult.messages = [...hookCtx.messages, hintMessage];
87
+ }
88
+ return hookResult;
89
+ };
90
+ }
91
+ /**
92
+ * Extract the anchors block from a system prompt.
93
+ * The agents library injects "## Active Anchors (Critical Information)"
94
+ * before BeforeLLM hooks run. We need to preserve it when rebuilding.
95
+ */
96
+ function extractAnchorsBlock(systemPrompt) {
97
+ const anchorMarker = '## Active Anchors (Critical Information)';
98
+ const anchorIdx = systemPrompt.indexOf(anchorMarker);
99
+ if (anchorIdx <= 0)
100
+ return '';
101
+ // Find the preceding "---" separator
102
+ const separatorBefore = systemPrompt.lastIndexOf('---', anchorIdx);
103
+ const startIdx = separatorBefore > 0 ? separatorBefore : anchorIdx;
104
+ // Find the end: either the next "---" after the anchor block, or end of string
105
+ const afterAnchor = systemPrompt.indexOf('\n\n---\n\n', anchorIdx);
106
+ if (afterAnchor > 0) {
107
+ return '\n\n' + systemPrompt.substring(startIdx, afterAnchor + 7);
108
+ }
109
+ return '\n\n' + systemPrompt.substring(startIdx);
110
+ }
@@ -12,3 +12,10 @@ export { CapabilityManager } from './manager.js';
12
12
  export type { CapabilityManagerConfig } from './manager.js';
13
13
  export { createLoadCapabilityTool } from './load-tool.js';
14
14
  export { generateCapabilityCatalog } from './catalog.js';
15
+ export { CapabilityContext } from './context.js';
16
+ export type { CapabilityContextConfig } from './context.js';
17
+ export { createCapabilityHook } from './hook.js';
18
+ export type { CapabilityHookConfig, ConditionalModule } from './hook.js';
19
+ export { autoDetectCapabilities } from './auto-detect.js';
20
+ export type { AutoDetectResult } from './auto-detect.js';
21
+ export { resolveProfileGroups, resolveUpfrontGroups } from './profile-resolver.js';
@@ -14,3 +14,11 @@ export { CapabilityManager } from './manager.js';
14
14
  export { createLoadCapabilityTool } from './load-tool.js';
15
15
  // Catalog generator
16
16
  export { generateCapabilityCatalog } from './catalog.js';
17
+ // Context — per-agent capability state + filter sync
18
+ export { CapabilityContext } from './context.js';
19
+ // Hook — BeforeLLM hook for dynamic prompt assembly
20
+ export { createCapabilityHook } from './hook.js';
21
+ // Auto-detection — scan messages for "Tool not found" errors
22
+ export { autoDetectCapabilities } from './auto-detect.js';
23
+ // Profile resolver — convert profiles/tool lists to group IDs
24
+ export { resolveProfileGroups, resolveUpfrontGroups } from './profile-resolver.js';
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Profile Resolver — converts tool profiles or custom tool lists
3
+ * into capability group IDs for the CapabilityManager.
4
+ */
5
+ import { type ToolProfile } from '../team/tool-config.js';
6
+ /**
7
+ * Convert a profile name or custom tool list into capability group IDs.
8
+ *
9
+ * @param profile - A named profile ('full', 'developer', etc.) or a custom tool name list
10
+ * @returns Array of group IDs that the profile allows
11
+ */
12
+ export declare function resolveProfileGroups(profile: ToolProfile | string[]): string[];
13
+ /**
14
+ * Determine which groups should be loaded upfront (direct tier).
15
+ * Upfront = groups marked as 'direct' tier AND in the agent's profile.
16
+ */
17
+ export declare function resolveUpfrontGroups(profileGroups: string[]): string[];
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Profile Resolver — converts tool profiles or custom tool lists
3
+ * into capability group IDs for the CapabilityManager.
4
+ */
5
+ import { TOOL_GROUPS, TOOL_PROFILES } from '../team/tool-config.js';
6
+ /**
7
+ * Convert a profile name or custom tool list into capability group IDs.
8
+ *
9
+ * @param profile - A named profile ('full', 'developer', etc.) or a custom tool name list
10
+ * @returns Array of group IDs that the profile allows
11
+ */
12
+ export function resolveProfileGroups(profile) {
13
+ if (typeof profile === 'string') {
14
+ // Named profile — look up in TOOL_PROFILES
15
+ const groups = TOOL_PROFILES[profile];
16
+ return [...groups];
17
+ }
18
+ // Custom tool list — find groups that contain at least one of these tools
19
+ const toolSet = new Set(profile);
20
+ const groups = [];
21
+ for (const [groupId, group] of Object.entries(TOOL_GROUPS)) {
22
+ if (group.tools.some((t) => toolSet.has(t))) {
23
+ groups.push(groupId);
24
+ }
25
+ }
26
+ return groups;
27
+ }
28
+ /**
29
+ * Determine which groups should be loaded upfront (direct tier).
30
+ * Upfront = groups marked as 'direct' tier AND in the agent's profile.
31
+ */
32
+ export function resolveUpfrontGroups(profileGroups) {
33
+ const profileSet = new Set(profileGroups);
34
+ return Object.entries(TOOL_GROUPS)
35
+ .filter(([, group]) => group.tier === 'direct')
36
+ .map(([id]) => id)
37
+ .filter((id) => profileSet.has(id));
38
+ }
package/dist/config.d.ts CHANGED
@@ -3,6 +3,49 @@
3
3
  */
4
4
  import type { LLMProvider, Message, Tool, ToolPermission, HooksConfig, AnchorInput, AgentEvent, ToolExecutionResult } from '@compilr-dev/agents';
5
5
  import type { Preset } from './presets/types.js';
6
+ import type { ToolProfile } from './team/tool-config.js';
7
+ import type { ConditionalModule } from './capabilities/hook.js';
8
+ /**
9
+ * Dynamic capability loading configuration.
10
+ *
11
+ * When enabled, tools are loaded on-demand instead of all upfront,
12
+ * reducing token usage by ~50-60% on initial turns.
13
+ */
14
+ export interface CapabilitiesConfig {
15
+ /** Enable dynamic capability loading. Default: false (all tools upfront) */
16
+ enabled: boolean;
17
+ /**
18
+ * Tool profile — determines which capability packs are available.
19
+ * Named profile ('full', 'developer', etc.) or custom tool name list.
20
+ * Default: 'full'
21
+ */
22
+ profile?: ToolProfile | string[];
23
+ /**
24
+ * Whether to enable meta-tools mode (get_tool_info + registry fallback).
25
+ * Loadable tools go through the meta-registry instead of being declared directly.
26
+ * Default: true when capabilities.enabled is true
27
+ */
28
+ metaTools?: boolean;
29
+ /**
30
+ * Max packs to auto-load per turn when "Tool not found" detected.
31
+ * Default: 2
32
+ */
33
+ maxAutoLoadsPerTurn?: number;
34
+ /**
35
+ * Additional tools to include that aren't in any capability pack
36
+ * (e.g., MCP tools, platform tools). Always available.
37
+ */
38
+ additionalTools?: Tool[];
39
+ /**
40
+ * Conditional prompt modules to include based on loaded packs.
41
+ * Default: git-safety, platform-tool-hints, factory-tool-hints
42
+ */
43
+ conditionalModules?: ConditionalModule[];
44
+ /**
45
+ * Static prompt sections to always include (e.g., meta-tools protocol).
46
+ */
47
+ staticSections?: string[];
48
+ }
6
49
  /**
7
50
  * Supported provider types for auto-detection
8
51
  */
@@ -147,6 +190,12 @@ export interface CompilrAgentConfig {
147
190
  hooks?: HooksConfig;
148
191
  /** Context management configuration */
149
192
  context?: ContextConfig;
193
+ /**
194
+ * Dynamic capability loading configuration.
195
+ * When enabled, tools are loaded on-demand for token efficiency.
196
+ * Omit or set enabled: false for the legacy all-tools-upfront mode.
197
+ */
198
+ capabilities?: CapabilitiesConfig;
150
199
  }
151
200
  /**
152
201
  * High-level agent interface
package/dist/index.d.ts CHANGED
@@ -34,7 +34,7 @@
34
34
  * ```
35
35
  */
36
36
  export { createCompilrAgent } from './agent.js';
37
- export type { CompilrAgentConfig, CompilrAgent, RunOptions, RunResult, ToolCallRecord, ToolConfig, UsageInfo, ProviderType, PermissionCallback, GuardrailConfig, ContextConfig, } from './config.js';
37
+ export type { CompilrAgentConfig, CompilrAgent, RunOptions, RunResult, ToolCallRecord, ToolConfig, UsageInfo, ProviderType, PermissionCallback, GuardrailConfig, ContextConfig, CapabilitiesConfig, } from './config.js';
38
38
  export { AgentTeam, TeamAgent, SharedContextManager, ArtifactStore, DelegationTracker, ContextResolver, } from './team/index.js';
39
39
  export type { AgentTeamConfig, TeamAgentConfig, ITeamPersistence, IArtifactStorage, ISessionRegistry, CustomAgentDefinition, 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, SkillToolRequirement, } from './team/index.js';
@@ -49,8 +49,8 @@ export { type ModelTier, type TierInfo, type ProviderModelMap, MODEL_TIERS, TIER
49
49
  export { assembleTools, deduplicateTools } from './tools.js';
50
50
  export { MetaToolsRegistry, createMetaTools, META_TOOLS_SYSTEM_PROMPT_PREFIX, } from './meta-tools/index.js';
51
51
  export type { MetaToolStats, MetaTools, FallbackOptions } from './meta-tools/index.js';
52
- export { CapabilityManager, CAPABILITY_PACKS, FORBIDDEN_PACK_SUGGESTIONS, getPackCount, getAllPackIds, createLoadCapabilityTool, generateCapabilityCatalog, } from './capabilities/index.js';
53
- export type { CapabilityPack, CapabilityTier, LoadedCapability, CapabilityCatalogEntry, CapabilityLoadResult, CapabilityManagerConfig, } from './capabilities/index.js';
52
+ export { CapabilityManager, CapabilityContext, createCapabilityHook, autoDetectCapabilities, resolveProfileGroups, resolveUpfrontGroups, CAPABILITY_PACKS, FORBIDDEN_PACK_SUGGESTIONS, getPackCount, getAllPackIds, createLoadCapabilityTool, generateCapabilityCatalog, } from './capabilities/index.js';
53
+ export type { CapabilityPack, CapabilityTier, LoadedCapability, CapabilityCatalogEntry, CapabilityLoadResult, CapabilityManagerConfig, CapabilityContextConfig, CapabilityHookConfig, ConditionalModule, AutoDetectResult, } from './capabilities/index.js';
54
54
  export { SystemPromptBuilder, buildSystemPrompt, detectGitRepository, getModuleStats, ALL_MODULES, IDENTITY_MODULE, STYLE_MODULE, TASK_EXECUTION_MODULE, TODO_MANAGEMENT_MODULE, TOOL_USAGE_DIRECT_MODULE, TOOL_USAGE_HINTS_MODULE, PLATFORM_TOOL_HINTS_MODULE, FACTORY_TOOL_HINTS_MODULE, TOOL_USAGE_META_MODULE, DELEGATION_MODULE, GIT_SAFETY_MODULE, SUGGEST_MODULE, IMPORTANT_RULES_MODULE, VISUAL_OUTPUT_MODULE, ENVIRONMENT_MODULE, shouldIncludeModule, getEstimatedTokensForConditions, getTotalEstimatedTokens, } from './system-prompt/index.js';
55
55
  export type { SystemPromptContext, BuildResult, SystemPromptModule, ModuleConditions, } from './system-prompt/index.js';
56
56
  export type { ProjectType, ProjectStatus, RepoPattern, WorkflowMode, LifecycleState, WorkItemType, WorkItemStatus, WorkItemPriority, GuidedStep, DocumentType, PlanStatus, Project, WorkItem, ProjectDocument, Plan, PlanSummary, PlanWithWorkItem, HistoryEntry, CreateProjectInput, UpdateProjectInput, ProjectListOptions, CreateWorkItemInput, UpdateWorkItemInput, QueryWorkItemsInput, CreateDocumentInput, UpdateDocumentInput, CreatePlanInput, UpdatePlanInput, ListPlansOptions, WorkItemQueryResult, ProjectListResult, BulkCreateItem, WorkItemComment, CreateCommentInput, UpdateCommentInput, IProjectRepository, IWorkItemRepository, IDocumentRepository, IPlanRepository, ICommentRepository, IAnchorService, IArtifactService, IEpisodeService, AnchorData, ArtifactType, ArtifactData, ArtifactSummaryData, WorkEpisode, ProjectWorkSummary, PlatformContext, PlatformToolsConfig, PlatformHooks, StepCriteria, } from './platform/index.js';
package/dist/index.js CHANGED
@@ -96,6 +96,14 @@ export { MetaToolsRegistry, createMetaTools, META_TOOLS_SYSTEM_PROMPT_PREFIX, }
96
96
  export {
97
97
  // Manager
98
98
  CapabilityManager,
99
+ // Context (per-agent state + filter sync)
100
+ CapabilityContext,
101
+ // Hook (BeforeLLM dynamic prompt assembly)
102
+ createCapabilityHook,
103
+ // Auto-detection (tool-not-found scanning)
104
+ autoDetectCapabilities,
105
+ // Profile resolution
106
+ resolveProfileGroups, resolveUpfrontGroups,
99
107
  // Pack definitions
100
108
  CAPABILITY_PACKS, FORBIDDEN_PACK_SUGGESTIONS, getPackCount, getAllPackIds,
101
109
  // Self-loading tool
@@ -104,17 +104,26 @@ export function createBacklogTools(config) {
104
104
  if (!item) {
105
105
  return createErrorResult(`Item not found: ${input.id}`);
106
106
  }
107
+ const itemData = {
108
+ id: item.itemId,
109
+ type: TYPE_TO_LIBRARY[item.type],
110
+ title: item.title,
111
+ description: item.description ?? '',
112
+ status: STATUS_TO_LIBRARY[item.status],
113
+ priority: item.priority,
114
+ };
115
+ // Include comments when available
116
+ if (ctx.comments) {
117
+ const itemComments = await ctx.comments.listByWorkItem(item.id);
118
+ itemData.comments = itemComments.map((c) => ({
119
+ id: c.id,
120
+ author: c.author,
121
+ content: c.content,
122
+ createdAt: c.createdAt.toISOString(),
123
+ }));
124
+ }
107
125
  return createSuccessResult({
108
- items: [
109
- {
110
- id: item.itemId,
111
- type: TYPE_TO_LIBRARY[item.type],
112
- title: item.title,
113
- description: item.description ?? '',
114
- status: STATUS_TO_LIBRARY[item.status],
115
- priority: item.priority,
116
- },
117
- ],
126
+ items: [itemData],
118
127
  total: 1,
119
128
  filtered: 1,
120
129
  });
@@ -13,12 +13,14 @@
13
13
  */
14
14
  import type { PlatformToolsConfig } from '../context.js';
15
15
  export declare function createWorkItemTools(config: PlatformToolsConfig): (import("@compilr-dev/agents").Tool<{
16
+ item_id?: string;
16
17
  project_id?: number;
17
18
  status?: string;
18
19
  type?: string;
19
20
  priority?: string;
20
21
  owner?: string;
21
22
  search?: string;
23
+ include_comments?: boolean;
22
24
  limit?: number;
23
25
  offset?: number;
24
26
  }> | import("@compilr-dev/agents").Tool<{
@@ -21,10 +21,14 @@ export function createWorkItemTools(config) {
21
21
  // ---------------------------------------------------------------------------
22
22
  const workitemQueryTool = defineTool({
23
23
  name: 'workitem_query',
24
- description: 'Query work items from the project backlog. Supports filtering by status, type, priority, owner, and search.',
24
+ description: 'Query work items from the project backlog. Use item_id for a single item with full details and comments. Supports filtering by status, type, priority, owner, and search.',
25
25
  inputSchema: {
26
26
  type: 'object',
27
27
  properties: {
28
+ item_id: {
29
+ type: 'string',
30
+ description: 'Get a specific item by ID (e.g., "REQ-001"). Returns full details with comments.',
31
+ },
28
32
  status: {
29
33
  type: 'string',
30
34
  enum: ['backlog', 'in_progress', 'completed', 'skipped', 'all'],
@@ -49,6 +53,10 @@ export function createWorkItemTools(config) {
49
53
  type: 'string',
50
54
  description: 'Search in title and description',
51
55
  },
56
+ include_comments: {
57
+ type: 'boolean',
58
+ description: 'Include full comments for each item (default: false for lists, always true for single item_id lookup)',
59
+ },
52
60
  limit: {
53
61
  type: 'number',
54
62
  description: 'Max items to return',
@@ -72,7 +80,47 @@ export function createWorkItemTools(config) {
72
80
  if (!projectId) {
73
81
  return createErrorResult('No project specified and no active project. Use project_get or /projects to select a project first.');
74
82
  }
75
- // Resolve "self" to the active agent ID via hook
83
+ const comments = ctx.comments;
84
+ // Single item lookup by item_id
85
+ if (input.item_id) {
86
+ const item = await ctx.workItems.getByItemId(projectId, input.item_id);
87
+ if (!item) {
88
+ return createErrorResult(`Work item "${input.item_id}" not found in current project`);
89
+ }
90
+ const itemData = {
91
+ id: item.id,
92
+ itemId: item.itemId,
93
+ type: item.type,
94
+ status: item.status,
95
+ priority: item.priority,
96
+ owner: item.owner,
97
+ guidedStep: item.guidedStep,
98
+ title: item.title,
99
+ description: item.description,
100
+ estimatedEffort: item.estimatedEffort,
101
+ completedAt: item.completedAt?.toISOString(),
102
+ commitHash: item.commitHash,
103
+ createdAt: item.createdAt.toISOString(),
104
+ };
105
+ // Always include full comments for single-item lookup
106
+ if (comments) {
107
+ const itemComments = await comments.listByWorkItem(item.id);
108
+ itemData.comments = itemComments.map((c) => ({
109
+ id: c.id,
110
+ author: c.author,
111
+ content: c.content,
112
+ createdAt: c.createdAt.toISOString(),
113
+ }));
114
+ }
115
+ return createSuccessResult({
116
+ success: true,
117
+ items: [itemData],
118
+ total: 1,
119
+ hasMore: false,
120
+ projectId,
121
+ });
122
+ }
123
+ // List query
76
124
  let resolvedOwner = input.owner;
77
125
  if (input.owner === 'self') {
78
126
  resolvedOwner = hooks?.resolveOwner?.('self') ?? undefined;
@@ -88,7 +136,7 @@ export function createWorkItemTools(config) {
88
136
  offset: input.offset ?? 0,
89
137
  };
90
138
  const result = await ctx.workItems.query(queryInput);
91
- const comments = ctx.comments;
139
+ const wantFullComments = input.include_comments === true;
92
140
  const items = await Promise.all(result.items.map(async (item) => {
93
141
  const base = {
94
142
  id: item.id,
@@ -107,16 +155,19 @@ export function createWorkItemTools(config) {
107
155
  };
108
156
  if (!comments)
109
157
  return base;
110
- const itemComments = await comments.listByWorkItem(item.id);
111
- return {
112
- ...base,
113
- comments: itemComments.map((c) => ({
158
+ if (wantFullComments) {
159
+ const itemComments = await comments.listByWorkItem(item.id);
160
+ base.comments = itemComments.map((c) => ({
114
161
  id: c.id,
115
162
  author: c.author,
116
163
  content: c.content,
117
164
  createdAt: c.createdAt.toISOString(),
118
- })),
119
- };
165
+ }));
166
+ }
167
+ else {
168
+ base.commentCount = await comments.countByWorkItem(item.id);
169
+ }
170
+ return base;
120
171
  }));
121
172
  return createSuccessResult({
122
173
  success: true,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@compilr-dev/sdk",
3
- "version": "0.2.15",
3
+ "version": "0.3.0",
4
4
  "description": "Universal agent runtime for building AI-powered applications",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",