@compilr-dev/cli 0.5.17 → 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/dist/.tsbuildinfo.app +1 -1
  2. package/dist/.tsbuildinfo.data +1 -1
  3. package/dist/.tsbuildinfo.domain +1 -1
  4. package/dist/agent.d.ts +41 -111
  5. package/dist/agent.js +238 -390
  6. package/dist/commands-v2/handlers/project.d.ts +1 -0
  7. package/dist/commands-v2/handlers/project.js +36 -2
  8. package/dist/commands-v2/handlers/team.js +23 -3
  9. package/dist/compilr-diff-companion.vsix +0 -0
  10. package/dist/entitlements/index.d.ts +23 -0
  11. package/dist/entitlements/index.js +110 -0
  12. package/dist/guide/cli-guide-entries.d.ts +15 -0
  13. package/dist/guide/cli-guide-entries.js +99 -0
  14. package/dist/guide/index.d.ts +5 -4
  15. package/dist/guide/index.js +4 -3
  16. package/dist/guide/shared-content.js +188 -21
  17. package/dist/handlers/permission-handler.js +10 -3
  18. package/dist/index.js +18 -0
  19. package/dist/repl-v2.d.ts +16 -0
  20. package/dist/repl-v2.js +51 -17
  21. package/dist/tools/db-tools.d.ts +1 -1
  22. package/dist/tools/platform-adapter.d.ts +1 -1
  23. package/dist/tools/platform-adapter.js +6 -1
  24. package/dist/tools.js +6 -1
  25. package/dist/ui/overlay/impl/app-model-overlay-v2.d.ts +57 -0
  26. package/dist/ui/overlay/impl/app-model-overlay-v2.js +232 -0
  27. package/dist/ui/overlay/impl/custom-agent-form-overlay-v2.d.ts +23 -1
  28. package/dist/ui/overlay/impl/custom-agent-form-overlay-v2.js +203 -47
  29. package/dist/ui/overlay/impl/model-overlay-v2.js +2 -2
  30. package/dist/ui/overlay/impl/new-overlay-v2.d.ts +2 -2
  31. package/dist/ui/overlay/impl/new-overlay-v2.js +10 -17
  32. package/dist/ui/overlay/impl/team-overlay-v2.js +2 -2
  33. package/dist/ui/overlay/index.d.ts +1 -0
  34. package/dist/ui/overlay/index.js +1 -0
  35. package/package.json +4 -4
package/dist/agent.js CHANGED
@@ -1,45 +1,108 @@
1
1
  /**
2
2
  * Agent Configuration
3
3
  *
4
- * Creates and configures the Agent instance with
5
- * ClaudeProvider, OllamaProvider, OpenAIProvider, or GeminiProvider
6
- * and available tools.
4
+ * Thin wrapper around createCompilrAgent() from @compilr-dev/sdk.
5
+ * Handles CLI-specific concerns: credential resolution, system prompt
6
+ * assembly, subagent callbacks, and token estimates.
7
7
  */
8
8
  import { log } from './foundation/logger.js';
9
- import { Agent, ContextManager, DEFAULT_CONTEXT_CONFIG, createTaskTool, createSuggestTool, defaultAgentTypes, TOOL_SETS, BUILTIN_GUARDRAILS, createProviderFromType, CapabilityManager, CapabilityContext, CAPABILITY_PACKS, GIT_SAFETY_MODULE, PLATFORM_TOOL_HINTS_MODULE, FACTORY_TOOL_HINTS_MODULE, TOOL_USAGE_META_MODULE, createLoadCapabilityTool, createCapabilityHook, resolveProfileGroups, resolveUpfrontGroups, } from '@compilr-dev/sdk';
9
+ import { createCompilrAgent, createTaskTool, defaultAgentTypes, TOOL_SETS, BUILTIN_GUARDRAILS, } from '@compilr-dev/sdk';
10
10
  import { isAutoCompactEnabled, isDelegationEnabled, getSetting } from './settings/index.js';
11
11
  import { getApiKey } from './utils/credentials.js';
12
- import { createToolRegistry, createMinimalToolRegistry, getDirectTools, getMetaTools, initializeMetaTools, getToolIndexForSystemPrompt, getFilteredToolIndexForSystemPrompt, getToolStats, setMetaToolFilter, createToolFallback, getRegisteredMetaTools, } from './tools.js';
13
- // TOOL_GROUPS no longer needed — profile resolution moved to SDK
14
- import { setCapabilityManager } from './multi-agent/capability-loader.js';
15
12
  import { getAgentRegistry } from './agents/registry.js';
16
13
  import { SystemPromptBuilder } from './system-prompt/index.js';
17
- import { getDefaultModelForTier, getModelContextWindow } from './models/index.js';
14
+ import { getDefaultModelForTier } from './models/index.js';
18
15
  import { PROVIDER_METADATA } from './models/providers.js';
19
16
  import { setStaticEstimates, estimateTokens, estimateJsonTokens, } from './utils/token-tracker.js';
20
17
  import { createFileLockCheckHook, createFileLockAcquireHook } from './multi-agent/file-lock-hook.js';
21
18
  import { getActiveProject } from './tools/project-db.js';
