@ziggs-ai/agent-sdk 0.11.1 → 0.13.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.
Files changed (52) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/dist/AgentHost.d.ts +27 -5
  3. package/dist/AgentHost.js +110 -15
  4. package/dist/agent/ClaudeSdkAgent.js +2 -2
  5. package/dist/agent/claudeSdkProtocol.d.ts +1 -1
  6. package/dist/agent/claudeSdkProtocol.js +3 -2
  7. package/dist/agent/claudeSdkTools.js +46 -10
  8. package/dist/cognition/validateContext.js +4 -1
  9. package/dist/context/batch.d.ts +3 -0
  10. package/dist/context/batch.js +8 -5
  11. package/dist/context/classifyEnvelope.js +4 -5
  12. package/dist/context/routingLabels.js +2 -3
  13. package/dist/formatters/AgreementFormatter.d.ts +7 -1
  14. package/dist/formatters/AgreementFormatter.js +17 -8
  15. package/dist/formatters/HistoryFormatter.d.ts +5 -0
  16. package/dist/formatters/HistoryFormatter.js +16 -7
  17. package/dist/index.d.ts +4 -2
  18. package/dist/index.js +8 -1
  19. package/dist/ingress/normalizeIncoming.d.ts +3 -8
  20. package/dist/runtime/PromptBuilder.js +32 -22
  21. package/dist/runtime/buildOutcome.js +4 -4
  22. package/dist/runtime/mandateDeclaration.d.ts +9 -0
  23. package/dist/runtime/mandateDeclaration.js +62 -0
  24. package/dist/runtime/runTurn.js +8 -0
  25. package/dist/server/ConnectionPool.d.ts +19 -0
  26. package/dist/server/ConnectionPool.js +30 -0
  27. package/dist/server/OutboxBuffer.d.ts +1 -1
  28. package/dist/server/ZiggsEffectHandler.d.ts +1 -1
  29. package/dist/server/ZiggsEffectHandler.js +6 -2
  30. package/dist/server/agreements/AgreementService.d.ts +19 -10
  31. package/dist/server/agreements/AgreementService.js +22 -11
  32. package/dist/server/inboxLoop.d.ts +6 -6
  33. package/dist/server/inboxLoop.js +31 -18
  34. package/dist/server/proactive/ProactiveTrigger.d.ts +7 -1
  35. package/dist/server/proactive/ProactiveTrigger.js +2 -2
  36. package/dist/server/requestMatch.d.ts +5 -0
  37. package/dist/server/{questMatch.js → requestMatch.js} +2 -2
  38. package/dist/server/runLauncher.js +24 -3
  39. package/dist/server/tools/index.d.ts +2 -2
  40. package/dist/server/tools/index.js +2 -2
  41. package/dist/server/tools/tier1/protocolRunner.js +16 -10
  42. package/dist/server/tools/tier1/protocolTools.js +12 -7
  43. package/dist/server/tools/tier2/linkTools.d.ts +1 -1
  44. package/dist/server/tools/tier2/linkTools.js +11 -9
  45. package/dist/server/tools/tier2/marketplaceTools.js +2 -2
  46. package/dist/server/tools/tier2/mcpConnectionTools.d.ts +33 -0
  47. package/dist/server/tools/tier2/mcpConnectionTools.js +138 -6
  48. package/dist/server/tools/tier2/paymentTools.d.ts +0 -8
  49. package/dist/server/tools/tier2/paymentTools.js +8 -12
  50. package/dist/types.d.ts +25 -10
  51. package/package.json +2 -2
  52. package/dist/server/questMatch.d.ts +0 -5
