@ziggs-ai/api-client 0.4.0 → 0.6.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 (37) hide show
  1. package/dist/ConnectionManager.d.ts +13 -1
  2. package/dist/ConnectionManager.js +63 -9
  3. package/dist/capabilities/agreements.d.ts +8 -0
  4. package/dist/capabilities/agreements.js +45 -0
  5. package/dist/capabilities/artifacts.js +19 -11
  6. package/dist/capabilities/chat.d.ts +3 -3
  7. package/dist/capabilities/chat.js +8 -8
  8. package/dist/capabilities/connections.js +8 -8
  9. package/dist/capabilities/context.js +11 -11
  10. package/dist/capabilities/discovery.js +4 -4
  11. package/dist/capabilities/grants.js +3 -3
  12. package/dist/capabilities/index.d.ts +3 -1
  13. package/dist/capabilities/index.js +3 -1
  14. package/dist/capabilities/links.d.ts +0 -3
  15. package/dist/capabilities/links.js +64 -102
  16. package/dist/capabilities/marketplace.d.ts +8 -0
  17. package/dist/capabilities/marketplace.js +55 -0
  18. package/dist/capabilities/payments.js +2 -2
  19. package/dist/http/AgreementClient.d.ts +8 -0
  20. package/dist/http/AgreementClient.js +6 -12
  21. package/dist/http/ArtifactsClient.d.ts +2 -2
  22. package/dist/http/ArtifactsClient.js +2 -2
  23. package/dist/http/ChatClient.js +1 -1
  24. package/dist/http/ContextReadClient.js +16 -1
  25. package/dist/http/InboxClient.d.ts +95 -65
  26. package/dist/http/InboxClient.js +46 -17
  27. package/dist/http/MarketplaceClient.d.ts +8 -0
  28. package/dist/http/agreementFlows.d.ts +34 -0
  29. package/dist/http/agreementFlows.js +80 -0
  30. package/dist/http/index.d.ts +2 -1
  31. package/dist/http/index.js +1 -0
  32. package/dist/index.d.ts +2 -0
  33. package/dist/index.js +2 -0
  34. package/dist/shared/rateLimit.d.ts +46 -0
  35. package/dist/shared/rateLimit.js +73 -0
  36. package/dist/types.d.ts +12 -1
  37. package/package.json +1 -1
@@ -1,9 +1,33 @@
1
- import { createAgreement, claimAgreement, listAgreements, revokeAgreement, } from '../http/AgreementClient.js';
1
+ import { createAgreement, listAgreements } from '../http/AgreementClient.js';
2
2
  import { fullCreds } from './types.js';
3
3
  const DEFAULT_WEB_URL = 'https://ziggsai.com';
4
+ /** Public Streamable-HTTP MCP endpoint; OAuth is discovered from it (RFC 9728). */
5
+ const ZIGGS_MCP_URL = 'https://mcp.ziggsai.com/mcp';
4
6
  function webAppOrigin(env) {
5
7
  return (env.webUrl?.trim() || DEFAULT_WEB_URL).replace(/\/$/, '');
6
8
  }
9
+ /**
10
+ * The second form an invite travels in: text the recipient pastes into their
11
+ * own assistant, which then connects itself and claims the invite. The claim
12
+ * URL alone only helps someone who already has Ziggs and an assistant wired
13
+ * up, so hand the caller both and let it pick per recipient.
14
+ */
15
+ function invitePasteText(agreementId, claimUrl) {
16
+ return [
17
+ 'Connect me to Ziggs and accept this agent link invite.',
18
+ '',
19
+ `1. Add this MCP server: ${ZIGGS_MCP_URL}`,
20
+ ' It speaks Streamable HTTP and uses OAuth — pasting the URL is enough,',
21
+ ' but I may need to approve a consent screen in my browser.',
22
+ `2. Once connected, call the tool ziggs_agreement_claim with agreementId "${agreementId}".`,
23
+ '3. Then tell me who I am linked with, and what they can and cannot see.',
24
+ '',
25
+ 'If you cannot add MCP servers yourself, tell me exactly where to paste that',
26
+ "URL in my assistant's settings, then continue from step 2.",
27
+ '',
28
+ `Invite link (if you need it in a browser instead): ${claimUrl}`,
29
+ ].join('\n');
30
+ }
7
31
  /**
8
32
  * ZIG-670 — link-shaped summaries, not raw agreement documents: the money
9
33
  * block, approvals array, and Mongo internals are noise on a trust
@@ -28,106 +52,73 @@ export function linkSummary(a) {
28
52
  /** A link is reach-only — the follow-up move differs by surface tool names. */
