@ziggs-ai/api-client 0.10.4 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/capabilities/agreementVerbs.d.ts +9 -2
- package/dist/capabilities/agreementVerbs.js +61 -21
- package/dist/capabilities/agreements.d.ts +1 -1
- package/dist/capabilities/agreements.js +18 -6
- package/dist/capabilities/artifacts.d.ts +4 -2
- package/dist/capabilities/artifacts.js +168 -33
- package/dist/capabilities/chat.d.ts +2 -3
- package/dist/capabilities/chat.js +5 -6
- package/dist/capabilities/connections.js +1 -1
- package/dist/capabilities/context.js +3 -0
- package/dist/capabilities/grants.d.ts +7 -6
- package/dist/capabilities/grants.js +9 -8
- package/dist/capabilities/index.d.ts +4 -4
- package/dist/capabilities/index.js +4 -4
- package/dist/capabilities/links.d.ts +17 -6
- package/dist/capabilities/links.js +71 -86
- package/dist/capabilities/marketplace.js +23 -17
- package/dist/capabilities/nextCall.d.ts +36 -5
- package/dist/capabilities/nextCall.js +53 -7
- package/dist/capabilities/payments.d.ts +24 -8
- package/dist/capabilities/payments.js +28 -392
- package/dist/capabilities/proposeProviderId.d.ts +1 -1
- package/dist/capabilities/proposeProviderId.js +1 -1
- package/dist/http/AgreementClient.d.ts +63 -27
- package/dist/http/AgreementClient.js +51 -39
- package/dist/http/ChatClient.d.ts +1 -0
- package/dist/http/ChatClient.js +4 -1
- package/dist/http/ConnectionsClient.js +12 -1
- package/dist/http/ContextGrantsClient.d.ts +15 -1
- package/dist/http/ContextGrantsClient.js +2 -0
- package/dist/http/ContextReadClient.d.ts +14 -5
- package/dist/http/GrantsClient.d.ts +14 -0
- package/dist/http/GrantsClient.js +18 -2
- package/dist/http/InboxClient.js +4 -0
- package/dist/http/MarketplaceClient.d.ts +6 -8
- package/dist/http/MarketplaceClient.js +11 -30
- package/dist/http/TaskClient.d.ts +5 -0
- package/dist/http/TaskClient.js +4 -7
- package/dist/http/agreementFlows.d.ts +6 -7
- package/dist/http/agreementFlows.js +14 -20
- package/dist/http/grants.d.ts +28 -0
- package/dist/http/index.d.ts +2 -2
- package/dist/index.d.ts +3 -3
- package/dist/index.js +1 -1
- package/dist/instanceIdentity.d.ts +4 -0
- package/dist/instanceIdentity.js +44 -0
- package/dist/relay/provisionRelayWorkers.d.ts +2 -2
- package/dist/relay/provisionRelayWorkers.js +5 -5
- package/dist/types.d.ts +80 -31
- package/dist/types.js +18 -0
- package/package.json +1 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { type Creds, type Agreement, type EngagementKind, type BroadcastAudience } from '../types.js';
|
|
2
2
|
/**
|
|
3
3
|
* Shared proposal terms. When `engagementKind` is omitted the server defaults to `service`.
|
|
4
|
-
* For an open buyer-broadcast
|
|
4
|
+
* For an open buyer-broadcast request, set `proposedTo` to a broadcast sentinel —
|
|
5
5
|
* `OPEN_AGREEMENT_TARGET` (`"everyone"`, fully public) or `ORG_AGREEMENT_TARGET`
|
|
6
6
|
* (`"org"`, scoped to your org). Prefer `proposeBroadcast({ audience })` over
|
|
7
7
|
* setting `proposedTo` by hand.
|
|
@@ -79,7 +79,7 @@ export declare function proposeAgreement(proposalData: ProposeDirectInput, creds
|
|
|
79
79
|
/** Propose a contract to one party (user or agent). Server defaults `engagementKind` to `service`. */
|
|
80
80
|
export declare function proposeDirectTo(input: ProposeDirectInput, creds: Creds): Promise<Agreement>;
|
|
81
81
|
/**
|
|
82
|
-
* Propose an open
|
|
82
|
+
* Propose an open request. `audience` ('everyone' default | 'org') picks the
|
|
83
83
|
* broadcast sentinel written to `proposedTo`. Server defaults `engagementKind`
|
|
84
84
|
* to `service`.
|
|
85
85
|
*/
|
|
@@ -124,8 +124,12 @@ export declare function respondToAgreement(agreementId: string, action: 'approve
|
|
|
124
124
|
export interface PendingApprovalFacts {
|
|
125
125
|
/** Party ids with a pending entry on the approvals ledger. */
|
|
126
126
|
pendingPartyIds: readonly string[];
|
|
127
|
-
/**
|
|
128
|
-
|
|
127
|
+
/**
|
|
128
|
+
* The direct responder slot, when the proposal names one. A side names up to
|
|
129
|
+
* two ids — the accountable principal and the delegate acting for it — and
|
|
130
|
+
* either may be the one holding the decision.
|
|
131
|
+
*/
|
|
132
|
+
proposedToIds?: readonly string[];
|
|
129
133
|
}
|
|
130
134
|
/**
|
|
131
135
|
* Which of `candidateIds` holds the pending approval slot, first match wins —
|
|
@@ -146,7 +150,7 @@ export declare function resolveMyPendingApprovalPartyId(agreement: Agreement, op
|
|
|
146
150
|
*
|
|
147
151
|
* `partyId` MUST match the authenticated actor (`actor.id === partyId`): owner
|
|
148
152
|
* principal when approving as human, delegate agent id when impersonating.
|
|
149
|
-
* Canonical for hire, service, link, and
|
|
153
|
+
* Canonical for hire, service, link, and request proposals.
|
|
150
154
|
*/
|
|
151
155
|
export declare function approveAgreementAsParty(agreementId: string, partyId: string, status: 'approved' | 'rejected', creds: Creds): Promise<Agreement>;
|
|
152
156
|
export interface CounterAgreementData {
|
|
@@ -181,24 +185,42 @@ export interface GetMyAgreementsFilters {
|
|
|
181
185
|
export declare function getMyAgreements(filters: GetMyAgreementsFilters | undefined, creds: Creds): Promise<Agreement[]>;
|
|
182
186
|
/** One agreement, shaped: a link comes back as its summary. */
|
|
183
187
|
export declare function getAgreement(agreementId: string, creds: Creds): Promise<Agreement | null>;
|
|
184
|
-
export interface
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
188
|
+
export interface CreateLinkBody {
|
|
189
|
+
/**
|
|
190
|
+
* Who to connect with: an email address, or the id of an agent that answers
|
|
191
|
+
* to them. Either way the link is with the PERSON — an id is only a way to
|
|
192
|
+
* find its owner, and an agent that answers to an org is refused. Omit for an
|
|
193
|
+
* open invite you share yourself.
|
|
194
|
+
*/
|
|
195
|
+
to?: string;
|
|
196
|
+
/** Message shown to whoever is asked to approve or claim it. */
|
|
192
197
|
description?: string;
|
|
193
|
-
|
|
194
|
-
/** Link invites only: how many people may claim this one link. Default 1. */
|
|
198
|
+
/** Open invites only: how many people may claim this one link. Default 1. */
|
|
195
199
|
maxClaims?: number;
|
|
196
|
-
metadata?: Record<string, unknown>;
|
|
197
200
|
}
|
|
198
|
-
|
|
201
|
+
/**
|
|
202
|
+
* The route's one answer, whatever was in `to`.
|
|
203
|
+
*
|
|
204
|
+
* Deliberately carries no agreement. Returning the row for an id target and a
|
|
205
|
+
* bare "sent" for an address made one of the two an existence oracle over the
|
|
206
|
+
* user table, so both say the same thing now: it went. `shareUrl` is null
|
|
207
|
+
* exactly when `to` was named, which the caller already knows.
|
|
208
|
+
*/
|
|
209
|
+
export interface CreateLinkResult {
|
|
199
210
|
ok: boolean;
|
|
200
|
-
|
|
201
|
-
|
|
211
|
+
status: 'sent';
|
|
212
|
+
shareUrl: string | null;
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Connect with someone — by email, by agent id, or with a shareable invite.
|
|
216
|
+
*
|
|
217
|
+
* This posts to `POST /agreements/links`. It used to post to `POST /agreements`,
|
|
218
|
+
* which also created work agreements inline and skipped the gates the proposal
|
|
219
|
+
* and marketplace paths run. Work agreements go through `proposeAgreement`
|
|
220
|
+
* (`POST /agreements/proposals`) and the marketplace claim routes; this rail
|
|
221
|
+
* carries links and nothing else.
|
|
222
|
+
*/
|
|
223
|
+
export declare function createLink(body: CreateLinkBody, creds: Creds): Promise<CreateLinkResult>;
|
|
202
224
|
export declare function revokeAgreement(agreementId: string, creds: Creds): Promise<{
|
|
203
225
|
ok: boolean;
|
|
204
226
|
agreement: Agreement;
|
|
@@ -210,17 +232,34 @@ export declare function fulfillAgreement(agreementId: string, creds: Creds): Pro
|
|
|
210
232
|
agreement: Agreement;
|
|
211
233
|
}>;
|
|
212
234
|
/** What an open-broadcast claim resolved to. ⚠️ Mirrors the server; it is authoritative. */
|
|
213
|
-
export type ClaimedKind = 'link' | 'offer' | '
|
|
235
|
+
export type ClaimedKind = 'link' | 'offer' | 'request' | 'hand-off';
|
|
214
236
|
/**
|
|
215
237
|
* Claim an open agreement. Three shapes are claimable:
|
|
216
238
|
* - open link invite (`engagementKind: link`, proposedTo everyone)
|
|
217
|
-
* - open broadcast
|
|
218
|
-
* - org-scoped broadcast
|
|
239
|
+
* - open broadcast request (proposedTo 'everyone', status open)
|
|
240
|
+
* - org-scoped broadcast request (proposedTo 'org') — the server rejects the
|
|
219
241
|
* claim with 403 if the caller is not a member of the agreement's orgId
|
|
220
242
|
*
|
|
221
243
|
* POST /agreements/:id/claim — canonical; no partyId in body.
|
|
222
244
|
*/
|
|
223
|
-
|
|
245
|
+
/**
|
|
246
|
+
* What a claimer can say about WHY it is claiming.
|
|
247
|
+
*
|
|
248
|
+
* A claim binds a party to terms somebody else posted, so the only thing there
|
|
249
|
+
* is to declare is the authority behind it: the job the claiming agent is
|
|
250
|
+
* already doing. Nothing here changes the row — the lineage of a posted
|
|
251
|
+
* agreement belongs to whoever posted it.
|
|
252
|
+
*/
|
|
253
|
+
export interface ClaimOptions {
|
|
254
|
+
/**
|
|
255
|
+
* The active agreement whose work this claim is part of. An agent acting
|
|
256
|
+
* inside a job its human approved does not need a second consent for the
|
|
257
|
+
* engagements that job requires, but it has to NAME the job — the server
|
|
258
|
+
* verifies the claim against the acting agent and never guesses one.
|
|
259
|
+
*/
|
|
260
|
+
mandateAgreementId?: string;
|
|
261
|
+
}
|
|
262
|
+
export declare function claimAgreement(agreementId: string, creds: Creds, opts?: ClaimOptions): Promise<{
|
|
224
263
|
ok: boolean;
|
|
225
264
|
agreement: Agreement;
|
|
226
265
|
kind?: ClaimedKind;
|
|
@@ -262,10 +301,7 @@ export declare class AgreementClient {
|
|
|
262
301
|
list(filters?: ListAgreementsFilters): Promise<Agreement[]>;
|
|
263
302
|
listMine(filters?: GetMyAgreementsFilters): Promise<Agreement[]>;
|
|
264
303
|
get(id: string): Promise<Agreement | null>;
|
|
265
|
-
|
|
266
|
-
ok: boolean;
|
|
267
|
-
agreement: Agreement;
|
|
268
|
-
}>;
|
|
304
|
+
createLink(data: CreateLinkBody): Promise<CreateLinkResult>;
|
|
269
305
|
revoke(id: string): Promise<{
|
|
270
306
|
ok: boolean;
|
|
271
307
|
agreement: Agreement;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { runtimeLog } from '../shared/runtimeLog.js';
|
|
2
2
|
import { getBackendUrl } from '../utils/urlUtils.js';
|
|
3
|
-
import { OPEN_AGREEMENT_TARGET, ORG_AGREEMENT_TARGET, isBroadcastTarget } from '../types.js';
|
|
3
|
+
import { OPEN_AGREEMENT_TARGET, ORG_AGREEMENT_TARGET, isBroadcastTarget, partySideIds } from '../types.js';
|
|
4
4
|
import { throwApiError } from '../shared/apiError.js';
|
|
5
5
|
// Lazy: read at call time so a `configureApiClient` call that lands after this
|
|
6
6
|
// module is imported still takes effect. Baking it at module-load time would
|
|
@@ -37,6 +37,10 @@ function assertCreds(creds, op) {
|
|
|
37
37
|
* unchanged.
|
|
38
38
|
*/
|
|
39
39
|
export function linkSummary(a) {
|
|
40
|
+
const side = (s) => ({
|
|
41
|
+
principal: s?.principal ?? null,
|
|
42
|
+
actor: s?.actor ?? null,
|
|
43
|
+
});
|
|
40
44
|
return {
|
|
41
45
|
agreementId: a.agreementId,
|
|
42
46
|
// Kept deliberately: callers branch on this, and a summary that hides what
|
|
@@ -44,11 +48,11 @@ export function linkSummary(a) {
|
|
|
44
48
|
engagementKind: a.engagementKind,
|
|
45
49
|
status: a.status,
|
|
46
50
|
proposalStatus: a.proposalStatus,
|
|
51
|
+
// No `payer`: a link carries no money, so the paying side is never occupied.
|
|
47
52
|
parties: {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
proposedTo: a.parties?.proposedTo ?? null,
|
|
53
|
+
creator: side(a.parties?.creator),
|
|
54
|
+
provider: side(a.parties?.provider),
|
|
55
|
+
proposedTo: side(a.parties?.proposedTo),
|
|
52
56
|
},
|
|
53
57
|
...(a.description ? { description: a.description } : {}),
|
|
54
58
|
// Seat bookkeeping on an open invite — link state, not commerce.
|
|
@@ -103,7 +107,7 @@ export async function proposeDirectTo(input, creds) {
|
|
|
103
107
|
return proposeAgreement(input, creds);
|
|
104
108
|
}
|
|
105
109
|
/**
|
|
106
|
-
* Propose an open
|
|
110
|
+
* Propose an open request. `audience` ('everyone' default | 'org') picks the
|
|
107
111
|
* broadcast sentinel written to `proposedTo`. Server defaults `engagementKind`
|
|
108
112
|
* to `service`.
|
|
109
113
|
*/
|
|
@@ -169,10 +173,10 @@ export async function respondToAgreement(agreementId, action, creds, opts = {})
|
|
|
169
173
|
ownerUserId: opts.ownerUserId,
|
|
170
174
|
agentId: creds.agentId,
|
|
171
175
|
});
|
|
172
|
-
const proposedTo = agreement.parties?.proposedTo;
|
|
173
|
-
if (isBroadcastTarget(proposedTo) || isBroadcastTarget(agreement.parties?.payer)) {
|
|
176
|
+
const proposedTo = agreement.parties?.proposedTo?.principal;
|
|
177
|
+
if (isBroadcastTarget(proposedTo) || isBroadcastTarget(agreement.parties?.payer?.principal)) {
|
|
174
178
|
// respond is approve/reject on DIRECT proposals only. An open
|
|
175
|
-
// broadcast (
|
|
179
|
+
// broadcast (request, standing offer, link invite) has no per-recipient
|
|
176
180
|
// approval slot — a BYSTANDER can neither approve nor reject it
|
|
177
181
|
//; their move is to claim it, which has its own verb.
|
|
178
182
|
//
|
|
@@ -206,11 +210,12 @@ export function resolvePendingApprovalPartyId(facts, candidateIds) {
|
|
|
206
210
|
}
|
|
207
211
|
// A row with no pending ledger entries is a legacy one (approvals[] predates
|
|
208
212
|
//), and there the named responder slot IS the decision.
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
actorIds
|
|
212
|
-
|
|
213
|
-
|
|
213
|
+
if (facts.pendingPartyIds.length === 0) {
|
|
214
|
+
const responderIds = facts.proposedToIds ?? [];
|
|
215
|
+
for (const id of actorIds) {
|
|
216
|
+
if (responderIds.includes(id))
|
|
217
|
+
return id;
|
|
218
|
+
}
|
|
214
219
|
}
|
|
215
220
|
return null;
|
|
216
221
|
}
|
|
@@ -223,7 +228,7 @@ export function resolveMyPendingApprovalPartyId(agreement, opts) {
|
|
|
223
228
|
pendingPartyIds: (agreement.approvals ?? [])
|
|
224
229
|
.filter((a) => a.status === 'pending' || a.status === 'PENDING')
|
|
225
230
|
.map((a) => a.partyId),
|
|
226
|
-
|
|
231
|
+
proposedToIds: partySideIds(agreement.parties?.proposedTo),
|
|
227
232
|
}, [opts.agentId, opts.ownerUserId]);
|
|
228
233
|
}
|
|
229
234
|
/**
|
|
@@ -232,7 +237,7 @@ export function resolveMyPendingApprovalPartyId(agreement, opts) {
|
|
|
232
237
|
*
|
|
233
238
|
* `partyId` MUST match the authenticated actor (`actor.id === partyId`): owner
|
|
234
239
|
* principal when approving as human, delegate agent id when impersonating.
|
|
235
|
-
* Canonical for hire, service, link, and
|
|
240
|
+
* Canonical for hire, service, link, and request proposals.
|
|
236
241
|
*/
|
|
237
242
|
export async function approveAgreementAsParty(agreementId, partyId, status, creds) {
|
|
238
243
|
if (!agreementId)
|
|
@@ -406,26 +411,38 @@ export async function getAgreement(agreementId, creds) {
|
|
|
406
411
|
const agreement = await getAgreementDocument(agreementId, creds);
|
|
407
412
|
return agreement ? shapeAgreement(agreement) : null;
|
|
408
413
|
}
|
|
409
|
-
|
|
414
|
+
/**
|
|
415
|
+
* Connect with someone — by email, by agent id, or with a shareable invite.
|
|
416
|
+
*
|
|
417
|
+
* This posts to `POST /agreements/links`. It used to post to `POST /agreements`,
|
|
418
|
+
* which also created work agreements inline and skipped the gates the proposal
|
|
419
|
+
* and marketplace paths run. Work agreements go through `proposeAgreement`
|
|
420
|
+
* (`POST /agreements/proposals`) and the marketplace claim routes; this rail
|
|
421
|
+
* carries links and nothing else.
|
|
422
|
+
*/
|
|
423
|
+
export async function createLink(body, creds) {
|
|
410
424
|
if (!body)
|
|
411
|
-
throw new Error('Body is required for
|
|
412
|
-
assertCreds(creds, '
|
|
413
|
-
|
|
414
|
-
const res = await fetch(getAgreementBaseUrl(), {
|
|
425
|
+
throw new Error('Body is required for link creation');
|
|
426
|
+
assertCreds(creds, 'link creation');
|
|
427
|
+
const res = await fetch(`${getAgreementBaseUrl()}/links`, {
|
|
415
428
|
method: 'POST',
|
|
416
429
|
headers: buildHeaders(creds),
|
|
417
430
|
body: JSON.stringify(body),
|
|
418
431
|
});
|
|
419
432
|
if (!res.ok) {
|
|
420
433
|
const responseBody = await res.text().catch(() => '');
|
|
421
|
-
throwApiError(res, responseBody, `
|
|
434
|
+
throwApiError(res, responseBody, `Link creation failed: ${res.status} ${res.statusText}`);
|
|
422
435
|
}
|
|
423
436
|
const data = await res.json().catch(() => null);
|
|
424
|
-
if (
|
|
425
|
-
throw new Error('Invalid response: expected { ok,
|
|
437
|
+
if (data?.['status'] !== 'sent') {
|
|
438
|
+
throw new Error('Invalid response: expected { ok, status: "sent", shareUrl } from POST /agreements/links');
|
|
426
439
|
}
|
|
427
|
-
const
|
|
428
|
-
return {
|
|
440
|
+
const shareUrl = data['shareUrl'];
|
|
441
|
+
return {
|
|
442
|
+
ok: data['ok'] === true,
|
|
443
|
+
status: 'sent',
|
|
444
|
+
shareUrl: typeof shareUrl === 'string' ? shareUrl : null,
|
|
445
|
+
};
|
|
429
446
|
}
|
|
430
447
|
export async function revokeAgreement(agreementId, creds) {
|
|
431
448
|
if (!agreementId)
|
|
@@ -469,20 +486,15 @@ export async function fulfillAgreement(agreementId, creds) {
|
|
|
469
486
|
const envelope = data;
|
|
470
487
|
return { ...envelope, agreement: shapeAgreement(envelope.agreement) };
|
|
471
488
|
}
|
|
472
|
-
|
|
473
|
-
* Claim an open agreement. Three shapes are claimable:
|
|
474
|
-
* - open link invite (`engagementKind: link`, proposedTo everyone)
|
|
475
|
-
* - open broadcast quest (proposedTo 'everyone', status open)
|
|
476
|
-
* - org-scoped broadcast quest (proposedTo 'org') — the server rejects the
|
|
477
|
-
* claim with 403 if the caller is not a member of the agreement's orgId
|
|
478
|
-
*
|
|
479
|
-
* POST /agreements/:id/claim — canonical; no partyId in body.
|
|
480
|
-
*/
|
|
481
|
-
export async function claimAgreement(agreementId, creds) {
|
|
489
|
+
export async function claimAgreement(agreementId, creds, opts = {}) {
|
|
482
490
|
if (!agreementId)
|
|
483
491
|
throw new Error('agreementId is required to claim an open agreement');
|
|
484
492
|
assertCreds(creds, 'open agreement claim');
|
|
485
|
-
const res = await fetch(`${getAgreementBaseUrl()}/${encodeURIComponent(agreementId)}/claim`, {
|
|
493
|
+
const res = await fetch(`${getAgreementBaseUrl()}/${encodeURIComponent(agreementId)}/claim`, {
|
|
494
|
+
method: 'POST',
|
|
495
|
+
headers: buildHeaders(creds),
|
|
496
|
+
body: JSON.stringify(opts.mandateAgreementId ? { mandateAgreementId: opts.mandateAgreementId } : {}),
|
|
497
|
+
});
|
|
486
498
|
if (!res.ok) {
|
|
487
499
|
const body = await res.text().catch(() => '');
|
|
488
500
|
throwApiError(res, body, `Open agreement claim failed: ${res.status} ${res.statusText}`);
|
|
@@ -493,7 +505,7 @@ export async function claimAgreement(agreementId, creds) {
|
|
|
493
505
|
}
|
|
494
506
|
// the route reports which kind of broadcast this turned out to be.
|
|
495
507
|
// A caller cannot work it out from the row — post-claim no sentinel is left,
|
|
496
|
-
// and a
|
|
508
|
+
// and a request (you do the work) reads the same shape as a standing offer (you
|
|
497
509
|
// pay for it) unless you know which slot you landed in.
|
|
498
510
|
const envelope = data;
|
|
499
511
|
return { ...envelope, agreement: shapeAgreement(envelope.agreement) };
|
|
@@ -683,7 +695,7 @@ export class AgreementClient {
|
|
|
683
695
|
list(filters) { return listAgreements(filters, this.creds); }
|
|
684
696
|
listMine(filters) { return getMyAgreements(filters, this.creds); }
|
|
685
697
|
get(id) { return getAgreement(id, this.creds); }
|
|
686
|
-
|
|
698
|
+
createLink(data) { return createLink(data, this.creds); }
|
|
687
699
|
revoke(id) { return revokeAgreement(id, this.creds); }
|
|
688
700
|
fulfill(id) { return fulfillAgreement(id, this.creds); }
|
|
689
701
|
claimAgreement(id) { return claimAgreement(id, this.creds); }
|
|
@@ -21,6 +21,7 @@ export interface SendChatMessageInput {
|
|
|
21
21
|
* Recipient id. Optional: when omitted, the backend infers the
|
|
22
22
|
* receiver if the chat has exactly one other member (one agent, or one
|
|
23
23
|
* other human). Provide it explicitly in chats with multiple members.
|
|
24
|
+
* Sent on the wire as `{ id }`; the SDK still takes a string.
|
|
24
25
|
*/
|
|
25
26
|
receiverId?: string;
|
|
26
27
|
text: string;
|
package/dist/http/ChatClient.js
CHANGED
|
@@ -93,7 +93,10 @@ export async function sendChatMessage(input, creds) {
|
|
|
93
93
|
text: input.text,
|
|
94
94
|
entryType,
|
|
95
95
|
contentType,
|
|
96
|
-
|
|
96
|
+
// Canonical wire shape is `{ id, type? }`; bare strings are still
|
|
97
|
+
// accepted by the backend after edge normalize, but first-party
|
|
98
|
+
// clients send the object.
|
|
99
|
+
receiver: input.receiverId ? { id: input.receiverId } : undefined,
|
|
97
100
|
underAgreementId: input.underAgreementId,
|
|
98
101
|
}),
|
|
99
102
|
});
|
|
@@ -93,10 +93,21 @@ export class ConnectionsClient {
|
|
|
93
93
|
async listForHolder() {
|
|
94
94
|
const grantsClient = new GrantsClient(this.operatorKey, this.agentId, this.baseUrl);
|
|
95
95
|
// All pages of the agent's live connection grants (not just the first page).
|
|
96
|
-
const items = await grantsClient.
|
|
96
|
+
const { items, unreadableRails } = await grantsClient.listAllGrantsWithRails({
|
|
97
97
|
scopeKind: 'connection',
|
|
98
98
|
health: 'active',
|
|
99
99
|
});
|
|
100
|
+
// An unreadable rail is not an empty one. Returning [] here made
|
|
101
|
+
// every caller say "no connection grant" when the truth was "this key may not
|
|
102
|
+
// look" — the needs gate refused work the agent could do, and the MCP tool
|
|
103
|
+
// told the owner to issue a grant that already existed. Throwing puts the
|
|
104
|
+
// missing scope in the message, where the person who can fix it will read it.
|
|
105
|
+
const railBlocked = unreadableRails.find((r) => r.rail === 'connection');
|
|
106
|
+
if (railBlocked && items.length === 0) {
|
|
107
|
+
throw new Error(`Cannot read this agent's connection grants: the operator key is missing the ` +
|
|
108
|
+
`"${railBlocked.requiredScope}" scope, so the grant list came back empty whether or not ` +
|
|
109
|
+
'a grant exists. Re-mint the key with that scope (the "launcher-mcp-broker" preset includes it).');
|
|
110
|
+
}
|
|
100
111
|
const byConnection = new Map();
|
|
101
112
|
for (const g of items) {
|
|
102
113
|
const connectionId = g.scope.id;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { GrantView } from './grants.js';
|
|
1
|
+
import type { GrantAccessKind, GrantHolderKind, GrantView } from './grants.js';
|
|
2
2
|
/**
|
|
3
3
|
* added `artifact` — the narrowest context scope: one specific artifact,
|
|
4
4
|
* shared without sharing any chat or agreement it sits in. Still the context
|
|
@@ -21,12 +21,26 @@ export interface ContextGrantScope {
|
|
|
21
21
|
export type ContextGrantRecord = GrantView;
|
|
22
22
|
export interface IssueContextGrantInput {
|
|
23
23
|
holderId: string;
|
|
24
|
+
/**
|
|
25
|
+
* What kind of principal the holder is: a person, an organization, or an
|
|
26
|
+
* agent. Always stated, because an id says nothing about what it names — and
|
|
27
|
+
* an organization holder is how one grant covers a whole team.
|
|
28
|
+
*/
|
|
29
|
+
holderKind: GrantHolderKind;
|
|
30
|
+
/**
|
|
31
|
+
* What the holder may do: `read` to see the scope, `write` to take part in
|
|
32
|
+
* it, `admit` to bring others in as well. Defaults to `write`.
|
|
33
|
+
*/
|
|
34
|
+
kind?: GrantAccessKind;
|
|
24
35
|
scope: ContextGrantScope;
|
|
25
36
|
temporal?: ContextTemporal;
|
|
26
37
|
expiresAt?: string | null;
|
|
27
38
|
}
|
|
28
39
|
export interface DelegateContextGrantInput {
|
|
29
40
|
holderId: string;
|
|
41
|
+
holderKind: GrantHolderKind;
|
|
42
|
+
/** Never stronger than the grant it comes from; defaults to the same. */
|
|
43
|
+
kind?: GrantAccessKind;
|
|
30
44
|
scope: ContextGrantScope;
|
|
31
45
|
temporal: ContextTemporal;
|
|
32
46
|
expiresAt?: string | null;
|
|
@@ -43,17 +43,26 @@ export interface ContextReadEnvelope<T = unknown> {
|
|
|
43
43
|
latestSequence?: string | null;
|
|
44
44
|
}
|
|
45
45
|
/** Aggregated chat snapshot from `GET /context/snapshot` (follow-up). */
|
|
46
|
+
/**
|
|
47
|
+
* One participant on the snapshot roster. `kind` is the declared holder kind the
|
|
48
|
+
* server stamped on the row, so a reader takes what a participant IS from the
|
|
49
|
+
* row rather than from which array it arrived in. Optional so a backend that
|
|
50
|
+
* predates it still parses.
|
|
51
|
+
*/
|
|
52
|
+
export interface ContextSnapshotParticipant {
|
|
53
|
+
role: string;
|
|
54
|
+
kind?: string;
|
|
55
|
+
isYou?: boolean;
|
|
56
|
+
[key: string]: unknown;
|
|
57
|
+
}
|
|
46
58
|
export interface ContextSnapshotResult {
|
|
47
59
|
history: unknown[];
|
|
48
60
|
agreements: unknown[];
|
|
49
|
-
agents: Array<{
|
|
61
|
+
agents: Array<ContextSnapshotParticipant & {
|
|
50
62
|
agentId: string;
|
|
51
|
-
role: string;
|
|
52
|
-
isYou: boolean;
|
|
53
63
|
}>;
|
|
54
|
-
users: Array<{
|
|
64
|
+
users: Array<ContextSnapshotParticipant & {
|
|
55
65
|
userId: string;
|
|
56
|
-
role: string;
|
|
57
66
|
}>;
|
|
58
67
|
latestSequence?: string | null;
|
|
59
68
|
chatMissing?: boolean;
|
|
@@ -66,4 +66,18 @@ export declare class GrantsClient {
|
|
|
66
66
|
* truncated. `maxPages` bounds the loop as a runaway guard.
|
|
67
67
|
*/
|
|
68
68
|
listAllGrants(query?: Omit<ListGrantsQuery, 'cursor'>, maxPages?: number): Promise<GrantView[]>;
|
|
69
|
+
/**
|
|
70
|
+
* The same sweep, keeping the part `listAllGrants` throws away: which rails the
|
|
71
|
+
* caller was not allowed to read.
|
|
72
|
+
*
|
|
73
|
+
* The distinction is not cosmetic. An empty page from an unreadable rail looks
|
|
74
|
+
* exactly like "you hold no grants", and a caller that cannot tell them apart
|
|
75
|
+
* reports the wrong one: a brokered connector agent with a live grant refused
|
|
76
|
+
* its task and told the buyer to ask for the grant they had just issued, because
|
|
77
|
+
* its key lacked `connections:read` and this method answered "none".
|
|
78
|
+
*/
|
|
79
|
+
listAllGrantsWithRails(query?: Omit<ListGrantsQuery, 'cursor'>, maxPages?: number): Promise<{
|
|
80
|
+
items: GrantView[];
|
|
81
|
+
unreadableRails: UnreadableRail[];
|
|
82
|
+
}>;
|
|
69
83
|
}
|
|
@@ -65,15 +65,31 @@ export class GrantsClient {
|
|
|
65
65
|
* truncated. `maxPages` bounds the loop as a runaway guard.
|
|
66
66
|
*/
|
|
67
67
|
async listAllGrants(query = {}, maxPages = 50) {
|
|
68
|
+
return (await this.listAllGrantsWithRails(query, maxPages)).items;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* The same sweep, keeping the part `listAllGrants` throws away: which rails the
|
|
72
|
+
* caller was not allowed to read.
|
|
73
|
+
*
|
|
74
|
+
* The distinction is not cosmetic. An empty page from an unreadable rail looks
|
|
75
|
+
* exactly like "you hold no grants", and a caller that cannot tell them apart
|
|
76
|
+
* reports the wrong one: a brokered connector agent with a live grant refused
|
|
77
|
+
* its task and told the buyer to ask for the grant they had just issued, because
|
|
78
|
+
* its key lacked `connections:read` and this method answered "none".
|
|
79
|
+
*/
|
|
80
|
+
async listAllGrantsWithRails(query = {}, maxPages = 50) {
|
|
68
81
|
const all = [];
|
|
82
|
+
const rails = new Map();
|
|
69
83
|
let cursor;
|
|
70
84
|
for (let page = 0; page < maxPages; page++) {
|
|
71
85
|
const res = await this.listGrants({ ...query, cursor });
|
|
72
86
|
all.push(...res.items);
|
|
87
|
+
for (const rail of res.unreadableRails ?? [])
|
|
88
|
+
rails.set(rail.rail, rail);
|
|
73
89
|
if (!res.nextCursor)
|
|
74
|
-
|
|
90
|
+
break;
|
|
75
91
|
cursor = res.nextCursor;
|
|
76
92
|
}
|
|
77
|
-
return all;
|
|
93
|
+
return { items: all, unreadableRails: [...rails.values()] };
|
|
78
94
|
}
|
|
79
95
|
}
|
package/dist/http/InboxClient.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { getBackendUrl } from '../utils/urlUtils.js';
|
|
2
2
|
import { pollSurfaceError } from '../shared/rateLimit.js';
|
|
3
|
+
import { INBOX_HOST_CLAIMANT_HEADER, instanceIdentity, } from '../instanceIdentity.js';
|
|
3
4
|
/**
|
|
4
5
|
* The doorbell, not the door: references addressed to this agent
|
|
5
6
|
* since its last ack — never content. Flow: inbox → read → act → ack
|
|
@@ -24,6 +25,9 @@ export class InboxClient {
|
|
|
24
25
|
const headers = {
|
|
25
26
|
Authorization: `Bearer ${this.operatorKey}`,
|
|
26
27
|
'Content-Type': 'application/json',
|
|
28
|
+
// This process, not the API. Two fleets of the same agent share
|
|
29
|
+
// one backend task; without this they both wake.
|
|
30
|
+
[INBOX_HOST_CLAIMANT_HEADER]: instanceIdentity(),
|
|
27
31
|
};
|
|
28
32
|
if (this.agentId)
|
|
29
33
|
headers['X-Agent-Id'] = this.agentId;
|
|
@@ -23,8 +23,7 @@ export interface PullOffersOptions {
|
|
|
23
23
|
since?: string;
|
|
24
24
|
}
|
|
25
25
|
export declare function pullOffers(options: PullOffersOptions | undefined, creds: Creds): Promise<Agreement[]>;
|
|
26
|
-
export
|
|
27
|
-
export interface PublishQuestPayload {
|
|
26
|
+
export interface PublishRequestPayload {
|
|
28
27
|
/** See PublishOfferPayload.billing. */
|
|
29
28
|
billing?: 'total' | 'per_task';
|
|
30
29
|
description: string;
|
|
@@ -38,12 +37,12 @@ export interface PublishQuestPayload {
|
|
|
38
37
|
/** Broadcast audience: 'everyone' (default, fully public) or 'org' (members of your active org only). */
|
|
39
38
|
audience?: BroadcastAudience;
|
|
40
39
|
}
|
|
41
|
-
export declare function
|
|
42
|
-
export interface
|
|
40
|
+
export declare function publishRequest(payload: PublishRequestPayload, creds: Creds): Promise<Agreement>;
|
|
41
|
+
export interface PullRequestsOptions {
|
|
43
42
|
limit?: number;
|
|
44
43
|
since?: string;
|
|
45
44
|
}
|
|
46
|
-
export declare function
|
|
45
|
+
export declare function pullRequests(options: PullRequestsOptions | undefined, creds: Creds): Promise<Agreement[]>;
|
|
47
46
|
export declare class MarketplaceClient {
|
|
48
47
|
private creds;
|
|
49
48
|
/**
|
|
@@ -53,7 +52,6 @@ export declare class MarketplaceClient {
|
|
|
53
52
|
constructor(operatorKey: string, agentId?: string);
|
|
54
53
|
publishOffer(payload: PublishOfferPayload): Promise<Agreement>;
|
|
55
54
|
pullOffers(options?: PullOffersOptions): Promise<Agreement[]>;
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
pullQuests(options?: PullQuestsOptions): Promise<Agreement[]>;
|
|
55
|
+
publishRequest(payload: PublishRequestPayload): Promise<Agreement>;
|
|
56
|
+
pullRequests(options?: PullRequestsOptions): Promise<Agreement[]>;
|
|
59
57
|
}
|
|
@@ -52,55 +52,37 @@ export async function pullOffers(options, creds) {
|
|
|
52
52
|
const data = await res.json().catch(() => null);
|
|
53
53
|
return data?.['offers'] ?? [];
|
|
54
54
|
}
|
|
55
|
-
export async function
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
assertCreds(creds, 'marketplace offer claim');
|
|
59
|
-
const res = await fetch(`${getMarketplaceBaseUrl()}/offers/claim`, {
|
|
60
|
-
method: 'POST',
|
|
61
|
-
headers: buildHeaders(creds),
|
|
62
|
-
body: JSON.stringify({ agreementId }),
|
|
63
|
-
});
|
|
64
|
-
if (!res.ok) {
|
|
65
|
-
const body = await res.text().catch(() => '');
|
|
66
|
-
throwApiError(res, body, `Marketplace offer claim failed: ${res.status}`);
|
|
67
|
-
}
|
|
68
|
-
const data = await res.json().catch(() => null);
|
|
69
|
-
if (!data?.['ok'])
|
|
70
|
-
throw new Error(data?.['error'] || 'Claim failed');
|
|
71
|
-
return shapeAgreement(data['offer']);
|
|
72
|
-
}
|
|
73
|
-
export async function publishQuest(payload, creds) {
|
|
74
|
-
assertCreds(creds, 'quest publish');
|
|
75
|
-
const res = await fetch(`${getMarketplaceBaseUrl()}/quests/publish`, {
|
|
55
|
+
export async function publishRequest(payload, creds) {
|
|
56
|
+
assertCreds(creds, 'request publish');
|
|
57
|
+
const res = await fetch(`${getMarketplaceBaseUrl()}/requests/publish`, {
|
|
76
58
|
method: 'POST',
|
|
77
59
|
headers: buildHeaders(creds),
|
|
78
60
|
body: JSON.stringify(payload || {}),
|
|
79
61
|
});
|
|
80
62
|
if (!res.ok) {
|
|
81
63
|
const body = await res.text().catch(() => '');
|
|
82
|
-
throwApiError(res, body, `
|
|
64
|
+
throwApiError(res, body, `Request publish failed: ${res.status}`);
|
|
83
65
|
}
|
|
84
66
|
const data = await res.json().catch(() => null);
|
|
85
67
|
if (!data?.['agreement'])
|
|
86
|
-
throw new Error('
|
|
68
|
+
throw new Error('Request publish returned no agreement');
|
|
87
69
|
return shapeAgreement(data['agreement']);
|
|
88
70
|
}
|
|
89
|
-
export async function
|
|
90
|
-
assertCreds(creds, '
|
|
71
|
+
export async function pullRequests(options, creds) {
|
|
72
|
+
assertCreds(creds, 'request pull');
|
|
91
73
|
const params = new URLSearchParams();
|
|
92
74
|
if (options?.limit != null)
|
|
93
75
|
params.set('limit', String(options.limit));
|
|
94
76
|
if (options?.since)
|
|
95
77
|
params.set('since', options.since);
|
|
96
78
|
const qs = params.toString();
|
|
97
|
-
const res = await fetch(`${getMarketplaceBaseUrl()}/
|
|
79
|
+
const res = await fetch(`${getMarketplaceBaseUrl()}/requests${qs ? `?${qs}` : ''}`, {
|
|
98
80
|
method: 'GET',
|
|
99
81
|
headers: buildHeaders(creds),
|
|
100
82
|
});
|
|
101
83
|
if (!res.ok) {
|
|
102
84
|
const body = await res.text().catch(() => '');
|
|
103
|
-
throwApiError(res, body, `
|
|
85
|
+
throwApiError(res, body, `Request pull failed: ${res.status}`);
|
|
104
86
|
}
|
|
105
87
|
const data = await res.json().catch(() => null);
|
|
106
88
|
return data?.['agreements'] ?? [];
|
|
@@ -120,7 +102,6 @@ export class MarketplaceClient {
|
|
|
120
102
|
}
|
|
121
103
|
publishOffer(payload) { return publishOffer(payload, this.creds); }
|
|
122
104
|
pullOffers(options) { return pullOffers(options, this.creds); }
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
pullQuests(options) { return pullQuests(options, this.creds); }
|
|
105
|
+
publishRequest(payload) { return publishRequest(payload, this.creds); }
|
|
106
|
+
pullRequests(options) { return pullRequests(options, this.creds); }
|
|
126
107
|
}
|
|
@@ -86,6 +86,11 @@ export interface ListTasksOptions {
|
|
|
86
86
|
limit?: number;
|
|
87
87
|
/** Filter to tasks assigned to this id. */
|
|
88
88
|
assignedTo?: string;
|
|
89
|
+
/**
|
|
90
|
+
* Filter to tasks CREATED by this id — what the caller handed out, as
|
|
91
|
+
* opposed to what was handed to it.
|
|
92
|
+
*/
|
|
93
|
+
createdBy?: string;
|
|
89
94
|
}
|
|
90
95
|
export interface ListTasksResult {
|
|
91
96
|
tasks: Task[];
|