@ziggs-ai/agent-sdk 0.16.1 → 0.17.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} +54 -40
- 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 +6 -3
- 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,9 @@
|
|
|
1
|
+
import { resolveWebAppOrigin, connectionsSettingsAppUrl } 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';
|
|
6
7
|
import { thinkingDefaults } from '../runtime/defaults.js';
|
|
7
8
|
/**
|
|
8
9
|
* Typed failure from the Ziggs MCP gateway. `code` is the stable
|
|
@@ -71,6 +72,11 @@ export function resolveZiggsGatewayTarget(config, defaults = {}) {
|
|
|
71
72
|
authorization: `Bearer ${operatorKey}`,
|
|
72
73
|
'x-agent-id': agentId,
|
|
73
74
|
'x-grant-id': config.grantId,
|
|
75
|
+
// The lane, so the broker can tell who this wake acts for. Until it was
|
|
76
|
+
// sent, this door was the unfenced one: the named-connector proxy states
|
|
77
|
+
// its lane, so the same account and the same grant got two different
|
|
78
|
+
// answers depending on which door the agent used.
|
|
79
|
+
...(defaults.laneId ? { 'x-ziggs-lane': defaults.laneId } : {}),
|
|
74
80
|
},
|
|
75
81
|
};
|
|
76
82
|
}
|
|
@@ -79,7 +85,7 @@ export function resolveZiggsGatewayTarget(config, defaults = {}) {
|
|
|
79
85
|
* the gateway resolves the invoking user's own connection + grant from
|
|
80
86
|
* `x-on-behalf-of-user` + the impersonated agent.
|
|
81
87
|
*/
|
|
82
|
-
export function resolvePerUserGatewayTarget(config, agentId, userId, onBehalfOfAgreementId) {
|
|
88
|
+
export function resolvePerUserGatewayTarget(config, agentId, userId, onBehalfOfAgreementId, laneId) {
|
|
83
89
|
const operatorKey = config.operatorKey || process.env.ZIGGS_OPERATOR_KEY;
|
|
84
90
|
if (!operatorKey) {
|
|
85
91
|
throw new Error('ziggs-per-user MCP transport requires an operatorKey (or ZIGGS_OPERATOR_KEY)');
|
|
@@ -103,6 +109,8 @@ export function resolvePerUserGatewayTarget(config, agentId, userId, onBehalfOfA
|
|
|
103
109
|
// agent (senderId), not the end-user who owns the connection. The gateway
|
|
104
110
|
// uses this agreement to resolve the delegation root's user.
|
|
105
111
|
...(onBehalfOfAgreementId ? { 'x-on-behalf-of-agreement': onBehalfOfAgreementId } : {}),
|
|
112
|
+
// The lane, for the same reason as the path-id door above.
|
|
113
|
+
...(laneId ? { 'x-ziggs-lane': laneId } : {}),
|
|
106
114
|
},
|
|
107
115
|
};
|
|
108
116
|
}
|
|
@@ -253,7 +261,7 @@ function trimToolDescriptions(tools, agentId) {
|
|
|
253
261
|
return { ...t, description: nextDesc };
|
|
254
262
|
});
|
|
255
263
|
if (trimmedCount > 0) {
|
|
256
|
-
console.warn(`[
|
|
264
|
+
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
265
|
}
|
|
258
266
|
return out;
|
|
259
267
|
}
|
|
@@ -267,7 +275,7 @@ function filterToolsByAllowlist(tools, allowedTools, agentId) {
|
|
|
267
275
|
return tools;
|
|
268
276
|
const patterns = allowedTools.map((p) => p.toLowerCase());
|
|
269
277
|
const kept = tools.filter((t) => patterns.some((p) => t.name.toLowerCase().includes(p)));
|
|
270
|
-
console.warn(`[
|
|
278
|
+
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
279
|
return kept;
|
|
272
280
|
}
|
|
273
281
|
/**
|
|
@@ -283,48 +291,48 @@ function capTools(tools, agentId, maxTools, extraToolCount, allowedTools) {
|
|
|
283
291
|
const budget = mcpToolBudget(maxTools, extraToolCount);
|
|
284
292
|
if (trimmed.length <= budget)
|
|
285
293
|
return trimmed;
|
|
286
|
-
console.warn(`[
|
|
294
|
+
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
295
|
return trimmed.slice(0, budget);
|
|
288
296
|
}
|
|
289
297
|
/**
|
|
290
|
-
* Wraps any MCP server as a Ziggs
|
|
298
|
+
* Wraps any MCP server as a Ziggs Agent.
|
|
291
299
|
*
|
|
292
300
|
* 1. Opens a connection to the MCP server (stdio, HTTP, or the Ziggs gateway).
|
|
293
301
|
* 2. Calls tools/list and maps each tool to a Ziggs ToolDefinition.
|
|
294
|
-
* 3. Returns an
|
|
302
|
+
* 3. Returns an Agent pre-loaded with those tools, ready to connectAsync().
|
|
295
303
|
*
|
|
296
304
|
* Usage (local stdio server):
|
|
297
|
-
* const {
|
|
305
|
+
* const { agent, closeMcp } = await createMcpAgent({
|
|
298
306
|
* transport: { type: 'stdio', command: 'npx', args: ['-y', '@modelcontextprotocol/server-github'] },
|
|
299
307
|
* agentId: 'my-github-agent',
|
|
300
308
|
* name: 'GitHub Agent',
|
|
301
309
|
* description: 'Wraps the GitHub MCP server',
|
|
302
310
|
* });
|
|
303
|
-
* await
|
|
311
|
+
* await agent.connectAsync();
|
|
304
312
|
*
|
|
305
313
|
* Usage (OAuth-protected remote server, brokered through Ziggs — the user
|
|
306
314
|
* connected the server and issued this agent a grant; the agent never sees
|
|
307
315
|
* the user's token):
|
|
308
|
-
* const {
|
|
316
|
+
* const { agent, closeMcp } = await createMcpAgent({
|
|
309
317
|
* transport: { type: 'ziggs', connectionId: 'con_abc', grantId: 'cgr_xyz' },
|
|
310
318
|
* agentId: 'my-notion-agent',
|
|
311
319
|
* name: 'Notion Agent',
|
|
312
320
|
* description: 'Works in the owner\'s Notion via their MCP connection',
|
|
313
321
|
* });
|
|
314
|
-
* await
|
|
322
|
+
* await agent.connectAsync();
|
|
315
323
|
*
|
|
316
324
|
* process.on('SIGTERM', async () => {
|
|
317
|
-
*
|
|
318
|
-
* await
|
|
325
|
+
* agent.disconnect();
|
|
326
|
+
* await agent.drain();
|
|
319
327
|
* await closeMcp();
|
|
320
328
|
* });
|
|
321
329
|
*/
|
|
322
330
|
export async function createMcpAgent(options) {
|
|
323
|
-
const { transport: transportConfig, extraTools = [], workflow, maxTools = DEFAULT_MAX_TOOLS, allowedTools, toolSchema, ...
|
|
331
|
+
const { transport: transportConfig, extraTools = [], workflow, maxTools = DEFAULT_MAX_TOOLS, allowedTools, toolSchema, ...agentOptions } = options;
|
|
324
332
|
// ── Per-user path: no boot connection; one MCP client per invoking
|
|
325
333
|
// user, each carrying that user's on-behalf-of header for an isolated session.
|
|
326
334
|
if (transportConfig.type === 'ziggs-per-user') {
|
|
327
|
-
const agentId = transportConfig.agentId ||
|
|
335
|
+
const agentId = transportConfig.agentId || agentOptions.agentId;
|
|
328
336
|
if (!agentId) {
|
|
329
337
|
throw new Error('ziggs-per-user MCP transport requires an agentId to impersonate');
|
|
330
338
|
}
|
|
@@ -367,9 +375,7 @@ export async function createMcpAgent(options) {
|
|
|
367
375
|
process.env.ZIGGS_BACKEND_URL ||
|
|
368
376
|
'http://localhost:3000').replace(/\/$/, '');
|
|
369
377
|
// 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\./, '//');
|
|
378
|
+
const appUrl = resolveWebAppOrigin(undefined, backendBase);
|
|
373
379
|
const serverLabel = transportConfig.serverUrl
|
|
374
380
|
? new URL(transportConfig.serverUrl).host
|
|
375
381
|
: 'the external service';
|
|
@@ -422,7 +428,7 @@ export async function createMcpAgent(options) {
|
|
|
422
428
|
const resolutionMiss = err instanceof ZiggsGatewayError && err.code === 'NO_USER_CONNECTION';
|
|
423
429
|
const transient = resolutionMiss || !(err instanceof ZiggsGatewayError);
|
|
424
430
|
if (attempt < MAX_ATTEMPTS && transient) {
|
|
425
|
-
console.warn(`[
|
|
431
|
+
console.warn(`[createMcpAgent] per-user ${name} attempt ${attempt}/${MAX_ATTEMPTS} failed (${err.message}); retrying with a fresh connection`);
|
|
426
432
|
await new Promise((r) => setTimeout(r, 500 * attempt));
|
|
427
433
|
continue;
|
|
428
434
|
}
|
|
@@ -433,7 +439,7 @@ export async function createMcpAgent(options) {
|
|
|
433
439
|
lastErr.code === 'NO_USER_CONNECTION' &&
|
|
434
440
|
transportConfig.serverUrl) {
|
|
435
441
|
const requestId = await triggerConnectionRequest(userId).catch(() => null);
|
|
436
|
-
const link =
|
|
442
|
+
const link = connectionsSettingsAppUrl(appUrl, requestId);
|
|
437
443
|
return {
|
|
438
444
|
content: [
|
|
439
445
|
{
|
|
@@ -445,13 +451,13 @@ export async function createMcpAgent(options) {
|
|
|
445
451
|
}
|
|
446
452
|
throw lastErr;
|
|
447
453
|
};
|
|
448
|
-
const
|
|
449
|
-
...
|
|
450
|
-
workflow: workflow ?? buildDefaultWorkflow(
|
|
454
|
+
const agent = createAgent({
|
|
455
|
+
...agentOptions,
|
|
456
|
+
workflow: workflow ?? buildDefaultWorkflow(agentOptions.description ?? '', tools),
|
|
451
457
|
tools: [...mapMcpTools(caller, tools), ...extraTools],
|
|
452
458
|
});
|
|
453
459
|
return {
|
|
454
|
-
|
|
460
|
+
agent,
|
|
455
461
|
closeMcp: async () => {
|
|
456
462
|
await Promise.all([...clients.values()].map(p => p.then(c => c.close()).catch(() => { })));
|
|
457
463
|
},
|
|
@@ -459,21 +465,29 @@ export async function createMcpAgent(options) {
|
|
|
459
465
|
};
|
|
460
466
|
}
|
|
461
467
|
// ── Single-client path (stdio / http / brokered ziggs): discover tools at boot.
|
|
462
|
-
const transport = buildTransport(transportConfig, { agentId:
|
|
468
|
+
const transport = buildTransport(transportConfig, { agentId: agentOptions.agentId });
|
|
463
469
|
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
|
-
|
|
470
|
+
try {
|
|
471
|
+
await client.connect(transport);
|
|
472
|
+
const rawTools = capTools((await client.listTools()).tools, agentOptions.agentId, maxTools, extraTools.length, allowedTools);
|
|
473
|
+
const caller = (name, args) => client.callTool({ name, arguments: args });
|
|
474
|
+
const mcpTools = mapMcpTools(caller, rawTools);
|
|
475
|
+
const resolvedWorkflow = workflow ?? buildDefaultWorkflow(agentOptions.description ?? '', rawTools);
|
|
476
|
+
const agent = createAgent({
|
|
477
|
+
...agentOptions,
|
|
478
|
+
workflow: resolvedWorkflow,
|
|
479
|
+
tools: [...mcpTools, ...extraTools],
|
|
480
|
+
});
|
|
481
|
+
return {
|
|
482
|
+
agent,
|
|
483
|
+
closeMcp: () => client.close(),
|
|
484
|
+
toolNames: rawTools.map((t) => t.name),
|
|
485
|
+
};
|
|
486
|
+
}
|
|
487
|
+
catch (err) {
|
|
488
|
+
// Validation and tool discovery can fail after stdio has spawned a child.
|
|
489
|
+
// A rejected factory must release the transport it already acquired.
|
|
490
|
+
await client.close().catch(() => { });
|
|
491
|
+
throw err;
|
|
492
|
+
}
|
|
479
493
|
}
|
|
@@ -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,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type InboxRequestRef, type InboxTaskRef, type Creds } from '@ziggs-ai/api-client';
|
|
1
|
+
import { type InboxChatNews, type InboxRequestRef, type InboxTaskRef, type Creds } from '@ziggs-ai/api-client';
|
|
2
2
|
import { SeenMessages } from './SeenMessages.js';
|
|
3
3
|
/**
|
|
4
4
|
* Per-agent inbox long-poll loop (delivery law, phase 4). This is the ONLY way
|
|
@@ -53,6 +53,19 @@ export interface InboxPassResult {
|
|
|
53
53
|
*/
|
|
54
54
|
failures: number;
|
|
55
55
|
}
|
|
56
|
+
/**
|
|
57
|
+
* Store publish waits on a `probe_*` chat reply, not inbox ack. Act those
|
|
58
|
+
* rooms before leftover mail so a standing host answers the gate even when
|
|
59
|
+
* the envelope also carries older chats.
|
|
60
|
+
*/
|
|
61
|
+
export declare function chatsInWakeOrder(chats: InboxChatNews[]): InboxChatNews[];
|
|
62
|
+
/**
|
|
63
|
+
* Store publish waits on a chat reply, not inbox ack. The ordinary pass can
|
|
64
|
+
* already be inside leftover mail when that room appears, so a side read
|
|
65
|
+
* answers `probe_*` chats without waiting for the current pass to finish.
|
|
66
|
+
* It never acks — the main pass still owns the watermark.
|
|
67
|
+
*/
|
|
68
|
+
export declare function answerPublishProbes(deps: InboxLoopDeps): Promise<number>;
|
|
56
69
|
/**
|
|
57
70
|
* One long-poll pass: hold up to `waitSeconds` for new inbox content, then read
|
|
58
71
|
* the deltas, deliver them into the host, and ack. Returns what it delivered.
|
|
@@ -67,7 +80,26 @@ export interface InboxLoopHandle {
|
|
|
67
80
|
stop(): Promise<void>;
|
|
68
81
|
/** True while the loop is running. */
|
|
69
82
|
readonly running: boolean;
|
|
83
|
+
/**
|
|
84
|
+
* When this loop last completed a pass against the server — the one true
|
|
85
|
+
* liveness signal for an agent, and the same fact the store shows as online.
|
|
86
|
+
* Null until the first pass returns.
|
|
87
|
+
*/
|
|
88
|
+
readonly lastPassAt: number | null;
|
|
89
|
+
/**
|
|
90
|
+
* When this loop last STARTED an iteration, successful or not. Separate from
|
|
91
|
+
* `lastPassAt` on purpose: a backend outage stops passes while the loop is
|
|
92
|
+
* perfectly healthy, so a restart decision reads this one and a staleness
|
|
93
|
+
* report reads the other. `running` says the loop exists; this says it turns.
|
|
94
|
+
*/
|
|
95
|
+
readonly lastAttemptAt: number | null;
|
|
70
96
|
}
|
|
97
|
+
/**
|
|
98
|
+
* How many consecutive passes have refused to advance the watermark.
|
|
99
|
+
* A capped envelope used to reset the counter (zero handover failures),
|
|
100
|
+
* so `forceAck` never fired on the one hold that lasts forever.
|
|
101
|
+
*/
|
|
102
|
+
export declare function heldPassAfter(prev: number, pass: Pick<InboxPassResult, 'acked' | 'failures' | 'truncated'>): number;
|
|
71
103
|
/**
|
|
72
104
|
* Run {@link pollInboxOnce} forever (until stopped). Each iteration long-polls
|
|
73
105
|
* for `waitSeconds`; a passing envelope returns instantly, an empty one holds.
|
|
@@ -82,4 +114,5 @@ export interface InboxLoopHandle {
|
|
|
82
114
|
*/
|
|
83
115
|
export declare function runInboxLoop(deps: InboxLoopDeps, opts?: {
|
|
84
116
|
waitSeconds?: number;
|
|
117
|
+
probePollMs?: number;
|
|
85
118
|
}): InboxLoopHandle;
|