@ziggs-ai/api-client 0.10.3 → 0.11.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.d.ts +2 -2
- package/dist/capabilities/agreementVerbs.js +19 -19
- package/dist/capabilities/agreements.d.ts +1 -1
- package/dist/capabilities/agreements.js +5 -5
- package/dist/capabilities/artifacts.d.ts +4 -2
- package/dist/capabilities/artifacts.js +169 -34
- package/dist/capabilities/chat.d.ts +2 -3
- package/dist/capabilities/chat.js +5 -6
- package/dist/capabilities/connections.js +1 -1
- package/dist/capabilities/context.js +3 -0
- package/dist/capabilities/grants.d.ts +7 -6
- package/dist/capabilities/grants.js +9 -8
- package/dist/capabilities/index.d.ts +2 -2
- package/dist/capabilities/index.js +2 -2
- package/dist/capabilities/links.d.ts +1 -1
- package/dist/capabilities/links.js +8 -11
- package/dist/capabilities/marketplace.js +63 -13
- package/dist/capabilities/payments.d.ts +24 -8
- package/dist/capabilities/payments.js +28 -392
- package/dist/capabilities/proposeProviderId.d.ts +1 -1
- package/dist/capabilities/proposeProviderId.js +1 -1
- package/dist/http/AgreementClient.d.ts +28 -22
- package/dist/http/AgreementClient.js +26 -17
- package/dist/http/ArtifactsClient.d.ts +3 -3
- package/dist/http/ArtifactsClient.js +6 -8
- package/dist/http/ChatClient.d.ts +1 -0
- package/dist/http/ChatClient.js +4 -1
- package/dist/http/ConnectionsClient.js +12 -1
- package/dist/http/ContextGrantsClient.d.ts +15 -1
- package/dist/http/ContextGrantsClient.js +2 -0
- package/dist/http/ContextReadClient.d.ts +18 -8
- package/dist/http/ContextReadClient.js +4 -3
- package/dist/http/GrantsClient.d.ts +14 -0
- package/dist/http/GrantsClient.js +18 -2
- package/dist/http/InboxClient.d.ts +1 -1
- package/dist/http/InboxClient.js +1 -1
- package/dist/http/MarketplaceClient.d.ts +6 -6
- package/dist/http/MarketplaceClient.js +11 -11
- package/dist/http/TaskClient.d.ts +13 -2
- package/dist/http/TaskClient.js +4 -2
- package/dist/http/agreementFlows.d.ts +4 -4
- package/dist/http/agreementFlows.js +9 -10
- package/dist/http/grants.d.ts +28 -0
- package/dist/http/index.d.ts +2 -2
- package/dist/index.d.ts +1 -1
- package/dist/types.d.ts +59 -22
- package/dist/types.js +6 -6
- package/package.json +1 -1
|
@@ -3,7 +3,7 @@ import { type CapabilityDefinition, type CapabilityEnv } from './types.js';
|
|
|
3
3
|
* A link is reach-only — the follow-up move differs by surface tool names.
|
|
4
4
|
*
|
|
5
5
|
* Opening a chat is itself the admission: the backend issues a standing from-now
|
|
6
|
-
*
|
|
6
|
+
* write grant to each side as it creates the room, so both delegates can
|
|
7
7
|
* read and post immediately. Do NOT tell agents to follow chat_open with an
|
|
8
8
|
* issue_grant on that same new chat — that only mints a duplicate grant carrying
|
|
9
9
|
* the default 30-day expiry. issue_grant / context_delegate are for scopes that
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { createLink, listAgreements } from '../http/AgreementClient.js';
|
|
2
2
|
import { nextCall, peerAgentId } from './nextCall.js';
|
|
3
3
|
import { fullCreds } from './types.js';
|
|
4
4
|
const DEFAULT_WEB_URL = 'https://ziggsai.com';
|
|
@@ -23,7 +23,7 @@ function inviteShareUrl(env, agreementId) {
|
|
|
23
23
|
* A link is reach-only — the follow-up move differs by surface tool names.
|
|
24
24
|
*
|
|
25
25
|
* Opening a chat is itself the admission: the backend issues a standing from-now
|
|
26
|
-
*
|
|
26
|
+
* write grant to each side as it creates the room, so both delegates can
|
|
27
27
|
* read and post immediately. Do NOT tell agents to follow chat_open with an
|
|
28
28
|
* issue_grant on that same new chat — that only mints a duplicate grant carrying
|
|
29
29
|
* the default 30-day expiry. issue_grant / context_delegate are for scopes that
|
|
@@ -36,10 +36,9 @@ export function linkIsReachOnly(env) {
|
|
|
36
36
|
}
|
|
37
37
|
const LINK_STATUSES = ['active', 'open', 'cancelled', 'all'];
|
|
38
38
|
/**
|
|
39
|
-
* Seat ceiling for one invite. ⚠️
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
* limit instead of letting an agent discover it by getting a 400.
|
|
39
|
+
* Seat ceiling for one invite. ⚠️ Mirrors the server's own ceiling, which
|
|
40
|
+
* rejects anything past it; this copy exists only so the tool description states
|
|
41
|
+
* the limit instead of letting an agent discover it by getting a 400.
|
|
43
42
|
*/
|
|
44
43
|
const MAX_LINK_INVITE_CLAIMS = 25;
|
|
45
44
|
///1022 — the link rail shrank to two tools. Links are agreements, so
|
|
@@ -68,8 +67,7 @@ export const createLinkInviteCapability = {
|
|
|
68
67
|
needsAgentId: true,
|
|
69
68
|
handler: async (args, env) => {
|
|
70
69
|
const maxClaims = args['maxClaims'];
|
|
71
|
-
const { agreement } = await
|
|
72
|
-
engagementKind: 'link',
|
|
70
|
+
const { agreement } = await createLink({
|
|
73
71
|
description: args['message'],
|
|
74
72
|
...(maxClaims == null ? {} : { maxClaims }),
|
|
75
73
|
}, fullCreds(env));
|
|
@@ -175,9 +173,8 @@ export const proposeLinkCapability = {
|
|
|
175
173
|
needsAgentId: true,
|
|
176
174
|
handler: async (args, env) => {
|
|
177
175
|
const counterparty = args['counterparty'];
|
|
178
|
-
const { agreement } = await
|
|
179
|
-
|
|
180
|
-
providerId: counterparty,
|
|
176
|
+
const { agreement } = await createLink({
|
|
177
|
+
targetAgentId: counterparty,
|
|
181
178
|
...(args['message'] ? { description: args['message'] } : {}),
|
|
182
179
|
}, fullCreds(env));
|
|
183
180
|
// The owner rewrite is surfaced rather than left to be noticed: an agent
|
|
@@ -1,14 +1,60 @@
|
|
|
1
|
-
import { pullOffers,
|
|
1
|
+
import { pullOffers, pullRequests } from '../http/MarketplaceClient.js';
|
|
2
2
|
import { fullCreds } from './types.js';
|
|
3
3
|
import { nextCall } from './nextCall.js';
|
|
4
|
-
const VIEW_KINDS = ['all', '
|
|
4
|
+
const VIEW_KINDS = ['all', 'requests', 'offers'];
|
|
5
5
|
function publishHint(env) {
|
|
6
|
-
const propose = env.surface === 'mcp' ? '
|
|
6
|
+
const propose = env.surface === 'mcp' ? 'ziggs_agreement_request' : 'agreement_request';
|
|
7
7
|
const claim = env.surface === 'mcp' ? 'ziggs_agreement_claim' : 'agreement_claim';
|
|
8
8
|
return (`Claim any row with ${claim} (agreementId). Publish your own with ${propose}: ` +
|
|
9
|
-
`proposedTo "everyone" or "org" with no providerId broadcasts a
|
|
9
|
+
`proposedTo "everyone" or "org" with no providerId broadcasts a request (claimer works, you pay); ` +
|
|
10
10
|
`the same with providerId = your own id publishes a standing offer (you work, claimer pays).`);
|
|
11
11
|
}
|
|
12
|
+
/** A broadcast sentinel in a party slot means "open", not a counterparty. */
|
|
13
|
+
const BROADCASTS = new Set(['everyone', 'org']);
|
|
14
|
+
const named = (id) => typeof id === 'string' && id && !BROADCASTS.has(id) ? id : undefined;
|
|
15
|
+
/**
|
|
16
|
+
* One listing, as a browser needs to read it.
|
|
17
|
+
*
|
|
18
|
+
* A whole agreement document is around 30 fields and ~2KB per row, so a default
|
|
19
|
+
* page of 20 is 40KB of context spent mostly on storage bookkeeping — internal
|
|
20
|
+
* versioning, content hashes, write provenance, approval and lane bookkeeping —
|
|
21
|
+
* none of which helps anyone decide whether to claim. This projection is what the
|
|
22
|
+
* decision actually needs: what the work is, who does it, what it costs, on what
|
|
23
|
+
* terms, and the id to claim it with.
|
|
24
|
+
*
|
|
25
|
+
* Deliberately no `raw` escape hatch. The full document is one `agreement_get`
|
|
26
|
+
* away for the row you chose, and an escape hatch here would just restore the
|
|
27
|
+
* cost for every row you did not.
|
|
28
|
+
*/
|
|
29
|
+
function toListingRow(a, kind) {
|
|
30
|
+
const terms = a?.terms ?? {};
|
|
31
|
+
const parties = a?.parties ?? {};
|
|
32
|
+
const requiredConnections = terms.requiredConnections ?? [];
|
|
33
|
+
return {
|
|
34
|
+
agreementId: a?.agreementId,
|
|
35
|
+
kind,
|
|
36
|
+
description: terms.description ?? '',
|
|
37
|
+
price: a?.money?.price ?? 0,
|
|
38
|
+
// What that price MEANS: a whole-engagement total, or a rate charged per
|
|
39
|
+
// completed task. Omitting it left a metered standing hire indistinguishable
|
|
40
|
+
// from a flat-priced one at the moment a caller decides to claim, and
|
|
41
|
+
// `per_task` is the default for a hire. Absent on rows written before the
|
|
42
|
+
// field existed, which read as `total`.
|
|
43
|
+
...(terms.billing ? { billing: terms.billing } : {}),
|
|
44
|
+
engagementKind: a?.engagementKind,
|
|
45
|
+
lifecycle: terms.lifecycle,
|
|
46
|
+
...(terms.expiresAt ? { expiresAt: terms.expiresAt } : {}),
|
|
47
|
+
...(terms.maxExecutions != null ? { maxExecutions: terms.maxExecutions } : {}),
|
|
48
|
+
// Who does the work on an offer, who is paying on a request. The other slot is
|
|
49
|
+
// the open one you would be filling by claiming, so it carries no name yet.
|
|
50
|
+
...(named(parties.providerAgent) ? { providerAgent: parties.providerAgent } : {}),
|
|
51
|
+
...(named(parties.provider) ? { provider: parties.provider } : {}),
|
|
52
|
+
...(named(parties.payer) ? { payer: parties.payer } : {}),
|
|
53
|
+
// Access the job cannot be done without — worth knowing before claiming it.
|
|
54
|
+
...(requiredConnections.length ? { requiredConnections } : {}),
|
|
55
|
+
createdAt: a?.createdAt,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
12
58
|
/**
|
|
13
59
|
* the marketplace read on both surfaces. Publishing and claiming
|
|
14
60
|
* ride the agreement grammar (propose-with-audience / agreement_claim); this
|
|
@@ -19,15 +65,15 @@ export const marketplaceViewCapability = {
|
|
|
19
65
|
names: { sdk: 'marketplace_view', mcp: 'ziggs_marketplace_view' },
|
|
20
66
|
title: 'Browse the marketplace',
|
|
21
67
|
descriptions: {
|
|
22
|
-
sdk: 'Browse the open marketplace — where every engagement STARTS (posted-first: reuse an active agreement, else claim a listing here, before ever proposing).
|
|
23
|
-
mcp: 'Browse the open marketplace — where every engagement STARTS (posted-first: reuse an active agreement, else claim a listing here, before ever proposing).
|
|
68
|
+
sdk: 'Browse the open marketplace — where every engagement STARTS (posted-first: reuse an active agreement, else claim a listing here, before ever proposing). Requests are work buyers broadcast (you would do the work); standing offers are 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 — listings are take-it-or-leave-it, never counter one; publish your own via agreement_propose with proposedTo "everyone"/"org" (providerId = your id for an offer, omitted for a request).',
|
|
69
|
+
mcp: 'Browse the open marketplace — where every engagement STARTS (posted-first: reuse an active agreement, else claim a listing here, before ever proposing). Requests are work buyers broadcast (you would do the work); standing offers are 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 — listings are take-it-or-leave-it, never counter one; publish your own with ziggs_agreement_offer (you do the work, the claimer pays) or ziggs_agreement_request (the claimer does the work, you pay).',
|
|
24
70
|
},
|
|
25
71
|
annotation: 'read-only',
|
|
26
72
|
params: {
|
|
27
73
|
kind: {
|
|
28
74
|
type: 'string',
|
|
29
75
|
enum: VIEW_KINDS,
|
|
30
|
-
description: 'all (default) |
|
|
76
|
+
description: 'all (default) | requests | offers',
|
|
31
77
|
},
|
|
32
78
|
limit: { type: 'number', description: 'Max rows per kind (default 20)' },
|
|
33
79
|
since: { type: 'string', description: 'ISO timestamp — only rows published after this' },
|
|
@@ -43,18 +89,22 @@ export const marketplaceViewCapability = {
|
|
|
43
89
|
limit: typeof args['limit'] === 'number' ? args['limit'] : 20,
|
|
44
90
|
...(args['since'] ? { since: args['since'] } : {}),
|
|
45
91
|
};
|
|
46
|
-
const [
|
|
47
|
-
kind === 'offers' ? Promise.resolve([]) :
|
|
48
|
-
kind === '
|
|
92
|
+
const [requests, offers] = await Promise.all([
|
|
93
|
+
kind === 'offers' ? Promise.resolve([]) : pullRequests(options, creds),
|
|
94
|
+
kind === 'requests' ? Promise.resolve([]) : pullOffers(options, creds),
|
|
49
95
|
]);
|
|
50
96
|
return {
|
|
51
|
-
...(kind !== 'offers'
|
|
52
|
-
|
|
97
|
+
...(kind !== 'offers'
|
|
98
|
+
? { requests: requests.map((q) => toListingRow(q, 'request')), requestCount: requests.length }
|
|
99
|
+
: {}),
|
|
100
|
+
...(kind !== 'requests'
|
|
101
|
+
? { offers: offers.map((o) => toListingRow(o, 'offer')), offerCount: offers.length }
|
|
102
|
+
: {}),
|
|
53
103
|
// Claiming is the move after browsing, and the id is in the row the
|
|
54
104
|
// caller just received. The prose hint stays for the publish side, which
|
|
55
105
|
// is a choice rather than a call.
|
|
56
106
|
readPlan: [
|
|
57
|
-
...(
|
|
107
|
+
...(requests.length || offers.length
|
|
58
108
|
? [
|
|
59
109
|
nextCall(env, 'agreement_claim', undefined, 'claim a listing from this view by passing its agreementId — listings are take-it-or-leave-it, never countered'),
|
|
60
110
|
]
|
|
@@ -1,11 +1,27 @@
|
|
|
1
1
|
import { type CapabilityDefinition } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Reading the balance is the whole rail on an agent surface.
|
|
4
|
+
*
|
|
5
|
+
* What left this surface is every verb that moved value on its own:
|
|
6
|
+
* `payment_transfer`, `payment_hold`, `payment_release`,
|
|
7
|
+
* `payment_wait_for_approval` and `payment_resolve_wallet`, plus the three
|
|
8
|
+
* grant mutations (issue / attenuate / revoke), which are now the wallet
|
|
9
|
+
* owner's to make in the web app rather than an agent's to make on their
|
|
10
|
+
* behalf. An agent holding a free-standing "move value from A to B" verb is
|
|
11
|
+
* what reads as transferring a financial asset between third parties, and it
|
|
12
|
+
* is the shape assistant-store connector policy rejects.
|
|
13
|
+
*
|
|
14
|
+
* What did NOT change: agreements still settle in points. The hold on
|
|
15
|
+
* activation, the transfer in `settleCompletedTask` and the settle on fulfill
|
|
16
|
+
* all stay exactly as they were, and priced agreements keep moving points
|
|
17
|
+
* between org wallets. That double-entry IS the meter — the chain and the
|
|
18
|
+
* middleman margins are the measurement we want — so removing the verbs costs
|
|
19
|
+
* no capability: nothing an agent could do through them was load-bearing for
|
|
20
|
+
* getting paid under an agreement.
|
|
21
|
+
*
|
|
22
|
+
* Balance stays readable, because "see your points" is the intended
|
|
23
|
+
* experience. Note that `ZIGGS_MCP_CORE_ONLY` gates this whole group off
|
|
24
|
+
* including balance, so it is not the lever for this on its own.
|
|
25
|
+
*/
|
|
2
26
|
export declare const paymentBalanceCapability: CapabilityDefinition;
|
|
3
|
-
export declare const paymentResolveWalletCapability: CapabilityDefinition;
|
|
4
|
-
export declare const paymentTransferCapability: CapabilityDefinition;
|
|
5
|
-
export declare const paymentWaitForApprovalCapability: CapabilityDefinition;
|
|
6
|
-
export declare const paymentHoldCapability: CapabilityDefinition;
|
|
7
|
-
export declare const paymentReleaseCapability: CapabilityDefinition;
|
|
8
|
-
export declare const paymentIssueGrantCapability: CapabilityDefinition;
|
|
9
|
-
export declare const paymentAttenuateGrantCapability: CapabilityDefinition;
|
|
10
|
-
export declare const paymentRevokeGrantCapability: CapabilityDefinition;
|
|
11
27
|
export declare const PAYMENT_CAPABILITIES: CapabilityDefinition[];
|
|
@@ -6,36 +6,37 @@ function client(env) {
|
|
|
6
6
|
throw new Error('operatorKey missing from tool context');
|
|
7
7
|
return new PaymentsClient(operatorKey, agentId, env.baseUrl);
|
|
8
8
|
}
|
|
9
|
-
/**
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
9
|
+
/**
|
|
10
|
+
* Reading the balance is the whole rail on an agent surface.
|
|
11
|
+
*
|
|
12
|
+
* What left this surface is every verb that moved value on its own:
|
|
13
|
+
* `payment_transfer`, `payment_hold`, `payment_release`,
|
|
14
|
+
* `payment_wait_for_approval` and `payment_resolve_wallet`, plus the three
|
|
15
|
+
* grant mutations (issue / attenuate / revoke), which are now the wallet
|
|
16
|
+
* owner's to make in the web app rather than an agent's to make on their
|
|
17
|
+
* behalf. An agent holding a free-standing "move value from A to B" verb is
|
|
18
|
+
* what reads as transferring a financial asset between third parties, and it
|
|
19
|
+
* is the shape assistant-store connector policy rejects.
|
|
20
|
+
*
|
|
21
|
+
* What did NOT change: agreements still settle in points. The hold on
|
|
22
|
+
* activation, the transfer in `settleCompletedTask` and the settle on fulfill
|
|
23
|
+
* all stay exactly as they were, and priced agreements keep moving points
|
|
24
|
+
* between org wallets. That double-entry IS the meter — the chain and the
|
|
25
|
+
* middleman margins are the measurement we want — so removing the verbs costs
|
|
26
|
+
* no capability: nothing an agent could do through them was load-bearing for
|
|
27
|
+
* getting paid under an agreement.
|
|
28
|
+
*
|
|
29
|
+
* Balance stays readable, because "see your points" is the intended
|
|
30
|
+
* experience. Note that `ZIGGS_MCP_CORE_ONLY` gates this whole group off
|
|
31
|
+
* including balance, so it is not the lever for this on its own.
|
|
32
|
+
*/
|
|
32
33
|
export const paymentBalanceCapability = {
|
|
33
34
|
key: 'payment_balance',
|
|
34
35
|
names: { sdk: 'payment_balance', mcp: 'ziggs_payment_balance' },
|
|
35
|
-
title: 'Check your
|
|
36
|
+
title: 'Check your points',
|
|
36
37
|
descriptions: {
|
|
37
|
-
sdk: "Check the caller's
|
|
38
|
-
mcp: "Check the caller's
|
|
38
|
+
sdk: "Check the caller's points balance and available balance (total minus points held against active agreements). Points move as a consequence of an agreement settling.",
|
|
39
|
+
mcp: "Check the caller's points balance and available balance (total minus points held against active agreements). Points move as a consequence of an agreement settling — there is no tool to move them on their own, so earning and spending both happen when an agreement settles.",
|
|
39
40
|
},
|
|
40
41
|
annotation: 'read-only',
|
|
41
42
|
params: {},
|
|
@@ -44,375 +45,10 @@ export const paymentBalanceCapability = {
|
|
|
44
45
|
return await client(env).balance();
|
|
45
46
|
}
|
|
46
47
|
catch (e) {
|
|
47
|
-
rethrowWithContext(e, 'Failed to
|
|
48
|
-
}
|
|
49
|
-
},
|
|
50
|
-
};
|
|
51
|
-
export const paymentResolveWalletCapability = {
|
|
52
|
-
key: 'payment_resolve_wallet',
|
|
53
|
-
names: { sdk: 'payment_resolve_wallet', mcp: 'ziggs_payment_resolve_wallet' },
|
|
54
|
-
title: 'Look up a wallet',
|
|
55
|
-
descriptions: {
|
|
56
|
-
sdk: 'Look up a walletId by userId or agentId. Use before a transfer when you only know the recipient by their platform ID.',
|
|
57
|
-
mcp: 'Look up a walletId by userId or agentId. Use before a transfer when you only know the recipient by their platform ID.',
|
|
58
|
-
},
|
|
59
|
-
annotation: 'read-only',
|
|
60
|
-
params: {
|
|
61
|
-
userId: { type: 'string', description: 'User to resolve' },
|
|
62
|
-
agentId: { type: 'string', description: 'Agent to resolve' },
|
|
63
|
-
},
|
|
64
|
-
handler: async (args, env) => {
|
|
65
|
-
if (!args['userId'] && !args['agentId'])
|
|
66
|
-
throw new Error('Provide userId or agentId to resolve a wallet');
|
|
67
|
-
try {
|
|
68
|
-
const wallet = await client(env).resolve({
|
|
69
|
-
userId: args['userId'],
|
|
70
|
-
agentId: args['agentId'],
|
|
71
|
-
});
|
|
72
|
-
return {
|
|
73
|
-
walletId: wallet?.walletId || null,
|
|
74
|
-
ownerId: wallet?.ownerId || null,
|
|
75
|
-
currency: wallet?.currency || 'pez',
|
|
76
|
-
status: wallet?.status || null,
|
|
77
|
-
};
|
|
78
|
-
}
|
|
79
|
-
catch (e) {
|
|
80
|
-
rethrowWithContext(e, 'Failed to resolve wallet');
|
|
81
|
-
}
|
|
82
|
-
},
|
|
83
|
-
};
|
|
84
|
-
export const paymentTransferCapability = {
|
|
85
|
-
key: 'payment_transfer',
|
|
86
|
-
names: { sdk: 'payment_transfer', mcp: 'ziggs_payment_transfer' },
|
|
87
|
-
title: 'Transfer funds',
|
|
88
|
-
descriptions: {
|
|
89
|
-
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.',
|
|
90
|
-
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.',
|
|
91
|
-
},
|
|
92
|
-
annotation: 'write',
|
|
93
|
-
params: {
|
|
94
|
-
toWalletId: {
|
|
95
|
-
type: 'string',
|
|
96
|
-
required: true,
|
|
97
|
-
description: 'Destination wal_... id — or a userId/agentId to auto-resolve',
|
|
98
|
-
},
|
|
99
|
-
amount: { type: 'number', required: true, description: 'Amount in integer cents, > 0' },
|
|
100
|
-
description: { type: 'string', description: 'Human-readable transfer memo' },
|
|
101
|
-
idempotencyKey: {
|
|
102
|
-
type: 'string',
|
|
103
|
-
description: 'Client-supplied key to make retries safe (auto-generated when omitted)',
|
|
104
|
-
},
|
|
105
|
-
paymentGrantId: {
|
|
106
|
-
type: 'string',
|
|
107
|
-
description: 'Payment grant to spend under (required for agent-impersonated transfers)',
|
|
108
|
-
},
|
|
109
|
-
},
|
|
110
|
-
handler: async (args, env) => {
|
|
111
|
-
if (!args['toWalletId'])
|
|
112
|
-
throw new Error('toWalletId is required');
|
|
113
|
-
const amount = args['amount'];
|
|
114
|
-
if (!amount || amount <= 0)
|
|
115
|
-
throw new Error('amount must be positive');
|
|
116
|
-
let result;
|
|
117
|
-
try {
|
|
118
|
-
result = await client(env).transfer({
|
|
119
|
-
to: args['toWalletId'],
|
|
120
|
-
amount: Math.round(amount),
|
|
121
|
-
description: args['description'] || 'Agent-initiated transfer',
|
|
122
|
-
idempotencyKey: args['idempotencyKey'],
|
|
123
|
-
paymentGrantId: args['paymentGrantId'],
|
|
124
|
-
});
|
|
125
|
-
}
|
|
126
|
-
catch (e) {
|
|
127
|
-
rethrowWithContext(e, 'Transfer failed');
|
|
128
|
-
}
|
|
129
|
-
if (result.status === 'approval_required') {
|
|
130
|
-
const base = {
|
|
131
|
-
status: 'approval_required',
|
|
132
|
-
approvalId: result.approvalId,
|
|
133
|
-
expiresAt: result.expiresAt,
|
|
134
|
-
reason: result.reason,
|
|
135
|
-
amount,
|
|
136
|
-
toWalletId: result.toWalletId,
|
|
137
|
-
};
|
|
138
|
-
// Same pause, surface-local guidance: the MCP delegate must surface the
|
|
139
|
-
// pending approval to the human (pull-only MCP has no push); the SDK
|
|
140
|
-
// runtime routes via structured next_actions.
|
|
141
|
-
if (env.surface === 'mcp') {
|
|
142
|
-
return {
|
|
143
|
-
...base,
|
|
144
|
-
note: 'Transfer paused: the wallet owner must approve this amount on the wallet page (also listed by ziggs_pending_decisions). Tell the human now (pull-only MCP has no push). ' +
|
|
145
|
-
'Wait inline with ziggs_payment_wait_for_approval when you expect a quick decision (≤2 min).',
|
|
146
|
-
};
|
|
147
|
-
}
|
|
148
|
-
return {
|
|
149
|
-
...base,
|
|
150
|
-
message: 'Transfer paused: the wallet owner must approve this amount.',
|
|
151
|
-
next_actions: [
|
|
152
|
-
{
|
|
153
|
-
tool: 'payment_wait_for_approval',
|
|
154
|
-
when: 'You expect a quick decision (≤2 min) and can wait inline.',
|
|
155
|
-
args: { approvalId: result.approvalId, timeoutMs: 120000 },
|
|
156
|
-
},
|
|
157
|
-
{
|
|
158
|
-
tool: 'task_update_plan_step',
|
|
159
|
-
when: 'You want to abandon the transfer and route around it.',
|
|
160
|
-
},
|
|
161
|
-
],
|
|
162
|
-
};
|
|
163
|
-
}
|
|
164
|
-
return {
|
|
165
|
-
status: 'transferred',
|
|
166
|
-
transactionId: result.transactionId,
|
|
167
|
-
amount,
|
|
168
|
-
toWalletId: result.toWalletId,
|
|
169
|
-
};
|
|
170
|
-
},
|
|
171
|
-
};
|
|
172
|
-
export const paymentWaitForApprovalCapability = {
|
|
173
|
-
key: 'payment_wait_for_approval',
|
|
174
|
-
names: { sdk: 'payment_wait_for_approval', mcp: 'ziggs_payment_wait_for_approval' },
|
|
175
|
-
title: 'Wait on a payment approval',
|
|
176
|
-
descriptions: {
|
|
177
|
-
sdk: 'Poll a paused transfer (status "approval_required") until the human decides or the timeout passes. Returns executed | rejected | expired | timeout | gone.',
|
|
178
|
-
mcp: 'Poll a paused transfer (status "approval_required") until the human decides or the timeout passes. Returns executed | rejected | expired | timeout | gone. Use for quick decisions (≤2 min); for longer waits, stop and check again next session.',
|
|
179
|
-
},
|
|
180
|
-
annotation: 'read-only',
|
|
181
|
-
params: {
|
|
182
|
-
approvalId: {
|
|
183
|
-
type: 'string',
|
|
184
|
-
required: true,
|
|
185
|
-
description: 'Approval to wait on (from the paused transfer)',
|
|
186
|
-
},
|
|
187
|
-
timeoutMs: { type: 'number', description: 'Max wait, default 120000' },
|
|
188
|
-
pollMs: { type: 'number', description: 'Poll interval, default 3000 (min 500)' },
|
|
189
|
-
},
|
|
190
|
-
handler: async (args, env) => {
|
|
191
|
-
if (!args['approvalId'])
|
|
192
|
-
throw new Error('approvalId is required');
|
|
193
|
-
try {
|
|
194
|
-
const result = await client(env).waitForApproval(args['approvalId'], {
|
|
195
|
-
timeoutMs: args['timeoutMs'],
|
|
196
|
-
pollMs: args['pollMs'],
|
|
197
|
-
});
|
|
198
|
-
const loose = result;
|
|
199
|
-
return {
|
|
200
|
-
status: result.status,
|
|
201
|
-
approvalId: args['approvalId'],
|
|
202
|
-
transactionId: loose['transactionId'] || null,
|
|
203
|
-
approval: loose['approval'] || null,
|
|
204
|
-
};
|
|
205
|
-
}
|
|
206
|
-
catch (e) {
|
|
207
|
-
rethrowWithContext(e, 'wait_for_approval failed');
|
|
208
|
-
}
|
|
209
|
-
},
|
|
210
|
-
};
|
|
211
|
-
export const paymentHoldCapability = {
|
|
212
|
-
key: 'payment_hold',
|
|
213
|
-
names: { sdk: 'payment_hold', mcp: 'ziggs_payment_hold' },
|
|
214
|
-
title: 'Hold funds in escrow',
|
|
215
|
-
descriptions: {
|
|
216
|
-
sdk: 'Pre-authorize (escrow) funds without moving them. Use to reserve payment at agreement formation; release with payment_release once work is complete or to refund if work is cancelled.',
|
|
217
|
-
mcp: "Pre-authorize (escrow) funds without moving them. Use to reserve payment at agreement formation; release with ziggs_payment_release once work is complete, or refund if it's cancelled.",
|
|
218
|
-
},
|
|
219
|
-
annotation: 'write',
|
|
220
|
-
params: {
|
|
221
|
-
amount: { type: 'number', required: true, description: 'Amount in integer cents, > 0' },
|
|
222
|
-
description: { type: 'string', description: 'Human-readable hold memo' },
|
|
223
|
-
idempotencyKey: { type: 'string', description: 'Client-supplied retry-safety key' },
|
|
224
|
-
paymentGrantId: {
|
|
225
|
-
type: 'string',
|
|
226
|
-
description: 'Payment grant authorizing this hold (required for agent actors; auto-picks an active wallet grant when omitted)',
|
|
227
|
-
},
|
|
228
|
-
},
|
|
229
|
-
handler: async (args, env) => {
|
|
230
|
-
const amount = args['amount'];
|
|
231
|
-
if (!amount || amount <= 0)
|
|
232
|
-
throw new Error('amount must be positive');
|
|
233
|
-
try {
|
|
234
|
-
const result = await client(env).hold({
|
|
235
|
-
amount: Math.round(amount),
|
|
236
|
-
description: args['description'] || 'Agent escrow hold',
|
|
237
|
-
idempotencyKey: args['idempotencyKey'],
|
|
238
|
-
paymentGrantId: args['paymentGrantId'],
|
|
239
|
-
});
|
|
240
|
-
return {
|
|
241
|
-
status: 'held',
|
|
242
|
-
transactionId: result.transaction?.transactionId || null,
|
|
243
|
-
amount,
|
|
244
|
-
};
|
|
245
|
-
}
|
|
246
|
-
catch (e) {
|
|
247
|
-
rethrowWithContext(e, 'Hold failed');
|
|
248
|
-
}
|
|
249
|
-
},
|
|
250
|
-
};
|
|
251
|
-
export const paymentReleaseCapability = {
|
|
252
|
-
key: 'payment_release',
|
|
253
|
-
names: { sdk: 'payment_release', mcp: 'ziggs_payment_release' },
|
|
254
|
-
title: 'Settle or refund a hold',
|
|
255
|
-
descriptions: {
|
|
256
|
-
sdk: "Settle or refund an escrow hold. Use action='complete' to transfer held funds to toWalletId (work done), or action='refund' to return funds to the sender (work cancelled).",
|
|
257
|
-
mcp: "Settle or refund an escrow hold. action='complete' transfers held funds to toWalletId (work done); action='refund' returns funds to the sender (work cancelled).",
|
|
258
|
-
},
|
|
259
|
-
annotation: 'write',
|
|
260
|
-
params: {
|
|
261
|
-
holdId: {
|
|
262
|
-
type: 'string',
|
|
263
|
-
required: true,
|
|
264
|
-
description: 'Hold to settle (transactionId from the hold)',
|
|
265
|
-
},
|
|
266
|
-
action: {
|
|
267
|
-
type: 'string',
|
|
268
|
-
required: true,
|
|
269
|
-
enum: ['complete', 'refund'],
|
|
270
|
-
description: 'complete = pay out, refund = return',
|
|
271
|
-
},
|
|
272
|
-
toWalletId: { type: 'string', description: 'Destination wallet — required when action=complete' },
|
|
273
|
-
idempotencyKey: { type: 'string', description: 'Client-supplied retry-safety key' },
|
|
274
|
-
},
|
|
275
|
-
handler: async (args, env) => {
|
|
276
|
-
if (!args['holdId'])
|
|
277
|
-
throw new Error('holdId is required');
|
|
278
|
-
const action = args['action'];
|
|
279
|
-
if (action !== 'complete' && action !== 'refund')
|
|
280
|
-
throw new Error("action must be 'complete' or 'refund'");
|
|
281
|
-
if (action === 'complete' && !args['toWalletId'])
|
|
282
|
-
throw new Error('toWalletId is required when action=complete');
|
|
283
|
-
try {
|
|
284
|
-
const result = await client(env).release({
|
|
285
|
-
holdId: args['holdId'],
|
|
286
|
-
action,
|
|
287
|
-
toWalletId: args['toWalletId'],
|
|
288
|
-
idempotencyKey: args['idempotencyKey'],
|
|
289
|
-
});
|
|
290
|
-
return {
|
|
291
|
-
status: action === 'complete' ? 'settled' : 'refunded',
|
|
292
|
-
transactionId: result.transaction?.transactionId || null,
|
|
293
|
-
holdId: args['holdId'],
|
|
294
|
-
action,
|
|
295
|
-
};
|
|
296
|
-
}
|
|
297
|
-
catch (e) {
|
|
298
|
-
rethrowWithContext(e, 'Release failed');
|
|
299
|
-
}
|
|
300
|
-
},
|
|
301
|
-
};
|
|
302
|
-
export const paymentIssueGrantCapability = {
|
|
303
|
-
key: 'payment_issue_grant',
|
|
304
|
-
names: { sdk: 'payment_issue_grant', mcp: 'ziggs_payment_issue_grant' },
|
|
305
|
-
title: 'Issue a spend grant',
|
|
306
|
-
descriptions: {
|
|
307
|
-
sdk: "Issue a payment grant delegating bounded spend from the operator's wallet to an agent holder. Caveats bound what the holder can do (max_amount, daily_budget, allowed_recipients, expiry). The holder spends by passing the grantId as paymentGrantId on transfers.",
|
|
308
|
-
mcp: "Issue a payment grant delegating bounded spend from the operator's wallet to an agent holder. Caveats bound what the holder can do (max_amount, daily_budget, allowed_recipients, expiry). The holder spends by passing the grantId as paymentGrantId on transfers.",
|
|
309
|
-
},
|
|
310
|
-
annotation: 'write',
|
|
311
|
-
params: {
|
|
312
|
-
holderId: { type: 'string', required: true, description: 'Agent that will hold the grant' },
|
|
313
|
-
...grantCaveatParams,
|
|
314
|
-
},
|
|
315
|
-
handler: async (args, env) => {
|
|
316
|
-
if (!args['holderId'])
|
|
317
|
-
throw new Error('holderId is required');
|
|
318
|
-
try {
|
|
319
|
-
const caveats = buildCaveats(args);
|
|
320
|
-
const { grant } = await client(env).issueGrant({
|
|
321
|
-
holderId: args['holderId'],
|
|
322
|
-
caveats,
|
|
323
|
-
});
|
|
324
|
-
return {
|
|
325
|
-
grantId: grant?.grantId || null,
|
|
326
|
-
holderId: grant?.holderId || args['holderId'],
|
|
327
|
-
caveats: grant?.caveats || caveats,
|
|
328
|
-
expiresAt: grant?.expiresAt || null,
|
|
329
|
-
};
|
|
330
|
-
}
|
|
331
|
-
catch (e) {
|
|
332
|
-
rethrowWithContext(e, 'Failed to issue payment grant');
|
|
333
|
-
}
|
|
334
|
-
},
|
|
335
|
-
};
|
|
336
|
-
export const paymentAttenuateGrantCapability = {
|
|
337
|
-
key: 'payment_attenuate_grant',
|
|
338
|
-
names: { sdk: 'payment_attenuate_grant', mcp: 'ziggs_payment_attenuate_grant' },
|
|
339
|
-
title: 'Pass on a tighter spend grant',
|
|
340
|
-
descriptions: {
|
|
341
|
-
sdk: 'Re-delegate a payment grant you hold to another agent with TIGHTER caveats (narrowing only — the child can never exceed the parent). Use to pass a bounded spend slice to a sub-agent.',
|
|
342
|
-
mcp: 'Re-delegate a payment grant you hold to another agent with TIGHTER caveats (narrowing only — the child can never exceed the parent). Use to pass a bounded spend slice to a sub-agent.',
|
|
343
|
-
},
|
|
344
|
-
annotation: 'write',
|
|
345
|
-
params: {
|
|
346
|
-
grantId: { type: 'string', required: true, description: 'Parent grant to attenuate' },
|
|
347
|
-
holderId: {
|
|
348
|
-
type: 'string',
|
|
349
|
-
required: true,
|
|
350
|
-
description: 'Agent that will hold the narrowed grant',
|
|
351
|
-
},
|
|
352
|
-
...grantCaveatParams,
|
|
353
|
-
},
|
|
354
|
-
handler: async (args, env) => {
|
|
355
|
-
const grantId = args['grantId'];
|
|
356
|
-
if (!grantId)
|
|
357
|
-
throw new Error('grantId is required');
|
|
358
|
-
if (!args['holderId'])
|
|
359
|
-
throw new Error('holderId is required');
|
|
360
|
-
try {
|
|
361
|
-
const caveats = buildCaveats(args);
|
|
362
|
-
const { grant } = await client(env).attenuateGrant({
|
|
363
|
-
grantId,
|
|
364
|
-
holderId: args['holderId'],
|
|
365
|
-
caveats,
|
|
366
|
-
});
|
|
367
|
-
return {
|
|
368
|
-
grantId: grant?.grantId || null,
|
|
369
|
-
parentGrantId: grant?.parentGrantId || grantId,
|
|
370
|
-
holderId: grant?.holderId || args['holderId'],
|
|
371
|
-
caveats: grant?.caveats || caveats,
|
|
372
|
-
expiresAt: grant?.expiresAt || null,
|
|
373
|
-
};
|
|
374
|
-
}
|
|
375
|
-
catch (e) {
|
|
376
|
-
rethrowWithContext(e, 'Failed to attenuate payment grant');
|
|
377
|
-
}
|
|
378
|
-
},
|
|
379
|
-
};
|
|
380
|
-
export const paymentRevokeGrantCapability = {
|
|
381
|
-
key: 'payment_revoke_grant',
|
|
382
|
-
names: { sdk: 'payment_revoke_grant', mcp: 'ziggs_payment_revoke_grant' },
|
|
383
|
-
title: 'Revoke a spend grant',
|
|
384
|
-
descriptions: {
|
|
385
|
-
sdk: 'Revoke a payment grant (and its attenuated children). The holder can no longer spend under it.',
|
|
386
|
-
mcp: 'Revoke a payment grant (and its attenuated children). The holder can no longer spend under it.',
|
|
387
|
-
},
|
|
388
|
-
annotation: 'destructive',
|
|
389
|
-
params: {
|
|
390
|
-
grantId: { type: 'string', required: true, description: 'Grant to revoke' },
|
|
391
|
-
},
|
|
392
|
-
handler: async (args, env) => {
|
|
393
|
-
const grantId = args['grantId'];
|
|
394
|
-
if (!grantId)
|
|
395
|
-
throw new Error('grantId is required');
|
|
396
|
-
try {
|
|
397
|
-
const result = await client(env).revokeGrant(grantId);
|
|
398
|
-
return { status: 'revoked', grantId, revoked: result?.revoked ?? null };
|
|
399
|
-
}
|
|
400
|
-
catch (e) {
|
|
401
|
-
rethrowWithContext(e, 'Failed to revoke payment grant');
|
|
48
|
+
rethrowWithContext(e, 'Failed to read points balance');
|
|
402
49
|
}
|
|
403
50
|
},
|
|
404
51
|
};
|
|
405
|
-
// payment_list_grants stays retired; the wallet rail is part of the
|
|
406
|
-
// unified grant_list capability (scopeKind: 'wallet'). Grant *mutations* stay
|
|
407
|
-
// on the payment rail above.
|
|
408
52
|
export const PAYMENT_CAPABILITIES = [
|
|
409
53
|
paymentBalanceCapability,
|
|
410
|
-
paymentTransferCapability,
|
|
411
|
-
paymentWaitForApprovalCapability,
|
|
412
|
-
paymentHoldCapability,
|
|
413
|
-
paymentReleaseCapability,
|
|
414
|
-
paymentResolveWalletCapability,
|
|
415
|
-
paymentIssueGrantCapability,
|
|
416
|
-
paymentAttenuateGrantCapability,
|
|
417
|
-
paymentRevokeGrantCapability,
|
|
418
54
|
];
|