@ziggs-ai/api-client 0.14.0 → 0.14.2
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.d.ts +14 -1
- package/dist/capabilities/agreements.js +41 -40
- package/dist/capabilities/artifacts.d.ts +4 -3
- package/dist/capabilities/artifacts.js +39 -16
- package/dist/capabilities/discovery.js +7 -5
- package/dist/capabilities/index.d.ts +3 -2
- package/dist/capabilities/index.js +3 -2
- package/dist/capabilities/introductions.js +1 -1
- package/dist/capabilities/links.js +14 -15
- package/dist/capabilities/nextCall.d.ts +0 -19
- package/dist/capabilities/nextCall.js +0 -31
- package/dist/capabilities/tasks.d.ts +9 -0
- package/dist/capabilities/tasks.js +60 -0
- package/dist/http/AgreementClient.js +6 -0
- package/dist/http/ArtifactsClient.d.ts +7 -6
- package/dist/http/ArtifactsClient.js +7 -6
- package/dist/http/ContextGrantsClient.js +20 -24
- package/dist/http/IntroductionsClient.d.ts +16 -5
- package/dist/http/OrgsClient.d.ts +14 -4
- package/dist/http/OrgsClient.js +16 -6
- package/dist/http/index.d.ts +1 -1
- package/dist/types.d.ts +23 -0
- package/package.json +1 -1
|
@@ -1,4 +1,17 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { ClaimedKind } from '../http/AgreementClient.js';
|
|
2
|
+
import type { Agreement } from '../types.js';
|
|
3
|
+
import { type CapabilityDefinition, type CapabilityEnv } from './types.js';
|
|
4
|
+
/**
|
|
5
|
+
* One claim result shape for both surfaces. The SDK protocol runner used to
|
|
6
|
+
* return `{agreement, kind}` while MCP returned `{status, kind, message,
|
|
7
|
+
* agreement}` — same HTTP call, two answers.
|
|
8
|
+
*/
|
|
9
|
+
export declare function presentClaimResult(agreement: Agreement, kind: ClaimedKind, env: CapabilityEnv): {
|
|
10
|
+
status: string;
|
|
11
|
+
kind: ClaimedKind;
|
|
12
|
+
message: string;
|
|
13
|
+
agreement: Agreement;
|
|
14
|
+
};
|
|
2
15
|
/**
|
|
3
16
|
* the one claim verb. Requests, standing offers, and link invites
|
|
4
17
|
* are all open broadcasts; claiming any of them is this call. The respond
|
|
@@ -9,6 +9,46 @@ function claimedWhat(kind) {
|
|
|
9
9
|
return 'Hand-off claimed — the pinned agent works FOR you.';
|
|
10
10
|
return 'Request claimed — you provide the work.';
|
|
11
11
|
}
|
|
12
|
+
/**
|
|
13
|
+
* One claim result shape for both surfaces. The SDK protocol runner used to
|
|
14
|
+
* return `{agreement, kind}` while MCP returned `{status, kind, message,
|
|
15
|
+
* agreement}` — same HTTP call, two answers.
|
|
16
|
+
*/
|
|
17
|
+
export function presentClaimResult(agreement, kind, env) {
|
|
18
|
+
if (kind === 'link') {
|
|
19
|
+
return {
|
|
20
|
+
status: 'linked',
|
|
21
|
+
kind,
|
|
22
|
+
message: `Link invite claimed — you are now linked. ${linkIsReachOnly(env)}`,
|
|
23
|
+
agreement,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
if (agreement?.status !== 'active') {
|
|
27
|
+
const held = (agreement?.approvals ?? []).find((a) => a.status === 'pending');
|
|
28
|
+
const approveUrl = `${webAppOrigin(env)}/app/agreements/${agreement.agreementId}`;
|
|
29
|
+
const remedy = held?.heldReason === 'contact-basis'
|
|
30
|
+
? 'This is a first engagement with that counterparty — your human approves once; a standing link covers it after that.'
|
|
31
|
+
: 'Claiming for a job your human already approved activates at once — name the job with mandateAgreementId.';
|
|
32
|
+
return {
|
|
33
|
+
status: 'claimed',
|
|
34
|
+
kind,
|
|
35
|
+
message: `${claimedWhat(kind)} It is not active yet: the formation is waiting on ` +
|
|
36
|
+
'human approval, so nothing can be created under it until that lands. ' +
|
|
37
|
+
`Your human can approve it here: ${approveUrl} — ${remedy}`,
|
|
38
|
+
agreement,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
return {
|
|
42
|
+
status: 'claimed',
|
|
43
|
+
kind,
|
|
44
|
+
message: kind === 'offer'
|
|
45
|
+
? 'Standing offer claimed — the publisher provides, your side pays. Spawn work under it with the task-create tool.'
|
|
46
|
+
: kind === 'hand-off'
|
|
47
|
+
? 'Hand-off claimed — the pinned agent works FOR you: you are the customer (and the payer when priced), never the worker. Spawn work under it with the task-create tool.'
|
|
48
|
+
: 'Request claimed — you provide the work. Read the terms, then post progress and set the task result under this agreement.',
|
|
49
|
+
agreement,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
12
52
|
/**
|
|
13
53
|
* the one claim verb. Requests, standing offers, and link invites
|
|
14
54
|
* are all open broadcasts; claiming any of them is this call. The respond
|
|
@@ -45,46 +85,7 @@ export const agreementClaimCapability = {
|
|
|
45
85
|
const { agreement, kind } = await claimOpenAgreement(args['agreementId'], fullCreds(env), typeof declaredMandate === 'string' && declaredMandate
|
|
46
86
|
? { mandateAgreementId: declaredMandate }
|
|
47
87
|
: {});
|
|
48
|
-
|
|
49
|
-
return {
|
|
50
|
-
status: 'linked',
|
|
51
|
-
kind,
|
|
52
|
-
message: `Link invite claimed — you are now linked. ${linkIsReachOnly(env)}`,
|
|
53
|
-
agreement,
|
|
54
|
-
};
|
|
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
|
-
}
|
|
78
|
-
return {
|
|
79
|
-
status: 'claimed',
|
|
80
|
-
kind,
|
|
81
|
-
message: kind === 'offer'
|
|
82
|
-
? 'Standing offer claimed — the publisher provides, your side pays. Spawn work under it with the task-create tool.'
|
|
83
|
-
: kind === 'hand-off'
|
|
84
|
-
? 'Hand-off claimed — the pinned agent works FOR you: you are the customer (and the payer when priced), never the worker. Spawn work under it with the task-create tool.'
|
|
85
|
-
: 'Request claimed — you provide the work. Read the terms, then post progress and set the task result under this agreement.',
|
|
86
|
-
agreement,
|
|
87
|
-
};
|
|
88
|
+
return presentClaimResult(agreement, kind, env);
|
|
88
89
|
},
|
|
89
90
|
};
|
|
90
91
|
export const AGREEMENT_CAPABILITIES = [agreementClaimCapability];
|
|
@@ -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,
|
|
@@ -5,15 +5,15 @@ export const agentSearchCapability = {
|
|
|
5
5
|
names: { sdk: 'agent_search', mcp: 'ziggs_agent_search' },
|
|
6
6
|
title: 'Search for agents',
|
|
7
7
|
descriptions: {
|
|
8
|
-
sdk: 'Search for agents by capability, name, or description. Returns ranked results with relevance scores. A keyword/natural-language query searches the published store AND
|
|
9
|
-
mcp: 'Find agents (AgentSearchClient). A keyword/natural-language query searches the published store AND
|
|
8
|
+
sdk: 'Search for agents by capability, name, or description. Returns ranked results with relevance scores. A keyword/natural-language query searches the published store AND your own org-mates. A link does NOT make the other person\'s agents findable — how they staff their side is theirs, and it changes without telling anyone; reach a linked person through the link itself (link_list gives their addressable peer id, then chat_open). Passing an EXACT agent id resolves that one agent even if unpublished/private, when you can reach it. Each row carries `doors` — its engagement doors: doors.listingAgreementId is a live listing to CLAIM (agreement_claim — the default, one hop from here) and doors.acceptsProposals says whether a direct proposal would even be accepted (most published agents are claim-only). Use returned agentId in grant/issue tools — do not guess ids.',
|
|
9
|
+
mcp: 'Find agents (AgentSearchClient). A keyword/natural-language query searches the published store AND your own org-mates — so you can find a teammate\'s delegate by name and ziggs_chat_open with it directly, even if it is unpublished/offline and has never been in a chat with you. A link does NOT make the other person\'s agents findable: a link makes the PERSON addressable, never their staffing list. To reach someone you are linked with, take their peer id from ziggs_link_list and ziggs_chat_open with that; their agents become known to you by turning up in rooms. Passing an EXACT agent id resolves that one agent even if unpublished/private, when you can reach it — an id you cannot reach comes back `reachability: "restricted"` with no name/profile, and a linked person\'s unpublished agent is one of those. Each row carries `doors` — its engagement doors: doors.listingAgreementId is a live listing to CLAIM (ziggs_agreement_claim — the default, one hop from here) and doors.acceptsProposals says whether a direct proposal would even be accepted (most published agents are claim-only). Each result carries a per-row `reachability` field derived from HOW you can reach it — `published` (store directory), `same-org`, `managed`, or `engaged`; it is not a blanket "published" label. Use returned agentId in grant/issue tools — do not guess ids.',
|
|
10
10
|
},
|
|
11
11
|
annotation: 'read-only',
|
|
12
12
|
params: {
|
|
13
13
|
query: {
|
|
14
14
|
type: 'string',
|
|
15
15
|
required: true,
|
|
16
|
-
description: 'Keyword/natural-language search (published store + your org-mates
|
|
16
|
+
description: 'Keyword/natural-language search (published store + your org-mates) OR an exact agent id (resolves that agent even if unpublished, when you can reach it)',
|
|
17
17
|
},
|
|
18
18
|
limit: { type: 'number', description: 'Max results (default server-side)' },
|
|
19
19
|
minScore: { type: 'number', description: 'Minimum match score filter' },
|
|
@@ -37,10 +37,12 @@ export const agentSearchCapability = {
|
|
|
37
37
|
return {
|
|
38
38
|
count: 0,
|
|
39
39
|
agents: [],
|
|
40
|
-
searched: ['published store', 'your org-mates'
|
|
40
|
+
searched: ['published store', 'your org-mates'],
|
|
41
41
|
hint: 'Zero hits means no agent profile matched these terms — discovery itself is up. ' +
|
|
42
42
|
'Matching is lexical against agent name/description/tags, so try shorter or different keywords. ' +
|
|
43
|
-
'If you already know the agent, pass its exact agent id as the query to resolve it directly.'
|
|
43
|
+
'If you already know the agent, pass its exact agent id as the query to resolve it directly. ' +
|
|
44
|
+
'Searching for someone you are LINKED with will always miss: a link makes the person addressable, ' +
|
|
45
|
+
'not their agents. Take their peer id from link_list and open a chat with the person instead.',
|
|
44
46
|
};
|
|
45
47
|
}
|
|
46
48
|
return { count: result.agents.length, agents: result.agents };
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
export { type CapabilitySurface, type CapabilityAnnotation, type CapabilityParam, type CapabilityEnv, type CapabilityDefinition, fullCreds, rethrowWithContext, } from './types.js';
|
|
2
|
-
export { nextCall, peerPrincipalId,
|
|
2
|
+
export { nextCall, peerPrincipalId, type NextCall, } from './nextCall.js';
|
|
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
6
|
export { INTRODUCTION_CAPABILITIES, mintIntroductionCapability, redeemIntroductionCapability, listIntroductionsCapability, revokeIntroductionCapability, } from './introductions.js';
|
|
7
|
-
export { AGREEMENT_CAPABILITIES, agreementClaimCapability } from './agreements.js';
|
|
7
|
+
export { AGREEMENT_CAPABILITIES, agreementClaimCapability, presentClaimResult, } from './agreements.js';
|
|
8
|
+
export { TASK_CAPABILITIES, listTasksCapability } from './tasks.js';
|
|
8
9
|
export { AGREEMENT_PROPOSE_PROVIDER_ID_DESCRIPTION } from './proposeProviderId.js';
|
|
9
10
|
export { MARKETPLACE_CAPABILITIES, marketplaceViewCapability } from './marketplace.js';
|
|
10
11
|
export { GRANTS_CAPABILITIES, listGrantsCapability } from './grants.js';
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
export { fullCreds, rethrowWithContext, } from './types.js';
|
|
2
|
-
export { nextCall, peerPrincipalId,
|
|
2
|
+
export { nextCall, peerPrincipalId, } from './nextCall.js';
|
|
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
6
|
export { INTRODUCTION_CAPABILITIES, mintIntroductionCapability, redeemIntroductionCapability, listIntroductionsCapability, revokeIntroductionCapability, } from './introductions.js';
|
|
7
|
-
export { AGREEMENT_CAPABILITIES, agreementClaimCapability } from './agreements.js';
|
|
7
|
+
export { AGREEMENT_CAPABILITIES, agreementClaimCapability, presentClaimResult, } from './agreements.js';
|
|
8
|
+
export { TASK_CAPABILITIES, listTasksCapability } from './tasks.js';
|
|
8
9
|
export { AGREEMENT_PROPOSE_PROVIDER_ID_DESCRIPTION } from './proposeProviderId.js';
|
|
9
10
|
export { MARKETPLACE_CAPABILITIES, marketplaceViewCapability } from './marketplace.js';
|
|
10
11
|
export { GRANTS_CAPABILITIES, listGrantsCapability } from './grants.js';
|
|
@@ -78,7 +78,7 @@ export const redeemIntroductionCapability = {
|
|
|
78
78
|
return {
|
|
79
79
|
introduction: intro,
|
|
80
80
|
message: staged
|
|
81
|
-
? `Redeemed. ${intro.from.
|
|
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
82
|
: intro.outcome === 'already_teammates'
|
|
83
83
|
? 'Redeemed, and there was nothing to link: you already answer to the same org or the same person. Talk to them directly.'
|
|
84
84
|
: 'Redeemed. You two are already linked, so nothing new was proposed.',
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createLink, listAgreements } from '../http/AgreementClient.js';
|
|
2
|
-
import { nextCall
|
|
2
|
+
import { nextCall } from './nextCall.js';
|
|
3
3
|
import { fullCreds } from './types.js';
|
|
4
4
|
const DEFAULT_WEB_URL = 'https://ziggsai.com';
|
|
5
5
|
export function webAppOrigin(env) {
|
|
@@ -31,7 +31,7 @@ function inviteShareUrl(env, agreementId) {
|
|
|
31
31
|
*/
|
|
32
32
|
export function linkIsReachOnly(env) {
|
|
33
33
|
return env.surface === 'mcp'
|
|
34
|
-
? 'A link is reach-only — it shares no context on its own. Open a chat with the PERSON on the other side (ziggs_chat_open, participantId =
|
|
34
|
+
? 'A link is reach-only — it shares no context on its own. Open a chat with the PERSON on the other side (ziggs_chat_open, participantId = that link\'s `peer.id`): delivery lands in their mailbox and whoever runs that side picks it up, which is not yours to choose. Use `peer.id`, never a principal out of `parties` — those are display faces, and chat_open refuses a face because it names no single party. Opening the room admits both sides to read and post from then on. To share context that already exists, issue a grant on it with ziggs_context_issue_grant or share a slice of one you hold with ziggs_context_delegate.'
|
|
35
35
|
: 'A link is reach-only — it shares no context on its own. Open a chat with the PERSON on the other side; delivery lands in their mailbox and whoever runs that side picks it up, which is not yours to choose. Opening the room admits both sides to it from then on. To share context that already exists, share a slice of a grant you hold with context_delegate, or ask the peer owner to issue one.';
|
|
36
36
|
}
|
|
37
37
|
const LINK_STATUSES = ['active', 'open', 'cancelled', 'all'];
|
|
@@ -53,8 +53,8 @@ export const listLinksCapability = {
|
|
|
53
53
|
names: { sdk: 'link_list', mcp: 'ziggs_link_list' },
|
|
54
54
|
title: 'List your links',
|
|
55
55
|
descriptions: {
|
|
56
|
-
sdk: 'List link agreements for this agent — bilateral agent-to-agent trust relationships (GET /agreements?engagementKind=link). Any agent can link with any other agent. Defaults to ACTIVE links only; pass status to see pending proposals ("open") or revoked ones ("cancelled"). Each item is a link summary: agreementId, status, proposalStatus, parties.
|
|
57
|
-
mcp: 'List link agreements for this agent — bilateral agent-to-agent trust relationships, NOT third-party service connections (see ziggs_connection_list for those) (GET /agreements?engagementKind=link). Any agent can link with any other agent. Defaults to ACTIVE links only; pass status to see pending proposals ("open") or revoked ones ("cancelled"). Each item is a link summary: agreementId, status, proposalStatus, and
|
|
56
|
+
sdk: 'List link agreements for this agent — bilateral agent-to-agent trust relationships (GET /agreements?engagementKind=link). Any agent can link with any other agent. Defaults to ACTIVE links only; pass status to see pending proposals ("open") or revoked ones ("cancelled"). Each item is a link summary: agreementId, status, proposalStatus, parties. Each active link carries `peer` — the person on the other side, by an id you can address (`peer.id`) plus their name. Address them with that. The principals in `parties` are display faces masked per viewer, and the actor slots record which agent carried the paperwork; neither is an address. Connect with someone new using link_propose; end a link with agreement_revoke.',
|
|
57
|
+
mcp: 'List link agreements for this agent — bilateral agent-to-agent trust relationships, NOT third-party service connections (see ziggs_connection_list for those) (GET /agreements?engagementKind=link). Any agent can link with any other agent. Defaults to ACTIVE links only; pass status to see pending proposals ("open") or revoked ones ("cancelled"). Each item is a link summary: agreementId, status, proposalStatus, parties, and — on links where you are one of the two sides — `peer`, the person on the other side by an addressable id (`peer.id`) and name. To reach them, pass `peer.id` to ziggs_chat_open. Do NOT address anything out of `parties`: those principals are display faces masked per viewer, and the actor slots are courier info, so both are refused as addresses. Approve pending links via ziggs_agreement_respond; connect with someone new using ziggs_link_propose; end one with ziggs_agreement_revoke.',
|
|
58
58
|
},
|
|
59
59
|
annotation: 'read-only',
|
|
60
60
|
params: {
|
|
@@ -77,27 +77,26 @@ export const listLinksCapability = {
|
|
|
77
77
|
const active = links.filter((a) => a.status === 'active');
|
|
78
78
|
// A link grants reach and nothing else, so the move after seeing one is
|
|
79
79
|
// always the same: open a room with that peer, or share context explicitly.
|
|
80
|
-
// Both were named in prose, and the peer id had to be dug out of `parties`
|
|
81
|
-
// by whoever read it — while being right here.
|
|
82
80
|
//
|
|
83
|
-
// The peer
|
|
84
|
-
//
|
|
85
|
-
//
|
|
86
|
-
//
|
|
87
|
-
//
|
|
88
|
-
//
|
|
81
|
+
// The peer id comes from the server now. Two earlier attempts read it off
|
|
82
|
+
// `parties` here and both were wrong: the peer's `actor` is courier info,
|
|
83
|
+
// null on any link two people formed from the web; the peer's `principal`
|
|
84
|
+
// is display state, masked to a `psn_` face across an org boundary — which
|
|
85
|
+
// is every link — and chat_open refuses a face. Neither could be fixed on
|
|
86
|
+
// this side, because the addressable id is not in the payload to find. It
|
|
87
|
+
// is now, as `peer`, resolved relative to whoever asked.
|
|
89
88
|
const readPlan = [];
|
|
90
89
|
let unnamedPeers = 0;
|
|
91
90
|
for (const link of active.slice(0, 3)) {
|
|
92
|
-
const peer =
|
|
91
|
+
const peer = link.peer?.id;
|
|
93
92
|
if (!peer) {
|
|
94
93
|
unnamedPeers += 1;
|
|
95
94
|
continue;
|
|
96
95
|
}
|
|
97
|
-
readPlan.push(nextCall(env, 'chat_open', { participantId: peer },
|
|
96
|
+
readPlan.push(nextCall(env, 'chat_open', { participantId: peer }, `open a room with ${link.peer?.name ?? 'this linked peer'} — a link alone carries no context`));
|
|
98
97
|
}
|
|
99
98
|
if (unnamedPeers > 0) {
|
|
100
|
-
readPlan.push(nextCall(env, 'chat_open', undefined, 'open a room with a linked peer: participantId is
|
|
99
|
+
readPlan.push(nextCall(env, 'chat_open', undefined, 'open a room with a linked peer: participantId is that link\'s `peer.id`. Do NOT pass a principal out of `parties` — those are display faces and chat_open refuses them'));
|
|
101
100
|
}
|
|
102
101
|
if (active.length) {
|
|
103
102
|
readPlan.push(nextCall(env, 'context_issue_grant', undefined, 'share context that already exists: a link does not share any on its own'));
|
|
@@ -54,22 +54,3 @@ export declare function nextCall(env: CapabilityEnv, capabilityKey: string, args
|
|
|
54
54
|
* call: the caller would run it, and it would do something they did not ask for.
|
|
55
55
|
*/
|
|
56
56
|
export declare function peerPrincipalId(parties: AgreementParties | undefined, selfId: string | undefined): string | null;
|
|
57
|
-
/**
|
|
58
|
-
* The peer's principal, located by finding MY side rather than by knowing my
|
|
59
|
-
* own principal id.
|
|
60
|
-
*
|
|
61
|
-
* An agent knows its own agent id and not the id of the person it answers for,
|
|
62
|
-
* so it cannot ask {@link peerPrincipalId} which of two people it is. What it
|
|
63
|
-
* CAN recognise is its own courier stamp: if my agent id is in one side's
|
|
64
|
-
* `actor`, that side is mine and the other side's principal is the peer.
|
|
65
|
-
*
|
|
66
|
-
* Note the difference from the bug this replaced. Reading the peer's `actor` as
|
|
67
|
-
* the peer's address was wrong — those slots are courier info, null on most
|
|
68
|
-
* links, and never a door. Reading MY OWN `actor` to work out which side I am on
|
|
69
|
-
* is sound, because I am comparing against an id I hold.
|
|
70
|
-
*
|
|
71
|
-
* Returns null when neither side carries my stamp, which is the common case for
|
|
72
|
-
* a link two people formed from the web. No pre-filled call is the right answer
|
|
73
|
-
* there: naming the wrong counterparty would get run.
|
|
74
|
-
*/
|
|
75
|
-
export declare function peerPrincipalForCourier(parties: AgreementParties | undefined, myAgentId: string | undefined): string | null;
|
|
@@ -45,34 +45,3 @@ export function peerPrincipalId(parties, selfId) {
|
|
|
45
45
|
return provider;
|
|
46
46
|
return null;
|
|
47
47
|
}
|
|
48
|
-
/**
|
|
49
|
-
* The peer's principal, located by finding MY side rather than by knowing my
|
|
50
|
-
* own principal id.
|
|
51
|
-
*
|
|
52
|
-
* An agent knows its own agent id and not the id of the person it answers for,
|
|
53
|
-
* so it cannot ask {@link peerPrincipalId} which of two people it is. What it
|
|
54
|
-
* CAN recognise is its own courier stamp: if my agent id is in one side's
|
|
55
|
-
* `actor`, that side is mine and the other side's principal is the peer.
|
|
56
|
-
*
|
|
57
|
-
* Note the difference from the bug this replaced. Reading the peer's `actor` as
|
|
58
|
-
* the peer's address was wrong — those slots are courier info, null on most
|
|
59
|
-
* links, and never a door. Reading MY OWN `actor` to work out which side I am on
|
|
60
|
-
* is sound, because I am comparing against an id I hold.
|
|
61
|
-
*
|
|
62
|
-
* Returns null when neither side carries my stamp, which is the common case for
|
|
63
|
-
* a link two people formed from the web. No pre-filled call is the right answer
|
|
64
|
-
* there: naming the wrong counterparty would get run.
|
|
65
|
-
*/
|
|
66
|
-
export function peerPrincipalForCourier(parties, myAgentId) {
|
|
67
|
-
if (!parties || !myAgentId)
|
|
68
|
-
return null;
|
|
69
|
-
const mineIsCreator = parties.creator?.actor === myAgentId;
|
|
70
|
-
const mineIsProvider = parties.provider?.actor === myAgentId;
|
|
71
|
-
// Both sides stamped with me is one estate on both ends, not a peer.
|
|
72
|
-
if (mineIsCreator === mineIsProvider)
|
|
73
|
-
return null;
|
|
74
|
-
const peer = mineIsCreator
|
|
75
|
-
? parties.provider?.principal
|
|
76
|
-
: parties.creator?.principal;
|
|
77
|
-
return peer && peer !== myAgentId ? peer : null;
|
|
78
|
-
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type CapabilityDefinition } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* One list-tasks verb. MCP and the hosted SDK both used to re-declare the
|
|
4
|
+
* same GET /tasks filters; assignedToMe vs assignedTo precedence then
|
|
5
|
+
* drifted independently. The handler is the single place that resolves
|
|
6
|
+
* those shorthands onto the HTTP query.
|
|
7
|
+
*/
|
|
8
|
+
export declare const listTasksCapability: CapabilityDefinition;
|
|
9
|
+
export declare const TASK_CAPABILITIES: CapabilityDefinition[];
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { listTasks } from '../http/TaskClient.js';
|
|
2
|
+
import { fullCreds } from './types.js';
|
|
3
|
+
/**
|
|
4
|
+
* One list-tasks verb. MCP and the hosted SDK both used to re-declare the
|
|
5
|
+
* same GET /tasks filters; assignedToMe vs assignedTo precedence then
|
|
6
|
+
* drifted independently. The handler is the single place that resolves
|
|
7
|
+
* those shorthands onto the HTTP query.
|
|
8
|
+
*/
|
|
9
|
+
export const listTasksCapability = {
|
|
10
|
+
key: 'task_list',
|
|
11
|
+
names: { sdk: 'task_list', mcp: 'ziggs_task_list' },
|
|
12
|
+
title: 'List tasks',
|
|
13
|
+
descriptions: {
|
|
14
|
+
sdk: 'List tasks reachable by this agent (GET /tasks). Scope follows the operator key — same reach as chats and agreements. Optional state / assignee filters and cursor pagination. Use assignedToMe to list your own open work, and createdByMe to list the work you handed to someone else.',
|
|
15
|
+
mcp: 'List tasks reachable by the acting agent (GET /tasks). Scope is determined by the operator key — same reach as chats and agreements. Optional state / assignee filters and cursor pagination. Use assignedToMe to list your own open work, and createdByMe to list the work you handed to someone else.',
|
|
16
|
+
},
|
|
17
|
+
annotation: 'read-only',
|
|
18
|
+
params: {
|
|
19
|
+
state: {
|
|
20
|
+
type: 'string',
|
|
21
|
+
description: 'Filter by state: active, completed, failed, cancelled',
|
|
22
|
+
},
|
|
23
|
+
cursor: {
|
|
24
|
+
type: 'string',
|
|
25
|
+
description: 'Opaque cursor from a prior nextCursor',
|
|
26
|
+
},
|
|
27
|
+
limit: {
|
|
28
|
+
type: 'number',
|
|
29
|
+
description: 'Max rows (server default when omitted)',
|
|
30
|
+
},
|
|
31
|
+
assignedTo: {
|
|
32
|
+
type: 'string',
|
|
33
|
+
description: 'Filter to tasks assigned to this agent/user id',
|
|
34
|
+
},
|
|
35
|
+
createdByMe: {
|
|
36
|
+
type: 'boolean',
|
|
37
|
+
description: 'Tasks YOU created — the work you handed out to others. This is the check before delegating: assignedToMe lists only what was handed TO you, so a job you already assigned does not appear there and you would assign it twice.',
|
|
38
|
+
},
|
|
39
|
+
assignedToMe: {
|
|
40
|
+
type: 'boolean',
|
|
41
|
+
description: "Shorthand for assignedTo=<this agent's id>. Takes precedence over assignedTo when both are set.",
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
needsAgentId: true,
|
|
45
|
+
handler: async (args, env) => {
|
|
46
|
+
const creds = fullCreds(env);
|
|
47
|
+
const assignedTo = args['assignedToMe']
|
|
48
|
+
? creds.agentId
|
|
49
|
+
: args['assignedTo'];
|
|
50
|
+
const createdBy = args['createdByMe'] ? creds.agentId : undefined;
|
|
51
|
+
return listTasks({
|
|
52
|
+
state: args['state'],
|
|
53
|
+
cursor: args['cursor'],
|
|
54
|
+
limit: typeof args['limit'] === 'number' ? args['limit'] : undefined,
|
|
55
|
+
assignedTo,
|
|
56
|
+
...(createdBy ? { createdBy } : {}),
|
|
57
|
+
}, creds);
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
export const TASK_CAPABILITIES = [listTasksCapability];
|
|
@@ -54,6 +54,12 @@ export function linkSummary(a) {
|
|
|
54
54
|
provider: side(a.parties?.provider),
|
|
55
55
|
proposedTo: side(a.parties?.proposedTo),
|
|
56
56
|
},
|
|
57
|
+
// The one id on a link that can be acted on. `parties` is display state —
|
|
58
|
+
// masked per viewer, so the peer arrives as a face that every addressing
|
|
59
|
+
// verb refuses — which left a link summary carrying nobody you could open a
|
|
60
|
+
// chat with. Dropping it here is what that looked like: the server sent the
|
|
61
|
+
// peer and the allow-list ate it before any caller saw it.
|
|
62
|
+
...(a.peer ? { peer: a.peer } : {}),
|
|
57
63
|
...(a.description ? { description: a.description } : {}),
|
|
58
64
|
// Seat bookkeeping on an open invite — link state, not commerce.
|
|
59
65
|
...(a.linkInvite ? { linkInvite: a.linkInvite } : {}),
|
|
@@ -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
|
|
@@ -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)}`, {
|
|
@@ -1,15 +1,26 @@
|
|
|
1
1
|
/** What became of one hello. `expired` is derived by the server, not stored. */
|
|
2
2
|
export type IntroductionStatus = 'open' | 'redeemed' | 'declined' | 'revoked' | 'expired';
|
|
3
3
|
export type IntroductionOutcome = 'link_pending' | 'already_teammates' | 'already_linked';
|
|
4
|
+
export interface IntroductionFrom {
|
|
5
|
+
orgId: string;
|
|
6
|
+
agentId: string | null;
|
|
7
|
+
agentCreatedAt?: string | null;
|
|
8
|
+
claimedLabel?: {
|
|
9
|
+
text: string;
|
|
10
|
+
selfChosen: boolean;
|
|
11
|
+
verified: boolean;
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* Same text as claimedLabel.text. Not identity — anyone can pick it.
|
|
15
|
+
*/
|
|
16
|
+
label: string;
|
|
17
|
+
}
|
|
4
18
|
export interface IntroductionView {
|
|
5
19
|
token: string;
|
|
6
20
|
status: IntroductionStatus;
|
|
7
21
|
outcome: IntroductionOutcome | null;
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
agentId: string | null;
|
|
11
|
-
orgId: string;
|
|
12
|
-
};
|
|
22
|
+
notice?: string;
|
|
23
|
+
from: IntroductionFrom;
|
|
13
24
|
venue: string | null;
|
|
14
25
|
venueRef: string | null;
|
|
15
26
|
note: string | null;
|
|
@@ -27,13 +27,23 @@ export type OrgResolution = {
|
|
|
27
27
|
* match. Ambiguous names return the candidates rather than guessing.
|
|
28
28
|
*/
|
|
29
29
|
export declare function resolveOrgSelector(orgs: MyOrg[], selector: string): OrgResolution;
|
|
30
|
-
/**
|
|
31
|
-
|
|
30
|
+
/**
|
|
31
|
+
* MCP OAuth auto-provisioned delegate id prefix.
|
|
32
|
+
*
|
|
33
|
+
* The backend stopped classifying delegates by their id and now reads a stamp
|
|
34
|
+
* on the agent row, which is what freed the prefix to drop the vendor name. A
|
|
35
|
+
* client holds credentials, not rows, so this is still a shape test — but it is
|
|
36
|
+
* a shape test against a NAME, and the only thing it decides is which access
|
|
37
|
+
* endpoint to read. It said `delegate--` until the ids were renamed;
|
|
38
|
+
* left alone, every delegate session would have fallen through to the hosted
|
|
39
|
+
* reader and reported the wrong org and connection state.
|
|
40
|
+
*/
|
|
41
|
+
export declare const MCP_OAUTH_DELEGATE_AGENT_ID_PREFIX = "delegate--";
|
|
32
42
|
/** True when `agentId` is an inbound MCP OAuth auto-provisioned delegate. */
|
|
33
43
|
export declare function isMcpOAuthDelegateAgentId(agentId: string): boolean;
|
|
34
44
|
/**
|
|
35
45
|
* runtime acting org from the server (self-hire / agent
|
|
36
|
-
* row): GET /agents/
|
|
46
|
+
* row): GET /agents/delegate/access. Moved here from ziggs-mcp's inline
|
|
37
47
|
* fetch ( "one client for every surface").
|
|
38
48
|
*
|
|
39
49
|
* Only valid for MCP OAuth Claude-delegate sessions. Hosted / fleet
|
|
@@ -44,7 +54,7 @@ export declare function fetchDelegateAccess(creds: Creds, baseUrl?: string): Pro
|
|
|
44
54
|
* session access for hosted / fleet impersonation (owner key +
|
|
45
55
|
* X-Agent-Id, or an agent-scoped key that is not a Claude OAuth delegate).
|
|
46
56
|
*
|
|
47
|
-
* `GET /agents/
|
|
57
|
+
* `GET /agents/delegate/access` refuses those credentials (or reports
|
|
48
58
|
* disconnected), even though every other tool works. The credential's tenant
|
|
49
59
|
* is already on the actor (`GET /orgs/me` → `activeOrgId`).
|
|
50
60
|
*/
|
package/dist/http/OrgsClient.js
CHANGED
|
@@ -42,29 +42,39 @@ export function resolveOrgSelector(orgs, selector) {
|
|
|
42
42
|
return { status: 'ambiguous', matches: byName };
|
|
43
43
|
return { status: 'not-found' };
|
|
44
44
|
}
|
|
45
|
-
/**
|
|
46
|
-
|
|
45
|
+
/**
|
|
46
|
+
* MCP OAuth auto-provisioned delegate id prefix.
|
|
47
|
+
*
|
|
48
|
+
* The backend stopped classifying delegates by their id and now reads a stamp
|
|
49
|
+
* on the agent row, which is what freed the prefix to drop the vendor name. A
|
|
50
|
+
* client holds credentials, not rows, so this is still a shape test — but it is
|
|
51
|
+
* a shape test against a NAME, and the only thing it decides is which access
|
|
52
|
+
* endpoint to read. It said `delegate--` until the ids were renamed;
|
|
53
|
+
* left alone, every delegate session would have fallen through to the hosted
|
|
54
|
+
* reader and reported the wrong org and connection state.
|
|
55
|
+
*/
|
|
56
|
+
export const MCP_OAUTH_DELEGATE_AGENT_ID_PREFIX = 'delegate--';
|
|
47
57
|
/** True when `agentId` is an inbound MCP OAuth auto-provisioned delegate. */
|
|
48
58
|
export function isMcpOAuthDelegateAgentId(agentId) {
|
|
49
59
|
return !!agentId && agentId.startsWith(MCP_OAUTH_DELEGATE_AGENT_ID_PREFIX);
|
|
50
60
|
}
|
|
51
61
|
/**
|
|
52
62
|
* runtime acting org from the server (self-hire / agent
|
|
53
|
-
* row): GET /agents/
|
|
63
|
+
* row): GET /agents/delegate/access. Moved here from ziggs-mcp's inline
|
|
54
64
|
* fetch ( "one client for every surface").
|
|
55
65
|
*
|
|
56
66
|
* Only valid for MCP OAuth Claude-delegate sessions. Hosted / fleet
|
|
57
67
|
* impersonation must use {@link fetchHostedAgentAccess}.
|
|
58
68
|
*/
|
|
59
69
|
export async function fetchDelegateAccess(creds, baseUrl) {
|
|
60
|
-
const url = `${baseUrl || getBackendUrl()}/agents/
|
|
70
|
+
const url = `${baseUrl || getBackendUrl()}/agents/delegate/access`;
|
|
61
71
|
const res = await fetch(url, {
|
|
62
72
|
method: 'GET',
|
|
63
73
|
headers: buildOperatorHeaders(creds.operatorKey, creds.agentId),
|
|
64
74
|
});
|
|
65
75
|
const body = await res.text().catch(() => '');
|
|
66
76
|
if (!res.ok) {
|
|
67
|
-
throwApiError(res, body, `GET /agents/
|
|
77
|
+
throwApiError(res, body, `GET /agents/delegate/access failed: ${res.status}`);
|
|
68
78
|
}
|
|
69
79
|
return body ? JSON.parse(body) : {};
|
|
70
80
|
}
|
|
@@ -72,7 +82,7 @@ export async function fetchDelegateAccess(creds, baseUrl) {
|
|
|
72
82
|
* session access for hosted / fleet impersonation (owner key +
|
|
73
83
|
* X-Agent-Id, or an agent-scoped key that is not a Claude OAuth delegate).
|
|
74
84
|
*
|
|
75
|
-
* `GET /agents/
|
|
85
|
+
* `GET /agents/delegate/access` refuses those credentials (or reports
|
|
76
86
|
* disconnected), even though every other tool works. The credential's tenant
|
|
77
87
|
* is already on the actor (`GET /orgs/me` → `activeOrgId`).
|
|
78
88
|
*/
|
package/dist/http/index.d.ts
CHANGED
|
@@ -12,7 +12,7 @@ export type { ContextReadType, ContextReadQuery, ContextReadEnvelope, ContextSna
|
|
|
12
12
|
export { ContextDiscoveryClient } from './ContextDiscoveryClient.js';
|
|
13
13
|
export type { DiscoverableItem } from './ContextDiscoveryClient.js';
|
|
14
14
|
export { IntroductionsClient } from './IntroductionsClient.js';
|
|
15
|
-
export type { IntroductionView, IntroductionStatus, IntroductionOutcome, MintIntroductionInput, } from './IntroductionsClient.js';
|
|
15
|
+
export type { IntroductionView, IntroductionFrom, IntroductionStatus, IntroductionOutcome, MintIntroductionInput, } from './IntroductionsClient.js';
|
|
16
16
|
export { GrantsClient } from './GrantsClient.js';
|
|
17
17
|
export type { ListGrantsQuery, ListGrantsResult, UnreadableRail } from './GrantsClient.js';
|
|
18
18
|
export { ContextGrantsClient } from './ContextGrantsClient.js';
|
package/dist/types.d.ts
CHANGED
|
@@ -168,6 +168,18 @@ export interface Agreement {
|
|
|
168
168
|
engagementKind?: EngagementKind;
|
|
169
169
|
proposalStatus?: ProposalStatus;
|
|
170
170
|
parties?: AgreementParties;
|
|
171
|
+
/**
|
|
172
|
+
* On a LINK, the person on the other side by an id you can address.
|
|
173
|
+
*
|
|
174
|
+
* `parties` is display state: read across an org boundary a principal comes
|
|
175
|
+
* back as a `psn_` face, which names no single party and every addressing
|
|
176
|
+
* verb refuses. This is the same person, resolved server-side relative to
|
|
177
|
+
* you. Present only when you are one of the two sides.
|
|
178
|
+
*/
|
|
179
|
+
peer?: {
|
|
180
|
+
id: string;
|
|
181
|
+
name: string;
|
|
182
|
+
};
|
|
171
183
|
approvals?: AgreementApprovalEntry[];
|
|
172
184
|
/** Legacy root-level price — always null in practice. Use money.price instead. */
|
|
173
185
|
price?: number | null;
|
|
@@ -175,6 +187,17 @@ export interface Agreement {
|
|
|
175
187
|
money?: {
|
|
176
188
|
price?: number | null;
|
|
177
189
|
paymentStatus?: string;
|
|
190
|
+
transactionId?: string | null;
|
|
191
|
+
/**
|
|
192
|
+
* Ledger hold for this agreement, or null if paymentStatus says held but
|
|
193
|
+
* no hold row exists. Amount is cents. Never includes a wallet id.
|
|
194
|
+
*/
|
|
195
|
+
held?: {
|
|
196
|
+
amount: number;
|
|
197
|
+
currency: string;
|
|
198
|
+
state: 'held' | 'released' | 'refunded';
|
|
199
|
+
agreementId: string;
|
|
200
|
+
} | null;
|
|
178
201
|
};
|
|
179
202
|
lifecycle?: string;
|
|
180
203
|
expiresAt?: string;
|