@compilr-dev/agents 0.5.5 → 0.5.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md ADDED
@@ -0,0 +1,24 @@
1
+ # Changelog
2
+
3
+ All notable changes to this package are documented here.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ > **Beta notice:** versions in the `0.x` range may contain breaking changes
9
+ > between minors. Read each release entry before upgrading.
10
+
11
+ ---
12
+
13
+ ## [Unreleased]
14
+
15
+ ### Added
16
+
17
+ ### Changed
18
+
19
+ ### Fixed
20
+
21
+ ---
22
+
23
+ For full git history see the repository commit log. Older `0.x` entries will be
24
+ backfilled here over time.
package/dist/index.d.ts CHANGED
@@ -52,7 +52,7 @@ export type { Guardrail, GuardrailInput, GuardrailAction, GuardrailResult, Guard
52
52
  export { MCPClient, MCPManager, mcpToolToTool, mcpToolsToTools, convertMCPResult, contentBlocksToString, generateToolName, normalizeServerConfig, MCPError, MCPErrorCode, isMCPError, createSDKNotInstalledError, } from './mcp/index.js';
53
53
  export type { MCPTransport, MCPConnectionStatus, MCPStdioOptions, MCPHttpOptions, MCPClientConfig, MCPServerConfig, MCPToolDefinition, MCPContentBlock, MCPToolResult, MCPClientEventType, MCPClientEvent, MCPClientEventHandler, MCPManagerOptions, MCPToolConversionOptions, } from './mcp/index.js';
54
54
  export { PermissionManager } from './permissions/index.js';
55
- export type { PermissionLevel, ToolPermission, PermissionRequest, PermissionCheckResult, PermissionHandler, PreviewGenerator, PermissionManagerOptions, PermissionEventType, PermissionEvent, PermissionEventHandler, } from './permissions/index.js';
55
+ export type { PermissionLevel, ToolPermission, PermissionRequest, PermissionCheckResult, PermissionHandler, PermissionHandlerResponse, PreviewGenerator, PermissionManagerOptions, PermissionEventType, PermissionEvent, PermissionEventHandler, } from './permissions/index.js';
56
56
  export { ProjectMemoryLoader, createProjectMemoryLoader, loadProjectMemory, hasProjectMemory, getProviderPatterns, getSupportedProviders, getGenericPatterns, PROVIDER_PATTERNS, GENERIC_PATTERNS, } from './memory/index.js';
57
57
  export type { LLMProviderName, MemoryFile, ProjectMemory, FilePattern, CombineStrategy, ProjectMemoryOptions, ProjectMemoryEventType, ProjectMemoryEvent, ProjectMemoryEventHandler, MemoryDiscoveryResult, ProviderPatterns, } from './memory/index.js';
58
58
  export { UsageTracker, createUsageTracker } from './costs/index.js';
@@ -8,7 +8,7 @@
8
8
  import { MCPError, MCPErrorCode, createSDKNotInstalledError } from './errors.js';
9
9
  /**
10
10
  * Cached SDK imports
11
- * Using loose constructor types to accommodate SDK version changes
11
+ * Using loose constructor types to accommodate SDK version changes (see comment above SDKClient)
12
12
  */
13
13
  let sdkCache = null;
14
14
  /**
@@ -205,12 +205,12 @@ export class MCPClient {
205
205
  async callTool(toolName, args) {
206
206
  const client = this.getConnectedClient();
207
207
  try {
208
- // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
208
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment -- MCP SDK boundary, see SDKClient header comment
209
209
  const result = await client.callTool({
210
210
  name: toolName,
211
211
  arguments: args,
212
212
  });
213
- // eslint-disable-next-line @typescript-eslint/no-unsafe-return
213
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-return -- MCP SDK boundary
214
214
  return result;
215
215
  }
216
216
  catch (error) {
@@ -2,4 +2,4 @@
2
2
  * Permissions module - Tool-level permission management
3
3
  */
4
4
  export { PermissionManager } from './manager.js';
5
- export type { PermissionLevel, ToolPermission, PermissionRequest, PermissionCheckResult, PermissionHandler, PreviewGenerator, PermissionManagerOptions, PermissionEventType, PermissionEvent, PermissionEventHandler, } from './types.js';
5
+ export type { PermissionLevel, ToolPermission, PermissionRequest, PermissionCheckResult, PermissionHandler, PermissionHandlerResponse, PreviewGenerator, PermissionManagerOptions, PermissionEventType, PermissionEvent, PermissionEventHandler, } from './types.js';
@@ -226,8 +226,8 @@ export class PermissionManager {
226
226
  return { allowed: true, level, askedUser: false, rule };
227
227
  }
228
228
  // Ask for permission
229
- const allowed = await this.askPermission(toolName, input, level, rule);
230
- if (allowed) {
229
+ const askResult = await this.askPermission(toolName, input, level, rule);
230
+ if (askResult.allowed) {
231
231
  this.sessionGrants.add(toolName);
232
232
  this.emit({ type: 'permission:session_granted', toolName, level, input, rule });
233
233
  }
@@ -235,11 +235,11 @@ export class PermissionManager {
235
235
  this.emit({ type: 'permission:denied', toolName, level, input, rule });
236
236
  }
237
237
  return {
238
- allowed,
238
+ allowed: askResult.allowed,
239
239
  level,
240
240
  askedUser: true,
241
241
  rule,
242
- reason: allowed ? undefined : 'User denied permission',
242
+ reason: askResult.allowed ? undefined : (askResult.reason ?? 'User denied permission'),
243
243
  };
244
244
  }
245
245
  case 'once': {
@@ -249,19 +249,19 @@ export class PermissionManager {
249
249
  return { allowed: true, level, askedUser: false, rule };
250
250
  }
251
251
  // Ask for permission each time (unless session-granted above)
252
- const allowed = await this.askPermission(toolName, input, level, rule);
253
- if (allowed) {
252
+ const askResult = await this.askPermission(toolName, input, level, rule);
253
+ if (askResult.allowed) {
254
254
  this.emit({ type: 'permission:granted', toolName, level, input, rule });
255
255
  }
256
256
  else {
257
257
  this.emit({ type: 'permission:denied', toolName, level, input, rule });
258
258
  }
259
259
  return {
260
- allowed,
260
+ allowed: askResult.allowed,
261
261
  level,
262
262
  askedUser: true,
263
263
  rule,
264
- reason: allowed ? undefined : 'User denied permission',
264
+ reason: askResult.allowed ? undefined : (askResult.reason ?? 'User denied permission'),
265
265
  };
266
266
  }
267
267
  }
@@ -281,7 +281,7 @@ export class PermissionManager {
281
281
  async askPermission(toolName, input, level, rule) {
282
282
  // If no handler, default to allow
283
283
  if (!this.handler) {
284
- return true;
284
+ return { allowed: true };
285
285
  }
286
286
  const preview = this.previewGenerator(toolName, input);
287
287
  const request = {
@@ -292,7 +292,12 @@ export class PermissionManager {
292
292
  preview,
293
293
  };
294
294
  this.emit({ type: 'permission:asked', toolName, level, input, rule });
295
- return this.handler(request);
295
+ const response = await this.handler(request);
296
+ // Normalize legacy boolean responses (backward compat)
297
+ if (typeof response === 'boolean') {
298
+ return { allowed: response };
299
+ }
300
+ return response;
296
301
  }
297
302
  /**
298
303
  * Grant session-level permission for a tool
@@ -84,25 +84,52 @@ export interface PermissionCheckResult {
84
84
  */
85
85
  reason?: string;
86
86
  }
87
+ /**
88
+ * Response shape from a permission handler.
89
+ *
90
+ * Either a plain boolean (legacy — backward compatible) OR a structured
91
+ * object that can carry an optional reason. The reason is delivered to
92
+ * the agent inline with the tool's permission-denied error so the agent
93
+ * sees the user's context immediately, in the same turn.
94
+ *
95
+ * @example
96
+ * ```typescript
97
+ * // Legacy (still supported):
98
+ * return true;
99
+ * return false;
100
+ *
101
+ * // With reason:
102
+ * return { allowed: false, reason: 'Use project_document_add instead' };
103
+ * return { allowed: true };
104
+ * ```
105
+ */
106
+ export type PermissionHandlerResponse = boolean | {
107
+ allowed: boolean;
108
+ reason?: string;
109
+ };
87
110
  /**
88
111
  * Handler called when permission is needed
89
112
  *
90
113
  * @param request - Information about the permission request
91
- * @returns true to allow execution, false to deny
114
+ * @returns true to allow / false to deny — or { allowed, reason? } to
115
+ * carry context (especially useful when denying with a message)
92
116
  *
93
117
  * @example
94
118
  * ```typescript
95
119
  * const handler: PermissionHandler = async (request) => {
96
120
  * // Show UI prompt to user
97
- * const allowed = await showConfirmDialog(
121
+ * const result = await showConfirmDialog(
98
122
  * `Allow ${request.toolName}?`,
99
123
  * request.description
100
124
  * );
101
- * return allowed;
125
+ * if (!result.allowed && result.message) {
126
+ * return { allowed: false, reason: result.message };
127
+ * }
128
+ * return result.allowed;
102
129
  * };
103
130
  * ```
104
131
  */
105
- export type PermissionHandler = (request: PermissionRequest) => boolean | Promise<boolean>;
132
+ export type PermissionHandler = (request: PermissionRequest) => PermissionHandlerResponse | Promise<PermissionHandlerResponse>;
106
133
  /**
107
134
  * Preview generator for permission requests
108
135
  *
@@ -318,7 +318,7 @@ export class GeminiNativeProvider {
318
318
  const toolId = funcCall.id ?? `tool_${String(Date.now())}_${Math.random().toString(36).slice(2, 9)}`;
319
319
  // Capture thought signature if present (Gemini 3 attaches it to function calls)
320
320
  // Note: thoughtSignature is not in the SDK types yet but exists on the response
321
- // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access
321
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access -- @google/genai types lag the actual API surface
322
322
  const signature = part.thoughtSignature;
323
323
  chunks.push({
324
324
  type: 'tool_use_start',
@@ -253,7 +253,7 @@ export function createTracingHooks(manager, config = {}) {
253
253
  * ```
254
254
  */
255
255
  export function createLoggingHooks(
256
- // eslint-disable-next-line no-console
256
+ // eslint-disable-next-line no-console -- callers can pass a real logger; default to console for zero-config use
257
257
  logger = console.log) {
258
258
  return {
259
259
  beforeIteration: [
@@ -129,7 +129,7 @@ export function createStructuredLogger(options = {}) {
129
129
  */
130
130
  function defaultOutput(entry) {
131
131
  const json = JSON.stringify(entry);
132
- /* eslint-disable no-console */
132
+ /* eslint-disable no-console -- this IS the default console output sink; callers can pass a custom output */
133
133
  switch (entry.level) {
134
134
  case 'debug':
135
135
  console.debug(json);
@@ -35,10 +35,10 @@ async function getOTelSDK() {
35
35
  try {
36
36
  // Use dynamic import with string to avoid TypeScript trying to resolve the module
37
37
  const modulePath = '@opentelemetry/api';
38
- // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
38
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment -- optional peer dep loaded via dynamic-import string; types unavailable at compile time
39
39
  const api = await import(modulePath);
40
40
  otelCache = {
41
- // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access
41
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access -- optional peer dep, see above
42
42
  trace: api.trace,
43
43
  };
44
44
  return otelCache;
@@ -132,7 +132,7 @@ export async function createOTelExporter(tracerName = 'agent-tracing', tracerVer
132
132
  * ```
133
133
  */
134
134
  export function createConsoleExporter(options = {}) {
135
- // eslint-disable-next-line no-console
135
+ // eslint-disable-next-line no-console -- console exporter — callers expect output to go to the console by default
136
136
  const { prettyPrint = true, output = console.log } = options;
137
137
  return {
138
138
  name: 'console',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@compilr-dev/agents",
3
- "version": "0.5.5",
3
+ "version": "0.5.7",
4
4
  "description": "Lightweight multi-LLM agent library for building CLI AI assistants",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -12,8 +12,11 @@
12
12
  }
13
13
  },
14
14
  "files": [
15
- "dist"
15
+ "dist",
16
+ "LICENSE",
17
+ "CHANGELOG.md"
16
18
  ],
19
+ "sideEffects": false,
17
20
  "scripts": {
18
21
  "build": "tsc",
19
22
  "clean": "rm -rf dist",
@@ -49,7 +52,7 @@
49
52
  },
50
53
  "homepage": "https://github.com/compilr-dev/agents#readme",
51
54
  "engines": {
52
- "node": ">=22.0.0"
55
+ "node": ">=20.0.0"
53
56
  },
54
57
  "peerDependencies": {
55
58
  "@anthropic-ai/sdk": ">=0.72.1",