@@ -89,19 +89,145 @@ export const mcpToolsListTool = defineTool('mcp_tools_list', {
89
89
  type: 'string',
90
90
  description: 'Grant to use. Omit to use the live grant found on that connection.',
91
91
  },
92
+ names: {
93
+ type: 'array',
94
+ items: { type: 'string' },
95
+ description: 'Tool names to return the FULL parameters (inputSchema) for. Omit to get the names and descriptions only — a whole server of schemas is tens of thousands of characters and gets cut, which is worse than not asking.',
96
+ },
92
97
  }, async (args, ctx) => {
93
98
  const t = await resolveTarget(ctx, args['connectionId'], args['grantId']);
99
+ /**
100
+ * Names first, schemas on demand — the same two-step this platform's own MCP
101
+ * surface settled on. One Linear server answers 53 tools and 78,000
102
+ * characters of schema; asking for all of it fills a wake with parameters for
103
+ * tools the agent will not call, and the cut lands mid-schema where the half
104
+ * that survives still looks complete.
105
+ */
106
+ const wanted = args['names']?.filter((n) => typeof n === 'string' && n);
94
107
  const tools = await withClient(ctx, t.connectionId, t.grantId, async (c) => {
95
108
  const res = await c.listTools();
96
- return (res.tools ?? []).map((tool) => ({
109
+ const rows = res.tools ?? [];
110
+ const picked = wanted?.length ? rows.filter((tool) => wanted.includes(tool.name)) : rows;
111
+ return picked.map((tool) => ({
97
112
  name: tool.name,
98
113
  description: tool.description ?? null,
114
+ /**
115
+ * The parameters, which this used to drop.
116
+ *
117
+ * Without them an agent has to GUESS what a tool takes, and a guessed
118
+ * argument is not an error — most servers ignore what they do not know
119
+ * and answer with an empty list. That empty list then reads as "nothing
120
+ * happened": a weekly digest reported one item shipped for a week with
121
+ * fifty, because `status`, `filter.state.name` and `updatedAfter` were
122
+ * all invented and all silently matched nothing.
123
+ */
124
+ ...(wanted?.length ? { inputSchema: tool.inputSchema ?? null } : {}),
99
125
  }));
100
126
  });
101
- return { connectionId: t.connectionId, grantId: t.grantId, tools };
127
+ const missing = wanted?.filter((n) => !tools.some((row) => row.name === n)) ?? [];
128
+ const bounded = boundMcpResult({ tools }, MAX_MCP_TOOLS_LIST_CHARS);
129
+ return {
130
+ connectionId: t.connectionId,
131
+ grantId: t.grantId,
132
+ ...bounded.result,
133
+ ...(bounded.truncated ? { truncated: bounded.truncated } : {}),
134
+ ...(missing.length ? { notFound: missing } : {}),
135
+ ...(wanted?.length
136
+ ? {}
137
+ : {
138
+ note: 'Names and descriptions only. Call again with names:["<tool>"] to get that tool\'s parameters before you pass any arguments — a parameter the server does not declare is usually ignored, and the empty result reads as real emptiness.',
139
+ }),
140
+ };
102
141
  }, {
103
- description: "List the tools a connected MCP server exposes, through the Ziggs gateway — the owner's credential stays server-side. The grant may allow only some of them; a call outside the grant is refused by the gateway, not by you.",
142
+ description: "List the tools a connected MCP server exposes, through the Ziggs gateway. Names and descriptions by default; pass names:[\"<tool>\"] for those tools' full parameters (inputSchema) — the owner's credential stays server-side. Read the schema before calling a tool with arguments: a parameter the server does not declare is usually ignored rather than rejected, so a guessed filter comes back as an empty result that looks like real emptiness. The grant may allow only some tools; a call outside it is refused by the gateway, not by you.",
104
143
  });
144
+ /**
145
+ * How much of one MCP tool result a wake will carry.
146
+ *
147
+ * A remote MCP server hands back the upstream API's payload verbatim, and those
148
+ * are sized for programs, not for a context window: GitHub's `list_pull_requests`
149
+ * with no paging argument answered ~150,000 characters. Past the runtime's limit
150
+ * the result stops being a result — the Claude SDK spills an oversized tool
151
+ * result to a file, and a hosted agent has no filesystem tool to read it with, so
152
+ * the wake ends in "the answer was too big and I cannot see it". Same class of
153
+ * problem as an MCP server that ships a 30K-character tool description, one layer
154
+ * later.
155
+ *
156
+ * A bounded answer with a marker beats an unreadable one: the model can act on
157
+ * "truncated, narrow the call", and it cannot act on a file path.
158
+ */
159
+ export const MAX_MCP_RESULT_CHARS = 24_000;
160
+ /**
161
+ * Tool schemas are bigger than tool results and are read once per wake, so they
162
+ * get their own, larger ceiling — a schema cut in half is worse than useless,
163
+ * because the half that survives looks complete.
164
+ */
165
+ export const MAX_MCP_TOOLS_LIST_CHARS = 60_000;
166
+ const TRUNCATION_ADVICE = 'Narrow the call instead of repeating it: most servers take paging (perPage/page) ' +
167
+ 'or filter arguments, and a named target usually has a narrower tool than a list.';
168
+ /**
169
+ * Bound one MCP result, telling the model plainly what was cut.
170
+ *
171
+ * Text blocks are cut individually rather than the JSON being sliced, because a
172
+ * sliced JSON string is invalid and a model handed invalid JSON reports the
173
+ * server as broken.
174
+ */
175
+ export function boundMcpResult(result, max = MAX_MCP_RESULT_CHARS) {
176
+ const whole = JSON.stringify(result) ?? '';
177
+ if (whole.length <= max)
178
+ return { result };
179
+ const content = result?.content;
180
+ if (!Array.isArray(content)) {
181
+ return {
182
+ result: { text: whole.slice(0, max) },
183
+ truncated: {
184
+ originalChars: whole.length,
185
+ keptChars: max,
186
+ note: `Result was ${whole.length} characters and is cut to ${max}. ${TRUNCATION_ADVICE}`,
187
+ },
188
+ };
189
+ }
190
+ const kept = [];
191
+ let used = 0;
192
+ let dropped = 0;
193
+ for (const block of content) {
194
+ const text = block?.text;
195
+ if (typeof text !== 'string') {
196
+ const size = (JSON.stringify(block) ?? '').length;
197
+ if (used + size > max) {
198
+ dropped += 1;
199
+ continue;
200
+ }
201
+ used += size;
202
+ kept.push(block);
203
+ continue;
204
+ }
205
+ const room = max - used;
206
+ if (room <= 0) {
207
+ dropped += 1;
208
+ continue;
209
+ }
210
+ if (text.length <= room) {
211
+ used += text.length;
212
+ kept.push(block);
213
+ continue;
214
+ }
215
+ kept.push({
216
+ ...block,
217
+ text: `${text.slice(0, room)}\n\n[…cut here: this block was ${text.length} characters]`,
218
+ });
219
+ used = max;
220
+ }
221
+ return {
222
+ result: { ...result, content: kept },
223
+ truncated: {
224
+ originalChars: whole.length,
225
+ keptChars: used,
226
+ droppedBlocks: dropped,
227
+ note: `Result was ${whole.length} characters, over the ${max}-character limit, so it is cut. What you have is the beginning, not the whole answer — do not report it as complete. ${TRUNCATION_ADVICE}`,
228
+ },
229
+ };
230
+ }
105
231
  export const mcpToolCallTool = defineTool('mcp_tool_call', {
106
232
  tool: {
107
233
  type: 'string',
@@ -125,12 +251,18 @@ export const mcpToolCallTool = defineTool('mcp_tool_call', {
125
251
  if (!name)
126
252
  throw new Error('tool is required');
127
253
  const t = await resolveTarget(ctx, args['connectionId'], args['grantId']);
128
- const result = await withClient(ctx, t.connectionId, t.grantId, (c) => c.callTool({
254
+ const raw = await withClient(ctx, t.connectionId, t.grantId, (c) => c.callTool({
129
255
  name,
130
256
  arguments: args['args'] ?? {},
131
257
  }));
132
- return { connectionId: t.connectionId, tool: name, result };
258
+ const bounded = boundMcpResult(raw);
259
+ return {
260
+ connectionId: t.connectionId,
261
+ tool: name,
262
+ result: bounded.result,
263
+ ...(bounded.truncated ? { truncated: bounded.truncated } : {}),
264
+ };
133
265
  }, {
134
- description: "Call one tool on a connected MCP server through the Ziggs gateway. Use this for a connection whose provider is a remote MCP server — connection_proxy is for named connectors (github, jira) and refuses these with \"Unknown provider: mcp\". The gateway injects the owner's token upstream and checks the tool name against your grant, so you never hold the credential. Discover names with mcp_tools_list, or read allowed_actions off the grant in grant_list.",
266
+ description: "Call one tool on a connected MCP server through the Ziggs gateway. Use this for a connection whose provider is a remote MCP server — connection_proxy is for named connectors (github, jira) and refuses these with \"Unknown provider: mcp\". The gateway injects the owner's token upstream and checks the tool name against your grant, so you never hold the credential. Discover names with mcp_tools_list, or read allowed_actions off the grant in grant_list. Results are bounded: a payload over the limit comes back cut, with a `truncated` field saying so — pass the server's paging or filter arguments rather than repeating the call.",
135
267
  });
136
268
  export const MCP_CONNECTION_TOOLS = [mcpToolsListTool, mcpToolCallTool];
@@ -1,11 +1,3 @@
1
1
  import type { ToolDefinition } from '../../../tools/defineTool.js';
2
2
  export declare const paymentBalanceTool: ToolDefinition;
3
- export declare const paymentTransferTool: ToolDefinition;
4
- export declare const paymentWaitForApprovalTool: ToolDefinition;
5
- export declare const paymentHoldTool: ToolDefinition;
6
- export declare const paymentReleaseTool: ToolDefinition;
7
- export declare const paymentResolveWalletTool: ToolDefinition;
8
- export declare const paymentIssueGrantTool: ToolDefinition;
9
- export declare const paymentAttenuateGrantTool: ToolDefinition;
10
- export declare const paymentRevokeGrantTool: ToolDefinition;
11
3
  export declare const PAYMENT_TOOLS: ToolDefinition[];
@@ -1,16 +1,12 @@
1
- import { PAYMENT_CAPABILITIES, paymentBalanceCapability, paymentTransferCapability, paymentWaitForApprovalCapability, paymentHoldCapability, paymentReleaseCapability, paymentResolveWalletCapability, paymentIssueGrantCapability, paymentAttenuateGrantCapability, paymentRevokeGrantCapability, } from '@ziggs-ai/api-client';
1
+ import { PAYMENT_CAPABILITIES, paymentBalanceCapability } from '@ziggs-ai/api-client';
2
2
  import { toolFromCapability, toolsFromCapabilities } from '../../../tools/capabilityAdapter.js';
3
3
  // the wallet toolset is defined once in api-client's shared
4
- // capability layer; this file only adapts it onto the SDK surface. Safety is
5
- // unchanged: money-moving calls stay policy-gated server-side, and there is
6
- // deliberately NO approve/decide tool on the agent surface.
4
+ // capability layer; this file only adapts it onto the SDK surface.
5
+ //
6
+ // The movement verbs are gone from both agent surfaces —
7
+ // transfer, hold, release, wait_for_approval, resolve_wallet, and the three
8
+ // grant mutations. Points are a meter, and an agent does not move the meter on
9
+ // its own. Agreements still settle in points server-side, so nothing an agent
10
+ // needs in order to be paid went with them. Reading your own balance survives.
7
11
  export const paymentBalanceTool = toolFromCapability(paymentBalanceCapability);
8
- export const paymentTransferTool = toolFromCapability(paymentTransferCapability);
9
- export const paymentWaitForApprovalTool = toolFromCapability(paymentWaitForApprovalCapability);
10
- export const paymentHoldTool = toolFromCapability(paymentHoldCapability);
11
- export const paymentReleaseTool = toolFromCapability(paymentReleaseCapability);
12
- export const paymentResolveWalletTool = toolFromCapability(paymentResolveWalletCapability);
13
- export const paymentIssueGrantTool = toolFromCapability(paymentIssueGrantCapability);
14
- export const paymentAttenuateGrantTool = toolFromCapability(paymentAttenuateGrantCapability);
15
- export const paymentRevokeGrantTool = toolFromCapability(paymentRevokeGrantCapability);
16
12
  export const PAYMENT_TOOLS = toolsFromCapabilities(PAYMENT_CAPABILITIES);
package/dist/types.d.ts CHANGED
@@ -4,15 +4,11 @@
4
4
  * One outcome vocabulary, one ctx shape, one transition signature.
5
5
  * Every state — parked or thinking — re-enters with exactly one Outcome.
6
6
  */
7
+ import type { AgreementParties } from '@ziggs-ai/api-client';
7
8
  import type { AgentNeed } from './tools/needs.js';
8
9
  export interface AgreementRef {
9
10
  agreementId: string;
10
- parties?: {
11
- creator?: string;
12
- provider?: string;
13
- payer?: string;
14
- proposedTo?: string;
15
- };
11
+ parties?: AgreementParties;
16
12
  proposal?: {
17
13
  status?: 'pending' | 'approved' | 'rejected' | 'cancelled';
18
14
  };
@@ -328,11 +324,23 @@ export interface LlmResponse {
328
324
  completionTokens?: number;
329
325
  };
330
326
  }
327
+ /**
328
+ * One participant on the snapshot roster. `kind` is the declared holder kind the
329
+ * server projection stamped, so a consumer reads what a participant IS instead
330
+ * of inferring it from which array the row arrived in. Optional because a
331
+ * backend that predates it simply omits the field.
332
+ */
333
+ export interface SnapshotParticipant {
334
+ role?: string;
335
+ kind?: string;
336
+ isYou?: boolean;
337
+ [key: string]: unknown;
338
+ }
331
339
  export interface ContextSnapshot {
332
340
  history: HistoryEntry[];
333
341
  agreements: unknown[];
334
- agents: unknown[];
335
- users: unknown[];
342
+ agents: SnapshotParticipant[];
343
+ users: SnapshotParticipant[];
336
344
  timelineSummary?: string | null;
337
345
  latestSequence?: string | null;
338
346
  unavailable?: boolean;
@@ -472,10 +480,17 @@ export type Effect = {
472
480
  stateId?: string;
473
481
  /** Replay-stable write keys for this wake — write-capable tools derive their idempotency key from here, never from the LLM. */
474
482
  writeKeys?: DeliveryWriteKeys;
475
- } | {
483
+ }
484
+ /**
485
+ * `receiverId` is optional: in a room with one other participant the backend
486
+ * resolves the recipient from the room itself, which is the only answer
487
+ * available when the wake's sender belongs to a DIFFERENT room.
488
+ * Name a receiver whenever the room holds more than two parties.
489
+ */
490
+ | {
476
491
  kind: 'send-message';
477
492
  sessionId: string;
478
- receiverId: string;
493
+ receiverId?: string;
479
494
  text: string;
480
495
  messageId?: string;
481
496
  } | {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ziggs-ai/agent-sdk",
3
- "version": "0.11.1",
3
+ "version": "0.13.0",
4
4
  "description": "Agent framework SDK for building autonomous agents on the Ziggs platform",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -26,7 +26,7 @@
26
26
  "@anthropic-ai/claude-agent-sdk": "^0.3.204",
27
27
  "@anthropic-ai/sdk": "^0.110.0",
28
28
  "@modelcontextprotocol/sdk": "^1.29.0",
29
- "@ziggs-ai/api-client": "0.10.4",
29
+ "@ziggs-ai/api-client": "0.12.0",
30
30
  "ajv": "^8.17.1",
31
31
  "dotenv": "^17.2.3",
32
32
  "openai": "^4.0.0",
@@ -1,5 +0,0 @@
1
- /**
2
- * exact-match triage for marketplace quests.
3
- * Empty match never hits (token-safe default). Comparison is exact after trim.
4
- */
5
- export declare function questExactMatch(match: string, tags: ReadonlyArray<string | null | undefined>): boolean;