19
+ import { allDbTools, allFactoryTools } from './tools/db-tools.js';
20
+ import { createGuideTool } from '@compilr-dev/sdk';
21
+ import { CLI_GUIDE_ENTRIES } from './guide/cli-guide-entries.js';
22
+ const cliGuideTool = createGuideTool({
23
+ environment: 'cli',
24
+ additionalEntries: CLI_GUIDE_ENTRIES,
25
+ });
26
+ import { defineTool, createSuccessResult } from '@compilr-dev/agents';
27
+ /** Create plan_submit and plan_mode_exit tools if callbacks are provided */
28
+ function buildPlanModeTools(callbacks) {
29
+ if (!callbacks || !callbacks.onPlanSubmit || !callbacks.onPlanModeExit)
30
+ return [];
31
+ const onSubmit = callbacks.onPlanSubmit;
32
+ const onExit = callbacks.onPlanModeExit;
33
+ const planSubmitTool = defineTool({
34
+ name: 'plan_submit',
35
+ description: 'Submit a plan for user approval. Call when your plan is ready for review. ' +
36
+ 'The user will choose to approve, revise, or reject. On approval, write tools are unlocked.',
37
+ inputSchema: {
38
+ type: 'object',
39
+ properties: {
40
+ plan_id: { type: 'number', description: 'ID of the plan to submit' },
41
+ summary: { type: 'string', description: 'One-line summary of the plan' },
42
+ },
43
+ required: ['plan_id'],
44
+ },
45
+ execute: async (input) => {
46
+ const result = await onSubmit({ planId: input.plan_id, summary: input.summary });
47
+ const messages = {
48
+ 'approve-auto': 'Plan approved. Auto-accept mode enabled. Proceed with implementation.',
49
+ approve: 'Plan approved. Proceed with implementation.',
50
+ revise: `Plan needs revision. Feedback: ${result.feedback ?? 'No specific feedback.'}`,
51
+ reject: 'Plan rejected.',
52
+ };
53
+ return createSuccessResult({ action: result.action, message: messages[result.action] });
54
+ },
55
+ });
56
+ const planModeExitTool = defineTool({
57
+ name: 'plan_mode_exit',
58
+ description: 'Exit plan mode without a plan. Use when the task is simple enough to implement directly.',
59
+ inputSchema: {
60
+ type: 'object',
61
+ properties: {
62
+ reason: { type: 'string', description: 'Brief reason for exiting plan mode' },
63
+ },
64
+ required: ['reason'],
65
+ },
66
+ execute: async (input) => {
67
+ await onExit({ reason: input.reason });
68
+ return createSuccessResult({ message: `Exited plan mode: ${input.reason}. Full tools now available.` });
69
+ },
70
+ });
71
+ return [planSubmitTool, planModeExitTool];
72
+ }
22
73
  /**
23
74
  * Default permission rules for potentially dangerous operations
24
- * Note: Tool names are lowercase (bash, write_file, edit, git_commit)
25
75
  */
26
76
  const DEFAULT_PERMISSION_RULES = [
27
- // File writes need approval each time (user can see diff before approving)
28
77
  { toolName: 'write_file', level: 'once', description: 'Write/create files' },
29
78
  { toolName: 'edit', level: 'once', description: 'Edit file contents' },
30
- // Git operations that modify state
31
79
  { toolName: 'git_commit', level: 'once', description: 'Create git commit' },
32
80
  { toolName: 'git_branch', level: 'once', description: 'Create/delete git branches' },
33
- // Shell commands need approval each time
34
81
  { toolName: 'bash', level: 'once', description: 'Execute shell command' },
35
- // Project runners (can have side effects)
36
82
  { toolName: 'run_tests', level: 'once', description: 'Run test suite' },
37
83
  { toolName: 'run_lint', level: 'once', description: 'Run linter (may auto-fix)' },
38
- // Note: backlog_write removed - use workitem_* tools which modify database
39
84
  ];
85
+ /** Delegation config for tool result auto-summarization */
86
+ const CLI_DELEGATION_CONFIG = {
87
+ enabled: true,
88
+ delegationThreshold: 8000,
89
+ summaryMaxTokens: 800,
90
+ resultTTL: 600_000,
91
+ maxStoredResults: 50,
92
+ strategy: 'auto',
93
+ toolOverrides: {
94
+ bash: { threshold: 12000 },
95
+ grep: { threshold: 4000 },
96
+ git_diff: { threshold: 6000 },
97
+ get_tool_info: { enabled: false },
98
+ list_tools: { enabled: false },
99
+ ask_user: { enabled: false },
100
+ ask_user_simple: { enabled: false },
101
+ todo_read: { enabled: false },
102
+ },
103
+ };
40
104
  /**
41
105
  * Get permission info for a tool by name.
42
- * Returns undefined if the tool doesn't require special permission (always allowed).
43
106
  */
