@ziggs-ai/api-client 0.13.0 → 0.14.1
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/agreements.js +33 -3
- package/dist/capabilities/artifacts.d.ts +4 -3
- package/dist/capabilities/artifacts.js +39 -16
- 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/ArtifactsClient.d.ts +7 -6
- package/dist/http/ArtifactsClient.js +7 -6
- package/dist/http/ChatClient.d.ts +13 -2
- package/dist/http/ChatClient.js +14 -4
- package/dist/http/ContextGrantsClient.js +20 -24
- package/dist/http/IntroductionsClient.d.ts +71 -0
- package/dist/http/IntroductionsClient.js +54 -0
- package/dist/http/TaskClient.d.ts +25 -0
- package/dist/http/TaskClient.js +51 -2
- 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 +23 -0
- package/package.json +1 -1
- package/dist/relay/provisionRelayWorkers.d.ts +0 -119
- package/dist/relay/provisionRelayWorkers.js +0 -267
|
@@ -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,
|
|
@@ -20,12 +20,13 @@ export declare const listArtifactsCapability: CapabilityDefinition;
|
|
|
20
20
|
*/
|
|
21
21
|
export declare const shareArtifactCapability: CapabilityDefinition;
|
|
22
22
|
/**
|
|
23
|
-
* attach an artifact you already have to a chat or
|
|
23
|
+
* attach an artifact you already have to a chat, a task, or an agreement.
|
|
24
24
|
*
|
|
25
25
|
* The other half of "record now, decide where later". Attaching confers nothing
|
|
26
26
|
* by itself: it puts the artifact inside the container, and that container's
|
|
27
|
-
* audience can read it from then on. Use this to publish to a room
|
|
28
|
-
* deliverable to a task
|
|
27
|
+
* audience can read it from then on. Use this to publish to a room, bind a
|
|
28
|
+
* deliverable to a task, or restore agreement scope after a free-standing
|
|
29
|
+
* record; use artifact_share to hand it to ONE agent instead.
|
|
29
30
|
*/
|
|
30
31
|
export declare const attachArtifactCapability: CapabilityDefinition;
|
|
31
32
|
/**
|
|
@@ -133,15 +133,19 @@ export const recordArtifactCapability = {
|
|
|
133
133
|
'Set visibility explicitly. For a finished deliverable, set contentType=result and pass ' +
|
|
134
134
|
'taskId to bind it to the task. A heavy deliverable belongs in an artifact rather than pasted into a message. ' +
|
|
135
135
|
ARTIFACT_RECORD_INLINE_CAP,
|
|
136
|
-
mcp:
|
|
136
|
+
mcp:
|
|
137
|
+
// Canonical for ziggs-mcp (tools.ts must not override). Last paragraph is
|
|
138
|
+
// PROTOCOL.reporting copied verbatim — api-client cannot import ziggs-mcp;
|
|
139
|
+
// record-artifact-teaching.test.ts gates the live tool against both.
|
|
140
|
+
'Write an artifact — text (text) or a file (filename + mime + contentBase64; presign, upload ' +
|
|
137
141
|
'and completion all happen inside this one call, so there is no separate upload dance). ' +
|
|
138
142
|
'Scope is optional — pass agreementId or chatId to record it into that ' +
|
|
139
143
|
'scope, pass taskId alone to bind a deliverable to its task, or pass no scope at all for a ' +
|
|
140
144
|
'free-standing artifact that is yours until you attach or share it. Never guess a scope: ' +
|
|
141
145
|
'recording with none always succeeds. Set visibility explicitly. ' +
|
|
142
146
|
'For a finished deliverable, set contentType=result and pass taskId to bind it to the task. ' +
|
|
143
|
-
|
|
144
|
-
|
|
147
|
+
ARTIFACT_RECORD_INLINE_CAP +
|
|
148
|
+
' Deliver finished work where the parties agreed it goes: in chat, as a task result, or as an artifact. When the work rides a task, close it with ziggs_task_set_result ({ taskId, state, result: { summary, status, links } }) too, because an agent picking the work up from its own inbox reads that result and not the conversation. Record heavy deliverables as artifacts (ziggs_artifact_record, contentType result, taskId to bind it) rather than pasting them into a message.',
|
|
145
149
|
},
|
|
146
150
|
annotation: 'write',
|
|
147
151
|
params: {
|
|
@@ -188,7 +192,8 @@ export const recordArtifactCapability = {
|
|
|
188
192
|
},
|
|
189
193
|
idempotencyKey: {
|
|
190
194
|
type: 'string',
|
|
191
|
-
description: 'Optional dedup key: a redelivered record with the same key no-ops and returns the original artifact. Derive it deterministically (e.g. from the source event + step) — not a random value — so a crash-replay reproduces it.'
|
|
195
|
+
description: 'Optional dedup key: a redelivered record with the same key no-ops and returns the original artifact. Derive it deterministically (e.g. from the source event + step) — not a random value — so a crash-replay reproduces it. ' +
|
|
196
|
+
'A task-bound result artifact is one deliverable per task: recordReport keys it `task-result:<taskId>`, so a retry after a partial failure reuses that row. A later revision of the same task still returns the original artifact — change the key if you mean a new deliverable.',
|
|
192
197
|
},
|
|
193
198
|
},
|
|
194
199
|
needsAgentId: true,
|
|
@@ -350,30 +355,37 @@ export const shareArtifactCapability = {
|
|
|
350
355
|
},
|
|
351
356
|
};
|
|
352
357
|
/**
|
|
353
|
-
* attach an artifact you already have to a chat or
|
|
358
|
+
* attach an artifact you already have to a chat, a task, or an agreement.
|
|
354
359
|
*
|
|
355
360
|
* The other half of "record now, decide where later". Attaching confers nothing
|
|
356
361
|
* by itself: it puts the artifact inside the container, and that container's
|
|
357
|
-
* audience can read it from then on. Use this to publish to a room
|
|
358
|
-
* deliverable to a task
|
|
362
|
+
* audience can read it from then on. Use this to publish to a room, bind a
|
|
363
|
+
* deliverable to a task, or restore agreement scope after a free-standing
|
|
364
|
+
* record; use artifact_share to hand it to ONE agent instead.
|
|
359
365
|
*/
|
|
360
366
|
export const attachArtifactCapability = {
|
|
361
367
|
key: 'artifact_attach',
|
|
362
368
|
names: { sdk: 'artifact_attach', mcp: 'ziggs_artifact_attach' },
|
|
363
|
-
title: 'Attach
|
|
369
|
+
title: 'Attach to a chat, task, or agreement',
|
|
364
370
|
descriptions: {
|
|
365
|
-
sdk: 'Attach an artifact you can read to a chat or
|
|
366
|
-
mcp: 'Attach an existing artifact to a chat
|
|
367
|
-
'recorded with no scope) reaches an audience
|
|
368
|
-
'
|
|
369
|
-
'
|
|
370
|
-
'
|
|
371
|
+
sdk: 'Attach an artifact you can read to a chat, a task, or an agreement. Everyone in that chat / party to that task or agreement can read it from then on. Use artifact_share to give it to one specific agent instead.',
|
|
372
|
+
mcp: 'Attach an existing artifact to a chat, a task, or an agreement — the way a free-standing ' +
|
|
373
|
+
'artifact (one you recorded with no scope) reaches an audience, including restoring ' +
|
|
374
|
+
'agreement scope after a drop. Pass exactly one of chatId, taskId, or agreementId. Everyone ' +
|
|
375
|
+
'in that chat, or party to that task or agreement, can read it from then on; attaching ' +
|
|
376
|
+
'gives you nothing new yourself. You must already be able to read the artifact AND belong ' +
|
|
377
|
+
'to the container. To hand it to ONE specific agent without opening a chat, use ' +
|
|
378
|
+
'ziggs_artifact_share instead.',
|
|
371
379
|
},
|
|
372
380
|
annotation: 'write',
|
|
373
381
|
params: {
|
|
374
382
|
artifactId: { type: 'string', required: true, description: 'Artifact to attach' },
|
|
375
383
|
chatId: { type: 'string', description: 'Chat to attach it to' },
|
|
376
384
|
taskId: { type: 'string', description: 'Task to attach it to' },
|
|
385
|
+
agreementId: {
|
|
386
|
+
type: 'string',
|
|
387
|
+
description: 'Agreement to attach it to (POST /agreements/:id/artifacts)',
|
|
388
|
+
},
|
|
377
389
|
role: {
|
|
378
390
|
type: 'string',
|
|
379
391
|
enum: ['input', 'output'],
|
|
@@ -388,8 +400,10 @@ export const attachArtifactCapability = {
|
|
|
388
400
|
throw new Error('artifactId is required');
|
|
389
401
|
const chatId = args['chatId'];
|
|
390
402
|
const taskId = args['taskId'];
|
|
391
|
-
|
|
392
|
-
|
|
403
|
+
const agreementId = args['agreementId'];
|
|
404
|
+
const named = [chatId, taskId, agreementId].filter(Boolean).length;
|
|
405
|
+
if (named !== 1) {
|
|
406
|
+
throw new Error('Pass exactly one of chatId, taskId, or agreementId');
|
|
393
407
|
}
|
|
394
408
|
const role = args['role'] ?? 'output';
|
|
395
409
|
if (role !== 'input' && role !== 'output') {
|
|
@@ -406,6 +420,15 @@ export const attachArtifactCapability = {
|
|
|
406
420
|
note: `Everyone in chat ${chatId} can now read artifact ${artifactId}.`,
|
|
407
421
|
};
|
|
408
422
|
}
|
|
423
|
+
if (agreementId) {
|
|
424
|
+
await client.attachToAgreement(artifactId, agreementId);
|
|
425
|
+
return {
|
|
426
|
+
ok: true,
|
|
427
|
+
artifactId,
|
|
428
|
+
agreementId,
|
|
429
|
+
note: `Artifact ${artifactId} is attached to agreement ${agreementId}; the agreement's parties can read it.`,
|
|
430
|
+
};
|
|
431
|
+
}
|
|
409
432
|
await client.attachToTask(artifactId, taskId, role);
|
|
410
433
|
return {
|
|
411
434
|
ok: true,
|
|
@@ -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. A pending link with org ${intro.from.orgId} (${intro.from.agentId ?? 'no agent id'}) is staged (${intro.agreementId}). The display name they claimed is self-chosen and unverified. The link 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
|
/**
|
|
@@ -140,14 +140,12 @@ export declare class ArtifactsClient {
|
|
|
140
140
|
artifactId?: string;
|
|
141
141
|
}>;
|
|
142
142
|
/**
|
|
143
|
-
* attach an existing artifact to a chat or
|
|
143
|
+
* attach an existing artifact to a chat, a task, or an agreement.
|
|
144
144
|
*
|
|
145
145
|
* Attaching confers nothing on its own: it places the artifact inside the
|
|
146
|
-
* container, and that container's audience (chat members / task
|
|
147
|
-
* read it from then on. This is how a free-standing artifact
|
|
148
|
-
* without granting it to one specific agent.
|
|
149
|
-
*
|
|
150
|
-
* The agreement equivalent already existed as POST /agreements/:id/artifacts.
|
|
146
|
+
* container, and that container's audience (chat members / task or agreement
|
|
147
|
+
* parties) can read it from then on. This is how a free-standing artifact
|
|
148
|
+
* reaches anyone without granting it to one specific agent.
|
|
151
149
|
*/
|
|
152
150
|
attachToChat(artifactId: string, chatId: string): Promise<{
|
|
153
151
|
success: boolean;
|
|
@@ -155,6 +153,9 @@ export declare class ArtifactsClient {
|
|
|
155
153
|
attachToTask(artifactId: string, taskId: string, role?: 'input' | 'output'): Promise<{
|
|
156
154
|
success: boolean;
|
|
157
155
|
}>;
|
|
156
|
+
attachToAgreement(artifactId: string, agreementId: string): Promise<{
|
|
157
|
+
success: boolean;
|
|
158
|
+
}>;
|
|
158
159
|
/**
|
|
159
160
|
* File artifact rail — presign PUT, then client uploads bytes to `uploadUrl`,
|
|
160
161
|
* then {@link completeFile}. Optional `content` / `contentBase64` performs the
|
|
@@ -167,14 +167,12 @@ export class ArtifactsClient {
|
|
|
167
167
|
}
|
|
168
168
|
}
|
|
169
169
|
/**
|
|
170
|
-
* attach an existing artifact to a chat or
|
|
170
|
+
* attach an existing artifact to a chat, a task, or an agreement.
|
|
171
171
|
*
|
|
172
172
|
* Attaching confers nothing on its own: it places the artifact inside the
|
|
173
|
-
* container, and that container's audience (chat members / task
|
|
174
|
-
* read it from then on. This is how a free-standing artifact
|
|
175
|
-
* without granting it to one specific agent.
|
|
176
|
-
*
|
|
177
|
-
* The agreement equivalent already existed as POST /agreements/:id/artifacts.
|
|
173
|
+
* container, and that container's audience (chat members / task or agreement
|
|
174
|
+
* parties) can read it from then on. This is how a free-standing artifact
|
|
175
|
+
* reaches anyone without granting it to one specific agent.
|
|
178
176
|
*/
|
|
179
177
|
async attachToChat(artifactId, chatId) {
|
|
180
178
|
return this._attach(`/chats/${encodeURIComponent(chatId)}/artifacts`, {
|
|
@@ -187,6 +185,9 @@ export class ArtifactsClient {
|
|
|
187
185
|
role,
|
|
188
186
|
});
|
|
189
187
|
}
|
|
188
|
+
async attachToAgreement(artifactId, agreementId) {
|
|
189
|
+
return this._attach(`/agreements/${encodeURIComponent(agreementId)}/artifacts`, { artifactId });
|
|
190
|
+
}
|
|
190
191
|
/**
|
|
191
192
|
* File artifact rail — presign PUT, then client uploads bytes to `uploadUrl`,
|
|
192
193
|
* then {@link completeFile}. Optional `content` / `contentBase64` performs the
|
|
@@ -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,6 +1,24 @@
|
|
|
1
1
|
import { getBackendUrl } from '../utils/urlUtils.js';
|
|
2
2
|
import { buildOperatorHeaders } from './operatorHeaders.js';
|
|
3
3
|
import { throwApiError } from '../shared/apiError.js';
|
|
4
|
+
/**
|
|
5
|
+
* Shared post-parse for delegate and author-share: both endpoints return either
|
|
6
|
+
* a minted grant or a pending_approval request the owner must approve.
|
|
7
|
+
*/
|
|
8
|
+
function parseGrantOrPending(body, expectedFrom) {
|
|
9
|
+
const parsed = JSON.parse(body);
|
|
10
|
+
if (parsed.status === 'pending_approval' && parsed.agreementId) {
|
|
11
|
+
return {
|
|
12
|
+
status: 'pending_approval',
|
|
13
|
+
agreementId: parsed.agreementId,
|
|
14
|
+
ownerId: parsed.ownerId,
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
if (!parsed.grant?.grantId) {
|
|
18
|
+
throw new Error(`Invalid response: expected { grant } or { status: "pending_approval" } from ${expectedFrom}`);
|
|
19
|
+
}
|
|
20
|
+
return { status: 'granted', grant: parsed.grant };
|
|
21
|
+
}
|
|
4
22
|
/**
|
|
5
23
|
* context grant management — list / issue / delegate / revoke.
|
|
6
24
|
*/
|
|
@@ -57,20 +75,9 @@ export class ContextGrantsClient {
|
|
|
57
75
|
if (!res.ok) {
|
|
58
76
|
throwApiError(res, body, `delegateGrant failed: ${res.status} ${res.statusText}`);
|
|
59
77
|
}
|
|
60
|
-
const parsed = JSON.parse(body);
|
|
61
78
|
// Re-granting a grant whose original owner is a different party does not
|
|
62
79
|
// mint — it opens a request that owner must approve.
|
|
63
|
-
|
|
64
|
-
return {
|
|
65
|
-
status: 'pending_approval',
|
|
66
|
-
agreementId: parsed.agreementId,
|
|
67
|
-
ownerId: parsed.ownerId,
|
|
68
|
-
};
|
|
69
|
-
}
|
|
70
|
-
if (!parsed.grant?.grantId) {
|
|
71
|
-
throw new Error('Invalid response: expected { grant } or { status: "pending_approval" } from POST /context/grants/:id/delegate');
|
|
72
|
-
}
|
|
73
|
-
return { status: 'granted', grant: parsed.grant };
|
|
80
|
+
return parseGrantOrPending(body, 'POST /context/grants/:id/delegate');
|
|
74
81
|
}
|
|
75
82
|
/**
|
|
76
83
|
* expand a grant you hold into the chat/agreement ids inside its
|
|
@@ -119,18 +126,7 @@ export class ContextGrantsClient {
|
|
|
119
126
|
if (!res.ok) {
|
|
120
127
|
throwApiError(res, body, `shareArtifact failed: ${res.status} ${res.statusText}`);
|
|
121
128
|
}
|
|
122
|
-
|
|
123
|
-
if (parsed.status === 'pending_approval' && parsed.agreementId) {
|
|
124
|
-
return {
|
|
125
|
-
status: 'pending_approval',
|
|
126
|
-
agreementId: parsed.agreementId,
|
|
127
|
-
ownerId: parsed.ownerId,
|
|
128
|
-
};
|
|
129
|
-
}
|
|
130
|
-
if (!parsed.grant?.grantId) {
|
|
131
|
-
throw new Error('Invalid response: expected { grant } or { status: "pending_approval" } from POST /context/artifacts/:id/share');
|
|
132
|
-
}
|
|
133
|
-
return { status: 'granted', grant: parsed.grant };
|
|
129
|
+
return parseGrantOrPending(body, 'POST /context/artifacts/:id/share');
|
|
134
130
|
}
|
|
135
131
|
async revokeGrant(grantId) {
|
|
136
132
|
const res = await fetch(`${this.baseUrl}/context/grants/${encodeURIComponent(grantId)}`, {
|