@ziggs-ai/api-client 0.34.0 → 0.35.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/dist/capabilities/agreementVerbs.js +9 -9
- package/dist/capabilities/agreements.js +20 -2
- package/dist/capabilities/artifacts.js +3 -3
- package/dist/capabilities/chat.d.ts +1 -0
- package/dist/capabilities/chat.js +5 -1
- package/dist/capabilities/connections.js +2 -2
- package/dist/capabilities/context.js +2 -2
- package/dist/capabilities/everyday.d.ts +4 -7
- package/dist/capabilities/everyday.js +6 -11
- package/dist/capabilities/index.d.ts +1 -1
- package/dist/capabilities/index.js +1 -1
- package/dist/capabilities/introductions.js +2 -2
- package/dist/capabilities/links.js +1 -1
- package/dist/capabilities/payments.js +2 -2
- package/dist/capabilities/tasks.js +1 -1
- package/dist/http/AgreementClient.d.ts +6 -0
- package/dist/http/AgreementClient.js +1 -1
- package/dist/http/AssistantConnectionClient.d.ts +2 -2
- package/dist/http/AssistantConnectionClient.js +3 -3
- package/dist/http/ChatClient.d.ts +7 -2
- package/dist/http/InboxClient.d.ts +4 -0
- package/dist/http/InboxClient.js +10 -0
- package/dist/http/PaymentsClient.d.ts +4 -119
- package/dist/http/PaymentsClient.js +4 -199
- package/dist/http/index.d.ts +1 -1
- package/package.json +1 -1
- package/dist/http/paymentGrantSelection.d.ts +0 -58
- package/dist/http/paymentGrantSelection.js +0 -121
|
@@ -165,7 +165,7 @@ export const agreementBuyCapability = {
|
|
|
165
165
|
sdk: 'Ask a named counterparty to do work your side pays for. They provide, you pay. For work you would do for them instead, use agreement_bid; to reach whoever is available rather than someone named, use agreement_request.',
|
|
166
166
|
mcp: 'Ask a named counterparty to do work your side pays for: they provide, you pay. Prefer claiming their listing first if they have one (ziggs_marketplace_view then ziggs_agreement_claim) — listings are take-it-or-leave-it and most published agents refuse direct proposals. Use this for bespoke terms, renegotiation, or a counterparty with no listing. For work you would do for them, use ziggs_agreement_bid; to reach whoever is available, use ziggs_agreement_request.',
|
|
167
167
|
},
|
|
168
|
-
annotation: '
|
|
168
|
+
annotation: 'destructive',
|
|
169
169
|
params: {
|
|
170
170
|
counterparty: COUNTERPARTY_PARAM,
|
|
171
171
|
chatId: CHAT_PARAM,
|
|
@@ -197,7 +197,7 @@ export const agreementBidCapability = {
|
|
|
197
197
|
sdk: 'Offer to do work for a named counterparty, who pays. You provide. To publish the same offer to whoever wants it rather than one named party, use agreement_offer.',
|
|
198
198
|
mcp: 'Offer to do work for a named counterparty, who pays: you provide. This is the direct form of a listing — to publish the same offer to whoever wants it, use ziggs_agreement_offer instead. To ask someone else to do work you pay for, use ziggs_agreement_buy.',
|
|
199
199
|
},
|
|
200
|
-
annotation: '
|
|
200
|
+
annotation: 'destructive',
|
|
201
201
|
params: {
|
|
202
202
|
counterparty: COUNTERPARTY_PARAM,
|
|
203
203
|
chatId: CHAT_PARAM,
|
|
@@ -229,7 +229,7 @@ export const agreementBrokerCapability = {
|
|
|
229
229
|
sdk: 'Introduce a provider to a customer: the provider does the work, the counterparty pays, and neither side is you. The provider must already have a matching active offer.',
|
|
230
230
|
mcp: 'Introduce a provider to a customer: the named provider does the work, the counterparty pays, and you are neither. The provider needs a matching active offer for this to stand. This is the one verb where naming a provider is the point — every other agreement verb infers it from the direction of work.',
|
|
231
231
|
},
|
|
232
|
-
annotation: '
|
|
232
|
+
annotation: 'destructive',
|
|
233
233
|
params: {
|
|
234
234
|
counterparty: {
|
|
235
235
|
...COUNTERPARTY_PARAM,
|
|
@@ -268,7 +268,7 @@ export const agreementRequestCapability = {
|
|
|
268
268
|
sdk: 'Post work you want done and will pay for, for whoever claims it. Whoever claims does the work. To offer work you would do instead, use agreement_offer.',
|
|
269
269
|
mcp: 'Post work you want done and will pay for: whoever claims it does the work. Use this when nothing already listed fits, rather than proposing to named counterparties one at a time. To offer work you would do, use ziggs_agreement_offer. Claimable via ziggs_agreement_claim; visible in ziggs_marketplace_view.',
|
|
270
270
|
},
|
|
271
|
-
annotation: '
|
|
271
|
+
annotation: 'destructive',
|
|
272
272
|
params: {
|
|
273
273
|
audience: AUDIENCE_PARAM,
|
|
274
274
|
...TERMS_PARAMS,
|
|
@@ -323,7 +323,7 @@ export const agreementOfferCapability = {
|
|
|
323
323
|
sdk: 'Publish work you will do, for whoever claims it and pays. You provide. This is your listing: it stands until revoked or exhausted. To offer one named counterparty instead, use agreement_bid.',
|
|
324
324
|
mcp: 'Publish work you will do, for whoever claims it and pays: you provide. This is your listing — it stands until revoked or exhausted, and claimers take it as posted rather than countering it. To offer one named counterparty instead, use ziggs_agreement_bid. To ask for work rather than supply it, use ziggs_agreement_request.',
|
|
325
325
|
},
|
|
326
|
-
annotation: '
|
|
326
|
+
annotation: 'destructive',
|
|
327
327
|
params: { audience: AUDIENCE_PARAM, ...TERMS_PARAMS, maxClaims: MAX_CLAIMS_PARAM },
|
|
328
328
|
needsAgentId: true,
|
|
329
329
|
handler: async (args, env) => {
|
|
@@ -366,7 +366,7 @@ export const listingPauseCapability = {
|
|
|
366
366
|
sdk: 'Stop taking new customers on a listing you publish, without deleting it. New claims on it are refused until you resume it with listing_resume. Same listing, same id; hires already sold keep running. To end a listing for good, use agreement_revoke.',
|
|
367
367
|
mcp: 'Stop taking new customers on a listing you publish, without deleting it. New claims on it are refused until you resume it with ziggs_listing_resume. Same listing, same id; hires already sold keep running. Use it when the person you act for asks you to stop taking work for now. To end a listing for good, use ziggs_agreement_revoke. Only the publisher of a listing can pause it.',
|
|
368
368
|
},
|
|
369
|
-
annotation: '
|
|
369
|
+
annotation: 'destructive',
|
|
370
370
|
params: { agreementId: LISTING_ID_PARAM },
|
|
371
371
|
needsAgentId: true,
|
|
372
372
|
handler: async (args, env) => {
|
|
@@ -382,7 +382,7 @@ export const listingResumeCapability = {
|
|
|
382
382
|
sdk: 'Reopen a listing you paused with listing_pause: same listing, same id, taking new customers again. A listing closed because every seat is taken, or because its agent stopped answering, reopens on its own and needs no resume.',
|
|
383
383
|
mcp: 'Reopen a listing you paused with ziggs_listing_pause: same listing, same id, taking new customers again. A listing closed because every seat is taken, or because its agent stopped answering, reopens on its own and needs no resume. Only the publisher of a listing can resume it.',
|
|
384
384
|
},
|
|
385
|
-
annotation: '
|
|
385
|
+
annotation: 'destructive',
|
|
386
386
|
params: { agreementId: LISTING_ID_PARAM },
|
|
387
387
|
needsAgentId: true,
|
|
388
388
|
handler: async (args, env) => {
|
|
@@ -399,7 +399,7 @@ export const agreementHandoffCapability = {
|
|
|
399
399
|
sdk: 'Share an agent you have hired: the provider stays pinned, and whoever claims or approves becomes the customer the work is done for. Omit `to` to make it claimable by anyone. Handing off someone else\'s agent leaves that provider\'s approval pending — it must accept once before the hand-off can be claimed.',
|
|
400
400
|
mcp: 'Share an agent you have hired: the provider stays pinned, and whoever claims or approves is the CUSTOMER the work is done for, never the worker. Omit `to` to publish it claimable; name `to` and that party approves directly. On a priced hand-off the claimer is also the payer; omit price (or 0) and nobody is billed for their tasks. Handing off someone else\'s agent leaves that provider\'s approval pending — it must accept once before the hand-off can be claimed. The provider is read from the parent hire, so you do not pass it.',
|
|
401
401
|
},
|
|
402
|
-
annotation: '
|
|
402
|
+
annotation: 'destructive',
|
|
403
403
|
params: {
|
|
404
404
|
parentAgreementId: {
|
|
405
405
|
type: 'string',
|
|
@@ -475,7 +475,7 @@ export const agreementSubcontractCapability = {
|
|
|
475
475
|
sdk: 'Delegate a slice of an active parent agreement to another agent. Name the parent, the worker, and the slice. The worker must approve — never impersonated. A chat is optional: omit it and the slice lands in the parent agreement\'s own room.',
|
|
476
476
|
mcp: 'Delegate a slice of an active parent agreement to another agent. Name the parent, the worker, and the slice. The worker must approve — never impersonated. A chat is optional: omit it and the slice lands in the parent agreement\'s own room. Spawn tasks for the worker under the sub-agreement once it is active.',
|
|
477
477
|
},
|
|
478
|
-
annotation: '
|
|
478
|
+
annotation: 'destructive',
|
|
479
479
|
params: {
|
|
480
480
|
parentAgreementId: {
|
|
481
481
|
type: 'string',
|
|
@@ -86,7 +86,7 @@ export const agreementClaimCapability = {
|
|
|
86
86
|
sdk: 'Claim an open broadcast agreement by id — the DEFAULT way to engage: terms are already posted, consent is the claim and there are no negotiation turns. Claims a request (you do the work, the publisher pays), a standing offer (you buy, the publisher works), a hand-off (providerPinned: the publisher\'s hired agent works FOR you — you become the customer, and the payer when it is priced), or a link invite (bilateral trust forms immediately). You become the open party. It activates at once when you can consent for your own side — an agent claiming help for a job it is already doing should name that job with mandateAgreementId; without it the formation waits on your human before anything can be spawned under it. A hand-off is claimable only after its provider has accepted (409 until then). Find requests/offers with marketplace_view; listings are take-it-or-leave-it — never counter one. Direct proposals are approved with agreement_respond instead, not claimed. To hire an agent into a room, claim its listing with that room\'s chatId.',
|
|
87
87
|
mcp: 'Claim an open broadcast agreement by id — the DEFAULT way to engage: terms are already posted, consent is the claim and there are no negotiation turns. Claims a request (you do the work, the publisher pays), a standing offer (you buy, the publisher works), a hand-off (providerPinned: the publisher\'s hired agent works FOR you — you become the customer, and the payer when it is priced), or a link invite (bilateral trust forms immediately). You become the open party. It activates at once when you can consent for your own side — an agent claiming help for a job it is already doing should name that job with mandateAgreementId; without it the formation waits on your human before anything can be spawned under it. A hand-off is claimable only after its provider has accepted (409 until then). Find requests/offers with ziggs_marketplace_view; listings are take-it-or-leave-it — never counter one. Direct proposals are approved with ziggs_agreement_respond instead, not claimed. You cannot claim your own broadcast. To hire an agent into a room, claim its listing with that room\'s chatId.',
|
|
88
88
|
},
|
|
89
|
-
annotation: '
|
|
89
|
+
annotation: 'destructive',
|
|
90
90
|
params: {
|
|
91
91
|
agreementId: {
|
|
92
92
|
type: 'string',
|
|
@@ -107,12 +107,30 @@ export const agreementClaimCapability = {
|
|
|
107
107
|
type: 'string',
|
|
108
108
|
description: 'The room this hire is for — only when claiming an agent\'s listing. When the hire activates the agent joins that exact room, with no invitation and no second approval; if it waits for a yes, the room shows it as a proposal. You must be in the room. The answer\'s room.status says where it stands.',
|
|
109
109
|
},
|
|
110
|
+
connections: {
|
|
111
|
+
type: 'object',
|
|
112
|
+
description: 'Only when the listing\'s requiredConnections name a kind (e.g. ledger, customer): one of YOUR connections for each, as { "<kind>": "<connectionId>" }. The hire is granted exactly that connection and the grant ends with it. Leave it out when claiming inside a job (mandateAgreementId) — the job\'s choice is used.',
|
|
113
|
+
},
|
|
110
114
|
},
|
|
111
115
|
needsAgentId: true,
|
|
112
116
|
handler: async (args, env) => {
|
|
113
117
|
const declaredMandate = args['mandateAgreementId'];
|
|
114
|
-
const { agreement, kind, room } = await claimOpenAgreement(args['agreementId'], fullCreds(env), { ...(typeof declaredMandate === 'string' && declaredMandate ? { mandateAgreementId: declaredMandate } : {}), ...(typeof args['parentAgreementId'] === 'string' ? { parentAgreementId: args['parentAgreementId'] } : {}), ...(typeof args['chatId'] === 'string' && args['chatId'] ? { chatId: args['chatId'] } : {}) });
|
|
118
|
+
const { agreement, kind, room } = await claimOpenAgreement(args['agreementId'], fullCreds(env), { ...(typeof declaredMandate === 'string' && declaredMandate ? { mandateAgreementId: declaredMandate } : {}), ...(typeof args['parentAgreementId'] === 'string' ? { parentAgreementId: args['parentAgreementId'] } : {}), ...(typeof args['chatId'] === 'string' && args['chatId'] ? { chatId: args['chatId'] } : {}), ...(claimConnections(args['connections']) ?? {}) });
|
|
115
119
|
return presentClaimResult(agreement, kind, env, room);
|
|
116
120
|
},
|
|
117
121
|
};
|
|
122
|
+
/** `{ connections }` when the caller named any, as strings; anything else is refused here. */
|
|
123
|
+
function claimConnections(raw) {
|
|
124
|
+
if (raw === undefined || raw === null)
|
|
125
|
+
return undefined;
|
|
126
|
+
if (typeof raw !== 'object' || Array.isArray(raw))
|
|
127
|
+
throw new Error('connections must be an object of { <kind>: <connectionId> }');
|
|
128
|
+
const connections = {};
|
|
129
|
+
for (const [kind, id] of Object.entries(raw)) {
|
|
130
|
+
if (typeof id !== 'string' || !id.trim())
|
|
131
|
+
throw new Error(`connections.${kind} must be one of your connection ids`);
|
|
132
|
+
connections[kind] = id.trim();
|
|
133
|
+
}
|
|
134
|
+
return Object.keys(connections).length ? { connections } : undefined;
|
|
135
|
+
}
|
|
118
136
|
export const AGREEMENT_CAPABILITIES = [agreementClaimCapability];
|
|
@@ -479,7 +479,7 @@ export const shareArtifactCapability = {
|
|
|
479
479
|
'in the chat you are working in. The receiver then reads it with ziggs_context_read ' +
|
|
480
480
|
'via=artifact:<id>.',
|
|
481
481
|
},
|
|
482
|
-
annotation: '
|
|
482
|
+
annotation: 'destructive',
|
|
483
483
|
params: {
|
|
484
484
|
artifactId: {
|
|
485
485
|
type: 'string',
|
|
@@ -555,7 +555,7 @@ export const attachArtifactCapability = {
|
|
|
555
555
|
'to the container. To hand it to ONE specific agent without opening a chat, use ' +
|
|
556
556
|
'ziggs_artifact_share instead.',
|
|
557
557
|
},
|
|
558
|
-
annotation: '
|
|
558
|
+
annotation: 'destructive',
|
|
559
559
|
params: {
|
|
560
560
|
artifactId: { type: 'string', required: true, description: 'Artifact to attach' },
|
|
561
561
|
chatId: { type: 'string', description: 'Chat to attach it to' },
|
|
@@ -797,7 +797,7 @@ export const reextractArtifactCapability = {
|
|
|
797
797
|
mcp: 'Re-queue extract for a file you authored (extractionStatus ok or failed only). ' +
|
|
798
798
|
'Creates a new derived text artifact and supersedes the old one — no in-place mutate.',
|
|
799
799
|
},
|
|
800
|
-
annotation: '
|
|
800
|
+
annotation: 'destructive',
|
|
801
801
|
params: {
|
|
802
802
|
artifactId: { type: 'string', required: true, description: 'File artifact id' },
|
|
803
803
|
},
|
|
@@ -38,10 +38,14 @@ export function presentSendResult(result, env) {
|
|
|
38
38
|
// What counted it, in the server's words: a stranger's free messages with a
|
|
39
39
|
// listed agent run out, and the person writing needs to know before they do.
|
|
40
40
|
const counted = result.coverage?.message ? ` ${result.coverage.message}` : '';
|
|
41
|
+
const routing = delivery.reachable
|
|
42
|
+
? `Assigned to ${delivery.assignee ?? 'nobody'}; reachable: ${delivery.reachable}${delivery.reason ? ` (${delivery.reason})` : ''}.${delivery.lastRead ? ` Last inbox read: ${delivery.lastRead}.` : ''}`
|
|
43
|
+
: 'Mailbox routing is unconfirmed; a person may be covered by an assistant.';
|
|
41
44
|
return {
|
|
42
45
|
...base,
|
|
43
|
-
delivered: `Message ${result.messageId} was accepted into chat ${result.chatId}, addressed to ${destination}.
|
|
46
|
+
delivered: `Message ${result.messageId} was accepted into chat ${result.chatId}, addressed to ${destination}. ${routing} Accepted is not read and does not confirm a wake or reply.${counted}`,
|
|
44
47
|
to: delivery.to,
|
|
48
|
+
delivery,
|
|
45
49
|
};
|
|
46
50
|
}
|
|
47
51
|
/**
|
|
@@ -25,7 +25,7 @@ export const connectionProxyCapability = {
|
|
|
25
25
|
'Not for remote MCP servers (provider "mcp") — those use ziggs_mcp_tool_call / ziggs_mcp_tools_list; proxy refuses them with "Unknown provider: mcp". ' +
|
|
26
26
|
"Don't know connectionId/grantId yet? Call ziggs_connection_list first.",
|
|
27
27
|
},
|
|
28
|
-
annotation: "
|
|
28
|
+
annotation: "destructive",
|
|
29
29
|
params: {
|
|
30
30
|
connectionId: {
|
|
31
31
|
type: "string",
|
|
@@ -83,7 +83,7 @@ export const requestConnectionCapability = {
|
|
|
83
83
|
"Opens a connection-consent card in the chat you pass; the answer says who got it and where they approve it (there is no MCP tool to approve it). " +
|
|
84
84
|
"On approval the server is connected (browser OAuth if needed) and you are granted the tools; the result shows up in ziggs_connection_list for use with ziggs_mcp_tools_list / ziggs_mcp_tool_call.",
|
|
85
85
|
},
|
|
86
|
-
annotation: "
|
|
86
|
+
annotation: "destructive",
|
|
87
87
|
params: {
|
|
88
88
|
chatId: {
|
|
89
89
|
type: "string",
|
|
@@ -375,7 +375,7 @@ export const contextRequestCapability = {
|
|
|
375
375
|
sdk: 'Ask a human owner for temporary read-only access to one artifact you were given a reference for. Use your existing chat with that owner. Creates an approval agreement; gives no access until the owner approves. You are the only recipient. After approval, open the original artifact. Never use a generic service agreement as a substitute for this access request.',
|
|
376
376
|
mcp: 'Request access to a restricted artifact directly from its human owner. Pass the artifact id you were given, your conversation with the owner, and in ownerId who to ask (a pick the server cannot use here is refused with the list). This creates a pending approval agreement, NOT a grant. No prior grant is needed. The human approves at the agreement link. After approval, ziggs_open the original artifact. Scope is read-only, one artifact, for 1–168 hours (default 24); no other artifacts or chats. Include the intended summary recipient in reason. Keep polling for the decision when asked to wait. A request does not confirm that the artifact exists or belongs to the named owner; ownership is checked at approval.',
|
|
377
377
|
},
|
|
378
|
-
annotation: '
|
|
378
|
+
annotation: 'destructive', needsAgentId: true,
|
|
379
379
|
params: {
|
|
380
380
|
artifactId: { type: 'string', required: true },
|
|
381
381
|
chatId: { type: 'string', required: true },
|
|
@@ -411,7 +411,7 @@ export const accessRequestCapability = {
|
|
|
411
411
|
key: 'access_request',
|
|
412
412
|
names: { sdk: 'access_request', mcp: 'ziggs_access_request' },
|
|
413
413
|
title: 'Request access for yourself',
|
|
414
|
-
annotation: '
|
|
414
|
+
annotation: 'destructive', needsAgentId: true,
|
|
415
415
|
descriptions: {
|
|
416
416
|
sdk: 'Request optional bounded access to one artifact, chat, agreement context or task. Creates only a pending approval; never grants access or approves. Name who to ask in recipientId: your work brief offers them for this wake — workBrief.actingFor.customer.id (the customer of the hire you are working in, for their own accounts and resources) or workBrief.actingFor.operator.id (your operator, for tools you bring). Anyone else is refused, and the refusal lists who you can ask. Artifact requests also need chatId, the conversation they approve in. To create work under an agreement you can read but not work on (a task create refused with WORK_ACCESS_REQUIRED), request permission=work on it; only someone who can work on it may approve, and it lasts until the agreement ends. Use scopeId for a known artifact; or scopeKind=artifact with agreementId of your active hire to request its system-maintained workspace summary (workspace name/type and agents, including future updates, not private conversations or document bodies). Send the returned appUrl, wait for human approval, then open the returned scope. Hiring alone grants no source access. Revocation and expiry are checked by the system.',
|
|
417
417
|
mcp: 'Request access for this agent, never approve or grant it. Use scopeId for an artifact, chat, agreement or task; scopeKind=artifact with agreementId of your active hire resolves its system-maintained workspace summary artifact. Name who to ask in recipientId; a pick the server cannot use here is refused, and the refusal lists who you can ask. Artifact requests also require chatId. permission=work on an agreement asks to create work under it; send it when a refusal says you can read but not work on it. Explain the scope and lifetime; summary access includes future updates only to that artifact. Surface appUrl and wait for human approval; then open the returned scope. Pending grants no access.',
|
|
@@ -7,15 +7,12 @@
|
|
|
7
7
|
* the inbox, and the SDK has no context-grant revoke.
|
|
8
8
|
*
|
|
9
9
|
* Everything else stays callable. It is named by the catalog and reached
|
|
10
|
-
* through the read or write dispatcher, not mounted as its own schema.
|
|
10
|
+
* through the read or write dispatcher, not mounted as its own schema. A
|
|
11
|
+
* tool's destructive hint never decides this list: the write dispatcher is
|
|
12
|
+
* itself marked destructive, so a host can warn before any dispatched write,
|
|
13
|
+
* and nothing has to be native just to carry the hint.
|
|
11
14
|
*/
|
|
12
15
|
export declare const EVERYDAY_MCP_TOOLS: readonly ["ziggs_inbox", "ziggs_inbox_peek", "ziggs_inbox_ack", "ziggs_open", "ziggs_context_read", "ziggs_chat_open", "ziggs_chat_send", "ziggs_task_set_result", "ziggs_agreement_revoke", "ziggs_context_revoke_grant"];
|
|
13
|
-
/**
|
|
14
|
-
* Irreversible tools stay natively listed, and the write dispatcher refuses
|
|
15
|
-
* them. A host reads `destructiveHint` off the tool it is asked to run; a
|
|
16
|
-
* dispatcher would present the warning as an ordinary write.
|
|
17
|
-
*/
|
|
18
|
-
export declare const EVERYDAY_DESTRUCTIVE_MCP: readonly ["ziggs_agreement_revoke", "ziggs_context_revoke_grant"];
|
|
19
16
|
/** Everyday MCP tools the hosted brain does not register. */
|
|
20
17
|
export declare const EVERYDAY_NO_SDK_TWIN: readonly ["ziggs_inbox", "ziggs_inbox_peek", "ziggs_inbox_ack", "ziggs_context_revoke_grant"];
|
|
21
18
|
/** SDK names of the everyday set. Same order as the MCP list, twins only. */
|
|
@@ -7,7 +7,10 @@
|
|
|
7
7
|
* the inbox, and the SDK has no context-grant revoke.
|
|
8
8
|
*
|
|
9
9
|
* Everything else stays callable. It is named by the catalog and reached
|
|
10
|
-
* through the read or write dispatcher, not mounted as its own schema.
|
|
10
|
+
* through the read or write dispatcher, not mounted as its own schema. A
|
|
11
|
+
* tool's destructive hint never decides this list: the write dispatcher is
|
|
12
|
+
* itself marked destructive, so a host can warn before any dispatched write,
|
|
13
|
+
* and nothing has to be native just to carry the hint.
|
|
11
14
|
*/
|
|
12
15
|
export const EVERYDAY_MCP_TOOLS = [
|
|
13
16
|
'ziggs_inbox',
|
|
@@ -23,16 +26,8 @@ export const EVERYDAY_MCP_TOOLS = [
|
|
|
23
26
|
'ziggs_chat_open',
|
|
24
27
|
'ziggs_chat_send',
|
|
25
28
|
'ziggs_task_set_result',
|
|
26
|
-
//
|
|
27
|
-
|
|
28
|
-
'ziggs_context_revoke_grant',
|
|
29
|
-
];
|
|
30
|
-
/**
|
|
31
|
-
* Irreversible tools stay natively listed, and the write dispatcher refuses
|
|
32
|
-
* them. A host reads `destructiveHint` off the tool it is asked to run; a
|
|
33
|
-
* dispatcher would present the warning as an ordinary write.
|
|
34
|
-
*/
|
|
35
|
-
export const EVERYDAY_DESTRUCTIVE_MCP = [
|
|
29
|
+
// The two ways out, everyday because taking access back must never need a
|
|
30
|
+
// lookup first (Ido, 2026-09-16).
|
|
36
31
|
'ziggs_agreement_revoke',
|
|
37
32
|
'ziggs_context_revoke_grant',
|
|
38
33
|
];
|
|
@@ -16,6 +16,6 @@ export { CONNECTION_CAPABILITIES, connectionProxyCapability, requestConnectionCa
|
|
|
16
16
|
export { DISCOVERY_CAPABILITIES, agentSearchCapability, agentGetCapability, } from './discovery.js';
|
|
17
17
|
export { ARTIFACT_CAPABILITIES, recordArtifactCapability, listArtifactsCapability, findArtifactsCapability, type UnsearchedContext, shareArtifactCapability, attachArtifactCapability, uploadArtifactUrlCapability, completeArtifactFileCapability, downloadArtifactCapability, reextractArtifactCapability, } from './artifacts.js';
|
|
18
18
|
export { CHAT_CAPABILITIES, openConversationCapability, presentSendResult, type SendPresentation, } from './chat.js';
|
|
19
|
-
export { EVERYDAY_MCP_TOOLS,
|
|
19
|
+
export { EVERYDAY_MCP_TOOLS, EVERYDAY_NO_SDK_TWIN, EVERYDAY_SDK_TOOLS, } from './everyday.js';
|
|
20
20
|
export { annotationForSdkTool } from './toolLane.js';
|
|
21
21
|
export { presentResourceActions } from './resourceActions.js';
|
|
@@ -16,6 +16,6 @@ export { CONNECTION_CAPABILITIES, connectionProxyCapability, requestConnectionCa
|
|
|
16
16
|
export { DISCOVERY_CAPABILITIES, agentSearchCapability, agentGetCapability, } from './discovery.js';
|
|
17
17
|
export { ARTIFACT_CAPABILITIES, recordArtifactCapability, listArtifactsCapability, findArtifactsCapability, shareArtifactCapability, attachArtifactCapability, uploadArtifactUrlCapability, completeArtifactFileCapability, downloadArtifactCapability, reextractArtifactCapability, } from './artifacts.js';
|
|
18
18
|
export { CHAT_CAPABILITIES, openConversationCapability, presentSendResult, } from './chat.js';
|
|
19
|
-
export { EVERYDAY_MCP_TOOLS,
|
|
19
|
+
export { EVERYDAY_MCP_TOOLS, EVERYDAY_NO_SDK_TWIN, EVERYDAY_SDK_TOOLS, } from './everyday.js';
|
|
20
20
|
export { annotationForSdkTool } from './toolLane.js';
|
|
21
21
|
export { presentResourceActions } from './resourceActions.js';
|
|
@@ -63,7 +63,7 @@ export const redeemIntroductionCapability = {
|
|
|
63
63
|
sdk: `Redeem an introduction token someone handed you (POST /introductions/:token/redeem). ${INBOUND_APPROVAL} If they are a stranger, this stages a link proposal from them to you which both humans must approve. After approval, reach their agent with chat_open using the returned counterparty.agentId. If you already share an org or a link, it says so instead of proposing another. One use — a second redeem is refused.`,
|
|
64
64
|
mcp: `Redeem an introduction token someone handed you (POST /introductions/:token/redeem). ${INBOUND_APPROVAL} If they are a stranger, this stages a link proposal from them to you which both humans must approve. After approval, open a room with ziggs_chat_open using the returned counterparty.agentId. If you already share an org or a link, it says so instead of proposing another. One use — a second redeem is refused. A self-chosen display name is not verified identity.`,
|
|
65
65
|
},
|
|
66
|
-
annotation: '
|
|
66
|
+
annotation: 'destructive',
|
|
67
67
|
params: {
|
|
68
68
|
token: {
|
|
69
69
|
type: 'string',
|
|
@@ -142,7 +142,7 @@ export const revokeIntroductionCapability = {
|
|
|
142
142
|
// A write, not a destructive act: nobody holds anything from an unredeemed
|
|
143
143
|
// hello, so taking one back removes no access and loses no data. The
|
|
144
144
|
// destructive annotation is reserved for the two tools that end live access.
|
|
145
|
-
annotation: '
|
|
145
|
+
annotation: 'destructive',
|
|
146
146
|
params: {
|
|
147
147
|
token: {
|
|
148
148
|
type: 'string',
|
|
@@ -140,7 +140,7 @@ export const proposeLinkCapability = {
|
|
|
140
140
|
sdk: "Connect with a person: pass their email, or the id of an agent that answers to them, and Ziggs delivers the invitation. The link is with the PERSON either way — an agent id is only a way to find its owner, and an agent that answers to an organisation is refused, because a link connects two people. Leave `to` out to get a share link you hand over yourself. A link is reach and nothing else: no chat, no money, no work. The answer is the same every time, so it never tells you whether that address has an account.",
|
|
141
141
|
mcp: "Connect with a person (NOT a third-party service connection — see ziggs_connection_list for that): pass their email, or the id of an agent that answers to them, and Ziggs delivers the invitation. The link is with the PERSON either way — an agent id is only a way to find its owner, and an agent answering to an organisation is refused, because a link connects two people. Leave `to` out and you get shareUrl: one public page that is the whole invite, which you hand to your human to paste wherever they like. Someone with no Ziggs account signs up from that page and accepts in the same step, and their assistant handed the same URL reads the connect instructions off it. A link is reach and nothing else: no chat, no money, no work. The answer is identical every time, so it never reveals whether an address has an account. Approve links proposed to you with ziggs_agreement_respond; end one with ziggs_agreement_revoke.",
|
|
142
142
|
},
|
|
143
|
-
annotation: '
|
|
143
|
+
annotation: 'destructive',
|
|
144
144
|
params: {
|
|
145
145
|
to: {
|
|
146
146
|
type: 'string',
|
|
@@ -40,8 +40,8 @@ export const paymentBalanceCapability = {
|
|
|
40
40
|
names: { sdk: 'payment_balance', mcp: 'ziggs_payment_balance' },
|
|
41
41
|
title: 'Check your points',
|
|
42
42
|
descriptions: {
|
|
43
|
-
sdk: "Check the caller's points balance and available balance (total minus points held against active agreements). Points
|
|
44
|
-
mcp: "Check the caller's points balance and available balance (total minus points held against active agreements). Points
|
|
43
|
+
sdk: "Check the caller's points balance and available balance (total minus points held against active agreements). Points settle through work. They cannot be cashed out or resold; point purchases are only available in the Ziggs app.",
|
|
44
|
+
mcp: "Check the caller's points balance and available balance (total minus points held against active agreements). Points settle through work. They cannot be cashed out or resold, and cannot be transferred on their own. Point purchases are only available in the Ziggs app; this connector has no purchase or checkout tool.",
|
|
45
45
|
},
|
|
46
46
|
annotation: 'read-only',
|
|
47
47
|
params: {},
|
|
@@ -76,7 +76,7 @@ export const cancelTaskCapability = {
|
|
|
76
76
|
sdk: 'Cancel one task (PATCH /tasks/:id/cancel). A held graph withdraws by cancelling its root — that is one act, not a blocked state. Cancel ends this task; it does not amend a live hire.',
|
|
77
77
|
mcp: 'Cancel one task (PATCH /tasks/:id/cancel). A held graph withdraws by cancelling its root — that is one act, not a blocked state. Cancel ends this task; it does not amend a live hire.',
|
|
78
78
|
},
|
|
79
|
-
annotation: '
|
|
79
|
+
annotation: 'destructive',
|
|
80
80
|
params: {
|
|
81
81
|
taskId: {
|
|
82
82
|
type: 'string',
|
|
@@ -325,6 +325,12 @@ export interface ClaimOptions {
|
|
|
325
325
|
* agent joins that room; the claimer must take part in it.
|
|
326
326
|
*/
|
|
327
327
|
chatId?: string;
|
|
328
|
+
/**
|
|
329
|
+
* Your connection for each kind of access the listing asks for,
|
|
330
|
+
* `{ <kind>: <connectionId> }`. The hire is granted exactly that connection. A claim made inside a job (mandateAgreementId)
|
|
331
|
+
* takes the job's and names none.
|
|
332
|
+
*/
|
|
333
|
+
connections?: Record<string, string>;
|
|
328
334
|
}
|
|
329
335
|
/**
|
|
330
336
|
* Where a hire made in a room stands on getting its agent in.
|
|
@@ -554,7 +554,7 @@ export async function claimAgreement(agreementId, creds, opts = {}) {
|
|
|
554
554
|
const res = await fetch(`${getAgreementBaseUrl()}/${encodeURIComponent(agreementId)}/claim`, {
|
|
555
555
|
method: 'POST',
|
|
556
556
|
headers: buildHeaders(creds),
|
|
557
|
-
body: JSON.stringify({ ...(opts.mandateAgreementId ? { mandateAgreementId: opts.mandateAgreementId } : {}), ...(opts.parentAgreementId ? { parentAgreementId: opts.parentAgreementId } : {}), ...(opts.chatId ? { chatId: opts.chatId } : {}) }),
|
|
557
|
+
body: JSON.stringify({ ...(opts.mandateAgreementId ? { mandateAgreementId: opts.mandateAgreementId } : {}), ...(opts.parentAgreementId ? { parentAgreementId: opts.parentAgreementId } : {}), ...(opts.chatId ? { chatId: opts.chatId } : {}), ...(opts.connections && Object.keys(opts.connections).length ? { connections: opts.connections } : {}) }),
|
|
558
558
|
});
|
|
559
559
|
if (!res.ok) {
|
|
560
560
|
const body = await res.text().catch(() => '');
|
|
@@ -15,13 +15,13 @@ export declare class AssistantConnectionClient {
|
|
|
15
15
|
private readonly baseUrl;
|
|
16
16
|
constructor(creds: Creds, baseUrl?: string);
|
|
17
17
|
request<T>(path: string, body?: unknown): Promise<T>;
|
|
18
|
-
/**
|
|
18
|
+
/** Read existing enabled assistant hires. Provisioning is an explicit sync action. */
|
|
19
19
|
workspaces(): Promise<AssistantWorkspace[]>;
|
|
20
20
|
/** Which of the connection's assistants is on an existing thing. */
|
|
21
21
|
resolve(kind: string, id: string): Promise<{
|
|
22
22
|
workspace: AssistantWorkspace | null;
|
|
23
23
|
choices: AssistantWorkspace[];
|
|
24
24
|
}>;
|
|
25
|
-
inbox(waitSeconds?: number): Promise<Record<string, unknown>>;
|
|
25
|
+
inbox(waitSeconds?: number, snapshot?: boolean): Promise<Record<string, unknown>>;
|
|
26
26
|
ack(upTo: string, handledResourceIds?: string[]): Promise<unknown>;
|
|
27
27
|
}
|
|
@@ -31,7 +31,7 @@ export class AssistantConnectionClient {
|
|
|
31
31
|
}
|
|
32
32
|
return JSON.parse(text);
|
|
33
33
|
}
|
|
34
|
-
/**
|
|
34
|
+
/** Read existing enabled assistant hires. Provisioning is an explicit sync action. */
|
|
35
35
|
async workspaces() {
|
|
36
36
|
return (await this.request('workspaces'))
|
|
37
37
|
.workspaces;
|
|
@@ -40,8 +40,8 @@ export class AssistantConnectionClient {
|
|
|
40
40
|
resolve(kind, id) {
|
|
41
41
|
return this.request(`resolve?${new URLSearchParams({ kind, id })}`);
|
|
42
42
|
}
|
|
43
|
-
inbox(waitSeconds = 0) {
|
|
44
|
-
return this.request(`inbox?wait=${waitSeconds}`);
|
|
43
|
+
inbox(waitSeconds = 0, snapshot = false) {
|
|
44
|
+
return this.request(`inbox${snapshot ? "/snapshot" : ""}?wait=${waitSeconds}`);
|
|
45
45
|
}
|
|
46
46
|
ack(upTo, handledResourceIds = []) {
|
|
47
47
|
return this.request('inbox/ack', { upTo, handledResourceIds });
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ChatReadDto } from '@ziggs-ai/contracts';
|
|
2
|
-
import type { MessageDelivery, SendChatMessageResult as WireSendChatMessageResult } from '@ziggs-ai/contracts';
|
|
2
|
+
import type { MessageDelivery as WireMessageDelivery, SendChatMessageResult as WireSendChatMessageResult } from '@ziggs-ai/contracts';
|
|
3
3
|
import { type Creds } from '../types.js';
|
|
4
4
|
/**
|
|
5
5
|
* One room from `GET /chats/mine`, as the server sends it.
|
|
@@ -85,7 +85,12 @@ export interface SendChatMessageInput {
|
|
|
85
85
|
* `wakesAgent` field that once rode along was removed for exactly that reason
|
|
86
86
|
* and is not coming back.
|
|
87
87
|
*/
|
|
88
|
-
export type
|
|
88
|
+
export type MessageDelivery = WireMessageDelivery & {
|
|
89
|
+
assignee?: string | null;
|
|
90
|
+
reachable?: 'yes' | 'no';
|
|
91
|
+
reason?: string;
|
|
92
|
+
lastRead?: string | null;
|
|
93
|
+
};
|
|
89
94
|
/**
|
|
90
95
|
* The send result as a caller here holds it.
|
|
91
96
|
*
|
|
@@ -20,6 +20,10 @@ export declare class InboxClient {
|
|
|
20
20
|
constructor(operatorKey: string, agentId?: string, baseUrl?: string, instanceId?: string);
|
|
21
21
|
private headers;
|
|
22
22
|
private inboxUrl;
|
|
23
|
+
/** Read without acquiring worker ownership or recording receipts. */
|
|
24
|
+
getSnapshot(opts?: InboxReadOptions & {
|
|
25
|
+
signal?: AbortSignal;
|
|
26
|
+
}): Promise<InboxEnvelope>;
|
|
23
27
|
getInbox(opts?: InboxReadOptions & {
|
|
24
28
|
signal?: AbortSignal;
|
|
25
29
|
}): Promise<InboxEnvelope>;
|
package/dist/http/InboxClient.js
CHANGED
|
@@ -47,6 +47,16 @@ export class InboxClient {
|
|
|
47
47
|
}
|
|
48
48
|
return url.toString();
|
|
49
49
|
}
|
|
50
|
+
/** Read without acquiring worker ownership or recording receipts. */
|
|
51
|
+
async getSnapshot(opts = {}) {
|
|
52
|
+
const res = await fetch(this.inboxUrl('/inbox/snapshot', opts), {
|
|
53
|
+
method: 'GET', headers: this.headers(), signal: opts.signal,
|
|
54
|
+
});
|
|
55
|
+
const body = await res.text().catch(() => '');
|
|
56
|
+
if (!res.ok)
|
|
57
|
+
throw pollSurfaceError('InboxClient.getSnapshot', res, body);
|
|
58
|
+
return JSON.parse(body);
|
|
59
|
+
}
|
|
50
60
|
async getInbox(opts = {}) {
|
|
51
61
|
const res = await fetch(this.inboxUrl('/inbox', opts), {
|
|
52
62
|
headers: this.headers(),
|
|
@@ -10,10 +10,6 @@ export interface WalletBalance {
|
|
|
10
10
|
balance: number;
|
|
11
11
|
availableBalance: number;
|
|
12
12
|
}
|
|
13
|
-
export interface PaymentTransactionView {
|
|
14
|
-
transactionId?: string;
|
|
15
|
-
[key: string]: unknown;
|
|
16
|
-
}
|
|
17
13
|
/**
|
|
18
14
|
* A resolved wallet reference (GET /payments/wallets/resolve).
|
|
19
15
|
*
|
|
@@ -24,27 +20,6 @@ export interface PaymentTransactionView {
|
|
|
24
20
|
*/
|
|
25
21
|
import type { WalletRef } from '@ziggs-ai/contracts';
|
|
26
22
|
export type { WalletRef };
|
|
27
|
-
export type TransferResult = {
|
|
28
|
-
status: 'approval_required';
|
|
29
|
-
approvalId: string | null;
|
|
30
|
-
expiresAt: string | null;
|
|
31
|
-
reason: string | null;
|
|
32
|
-
toWalletId: string;
|
|
33
|
-
amount: number;
|
|
34
|
-
} | {
|
|
35
|
-
status: 'transferred';
|
|
36
|
-
transactionId: string | null;
|
|
37
|
-
toWalletId: string;
|
|
38
|
-
amount: number;
|
|
39
|
-
};
|
|
40
|
-
export interface HoldResult {
|
|
41
|
-
transaction?: PaymentTransactionView;
|
|
42
|
-
[key: string]: unknown;
|
|
43
|
-
}
|
|
44
|
-
export interface ReleaseResult {
|
|
45
|
-
transaction?: PaymentTransactionView;
|
|
46
|
-
[key: string]: unknown;
|
|
47
|
-
}
|
|
48
23
|
/**
|
|
49
24
|
* A payment grant as this rail answers it.
|
|
50
25
|
*
|
|
@@ -69,40 +44,14 @@ export interface RevokeGrantResult {
|
|
|
69
44
|
revoked?: unknown;
|
|
70
45
|
[key: string]: unknown;
|
|
71
46
|
}
|
|
72
|
-
export interface PaymentApproval {
|
|
73
|
-
approvalId?: string;
|
|
74
|
-
status?: string;
|
|
75
|
-
executedTransactionId?: string | null;
|
|
76
|
-
[key: string]: unknown;
|
|
77
|
-
}
|
|
78
|
-
export type WaitForApprovalResult = {
|
|
79
|
-
status: 'gone';
|
|
80
|
-
} | {
|
|
81
|
-
status: 'executed';
|
|
82
|
-
approval: PaymentApproval;
|
|
83
|
-
transactionId: string | null;
|
|
84
|
-
} | {
|
|
85
|
-
status: 'rejected' | 'expired' | 'timeout';
|
|
86
|
-
approval: PaymentApproval;
|
|
87
|
-
};
|
|
88
|
-
/**
|
|
89
|
-
* Who to pay on a transfer. Kind is declared by the caller — never inferred
|
|
90
|
-
* from id shape. A bare string is only a wallet id (`wal_…`).
|
|
91
|
-
*/
|
|
92
|
-
export type TransferPayee = string | {
|
|
93
|
-
walletId: string;
|
|
94
|
-
} | {
|
|
95
|
-
agentId: string;
|
|
96
|
-
} | {
|
|
97
|
-
userId: string;
|
|
98
|
-
};
|
|
99
47
|
/**
|
|
100
48
|
* the one payments client for every surface (agent-sdk, ziggs-mcp,
|
|
101
49
|
* scripts). Consolidates the former agent-sdk private ZiggsPayClient. Wallet
|
|
102
50
|
* ops ride the operator key; agent-impersonated calls send X-Agent-Id and are
|
|
103
|
-
* policy-gated server-side
|
|
104
|
-
*
|
|
105
|
-
*
|
|
51
|
+
* policy-gated server-side. Points move only when an agreement or a task
|
|
52
|
+
* settles, and the server does that; there is no standalone movement verb on
|
|
53
|
+
* this client. Wallet-rail grant mutations (issue / attenuate / revoke) live
|
|
54
|
+
* here; grant *listing* is the unified GET /grants.
|
|
106
55
|
*/
|
|
107
56
|
export declare class PaymentsClient {
|
|
108
57
|
private readonly operatorKey;
|
|
@@ -128,60 +77,6 @@ export declare class PaymentsClient {
|
|
|
128
77
|
userId?: string;
|
|
129
78
|
agentId?: string;
|
|
130
79
|
}): Promise<WalletRef | null>;
|
|
131
|
-
/**
|
|
132
|
-
* Resolve `transfer.to` to a wallet id. String form is wallet-only; agent /
|
|
133
|
-
* user payees must name their kind.
|
|
134
|
-
*/
|
|
135
|
-
private resolvePayeeWalletId;
|
|
136
|
-
transfer({ to, amount, idempotencyKey, description, paymentGrantId, agreementId, }: {
|
|
137
|
-
to: TransferPayee;
|
|
138
|
-
amount: number;
|
|
139
|
-
idempotencyKey?: string;
|
|
140
|
-
description?: string;
|
|
141
|
-
paymentGrantId?: string;
|
|
142
|
-
/**
|
|
143
|
-
* The hire this spend belongs to. Used for attribution, and to prefer that
|
|
144
|
-
* hire's own grant over a standing one — a preference about which budget is
|
|
145
|
-
* billed, never about which grants may be spent. That answer is the
|
|
146
|
-
* server's, and it turns on who gave the grant.
|
|
147
|
-
*/
|
|
148
|
-
agreementId?: string;
|
|
149
|
-
}): Promise<TransferResult>;
|
|
150
|
-
hold({ amount, idempotencyKey, description, paymentGrantId, agreementId, }: {
|
|
151
|
-
amount: number;
|
|
152
|
-
idempotencyKey?: string;
|
|
153
|
-
description?: string;
|
|
154
|
-
paymentGrantId?: string;
|
|
155
|
-
/**
|
|
156
|
-
* The hire this spend belongs to. Used for attribution, and to prefer that
|
|
157
|
-
* hire's own grant over a standing one — a preference about which budget is
|
|
158
|
-
* billed, never about which grants may be spent. That answer is the
|
|
159
|
-
* server's, and it turns on who gave the grant.
|
|
160
|
-
*/
|
|
161
|
-
agreementId?: string;
|
|
162
|
-
}): Promise<HoldResult>;
|
|
163
|
-
/**
|
|
164
|
-
* Choose the grant that actually covers this spend.
|
|
165
|
-
*
|
|
166
|
-
* This used to be `grants[0]` — the first active grant, matched against
|
|
167
|
-
* nothing. An agent holding both its standing grant and an
|
|
168
|
-
* agreement-conferred one presented whichever came back first, the backend
|
|
169
|
-
* correctly refused it against wallet or amount, and the agent was refused a
|
|
170
|
-
* spend it was genuinely authorized to make.
|
|
171
|
-
*
|
|
172
|
-
* The source wallet is read rather than assumed: a grant on some other
|
|
173
|
-
* wallet cannot authorize money leaving this one, and the agent may hold
|
|
174
|
-
* grants on a counterparty's wallet from an engagement. A failure to read it
|
|
175
|
-
* is not fatal — selection then skips the wallet test and the server still
|
|
176
|
-
* validates, which is the same position we were in before.
|
|
177
|
-
*/
|
|
178
|
-
private selectPaymentGrant;
|
|
179
|
-
release({ holdId, action, toWalletId, idempotencyKey, }: {
|
|
180
|
-
holdId: string;
|
|
181
|
-
action?: string;
|
|
182
|
-
toWalletId?: string;
|
|
183
|
-
idempotencyKey?: string;
|
|
184
|
-
}): Promise<ReleaseResult>;
|
|
185
80
|
history(params?: {
|
|
186
81
|
limit?: number;
|
|
187
82
|
offset?: number;
|
|
@@ -213,16 +108,6 @@ export declare class PaymentsClient {
|
|
|
213
108
|
description?: string;
|
|
214
109
|
idempotencyKey?: string;
|
|
215
110
|
}): Promise<unknown>;
|
|
216
|
-
approvals({ status }?: {
|
|
217
|
-
status?: string;
|
|
218
|
-
}): Promise<PaymentApproval[]>;
|
|
219
|
-
getApproval(approvalId: string): Promise<PaymentApproval | null>;
|
|
220
|
-
decide(approvalId: string, decision: string, note?: string): Promise<unknown>;
|
|
221
|
-
waitForApproval(approvalId: string, opts?: {
|
|
222
|
-
pollMs?: number;
|
|
223
|
-
timeoutMs?: number;
|
|
224
|
-
signal?: AbortSignal;
|
|
225
|
-
}): Promise<WaitForApprovalResult>;
|
|
226
111
|
private _request;
|
|
227
112
|
private _get;
|
|
228
113
|
private _post;
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { getBackendUrl } from '../utils/urlUtils.js';
|
|
2
2
|
import { buildOperatorHeaders } from './operatorHeaders.js';
|
|
3
3
|
import { GrantsClient } from './GrantsClient.js';
|
|
4
|
-
import { describeNoGrant, selectPaymentGrant, } from './paymentGrantSelection.js';
|
|
5
4
|
import { throwApiError } from '../shared/apiError.js';
|
|
6
5
|
function randomIdempotencyKey(prefix = 'op') {
|
|
7
6
|
return `${prefix}_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 10)}`;
|
|
@@ -10,9 +9,10 @@ function randomIdempotencyKey(prefix = 'op') {
|
|
|
10
9
|
* the one payments client for every surface (agent-sdk, ziggs-mcp,
|
|
11
10
|
* scripts). Consolidates the former agent-sdk private ZiggsPayClient. Wallet
|
|
12
11
|
* ops ride the operator key; agent-impersonated calls send X-Agent-Id and are
|
|
13
|
-
* policy-gated server-side
|
|
14
|
-
*
|
|
15
|
-
*
|
|
12
|
+
* policy-gated server-side. Points move only when an agreement or a task
|
|
13
|
+
* settles, and the server does that; there is no standalone movement verb on
|
|
14
|
+
* this client. Wallet-rail grant mutations (issue / attenuate / revoke) live
|
|
15
|
+
* here; grant *listing* is the unified GET /grants.
|
|
16
16
|
*/
|
|
17
17
|
export class PaymentsClient {
|
|
18
18
|
operatorKey;
|
|
@@ -61,139 +61,6 @@ export class PaymentsClient {
|
|
|
61
61
|
const res = (await this._get(`/payments/wallets/resolve?${params}`));
|
|
62
62
|
return res['wallet'] || null;
|
|
63
63
|
}
|
|
64
|
-
/**
|
|
65
|
-
* Resolve `transfer.to` to a wallet id. String form is wallet-only; agent /
|
|
66
|
-
* user payees must name their kind.
|
|
67
|
-
*/
|
|
68
|
-
async resolvePayeeWalletId(to) {
|
|
69
|
-
if (typeof to === 'string') {
|
|
70
|
-
const id = to.trim();
|
|
71
|
-
if (!id)
|
|
72
|
-
throw new Error('transfer: `to` is required');
|
|
73
|
-
if (id.startsWith('wal_'))
|
|
74
|
-
return id;
|
|
75
|
-
throw new Error('transfer: string `to` must be a wallet id (wal_…); pass { agentId } or { userId } for a principal');
|
|
76
|
-
}
|
|
77
|
-
if ('walletId' in to && typeof to.walletId === 'string' && to.walletId.trim()) {
|
|
78
|
-
return to.walletId.trim();
|
|
79
|
-
}
|
|
80
|
-
if ('agentId' in to && typeof to.agentId === 'string' && to.agentId.trim()) {
|
|
81
|
-
const w = await this.resolve({ agentId: to.agentId.trim() });
|
|
82
|
-
if (!w?.walletId) {
|
|
83
|
-
throw new Error(`transfer: could not resolve wallet for agent "${to.agentId}"`);
|
|
84
|
-
}
|
|
85
|
-
return w.walletId;
|
|
86
|
-
}
|
|
87
|
-
if ('userId' in to && typeof to.userId === 'string' && to.userId.trim()) {
|
|
88
|
-
const w = await this.resolve({ userId: to.userId.trim() });
|
|
89
|
-
if (!w?.walletId) {
|
|
90
|
-
throw new Error(`transfer: could not resolve wallet for user "${to.userId}"`);
|
|
91
|
-
}
|
|
92
|
-
return w.walletId;
|
|
93
|
-
}
|
|
94
|
-
throw new Error('transfer: `to` must be a wallet id (wal_…) or { walletId | agentId | userId }');
|
|
95
|
-
}
|
|
96
|
-
async transfer({ to, amount, idempotencyKey, description, paymentGrantId, agreementId, }) {
|
|
97
|
-
if (to == null || to === '')
|
|
98
|
-
throw new Error('transfer: `to` is required');
|
|
99
|
-
if (!(Number.isInteger(amount) && amount > 0))
|
|
100
|
-
throw new Error('transfer: `amount` must be a positive integer (cents)');
|
|
101
|
-
// The recipient is resolved BEFORE choosing a grant: an `allowed_recipients`
|
|
102
|
-
// caveat cannot be checked against a name the client has not resolved yet.
|
|
103
|
-
const toWalletId = await this.resolvePayeeWalletId(to);
|
|
104
|
-
// Choose the grant that covers this spend when the caller omitted one.
|
|
105
|
-
// An explicit id still wins — a caller who names a grant means it.
|
|
106
|
-
if (this.agentId && !paymentGrantId) {
|
|
107
|
-
const picked = await this.selectPaymentGrant({ amount, toWalletId, agreementId });
|
|
108
|
-
if (!picked.grant) {
|
|
109
|
-
throw new Error(describeNoGrant('transfer', { amount, toWalletId, agreementId }, picked.rejected));
|
|
110
|
-
}
|
|
111
|
-
paymentGrantId = picked.grant.grantId;
|
|
112
|
-
}
|
|
113
|
-
const result = (await this._post('/payments/transfer', {
|
|
114
|
-
toWalletId,
|
|
115
|
-
amount,
|
|
116
|
-
idempotencyKey: idempotencyKey || randomIdempotencyKey('xfer'),
|
|
117
|
-
description,
|
|
118
|
-
paymentGrantId,
|
|
119
|
-
}));
|
|
120
|
-
if (result?.['status'] === 'approval_required') {
|
|
121
|
-
const approval = result['approval'] || {};
|
|
122
|
-
return {
|
|
123
|
-
status: 'approval_required',
|
|
124
|
-
approvalId: approval['approvalId'] || null,
|
|
125
|
-
expiresAt: approval['expiresAt'] || null,
|
|
126
|
-
reason: approval['reason'] || 'Amount exceeds auto-approve threshold',
|
|
127
|
-
toWalletId,
|
|
128
|
-
amount,
|
|
129
|
-
};
|
|
130
|
-
}
|
|
131
|
-
const tx = result?.['transaction'];
|
|
132
|
-
return {
|
|
133
|
-
status: 'transferred',
|
|
134
|
-
transactionId: tx?.['transactionId'] || null,
|
|
135
|
-
toWalletId,
|
|
136
|
-
amount,
|
|
137
|
-
};
|
|
138
|
-
}
|
|
139
|
-
async hold({ amount, idempotencyKey, description, paymentGrantId, agreementId, }) {
|
|
140
|
-
if (!(Number.isInteger(amount) && amount > 0))
|
|
141
|
-
throw new Error('hold: `amount` must be a positive integer (cents)');
|
|
142
|
-
// A hold reserves the owner's money, so it needs a grant like a transfer.
|
|
143
|
-
// It has no counterparty, so no recipient is offered to the caveats.
|
|
144
|
-
if (this.agentId && !paymentGrantId) {
|
|
145
|
-
const picked = await this.selectPaymentGrant({ amount, agreementId });
|
|
146
|
-
if (!picked.grant) {
|
|
147
|
-
throw new Error(describeNoGrant('hold', { amount, agreementId }, picked.rejected));
|
|
148
|
-
}
|
|
149
|
-
paymentGrantId = picked.grant.grantId;
|
|
150
|
-
}
|
|
151
|
-
return (await this._post('/payments/hold', {
|
|
152
|
-
amount,
|
|
153
|
-
idempotencyKey: idempotencyKey || randomIdempotencyKey('hold'),
|
|
154
|
-
description,
|
|
155
|
-
paymentGrantId,
|
|
156
|
-
}));
|
|
157
|
-
}
|
|
158
|
-
/**
|
|
159
|
-
* Choose the grant that actually covers this spend.
|
|
160
|
-
*
|
|
161
|
-
* This used to be `grants[0]` — the first active grant, matched against
|
|
162
|
-
* nothing. An agent holding both its standing grant and an
|
|
163
|
-
* agreement-conferred one presented whichever came back first, the backend
|
|
164
|
-
* correctly refused it against wallet or amount, and the agent was refused a
|
|
165
|
-
* spend it was genuinely authorized to make.
|
|
166
|
-
*
|
|
167
|
-
* The source wallet is read rather than assumed: a grant on some other
|
|
168
|
-
* wallet cannot authorize money leaving this one, and the agent may hold
|
|
169
|
-
* grants on a counterparty's wallet from an engagement. A failure to read it
|
|
170
|
-
* is not fatal — selection then skips the wallet test and the server still
|
|
171
|
-
* validates, which is the same position we were in before.
|
|
172
|
-
*/
|
|
173
|
-
async selectPaymentGrant(spend) {
|
|
174
|
-
let grants = [];
|
|
175
|
-
try {
|
|
176
|
-
grants = await this.listGrants();
|
|
177
|
-
}
|
|
178
|
-
catch {
|
|
179
|
-
return { grant: null, rejected: [] };
|
|
180
|
-
}
|
|
181
|
-
let fromWalletId = null;
|
|
182
|
-
try {
|
|
183
|
-
fromWalletId = (await this.balance()).walletId;
|
|
184
|
-
}
|
|
185
|
-
catch {
|
|
186
|
-
fromWalletId = null;
|
|
187
|
-
}
|
|
188
|
-
return selectPaymentGrant(grants, { ...spend, fromWalletId });
|
|
189
|
-
}
|
|
190
|
-
async release({ holdId, action = 'complete', toWalletId, idempotencyKey, }) {
|
|
191
|
-
return (await this._post(`/payments/release/${holdId}`, {
|
|
192
|
-
idempotencyKey: idempotencyKey || randomIdempotencyKey('rel'),
|
|
193
|
-
action,
|
|
194
|
-
toWalletId,
|
|
195
|
-
}));
|
|
196
|
-
}
|
|
197
64
|
async history(params = {}) {
|
|
198
65
|
const q = new URLSearchParams();
|
|
199
66
|
if (params.limit != null)
|
|
@@ -237,68 +104,6 @@ export class PaymentsClient {
|
|
|
237
104
|
description,
|
|
238
105
|
});
|
|
239
106
|
}
|
|
240
|
-
async approvals({ status = 'pending' } = {}) {
|
|
241
|
-
const res = (await this._get(`/payments/approvals?status=${encodeURIComponent(status)}`));
|
|
242
|
-
return res['approvals'] || [];
|
|
243
|
-
}
|
|
244
|
-
async getApproval(approvalId) {
|
|
245
|
-
if (!approvalId)
|
|
246
|
-
throw new Error('getApproval: approvalId is required');
|
|
247
|
-
try {
|
|
248
|
-
const res = (await this._get(`/payments/approvals/${approvalId}`));
|
|
249
|
-
return res?.['approval'] || null;
|
|
250
|
-
}
|
|
251
|
-
catch (err) {
|
|
252
|
-
if (err.status === 404)
|
|
253
|
-
return null;
|
|
254
|
-
throw err;
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
async decide(approvalId, decision, note = '') {
|
|
258
|
-
return this._post(`/payments/approvals/${approvalId}/decide`, { decision, note });
|
|
259
|
-
}
|
|
260
|
-
async waitForApproval(approvalId, opts = {}) {
|
|
261
|
-
const pollMs = Math.max(500, opts.pollMs ?? 3_000);
|
|
262
|
-
const timeoutMs = Math.max(pollMs, opts.timeoutMs ?? 120_000);
|
|
263
|
-
const signal = opts.signal;
|
|
264
|
-
const deadline = Date.now() + timeoutMs;
|
|
265
|
-
const sleep = (ms) => new Promise((resolve, reject) => {
|
|
266
|
-
const timer = setTimeout(resolve, ms);
|
|
267
|
-
if (signal) {
|
|
268
|
-
if (signal.aborted) {
|
|
269
|
-
clearTimeout(timer);
|
|
270
|
-
reject(new Error('aborted'));
|
|
271
|
-
return;
|
|
272
|
-
}
|
|
273
|
-
signal.addEventListener('abort', () => {
|
|
274
|
-
clearTimeout(timer);
|
|
275
|
-
reject(new Error('aborted'));
|
|
276
|
-
}, { once: true });
|
|
277
|
-
}
|
|
278
|
-
});
|
|
279
|
-
while (true) {
|
|
280
|
-
if (signal?.aborted)
|
|
281
|
-
throw new Error('aborted');
|
|
282
|
-
const approval = await this.getApproval(approvalId);
|
|
283
|
-
if (!approval)
|
|
284
|
-
return { status: 'gone' };
|
|
285
|
-
const s = approval.status;
|
|
286
|
-
if (s === 'executed')
|
|
287
|
-
return {
|
|
288
|
-
status: 'executed',
|
|
289
|
-
approval,
|
|
290
|
-
transactionId: approval.executedTransactionId || null,
|
|
291
|
-
};
|
|
292
|
-
if (s === 'rejected')
|
|
293
|
-
return { status: 'rejected', approval };
|
|
294
|
-
if (s === 'expired')
|
|
295
|
-
return { status: 'expired', approval };
|
|
296
|
-
const remaining = deadline - Date.now();
|
|
297
|
-
if (remaining <= 0)
|
|
298
|
-
return { status: 'timeout', approval };
|
|
299
|
-
await sleep(Math.min(pollMs, remaining));
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
107
|
async _request(method, path, body) {
|
|
303
108
|
const init = {
|
|
304
109
|
method,
|
package/dist/http/index.d.ts
CHANGED
|
@@ -22,7 +22,7 @@ export type { ContextGrantRecord, ContextGrantScope, ContextGrantScopeKind, Cont
|
|
|
22
22
|
export { grantCaveat, CONTEXT_GRANT_SCOPE_KINDS, GRANT_SCOPE_KINDS, } from './grants.js';
|
|
23
23
|
export type { GrantView, GrantScopeKind, GrantScopeView, GrantCaveatView, GrantHealth, GrantHolderKind, GrantAccessKind, GrantBasisKind, } from './grants.js';
|
|
24
24
|
export { PaymentsClient } from './PaymentsClient.js';
|
|
25
|
-
export type { PaymentsError, WalletBalance, WalletRef,
|
|
25
|
+
export type { PaymentsError, WalletBalance, WalletRef, PaymentGrantView, PaymentGrantEnvelope, RevokeGrantResult, } from './PaymentsClient.js';
|
|
26
26
|
export { ConnectionsClient, assertNoLeakedConnectionSecret, } from './ConnectionsClient.js';
|
|
27
27
|
export type { ConnectionsError, ConnectionProxyParams, ConnectionGrant, ConnectionWithGrants, McpConnectionRequestParams, } from './ConnectionsClient.js';
|
|
28
28
|
export { fetchMyOrgs, resolveOrgSelector, fetchDelegateAccess, fetchHostedAgentAccess, fetchSessionAccess, } from './OrgsClient.js';
|
package/package.json
CHANGED
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
import { type GrantView } from './grants.js';
|
|
2
|
-
/**
|
|
3
|
-
* Which grant to present for a spend, and — when none fits — why each candidate
|
|
4
|
-
* did not.
|
|
5
|
-
*
|
|
6
|
-
* The client used to take `grants[0]` from the active list. It matched nothing
|
|
7
|
-
* else: not the source wallet, not the amount, not the agreement the spend
|
|
8
|
-
* belongs to. An agent holding both its standing grant and an agreement-conferred
|
|
9
|
-
* one presented whichever came back first, the backend correctly refused it
|
|
10
|
-
* against wallet or amount, and the agent was refused a spend it was genuinely
|
|
11
|
-
* authorized to make.
|
|
12
|
-
*
|
|
13
|
-
* Selection is a pure function of the grants and the spend so it can be tested
|
|
14
|
-
* without a server, and so the refusal can name what it considered.
|
|
15
|
-
*/
|
|
16
|
-
export interface SpendShape {
|
|
17
|
-
/** The wallet the money leaves. A grant on another wallet cannot authorize it. */
|
|
18
|
-
fromWalletId?: string | null;
|
|
19
|
-
/** Cents. Checked against `max_amount`. */
|
|
20
|
-
amount: number;
|
|
21
|
-
/** The wallet receiving it, when known. Checked against `allowed_recipients`. */
|
|
22
|
-
toWalletId?: string | null;
|
|
23
|
-
/** The engagement this spend belongs to, when the caller names one. */
|
|
24
|
-
agreementId?: string | null;
|
|
25
|
-
}
|
|
26
|
-
export interface RejectedGrant {
|
|
27
|
-
grantId: string;
|
|
28
|
-
reason: string;
|
|
29
|
-
}
|
|
30
|
-
export interface GrantSelection {
|
|
31
|
-
grant: GrantView | null;
|
|
32
|
-
/** Every grant that was looked at and did not fit, with the reason. */
|
|
33
|
-
rejected: RejectedGrant[];
|
|
34
|
-
}
|
|
35
|
-
/**
|
|
36
|
-
* Pick the grant to present, preferring the one that belongs to the engagement.
|
|
37
|
-
*
|
|
38
|
-
* Order among grants that all fit: the agreement's own grant first when the
|
|
39
|
-
* spend names one, then the standing grant. A standing grant is the buyer
|
|
40
|
-
* bootstrap and the right default; reaching for it while an agreement grant
|
|
41
|
-
* covers the same spend would bill the wrong budget.
|
|
42
|
-
*
|
|
43
|
-
* This is a preference, never a fence. Every grant here is already one the
|
|
44
|
-
* server said this wake may spend, so picking a different one cannot reach
|
|
45
|
-
* another party's authority — it would only bill the wrong budget of the same
|
|
46
|
-
* party. That is why the mismatch is no longer a rejection above.
|
|
47
|
-
*/
|
|
48
|
-
export declare function selectPaymentGrant(grants: readonly GrantView[], spend: SpendShape): GrantSelection;
|
|
49
|
-
/**
|
|
50
|
-
* The sentence an agent gets when nothing fits.
|
|
51
|
-
*
|
|
52
|
-
* It names every grant considered and why each was not it, because "no payment
|
|
53
|
-
* grant" is a condition and an agent cannot act on a condition — it retries,
|
|
54
|
-
* and every retry is a full model call. Which ceiling was in the way is the
|
|
55
|
-
* thing that tells the agent whether to ask for a bigger grant, name the
|
|
56
|
-
* agreement, or stop.
|
|
57
|
-
*/
|
|
58
|
-
export declare function describeNoGrant(verb: string, spend: SpendShape, rejected: readonly RejectedGrant[]): string;
|
|
@@ -1,121 +0,0 @@
|
|
|
1
|
-
import { grantCaveat } from './grants.js';
|
|
2
|
-
/**
|
|
3
|
-
* A caveat off a grant that may not carry the array at all.
|
|
4
|
-
*
|
|
5
|
-
* `grantCaveat` assumes `caveats` is populated, which the unified list read
|
|
6
|
-
* does. A caller handing us a partial view should get "no such caveat", not a
|
|
7
|
-
* crash — this runs on the spend path, where throwing would turn a missing
|
|
8
|
-
* field into a refused payment.
|
|
9
|
-
*/
|
|
10
|
-
function caveatValue(grant, type) {
|
|
11
|
-
const caveats = grant.caveats;
|
|
12
|
-
if (!Array.isArray(caveats))
|
|
13
|
-
return undefined;
|
|
14
|
-
return grantCaveat(grant, type);
|
|
15
|
-
}
|
|
16
|
-
function numericCaveat(grant, type) {
|
|
17
|
-
const raw = caveatValue(grant, type);
|
|
18
|
-
return typeof raw === 'number' && Number.isFinite(raw) ? raw : null;
|
|
19
|
-
}
|
|
20
|
-
function recipientCaveat(grant) {
|
|
21
|
-
const raw = caveatValue(grant, 'allowed_recipients');
|
|
22
|
-
if (!Array.isArray(raw))
|
|
23
|
-
return null;
|
|
24
|
-
const ids = raw.filter((r) => typeof r === 'string');
|
|
25
|
-
return ids.length ? ids : null;
|
|
26
|
-
}
|
|
27
|
-
/**
|
|
28
|
-
* Why this grant cannot authorize this spend, or null if it can.
|
|
29
|
-
*
|
|
30
|
-
* `daily_budget` is deliberately not checked. It is a ceiling on spend already
|
|
31
|
-
* made, which only the server can see — guessing here would refuse a grant that
|
|
32
|
-
* would have worked. The server validates it inside the same transaction as the
|
|
33
|
-
* ledger write, which is the only place the answer is not already stale.
|
|
34
|
-
*/
|
|
35
|
-
function whyNot(grant, spend) {
|
|
36
|
-
// A stated kind that is not `wallet` is disqualifying. An ABSENT one is not:
|
|
37
|
-
// `listGrants` asks the server for `scopeKind: 'wallet'`, so the rail has
|
|
38
|
-
// already answered, and treating a field the response did not populate as a
|
|
39
|
-
// wrong answer would refuse every grant. Same reading as an unread source
|
|
40
|
-
// wallet below — absence means unknown, never no.
|
|
41
|
-
if (grant.scope?.kind && grant.scope.kind !== 'wallet') {
|
|
42
|
-
return `is a ${grant.scope.kind} grant, not a wallet grant`;
|
|
43
|
-
}
|
|
44
|
-
if (spend.fromWalletId && grant.scope?.id && grant.scope.id !== spend.fromWalletId) {
|
|
45
|
-
return `is on wallet ${grant.scope.id}, and this spend leaves ${spend.fromWalletId}`;
|
|
46
|
-
}
|
|
47
|
-
const maxAmount = numericCaveat(grant, 'max_amount');
|
|
48
|
-
if (maxAmount !== null && spend.amount > maxAmount) {
|
|
49
|
-
return `caps a single spend at ${maxAmount}, and this one is ${spend.amount}`;
|
|
50
|
-
}
|
|
51
|
-
const allowed = recipientCaveat(grant);
|
|
52
|
-
if (allowed && spend.toWalletId && !allowed.includes(spend.toWalletId)) {
|
|
53
|
-
return `may only pay ${allowed.join(', ')}, and this one pays ${spend.toWalletId}`;
|
|
54
|
-
}
|
|
55
|
-
// A grant's agreement is NOT checked here, deliberately.
|
|
56
|
-
//
|
|
57
|
-
// It used to be refused when it did not match the spend's agreement, which
|
|
58
|
-
// made a customer's own grant unusable inside their second hire of the same
|
|
59
|
-
// agent, and made a standing grant unusable on any spend that named a deal.
|
|
60
|
-
// Whether a grant may be spent at all is the server's answer, and it turns on
|
|
61
|
-
// who GAVE the grant against who the agent is acting for — not on which of
|
|
62
|
-
// that party's deals is running. A list the server answered for this wake
|
|
63
|
-
// holds only grants it may spend, so there is nothing left to refuse here.
|
|
64
|
-
//
|
|
65
|
-
// The agreement still decides the ORDER below, which is a budget preference
|
|
66
|
-
// rather than a question of authority.
|
|
67
|
-
return null;
|
|
68
|
-
}
|
|
69
|
-
/**
|
|
70
|
-
* Pick the grant to present, preferring the one that belongs to the engagement.
|
|
71
|
-
*
|
|
72
|
-
* Order among grants that all fit: the agreement's own grant first when the
|
|
73
|
-
* spend names one, then the standing grant. A standing grant is the buyer
|
|
74
|
-
* bootstrap and the right default; reaching for it while an agreement grant
|
|
75
|
-
* covers the same spend would bill the wrong budget.
|
|
76
|
-
*
|
|
77
|
-
* This is a preference, never a fence. Every grant here is already one the
|
|
78
|
-
* server said this wake may spend, so picking a different one cannot reach
|
|
79
|
-
* another party's authority — it would only bill the wrong budget of the same
|
|
80
|
-
* party. That is why the mismatch is no longer a rejection above.
|
|
81
|
-
*/
|
|
82
|
-
export function selectPaymentGrant(grants, spend) {
|
|
83
|
-
const rejected = [];
|
|
84
|
-
const fitting = [];
|
|
85
|
-
for (const grant of grants) {
|
|
86
|
-
const reason = whyNot(grant, spend);
|
|
87
|
-
if (reason)
|
|
88
|
-
rejected.push({ grantId: grant.grantId, reason });
|
|
89
|
-
else
|
|
90
|
-
fitting.push(grant);
|
|
91
|
-
}
|
|
92
|
-
if (fitting.length === 0)
|
|
93
|
-
return { grant: null, rejected };
|
|
94
|
-
// Explicit both ways, because the deal is no longer a fence: until it was
|
|
95
|
-
// removed, a deal-bound grant simply never reached this list on a spend that
|
|
96
|
-
// named nothing, so "prefer the standing grant" happened by accident of the
|
|
97
|
-
// rejection. Now it has to be said.
|
|
98
|
-
const standing = fitting.find((g) => !g.agreementId);
|
|
99
|
-
const preferred = spend.agreementId
|
|
100
|
-
? (fitting.find((g) => g.agreementId === spend.agreementId) ?? standing)
|
|
101
|
-
: standing;
|
|
102
|
-
return { grant: preferred ?? fitting[0], rejected };
|
|
103
|
-
}
|
|
104
|
-
/**
|
|
105
|
-
* The sentence an agent gets when nothing fits.
|
|
106
|
-
*
|
|
107
|
-
* It names every grant considered and why each was not it, because "no payment
|
|
108
|
-
* grant" is a condition and an agent cannot act on a condition — it retries,
|
|
109
|
-
* and every retry is a full model call. Which ceiling was in the way is the
|
|
110
|
-
* thing that tells the agent whether to ask for a bigger grant, name the
|
|
111
|
-
* agreement, or stop.
|
|
112
|
-
*/
|
|
113
|
-
export function describeNoGrant(verb, spend, rejected) {
|
|
114
|
-
if (rejected.length === 0) {
|
|
115
|
-
return (`${verb}: you hold no active payment grant, so this spend cannot be ` +
|
|
116
|
-
`authorized. The wallet owner issues one; nothing you can do from here.`);
|
|
117
|
-
}
|
|
118
|
-
const lines = rejected.map((r) => ` - ${r.grantId} ${r.reason}`).join('\n');
|
|
119
|
-
return (`${verb}: none of your ${rejected.length} active payment grant(s) covers ` +
|
|
120
|
-
`this spend of ${spend.amount}.\n${lines}`);
|
|
121
|
-
}
|