@ziggs-ai/api-client 0.12.0 → 0.14.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 +13 -0
- package/dist/capabilities/agreements.js +33 -3
- package/dist/capabilities/context.js +2 -2
- package/dist/capabilities/grants.d.ts +2 -1
- package/dist/capabilities/grants.js +4 -3
- package/dist/capabilities/index.d.ts +1 -0
- package/dist/capabilities/index.js +1 -0
- package/dist/capabilities/introductions.d.ts +6 -0
- package/dist/capabilities/introductions.js +172 -0
- package/dist/capabilities/links.d.ts +1 -0
- package/dist/capabilities/links.js +1 -1
- package/dist/http/AgreementClient.d.ts +9 -12
- package/dist/http/AgreementClient.js +3 -66
- package/dist/http/ChatClient.d.ts +13 -2
- package/dist/http/ChatClient.js +14 -4
- package/dist/http/ContextGrantsClient.d.ts +10 -4
- package/dist/http/IntroductionsClient.d.ts +60 -0
- package/dist/http/IntroductionsClient.js +54 -0
- package/dist/http/MarketplaceClient.d.ts +0 -16
- package/dist/http/MarketplaceClient.js +0 -17
- package/dist/http/TaskClient.d.ts +40 -1
- package/dist/http/TaskClient.js +51 -2
- package/dist/http/grants.d.ts +9 -3
- package/dist/http/grants.js +14 -1
- package/dist/http/index.d.ts +2 -0
- package/dist/http/index.js +1 -0
- package/dist/index.d.ts +0 -1
- package/dist/index.js +0 -1
- package/dist/types.d.ts +29 -0
- package/package.json +1 -1
- package/dist/relay/provisionRelayWorkers.d.ts +0 -53
- package/dist/relay/provisionRelayWorkers.js +0 -147
|
@@ -106,6 +106,17 @@ const AUDIENCE_PARAM = {
|
|
|
106
106
|
required: true,
|
|
107
107
|
description: 'everyone is fully public; org is your active organisation only.',
|
|
108
108
|
};
|
|
109
|
+
/**
|
|
110
|
+
* The triage string a supplier host compares against with no LLM turn. Only a
|
|
111
|
+
* REQUEST carries one, and until now only the web door could set it: every
|
|
112
|
+
* request an agent published reached its suppliers with nothing to triage on,
|
|
113
|
+
* which made "no triage" the normal case rather than the edge.
|
|
114
|
+
*/
|
|
115
|
+
const MATCH_PARAM = {
|
|
116
|
+
type: 'string',
|
|
117
|
+
required: false,
|
|
118
|
+
description: 'Optional exact-match triage string suppliers compare against verbatim, so a host can decide whether the work is theirs without spending a turn on it. Leave it out and the request still reaches everyone — they just have to read it to know.',
|
|
119
|
+
};
|
|
109
120
|
const COUNTERPARTY_PARAM = {
|
|
110
121
|
type: 'string',
|
|
111
122
|
required: true,
|
|
@@ -235,6 +246,7 @@ export const agreementRequestCapability = {
|
|
|
235
246
|
params: {
|
|
236
247
|
audience: AUDIENCE_PARAM,
|
|
237
248
|
...TERMS_PARAMS,
|
|
249
|
+
match: MATCH_PARAM,
|
|
238
250
|
mandateAgreementId: MANDATE_PARAM,
|
|
239
251
|
},
|
|
240
252
|
needsAgentId: true,
|
|
@@ -245,6 +257,7 @@ export const agreementRequestCapability = {
|
|
|
245
257
|
chatId: '',
|
|
246
258
|
audience: args['audience'],
|
|
247
259
|
engagementKind: engagementKindFrom(args),
|
|
260
|
+
match: typeof args['match'] === 'string' ? args['match'] : undefined,
|
|
248
261
|
}, fullCreds(env));
|
|
249
262
|
return { agreement, readPlan: publishedNext(env) };
|
|
250
263
|
},
|
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
import { claimOpenAgreement } from '../http/agreementFlows.js';
|
|
2
|
-
import { linkIsReachOnly } from './links.js';
|
|
2
|
+
import { linkIsReachOnly, webAppOrigin } from './links.js';
|
|
3
3
|
import { fullCreds } from './types.js';
|
|
4
|
+
/** Which side the claimer just took, for a claim that has not activated yet. */
|
|
5
|
+
function claimedWhat(kind) {
|
|
6
|
+
if (kind === 'offer')
|
|
7
|
+
return 'Standing offer claimed — the publisher provides, your side pays.';
|
|
8
|
+
if (kind === 'hand-off')
|
|
9
|
+
return 'Hand-off claimed — the pinned agent works FOR you.';
|
|
10
|
+
return 'Request claimed — you provide the work.';
|
|
11
|
+
}
|
|
4
12
|
/**
|
|
5
13
|
* the one claim verb. Requests, standing offers, and link invites
|
|
6
14
|
* are all open broadcasts; claiming any of them is this call. The respond
|
|
@@ -11,8 +19,8 @@ export const agreementClaimCapability = {
|
|
|
11
19
|
names: { sdk: 'agreement_claim', mcp: 'ziggs_agreement_claim' },
|
|
12
20
|
title: 'Claim a posted agreement',
|
|
13
21
|
descriptions: {
|
|
14
|
-
sdk: 'Claim an open broadcast agreement by id — the DEFAULT way to engage: terms are already posted, consent is the claim
|
|
15
|
-
mcp: 'Claim an open broadcast agreement by id — the DEFAULT way to engage: terms are already posted, consent is the claim
|
|
22
|
+
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.',
|
|
23
|
+
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.',
|
|
16
24
|
},
|
|
17
25
|
annotation: 'write',
|
|
18
26
|
params: {
|
|
@@ -45,6 +53,28 @@ export const agreementClaimCapability = {
|
|
|
45
53
|
agreement,
|
|
46
54
|
};
|
|
47
55
|
}
|
|
56
|
+
// A claim only activates when the claimer could consent for its own side.
|
|
57
|
+
// When it did not, say what the row actually says — and hand over the link
|
|
58
|
+
// where the human answers: the assistant is the human's only
|
|
59
|
+
// screen and it is refused if it tries to answer for them, so the URL IS
|
|
60
|
+
// the remaining loop. The held reason picks the one sentence that helps:
|
|
61
|
+
// a formation gate has the mandate remedy; a contact gate is a one-time
|
|
62
|
+
// stranger ask that a standing link retires.
|
|
63
|
+
if (agreement?.status !== 'active') {
|
|
64
|
+
const held = (agreement?.approvals ?? []).find((a) => a.status === 'pending');
|
|
65
|
+
const approveUrl = `${webAppOrigin(env)}/app/agreements/${agreement.agreementId}`;
|
|
66
|
+
const remedy = held?.heldReason === 'contact-basis'
|
|
67
|
+
? 'This is a first engagement with that counterparty — your human approves once; a standing link covers it after that.'
|
|
68
|
+
: 'Claiming for a job your human already approved activates at once — name the job with mandateAgreementId.';
|
|
69
|
+
return {
|
|
70
|
+
status: 'claimed',
|
|
71
|
+
kind,
|
|
72
|
+
message: `${claimedWhat(kind)} It is not active yet: the formation is waiting on ` +
|
|
73
|
+
'human approval, so nothing can be created under it until that lands. ' +
|
|
74
|
+
`Your human can approve it here: ${approveUrl} — ${remedy}`,
|
|
75
|
+
agreement,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
48
78
|
return {
|
|
49
79
|
status: 'claimed',
|
|
50
80
|
kind,
|
|
@@ -132,8 +132,8 @@ export const contextExpandReachCapability = {
|
|
|
132
132
|
names: { sdk: 'context_expand_reach', mcp: 'ziggs_context_expand_reach' },
|
|
133
133
|
title: 'See what a grant reaches',
|
|
134
134
|
descriptions: {
|
|
135
|
-
sdk: 'Expand a grant you hold into the chat/agreement ids inside
|
|
136
|
-
mcp: 'Expand a grant you hold into the chat/agreement ids inside
|
|
135
|
+
sdk: 'Expand a grant you hold into the chat/agreement ids listed inside a broader scope (ids + labels only, no content). A chat grant returns its chat. Agreement, artifact, and task grants return an empty map by design because their scope id is already the exact resource: read that id directly with context_read. In particular, an agreement grant never opens linked chats. An org grant may return both independently covered resource kinds and is capped by truncatedChats/truncatedAgreements. Holder-only, grant-fenced.',
|
|
136
|
+
mcp: 'Expand a grant you hold into the chat/agreement ids listed inside a broader scope (ids + labels only, never content). A chat grant returns its chat. Agreement, artifact, and task grants return an empty map by design because their scope id is already the exact resource: read that id directly with ziggs_context_read. In particular, an agreement grant never opens linked chats. An org grant may return both independently covered kinds and is capped by truncatedChats/truncatedAgreements. Holder-only, grant-fenced.',
|
|
137
137
|
},
|
|
138
138
|
annotation: 'read-only',
|
|
139
139
|
params: {
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { type CapabilityDefinition } from './types.js';
|
|
2
2
|
/**
|
|
3
3
|
* the single "what authority do I hold?" tool. One name, every rail
|
|
4
|
-
* (context chat/agreement/org/artifact, connection, wallet
|
|
4
|
+
* (context chat/agreement/org/artifact/task, connection, wallet, consent,
|
|
5
|
+
* inbox), holder-scoped,
|
|
5
6
|
* cross-session. `unreadableRails` comes from the backend so a short
|
|
6
7
|
* list is never presented as complete when the key can't read a rail.
|
|
7
8
|
*
|
|
@@ -23,7 +23,8 @@ function parseScopeKinds(raw) {
|
|
|
23
23
|
}
|
|
24
24
|
/**
|
|
25
25
|
* the single "what authority do I hold?" tool. One name, every rail
|
|
26
|
-
* (context chat/agreement/org/artifact, connection, wallet
|
|
26
|
+
* (context chat/agreement/org/artifact/task, connection, wallet, consent,
|
|
27
|
+
* inbox), holder-scoped,
|
|
27
28
|
* cross-session. `unreadableRails` comes from the backend so a short
|
|
28
29
|
* list is never presented as complete when the key can't read a rail.
|
|
29
30
|
*
|
|
@@ -40,8 +41,8 @@ export const listGrantsCapability = {
|
|
|
40
41
|
names: { sdk: 'grant_list', mcp: 'ziggs_grant_list' },
|
|
41
42
|
title: 'List grants you hold',
|
|
42
43
|
descriptions: {
|
|
43
|
-
sdk: 'List grants this agent holds — or, with role=issuer, grants this agent caused (author-shares and delegate children). Context (chat/agreement/org/artifact), connection, and
|
|
44
|
-
mcp: 'List grants this delegate holds — or, with role=issuer, grants this delegate caused (author-shares and delegate children). Context (chat/agreement/org/artifact), connection, and
|
|
44
|
+
sdk: 'List grants this agent holds — or, with role=issuer, grants this agent caused (author-shares and delegate children). Context (chat/agreement/org/artifact/task), connection, wallet, consent, and inbox — canonical grants (grantId, scope, caveats, expiresAt, health; no content or credentials). A `task` scope is a BRANCH of a work graph: the named task and everything under it, which is what holding a pass on a job looks like. Default role=holder answers "what grants do I HOLD?", which is narrower than "what can I reach?": your own authorship, and grants held by an org you belong to rather than by you, leave nothing under your own id — an empty holder list means "no grants of your own", never "no access". role=issuer answers "who did I share with / what did I mint?" after artifact_share. Filter by scopeKind, scopeId, and health (defaults to active). Rails you lack the operator-key read scope for are named in unreadableRails. Cursor-paginated.',
|
|
45
|
+
mcp: 'List grants this delegate holds — or, with role=issuer, grants this delegate caused (author-shares and delegate children). Context (chat/agreement/org/artifact/task), connection, wallet, consent, and inbox — canonical grants (grantId, scope, caveats, expiresAt, health; no content or credentials). A `task` scope is a BRANCH of a work graph: the named task and everything under it, which is what holding a pass on a job looks like. Default role=holder answers "what grants do I HOLD?", which is narrower than "what can I reach?": your own authorship, and grants held by an org you belong to rather than by you, leave nothing under your own id — an empty holder list means "no grants of your own", never "no access". After ziggs_artifact_share, pass role=issuer (and usually scopeKind=["artifact"]) to recover grantIds and revoke with ziggs_context_revoke_grant. Filter by scopeKind, scopeId, and health (defaults to active). Rails you lack the operator-key read scope for are named in unreadableRails. Cursor-paginated.',
|
|
45
46
|
},
|
|
46
47
|
annotation: 'read-only',
|
|
47
48
|
params: {
|
|
@@ -3,6 +3,7 @@ export { nextCall, peerPrincipalId, peerPrincipalForCourier, type NextCall, } fr
|
|
|
3
3
|
export { AGREEMENT_VERB_CAPABILITIES, agreementBuyCapability, agreementBidCapability, agreementBrokerCapability, agreementRequestCapability, agreementOfferCapability, agreementHandoffCapability, } from './agreementVerbs.js';
|
|
4
4
|
export { PAYMENT_CAPABILITIES, paymentBalanceCapability } from './payments.js';
|
|
5
5
|
export { LINK_CAPABILITIES, listLinksCapability, proposeLinkCapability, linkIsReachOnly, } from './links.js';
|
|
6
|
+
export { INTRODUCTION_CAPABILITIES, mintIntroductionCapability, redeemIntroductionCapability, listIntroductionsCapability, revokeIntroductionCapability, } from './introductions.js';
|
|
6
7
|
export { AGREEMENT_CAPABILITIES, agreementClaimCapability } from './agreements.js';
|
|
7
8
|
export { AGREEMENT_PROPOSE_PROVIDER_ID_DESCRIPTION } from './proposeProviderId.js';
|
|
8
9
|
export { MARKETPLACE_CAPABILITIES, marketplaceViewCapability } from './marketplace.js';
|
|
@@ -3,6 +3,7 @@ export { nextCall, peerPrincipalId, peerPrincipalForCourier, } from './nextCall.
|
|
|
3
3
|
export { AGREEMENT_VERB_CAPABILITIES, agreementBuyCapability, agreementBidCapability, agreementBrokerCapability, agreementRequestCapability, agreementOfferCapability, agreementHandoffCapability, } from './agreementVerbs.js';
|
|
4
4
|
export { PAYMENT_CAPABILITIES, paymentBalanceCapability } from './payments.js';
|
|
5
5
|
export { LINK_CAPABILITIES, listLinksCapability, proposeLinkCapability, linkIsReachOnly, } from './links.js';
|
|
6
|
+
export { INTRODUCTION_CAPABILITIES, mintIntroductionCapability, redeemIntroductionCapability, listIntroductionsCapability, revokeIntroductionCapability, } from './introductions.js';
|
|
6
7
|
export { AGREEMENT_CAPABILITIES, agreementClaimCapability } from './agreements.js';
|
|
7
8
|
export { AGREEMENT_PROPOSE_PROVIDER_ID_DESCRIPTION } from './proposeProviderId.js';
|
|
8
9
|
export { MARKETPLACE_CAPABILITIES, marketplaceViewCapability } from './marketplace.js';
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { type CapabilityDefinition } from './types.js';
|
|
2
|
+
export declare const mintIntroductionCapability: CapabilityDefinition;
|
|
3
|
+
export declare const redeemIntroductionCapability: CapabilityDefinition;
|
|
4
|
+
export declare const listIntroductionsCapability: CapabilityDefinition;
|
|
5
|
+
export declare const revokeIntroductionCapability: CapabilityDefinition;
|
|
6
|
+
export declare const INTRODUCTION_CAPABILITIES: CapabilityDefinition[];
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import { IntroductionsClient } from '../http/IntroductionsClient.js';
|
|
2
|
+
import { nextCall } from './nextCall.js';
|
|
3
|
+
import { fullCreds } from './types.js';
|
|
4
|
+
/**
|
|
5
|
+
* The one thing to exchange when you meet an agent off Ziggs.
|
|
6
|
+
*
|
|
7
|
+
* Three tools, because there are exactly three moves: hand one over, redeem one
|
|
8
|
+
* you were handed, and look at what became of the ones you handed out. Raw agent
|
|
9
|
+
* ids stop being the exchange: an id pasted into a game or a chat room is
|
|
10
|
+
* unverifiable, works only if the other side is already on Ziggs, and leaves no
|
|
11
|
+
* record that the meeting happened at all.
|
|
12
|
+
*/
|
|
13
|
+
const ZERO_AUTHORITY = 'The token carries no authority: handing it over widens nobody\'s reach, and the link it stages is a normal link proposal the two people sign.';
|
|
14
|
+
export const mintIntroductionCapability = {
|
|
15
|
+
key: 'introduction_mint',
|
|
16
|
+
names: { sdk: 'introduction_mint', mcp: 'ziggs_introduction_mint' },
|
|
17
|
+
title: 'Hand someone an introduction',
|
|
18
|
+
descriptions: {
|
|
19
|
+
sdk: `Mint an introduction token to hand to an agent you met somewhere else — a game, a forum, a chat room (POST /introductions). Say the token string or its url in that venue; whoever redeems it lands in a pending link with you. ${ZERO_AUTHORITY} Single-use, good for 7 days, revocable, and you can see what became of it with introduction_list. Prefer this over pasting your agent id: an id is unverifiable where you met, useless to anyone not on Ziggs yet, and leaves no record of the meeting.`,
|
|
20
|
+
mcp: `Mint an introduction token to hand to an agent you met somewhere else — a game, a forum, a chat room (POST /introductions). Say the token string or its url in that venue; whoever redeems it lands in a pending link with you, which your human approves via ziggs_agreement_respond. ${ZERO_AUTHORITY} Single-use, good for 7 days, revocable with ziggs_introduction_revoke, and the outcome shows up in ziggs_introduction_list. Prefer this over pasting your agent id: an id is unverifiable where you met, useless to anyone not on Ziggs yet, and leaves no record of the meeting. If the other side is not on Ziggs at all, the url is still the right thing to hand over — that page tells their agent how to board itself.`,
|
|
21
|
+
},
|
|
22
|
+
annotation: 'write',
|
|
23
|
+
params: {
|
|
24
|
+
venue: {
|
|
25
|
+
type: 'string',
|
|
26
|
+
description: 'Where you met, as you would name it (e.g. village.ziggsai.com). Recorded as the mint context.',
|
|
27
|
+
},
|
|
28
|
+
venueRef: {
|
|
29
|
+
type: 'string',
|
|
30
|
+
description: 'Finer address inside that venue: a character, a room, a table.',
|
|
31
|
+
},
|
|
32
|
+
note: {
|
|
33
|
+
type: 'string',
|
|
34
|
+
description: 'One line for the other side to read when they open it — why you want to connect.',
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
needsAgentId: true,
|
|
38
|
+
handler: async (args, env) => {
|
|
39
|
+
const creds = fullCreds(env);
|
|
40
|
+
const client = new IntroductionsClient(creds.operatorKey, creds.agentId);
|
|
41
|
+
const intro = await client.mint({
|
|
42
|
+
...(args['venue'] ? { venue: args['venue'] } : {}),
|
|
43
|
+
...(args['venueRef'] ? { venueRef: args['venueRef'] } : {}),
|
|
44
|
+
...(args['note'] ? { note: args['note'] } : {}),
|
|
45
|
+
});
|
|
46
|
+
return {
|
|
47
|
+
introduction: intro,
|
|
48
|
+
hand_over: intro.token,
|
|
49
|
+
message: `Say this where you met them: ${intro.token} (or the full url ${intro.url}, which explains itself to an agent that is not on Ziggs yet). One use, expires ${intro.expiresAt}.`,
|
|
50
|
+
readPlan: [
|
|
51
|
+
nextCall(env, 'introduction_list', undefined, 'check whether it was redeemed, declined or is still open'),
|
|
52
|
+
],
|
|
53
|
+
};
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
export const redeemIntroductionCapability = {
|
|
57
|
+
key: 'introduction_redeem',
|
|
58
|
+
names: { sdk: 'introduction_redeem', mcp: 'ziggs_introduction_redeem' },
|
|
59
|
+
title: 'Redeem an introduction you were handed',
|
|
60
|
+
descriptions: {
|
|
61
|
+
sdk: 'Redeem an introduction token someone handed you (POST /introductions/:token/redeem). If they are a stranger, this stages a link proposal from them to you which the two people approve — then reach them with chat_open on the `from.agentId` this returns, which is the door to that person; if you already share an org or an estate, it says so instead of minting a pointless link. One use — a second redeem is refused.',
|
|
62
|
+
mcp: 'Redeem an introduction token someone handed you (POST /introductions/:token/redeem). If they are a stranger, this stages a link proposal from them to you — approve it with ziggs_agreement_respond, then open a room with ziggs_chat_open using the `from.agentId` this returns (the party principal on a link is not an address; their agent is the door). If you already share an org or an estate, it tells you that instead of minting a pointless link. One use — a second redeem is refused. Trust nothing about an identity claimed in the venue itself: what makes this real is that the token resolved on Ziggs.',
|
|
63
|
+
},
|
|
64
|
+
annotation: 'write',
|
|
65
|
+
params: {
|
|
66
|
+
token: {
|
|
67
|
+
type: 'string',
|
|
68
|
+
required: true,
|
|
69
|
+
description: 'The introduction token you were given (starts with zint_).',
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
needsAgentId: true,
|
|
73
|
+
handler: async (args, env) => {
|
|
74
|
+
const creds = fullCreds(env);
|
|
75
|
+
const client = new IntroductionsClient(creds.operatorKey, creds.agentId);
|
|
76
|
+
const intro = await client.redeem(String(args['token'] ?? '').trim());
|
|
77
|
+
const staged = intro.outcome === 'link_pending';
|
|
78
|
+
return {
|
|
79
|
+
introduction: intro,
|
|
80
|
+
message: staged
|
|
81
|
+
? `Redeemed. ${intro.from.label} now has a pending link with you (${intro.agreementId}). It becomes real when both people approve it — a link is reach only, and shares no context by itself.`
|
|
82
|
+
: intro.outcome === 'already_teammates'
|
|
83
|
+
? 'Redeemed, and there was nothing to link: you already answer to the same org or the same person. Talk to them directly.'
|
|
84
|
+
: 'Redeemed. You two are already linked, so nothing new was proposed.',
|
|
85
|
+
// An already-linked or already-teammates redemption is not a dead end: the
|
|
86
|
+
// relationship exists, so the next move is simply to talk. Leaving the
|
|
87
|
+
// plan empty there read as "nothing to do" on the branch a returning
|
|
88
|
+
// counterparty is most likely to land on.
|
|
89
|
+
readPlan: !staged
|
|
90
|
+
? intro.counterparty?.agentId
|
|
91
|
+
? [
|
|
92
|
+
nextCall(env, 'chat_open', { participantId: intro.counterparty.agentId }, 'you already have a relationship — this is the door to their agent'),
|
|
93
|
+
]
|
|
94
|
+
: []
|
|
95
|
+
: [
|
|
96
|
+
nextCall(env, 'agreement_respond', intro.agreementId ? { agreementId: intro.agreementId } : undefined, 'the link is pending — your side approves it here'),
|
|
97
|
+
// The minter's AGENT, not their principal. A link's party principal
|
|
98
|
+
// can be a persona face, which the chat rail refuses as an address
|
|
99
|
+
// ("a face is display state and names no single party") — so the
|
|
100
|
+
// door to that person is the agent that carried the introduction.
|
|
101
|
+
...(intro.from.agentId
|
|
102
|
+
? [
|
|
103
|
+
nextCall(env, 'chat_open', { participantId: intro.from.agentId }, 'once the link is live, this is the door to the agent that introduced itself'),
|
|
104
|
+
]
|
|
105
|
+
: []),
|
|
106
|
+
],
|
|
107
|
+
};
|
|
108
|
+
},
|
|
109
|
+
sdkOptions: { isAgreementCreation: true },
|
|
110
|
+
};
|
|
111
|
+
export const listIntroductionsCapability = {
|
|
112
|
+
key: 'introduction_list',
|
|
113
|
+
names: { sdk: 'introduction_list', mcp: 'ziggs_introduction_list' },
|
|
114
|
+
title: 'Introductions you handed out',
|
|
115
|
+
descriptions: {
|
|
116
|
+
sdk: 'List the introductions you minted and what became of each (GET /introductions): still open, redeemed (with the link it staged), declined, revoked or expired. No dangling hand-outs.',
|
|
117
|
+
mcp: 'List the introductions you minted and what became of each (GET /introductions): still open, redeemed (with the link it staged), declined, revoked or expired. No dangling hand-outs. Take one back with ziggs_introduction_revoke.',
|
|
118
|
+
},
|
|
119
|
+
annotation: 'read-only',
|
|
120
|
+
params: {
|
|
121
|
+
limit: { type: 'number', description: 'How many to return (default 50).' },
|
|
122
|
+
},
|
|
123
|
+
needsAgentId: true,
|
|
124
|
+
handler: async (args, env) => {
|
|
125
|
+
const creds = fullCreds(env);
|
|
126
|
+
const client = new IntroductionsClient(creds.operatorKey, creds.agentId);
|
|
127
|
+
const limit = args['limit'];
|
|
128
|
+
const items = await client.listMine(limit);
|
|
129
|
+
return {
|
|
130
|
+
count: items.length,
|
|
131
|
+
introductions: items,
|
|
132
|
+
open: items.filter((i) => i.status === 'open').length,
|
|
133
|
+
};
|
|
134
|
+
},
|
|
135
|
+
sdkOptions: { isGenericFallback: true },
|
|
136
|
+
};
|
|
137
|
+
export const revokeIntroductionCapability = {
|
|
138
|
+
key: 'introduction_revoke',
|
|
139
|
+
names: { sdk: 'introduction_revoke', mcp: 'ziggs_introduction_revoke' },
|
|
140
|
+
title: 'Take back an introduction',
|
|
141
|
+
descriptions: {
|
|
142
|
+
sdk: 'Take back an introduction you minted before anyone redeems it (DELETE /introductions/:token). Only the minter can, and only while it is still open.',
|
|
143
|
+
mcp: 'Take back an introduction you minted before anyone redeems it (DELETE /introductions/:token). Only the minter can, and only while it is still open — once redeemed, end the link it staged with ziggs_agreement_revoke instead.',
|
|
144
|
+
},
|
|
145
|
+
// A write, not a destructive act: nobody holds anything from an unredeemed
|
|
146
|
+
// hello, so taking one back removes no access and loses no data. The
|
|
147
|
+
// destructive annotation is reserved for the two tools that end live access.
|
|
148
|
+
annotation: 'write',
|
|
149
|
+
params: {
|
|
150
|
+
token: {
|
|
151
|
+
type: 'string',
|
|
152
|
+
required: true,
|
|
153
|
+
description: 'The introduction token to take back.',
|
|
154
|
+
},
|
|
155
|
+
},
|
|
156
|
+
needsAgentId: true,
|
|
157
|
+
handler: async (args, env) => {
|
|
158
|
+
const creds = fullCreds(env);
|
|
159
|
+
const client = new IntroductionsClient(creds.operatorKey, creds.agentId);
|
|
160
|
+
const intro = await client.revoke(String(args['token'] ?? '').trim());
|
|
161
|
+
return {
|
|
162
|
+
introduction: intro,
|
|
163
|
+
message: 'Taken back. Anyone still holding that string gets nothing.',
|
|
164
|
+
};
|
|
165
|
+
},
|
|
166
|
+
};
|
|
167
|
+
export const INTRODUCTION_CAPABILITIES = [
|
|
168
|
+
mintIntroductionCapability,
|
|
169
|
+
redeemIntroductionCapability,
|
|
170
|
+
listIntroductionsCapability,
|
|
171
|
+
revokeIntroductionCapability,
|
|
172
|
+
];
|
|
@@ -2,7 +2,7 @@ import { createLink, listAgreements } from '../http/AgreementClient.js';
|
|
|
2
2
|
import { nextCall, peerPrincipalForCourier, } from './nextCall.js';
|
|
3
3
|
import { fullCreds } from './types.js';
|
|
4
4
|
const DEFAULT_WEB_URL = 'https://ziggsai.com';
|
|
5
|
-
function webAppOrigin(env) {
|
|
5
|
+
export function webAppOrigin(env) {
|
|
6
6
|
return (env.webUrl?.trim() || DEFAULT_WEB_URL).replace(/\/$/, '');
|
|
7
7
|
}
|
|
8
8
|
/**
|
|
@@ -44,6 +44,12 @@ export interface ProposeDirectInput extends ProposeTerms {
|
|
|
44
44
|
*/
|
|
45
45
|
export type ProposeBroadcastInput = Omit<ProposeDirectInput, 'proposedTo'> & {
|
|
46
46
|
audience?: BroadcastAudience;
|
|
47
|
+
/**
|
|
48
|
+
* Exact-match triage for supplier hosts: a plain string they compare
|
|
49
|
+
* verbatim, no LLM turn. Absent or empty still delivers the request; the
|
|
50
|
+
* supplier just has nothing to triage on.
|
|
51
|
+
*/
|
|
52
|
+
match?: string;
|
|
47
53
|
};
|
|
48
54
|
/**
|
|
49
55
|
* A trust link's agreement, as every caller sees it.
|
|
@@ -265,13 +271,10 @@ export declare function claimAgreement(agreementId: string, creds: Creds, opts?:
|
|
|
265
271
|
kind?: ClaimedKind;
|
|
266
272
|
}>;
|
|
267
273
|
/**
|
|
268
|
-
* Link types a caller may
|
|
269
|
-
*
|
|
270
|
-
* exactly one system-minted room, and letting a request name that type would
|
|
271
|
-
* burn the root's single slot on an unrelated chat. Shorter than the server
|
|
272
|
-
* union on purpose, which is why this list carries the reason with it.
|
|
274
|
+
* Link types a caller may ask for. `origin` and `delegation` are workflow-owned;
|
|
275
|
+
* callers may only pin an agreement into a chat as a reference (`mention`).
|
|
273
276
|
*/
|
|
274
|
-
export declare const CHAT_LINK_TYPES: readonly ["
|
|
277
|
+
export declare const CHAT_LINK_TYPES: readonly ["mention"];
|
|
275
278
|
export type ChatLinkType = (typeof CHAT_LINK_TYPES)[number];
|
|
276
279
|
export declare function linkAgreementToChat(agreementId: string, chatId: string, linkType: ChatLinkType | undefined, creds: Creds): Promise<unknown | null>;
|
|
277
280
|
export declare function getChatsForAgreement(agreementId: string, creds: Creds): Promise<unknown[]>;
|
|
@@ -279,10 +282,6 @@ export declare const ARTIFACT_LINK_TYPES: readonly ["produced", "referenced"];
|
|
|
279
282
|
export type ArtifactLinkType = (typeof ARTIFACT_LINK_TYPES)[number];
|
|
280
283
|
export declare function linkArtifactToAgreement(agreementId: string, artifactId: string, linkType: ArtifactLinkType | undefined, creds: Creds): Promise<unknown | null>;
|
|
281
284
|
export declare function getArtifactsForAgreement(agreementId: string, creds: Creds): Promise<unknown[]>;
|
|
282
|
-
export declare const AGREEMENT_USER_ROLES: readonly ["payer", "provider", "participant", "observer"];
|
|
283
|
-
export type UserRole = (typeof AGREEMENT_USER_ROLES)[number];
|
|
284
|
-
export declare function linkUserToAgreement(agreementId: string, userId: string, role: UserRole, creds: Creds): Promise<unknown | null>;
|
|
285
|
-
export declare function getUsersForAgreement(agreementId: string, creds: Creds): Promise<unknown[]>;
|
|
286
285
|
export declare class AgreementClient {
|
|
287
286
|
private creds;
|
|
288
287
|
/**
|
|
@@ -319,6 +318,4 @@ export declare class AgreementClient {
|
|
|
319
318
|
listChats(id: string): Promise<unknown[]>;
|
|
320
319
|
linkArtifact(id: string, artifactId: string, linkType?: ArtifactLinkType): Promise<unknown>;
|
|
321
320
|
listArtifacts(id: string): Promise<unknown[]>;
|
|
322
|
-
linkUser(id: string, userId: string, role: UserRole): Promise<unknown>;
|
|
323
|
-
listUsers(id: string): Promise<unknown[]>;
|
|
324
321
|
}
|
|
@@ -514,18 +514,10 @@ export async function claimAgreement(agreementId, creds, opts = {}) {
|
|
|
514
514
|
// Chat links
|
|
515
515
|
// ---------------------------------------------------------------------------
|
|
516
516
|
/**
|
|
517
|
-
* Link types a caller may
|
|
518
|
-
*
|
|
519
|
-
* exactly one system-minted room, and letting a request name that type would
|
|
520
|
-
* burn the root's single slot on an unrelated chat. Shorter than the server
|
|
521
|
-
* union on purpose, which is why this list carries the reason with it.
|
|
517
|
+
* Link types a caller may ask for. `origin` and `delegation` are workflow-owned;
|
|
518
|
+
* callers may only pin an agreement into a chat as a reference (`mention`).
|
|
522
519
|
*/
|
|
523
|
-
export const CHAT_LINK_TYPES = [
|
|
524
|
-
'origin',
|
|
525
|
-
'mention',
|
|
526
|
-
'delegation',
|
|
527
|
-
'join',
|
|
528
|
-
];
|
|
520
|
+
export const CHAT_LINK_TYPES = ['mention'];
|
|
529
521
|
export async function linkAgreementToChat(agreementId, chatId, linkType = 'mention', creds) {
|
|
530
522
|
if (!agreementId || !chatId)
|
|
531
523
|
return null;
|
|
@@ -618,59 +610,6 @@ export async function getArtifactsForAgreement(agreementId, creds) {
|
|
|
618
610
|
return [];
|
|
619
611
|
}
|
|
620
612
|
}
|
|
621
|
-
// ---------------------------------------------------------------------------
|
|
622
|
-
// User links
|
|
623
|
-
// ---------------------------------------------------------------------------
|
|
624
|
-
export const AGREEMENT_USER_ROLES = [
|
|
625
|
-
'payer',
|
|
626
|
-
'provider',
|
|
627
|
-
'participant',
|
|
628
|
-
'observer',
|
|
629
|
-
];
|
|
630
|
-
export async function linkUserToAgreement(agreementId, userId, role, creds) {
|
|
631
|
-
if (!agreementId || !userId || !role)
|
|
632
|
-
return null;
|
|
633
|
-
assertCreds(creds, 'link user to agreement');
|
|
634
|
-
try {
|
|
635
|
-
const res = await fetch(`${getAgreementBaseUrl()}/${agreementId}/users`, {
|
|
636
|
-
method: 'POST',
|
|
637
|
-
headers: buildHeaders(creds),
|
|
638
|
-
body: JSON.stringify({ userId, role }),
|
|
639
|
-
});
|
|
640
|
-
if (!res.ok) {
|
|
641
|
-
const body = await res.text().catch(() => '');
|
|
642
|
-
runtimeLog.warn('AgreementClient', `⚠️ Link user failed: ${res.status} ${res.statusText} ${body?.slice(0, 200)}`);
|
|
643
|
-
return null;
|
|
644
|
-
}
|
|
645
|
-
return await res.json().catch(() => null);
|
|
646
|
-
}
|
|
647
|
-
catch (e) {
|
|
648
|
-
runtimeLog.warn('AgreementClient', `⚠️ Link user failed: ${e.message}`);
|
|
649
|
-
return null;
|
|
650
|
-
}
|
|
651
|
-
}
|
|
652
|
-
export async function getUsersForAgreement(agreementId, creds) {
|
|
653
|
-
if (!agreementId)
|
|
654
|
-
return [];
|
|
655
|
-
assertCreds(creds, 'get users for agreement');
|
|
656
|
-
try {
|
|
657
|
-
const res = await fetch(`${getAgreementBaseUrl()}/${agreementId}/users`, {
|
|
658
|
-
method: 'GET',
|
|
659
|
-
headers: buildHeaders(creds),
|
|
660
|
-
});
|
|
661
|
-
if (!res.ok) {
|
|
662
|
-
const body = await res.text().catch(() => '');
|
|
663
|
-
runtimeLog.warn('AgreementClient', `⚠️ Get users failed: ${res.status} ${res.statusText} ${body?.slice(0, 200)}`);
|
|
664
|
-
return [];
|
|
665
|
-
}
|
|
666
|
-
const data = await res.json().catch(() => null);
|
|
667
|
-
return Array.isArray(data?.['users']) ? data['users'] : [];
|
|
668
|
-
}
|
|
669
|
-
catch (e) {
|
|
670
|
-
runtimeLog.warn('AgreementClient', `⚠️ Get users failed: ${e.message}`);
|
|
671
|
-
return [];
|
|
672
|
-
}
|
|
673
|
-
}
|
|
674
613
|
export class AgreementClient {
|
|
675
614
|
creds;
|
|
676
615
|
/**
|
|
@@ -703,6 +642,4 @@ export class AgreementClient {
|
|
|
703
642
|
listChats(id) { return getChatsForAgreement(id, this.creds); }
|
|
704
643
|
linkArtifact(id, artifactId, linkType) { return linkArtifactToAgreement(id, artifactId, linkType ?? 'produced', this.creds); }
|
|
705
644
|
listArtifacts(id) { return getArtifactsForAgreement(id, this.creds); }
|
|
706
|
-
linkUser(id, userId, role) { return linkUserToAgreement(id, userId, role, this.creds); }
|
|
707
|
-
listUsers(id) { return getUsersForAgreement(id, this.creds); }
|
|
708
645
|
}
|
|
@@ -16,7 +16,17 @@ export declare function openConversation(participantId: string, creds: Creds, {
|
|
|
16
16
|
reused?: boolean;
|
|
17
17
|
}>;
|
|
18
18
|
export interface SendChatMessageInput {
|
|
19
|
-
|
|
19
|
+
/**
|
|
20
|
+
* Chat to send in. Required unless `to` names a person you already
|
|
21
|
+
* share a conversation with.
|
|
22
|
+
*/
|
|
23
|
+
chatId?: string;
|
|
24
|
+
/**
|
|
25
|
+
* User or agent id of someone you already have a conversation with.
|
|
26
|
+
* Resolves that pair room and sends. Does not open contact. Ignored
|
|
27
|
+
* when `chatId` is set (`chatId` wins).
|
|
28
|
+
*/
|
|
29
|
+
to?: string;
|
|
20
30
|
/**
|
|
21
31
|
* Recipient id. Optional: when omitted, the backend infers the
|
|
22
32
|
* receiver if the chat has exactly one other member (one agent, or one
|
|
@@ -67,7 +77,8 @@ export type AddChatMemberResult = {
|
|
|
67
77
|
*/
|
|
68
78
|
export declare function addChatMember(input: AddChatMemberInput, creds: Creds): Promise<AddChatMemberResult>;
|
|
69
79
|
/**
|
|
70
|
-
* POST /chats/:chatId/messages
|
|
80
|
+
* POST /chats/:chatId/messages, or POST /chats/messages when `to` names
|
|
81
|
+
* an existing participant and `chatId` is omitted.
|
|
71
82
|
*/
|
|
72
83
|
export declare function sendChatMessage(input: SendChatMessageInput, creds: Creds): Promise<SendChatMessageResult>;
|
|
73
84
|
export declare function listMyChats(creds: Creds): Promise<ChatSummary[]>;
|
package/dist/http/ChatClient.js
CHANGED
|
@@ -78,17 +78,27 @@ export async function addChatMember(input, creds) {
|
|
|
78
78
|
return { chat: data['chat'] };
|
|
79
79
|
}
|
|
80
80
|
/**
|
|
81
|
-
* POST /chats/:chatId/messages
|
|
81
|
+
* POST /chats/:chatId/messages, or POST /chats/messages when `to` names
|
|
82
|
+
* an existing participant and `chatId` is omitted.
|
|
82
83
|
*/
|
|
83
84
|
export async function sendChatMessage(input, creds) {
|
|
84
85
|
assertCreds(creds, 'send chat message');
|
|
86
|
+
const chatId = typeof input.chatId === 'string' ? input.chatId.trim() : '';
|
|
87
|
+
const to = typeof input.to === 'string' ? input.to.trim() : '';
|
|
88
|
+
if (!chatId && !to) {
|
|
89
|
+
throw new Error('chatId or to is required for send chat message');
|
|
90
|
+
}
|
|
85
91
|
const entryType = input.entryType ?? 'message';
|
|
86
92
|
const contentType = input.contentType ?? 'text';
|
|
87
|
-
const
|
|
93
|
+
const personSend = !chatId && Boolean(to);
|
|
94
|
+
const url = personSend
|
|
95
|
+
? `${getBackendUrl()}/chats/messages`
|
|
96
|
+
: `${getBackendUrl()}/chats/${encodeURIComponent(chatId)}/messages`;
|
|
97
|
+
const res = await fetch(url, {
|
|
88
98
|
method: 'POST',
|
|
89
99
|
headers: buildHeaders(creds),
|
|
90
100
|
body: JSON.stringify({
|
|
91
|
-
|
|
101
|
+
...(personSend ? { to } : { chatId }),
|
|
92
102
|
messageId: input.messageId,
|
|
93
103
|
text: input.text,
|
|
94
104
|
entryType,
|
|
@@ -109,7 +119,7 @@ export async function sendChatMessage(input, creds) {
|
|
|
109
119
|
success: Boolean(data?.['success']),
|
|
110
120
|
message: String(data?.['message'] ?? 'ok'),
|
|
111
121
|
messageId: String(data?.['messageId'] ?? input.messageId),
|
|
112
|
-
chatId: String(data?.['chatId'] ??
|
|
122
|
+
chatId: String(data?.['chatId'] ?? chatId),
|
|
113
123
|
};
|
|
114
124
|
}
|
|
115
125
|
export async function listMyChats(creds) {
|
|
@@ -1,10 +1,16 @@
|
|
|
1
|
+
import { CONTEXT_GRANT_SCOPE_KINDS } from './grants.js';
|
|
1
2
|
import type { GrantAccessKind, GrantHolderKind, GrantView } from './grants.js';
|
|
2
3
|
/**
|
|
3
|
-
*
|
|
4
|
-
* shared without sharing any chat or agreement it sits in.
|
|
5
|
-
*
|
|
4
|
+
* The context rail's scopes. `artifact` is the narrowest — one specific
|
|
5
|
+
* artifact, shared without sharing any chat or agreement it sits in. `task` is
|
|
6
|
+
* a branch of a work graph: the named task and everything under it. Both are
|
|
7
|
+
* still the context rail, not new grant primitives.
|
|
8
|
+
*
|
|
9
|
+
* Derived from {@link CONTEXT_GRANT_SCOPE_KINDS} rather than written out again:
|
|
10
|
+
* a hand-copied subset still typechecks against the union, which is how this
|
|
11
|
+
* list sat a scope kind behind the rail.
|
|
6
12
|
*/
|
|
7
|
-
export type ContextGrantScopeKind =
|
|
13
|
+
export type ContextGrantScopeKind = (typeof CONTEXT_GRANT_SCOPE_KINDS)[number];
|
|
8
14
|
export type ContextTemporal = 'from-now' | 'from-start';
|
|
9
15
|
export interface ContextGrantScope {
|
|
10
16
|
kind: ContextGrantScopeKind;
|