@ziggs-ai/agent-sdk 0.2.0 → 0.2.2

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 (34) hide show
  1. package/dist/AgentHost.d.ts +9 -0
  2. package/dist/AgentHost.js +2 -0
  3. package/dist/adapters/McpAdapter.js +14 -13
  4. package/dist/agent/ClaudeSdkAgent.d.ts +5 -0
  5. package/dist/agent/ClaudeSdkAgent.js +12 -2
  6. package/dist/agent/claudeCognition.d.ts +7 -0
  7. package/dist/agent/claudeCognition.js +38 -7
  8. package/dist/agent/claudeSdkTools.js +17 -2
  9. package/dist/index.d.ts +2 -5
  10. package/dist/index.js +10 -4
  11. package/dist/server/InboxCatchUp.js +11 -10
  12. package/dist/server/tools/index.d.ts +7 -3
  13. package/dist/server/tools/index.js +13 -3
  14. package/dist/server/tools/tier2/artifactTools.d.ts +3 -0
  15. package/dist/server/tools/tier2/artifactTools.js +82 -0
  16. package/dist/server/tools/tier2/chatTools.d.ts +3 -0
  17. package/dist/server/tools/tier2/chatTools.js +37 -0
  18. package/dist/server/tools/tier2/connectionTools.d.ts +1 -0
  19. package/dist/server/tools/tier2/connectionTools.js +52 -5
  20. package/dist/server/tools/tier2/contextTools.d.ts +4 -2
  21. package/dist/server/tools/tier2/contextTools.js +105 -12
  22. package/dist/server/tools/tier2/grantsTools.d.ts +3 -0
  23. package/dist/server/tools/tier2/grantsTools.js +81 -0
  24. package/dist/server/tools/tier2/linkTools.d.ts +7 -0
  25. package/dist/server/tools/tier2/linkTools.js +156 -0
  26. package/dist/server/tools/tier2/paymentTools.d.ts +0 -1
  27. package/dist/server/tools/tier2/paymentTools.js +9 -13
  28. package/package.json +2 -2
  29. package/dist/server/ziggsconnect/ZiggsConnectClient.d.ts +0 -35
  30. package/dist/server/ziggsconnect/ZiggsConnectClient.js +0 -95
  31. package/dist/server/ziggscontext/ZiggsContextClient.d.ts +0 -48
  32. package/dist/server/ziggscontext/ZiggsContextClient.js +0 -90
  33. package/dist/server/ziggspay/ZiggsPayClient.d.ts +0 -82
  34. package/dist/server/ziggspay/ZiggsPayClient.js +0 -188
@@ -75,6 +75,15 @@ export interface AgentHostOptions {
75
75
  cognition?: CognitionKind;
76
76
  /** Max agentic turns per Claude SDK wake. Default 30. */
77
77
  claudeMaxTurns?: number;
78
+ /**
79
+ * ZIG-860: claude-sdk agents resume their transcript across wakes by default,
80
+ * so per-wake input grows monotonically on a long lane. When true, each wake
81
+ * starts a fresh Claude session — the protocol is backend-authoritative (the
82
+ * brain re-reads via ziggs_context_snapshot / the wake event), so the
83
+ * transcript is convenience memory, not source of truth. Ideal for
84
+ * step-driven orchestrators whose every wake already carries what it needs.
85
+ */
86
+ claudeStatelessPerWake?: boolean;
78
87
  services?: AgentServices;
79
88
  workflow?: Workflow;
80
89
  states?: Workflow['states'];
package/dist/AgentHost.js CHANGED
@@ -144,6 +144,7 @@ export class AgentHost {
144
144
  brain: resolved.brain,
145
145
  cognition: resolved.cognition ?? 'fsm',
146
146
  claudeMaxTurns: resolved.claudeMaxTurns,
147
+ claudeStatelessPerWake: resolved.claudeStatelessPerWake,
147
148
  };
148
149
  this.inboxCatchUpEnabled = options.inboxCatchUp !== false;
149
150
  this.sessionResolver = options.sessionResolver || DEFAULT_SESSION_RESOLVER;
@@ -247,6 +248,7 @@ export class AgentHost {
247
248
  // eff({ kind: 'tool-call' }) → the shared executor, which sources those
248
249
  // services from the effect handler's deps like the FSM path (ZIG-775).
249
250
  maxTurns: this.options.claudeMaxTurns,
251
+ statelessPerWake: this.options.claudeStatelessPerWake,
250
252
  })
