@agentforge-io/core 0.2.0 → 2.0.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.
@@ -26,9 +26,17 @@ export declare class AgentRunnerService {
26
26
  * - `extras`: a name→definition map so `dispatchTool` can route execution
27
27
  * for tools that aren't in the global registry
28
28
  *
29
- * Extras with the same name as a global tool shadow the global one — useful
30
- * when a connector wants to override the built-in `http_request`, for
31
- * example. The shadowing only lasts for this call.
29
+ * **Connector tools follow the same whitelist as global tools.** An extra
30
+ * is only injected if its name appears in `agent.tools[]`. That keeps
31
+ * "an agent only sees what its definition declares" — a single contract
32
+ * for built-ins, MCP tools, and connector tools alike. Without this
33
+ * filter, any agent run by a user with Gmail/Drive/... connected would
34
+ * silently gain access to those APIs, which is both a security hole and
35
+ * makes curation impossible.
36
+ *
37
+ * Extras with the same name as a global tool shadow the global one —
38
+ * useful when a connector wants to override the built-in `http_request`,
39
+ * for example. The shadowing only lasts for this call.
32
40
  */
33
41
  private buildToolList;
34
42
  private dispatchTool;
@@ -193,17 +193,28 @@ class AgentRunnerService {
193
193
  * - `extras`: a name→definition map so `dispatchTool` can route execution
194
194
  * for tools that aren't in the global registry
195
195
  *
196
- * Extras with the same name as a global tool shadow the global one — useful
197
- * when a connector wants to override the built-in `http_request`, for
198
- * example. The shadowing only lasts for this call.
196
+ * **Connector tools follow the same whitelist as global tools.** An extra
197
+ * is only injected if its name appears in `agent.tools[]`. That keeps
198
+ * "an agent only sees what its definition declares" — a single contract
199
+ * for built-ins, MCP tools, and connector tools alike. Without this
200
+ * filter, any agent run by a user with Gmail/Drive/... connected would
201
+ * silently gain access to those APIs, which is both a security hole and
202
+ * makes curation impossible.
203
+ *
204
+ * Extras with the same name as a global tool shadow the global one —
205
+ * useful when a connector wants to override the built-in `http_request`,
206
+ * for example. The shadowing only lasts for this call.
199
207
  */
200
208
  buildToolList(agent, overrides) {
201
209
  const fromRegistry = agent.tools?.length
202
210
  ? this.toolRegistry.getToolsForAgent(agent.id, agent.tools)
203
211
  : [];
212
+ const allowed = new Set(agent.tools ?? []);
204
213
  const extras = new Map();
205
214
  const extrasSchema = [];
206
215
  for (const t of overrides?.extraTools ?? []) {
216
+ if (!allowed.has(t.name))
217
+ continue;
207
218
  extras.set(t.name, t);
208
219
  extrasSchema.push({
209
220
  name: t.name,
@@ -29,6 +29,18 @@ export interface AgentRecord {
29
29
  mcpServers?: McpServerConfig[];
30
30
  metadata?: Record<string, unknown>;
31
31
  isActive?: boolean;
32
+ /**
33
+ * When set, connector tools (Gmail, Drive, ...) are resolved against
34
+ * this user's OAuth authorizations instead of the caller's. Used by
35
+ * public-chat agents — a visitor's browser session can leverage the
36
+ * agent owner's authorized connectors without exposing the owner's
37
+ * identity in the conversation record.
38
+ *
39
+ * Leave undefined for personal agents where the caller owns the
40
+ * connectors themselves; in that case the SDK falls back to the
41
+ * caller's userId, preserving the original behavior.
42
+ */
43
+ connectorOwnerUserId?: string;
32
44
  }
33
45
  /**
34
46
  * Host-supplied resolver for per-tenant agent configurations. The SDK never
@@ -157,7 +157,11 @@ class AgentService {
157
157
  role: 'user',
158
158
  content: params.content,
159
159
  });
160
- const extraTools = await this.resolveExtraTools(params.userId);
160
+ // Connector tools follow `agent.connectorOwnerUserId` when the
161
+ // resolved agent declares one (e.g. a public-chat agent reusing the
162
+ // owner's Gmail authorization); otherwise they fall back to the
163
+ // caller's userId, which is the historical personal-agent path.
164
+ const extraTools = await this.resolveExtraTools(agent.connectorOwnerUserId ?? params.userId);
161
165
  const response = await this.runner.run(agent, messages, {
162
166
  userId: params.userId,
163
167
  conversationId: params.conversationId,
@@ -217,7 +221,10 @@ class AgentService {
217
221
  });
218
222
  let fullContent = '';
219
223
  let finalUsage = { inputTokens: 0, outputTokens: 0, totalTokens: 0 };
220
- const extraTools = await this.resolveExtraTools(params.userId);
224
+ // Same precedence as sendMessage — agent.connectorOwnerUserId takes
225
+ // priority so a public-chat agent always uses the owner's connector
226
+ // toolbelt regardless of which visitor session is streaming.
227
+ const extraTools = await this.resolveExtraTools(agent.connectorOwnerUserId ?? params.userId);
221
228
  for await (const chunk of this.runner.stream(agent, messages, {
222
229
  userId: params.userId,
223
230
  conversationId: params.conversationId,
@@ -325,5 +332,6 @@ function toAgentDefinition(record) {
325
332
  tools: record.tools,
326
333
  mcpServers: record.mcpServers,
327
334
  metadata: record.metadata,
335
+ connectorOwnerUserId: record.connectorOwnerUserId,
328
336
  };
329
337
  }
@@ -143,6 +143,23 @@ export declare class ConnectorRegistryService {
143
143
  }>;
144
144
  listForUser(userId: string): Promise<ConnectorStatus[]>;
145
145
  disconnect(userId: string, connectorId: string): Promise<void>;
146
+ /**
147
+ * Returns the full catalog of tool *definitions* across every registered
148
+ * connector, regardless of whether any user has authorized them. Used by
149
+ * the agent-editor UI so an operator can whitelist connector tools per
150
+ * agent the same way they whitelist built-ins — the runner enforces the
151
+ * whitelist via `agent.tools[]`, so listing here doesn't grant access.
152
+ *
153
+ * No tokens are surfaced and no `execute` is returned: this view is
154
+ * deliberately metadata-only.
155
+ */
156
+ availableTools(): Array<{
157
+ name: string;
158
+ description: string;
159
+ connectorId: string;
160
+ connectorName: string;
161
+ inputSchema: unknown;
162
+ }>;
146
163
  /**
147
164
  * Returns the toolbelt for `userId`: one `AgentToolDefinition` per tool
148
165
  * of every connector the user has authorized. Each handler is bound to a
@@ -179,6 +179,32 @@ class ConnectorRegistryService {
179
179
  throw new ConnectorError('not_connected', 'No active auth row');
180
180
  await this.deps.authRepo.delete(a.id);
181
181
  }
182
+ // ─── Catalog (no auth) ───────────────────────────────────────────────────
183
+ /**
184
+ * Returns the full catalog of tool *definitions* across every registered
185
+ * connector, regardless of whether any user has authorized them. Used by
186
+ * the agent-editor UI so an operator can whitelist connector tools per
187
+ * agent the same way they whitelist built-ins — the runner enforces the
188
+ * whitelist via `agent.tools[]`, so listing here doesn't grant access.
189
+ *
190
+ * No tokens are surfaced and no `execute` is returned: this view is
191
+ * deliberately metadata-only.
192
+ */
193
+ availableTools() {
194
+ const out = [];
195
+ for (const def of this.defs.values()) {
196
+ for (const factory of def.tools) {
197
+ out.push({
198
+ name: factory.definition.name,
199
+ description: factory.definition.description,
200
+ connectorId: def.id,
201
+ connectorName: def.name,
202
+ inputSchema: factory.definition.inputSchema,
203
+ });
204
+ }
205
+ }
206
+ return out;
207
+ }
182
208
  // ─── Per-user tool synthesis ─────────────────────────────────────────────
183
209
  /**
184
210
  * Returns the toolbelt for `userId`: one `AgentToolDefinition` per tool
@@ -94,6 +94,15 @@ export interface AgentDefinition {
94
94
  requiredPlan?: string[];
95
95
  /** Custom metadata */
96
96
  metadata?: Record<string, unknown>;
97
+ /**
98
+ * When set, connector tools (Gmail, Drive, ...) are resolved against
99
+ * this user's OAuth authorizations instead of the caller's. Lets a
100
+ * public-chat agent reuse the agent owner's authorized connectors
101
+ * while keeping the conversation scoped to the visitor. Leave
102
+ * undefined for personal agents — the SDK falls back to the caller's
103
+ * userId, preserving the original behavior.
104
+ */
105
+ connectorOwnerUserId?: string;
97
106
  }
98
107
  /**
99
108
  * Configuration for an MCP server the runtime should connect to.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentforge-io/core",
3
- "version": "0.2.0",
3
+ "version": "2.0.0",
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",