29
53
  export function linkIsReachOnly(env) {
30
54
  return env.surface === 'mcp'
31
- ? 'A link is reach-only. Open a chat with the peer (ziggs_open_conversation, participantId = peer agent id) and grant it chat access with ziggs_issue_grant, or share a slice of a grant you hold with ziggs_delegate_grant, before reading context.'
55
+ ? 'A link is reach-only. Open a chat with the peer (ziggs_chat_open, participantId = peer agent id) and grant it chat access with ziggs_context_issue_grant, or share a slice of a grant you hold with ziggs_context_delegate, before reading context.'
32
56
  : 'A link is reach-only: it does not grant context. Share a slice of a grant you hold with context_delegate, or ask the peer owner to issue one, before reading.';
33
57
  }
34
58
  const LINK_STATUSES = ['active', 'open', 'cancelled', 'all'];
35
- export const requestLinkCapability = {
36
- key: 'request_link',
37
- names: { sdk: 'request_link', mcp: 'ziggs_request_link' },
59
+ /**
60
+ * Seat ceiling for one invite. ⚠️ SYNC: backend
61
+ * src/agreements/agreements.service.ts MAX_LINK_INVITE_CLAIMS — the backend
62
+ * rejects anything past it; this is only so the tool description says the
63
+ * limit instead of letting an agent discover it by getting a 400.
64
+ */
65
+ const MAX_LINK_INVITE_CLAIMS = 25;
66
+ // ZIG-1021/1022 — the link rail shrank to two tools. Links are agreements, so
67
+ // the agreement verbs carry the rest: request a direct link with
68
+ // agreement_propose (engagementKind 'link', proposedTo = the agent id), claim
69
+ // an invite with agreement_claim, end a link with agreement_revoke.
70
+ export const createLinkInviteCapability = {
71
+ key: 'link_create_invite',
72
+ names: { sdk: 'link_create_invite', mcp: 'ziggs_link_create_invite' },
38
73
  descriptions: {
39
- sdk: 'Request a bilateral trust link with another agent before cross-org reach (party-to-party, NOT a third-party service connection — that is the connection rail). A link is just an agreement (engagementKind "link"). The target OWNER must approve it before unpublished delegates can message each other.',
40
- mcp: 'Request a bilateral trust link with another agent before cross-org reach (party-to-party, NOT a third-party service connection — see ziggs_list_my_connections for that). A link is just an agreement (POST /agreements {engagementKind:"link"}). The target OWNER must approve it (via ziggs_respond_to_agreement) before unpublished delegates can message each other.',
74
+ sdk: "Create a shareable OPEN link invite (bilateral agent-to-agent trust) when you do NOT have the counterparty's agent id (e.g. connecting across orgs). Creates an open link agreement proposed to everyone; the recipient forms the link by claiming the returned inviteId with agreement_claim. Set maxClaims to let several people claim the same link — each gets their own separate connection. When you DO have the agent id, propose the link directly instead: agreement_propose with engagementKind \"link\" and proposedTo = that id.",
75
+ mcp: 'Create a shareable OPEN link invite (bilateral agent-to-agent trust, NOT a third-party service connection — see ziggs_connection_list for that) when you do NOT have the counterparty\'s agent id (e.g. connecting across orgs). Creates an open link agreement (POST /agreements {engagementKind:"link"}, proposedTo:"everyone"). Share the returned inviteId (agreementId) out-of-band; the recipient forms the link by calling ziggs_agreement_claim — neither side pastes an agent id. Set maxClaims to share ONE link with several people; each claimer gets their own separate connection, not a group. Revoke via ziggs_agreement_revoke to disable. When you DO have the agent id, propose the link directly instead: ziggs_agreement_propose with engagementKind "link" and proposedTo = that id.',
41
76
  },
42
77
  annotation: 'write',
43
78
  params: {
44
- providerId: {
45
- type: 'string',
46
- required: true,
47
- description: 'Bare agent id to link with (the target delegate). Use agent search or a known delegate id — do not guess.',
48
- },
49
79
  message: {
50
80
  type: 'string',
51
- description: 'Optional note shown to the counterparty human on approval (agreement description)',
81
+ description: 'Optional note shown to whoever opens the invite (agreement description)',
82
+ },
83
+ maxClaims: {
84
+ type: 'number',
85
+ description: `How many people may claim this one link (default 1, max ${MAX_LINK_INVITE_CLAIMS}). Each claimer forms their own separate connection with you — this does not create a group.`,
52
86
  },
53
87
  },
54
88
  needsAgentId: true,
55
89
  handler: async (args, env) => {
90
+ const maxClaims = args['maxClaims'];
56
91
  const { agreement } = await createAgreement({
57
92
  engagementKind: 'link',
58
- providerId: args['providerId'],
59
93
  description: args['message'],
94
+ ...(maxClaims == null ? {} : { maxClaims }),
60
95
  }, fullCreds(env));
61
- return {
62
- status: 'pending',
63
- message: env.surface === 'mcp'
64
- ? 'Link agreement created — the counterparty owner must approve (ziggs_respond_to_agreement) before cross-org reach. Surface pending state to the human.'
65
- : 'Link agreement created — the counterparty owner must approve (agreement_respond on their side) before cross-org reach. Surface pending state to your principal.',
66
- agreement: linkSummary(agreement),
67
- };
68
- },
69
- sdkOptions: { isAgreementCreation: true },
70
- };
71
- export const createLinkInviteCapability = {
72
- key: 'create_link_invite',
73
- names: { sdk: 'create_link_invite', mcp: 'ziggs_create_link_invite' },
74
- descriptions: {
75
- sdk: "Create a shareable OPEN link invite (bilateral agent-to-agent trust) when you do NOT have the counterparty's agent id (e.g. connecting across orgs). Creates an open link agreement proposed to everyone; the recipient forms the link by claiming the returned inviteId.",
76
- mcp: 'Create a shareable OPEN link invite (bilateral agent-to-agent trust, NOT a third-party service connection — see ziggs_list_my_connections for that) when you do NOT have the counterparty\'s agent id (e.g. connecting across orgs). Creates an open link agreement (POST /agreements {engagementKind:"link"}, proposedTo:"everyone"). Share the returned inviteId (agreementId) out-of-band; the recipient forms the link by calling ziggs_claim_link_invite — neither side pastes an agent id. Revoke via ziggs_revoke_link to disable.',
77
- },
78
- annotation: 'write',
79
- params: {
80
- message: {
81
- type: 'string',
82
- description: 'Optional note shown to whoever opens the invite (agreement description)',
83
- },
84
- },
85
- needsAgentId: true,
86
- handler: async (args, env) => {
87
- const { agreement } = await createAgreement({ engagementKind: 'link', description: args['message'] }, fullCreds(env));
96
+ const claimUrl = `${webAppOrigin(env)}/app/link-invites/${agreement.agreementId}`;
97
+ const seats = agreement.linkInvite?.maxClaims ?? 1;
98
+ const seatNote = seats > 1
99
+ ? `valid 7 days and claimable by up to ${seats} people (each gets their own separate connection — not a group)`
100
+ : 'single-use and valid 7 days';
88
101
  return {
89
102
  status: 'open',
90
103
  inviteId: agreement.agreementId,
91
- claimUrl: `${webAppOrigin(env)}/app/link-invites/${agreement.agreementId}`,
104
+ claimUrl,
105
+ maxClaims: seats,
106
+ seatsRemaining: seats - (agreement.linkInvite?.claimsUsed ?? 0),
107
+ pasteText: invitePasteText(agreement.agreementId, claimUrl),
92
108
  message: env.surface === 'mcp'
93
- ? 'Open link invite created. Share claimUrl with the counterparty — they open it in the Ziggs web app to claim and activate the link. No agent id needed on either side.'
94
- : 'Open link invite created. Share claimUrl (or the inviteId) with the counterparty — they claim it in the Ziggs web app or via their claim_link_invite tool. No agent id needed on either side. Revoke with revoke_link to disable.',
109
+ ? `Open link invite created, ${seatNote}. Give the human BOTH forms and say which is which: claimUrl for a recipient who already uses Ziggs, pasteText for one who has an AI assistant but no Ziggs account — pasting it makes their assistant connect and claim the invite itself. A recipient with no Ziggs account can sign up straight from the link; no beta code needed. No agent id needed on either side.`
110
+ : `Open link invite created, ${seatNote}. Share claimUrl with a counterparty who already uses Ziggs, or pasteText with one who has an assistant but no Ziggs account yet — their assistant connects and claims it. A recipient with no Ziggs account can sign up straight from the link; no beta code needed. No agent id needed on either side. Revoke with agreement_revoke to disable.`,
95
111
  agreement: linkSummary(agreement),
96
112
  };
97
113
  },
98
114
  sdkOptions: { isAgreementCreation: true },
99
115
  };
100
- export const claimLinkInviteCapability = {
101
- key: 'claim_link_invite',
102
- names: { sdk: 'claim_link_invite', mcp: 'ziggs_claim_link_invite' },
103
- descriptions: {
104
- sdk: 'Claim an open link invite by its id to form a bilateral link. You become the counterparty and the link activates immediately (cross-org reach). You cannot claim your own invite.',
105
- mcp: 'Claim an open link invite by its id to form a bilateral link (agent-to-agent trust, NOT a third-party service connection — see ziggs_list_my_connections for that) (POST /agreements/:id/claim). You become the counterparty and the link activates immediately (cross-org reach + bilateral context grants). You cannot claim your own invite.',
106
- },
107
- annotation: 'write',
108
- params: {
109
- agreementId: {
110
- type: 'string',
111
- required: true,
112
- description: 'The invite id (agreementId) shared by the issuer',
113
- },
114
- },
115
- needsAgentId: true,
116
- handler: async (args, env) => {
117
- const { agreement } = await claimAgreement(args['agreementId'], fullCreds(env));
118
- return {
119
- status: 'linked',
120
- message: `Link invite claimed — you are now linked. ${linkIsReachOnly(env)}`,
121
- agreement: linkSummary(agreement),
122
- };
123
- },
124
- };
125
116
  export const listLinksCapability = {
126
- key: 'list_links',
127
- names: { sdk: 'list_links', mcp: 'ziggs_list_links' },
117
+ key: 'link_list',
118
+ names: { sdk: 'link_list', mcp: 'ziggs_link_list' },
128
119
  descriptions: {
129
- sdk: 'List link agreements for this agent — bilateral agent-to-agent trust relationships (GET /agreements?engagementKind=link). Defaults to ACTIVE links only; pass status to see pending proposals ("open") or revoked ones ("cancelled"). Each item is a link summary: agreementId, status, proposalStatus, parties.',
130
- mcp: 'List link agreements for this delegate — bilateral agent-to-agent trust relationships, NOT third-party service connections (see ziggs_list_my_connections for those) (GET /agreements?engagementKind=link). Defaults to ACTIVE links only; pass status to see pending proposals ("open") or revoked ones ("cancelled"). Each item is a link summary: agreementId, status, proposalStatus, parties.creatorAgent (requester), parties.providerAgent (target), parties.proposedTo (target owner). Approve pending links via ziggs_respond_to_agreement.',
120
+ sdk: 'List link agreements for this agent — bilateral agent-to-agent trust relationships (GET /agreements?engagementKind=link). Defaults to ACTIVE links only; pass status to see pending proposals ("open") or revoked ones ("cancelled"). Each item is a link summary: agreementId, status, proposalStatus, parties. Request a new link with agreement_propose (engagementKind "link"), or link_create_invite when you lack the agent id; end one with agreement_revoke.',
121
+ mcp: 'List link agreements for this delegate — bilateral agent-to-agent trust relationships, NOT third-party service connections (see ziggs_connection_list for those) (GET /agreements?engagementKind=link). Defaults to ACTIVE links only; pass status to see pending proposals ("open") or revoked ones ("cancelled"). Each item is a link summary: agreementId, status, proposalStatus, parties.creatorAgent (requester), parties.providerAgent (target), parties.proposedTo (target owner). Approve pending links via ziggs_agreement_respond; request a new one with ziggs_agreement_propose (engagementKind "link"), or ziggs_link_create_invite when you lack the agent id; end one with ziggs_agreement_revoke.',
131
122
  },
132
123
  annotation: 'read-only',
133
124
  params: {
@@ -158,36 +149,7 @@ export const listLinksCapability = {
158
149
  },
159
150
  sdkOptions: { isGenericFallback: true },
160
151
  };
161
- export const revokeLinkCapability = {
162
- key: 'revoke_link',
163
- names: { sdk: 'revoke_link', mcp: 'ziggs_revoke_link' },
164
- descriptions: {
165
- sdk: 'Revoke a bilateral link agreement (DELETE /agreements/:agreementId). Either party may revoke; cross-org reach ends immediately. For non-link agreements (hire/service/quest), use agreement_revoke — same endpoint, different messaging.',
166
- mcp: 'Revoke a bilateral link agreement — agent-to-agent trust, NOT a third-party service connection (DELETE /agreements/:agreementId). Either party may revoke; cross-org reach ends immediately. For non-link agreements (hire/service/quest), use ziggs_revoke_agreement — same endpoint, different messaging.',
167
- },
168
- annotation: 'destructive',
169
- params: {
170
- agreementId: {
171
- type: 'string',
172
- required: true,
173
- description: 'agreementId of the link agreement (from the link lister)',
174
- },
175
- },
176
- needsAgentId: true,
177
- handler: async (args, env) => {
178
- const result = await revokeAgreement(args['agreementId'], fullCreds(env));
179
- return {
180
- status: 'revoked',
181
- message: 'Link revoked — unpublished cross-org reach to this peer is blocked again.',
182
- agreementId: args['agreementId'],
183
- agreement: result.agreement ? linkSummary(result.agreement) : null,
184
- };
185
- },
186
- };
187
152
  export const LINK_CAPABILITIES = [
188
- requestLinkCapability,
189
153
  createLinkInviteCapability,
190
- claimLinkInviteCapability,
191
154
  listLinksCapability,
192
- revokeLinkCapability,
193
155
  ];
@@ -0,0 +1,8 @@
1
+ import { type CapabilityDefinition } from './types.js';
2
+ /**
3
+ * ZIG-1023 — the marketplace read on both surfaces. Publishing and claiming
4
+ * ride the agreement grammar (propose-with-audience / agreement_claim); this
5
+ * is the browse that hands you the agreementIds those verbs need.
6
+ */
7
+ export declare const marketplaceViewCapability: CapabilityDefinition;
8
+ export declare const MARKETPLACE_CAPABILITIES: CapabilityDefinition[];
@@ -0,0 +1,55 @@
1
+ import { pullOffers, pullQuests } from '../http/MarketplaceClient.js';
2
+ import { fullCreds } from './types.js';
3
+ const VIEW_KINDS = ['all', 'quests', 'offers'];
4
+ function publishHint(env) {
5
+ const propose = env.surface === 'mcp' ? 'ziggs_agreement_propose' : 'agreement_propose';
6
+ const claim = env.surface === 'mcp' ? 'ziggs_agreement_claim' : 'agreement_claim';
7
+ return (`Claim any row with ${claim} (agreementId). Publish your own with ${propose}: ` +
8
+ `proposedTo "everyone" or "org" with no providerId broadcasts a quest (claimer works, you pay); ` +
9
+ `the same with providerId = your own id publishes a standing offer (you work, claimer pays).`);
10
+ }
11
+ /**
12
+ * ZIG-1023 — the marketplace read on both surfaces. Publishing and claiming
13
+ * ride the agreement grammar (propose-with-audience / agreement_claim); this
14
+ * is the browse that hands you the agreementIds those verbs need.
15
+ */
16
+ export const marketplaceViewCapability = {
17
+ key: 'marketplace_view',
18
+ names: { sdk: 'marketplace_view', mcp: 'ziggs_marketplace_view' },
19
+ descriptions: {
20
+ sdk: 'Browse the open marketplace: quests (work buyers broadcast — you would do the work) and standing offers (services sellers broadcast — you would buy). Returns public rows plus org-scoped rows from your org, filtered server-side. Claim a row with agreement_claim; publish your own via agreement_propose with proposedTo "everyone"/"org" (providerId = your id for an offer, omitted for a quest).',
21
+ mcp: 'Browse the open marketplace: quests (work buyers broadcast — you would do the work) and standing offers (services sellers broadcast — you would buy). Returns public rows plus org-scoped rows from your org, filtered server-side. Claim a row with ziggs_agreement_claim; publish your own via ziggs_agreement_propose with proposedTo "everyone"/"org" (providerId = your id for an offer, omitted for a quest).',
22
+ },
23
+ annotation: 'read-only',
24
+ params: {
25
+ kind: {
26
+ type: 'string',
27
+ enum: VIEW_KINDS,
28
+ description: 'all (default) | quests | offers',
29
+ },
30
+ limit: { type: 'number', description: 'Max rows per kind (default 20)' },
31
+ since: { type: 'string', description: 'ISO timestamp — only rows published after this' },
32
+ },
33
+ needsAgentId: true,
34
+ handler: async (args, env) => {
35
+ const kind = args['kind'] ?? 'all';
36
+ if (!VIEW_KINDS.includes(kind)) {
37
+ throw new Error(`kind must be one of ${VIEW_KINDS.join(', ')}`);
38
+ }
39
+ const creds = fullCreds(env);
40
+ const options = {
41
+ limit: typeof args['limit'] === 'number' ? args['limit'] : 20,
42
+ ...(args['since'] ? { since: args['since'] } : {}),
43
+ };
44
+ const [quests, offers] = await Promise.all([
45
+ kind === 'offers' ? Promise.resolve([]) : pullQuests(options, creds),
46
+ kind === 'quests' ? Promise.resolve([]) : pullOffers(options, creds),
47
+ ]);
48
+ return {
49
+ ...(kind !== 'offers' ? { quests, questCount: quests.length } : {}),
50
+ ...(kind !== 'quests' ? { offers, offerCount: offers.length } : {}),
51
+ nextSteps: publishHint(env),
52
+ };
53
+ },
54
+ };
55
+ export const MARKETPLACE_CAPABILITIES = [marketplaceViewCapability];
@@ -84,7 +84,7 @@ export const paymentTransferCapability = {
84
84
  names: { sdk: 'payment_transfer', mcp: 'ziggs_payment_transfer' },
85
85
  descriptions: {
86
86
  sdk: 'Transfer funds to another wallet. Amounts are integer cents. Transfers above the wallet owner\'s policy pause with status "approval_required" — wait inline with payment_wait_for_approval when you expect a quick decision.',
87
- mcp: 'Transfer funds to another wallet. Amounts are integer cents. As a delegate you spend under a payment grant the wallet owner issued (paymentGrantId — find yours via ziggs_list_grants scopeKind=wallet). Transfers above the owner\'s policy pause with status "approval_required": the human approves on the wallet page (it also shows in ziggs_pending_decisions) — you can wait inline with ziggs_payment_wait_for_approval, and you must NEVER approve your own transfer.',
87
+ mcp: 'Transfer funds to another wallet. Amounts are integer cents. As a delegate you spend under a payment grant the wallet owner issued (paymentGrantId — find yours via ziggs_grant_list scopeKind=wallet). Transfers above the owner\'s policy pause with status "approval_required": the human approves on the wallet page (it also shows in ziggs_pending_decisions) — you can wait inline with ziggs_payment_wait_for_approval, and you must NEVER approve your own transfer.',
88
88
  },
89
89
  annotation: 'write',
90
90
  params: {
@@ -389,7 +389,7 @@ export const paymentRevokeGrantCapability = {
389
389
  },
390
390
  };
391
391
  // ZIG-893 — payment_list_grants stays retired; the wallet rail is part of the
392
- // unified list_grants capability (scopeKind: 'wallet'). Grant *mutations* stay
392
+ // unified grant_list capability (scopeKind: 'wallet'). Grant *mutations* stay
393
393
  // on the payment rail above.
394
394
  export const PAYMENT_CAPABILITIES = [
395
395
  paymentBalanceCapability,
@@ -14,6 +14,12 @@ export interface ProposeTerms {
14
14
  lifecycle?: string;
15
15
  expiresAt?: string;
16
16
  maxExecutions?: number;
17
+ /**
18
+ * How `price` reads. `total` (default) escrows one price for the whole
19
+ * engagement and pays at fulfillment; `per_task` is a RATE settled as each
20
+ * task completes (standing/open agreements only, and the default for a hire).
21
+ */
22
+ billing?: 'total' | 'per_task';
17
23
  agreementDescription?: string;
18
24
  parentAgreementId?: string;
19
25
  parentTaskId?: string;
@@ -148,6 +154,8 @@ export interface CreateAgreementBody {
148
154
  maxExecutions?: number;
149
155
  description?: string;
150
156
  engagementKind?: EngagementKind;
157
+ /** Link invites only: how many people may claim this one link. Default 1. */
158
+ maxClaims?: number;
151
159
  metadata?: Record<string, unknown>;
152
160
  }
153
161
  export declare function createAgreement(body: CreateAgreementBody, creds: Creds): Promise<{
@@ -125,18 +125,12 @@ export async function respondToAgreement(agreementId, action, creds, opts = {})
125
125
  throw new Error(`Agreement ${agreementId} not found`);
126
126
  }
127
127
  const proposedTo = agreement.parties?.proposedTo;
128
- if (isBroadcastTarget(proposedTo)) {
129
- if (action === 'reject') {
130
- // ZIG-719: an open/broadcast proposal is offered to many via a sentinel
131
- // `proposedTo` ('everyone' | 'org'), so there is no per-recipient party
132
- // slot for one recipient to reject and nothing a single reject could
133
- // change server-side. Returning the agreement here reported a fake
134
- // success while the item stayed claimable. Fail honestly instead — the
135
- // caller either claims it (action=approve → POST /claim) or ignores it.
136
- throw new Error(`Agreement ${agreementId} is an open/broadcast proposal (proposedTo="${String(proposedTo)}") and cannot be rejected by a single recipient — it has no personal approval slot. Ignore it to pass on it, or use action=approve to claim it.`);
137
- }
138
- const { agreement: updated } = await claimAgreement(agreementId, creds);
139
- return updated;
128
+ if (isBroadcastTarget(proposedTo) || isBroadcastTarget(agreement.parties?.payer)) {
129
+ // ZIG-1021: respond is approve/reject on DIRECT proposals only. An open
130
+ // broadcast (quest, standing offer, link invite) has no per-recipient
131
+ // approval slot — one recipient can neither approve nor reject it
132
+ // (ZIG-719); the move is to claim it, which has its own verb now.
133
+ throw new Error(`Agreement ${agreementId} is an open broadcast and has no personal approval slot — respond cannot ${action} it. Claim it with the claim tool (agreement_claim / ziggs_agreement_claim), or ignore it to pass.`);
140
134
  }
141
135
  const partyId = resolveMyPendingApprovalPartyId(agreement, {
142
136
  ownerUserId: opts.ownerUserId,
@@ -62,8 +62,8 @@ export declare class ArtifactsClient {
62
62
  * ZIG-899 — the deliberate-record variant: a deliverable the model chose to
63
63
  * record must fail loudly and hand back the artifactId, unlike `write`'s
64
64
  * soft-fail breadcrumb contract. This is the one wire call for both agent
65
- * surfaces (the SDK record_artifact tool and ziggs-mcp's
66
- * ziggs_record_artifact).
65
+ * surfaces (the SDK artifact_record tool and ziggs-mcp's
66
+ * ziggs_artifact_record).
67
67
  */
68
68
  writeStrict(input: WriteArtifactInput): Promise<{
69
69
  artifactId?: string;
@@ -75,8 +75,8 @@ export class ArtifactsClient {
75
75
  * ZIG-899 — the deliberate-record variant: a deliverable the model chose to
76
76
  * record must fail loudly and hand back the artifactId, unlike `write`'s
77
77
  * soft-fail breadcrumb contract. This is the one wire call for both agent
78
- * surfaces (the SDK record_artifact tool and ziggs-mcp's
79
- * ziggs_record_artifact).
78
+ * surfaces (the SDK artifact_record tool and ziggs-mcp's
79
+ * ziggs_artifact_record).
80
80
  */
81
81
  async writeStrict(input) {
82
82
  if (!input.text || !input.text.trim()) {
@@ -113,7 +113,7 @@ export async function sendChatMessage(input, creds) {
113
113
  export async function listMyChats(creds) {
114
114
  assertCreds(creds, 'list my chats');
115
115
  // ZIG-699 — empty array only for a genuine empty 200; any failure (non-2xx /
116
- // network) throws so ziggs_list_chats reports a real error instead of "you
116
+ // network) throws so ziggs_chat_list reports a real error instead of "you
117
117
  // have no chats". Status stays whitespace-delimited for toolError classification.
118
118
  let res;
119
119
  try {
@@ -1,5 +1,6 @@
1
1
  import 'dotenv/config';
2
2
  import { getBackendUrl } from '../utils/urlUtils.js';
3
+ import { pollSurfaceError } from '../shared/rateLimit.js';
3
4
  /**
4
5
  * Protocol-first uniform context reads (ZIG-427).
5
6
  * Wraps `GET /context/read/:type` — one client, one envelope, four types.
@@ -49,7 +50,18 @@ export class ContextReadClient {
49
50
  const res = await fetch(url.toString(), { headers });
50
51
  const body = await res.text().catch(() => '');
51
52
  if (!res.ok) {
52
- throw new Error(`ContextReadClient.read ${type} ${res.status} ${body.slice(0, 200)}`);
53
+ // Carry the status like `snapshot()` does, so callers can branch on it.
54
+ // A 403 here is a legitimate outcome, not a transport failure: addressing
55
+ // and authorisation are separate, so an agent can be told about mail it
56
+ // is not (or is no longer) allowed to open.
57
+ // ZIG-1019: a 429 carries the server's own wait; everything else keeps
58
+ // the plain status-tagged error callers already branch on.
59
+ if (res.status === 429) {
60
+ throw pollSurfaceError(`ContextReadClient.read ${type}`, res, body);
61
+ }
62
+ const err = new Error(`ContextReadClient.read ${type} ${res.status} ${body.slice(0, 200)}`);
63
+ err.status = res.status;
64
+ throw err;
53
65
  }
54
66
  return JSON.parse(body);
55
67
  }
@@ -82,6 +94,9 @@ export class ContextReadClient {
82
94
  const res = await fetch(url.toString(), { headers });
83
95
  const body = await res.text().catch(() => '');
84
96
  if (!res.ok) {
97
+ if (res.status === 429) {
98
+ throw pollSurfaceError('ContextReadClient.snapshot', res, body);
99
+ }
85
100
  const err = new Error(`ContextReadClient.snapshot ${res.status} ${body.slice(0, 200)}`);
86
101
  err.status = res.status;
87
102
  throw err;