44
107
  export function getToolPermissionInfo(toolName) {
45
108
  const rule = DEFAULT_PERMISSION_RULES.find(r => r.toolName === toolName);
@@ -53,7 +116,6 @@ export function getToolPermissionInfo(toolName) {
53
116
  }
54
117
  /**
55
118
  * Get information about built-in guardrails.
56
- * Used for displaying guardrail info in /help or status overlays.
57
119
  */
58
120
  export function getBuiltinGuardrailInfo() {
59
121
  return BUILTIN_GUARDRAILS.map((g) => ({
@@ -66,21 +128,16 @@ export function getBuiltinGuardrailInfo() {
66
128
  }
67
129
  /**
68
130
  * Merges built-in agent types with custom agents from registry.
69
- * Custom agents get safe read-only tools by default.
70
131
  */
71
132
  function getMergedAgentTypes() {
72
133
  const registry = getAgentRegistry();
73
134
  const customAgents = registry.getCustomAgents();
74
- // Start with default agent types (library now has correct tool names)
75
135
  const mergedTypes = { ...defaultAgentTypes };
76
- // Add custom agents from registry
77
136
  for (const agent of customAgents) {
78
137
  mergedTypes[agent.name] = {
79
138
  description: agent.description,
80
139
  systemPrompt: agent.systemPrompt,
81
140
  defaultModel: agent.model === 'inherit' ? undefined : agent.model,
82
- // Safe read-only tools for custom agents (using library's TOOL_SETS)
83
- // Type assertion needed because TOOL_SETS typing might not be exported correctly
84
141
  allowedTools: Array.isArray(TOOL_SETS.READ_ONLY) ? [...(TOOL_SETS.READ_ONLY)] : [],
85
142
  maxIterations: 10,
86
143
  contextMode: 'inherit-summary',
@@ -88,189 +145,52 @@ function getMergedAgentTypes() {
88
145
  }
89
146
  return mergedTypes;
90
147
  }
91
- /**
92
- * Creates the appropriate LLM provider based on configuration.
93
- * Resolves CLI-specific concerns (credentials, settings) then delegates
94
- * provider instantiation to the SDK's createProviderFromType().
95
- */
96
- function createProvider(options) {
148
+ // ─── Provider Resolution (CLI-specific credentials) ─────────────────────────
149
+ function resolveProviderConfig(options) {
97
150
  const providerType = options.provider ?? 'claude';
98
- const quiet = options.quiet ?? false;
99
151
  const meta = PROVIDER_METADATA[providerType];
100
- // 1. Resolve API key from CLI credential store
101
152
  const apiKey = meta.requiresKey ? getApiKey(meta.credentialKey) : undefined;
102
153
  if (!apiKey && meta.requiresKey) {
103
154
  throw new Error(`${meta.displayName} API key not found.\n` +
104
155
  `Run /keys to set it, or: export ${meta.envVar}=...` +
105
156
  (providerType === 'claude' ? '\nOr use --provider ollama for local models.' : ''));
106
157
  }
107
- // 2. Resolve model (from options or registry default for balanced tier)
108
158
  const model = options.model ?? getDefaultModelForTier(providerType, 'balanced')?.id;
109
- // 3. Resolve Ollama base URL from settings
110
159
  const baseUrl = providerType === 'ollama'
111
160
  ? ((typeof getSetting('ollamaBaseUrl') === 'string'
112
161
  ? getSetting('ollamaBaseUrl')
113
- : undefined) ??
114
- process.env.OLLAMA_BASE_URL ??
115
- 'http://localhost:11434')
116
- : undefined;
117
- // 4. Log (unless quiet)
118
- if (!quiet)
119
- console.log(`Using ${meta.displayName} with model: ${model ?? 'default'}`);
120
- if (!quiet && providerType === 'ollama' && baseUrl !== 'http://localhost:11434') {
121
- console.log(` Ollama server: ${baseUrl ?? ''}`);
122
- }
123
- // 5. Delegate to SDK (with tiktoken-based token estimator for debug payload)
124
- return createProviderFromType(providerType, {
125
- model,
126
- apiKey: apiKey ?? undefined,
127
- baseUrl,
128
- siteName: providerType === 'openrouter' ? 'compilr-cli' : undefined,
129
- estimateTokens,
130
- });
131
- }
132
- // =============================================================================
133
- // Dynamic Capability Loading Helpers
134
- // =============================================================================
135
- /**
136
- * Derive profile group IDs from a tool filter (array of tool names).
137
- * A group is included if any of its tools are in the filter.
138
- */
139
- // getProfileGroupsFromToolFilter → replaced by resolveProfileGroups from SDK
140
- // autoDetectCapabilities → replaced by autoDetectCapabilities from SDK
141
- // createCapabilityHook → replaced by createCapabilityHook + CapabilityContext from SDK
142
- /**
143
- * Creates an Agent instance configured with all tools.
144
- */
145
- export function createAgent(options = {}) {
146
- const provider = createProvider(options);
147
- // Create context manager with model-specific limits
148
- // Uses library defaults: compact at 50%, summarize at 90%, emergency at 95%
149
- // Auto-compact can be disabled via settings (requires restart)
150
- const autoCompact = isAutoCompactEnabled();
151
- const contextManager = new ContextManager({
152
- provider,
153
- config: {
154
- maxContextTokens: getModelContextWindow(options.model ?? '', options.provider),
155
- // If auto-compact disabled, set threshold to 100% (never triggers)
156
- compaction: autoCompact
157
- ? DEFAULT_CONTEXT_CONFIG.compaction
158
- : {
159
- ...DEFAULT_CONTEXT_CONFIG.compaction,
160
- triggerThreshold: 1.0, // Never auto-trigger
161
- triggerInterval: Number.MAX_SAFE_INTEGER, // Never interval-trigger
162
- },
163
- },
164
- });
165
- // Create event handler for verbose mode
166
- const onEvent = options.verbose
167
- ? (event) => {
168
- if (event.type === 'tool_start') {
169
- log.debug({ component: 'agent', tool: event.name }, 'Tool starting');
170
- }
171
- else if (event.type === 'tool_end') {
172
- log.debug({ component: 'agent', tool: event.name }, 'Tool done');
173
- }
174
- }
162
+ : undefined) ?? process.env.OLLAMA_BASE_URL ?? 'http://localhost:11434')
175
163
  : undefined;
176
- // Build modular system prompt based on current context
177
- // Note: Anchors are NOT added here - they're handled by library's AnchorManager
178
- // which re-injects them on every LLM call (survives compaction)
179
- // Initialize meta-tools if enabled (needed for tool index)
180
- // includeWarmTools=true moves 11 warm tools into meta-registry (hot-tools mode)
181
- const useMetaTools = options.enableMetaTools && !options.minimal;
182
- if (useMetaTools) {
183
- initializeMetaTools(true);
184
- }
185
- // ==========================================================================
186
- // Dynamic Capability Loading
187
- // When meta-tools are enabled, create a CapabilityManager to control which
188
- // tools and prompt modules are active per-turn.
189
- // ==========================================================================
190
- let capabilityManager;
191
- let capabilityContext;
192
- let orphanToolNames = [];
193
- if (useMetaTools) {
194
- // Use SDK's profile resolver instead of local implementation
195
- const profileGroups = options.toolFilter
196
- ? resolveProfileGroups(options.toolFilter)
197
- : Object.keys(CAPABILITY_PACKS);
198
- const upfrontGroupIds = resolveUpfrontGroups(profileGroups);
199
- capabilityManager = new CapabilityManager({
200
- profileGroups,
201
- packs: CAPABILITY_PACKS,
202
- upfrontGroups: upfrontGroupIds,
203
- });
204
- // Expose the capability manager for slash command handlers (Phase 3)
205
- setCapabilityManager(capabilityManager);
206
- // Compute orphan tools (meta-registry tools not in any capability pack)
207
- const packedTools = new Set();
208
- for (const pack of Object.values(CAPABILITY_PACKS)) {
209
- for (const tool of pack.tools) {
210
- packedTools.add(tool);
211
- }
212
- }
213
- const registered = getRegisteredMetaTools();
214
- orphanToolNames = registered
215
- .map((t) => t.definition.name)
216
- .filter((name) => !packedTools.has(name));
217
- // Create capability context (SDK) for filter sync + auto-load callback
218
- capabilityContext = new CapabilityContext({
219
- manager: capabilityManager,
220
- orphanTools: orphanToolNames,
221
- onFilterUpdate: (allowed) => { setMetaToolFilter(allowed); },
222
- });
223
- capabilityContext.syncFilter();
224
- // Log stats (unless quiet mode)
225
- if (!options.quiet) {
226
- const stats = getToolStats();
227
- const loadedPacks = capabilityManager.getLoadedPackIds();
228
- const loadableCount = capabilityManager.getCatalog().length;
229
- console.log(`[Meta-tools] Direct: ${String(stats.directTools)}, ` +
230
- `Meta: ${String(stats.metaRegistryTools)}, ` +
231
- `Est. savings: ~${String(stats.tokenSavings)} tokens`);
232
- console.log(`[Capabilities] Upfront: ${String(loadedPacks.length)} packs, ` +
233
- `Loadable: ${String(loadableCount)} packs`);
164
+ if (!options.quiet) {
165
+ log.info({ component: 'agent', provider: meta.displayName, model: model ?? 'default' }, 'Provider configured');
166
+ if (providerType === 'ollama' && baseUrl !== 'http://localhost:11434') {
167
+ log.info({ component: 'agent', baseUrl }, 'Ollama server');
234
168
  }
235
169
  }
236
- // Generate tool index for static system prompt (only when NOT using capability loading)
237
- const metaToolsIndex = useMetaTools && !capabilityManager
238
- ? getToolIndexForSystemPrompt()
239
- : undefined;
240
- let systemPrompt;
241
- // Check if we should use minimal system prompt (for testing role identity)
170
+ return { provider: providerType, model, apiKey: apiKey ?? undefined, baseUrl };
171
+ }
172
+ // ─── System Prompt Assembly ─────────────────────────────────────────────────
173
+ function buildSystemPrompt(options) {
242
174
  if (options.useMinimalSystemPrompt && options.systemPromptAddition) {
243
- // Skip all modules - just use the role prompt directly
244
- systemPrompt = options.systemPromptAddition;
175
+ return options.systemPromptAddition;
245
176
  }
246
- else {
247
- // Use modular system prompt builder
248
- // When capability loading is active, exclude conditional modules:
249
- // git-safety, platform-tool-hints, factory-tool-hints, tool-usage-meta
250
- // These are dynamically added by the BeforeLLM hook based on loaded capabilities.
251
- const promptBuilder = new SystemPromptBuilder({
252
- enableMetaTools: capabilityManager ? false : useMetaTools,
253
- hasGit: capabilityManager ? false : undefined, // false = skip git-safety; undefined = auto-detect
254
- projectName: options.projectName,
255
- projectSlug: options.projectSlug,
256
- projectId: options.projectId,
257
- projectContext: options.projectContext,
258
- guidedModeContext: options.guidedModeContext,
259
- planModeContext: options.planModeContext,
260
- metaToolsIndex,
261
- hasRoleIdentity: !!options.systemPromptAddition, // Skip default identity when role is specified
262
- });
263
- const buildResult = promptBuilder.build();
264
- systemPrompt = buildResult.prompt;
265
- // Prepend role-specific system prompt (for team agents)
266
- // The default identity module is already excluded via hasRoleIdentity flag
267
- if (options.systemPromptAddition) {
268
- // Extract role name from the role prompt (format: "# ROLE: ROLE_NAME")
269
- const roleMatch = options.systemPromptAddition.match(/^#\s*ROLE:\s*(.+)$/m);
270
- const roleName = roleMatch ? roleMatch[1].trim() : 'specialized team member';
271
- // Put FULL role prompt at the END (recency effect is stronger than primacy)
272
- // LLMs weight the end of context more heavily
273
- const roleEnding = `
177
+ const promptBuilder = new SystemPromptBuilder({
178
+ enableMetaTools: false, // SDK handles capability-based prompt assembly
179
+ hasGit: undefined, // Auto-detect (SDK capability hook will override if needed)
180
+ projectName: options.projectName,
181
+ projectSlug: options.projectSlug,
182
+ projectId: options.projectId,
183
+ projectContext: options.projectContext,
184
+ guidedModeContext: options.guidedModeContext,
185
+ planModeContext: options.planModeContext,
186
+ hasRoleIdentity: !!options.systemPromptAddition,
187
+ });
188
+ let systemPrompt = promptBuilder.build().prompt;
189
+ // Wrap role identity at beginning AND end for team agents
190
+ if (options.systemPromptAddition) {
191
+ const roleMatch = options.systemPromptAddition.match(/^#\s*ROLE:\s*(.+)$/m);
192
+ const roleName = roleMatch ? roleMatch[1].trim() : 'specialized team member';
193
+ const roleEnding = `
274
194
 
275
195
  ---
276
196
 
@@ -279,113 +199,130 @@ export function createAgent(options = {}) {
279
199
  ${options.systemPromptAddition}
280
200
 
281
201
  **CRITICAL**: When asked "what is your role?", "what are you?", or similar identity questions, your response should acknowledge your assigned role. For example: "In this team, I'm the ${roleName}" or "I'm operating as the ${roleName} in this session."`;
282
- // Put role at BOTH beginning AND end, with full role prompt at the end
283
- systemPrompt = options.systemPromptAddition + '\n\n' + systemPrompt + roleEnding;
284
- }
202
+ systemPrompt = options.systemPromptAddition + '\n\n' + systemPrompt + roleEnding;
285
203
  }
286
- // Store base prompt for capability-aware BeforeLLM hook
287
- const baseSystemPrompt = capabilityManager ? systemPrompt : undefined;
288
- const agent = new Agent({
204
+ return systemPrompt;
205
+ }
206
+ // ─── Context Pressure Hook ──────────────────────────────────────────────────
207
+ function createContextPressureHook() {
208
+ return (ctx) => {
209
+ const msgCount = ctx.messages.length;
210
+ if (msgCount < 20)
211
+ return undefined;
212
+ let hint;
213
+ if (msgCount < 40) {
214
+ hint = `[Context: ~${String(msgCount)} messages] Consider task() for multi-file operations.`;
215
+ }
216
+ else if (msgCount < 60) {
217
+ hint = `[Context: ~${String(msgCount)} messages - HIGH] Prefer task() sub-agents for file reads and code search.`;
218
+ }
219
+ else {
220
+ hint = `[Context: ~${String(msgCount)} messages - CRITICAL] Use task() for ALL exploration. Do not read files directly.`;
221
+ }
222
+ const messages = [...ctx.messages];
223
+ messages.splice(1, 0, { role: 'user', content: hint });
224
+ return { messages };
225
+ };
226
+ }
227
+ // ─── Main Factory ───────────────────────────────────────────────────────────
228
+ /**
229
+ * Creates an Agent instance configured with all tools.
230
+ *
231
+ * Delegates to createCompilrAgent() from @compilr-dev/sdk for:
232
+ * - Provider creation, context management, observation masking
233
+ * - Smart windowing, tool output guards
234
+ * - Capability loading (meta-tools, dynamic prompt assembly)
235
+ * - Permission handling, guardrails, delegation
236
+ *
237
+ * CLI adds on top:
238
+ * - Credential resolution from CLI keystore
239
+ * - System prompt with project context and role identity
240
+ * - Subagent callbacks for UI updates
241
+ * - Token estimate logging
242
+ */
243
+ export function createAgent(options = {}) {
244
+ const { provider, model, apiKey } = resolveProviderConfig(options);
245
+ const systemPrompt = buildSystemPrompt(options);
246
+ log.info({
247
+ component: 'agent',
248
+ provider,
249
+ model: model ?? 'default',
250
+ quiet: options.quiet ?? false,
251
+ metaTools: options.enableMetaTools ?? false,
252
+ projectName: options.projectName,
253
+ }, 'Creating agent');
254
+ // ── Create agent via SDK ──────────────────────────────────────────────────
255
+ const compilrAgent = createCompilrAgent({
289
256
  provider,
257
+ model,
258
+ apiKey,
290
259
  systemPrompt,
291
260
  maxIterations: options.maxIterations ?? 50,
292
- onEvent,
293
- contextManager,
294
- // Set tool timeout to 2 minutes (default is 30s which is too short for complex operations)
295
- // Sub-agents inherit this timeout from the parent agent
296
261
  toolTimeoutMs: 120000,
297
- // Enable file tracking for context restoration hints
298
262
  enableFileTracking: true,
299
- // Enable permissions if handler is provided
263
+ logger: log,
264
+ // Permissions
300
265
  permissions: options.onPermissionRequest
301
- ? {
302
- enabled: true,
303
- defaultLevel: 'always', // Allow tools by default, unless specified in rules
304
- rules: DEFAULT_PERMISSION_RULES,
305
- onPermissionRequest: options.onPermissionRequest,
306
- }
307
- : undefined,
308
- // Iteration limit handler - asks user if they want to continue
266
+ ? options.onPermissionRequest
267
+ : 'auto',
268
+ permissionRules: DEFAULT_PERMISSION_RULES,
269
+ includeDefaultRules: true,
270
+ // Iteration limits
309
271
  onIterationLimitReached: options.onIterationLimitReached,
310
- // Anchors - critical info that survives context compaction
311
- // Library re-injects anchors on every LLM call
312
- anchors: options.enableAnchors
313
- ? {
314
- maxAnchors: 50,
315
- maxTokens: 4000,
316
- includeDefaults: true,
317
- // Note: We manage persistence ourselves via project-anchors.ts
318
- // Don't set persistPath - we load anchors manually below
319
- }
272
+ // Pins / anchors
273
+ pins: options.enableAnchors
274
+ ? { maxAnchors: 50, maxTokens: 4000, includeDefaults: true }
320
275
  : undefined,
321
- // Guardrails - pattern-based safety checks for risky operations
322
- // 15 built-in patterns: git destructive ops, rm -rf, DROP TABLE, secrets, etc.
276
+ // Guardrails
323
277
  guardrails: options.enableGuardrails
324
- ? {
325
- enabled: true,
326
- includeDefaults: true,
327
- onTriggered: options.onGuardrailTriggered,
278
+ ? { includeDefaults: true }
279
+ : false,
280
+ // Delegation
281
+ delegation: isDelegationEnabled() ? CLI_DELEGATION_CONFIG : undefined,
282
+ // Context management
283
+ context: {
284
+ compactionThreshold: isAutoCompactEnabled() ? undefined : 1.0,
285
+ },
286
+ // Suggest tool
287
+ onSuggest: options.onSuggest,
288
+ // Verbose event handler
289
+ onEvent: options.verbose
290
+ ? (event) => {
291
+ if (event.type === 'tool_start') {
292
+ log.debug({ component: 'agent', tool: event.name }, 'Tool starting');
293
+ }
294
+ else if (event.type === 'tool_end') {
295
+ log.debug({ component: 'agent', tool: event.name }, 'Tool done');
296
+ }
328
297
  }
329
298
  : undefined,
330
- // Tool result delegation auto-summarize large results to conserve context
331
- delegation: isDelegationEnabled()
299
+ // Capability loading (replaces CLI's manual meta-tools wiring)
300
+ // Platform tools (DB, factory) + MCP + guide + plan mode tools
301
+ capabilities: (options.enableMetaTools && !options.minimal && !options.noTools)
332
302
  ? {
333
303
  enabled: true,
334
- delegationThreshold: 8000,
335
- summaryMaxTokens: 800,
336
- resultTTL: 600_000,
337
- maxStoredResults: 50,
338
- strategy: 'auto',
339
- toolOverrides: {
340
- // Custom thresholds
341
- bash: { threshold: 12000 },
342
- grep: { threshold: 4000 },
343
- git_diff: { threshold: 6000 },
344
- // Never summarize — agent needs verbatim results
345
- get_tool_info: { enabled: false },
346
- list_tools: { enabled: false },
347
- ask_user: { enabled: false },
348
- ask_user_simple: { enabled: false },
349
- todo_read: { enabled: false },
350
- },
304
+ profile: options.toolFilter ?? 'full',
305
+ additionalTools: [
306
+ ...allDbTools,
307
+ ...allFactoryTools,
308
+ ...(options.mcpTools ?? []),
309
+ cliGuideTool,
310
+ ...buildPlanModeTools(options.planModeCallbacks),
311
+ ],
351
312
  }
352
- : undefined,
353
- // Hooks: context pressure, capability loading, file lock check/acquire
313
+ : {
314
+ enabled: false,
315
+ additionalTools: [
316
+ ...allDbTools,
317
+ ...allFactoryTools,
318
+ ...(options.mcpTools ?? []),
319
+ cliGuideTool,
320
+ ...buildPlanModeTools(options.planModeCallbacks),
321
+ ],
322
+ },
323
+ // CLI-specific hooks
354
324
  hooks: {
355
- beforeLLM: [
356
- // Context pressure hook
357
- (ctx) => {
358
- const utilization = contextManager.getUtilization();
359
- if (utilization < 0.3)
360
- return undefined;
361
- const pct = Math.round(utilization * 100);
362
- let hint;
363
- if (utilization < 0.6) {
364
- hint = `[Context: ${String(pct)}%] Consider task() for multi-file operations.`;
365
- }
366
- else if (utilization < 0.8) {
367
- hint = `[Context: ${String(pct)}% - HIGH] Prefer task() sub-agents for file reads and code search.`;
368
- }
369
- else {
370
- hint = `[Context: ${String(pct)}% - CRITICAL] Use task() for ALL exploration. Do not read files directly.`;
371
- }
372
- const messages = [...ctx.messages];
373
- messages.splice(1, 0, { role: 'user', content: hint });
374
- return { messages };
375
- },
376
- // Capability-aware system prompt assembly (SDK hook with auto-detection)
377
- ...(capabilityContext && baseSystemPrompt
378
- ? [createCapabilityHook(capabilityContext, baseSystemPrompt, {
379
- staticSections: [TOOL_USAGE_META_MODULE.content],
380
- conditionalModules: [
381
- { content: GIT_SAFETY_MODULE.content, whenModuleActive: ['git-safety'] },
382
- { content: PLATFORM_TOOL_HINTS_MODULE.content, whenModuleActive: ['platform-tool-hints'] },
383
- { content: FACTORY_TOOL_HINTS_MODULE.content, whenModuleActive: ['factory-tool-hints'] },
384
- ],
385
- getToolIndex: (allowedNames) => getFilteredToolIndexForSystemPrompt(allowedNames),
386
- })]
387
- : []),
388
- ],
325
+ beforeLLM: [createContextPressureHook()],
389
326
  beforeTool: [createFileLockCheckHook(getActiveProject)],
390
327
  afterTool: [
391
328
  createFileLockAcquireHook(getActiveProject),
@@ -393,7 +330,8 @@ ${options.systemPromptAddition}
393
330
  ],
394
331
  },
395
332
  });
396
- // Load persisted anchors into the agent's pin manager
333
+ const agent = compilrAgent.unwrap();
334
+ // ── Load persisted anchors ────────────────────────────────────────────────
397
335
  if (options.enableAnchors && options.persistedAnchors) {
398
336
  for (const anchor of options.persistedAnchors) {
399
337
  agent.addPin({
@@ -402,112 +340,44 @@ ${options.systemPromptAddition}
402
340
  priority: anchor.priority,
403
341
  scope: anchor.scope,
404
342
  tags: anchor.tags,
405
- // Note: projectId is for our tracking, not needed by library
406
343
  });
407
344
  }
408
345
  }
409
- // Register tools using registerTools method (skip if noTools is set)
410
- if (!options.noTools) {
411
- // When meta-tools is enabled, use direct tools + meta-tools instead of all tools
412
- let tools;
413
- if (options.minimal) {
414
- tools = createMinimalToolRegistry();
415
- }
416
- else if (options.enableMetaTools) {
417
- // Hot-tools mode: only hot tools + get_tool_info (for schema introspection)
418
- // Warm tools + meta-registry tools are accessed transparently via fallback handler
419
- tools = [...getDirectTools(true), ...getMetaTools()];
420
- // Add load_capability tool when the agent has loadable packs
421
- if (capabilityManager?.hasLoadablePacks()) {
422
- tools.push(createLoadCapabilityTool(capabilityManager));
423
- }
424
- }
425
- else {
426
- // Legacy mode: all tools declared directly
427
- // eslint-disable-next-line @typescript-eslint/no-deprecated -- Intentional for backward compatibility
428
- tools = createToolRegistry();
429
- }
430
- // Apply tool filter if specified (filter direct tools)
431
- if (options.toolFilter && options.toolFilter.length > 0) {
432
- const allowedTools = new Set(options.toolFilter);
433
- // get_tool_info and load_capability are always allowed
434
- const alwaysAllowed = new Set(['get_tool_info', 'load_capability']);
435
- tools = tools.filter((tool) => allowedTools.has(tool.definition.name) || alwaysAllowed.has(tool.definition.name));
436
- // When capability loading is active, the BeforeLLM hook manages the meta-tool filter.
437
- // Otherwise, set it to the full tool filter.
438
- if (!capabilityManager) {
439
- setMetaToolFilter(options.toolFilter);
440
- }
441
- }
442
- else if (!capabilityManager) {
443
- // No filter and no capability loading - clear any previous filter
444
- setMetaToolFilter(null);
445
- }
446
- // Type assertion needed for tool registry compatibility
447
- agent.registerTools(tools);
448
- // Register MCP tools (from external MCP servers)
449
- // These are registered as direct tools since their schemas are unknown at build time
450
- if (options.mcpTools && options.mcpTools.length > 0) {
451
- agent.registerTools(options.mcpTools);
452
- }
453
- // Set up transparent fallback to meta-registry
454
- // When the agent calls a tool not in direct tools (e.g., workitem_add),
455
- // the fallback handler checks the meta-registry and executes it.
456
- if (options.enableMetaTools && !options.minimal) {
457
- agent.getToolRegistry().setFallbackHandler(createToolFallback());
458
- }
459
- }
460
- // Register Task tool for sub-agent spawning (only in full mode)
346
+ // ── Register subagent task tool (CLI-specific callbacks) ──────────────────
461
347
  if (!options.minimal && !options.noTools) {
462
- // Track active subagents to correlate spawn/complete/events
463
- // Track active subagents by toolUseId for token counting
464
348
  const activeSubagents = new Map();
465
- // Provide a way for caller to clear tracking state between runs
466
349
  if (options.onSubagentTrackingReady) {
467
- options.onSubagentTrackingReady(() => {
468
- activeSubagents.clear();
469
- });
350
+ options.onSubagentTrackingReady(() => { activeSubagents.clear(); });
470
351
  }
471
352
  const taskTool = createTaskTool({
472
353
  parentAgent: agent,
473
354
  agentTypes: getMergedAgentTypes(),
474
355
  enableEventStreaming: true,
475
- defaultTimeout: 120000, // 2 minutes for subagent operations
476
- // Now uses toolUseId for direct correlation (no more FIFO matching!)
356
+ defaultTimeout: 120000,
477
357
  onSpawn: (agentType, description, toolUseId) => {
478
358
  if (options.verbose) {
479
359
  log.debug({ component: 'agent', agentType, toolUseId }, 'Spawning sub-agent: %s', description);
480
360
  }
481
- // Immediately notify with toolUseId - no more queuing/matching!
482
361
  if (toolUseId) {
483
362
  activeSubagents.set(toolUseId, { agentType, tokenCount: 0 });
484
- if (options.onSubagentStart) {
485
- options.onSubagentStart(toolUseId, agentType, description);
486
- }
363
+ options.onSubagentStart?.(toolUseId, agentType, description);
487
364
  }
488
365
  },
489
366
  onComplete: (agentType, result, toolUseId) => {
490
367
  if (options.verbose) {
491
368
  log.debug({ component: 'agent', agentType, iterations: result.iterations }, 'Sub-agent completed');
492
369
  }
493
- // Direct notification with toolUseId - no more searching!
494
- if (toolUseId && options.onSubagentEnd) {
495
- options.onSubagentEnd(toolUseId, true, result.toolCalls);
370
+ if (toolUseId) {
371
+ options.onSubagentEnd?.(toolUseId, true, result.toolCalls);
496
372
  activeSubagents.delete(toolUseId);
497
373
  }
498
374
  },
499
- // Stream subagent events for live updates
500
375
  onSubAgentEvent: (eventInfo) => {
501
376
  const { toolUseId, event } = eventInfo;
502
- // Handle tool events for live updates
503
377
  if (event.type === 'tool_start' && toolUseId && event.name && options.onSubagentToolUse) {
504
- // Extract tool name and input summary
505
- const toolName = event.name;
506
- let summary;
507
- // Try to get a meaningful summary from input
508
378
  const input = event.input;
379
+ let summary;
509
380
  if (input) {
510
- // Common patterns for tool input summaries
511
381
  const path = input.path;
512
382
  const pattern = input.pattern;
513
383
  const command = input.command;
@@ -521,64 +391,42 @@ ${options.systemPromptAddition}
521
391
  else if (typeof filePath === 'string')
522
392
  summary = filePath;
523
393
  }
524
- options.onSubagentToolUse(toolUseId, toolName, summary);
394
+ options.onSubagentToolUse(toolUseId, event.name, summary);
525
395
  }
526
396
  },
527
397
  });
528
- // Type assertion needed for task tool compatibility
529
398
  agent.registerTool(taskTool);
530
- // Register Suggest tool for next action suggestions
531
- const suggestTool = createSuggestTool({
532
- onSuggest: options.onSuggest,
533
- });
534
- agent.registerTool(suggestTool);
399
+ // Note: suggest tool is already registered by createCompilrAgent via onSuggest config
535
400
  }
536
- // ==========================================================================
537
- // Calculate and store token estimates for debugging
538
- // ==========================================================================
401
+ // ── Token estimate logging ────────────────────────────────────────────────
539
402
  if (!options.quiet) {
540
- // Calculate tool tokens from the tools we registered
403
+ const toolDefs = agent.getToolRegistry().getDefinitions();
541
404
  let toolTokens = 0;
542
- // Get the tools array based on options
543
- let toolsForEstimate = [];
544
- if (!options.noTools) {
545
- if (options.minimal) {
546
- toolsForEstimate = createMinimalToolRegistry();
547
- }
548
- else if (options.enableMetaTools) {
549
- toolsForEstimate = [...getDirectTools(true), ...getMetaTools()];
550
- }
551
- else {
552
- // eslint-disable-next-line @typescript-eslint/no-deprecated
553
- toolsForEstimate = createToolRegistry();
554
- }
555
- }
556
- for (const tool of toolsForEstimate) {
557
- const def = tool.definition;
405
+ for (const def of toolDefs) {
558
406
  toolTokens += estimateJsonTokens({
559
407
  name: def.name,
560
408
  description: def.description,
561
409
  input_schema: def.inputSchema,
562
410
  });
563
411
  }
564
- // Add task tool and suggest tool estimates (if registered)
565
- if (!options.minimal && !options.noTools) {
566
- // Task tool has a large description with all agent types
567
- toolTokens += 700; // Approximate for task tool
568
- toolTokens += 200; // Approximate for suggest tool
569
- }
570
- // Calculate estimates
571
412
  const systemTokens = estimateTokens(systemPrompt);
572
413
  const roleTokens = options.systemPromptAddition
573
- ? estimateTokens(options.systemPromptAddition) * 2 // Role appears at start and end
414
+ ? estimateTokens(options.systemPromptAddition) * 2
574
415
  : 0;
575
416
  const contextTokens = estimateTokens(options.projectContext);
576
417
  setStaticEstimates({
577
- system: systemTokens - roleTokens, // Subtract role since it's counted separately
418
+ system: systemTokens - roleTokens,
578
419
  role: roleTokens,
579
420
  tools: toolTokens,
580
421
  context: contextTokens,
581
422
  });
423
+ log.info({
424
+ component: 'agent',
425
+ toolCount: toolDefs.length,
426
+ systemTokens,
427
+ toolTokens,
428
+ contextTokens,
429
+ }, 'Agent created');
582
430
  }
583
431
  return agent;
584
432
  }