@agentforge-io/core 2.0.19 → 2.0.20

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.
@@ -105,6 +105,19 @@ export declare class AgentService {
105
105
  * authenticated user's connector tools (Gmail, Drive, …) on the fly
106
106
  * via `overrides.extraTools`. Optional — connectors are opt-in. */
107
107
  connectorRegistry?: ConnectorRegistryService | undefined);
108
+ /**
109
+ * Look up the human-friendly connector name + tool description for a
110
+ * given tool slug. Powers the friendly copy in `awaiting_approval` /
111
+ * `tool_blocked` cards: the visitor sees "Granola" instead of
112
+ * `granola_list_meetings`. Returns `undefined` for built-in / MCP
113
+ * tools, or when the registry isn't wired — the chat client falls
114
+ * back to the raw `toolName` in that case.
115
+ *
116
+ * Iterates every registered connector definition. The registry is
117
+ * small (handful of entries) so this is O(connectors × tools);
118
+ * acceptable for the rare per-approval call path.
119
+ */
120
+ private describeTool;
108
121
  /**
109
122
  * Fetch the connector tools the user has authorized, swallowing failures.
110
123
  * The agent loop must keep working even if a connector's refresh token is
@@ -29,6 +29,38 @@ class AgentService {
29
29
  this.hooks = hooks;
30
30
  this.connectorRegistry = connectorRegistry;
31
31
  }
32
+ /**
33
+ * Look up the human-friendly connector name + tool description for a
34
+ * given tool slug. Powers the friendly copy in `awaiting_approval` /
35
+ * `tool_blocked` cards: the visitor sees "Granola" instead of
36
+ * `granola_list_meetings`. Returns `undefined` for built-in / MCP
37
+ * tools, or when the registry isn't wired — the chat client falls
38
+ * back to the raw `toolName` in that case.
39
+ *
40
+ * Iterates every registered connector definition. The registry is
41
+ * small (handful of entries) so this is O(connectors × tools);
42
+ * acceptable for the rare per-approval call path.
43
+ */
44
+ describeTool(toolName) {
45
+ if (!this.connectorRegistry)
46
+ return undefined;
47
+ try {
48
+ for (const def of this.connectorRegistry.list()) {
49
+ for (const factory of def.tools) {
50
+ if (factory.definition.name === toolName) {
51
+ return {
52
+ connectorName: def.name,
53
+ toolDescription: factory.definition.description,
54
+ };
55
+ }
56
+ }
57
+ }
58
+ }
59
+ catch {
60
+ // Registry is misconfigured — fall back to bare tool name.
61
+ }
62
+ return undefined;
63
+ }
32
64
  /**
33
65
  * Fetch the connector tools the user has authorized, swallowing failures.
34
66
  * The agent loop must keep working even if a connector's refresh token is
@@ -265,44 +297,58 @@ class AgentService {
265
297
  // stream may close, but the conversation history doesn't lose
266
298
  // context.
267
299
  if ((0, tool_approval_gate_1.isToolApprovalRequired)(err)) {
300
+ const ctx = this.describeTool(err.toolName);
268
301
  yield {
269
302
  type: 'awaiting_approval',
270
303
  approvalId: err.approvalId,
271
304
  toolName: err.toolName,
272
305
  expiresAt: err.expiresAt,
306
+ connectorName: ctx?.connectorName,
307
+ toolDescription: ctx?.toolDescription,
273
308
  };
274
309
  await this.conversations.addMessage({
275
310
  conversationId: params.conversationId,
276
311
  userId: params.userId,
277
312
  role: 'assistant',
278
- // Plain-text body so legacy clients (or a server reload of
279
- // history into a non-aware client) still get a readable
280
- // line. The card lives in `metadata`.
281
- content: `(waiting for approval to run \`${err.toolName}\`)`,
313
+ // Plain-text fallback. Friendly enough for legacy clients
314
+ // that don't render `metadata.kind`. The structured card
315
+ // lives in `metadata` for capable widgets.
316
+ content: ctx?.connectorName
317
+ ? `Necesito tu permiso para usar ${ctx.connectorName}.`
318
+ : `Necesito tu permiso para continuar.`,
282
319
  metadata: {
283
320
  kind: 'awaiting_approval',
284
321
  approvalId: err.approvalId,
285
322
  toolName: err.toolName,
286
323
  expiresAt: err.expiresAt,
324
+ connectorName: ctx?.connectorName,
325
+ toolDescription: ctx?.toolDescription,
287
326
  },
288
327
  });
289
328
  return;
290
329
  }
291
330
  if ((0, tool_approval_gate_1.isToolBlockedError)(err)) {
331
+ const ctx = this.describeTool(err.toolName);
292
332
  yield {
293
333
  type: 'tool_blocked',
294
334
  toolName: err.toolName,
295
335
  reason: err.reason,
336
+ connectorName: ctx?.connectorName,
337
+ toolDescription: ctx?.toolDescription,
296
338
  };
297
339
  await this.conversations.addMessage({
298
340
  conversationId: params.conversationId,
299
341
  userId: params.userId,
300
342
  role: 'assistant',
301
- content: err.reason ?? `Tool "${err.toolName}" is blocked.`,
343
+ content: ctx?.connectorName
344
+ ? `No puedo usar ${ctx.connectorName} en esta cuenta.`
345
+ : `No puedo usar esa herramienta en esta cuenta.`,
302
346
  metadata: {
303
347
  kind: 'tool_blocked',
304
348
  toolName: err.toolName,
305
349
  reason: err.reason,
350
+ connectorName: ctx?.connectorName,
351
+ toolDescription: ctx?.toolDescription,
306
352
  },
307
353
  });
308
354
  return;
@@ -187,6 +187,16 @@ export type StreamChunk = {
187
187
  approvalId: string;
188
188
  toolName: string;
189
189
  expiresAt: string;
190
+ /** Human-friendly connector name (`'Granola'`, `'Notion'`). The
191
+ * runner enriches it from the registry so the chat widget can
192
+ * render a sentence like "I need permission to use Granola"
193
+ * instead of the raw tool slug. Optional — clients fall back
194
+ * to `toolName` when the runtime doesn't supply this. */
195
+ connectorName?: string;
196
+ /** Human-friendly first sentence of the tool's description.
197
+ * Same story: enables the widget to say what the tool DOES in
198
+ * natural language. Optional. */
199
+ toolDescription?: string;
190
200
  } | {
191
201
  /** Emitted when a tool dispatch hit `kind: 'blocked'`. Mirrors the
192
202
  * shape above so the client renders a terminal-error card with
@@ -194,6 +204,8 @@ export type StreamChunk = {
194
204
  type: 'tool_blocked';
195
205
  toolName: string;
196
206
  reason?: string;
207
+ connectorName?: string;
208
+ toolDescription?: string;
197
209
  } | {
198
210
  type: 'done';
199
211
  messageId: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentforge-io/core",
3
- "version": "2.0.19",
3
+ "version": "2.0.20",
4
4
  "description": "Framework-free AI runtime SDK. Owns: agent loop (Anthropic), conversations, tools, streaming, agent-job queue, SdkHooks. Identity, billing, infra (email/uploads/secrets) live in the host's modules — not here.",
5
5
  "license": "MIT",
6
6
  "main": "dist/index.js",