251
253
  : new Agent({
252
254
  workflow,
@@ -1,6 +1,7 @@
1
1
  import { Client } from '@modelcontextprotocol/sdk/client/index.js';
2
2
  import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
3
3
  import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
4
+ import { ConnectionsClient } from '@ziggs-ai/api-client';
4
5
  import { AgentHost } from '../AgentHost.js';
5
6
  import { thinkingDefaults } from '../runtime/defaults.js';
6
7
  /**
@@ -368,21 +369,21 @@ export async function createMcpAgent(options) {
368
369
  if (!transportConfig.serverUrl)
369
370
  return null;
370
371
  const operatorKey = transportConfig.operatorKey || process.env.ZIGGS_OPERATOR_KEY;
371
- const res = await fetch(`${backendBase}/connections/mcp/requests`, {
372
- method: 'POST',
373
- headers: {
374
- 'content-type': 'application/json',
375
- authorization: `Bearer ${operatorKey}`,
376
- 'x-agent-id': agentId,
377
- 'x-on-behalf-of-user': userId,
378
- },
379
- body: JSON.stringify({
372
+ if (!operatorKey)
373
+ return null;
374
+ try {
375
+ const body = await new ConnectionsClient(operatorKey, agentId, backendBase).requestMcpConnection({
380
376
  serverUrl: transportConfig.serverUrl,
381
377
  tools: tools.map(t => t.name),
382
- }),
383
- });
384
- const body = (await res.json().catch(() => null));
385
- return body?.request?.requestId ?? null;
378
+ onBehalfOfUserId: userId,
379
+ });
380
+ return (body?.request?.requestId) ?? null;
381
+ }
382
+ catch {
383
+ // Best-effort: the backend dedups pending requests and the caller falls
384
+ // back to a plain Connections-page link when there's no requestId.
385
+ return null;
386
+ }
386
387
  };
387
388
  const caller = async (name, args, context) => {
388
389
  const userId = typeof context.senderId === 'string' ? context.senderId : undefined;
@@ -13,6 +13,10 @@ export interface ClaudeSdkAgentOptions {
13
13
  operatorKey: string;
14
14
  agentId: string;
15
15
  maxTurns?: number;
16
+ /** ZIG-860: when true, never resume the prior Claude session — each wake is a
17
+ * fresh transcript. Keeps per-wake input flat on step-driven orchestrators
18
+ * whose wakes already carry their state. */
19
+ statelessPerWake?: boolean;
16
20
  /** Injectable for tests. Defaults to @anthropic-ai/claude-agent-sdk query(). */
17
21
  queryFn?: typeof query;
18
22
  }
@@ -32,6 +36,7 @@ export declare class ClaudeSdkAgent {
32
36
  private operatorKey;
33
37
  private agentId;
34
38
  private maxTurns;
39
+ private statelessPerWake;
35
40
  private queryFn;
36
41
  constructor(opts: ClaudeSdkAgentOptions);
37
42
  tick({ event, ctx, state, identity, eff }: TickInput): Promise<TickOutput>;
@@ -141,6 +141,7 @@ export class ClaudeSdkAgent {
141
141
  operatorKey;
142
142
  agentId;
143
143
  maxTurns;
144
+ statelessPerWake;
144
145
  queryFn;
145
146
  constructor(opts) {
146
147
  if (!opts.workflow?.states)
@@ -158,6 +159,7 @@ export class ClaudeSdkAgent {
158
159
  this.operatorKey = opts.operatorKey;
159
160
  this.agentId = opts.agentId;
160
161
  this.maxTurns = opts.maxTurns ?? DEFAULT_MAX_TURNS;
162
+ this.statelessPerWake = opts.statelessPerWake ?? false;
161
163
  this.queryFn = opts.queryFn ?? query;
162
164
  }
163
165
  async tick({ event, ctx, state, identity, eff }) {
@@ -248,7 +250,10 @@ export class ClaudeSdkAgent {
248
250
  if (line)
249
251
  runtimeLog.warn('ClaudeSdkAgent', `stderr session=${identity.sessionId}: ${line}`);
250
252
  },
251
- resume: nextCtx.claudeSessionId,
253
+ // ZIG-860: stateless-per-wake never resumes — each wake is a fresh
254
+ // transcript, so per-wake input stays flat instead of replaying the
255
+ // whole lane history every turn.
256
+ resume: this.statelessPerWake ? undefined : nextCtx.claudeSessionId,
252
257
  });
253
258
  let metrics = { llmCalls: 0, tokens: { total: 0, input: 0, output: 0, cacheRead: 0, cacheCreation: 0 } };
254
259
  let sessionId = nextCtx.claudeSessionId;
@@ -298,7 +303,12 @@ export class ClaudeSdkAgent {
298
303
  runtimeLog.warn('ClaudeSdkAgent', `query failed session=${identity.sessionId}: ${err.message}`);
299
304
  throw err;
300
305
  }
301
- if (sessionId)
306
+ // Stateless-per-wake keeps no session id — clear any carried over from the
307
+ // prior ctx so the next wake starts fresh too (ZIG-860). Otherwise remember
308
+ // this wake's session for resume.
309
+ if (this.statelessPerWake)
310
+ nextCtx.claudeSessionId = undefined;
311
+ else if (sessionId)
302
312
  nextCtx.claudeSessionId = sessionId;
303
313
  // ZIG-816: bound resumed-transcript growth. `metrics.tokens.input` is the
304
314
  // wake's billable input off the result message (dominated by the resumed
@@ -63,5 +63,12 @@ export declare function summarizeContext(snap: Record<string, unknown> | null |
63
63
  * Turn a Ziggs wake event + context digest into the user prompt that starts a
64
64
  * Claude-brain turn. The agent's standing instructions come from its system
65
65
  * prompt (protocol + role); this is just "here's what woke you, go handle it."
66
+ *
67
+ * ZIG-880: EventQueue coalesces events that arrive while the lane is busy into
68
+ * a `{type:'batch', events:[...]}` frame. The FSM path unwraps those
69
+ * (PromptBuilder), but this path used to render just "Event type: batch" —
70
+ * every payload inside (including task results) was invisible to the model,
71
+ * stalling delegation pipelines whose notifications share one chat lane.
72
+ * Render each batched event in order instead.
66
73
  */
67
74
  export declare function buildClaudeEventPrompt(event: RawEvent | undefined, contextSummary: string): string;
@@ -1,3 +1,4 @@
1
+ import { getBatchEvents } from '../context/batch.js';
1
2
  const DEFAULT_CLAUDE_MODEL = 'claude-sonnet-4-6';
2
3
  /**
3
4
  * Pick the Claude model for a Claude-brain wake. The brain must run a `claude-*`
@@ -67,13 +68,8 @@ export function summarizeContext(snap) {
67
68
  const users = Array.isArray(snap.users) ? snap.users.length : 0;
68
69
  return `history=${history} messages, agreements=${agreements}, users=${users}`;
69
70
  }
70
- /**
71
- * Turn a Ziggs wake event + context digest into the user prompt that starts a
72
- * Claude-brain turn. The agent's standing instructions come from its system
73
- * prompt (protocol + role); this is just "here's what woke you, go handle it."
74
- */
75
- export function buildClaudeEventPrompt(event, contextSummary) {
76
- const lines = ['You received a new Ziggs event. Handle it per your protocol.'];
71
+ /** Render ONE event's fields into prompt lines (shared by single and batch frames). */
72
+ function renderEventLines(event, lines) {
77
73
  if (event?.type)
78
74
  lines.push(`Event type: ${event.type}`);
79
75
  if (event?.text)
@@ -94,6 +90,41 @@ export function buildClaudeEventPrompt(event, contextSummary) {
94
90
  lines.push(`Agreement: ${result.agreementId}`);
95
91
  if (typeof result.state === 'string')
96
92
  lines.push(`Task state: ${result.state}`);
93
+ // ZIG-860: surface the task's RESULT payload too. Completion notifies now
94
+ // pin it (backend), and an orchestrator consuming a worker's output has no
95
+ // task-read tool — without this line the step outcome never reaches the
96
+ // model and a chained pipeline stalls right after its first task.
97
+ if (result.result != null) {
98
+ const payload = typeof result.result === 'string' ? result.result : JSON.stringify(result.result);
99
+ const MAX = 12_000;
100
+ lines.push(`Task result: ${payload.length > MAX ? `${payload.slice(0, MAX)} …[truncated ${payload.length - MAX} chars]` : payload}`);
101
+ }
102
+ }
103
+ }
104
+ /**
105
+ * Turn a Ziggs wake event + context digest into the user prompt that starts a
106
+ * Claude-brain turn. The agent's standing instructions come from its system
107
+ * prompt (protocol + role); this is just "here's what woke you, go handle it."
108
+ *
109
+ * ZIG-880: EventQueue coalesces events that arrive while the lane is busy into
110
+ * a `{type:'batch', events:[...]}` frame. The FSM path unwraps those
111
+ * (PromptBuilder), but this path used to render just "Event type: batch" —
112
+ * every payload inside (including task results) was invisible to the model,
113
+ * stalling delegation pipelines whose notifications share one chat lane.
114
+ * Render each batched event in order instead.
115
+ */
116
+ export function buildClaudeEventPrompt(event, contextSummary) {
117
+ const lines = ['You received a new Ziggs event. Handle it per your protocol.'];
118
+ const batched = getBatchEvents(event);
119
+ if (event && batched.length > 1) {
120
+ lines.push(`This wake carries ${batched.length} coalesced events (oldest first). Handle them together — act once per distinct piece of work, not once per notification.`);
121
+ batched.forEach((e, i) => {
122
+ lines.push('', `— Event ${i + 1} of ${batched.length} —`);
123
+ renderEventLines(e, lines);
124
+ });
125
+ }
126
+ else {
127
+ renderEventLines(event, lines);
97
128
  }
98
129
  if (contextSummary) {
99
130
  lines.push('', 'Session context snapshot:', contextSummary);
@@ -1,10 +1,17 @@
1
1
  import { randomUUID } from 'node:crypto';
2
2
  import { defineTool } from '../tools/defineTool.js';
3
+ import { listGrantsTool } from '../server/tools/tier2/grantsTools.js';
4
+ // ZIG-860: default message-history cap. An uncapped snapshot returns the whole
5
+ // chat — in a delegation chat that accumulates notification rows, every wake's
6
+ // snapshot ballooned (observed 35–47k input tokens/wake, ~205k/run). 12 recent
7
+ // messages cover "what woke me and what was just said"; the model can pass a
8
+ // larger maxMessages explicitly when it genuinely needs deeper history.
9
+ const DEFAULT_SNAPSHOT_MAX_MESSAGES = 12;
3
10
  // Named distinctly from ziggs-mcp's per-type `ziggs_read_context`: this is the
4
11
  // one-shot aggregate snapshot (grant-fenced server-side), not a cursored read.
5
12
  export const ziggsContextSnapshotTool = defineTool('ziggs_context_snapshot', {
6
13
  chatId: { type: 'string', description: 'Chat/session id. Defaults to the current lane session.' },
7
- maxMessages: { type: 'number', description: 'Optional message history cap.' },
14
+ maxMessages: { type: 'number', description: `Message history cap (default ${DEFAULT_SNAPSHOT_MAX_MESSAGES}). Pass a larger value when you need deeper history.` },
8
15
  }, async (args, ctx) => {
9
16
  const c = ctx;
10
17
  if (!c.eff)
@@ -15,7 +22,9 @@ export const ziggsContextSnapshotTool = defineTool('ziggs_context_snapshot', {
15
22
  return c.eff({
16
23
  kind: 'read-context',
17
24
  sessionId,
18
- opts: args.maxMessages != null ? { maxMessages: Number(args.maxMessages) } : undefined,
25
+ opts: {
26
+ maxMessages: args.maxMessages != null ? Number(args.maxMessages) : DEFAULT_SNAPSHOT_MAX_MESSAGES,
27
+ },
19
28
  });
20
29
  }, { description: 'Read the chat snapshot: history, agreements, agents, and users for a session.' });
21
30
  export const ziggsSendMessageTool = defineTool('ziggs_send_message', {
@@ -51,7 +60,13 @@ export const ziggsSendMessageTool = defineTool('ziggs_send_message', {
51
60
  // Inbox read/ack are deliberately NOT here: the host owns the pull/watermark
52
61
  // cycle (InboxCatchUp on connect). A brain-side ack raced the host's and the
53
62
  // mandatory per-wake inbox poll fed the tool-operation echo loop (ZIG-759).
63
+ //
64
+ // list_grants IS here (ZIG-893): the hosted agent must always be able to answer
65
+ // "what grants of mine do you hold?" across every rail — the 2026-07-20 incident
66
+ // was a hosted agent with no grant-read tool telling its principal that lookup
67
+ // doesn't exist. It reads operatorKey/agentId straight off the tool context.
54
68
  export const CLAUDE_SDK_TOOLS = [
55
69
  ziggsContextSnapshotTool,
56
70
  ziggsSendMessageTool,
71
+ listGrantsTool,
57
72
  ];
package/dist/index.d.ts CHANGED
@@ -51,11 +51,8 @@ export { OpenAIAdapter } from './adapters/OpenAIAdapter.js';
51
51
  export { extractJSON, safeParseJSON } from './utils/jsonExtractor.js';
52
52
  export { HistoryFormatter, historyFormatter, AgreementFormatter, agreementFormatter } from './formatters/index.js';
53
53
  export { ProactiveTrigger } from './server/proactive/ProactiveTrigger.js';
54
- export { ZiggsPayClient, createZiggsPayClient } from './server/ziggspay/ZiggsPayClient.js';
55
- export { ZiggsConnectClient, createZiggsConnectClient } from './server/ziggsconnect/ZiggsConnectClient.js';
56
- export { ZiggsContextClient } from './server/ziggscontext/ZiggsContextClient.js';
57
- export type { ZiggsContextClientOptions, ContextReachDescriptor, DelegateContextGrantParams, } from './server/ziggscontext/ZiggsContextClient.js';
58
- export { PROTOCOL_TOOLS, agreementProposeTool, agreementSubcontractTool, agreementRespondTool, agreementCounterProposalTool, agreementCheckProposalTool, taskSpawnTool, taskUpdateTool, taskUpdatePlanStepTool, dispatchProtocolOp, PAYMENT_TOOLS, paymentBalanceTool, paymentTransferTool, paymentHoldTool, paymentReleaseTool, paymentResolveWalletTool, CONNECTION_TOOLS, connectionProxyTool, connectionListGrantsTool, DISCOVERY_TOOLS, agentSearchTool, agentGetTool, MARKETPLACE_TOOLS, marketplaceViewTool, } from './server/tools/index.js';
54
+ export { PROTOCOL_TOOLS, agreementProposeTool, agreementSubcontractTool, agreementRespondTool, agreementCounterProposalTool, agreementCheckProposalTool, taskSpawnTool, taskUpdateTool, taskUpdatePlanStepTool, dispatchProtocolOp, PAYMENT_TOOLS, paymentBalanceTool, paymentTransferTool, paymentHoldTool, paymentReleaseTool, paymentResolveWalletTool, CONNECTION_TOOLS, connectionProxyTool, connectionListGrantsTool, requestConnectionTool, DISCOVERY_TOOLS, agentSearchTool, agentGetTool, MARKETPLACE_TOOLS, marketplaceViewTool, CONTEXT_TOOLS, contextReadTool, contextDelegateTool, contextExpandReachTool, contextDiscoverGrantableTool, GRANTS_TOOLS, listGrantsTool, LINK_TOOLS, requestLinkTool, createLinkInviteTool, claimLinkInviteTool, listLinksTool, revokeLinkTool, ARTIFACT_TOOLS, recordArtifactTool, CHAT_TOOLS, openConversationTool, } from './server/tools/index.js';
55
+ export { CLAUDE_SDK_TOOLS } from './agent/claudeSdkTools.js';
59
56
  export { PROTOCOL_TOOL_NAMES, PROTOCOL_TOOL_TO_OPERATION, mapProtocolToolToOperation, isProtocolToolName } from './tasks/protocolRegistry.js';
60
57
  export { InMemoryStore, FileMemoryStore } from './memory/MemoryStore.js';
61
58
  export type { MemoryStore } from './memory/MemoryStore.js';
package/dist/index.js CHANGED
@@ -44,16 +44,22 @@ export { OpenAIAdapter } from './adapters/OpenAIAdapter.js';
44
44
  export { extractJSON, safeParseJSON } from './utils/jsonExtractor.js';
45
45
  export { HistoryFormatter, historyFormatter, AgreementFormatter, agreementFormatter } from './formatters/index.js';
46
46
  export { ProactiveTrigger } from './server/proactive/ProactiveTrigger.js';
47
- export { ZiggsPayClient, createZiggsPayClient } from './server/ziggspay/ZiggsPayClient.js';
48
- export { ZiggsConnectClient, createZiggsConnectClient } from './server/ziggsconnect/ZiggsConnectClient.js';
49
- export { ZiggsContextClient } from './server/ziggscontext/ZiggsContextClient.js';
47
+ // ZIG-894 — the hand-rolled ZiggsPayClient / ZiggsConnectClient were deleted;
48
+ // payment and connection tools now use @ziggs-ai/api-client's PaymentsClient /
49
+ // ConnectionsClient, the same clients ziggs-mcp uses.
50
+ // ZIG-869 — the hand-rolled ZiggsContextClient was deleted; context tools now
51
+ // use the same @ziggs-ai/api-client clients as ziggs-mcp (GrantsClient /
52
+ // ContextGrantsClient / ContextReadClient). Grant/reach types live there.
50
53
  // All agent tool bundles live under server/tools/ (tier1 = protocol grammar,
51
54
  // tier2 = opt-in HTTP capability bundles). Single home: server/tools/index.ts.
52
55
  export {
53
56
  // Tier 1 — protocol grammar (framework-managed via taskTools).
54
57
  PROTOCOL_TOOLS, agreementProposeTool, agreementSubcontractTool, agreementRespondTool, agreementCounterProposalTool, agreementCheckProposalTool, taskSpawnTool, taskUpdateTool, taskUpdatePlanStepTool, dispatchProtocolOp,
55
58
  // Tier 2 — opt-in capability bundles (spread into `tools:`).
56
- PAYMENT_TOOLS, paymentBalanceTool, paymentTransferTool, paymentHoldTool, paymentReleaseTool, paymentResolveWalletTool, CONNECTION_TOOLS, connectionProxyTool, connectionListGrantsTool, DISCOVERY_TOOLS, agentSearchTool, agentGetTool, MARKETPLACE_TOOLS, marketplaceViewTool, } from './server/tools/index.js';
59
+ PAYMENT_TOOLS, paymentBalanceTool, paymentTransferTool, paymentHoldTool, paymentReleaseTool, paymentResolveWalletTool, CONNECTION_TOOLS, connectionProxyTool, connectionListGrantsTool, requestConnectionTool, DISCOVERY_TOOLS, agentSearchTool, agentGetTool, MARKETPLACE_TOOLS, marketplaceViewTool, CONTEXT_TOOLS, contextReadTool, contextDelegateTool, contextExpandReachTool, contextDiscoverGrantableTool, GRANTS_TOOLS, listGrantsTool, LINK_TOOLS, requestLinkTool, createLinkInviteTool, claimLinkInviteTool, listLinksTool, revokeLinkTool, ARTIFACT_TOOLS, recordArtifactTool, CHAT_TOOLS, openConversationTool, } from './server/tools/index.js';
60
+ // The always-on toolset for the hosted Claude cognition (ZIG-903 parity guard
61
+ // reads this as part of the real SDK registry).
62
+ export { CLAUDE_SDK_TOOLS } from './agent/claudeSdkTools.js';
57
63
  export { PROTOCOL_TOOL_NAMES, PROTOCOL_TOOL_TO_OPERATION, mapProtocolToolToOperation, isProtocolToolName } from './tasks/protocolRegistry.js';
58
64
  export { InMemoryStore, FileMemoryStore } from './memory/MemoryStore.js';
59
65
  export { classifyIncomingEvent } from './context/classifyEnvelope.js';
@@ -1,4 +1,4 @@
1
- import { ContextReadClient, ContextDiscoveryClient, InboxClient, getChatsForAgreement, getAgreementStatus, } from '@ziggs-ai/api-client';
1
+ import { ContextReadClient, GrantsClient, InboxClient, getChatsForAgreement, getAgreementStatus, } from '@ziggs-ai/api-client';
2
2
  import { runtimeLog } from '../shared/runtimeLog.js';
3
3
  const READ_PAGE = 100;
4
4
  function log(label, msg) {
@@ -47,7 +47,7 @@ function rowToMetadata(row, chatId) {
47
47
  text: row.text,
48
48
  };
49
49
  }
50
- async function chatIdsForScope(scope, creds, baseUrl, discovery) {
50
+ async function chatIdsForScope(scope, creds, baseUrl, grants) {
51
51
  const { kind, id } = scope.scope;
52
52
  switch (kind) {
53
53
  case 'chat':
@@ -57,12 +57,13 @@ async function chatIdsForScope(scope, creds, baseUrl, discovery) {
57
57
  return [...new Set(links.map((l) => l.chatId).filter(Boolean))];
58
58
  }
59
59
  case 'org': {
60
- const reach = await discovery.discover();
61
- return [
62
- ...new Set(reach
63
- .filter((r) => r.scope.kind === 'chat')
64
- .map((r) => r.scope.id)),
65
- ];
60
+ // All pages of live chat grants — an org-scope catch-up must not miss
61
+ // chats hidden behind pagination (GET /grants defaults to one page).
62
+ const items = await grants.listAllGrants({
63
+ scopeKind: 'chat',
64
+ health: 'active',
65
+ });
66
+ return [...new Set(items.map((g) => g.scope.id))];
66
67
  }
67
68
  default:
68
69
  return [];
@@ -120,7 +121,7 @@ export async function runInboxCatchUp(deps) {
120
121
  const label = deps.label ?? 'InboxCatchUp';
121
122
  const inbox = new InboxClient(deps.creds.operatorKey, deps.creds.agentId, deps.baseUrl);
122
123
  const reader = new ContextReadClient(deps.creds.operatorKey, deps.creds.agentId, deps.baseUrl);
123
- const discovery = new ContextDiscoveryClient(deps.creds.operatorKey, deps.creds.agentId, deps.baseUrl);
124
+ const grants = new GrantsClient(deps.creds.operatorKey, deps.creds.agentId, deps.baseUrl);
124
125
  const envelope = await inbox.getInbox();
125
126
  const countCap = envelope.countCap;
126
127
  let messagesDelivered = 0;
@@ -154,7 +155,7 @@ export async function runInboxCatchUp(deps) {
154
155
  log(label, `scope agreement:${scope.scope.id} terminal — acked, not re-delivered`);
155
156
  continue;
156
157
  }
157
- const chatIds = await chatIdsForScope(scope, deps.creds, deps.baseUrl, discovery);
158
+ const chatIds = await chatIdsForScope(scope, deps.creds, deps.baseUrl, grants);
158
159
  if (!chatIds.length && (scope.newMessages > 0 || scope.newArtifacts > 0)) {
159
160
  warn(label, `no chat ids for scope ${scope.scope.kind}:${scope.scope.id} (${scope.newMessages} msgs)`);
160
161
  }
@@ -19,6 +19,10 @@ export { PROTOCOL_TOOLS, agreementProposeTool, agreementSubcontractTool, agreeme
19
19
  export { dispatchProtocolOp } from './tier1/protocolRunner.js';
20
20
  export { DISCOVERY_TOOLS, agentSearchTool, agentGetTool } from './tier2/discoveryTools.js';
21
21
  export { MARKETPLACE_TOOLS, marketplaceViewTool } from './tier2/marketplaceTools.js';
22
- export { PAYMENT_TOOLS, paymentBalanceTool, paymentTransferTool, paymentWaitForApprovalTool, paymentHoldTool, paymentReleaseTool, paymentResolveWalletTool, paymentIssueGrantTool, paymentAttenuateGrantTool, paymentRevokeGrantTool, paymentListGrantsTool, } from './tier2/paymentTools.js';
23
- export { CONNECTION_TOOLS, connectionProxyTool, connectionListGrantsTool, } from './tier2/connectionTools.js';
24
- export { CONTEXT_TOOLS, contextDiscoverTool, contextDelegateTool, } from './tier2/contextTools.js';
22
+ export { PAYMENT_TOOLS, paymentBalanceTool, paymentTransferTool, paymentWaitForApprovalTool, paymentHoldTool, paymentReleaseTool, paymentResolveWalletTool, paymentIssueGrantTool, paymentAttenuateGrantTool, paymentRevokeGrantTool, } from './tier2/paymentTools.js';
23
+ export { CONNECTION_TOOLS, connectionProxyTool, connectionListGrantsTool, requestConnectionTool, } from './tier2/connectionTools.js';
24
+ export { CONTEXT_TOOLS, contextReadTool, contextDelegateTool, contextExpandReachTool, contextDiscoverGrantableTool, } from './tier2/contextTools.js';
25
+ export { GRANTS_TOOLS, listGrantsTool } from './tier2/grantsTools.js';
26
+ export { LINK_TOOLS, requestLinkTool, createLinkInviteTool, claimLinkInviteTool, listLinksTool, revokeLinkTool, } from './tier2/linkTools.js';
27
+ export { ARTIFACT_TOOLS, recordArtifactTool } from './tier2/artifactTools.js';
28
+ export { CHAT_TOOLS, openConversationTool } from './tier2/chatTools.js';
@@ -21,6 +21,16 @@ export { dispatchProtocolOp } from './tier1/protocolRunner.js';
21
21
  // ── Tier 2: capability bundles ──────────────────────────────────────────────
22
22
  export { DISCOVERY_TOOLS, agentSearchTool, agentGetTool } from './tier2/discoveryTools.js';
23
23
  export { MARKETPLACE_TOOLS, marketplaceViewTool } from './tier2/marketplaceTools.js';
24
- export { PAYMENT_TOOLS, paymentBalanceTool, paymentTransferTool, paymentWaitForApprovalTool, paymentHoldTool, paymentReleaseTool, paymentResolveWalletTool, paymentIssueGrantTool, paymentAttenuateGrantTool, paymentRevokeGrantTool, paymentListGrantsTool, } from './tier2/paymentTools.js';
25
- export { CONNECTION_TOOLS, connectionProxyTool, connectionListGrantsTool, } from './tier2/connectionTools.js';
26
- export { CONTEXT_TOOLS, contextDiscoverTool, contextDelegateTool, } from './tier2/contextTools.js';
24
+ export { PAYMENT_TOOLS, paymentBalanceTool, paymentTransferTool, paymentWaitForApprovalTool, paymentHoldTool, paymentReleaseTool, paymentResolveWalletTool, paymentIssueGrantTool, paymentAttenuateGrantTool, paymentRevokeGrantTool, } from './tier2/paymentTools.js';
25
+ export { CONNECTION_TOOLS, connectionProxyTool, connectionListGrantsTool, requestConnectionTool, } from './tier2/connectionTools.js';
26
+ export { CONTEXT_TOOLS, contextReadTool, contextDelegateTool, contextExpandReachTool, contextDiscoverGrantableTool, } from './tier2/contextTools.js';
27
+ // ZIG-893: one grant lister across every rail (retires context_discover +
28
+ // payment_list_grants). Always-available on the hosted agent — see CLAUDE_SDK_TOOLS.
29
+ export { GRANTS_TOOLS, listGrantsTool } from './tier2/grantsTools.js';
30
+ // ZIG-898: links (agreements with engagementKind 'link') — cross-org reach.
31
+ export { LINK_TOOLS, requestLinkTool, createLinkInviteTool, claimLinkInviteTool, listLinksTool, revokeLinkTool, } from './tier2/linkTools.js';
32
+ // ZIG-899: deliberate artifact recording (the model decides "record this").
33
+ export { ARTIFACT_TOOLS, recordArtifactTool } from './tier2/artifactTools.js';
34
+ // ZIG-900 decision: open_conversation only — chat listing stays a context-rail
35
+ // read (list_grants scopeKind=chat), no duplicate lister (ZIG-626 rule).
36
+ export { CHAT_TOOLS, openConversationTool } from './tier2/chatTools.js';
@@ -0,0 +1,3 @@
1
+ import { type ToolDefinition } from '../../../tools/defineTool.js';
2
+ export declare const recordArtifactTool: ToolDefinition;
3
+ export declare const ARTIFACT_TOOLS: ToolDefinition[];
@@ -0,0 +1,82 @@
1
+ import { defineTool } from '../../../tools/defineTool.js';
2
+ import { ArtifactsClient } from '@ziggs-ai/api-client';
3
+ /**
4
+ * ZIG-899 — deliberate artifact recording on the SDK surface, same base name
5
+ * as ziggs-mcp's ziggs_record_artifact. Until now artifacts were written only
6
+ * as a host-side effect (recordReport / record-event); the model could not
7
+ * decide "this is a deliverable, record it" — which is exactly what the
8
+ * delegate protocol tells agents to do for heavy results. Rides
9
+ * ArtifactsClient.writeStrict (fail loudly, return the artifactId) — the same
10
+ * wire call as the MCP tool.
11
+ */
12
+ function creds(ctx) {
13
+ const operatorKey = ctx?.['operatorKey'];
14
+ const agentId = ctx?.['agentId'];
15
+ if (!operatorKey)
16
+ throw new Error('operatorKey missing from tool context');
17
+ if (!agentId)
18
+ throw new Error('agentId missing from tool context');
19
+ return { operatorKey, agentId };
20
+ }
21
+ /**
22
+ * ZIG-560 teaching carried over from the MCP tool: name the result slot on the
23
+ * success path so an agent finds the right move unaided — reworded to the SDK
24
+ * task grammar (task_update carries the terminal result on this surface).
25
+ */
26
+ function reportingHint(contentType, taskId) {
27
+ if (contentType === 'result') {
28
+ return taskId
29
+ ? 'Recorded as a task-bound result artifact. Close the task by setting its terminal result with task_update.'
30
+ : 'Recorded as a result artifact, but not bound to a task — pass taskId to bind it, then close the task with task_update.';
31
+ }
32
+ return 'Reporting finished work? Record it with content_type=result bound to the task (taskId), then close the task with task_update — chat messages are conversation only.';
33
+ }
34
+ export const recordArtifactTool = defineTool('record_artifact', {
35
+ text: { type: 'string', required: true, description: 'Artifact body' },
36
+ visibility: {
37
+ type: 'string',
38
+ required: true,
39
+ enum: ['chat', 'agent-private'],
40
+ description: 'chat = visible to scope parties; agent-private = your eyes only',
41
+ },
42
+ chatId: { type: 'string', description: 'Target chat (xor agreementId)' },
43
+ agreementId: { type: 'string', description: 'Target agreement (xor chatId)' },
44
+ taskId: {
45
+ type: 'string',
46
+ description: 'Optional task — creates a TaskArtifactLink alongside the primary scope link',
47
+ },
48
+ content_type: { type: 'string', description: 'Default text; use result for a finished deliverable' },
49
+ }, async (args, ctx) => {
50
+ const chatId = args['chatId'];
51
+ const agreementId = args['agreementId'];
52
+ if ((chatId && agreementId) || (!chatId && !agreementId)) {
53
+ throw new Error('Pass exactly one of chatId or agreementId');
54
+ }
55
+ const visibility = args['visibility'];
56
+ if (visibility !== 'chat' && visibility !== 'agent-private') {
57
+ throw new Error('visibility must be chat or agent-private');
58
+ }
59
+ const contentType = args['content_type'];
60
+ const taskId = args['taskId'];
61
+ const { operatorKey, agentId } = creds(ctx);
62
+ const { artifactId } = await new ArtifactsClient(operatorKey, agentId).writeStrict({
63
+ text: args['text'],
64
+ visibility,
65
+ chatId,
66
+ agreementId,
67
+ taskId,
68
+ content_type: contentType,
69
+ });
70
+ return {
71
+ ok: true,
72
+ artifactId,
73
+ visibility,
74
+ chatId,
75
+ agreementId,
76
+ taskId,
77
+ reportingHint: reportingHint(contentType, taskId),
78
+ };
79
+ }, {
80
+ description: 'Write an artifact to a chat or agreement scope. Set visibility explicitly. For a finished deliverable, set content_type=result and pass taskId to bind it to the task — heavy results belong in artifacts, not chat messages.',
81
+ });
82
+ export const ARTIFACT_TOOLS = [recordArtifactTool];
@@ -0,0 +1,3 @@
1
+ import { type ToolDefinition } from '../../../tools/defineTool.js';
2
+ export declare const openConversationTool: ToolDefinition;
3
+ export declare const CHAT_TOOLS: ToolDefinition[];
@@ -0,0 +1,37 @@
1
+ import { defineTool } from '../../../tools/defineTool.js';
2
+ import { openConversation } from '@ziggs-ai/api-client';
3
+ /**
4
+ * ZIG-900 — the decided chat surface for hosted agents: open_conversation
5
+ * only, same base name as MCP's ziggs_open_conversation. There is deliberately
6
+ * NO chat-listing tool on the SDK: agents read context only via held grants
7
+ * (ZIG-425) and chat membership auto-mints a chat grant (ZIG-428/426), so
8
+ * "what chats can I see" is already answered by list_grants scopeKind=chat —
9
+ * adding a lister would duplicate a context-family read (ZIG-626 rule).
10
+ */
11
+ function creds(ctx) {
12
+ const operatorKey = ctx?.['operatorKey'];
13
+ const agentId = ctx?.['agentId'];
14
+ if (!operatorKey)
15
+ throw new Error('operatorKey missing from tool context');
16
+ if (!agentId)
17
+ throw new Error('agentId missing from tool context');
18
+ return { operatorKey, agentId };
19
+ }
20
+ export const openConversationTool = defineTool('open_conversation', {
21
+ participantId: {
22
+ type: 'string',
23
+ required: true,
24
+ description: 'User or agent id to converse with (from agent_search — do not guess ids)',
25
+ },
26
+ }, async (args, ctx) => {
27
+ if (!args['participantId'])
28
+ throw new Error('participantId is required');
29
+ const { chatId } = await openConversation(args['participantId'], creds(ctx));
30
+ return {
31
+ chatId,
32
+ note: 'Conversation is open (or reused). Your membership auto-mints a chat grant, so the chat also appears in list_grants scopeKind=chat for context reads.',
33
+ };
34
+ }, {
35
+ description: 'Open or reuse a chat with a user or agent participant. To reach an agent in ANOTHER org, establish a link first — request_link (if you have its agent id) or create_link_invite (if you do not), approved/claimed — otherwise this fails with AGENT_NOT_PUBLISHED. To list chats you can already read, use list_grants scopeKind=chat.',
36
+ });
37
+ export const CHAT_TOOLS = [openConversationTool];
@@ -1,4 +1,5 @@
1
1
  import { type ToolDefinition } from '../../../tools/defineTool.js';
2
2
  export declare const connectionProxyTool: ToolDefinition;
3
3
  export declare const connectionListGrantsTool: ToolDefinition;
4
+ export declare const requestConnectionTool: ToolDefinition;
4
5
  export declare const CONNECTION_TOOLS: ToolDefinition[];
@@ -1,13 +1,13 @@
1
1
  import { defineTool } from '../../../tools/defineTool.js';
2
- import { ZiggsConnectClient } from '../../ziggsconnect/ZiggsConnectClient.js';
2
+ import { ConnectionsClient } from '@ziggs-ai/api-client';
3
3
  function clientFor(ctx) {
4
4
  const operatorKey = ctx?.['operatorKey'];
5
5
  if (!operatorKey)
6
6
  throw new Error('operatorKey missing from tool context');
7
- return new ZiggsConnectClient({
8
- operatorKey,
9
- actAsAgentId: ctx?.['agentId'] || undefined,
10
- });
7
+ // BACKEND_URL / ZIGGS_BACKEND_URL are the SDK runner's overrides; the shared
8
+ // client defaults to HTTP_URL (api-client convention) when neither is set.
9
+ const baseUrl = process.env.BACKEND_URL || process.env.ZIGGS_BACKEND_URL || undefined;
10
+ return new ConnectionsClient(operatorKey, ctx?.['agentId'] || undefined, baseUrl);
11
11
  }
12
12
  function rethrowWithContext(error, prefix) {
13
13
  const e = error;
@@ -57,7 +57,54 @@ export const connectionListGrantsTool = defineTool('connection_list_grants', { c
57
57
  rethrowWithContext(e, 'Failed to list connection grants');
58
58
  }
59
59
  });
60
+ export const requestConnectionTool = defineTool('request_connection', {
61
+ chatId: {
62
+ type: 'string',
63
+ required: true,
64
+ description: 'The chat you are working in — the consent card is opened there',
65
+ },
66
+ serverUrl: { type: 'string', required: true, description: 'Remote MCP server URL (https)' },
67
+ tools: {
68
+ type: 'array',
69
+ items: { type: 'string' },
70
+ required: true,
71
+ description: "Tool names you want — become the grant's allowed_actions caveats",
72
+ },
73
+ reason: {
74
+ type: 'string',
75
+ description: 'Plain-language reason shown to the human deciding',
76
+ },
77
+ }, async (args, ctx) => {
78
+ if (!args['chatId'])
79
+ throw new Error('chatId is required');
80
+ if (!args['serverUrl'])
81
+ throw new Error('serverUrl is required');
82
+ const tools = args['tools'];
83
+ if (!Array.isArray(tools) || tools.length === 0) {
84
+ throw new Error('tools must be a non-empty array of tool names');
85
+ }
86
+ try {
87
+ const result = await clientFor(ctx).requestMcpConnection({
88
+ chatId: args['chatId'],
89
+ serverUrl: args['serverUrl'],
90
+ tools: tools,
91
+ reason: args['reason'],
92
+ });
93
+ return {
94
+ ok: true,
95
+ ...result,
96
+ note: 'A connection-consent card is now in the chat awaiting your principal — they approve it right there (ZIG-798 flow). ' +
97
+ 'Once approved, the connection + grant appear in list_grants (scopeKind=connection) / connection_list_grants for connection_proxy.',
98
+ };
99
+ }
100
+ catch (e) {
101
+ rethrowWithContext(e, 'Connection request failed');
102
+ }
103
+ }, {
104
+ description: 'Ask your principal (the human) to connect a remote MCP server and grant you the listed tools. Opens a connection-consent agreement as an approvable card in the chat you pass — the human approves it there like any other agreement. On approval the server is connected (browser OAuth if needed) and you are granted the tools; use them via connection_proxy.',
105
+ });
60
106
  export const CONNECTION_TOOLS = [
61
107
  connectionProxyTool,
62
108
  connectionListGrantsTool,
109
+ requestConnectionTool,
63
110
  ];
@@ -1,4 +1,6 @@
1
1
  import { type ToolDefinition } from '../../../tools/defineTool.js';
2
- export declare const contextDiscoverTool: ToolDefinition;
2
+ export declare const contextReadTool: ToolDefinition;
3
3
  export declare const contextDelegateTool: ToolDefinition;
4
- export declare const CONTEXT_TOOLS: readonly [ToolDefinition, ToolDefinition];
4
+ export declare const contextExpandReachTool: ToolDefinition;
5
+ export declare const contextDiscoverGrantableTool: ToolDefinition;
6
+ export declare const CONTEXT_TOOLS: readonly [ToolDefinition, ToolDefinition, ToolDefinition, ToolDefinition];