@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 +124 -5
- package/dist/capabilities/auto-detect.d.ts +22 -0
- package/dist/capabilities/auto-detect.js +49 -0
- package/dist/capabilities/context.d.ts +46 -0
- package/dist/capabilities/context.js +50 -0
- package/dist/capabilities/hook.d.ts +52 -0
- package/dist/capabilities/hook.js +110 -0
- package/dist/capabilities/index.d.ts +7 -0
- package/dist/capabilities/index.js +8 -0
- package/dist/capabilities/profile-resolver.d.ts +17 -0
- package/dist/capabilities/profile-resolver.js +38 -0
- package/dist/config.d.ts +49 -0
- package/dist/index.d.ts +3 -3
- package/dist/index.js +8 -0
- package/dist/platform/tools/backlog-tools.js +19 -10
- package/dist/platform/tools/workitem-tools.d.ts +2 -0
- package/dist/platform/tools/workitem-tools.js +60 -9
- package/package.json +1 -1
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
|
|
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:
|
|
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
|
|
146
|
-
this.agent.registerTools(
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
111
|
-
|
|
112
|
-
|
|
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,
|