@ziggs-ai/agent-sdk 0.16.1 → 0.18.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/CHANGELOG.md +10 -0
- package/README.md +110 -88
- package/dist/{AgentHost.d.ts → Agent.d.ts} +50 -27
- package/dist/{AgentHost.js → Agent.js} +135 -129
- package/dist/adapters/AnthropicAdapter.d.ts +1 -1
- package/dist/adapters/AnthropicAdapter.js +1 -1
- package/dist/{agent/ClaudeSdkAgent.d.ts → brain/ClaudeBrain.d.ts} +23 -11
- package/dist/{agent/ClaudeSdkAgent.js → brain/ClaudeBrain.js} +99 -99
- package/dist/{agent/Agent.d.ts → brain/ZiggsBrain.d.ts} +23 -20
- package/dist/{agent/Agent.js → brain/ZiggsBrain.js} +12 -11
- package/dist/{agent → brain}/claudeCognition.d.ts +1 -1
- package/dist/{agent → brain}/claudeCognition.js +1 -1
- package/dist/{agent → brain}/claudeSdkProtocol.d.ts +1 -1
- package/dist/{agent → brain}/claudeSdkProtocol.js +1 -1
- package/dist/{agent → brain}/claudeSdkTools.d.ts +1 -1
- package/dist/{agent → brain}/claudeSdkTools.js +5 -14
- package/dist/{agent → brain}/hostedAgentConnection.d.ts +1 -1
- package/dist/{agent → brain}/hostedAgentConnection.js +3 -3
- package/dist/brain/index.d.ts +6 -6
- package/dist/brain/index.js +2 -2
- package/dist/brain/onBehalfAgreement.d.ts +51 -0
- package/dist/brain/onBehalfAgreement.js +99 -0
- package/dist/{agent → brain}/toolManagerToSdkMcp.js +10 -1
- package/dist/defineAgent.js +4 -4
- package/dist/formatters/HistoryFormatter.js +2 -1
- package/dist/index.d.ts +30 -31
- package/dist/index.js +24 -25
- package/dist/{adapters/McpAdapter.d.ts → mcp/createMcpAgent.d.ts} +16 -15
- package/dist/{adapters/McpAdapter.js → mcp/createMcpAgent.js} +82 -46
- package/dist/memory/MemoryStore.d.ts +18 -1
- package/dist/memory/MemoryStore.js +43 -0
- package/dist/{server → platform}/ConnectionPool.d.ts +9 -3
- package/dist/{server → platform}/ConnectionPool.js +16 -4
- package/dist/{server → platform}/EventQueue.d.ts +1 -1
- package/dist/{server → platform}/StateTimers.d.ts +1 -1
- package/dist/{server → platform}/StateTimers.js +1 -1
- package/dist/{server → platform}/ZiggsEffectHandler.d.ts +4 -4
- package/dist/{server → platform}/ZiggsEffectHandler.js +2 -2
- package/dist/{server → platform}/inboxLoop.d.ts +34 -1
- package/dist/{server → platform}/inboxLoop.js +122 -14
- package/dist/{server → platform}/polls/PollKind.d.ts +4 -7
- package/dist/{server → platform}/polls/PollKind.js +3 -6
- package/dist/{server → platform}/polls/PollRunner.d.ts +5 -6
- package/dist/{server → platform}/polls/PollRunner.js +24 -12
- package/dist/platform/polls/declaredPollRunner.d.ts +4 -0
- package/dist/platform/polls/declaredPollRunner.js +29 -0
- package/dist/{server → platform}/tools/tier2/mcpConnectionTools.js +32 -18
- package/dist/runtime/bootstrapCtx.d.ts +1 -1
- package/dist/runtime/bootstrapCtx.js +1 -1
- package/dist/runtime/buildOutcome.js +1 -1
- package/dist/runtime/runTurn.d.ts +1 -1
- package/dist/runtime/runTurn.js +18 -18
- package/dist/runtime/validateWorkflow.js +1 -1
- package/dist/skills/parseSkill.d.ts +7 -2
- package/dist/skills/parseSkill.js +22 -5
- package/dist/skills/skillReadReference.d.ts +6 -0
- package/dist/skills/skillReadReference.js +27 -0
- package/dist/tools/needs.js +2 -2
- package/dist/tools/toolCreds.d.ts +1 -1
- package/dist/tools/toolCreds.js +1 -1
- package/dist/types.d.ts +3 -3
- package/package.json +2 -2
- package/dist/server/createHealthServer.d.ts +0 -7
- package/dist/server/createHealthServer.js +0 -75
- package/dist/server/runLauncher.d.ts +0 -22
- package/dist/server/runLauncher.js +0 -160
- /package/dist/{agent → brain}/toolManagerToSdkMcp.d.ts +0 -0
- /package/dist/{server → platform}/EventQueue.js +0 -0
- /package/dist/{server → platform}/OutboxBuffer.d.ts +0 -0
- /package/dist/{server → platform}/OutboxBuffer.js +0 -0
- /package/dist/{server → platform}/SeenMessages.d.ts +0 -0
- /package/dist/{server → platform}/SeenMessages.js +0 -0
- /package/dist/{server → platform}/agreements/AgreementService.d.ts +0 -0
- /package/dist/{server → platform}/agreements/AgreementService.js +0 -0
- /package/dist/{server → platform}/proactive/ProactiveTrigger.d.ts +0 -0
- /package/dist/{server → platform}/proactive/ProactiveTrigger.js +0 -0
- /package/dist/{server → platform}/requestMatch.d.ts +0 -0
- /package/dist/{server → platform}/requestMatch.js +0 -0
- /package/dist/{server → platform}/tasks/TaskService.d.ts +0 -0
- /package/dist/{server → platform}/tasks/TaskService.js +0 -0
- /package/dist/{server → platform}/tasks/index.d.ts +0 -0
- /package/dist/{server → platform}/tasks/index.js +0 -0
- /package/dist/{server → platform}/telemetryIngest.d.ts +0 -0
- /package/dist/{server → platform}/telemetryIngest.js +0 -0
- /package/dist/{server → platform}/tools/index.d.ts +0 -0
- /package/dist/{server → platform}/tools/index.js +0 -0
- /package/dist/{server → platform}/tools/tier1/protocolRunner.d.ts +0 -0
- /package/dist/{server → platform}/tools/tier1/protocolRunner.js +0 -0
- /package/dist/{server → platform}/tools/tier1/protocolTools.d.ts +0 -0
- /package/dist/{server → platform}/tools/tier1/protocolTools.js +0 -0
- /package/dist/{server → platform}/tools/tier2/artifactTools.d.ts +0 -0
- /package/dist/{server → platform}/tools/tier2/artifactTools.js +0 -0
- /package/dist/{server → platform}/tools/tier2/chatTools.d.ts +0 -0
- /package/dist/{server → platform}/tools/tier2/chatTools.js +0 -0
- /package/dist/{server → platform}/tools/tier2/connectionTools.d.ts +0 -0
- /package/dist/{server → platform}/tools/tier2/connectionTools.js +0 -0
- /package/dist/{server → platform}/tools/tier2/contextTools.d.ts +0 -0
- /package/dist/{server → platform}/tools/tier2/contextTools.js +0 -0
- /package/dist/{server → platform}/tools/tier2/discoveryTools.d.ts +0 -0
- /package/dist/{server → platform}/tools/tier2/discoveryTools.js +0 -0
- /package/dist/{server → platform}/tools/tier2/grantsTools.d.ts +0 -0
- /package/dist/{server → platform}/tools/tier2/grantsTools.js +0 -0
- /package/dist/{server → platform}/tools/tier2/introductionTools.d.ts +0 -0
- /package/dist/{server → platform}/tools/tier2/introductionTools.js +0 -0
- /package/dist/{server → platform}/tools/tier2/linkTools.d.ts +0 -0
- /package/dist/{server → platform}/tools/tier2/linkTools.js +0 -0
- /package/dist/{server → platform}/tools/tier2/marketplaceTools.d.ts +0 -0
- /package/dist/{server → platform}/tools/tier2/marketplaceTools.js +0 -0
- /package/dist/{server → platform}/tools/tier2/mcpConnectionTools.d.ts +0 -0
- /package/dist/{server → platform}/tools/tier2/paymentTools.d.ts +0 -0
- /package/dist/{server → platform}/tools/tier2/paymentTools.js +0 -0
- /package/dist/{server → platform}/tools/tier2/taskTools.d.ts +0 -0
- /package/dist/{server → platform}/tools/tier2/taskTools.js +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
|
|
2
2
|
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
|
|
3
|
-
import {
|
|
3
|
+
import { type Agent, type AgentOptions } from '../Agent.js';
|
|
4
4
|
import type { ToolDefinition } from '../tools/defineTool.js';
|
|
5
5
|
import type { Workflow } from '../types.js';
|
|
6
6
|
export type McpTransportConfig = {
|
|
@@ -34,7 +34,7 @@ export type McpTransportConfig = {
|
|
|
34
34
|
operatorKey?: string;
|
|
35
35
|
/** Ziggs backend base URL — defaults to BACKEND_URL / ZIGGS_BACKEND_URL. */
|
|
36
36
|
baseUrl?: string;
|
|
37
|
-
/** Agent to impersonate (X-Agent-Id) — defaults to the
|
|
37
|
+
/** Agent to impersonate (X-Agent-Id) — defaults to the agent agentId. */
|
|
38
38
|
agentId?: string;
|
|
39
39
|
} | {
|
|
40
40
|
/**
|
|
@@ -51,7 +51,7 @@ export type McpTransportConfig = {
|
|
|
51
51
|
operatorKey?: string;
|
|
52
52
|
/** Ziggs backend base URL — defaults to BACKEND_URL / ZIGGS_BACKEND_URL. */
|
|
53
53
|
baseUrl?: string;
|
|
54
|
-
/** Agent to impersonate (X-Agent-Id) — defaults to the
|
|
54
|
+
/** Agent to impersonate (X-Agent-Id) — defaults to the agent agentId. */
|
|
55
55
|
agentId?: string;
|
|
56
56
|
/**
|
|
57
57
|
* The MCP server URL this agent fronts. Not used for gateway calls (the
|
|
@@ -84,6 +84,7 @@ export declare function resolveZiggsGatewayTarget(config: Extract<McpTransportCo
|
|
|
84
84
|
type: 'ziggs';
|
|
85
85
|
}>, defaults?: {
|
|
86
86
|
agentId?: string;
|
|
87
|
+
laneId?: string;
|
|
87
88
|
}): {
|
|
88
89
|
url: URL;
|
|
89
90
|
headers: Record<string, string>;
|
|
@@ -95,11 +96,11 @@ export declare function resolveZiggsGatewayTarget(config: Extract<McpTransportCo
|
|
|
95
96
|
*/
|
|
96
97
|
export declare function resolvePerUserGatewayTarget(config: Extract<McpTransportConfig, {
|
|
97
98
|
type: 'ziggs-per-user';
|
|
98
|
-
}>, agentId: string, userId: string, onBehalfOfAgreementId?: string): {
|
|
99
|
+
}>, agentId: string, userId: string, onBehalfOfAgreementId?: string, laneId?: string): {
|
|
99
100
|
url: URL;
|
|
100
101
|
headers: Record<string, string>;
|
|
101
102
|
};
|
|
102
|
-
export interface CreateMcpAgentOptions extends Omit<
|
|
103
|
+
export interface CreateMcpAgentOptions extends Omit<AgentOptions, 'tools' | 'workflow' | 'states' | 'initial'> {
|
|
103
104
|
/** MCP server connection — stdio (local process) or HTTP (remote). */
|
|
104
105
|
transport: McpTransportConfig;
|
|
105
106
|
/** Additional Ziggs tools to register alongside the MCP-derived ones. */
|
|
@@ -142,8 +143,8 @@ export interface CreateMcpAgentOptions extends Omit<AgentHostOptions, 'tools' |
|
|
|
142
143
|
*/
|
|
143
144
|
export declare function mcpToolBudget(maxTools: number, extraToolCount: number): number;
|
|
144
145
|
export interface McpAgentHandle {
|
|
145
|
-
|
|
146
|
-
/** Disconnect the MCP client (call on shutdown alongside
|
|
146
|
+
agent: Agent;
|
|
147
|
+
/** Disconnect the MCP client (call on shutdown alongside agent.disconnect()). */
|
|
147
148
|
closeMcp: () => Promise<void>;
|
|
148
149
|
/** Names of MCP tools that were mapped to Ziggs tools on startup. */
|
|
149
150
|
toolNames: string[];
|
|
@@ -164,35 +165,35 @@ export declare const MAX_TOOL_DESC_CHARS = 2000;
|
|
|
164
165
|
/** Truncate one description at a sentence/newline boundary near the cap. */
|
|
165
166
|
export declare function trimDescription(desc: string | undefined): string | undefined;
|
|
166
167
|
/**
|
|
167
|
-
* Wraps any MCP server as a Ziggs
|
|
168
|
+
* Wraps any MCP server as a Ziggs Agent.
|
|
168
169
|
*
|
|
169
170
|
* 1. Opens a connection to the MCP server (stdio, HTTP, or the Ziggs gateway).
|
|
170
171
|
* 2. Calls tools/list and maps each tool to a Ziggs ToolDefinition.
|
|
171
|
-
* 3. Returns an
|
|
172
|
+
* 3. Returns an Agent pre-loaded with those tools, ready to connectAsync().
|
|
172
173
|
*
|
|
173
174
|
* Usage (local stdio server):
|
|
174
|
-
* const {
|
|
175
|
+
* const { agent, closeMcp } = await createMcpAgent({
|
|
175
176
|
* transport: { type: 'stdio', command: 'npx', args: ['-y', '@modelcontextprotocol/server-github'] },
|
|
176
177
|
* agentId: 'my-github-agent',
|
|
177
178
|
* name: 'GitHub Agent',
|
|
178
179
|
* description: 'Wraps the GitHub MCP server',
|
|
179
180
|
* });
|
|
180
|
-
* await
|
|
181
|
+
* await agent.connectAsync();
|
|
181
182
|
*
|
|
182
183
|
* Usage (OAuth-protected remote server, brokered through Ziggs — the user
|
|
183
184
|
* connected the server and issued this agent a grant; the agent never sees
|
|
184
185
|
* the user's token):
|
|
185
|
-
* const {
|
|
186
|
+
* const { agent, closeMcp } = await createMcpAgent({
|
|
186
187
|
* transport: { type: 'ziggs', connectionId: 'con_abc', grantId: 'cgr_xyz' },
|
|
187
188
|
* agentId: 'my-notion-agent',
|
|
188
189
|
* name: 'Notion Agent',
|
|
189
190
|
* description: 'Works in the owner\'s Notion via their MCP connection',
|
|
190
191
|
* });
|
|
191
|
-
* await
|
|
192
|
+
* await agent.connectAsync();
|
|
192
193
|
*
|
|
193
194
|
* process.on('SIGTERM', async () => {
|
|
194
|
-
*
|
|
195
|
-
* await
|
|
195
|
+
* agent.disconnect();
|
|
196
|
+
* await agent.drain();
|
|
196
197
|
* await closeMcp();
|
|
197
198
|
* });
|
|
198
199
|
*/
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
+
import { resolveWebAppOrigin, connectionsSettingsAppUrl, laneHeader } from '@ziggs-ai/api-client';
|
|
1
2
|
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
|
2
3
|
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
|
|
3
4
|
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
|
|
4
5
|
import { ConnectionsClient, getBackendUrl } from '@ziggs-ai/api-client';
|
|
5
|
-
import {
|
|
6
|
+
import { createAgent } from '../Agent.js';
|
|
7
|
+
import { laneFromToolContext } from '../tools/toolCreds.js';
|
|
6
8
|
import { thinkingDefaults } from '../runtime/defaults.js';
|
|
7
9
|
/**
|
|
8
10
|
* Typed failure from the Ziggs MCP gateway. `code` is the stable
|
|
@@ -71,6 +73,12 @@ export function resolveZiggsGatewayTarget(config, defaults = {}) {
|
|
|
71
73
|
authorization: `Bearer ${operatorKey}`,
|
|
72
74
|
'x-agent-id': agentId,
|
|
73
75
|
'x-grant-id': config.grantId,
|
|
76
|
+
// The lane, so the broker can tell who this wake acts for. Until it was
|
|
77
|
+
// sent, this door was the unfenced one: the named-connector proxy states
|
|
78
|
+
// its lane, so the same account and the same grant got two different
|
|
79
|
+
// answers depending on which door the agent used. Built by the shared
|
|
80
|
+
// helper, because a copy of this header going missing is invisible.
|
|
81
|
+
...laneHeader(defaults.laneId),
|
|
74
82
|
},
|
|
75
83
|
};
|
|
76
84
|
}
|
|
@@ -79,7 +87,7 @@ export function resolveZiggsGatewayTarget(config, defaults = {}) {
|
|
|
79
87
|
* the gateway resolves the invoking user's own connection + grant from
|
|
80
88
|
* `x-on-behalf-of-user` + the impersonated agent.
|
|
81
89
|
*/
|
|
82
|
-
export function resolvePerUserGatewayTarget(config, agentId, userId, onBehalfOfAgreementId) {
|
|
90
|
+
export function resolvePerUserGatewayTarget(config, agentId, userId, onBehalfOfAgreementId, laneId) {
|
|
83
91
|
const operatorKey = config.operatorKey || process.env.ZIGGS_OPERATOR_KEY;
|
|
84
92
|
if (!operatorKey) {
|
|
85
93
|
throw new Error('ziggs-per-user MCP transport requires an operatorKey (or ZIGGS_OPERATOR_KEY)');
|
|
@@ -103,6 +111,8 @@ export function resolvePerUserGatewayTarget(config, agentId, userId, onBehalfOfA
|
|
|
103
111
|
// agent (senderId), not the end-user who owns the connection. The gateway
|
|
104
112
|
// uses this agreement to resolve the delegation root's user.
|
|
105
113
|
...(onBehalfOfAgreementId ? { 'x-on-behalf-of-agreement': onBehalfOfAgreementId } : {}),
|
|
114
|
+
// The lane, for the same reason as the path-id door above.
|
|
115
|
+
...laneHeader(laneId),
|
|
106
116
|
},
|
|
107
117
|
};
|
|
108
118
|
}
|
|
@@ -127,6 +137,15 @@ export function buildTransport(config, defaults = {}) {
|
|
|
127
137
|
});
|
|
128
138
|
}
|
|
129
139
|
if (config.type === 'ziggs') {
|
|
140
|
+
/**
|
|
141
|
+
* No lane, and there cannot be one: this client is built ONCE at boot and
|
|
142
|
+
* shared by every wake, so any lane baked into its headers would be the
|
|
143
|
+
* wrong one for all but the first. The `ziggs` transport is the legacy
|
|
144
|
+
* fleet entry (`scripts/run-mcp-fleet.ts`) — a fixed connection + grant the
|
|
145
|
+
* operator configured for itself, so the lane-less fence (its own estate)
|
|
146
|
+
* is the right answer for it. A per-lane client belongs to `ziggs-per-user`,
|
|
147
|
+
* which builds one per wake and states the lane there.
|
|
148
|
+
*/
|
|
130
149
|
const target = resolveZiggsGatewayTarget(config, defaults);
|
|
131
150
|
return new StreamableHTTPClientTransport(target.url, {
|
|
132
151
|
requestInit: { headers: target.headers },
|
|
@@ -253,7 +272,7 @@ function trimToolDescriptions(tools, agentId) {
|
|
|
253
272
|
return { ...t, description: nextDesc };
|
|
254
273
|
});
|
|
255
274
|
if (trimmedCount > 0) {
|
|
256
|
-
console.warn(`[
|
|
275
|
+
console.warn(`[createMcpAgent] ${agentId}: trimmed ${trimmedCount} oversized tool description(s) to ${MAX_TOOL_DESC_CHARS} chars, saving ~${Math.round(savedChars / 4 / 1000)}K tokens/turn.`);
|
|
257
276
|
}
|
|
258
277
|
return out;
|
|
259
278
|
}
|
|
@@ -267,7 +286,7 @@ function filterToolsByAllowlist(tools, allowedTools, agentId) {
|
|
|
267
286
|
return tools;
|
|
268
287
|
const patterns = allowedTools.map((p) => p.toLowerCase());
|
|
269
288
|
const kept = tools.filter((t) => patterns.some((p) => t.name.toLowerCase().includes(p)));
|
|
270
|
-
console.warn(`[
|
|
289
|
+
console.warn(`[createMcpAgent] ${agentId}: tool allowlist active — bound ${kept.length}/${tools.length} tools matching [${allowedTools.join(', ')}]${kept.length === 0 ? ' ⚠️ NONE MATCHED — check patterns against the server\'s tool names' : ': ' + kept.map((t) => t.name).join(', ')}.`);
|
|
271
290
|
return kept;
|
|
272
291
|
}
|
|
273
292
|
/**
|
|
@@ -283,48 +302,48 @@ function capTools(tools, agentId, maxTools, extraToolCount, allowedTools) {
|
|
|
283
302
|
const budget = mcpToolBudget(maxTools, extraToolCount);
|
|
284
303
|
if (trimmed.length <= budget)
|
|
285
304
|
return trimmed;
|
|
286
|
-
console.warn(`[
|
|
305
|
+
console.warn(`[createMcpAgent] ${agentId}: MCP server exposes ${trimmed.length} tools, over the ${maxTools}-tool LLM cap; binding the first ${budget}, dropping ${trimmed.length - budget}.`);
|
|
287
306
|
return trimmed.slice(0, budget);
|
|
288
307
|
}
|
|
289
308
|
/**
|
|
290
|
-
* Wraps any MCP server as a Ziggs
|
|
309
|
+
* Wraps any MCP server as a Ziggs Agent.
|
|
291
310
|
*
|
|
292
311
|
* 1. Opens a connection to the MCP server (stdio, HTTP, or the Ziggs gateway).
|
|
293
312
|
* 2. Calls tools/list and maps each tool to a Ziggs ToolDefinition.
|
|
294
|
-
* 3. Returns an
|
|
313
|
+
* 3. Returns an Agent pre-loaded with those tools, ready to connectAsync().
|
|
295
314
|
*
|
|
296
315
|
* Usage (local stdio server):
|
|
297
|
-
* const {
|
|
316
|
+
* const { agent, closeMcp } = await createMcpAgent({
|
|
298
317
|
* transport: { type: 'stdio', command: 'npx', args: ['-y', '@modelcontextprotocol/server-github'] },
|
|
299
318
|
* agentId: 'my-github-agent',
|
|
300
319
|
* name: 'GitHub Agent',
|
|
301
320
|
* description: 'Wraps the GitHub MCP server',
|
|
302
321
|
* });
|
|
303
|
-
* await
|
|
322
|
+
* await agent.connectAsync();
|
|
304
323
|
*
|
|
305
324
|
* Usage (OAuth-protected remote server, brokered through Ziggs — the user
|
|
306
325
|
* connected the server and issued this agent a grant; the agent never sees
|
|
307
326
|
* the user's token):
|
|
308
|
-
* const {
|
|
327
|
+
* const { agent, closeMcp } = await createMcpAgent({
|
|
309
328
|
* transport: { type: 'ziggs', connectionId: 'con_abc', grantId: 'cgr_xyz' },
|
|
310
329
|
* agentId: 'my-notion-agent',
|
|
311
330
|
* name: 'Notion Agent',
|
|
312
331
|
* description: 'Works in the owner\'s Notion via their MCP connection',
|
|
313
332
|
* });
|
|
314
|
-
* await
|
|
333
|
+
* await agent.connectAsync();
|
|
315
334
|
*
|
|
316
335
|
* process.on('SIGTERM', async () => {
|
|
317
|
-
*
|
|
318
|
-
* await
|
|
336
|
+
* agent.disconnect();
|
|
337
|
+
* await agent.drain();
|
|
319
338
|
* await closeMcp();
|
|
320
339
|
* });
|
|
321
340
|
*/
|
|
322
341
|
export async function createMcpAgent(options) {
|
|
323
|
-
const { transport: transportConfig, extraTools = [], workflow, maxTools = DEFAULT_MAX_TOOLS, allowedTools, toolSchema, ...
|
|
342
|
+
const { transport: transportConfig, extraTools = [], workflow, maxTools = DEFAULT_MAX_TOOLS, allowedTools, toolSchema, ...agentOptions } = options;
|
|
324
343
|
// ── Per-user path: no boot connection; one MCP client per invoking
|
|
325
344
|
// user, each carrying that user's on-behalf-of header for an isolated session.
|
|
326
345
|
if (transportConfig.type === 'ziggs-per-user') {
|
|
327
|
-
const agentId = transportConfig.agentId ||
|
|
346
|
+
const agentId = transportConfig.agentId || agentOptions.agentId;
|
|
328
347
|
if (!agentId) {
|
|
329
348
|
throw new Error('ziggs-per-user MCP transport requires an agentId to impersonate');
|
|
330
349
|
}
|
|
@@ -333,14 +352,17 @@ export async function createMcpAgent(options) {
|
|
|
333
352
|
}
|
|
334
353
|
const tools = capTools(toolSchema, agentId, maxTools, extraTools.length, allowedTools);
|
|
335
354
|
const clients = new Map();
|
|
336
|
-
const clientFor = (userId, onBehalfOfAgreementId) => {
|
|
337
|
-
// Key by user + agreement: the gateway may resolve a different
|
|
338
|
-
// per agreement (delegation),
|
|
339
|
-
|
|
355
|
+
const clientFor = (userId, onBehalfOfAgreementId, laneId) => {
|
|
356
|
+
// Key by user + agreement + lane: the gateway may resolve a different
|
|
357
|
+
// end-user per agreement (delegation), and the lane decides which party's
|
|
358
|
+
// grants this session may spend, so sessions must not be shared across
|
|
359
|
+
// any of the three. The headers are fixed when the transport is built,
|
|
360
|
+
// so a session reused across lanes would carry the first lane's fence.
|
|
361
|
+
const cacheKey = `${userId}|${onBehalfOfAgreementId ?? ''}|${laneId ?? ''}`;
|
|
340
362
|
let p = clients.get(cacheKey);
|
|
341
363
|
if (!p) {
|
|
342
364
|
p = (async () => {
|
|
343
|
-
const target = resolvePerUserGatewayTarget(transportConfig, agentId, userId, onBehalfOfAgreementId);
|
|
365
|
+
const target = resolvePerUserGatewayTarget(transportConfig, agentId, userId, onBehalfOfAgreementId, laneId);
|
|
344
366
|
const transport = new StreamableHTTPClientTransport(target.url, {
|
|
345
367
|
requestInit: { headers: target.headers },
|
|
346
368
|
fetch: wrapGatewayFetch(),
|
|
@@ -367,9 +389,7 @@ export async function createMcpAgent(options) {
|
|
|
367
389
|
process.env.ZIGGS_BACKEND_URL ||
|
|
368
390
|
'http://localhost:3000').replace(/\/$/, '');
|
|
369
391
|
// App URL for the user-facing connect link: api.ziggsai.com → ziggsai.com.
|
|
370
|
-
const appUrl = backendBase
|
|
371
|
-
? 'http://localhost:1234'
|
|
372
|
-
: backendBase.replace(/\/\/api\./, '//');
|
|
392
|
+
const appUrl = resolveWebAppOrigin(undefined, backendBase);
|
|
373
393
|
const serverLabel = transportConfig.serverUrl
|
|
374
394
|
? new URL(transportConfig.serverUrl).host
|
|
375
395
|
: 'the external service';
|
|
@@ -404,6 +424,14 @@ export async function createMcpAgent(options) {
|
|
|
404
424
|
// connection-owning end-user; pass the agreement so the gateway resolves
|
|
405
425
|
// the delegation root's user.
|
|
406
426
|
const onBehalfOfAgreementId = typeof context.onBehalfOfAgreementId === 'string' ? context.onBehalfOfAgreementId : undefined;
|
|
427
|
+
/**
|
|
428
|
+
* The lane this wake runs in, read the same way every tool reads it.
|
|
429
|
+
* Without it the relay states no lane, the gateway resolves the wake to
|
|
430
|
+
* the agent's own operator, and any grant the CUSTOMER gave is refused —
|
|
431
|
+
* on the door built for exactly that case. Omitting a lane can only
|
|
432
|
+
* narrow reach, never widen it, so the miss never raises.
|
|
433
|
+
*/
|
|
434
|
+
const laneId = laneFromToolContext(context);
|
|
407
435
|
// (B): per-user resolution is intermittently flaky even when the
|
|
408
436
|
// connection is valid and resolvable — the same on-behalf call fails with
|
|
409
437
|
// NO_USER_CONNECTION on one wake and succeeds on the next. clientFor never
|
|
@@ -414,7 +442,7 @@ export async function createMcpAgent(options) {
|
|
|
414
442
|
let lastErr;
|
|
415
443
|
for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {
|
|
416
444
|
try {
|
|
417
|
-
const c = await clientFor(userId, onBehalfOfAgreementId);
|
|
445
|
+
const c = await clientFor(userId, onBehalfOfAgreementId, laneId);
|
|
418
446
|
return (await c.callTool({ name, arguments: args }));
|
|
419
447
|
}
|
|
420
448
|
catch (err) {
|
|
@@ -422,7 +450,7 @@ export async function createMcpAgent(options) {
|
|
|
422
450
|
const resolutionMiss = err instanceof ZiggsGatewayError && err.code === 'NO_USER_CONNECTION';
|
|
423
451
|
const transient = resolutionMiss || !(err instanceof ZiggsGatewayError);
|
|
424
452
|
if (attempt < MAX_ATTEMPTS && transient) {
|
|
425
|
-
console.warn(`[
|
|
453
|
+
console.warn(`[createMcpAgent] per-user ${name} attempt ${attempt}/${MAX_ATTEMPTS} failed (${err.message}); retrying with a fresh connection`);
|
|
426
454
|
await new Promise((r) => setTimeout(r, 500 * attempt));
|
|
427
455
|
continue;
|
|
428
456
|
}
|
|
@@ -433,7 +461,7 @@ export async function createMcpAgent(options) {
|
|
|
433
461
|
lastErr.code === 'NO_USER_CONNECTION' &&
|
|
434
462
|
transportConfig.serverUrl) {
|
|
435
463
|
const requestId = await triggerConnectionRequest(userId).catch(() => null);
|
|
436
|
-
const link =
|
|
464
|
+
const link = connectionsSettingsAppUrl(appUrl, requestId);
|
|
437
465
|
return {
|
|
438
466
|
content: [
|
|
439
467
|
{
|
|
@@ -445,13 +473,13 @@ export async function createMcpAgent(options) {
|
|
|
445
473
|
}
|
|
446
474
|
throw lastErr;
|
|
447
475
|
};
|
|
448
|
-
const
|
|
449
|
-
...
|
|
450
|
-
workflow: workflow ?? buildDefaultWorkflow(
|
|
476
|
+
const agent = createAgent({
|
|
477
|
+
...agentOptions,
|
|
478
|
+
workflow: workflow ?? buildDefaultWorkflow(agentOptions.description ?? '', tools),
|
|
451
479
|
tools: [...mapMcpTools(caller, tools), ...extraTools],
|
|
452
480
|
});
|
|
453
481
|
return {
|
|
454
|
-
|
|
482
|
+
agent,
|
|
455
483
|
closeMcp: async () => {
|
|
456
484
|
await Promise.all([...clients.values()].map(p => p.then(c => c.close()).catch(() => { })));
|
|
457
485
|
},
|
|
@@ -459,21 +487,29 @@ export async function createMcpAgent(options) {
|
|
|
459
487
|
};
|
|
460
488
|
}
|
|
461
489
|
// ── Single-client path (stdio / http / brokered ziggs): discover tools at boot.
|
|
462
|
-
const transport = buildTransport(transportConfig, { agentId:
|
|
490
|
+
const transport = buildTransport(transportConfig, { agentId: agentOptions.agentId });
|
|
463
491
|
const client = new Client({ name: 'ziggs-mcp-agent', version: '1.0.0' });
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
492
|
+
try {
|
|
493
|
+
await client.connect(transport);
|
|
494
|
+
const rawTools = capTools((await client.listTools()).tools, agentOptions.agentId, maxTools, extraTools.length, allowedTools);
|
|
495
|
+
const caller = (name, args) => client.callTool({ name, arguments: args });
|
|
496
|
+
const mcpTools = mapMcpTools(caller, rawTools);
|
|
497
|
+
const resolvedWorkflow = workflow ?? buildDefaultWorkflow(agentOptions.description ?? '', rawTools);
|
|
498
|
+
const agent = createAgent({
|
|
499
|
+
...agentOptions,
|
|
500
|
+
workflow: resolvedWorkflow,
|
|
501
|
+
tools: [...mcpTools, ...extraTools],
|
|
502
|
+
});
|
|
503
|
+
return {
|
|
504
|
+
agent,
|
|
505
|
+
closeMcp: () => client.close(),
|
|
506
|
+
toolNames: rawTools.map((t) => t.name),
|
|
507
|
+
};
|
|
508
|
+
}
|
|
509
|
+
catch (err) {
|
|
510
|
+
// Validation and tool discovery can fail after stdio has spawned a child.
|
|
511
|
+
// A rejected factory must release the transport it already acquired.
|
|
512
|
+
await client.close().catch(() => { });
|
|
513
|
+
throw err;
|
|
514
|
+
}
|
|
479
515
|
}
|
|
@@ -3,7 +3,9 @@
|
|
|
3
3
|
*
|
|
4
4
|
* This is the SDK's contract; the *storage* is operator-provided. The agent
|
|
5
5
|
* picks the key shape (often `counterparty:<id>`, sometimes `agreement:<id>:notes`,
|
|
6
|
-
* etc.) — the SDK does not enforce one.
|
|
6
|
+
* etc.) — the SDK does not enforce one. The host prefixes every key with
|
|
7
|
+
* the hire the wake is inside (or the agent's own section when there is
|
|
8
|
+
* no hire), so one customer's notes cannot be read in another's wake.
|
|
7
9
|
*
|
|
8
10
|
* Reference implementations: `InMemoryStore` (default; non-durable) and
|
|
9
11
|
* `FileMemoryStore` (single JSON file; useful for local dev). Anything
|
|
@@ -53,3 +55,18 @@ export declare class FileMemoryStore implements MemoryStore {
|
|
|
53
55
|
}[]>;
|
|
54
56
|
delete(key: string): Promise<void>;
|
|
55
57
|
}
|
|
58
|
+
/**
|
|
59
|
+
* Section a wake may read and write. A hire (pinned agreement, or an
|
|
60
|
+
* `agrn-` lane) gets its own prefix. A wake with no hire shares the
|
|
61
|
+
* agent's own section — same as before for in-process tools that key by
|
|
62
|
+
* chat themselves.
|
|
63
|
+
*/
|
|
64
|
+
export declare function memorySectionForWake(input: {
|
|
65
|
+
sessionId?: string | null;
|
|
66
|
+
activeAgreementId?: string | null;
|
|
67
|
+
}): string;
|
|
68
|
+
/**
|
|
69
|
+
* Present a store as if it only contained one section. Agent keys stay
|
|
70
|
+
* unchanged; get/put/list/delete all go through `section:`.
|
|
71
|
+
*/
|
|
72
|
+
export declare function partitionMemoryStore(store: MemoryStore, section: string): MemoryStore;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { agreementIdFromLane } from '@ziggs-ai/api-client';
|
|
1
2
|
/** Non-durable reference impl. Per-process; lost on restart. */
|
|
2
3
|
export class InMemoryStore {
|
|
3
4
|
store = new Map();
|
|
@@ -76,3 +77,45 @@ export class FileMemoryStore {
|
|
|
76
77
|
await this._flush();
|
|
77
78
|
}
|
|
78
79
|
}
|
|
80
|
+
/**
|
|
81
|
+
* Section a wake may read and write. A hire (pinned agreement, or an
|
|
82
|
+
* `agrn-` lane) gets its own prefix. A wake with no hire shares the
|
|
83
|
+
* agent's own section — same as before for in-process tools that key by
|
|
84
|
+
* chat themselves.
|
|
85
|
+
*/
|
|
86
|
+
export function memorySectionForWake(input) {
|
|
87
|
+
const pinned = typeof input.activeAgreementId === 'string'
|
|
88
|
+
? input.activeAgreementId.trim()
|
|
89
|
+
: '';
|
|
90
|
+
if (pinned)
|
|
91
|
+
return `hire:${pinned}`;
|
|
92
|
+
const fromLane = agreementIdFromAgrnSession(input.sessionId);
|
|
93
|
+
if (fromLane)
|
|
94
|
+
return `hire:${fromLane}`;
|
|
95
|
+
return 'agent';
|
|
96
|
+
}
|
|
97
|
+
function agreementIdFromAgrnSession(sessionId) {
|
|
98
|
+
return agreementIdFromLane(sessionId)?.trim() || null;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Present a store as if it only contained one section. Agent keys stay
|
|
102
|
+
* unchanged; get/put/list/delete all go through `section:`.
|
|
103
|
+
*/
|
|
104
|
+
export function partitionMemoryStore(store, section) {
|
|
105
|
+
const prefix = `${section}:`;
|
|
106
|
+
const partitioned = {
|
|
107
|
+
get: (key) => store.get(prefix + key),
|
|
108
|
+
put: (key, value) => store.put(prefix + key, value),
|
|
109
|
+
list: async (keyPrefix) => {
|
|
110
|
+
const rows = await store.list(prefix + keyPrefix);
|
|
111
|
+
return rows.map(({ key, value }) => ({
|
|
112
|
+
key: key.startsWith(prefix) ? key.slice(prefix.length) : key,
|
|
113
|
+
value,
|
|
114
|
+
}));
|
|
115
|
+
},
|
|
116
|
+
};
|
|
117
|
+
if (store.delete) {
|
|
118
|
+
partitioned.delete = (key) => store.delete(prefix + key);
|
|
119
|
+
}
|
|
120
|
+
return partitioned;
|
|
121
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { type Agent, type AgentOptions } from '../Agent.js';
|
|
2
2
|
interface ConnectionManagerMeta {
|
|
3
3
|
domain?: string;
|
|
4
4
|
expertise?: string[];
|
|
@@ -23,11 +23,11 @@ export declare class ConnectionPool {
|
|
|
23
23
|
* registered and never connected is an agent nobody hosts, which is the
|
|
24
24
|
* failure this pool exists to make impossible.
|
|
25
25
|
*/
|
|
26
|
-
register(configs:
|
|
26
|
+
register(configs: AgentOptions[], metaArr?: ConnectionManagerMeta[]): void;
|
|
27
27
|
/** Connect every registered agent. Reports the ones that could not come up. */
|
|
28
28
|
connectAll(): Promise<void>;
|
|
29
29
|
/** The host for one agent, connecting it if it somehow is not up yet. */
|
|
30
|
-
startAgent(agentId: string): Promise<
|
|
30
|
+
startAgent(agentId: string): Promise<Agent>;
|
|
31
31
|
stopAgent(agentId: string): Promise<void>;
|
|
32
32
|
disconnectAll(): Promise<void>;
|
|
33
33
|
/**
|
|
@@ -68,6 +68,12 @@ export declare class ConnectionPool {
|
|
|
68
68
|
query(filter: QueryFilter): string[];
|
|
69
69
|
list(): string[];
|
|
70
70
|
listActive(): string[];
|
|
71
|
+
/**
|
|
72
|
+
* Inbox liveness for every live agent in this process. The runner's health
|
|
73
|
+
* route answers from this rather than from "the HTTP server replied", which
|
|
74
|
+
* a wedged process does happily while none of its agents read anything.
|
|
75
|
+
*/
|
|
76
|
+
liveness(): ReturnType<Agent['inboxLiveness']>[];
|
|
71
77
|
get size(): number;
|
|
72
78
|
getMeta(id: string): ConnectionManagerMeta | undefined;
|
|
73
79
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* ConnectionPool — the agents this launcher process hosts.
|
|
3
3
|
*
|
|
4
|
-
* Holds N `
|
|
4
|
+
* Holds N `AgentOptions` configs and connects every one of them at boot
|
|
5
5
|
*. Each host long-polls its own inbox for the life of the process —
|
|
6
6
|
* there is no agent socket. There is no subset, no cap and no lifecycle policy:
|
|
7
7
|
* a write releasing that poll is the only signal anything needs.
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* answer was "none of them".
|
|
15
15
|
*/
|
|
16
16
|
import { ConnectionManager } from '@ziggs-ai/api-client';
|
|
17
|
-
import {
|
|
17
|
+
import { createAgent } from '../Agent.js';
|
|
18
18
|
import { runtimeLog } from '../shared/runtimeLog.js';
|
|
19
19
|
export class ConnectionPool {
|
|
20
20
|
manager = new ConnectionManager();
|
|
@@ -48,7 +48,7 @@ export class ConnectionPool {
|
|
|
48
48
|
const cfg = this.configs.get(agentId);
|
|
49
49
|
const originalTap = cfg.effectTap;
|
|
50
50
|
const pool = this;
|
|
51
|
-
const host =
|
|
51
|
+
const host = createAgent({
|
|
52
52
|
...cfg,
|
|
53
53
|
effectTap(effect, response) {
|
|
54
54
|
if (effect.kind === 'send-message') {
|
|
@@ -72,7 +72,11 @@ export class ConnectionPool {
|
|
|
72
72
|
}
|
|
73
73
|
/** Connect every registered agent. Reports the ones that could not come up. */
|
|
74
74
|
async connectAll() {
|
|
75
|
-
|
|
75
|
+
await this.manager.connectAll();
|
|
76
|
+
const connected = this.listActive().length;
|
|
77
|
+
if (connected !== this.size) {
|
|
78
|
+
throw new Error(`Only ${connected}/${this.size} agents started; process must restart`);
|
|
79
|
+
}
|
|
76
80
|
}
|
|
77
81
|
/** The host for one agent, connecting it if it somehow is not up yet. */
|
|
78
82
|
async startAgent(agentId) {
|
|
@@ -181,6 +185,14 @@ export class ConnectionPool {
|
|
|
181
185
|
query(filter) { return this.manager.query(filter); }
|
|
182
186
|
list() { return this.manager.list(); }
|
|
183
187
|
listActive() { return this.manager.listActive(); }
|
|
188
|
+
/**
|
|
189
|
+
* Inbox liveness for every live agent in this process. The runner's health
|
|
190
|
+
* route answers from this rather than from "the HTTP server replied", which
|
|
191
|
+
* a wedged process does happily while none of its agents read anything.
|
|
192
|
+
*/
|
|
193
|
+
liveness() {
|
|
194
|
+
return [...this.hosts.values()].map((host) => host.inboxLiveness());
|
|
195
|
+
}
|
|
184
196
|
get size() { return this.manager.size; }
|
|
185
197
|
getMeta(id) { return this.manager.getMeta(id); }
|
|
186
198
|
}
|
|
@@ -7,7 +7,7 @@ export interface EventQueueBusyLane {
|
|
|
7
7
|
queued: number;
|
|
8
8
|
processing: boolean;
|
|
9
9
|
}
|
|
10
|
-
/** Outcome of `waitForIdle` / `
|
|
10
|
+
/** Outcome of `waitForIdle` / `Agent.drain`. Timeout is data, not a throw. */
|
|
11
11
|
export interface WaitForIdleResult {
|
|
12
12
|
idle: boolean;
|
|
13
13
|
timedOut: boolean;
|
|
@@ -6,7 +6,7 @@ export interface StateTimeoutEvent {
|
|
|
6
6
|
}
|
|
7
7
|
/**
|
|
8
8
|
* Per-lane liveness timers for parked states declaring timeoutMs. Owned by
|
|
9
|
-
*
|
|
9
|
+
* Agent. A firing timer enqueues a state-timeout frame on the SAME lane
|
|
10
10
|
* (caller-provided enqueue → EventQueue), so it serializes behind any real
|
|
11
11
|
* event and can never race one. Epochs make stale frames identifiable: a
|
|
12
12
|
* queued timeout frame whose epoch is not the lane's currently-armed epoch was
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { runtimeLog } from '../shared/runtimeLog.js';
|
|
2
2
|
/**
|
|
3
3
|
* Per-lane liveness timers for parked states declaring timeoutMs. Owned by
|
|
4
|
-
*
|
|
4
|
+
* Agent. A firing timer enqueues a state-timeout frame on the SAME lane
|
|
5
5
|
* (caller-provided enqueue → EventQueue), so it serializes behind any real
|
|
6
6
|
* event and can never race one. Epochs make stale frames identifiable: a
|
|
7
7
|
* queued timeout frame whose epoch is not the lane's currently-armed epoch was
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Ziggs-flavored EffectHandler.
|
|
3
3
|
*
|
|
4
|
-
* Translates the
|
|
4
|
+
* Translates the brain's Effect requests into concrete backend calls:
|
|
5
5
|
* reads go through the grant-fenced ContextReadClient snapshot, writes through
|
|
6
6
|
* artifactsClient / taskService / agreementService / messageSender, and LLM
|
|
7
|
-
* turns through the adapter. This is the only place where
|
|
7
|
+
* turns through the adapter. This is the only place where brain intent meets
|
|
8
8
|
* backend reality.
|
|
9
9
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
10
|
+
* Belongs to the SDK body. The operator runner owns process startup and
|
|
11
|
+
* health serving; it does not perform the brain’s effects.
|
|
12
12
|
*/
|
|
13
13
|
import type { Effect, EffectHandler, LlmMessage, LlmResponse, LlmToolSchema, ToolCallResult } from '../types.js';
|
|
14
14
|
/** Minimal LLM adapter contract — OpenAIAdapter satisfies this. */
|
|
@@ -115,7 +115,7 @@ export async function executeToolWithSideEffects(deps, eff, effect, roster) {
|
|
|
115
115
|
const argsPreview = previewToolArgs(args);
|
|
116
116
|
// #6 — breadcrumbs are best-effort. Awaiting them bracketed every
|
|
117
117
|
// tool call; a slow/wedged artifacts backend stalled the whole wake. Match
|
|
118
|
-
//
|
|
118
|
+
// ClaudeBrain thought writes (fire-and-forget).
|
|
119
119
|
void emitToolOperationArtifact(deps.artifactsClient, effect.sessionId, 'started', {
|
|
120
120
|
state: 'started',
|
|
121
121
|
tool: effect.name,
|
|
@@ -230,7 +230,7 @@ export function createZiggsEffectHandler(deps) {
|
|
|
230
230
|
case 'read-context': {
|
|
231
231
|
// Grant-fenced aggregate read: the backend composes the ContextSnapshot
|
|
232
232
|
// (GET /context/snapshot) through the same access + temporal fence as
|
|
233
|
-
// the per-type reads. Every producer —
|
|
233
|
+
// the per-type reads. Every producer — ClaudeBrain.tick, runTurn,
|
|
234
234
|
// and the on-demand context_snapshot tool — routes through here.
|
|
235
235
|
const snap = await buildSnapshot(deps, effect.sessionId, deps.agentId, {
|
|
236
236
|
limit: effect.opts?.maxMessages,
|