@ziggs-ai/api-client 0.9.0 → 0.9.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/ConnectionManager.d.ts +21 -57
- package/dist/ConnectionManager.js +34 -163
- package/dist/capabilities/agreements.js +2 -2
- package/dist/capabilities/connections.js +1 -1
- package/dist/capabilities/index.d.ts +1 -1
- package/dist/capabilities/index.js +1 -1
- package/dist/capabilities/links.d.ts +0 -8
- package/dist/capabilities/links.js +3 -25
- package/dist/capabilities/types.js +3 -0
- package/dist/http/AgreementClient.d.ts +32 -0
- package/dist/http/AgreementClient.js +93 -19
- package/dist/http/ChatClient.js +2 -2
- package/dist/http/ConnectionsClient.js +4 -4
- package/dist/http/ContextDiscoveryClient.js +2 -1
- package/dist/http/ContextReadClient.js +5 -16
- package/dist/http/GrantsClient.js +2 -1
- package/dist/http/InboxClient.d.ts +0 -47
- package/dist/http/InboxClient.js +0 -39
- package/dist/http/MarketplaceClient.js +5 -3
- package/dist/http/MessagesClient.js +3 -5
- package/dist/http/OrgsClient.js +3 -2
- package/dist/http/PaymentsClient.js +3 -5
- package/dist/http/agreementFlows.d.ts +13 -5
- package/dist/http/agreementFlows.js +18 -24
- package/dist/http/index.d.ts +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.js +3 -2
- package/dist/shared/apiError.d.ts +22 -1
- package/dist/shared/apiError.js +60 -3
- package/dist/shared/rateLimit.d.ts +12 -22
- package/dist/shared/rateLimit.js +18 -51
- package/dist/types.d.ts +15 -1
- package/dist/types.js +20 -1
- package/package.json +1 -1
|
@@ -25,6 +25,54 @@ function assertCreds(creds, op) {
|
|
|
25
25
|
if (!creds?.agentId)
|
|
26
26
|
throw new Error(`agentId is required for ${op}`);
|
|
27
27
|
}
|
|
28
|
+
/**
|
|
29
|
+
* A trust link's agreement, as every caller sees it.
|
|
30
|
+
*
|
|
31
|
+
* A link is reach, not commerce: it carries no money, no escrow, no execution
|
|
32
|
+
* state and no approvals ledger. Handing the raw document over anyway put Mongo
|
|
33
|
+
* bookkeeping in front of an LLM, which is what ZIG-957 forbade.
|
|
34
|
+
*
|
|
35
|
+
* Lives here rather than in `capabilities/links.ts` because this is where the
|
|
36
|
+
* rule is applied (ZIG-1111); that module re-exports it so the public name is
|
|
37
|
+
* unchanged.
|
|
38
|
+
*/
|
|
39
|
+
export function linkSummary(a) {
|
|
40
|
+
return {
|
|
41
|
+
agreementId: a.agreementId,
|
|
42
|
+
// Kept deliberately: callers branch on this, and a summary that hides what
|
|
43
|
+
// kind of thing it describes breaks the code it is meant to protect.
|
|
44
|
+
engagementKind: a.engagementKind,
|
|
45
|
+
status: a.status,
|
|
46
|
+
proposalStatus: a.proposalStatus,
|
|
47
|
+
parties: {
|
|
48
|
+
creatorAgent: a.parties?.creatorAgent ?? null,
|
|
49
|
+
providerAgent: a.parties?.providerAgent ?? null,
|
|
50
|
+
creator: a.parties?.creator ?? null,
|
|
51
|
+
proposedTo: a.parties?.proposedTo ?? null,
|
|
52
|
+
},
|
|
53
|
+
...(a.description ? { description: a.description } : {}),
|
|
54
|
+
// Seat bookkeeping on an open invite — link state, not commerce.
|
|
55
|
+
...(a.linkInvite ? { linkInvite: a.linkInvite } : {}),
|
|
56
|
+
createdAt: a.createdAt,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Every agreement document this client parses passes through here.
|
|
61
|
+
*
|
|
62
|
+
* The rule — a link is returned as its summary, anything else verbatim — used to
|
|
63
|
+
* be written out at six call sites across the SDK runner, the MCP tools and the
|
|
64
|
+
* capability layer, and three verbs never got it: `agreement_counter`,
|
|
65
|
+
* `agreement_fulfill` and `agreement_subcontract` returned the raw document
|
|
66
|
+
* (ZIG-1111). Applying it at the parse boundary means a new verb inherits the
|
|
67
|
+
* rule instead of remembering to opt in, and no surface can word its own verdict.
|
|
68
|
+
*
|
|
69
|
+
* Typed as `Agreement` on the way out: every key the summary keeps IS an
|
|
70
|
+
* Agreement field, so this narrows a document rather than returning a different
|
|
71
|
+
* shape.
|
|
72
|
+
*/
|
|
73
|
+
export function shapeAgreement(a) {
|
|
74
|
+
return a.engagementKind === 'link' ? linkSummary(a) : a;
|
|
75
|
+
}
|
|
28
76
|
export async function proposeAgreement(proposalData, creds) {
|
|
29
77
|
if (!proposalData)
|
|
30
78
|
throw new Error('Proposal data is required for proposal creation');
|
|
@@ -48,7 +96,7 @@ export async function proposeAgreement(proposalData, creds) {
|
|
|
48
96
|
if (!data?.['agreement']) {
|
|
49
97
|
throw new Error('Invalid response: expected { agreement } from POST /agreements/proposals');
|
|
50
98
|
}
|
|
51
|
-
return data['agreement'];
|
|
99
|
+
return shapeAgreement(data['agreement']);
|
|
52
100
|
}
|
|
53
101
|
/** Propose a contract to one party (user or agent). Server defaults `engagementKind` to `service`. */
|
|
54
102
|
export async function proposeDirectTo(input, creds) {
|
|
@@ -96,7 +144,7 @@ export async function delegateAgreement(proposalData, creds) {
|
|
|
96
144
|
if (!data?.['agreement']) {
|
|
97
145
|
throw new Error('Invalid response: expected { agreement } from POST /agreements/:parentAgreementId/delegations');
|
|
98
146
|
}
|
|
99
|
-
return data['agreement'];
|
|
147
|
+
return shapeAgreement(data['agreement']);
|
|
100
148
|
}
|
|
101
149
|
/**
|
|
102
150
|
* Approve or reject a pending agreement (ZIG-524 canonical client path).
|
|
@@ -110,7 +158,10 @@ export async function respondToAgreement(agreementId, action, creds, opts = {})
|
|
|
110
158
|
if (!agreementId || !action)
|
|
111
159
|
throw new Error('Agreement ID and action are required for proposal response');
|
|
112
160
|
assertCreds(creds, 'proposal response');
|
|
113
|
-
|
|
161
|
+
// Unshaped on purpose: the approvals ledger below is exactly what the link
|
|
162
|
+
// summary drops, and resolving the caller's slot from a summary would fail
|
|
163
|
+
// every link approval with "no pending approval entry".
|
|
164
|
+
const agreement = opts.agreement ?? (await getAgreementDocument(agreementId, creds));
|
|
114
165
|
if (!agreement) {
|
|
115
166
|
throw new Error(`Agreement ${agreementId} not found`);
|
|
116
167
|
}
|
|
@@ -213,7 +264,7 @@ export async function approveAgreementAsParty(agreementId, partyId, status, cred
|
|
|
213
264
|
if (!data?.['agreement']) {
|
|
214
265
|
throw new Error('Invalid response: expected { agreement } from PUT /agreements/:id/approvals/:partyId');
|
|
215
266
|
}
|
|
216
|
-
return data['agreement'];
|
|
267
|
+
return shapeAgreement(data['agreement']);
|
|
217
268
|
}
|
|
218
269
|
export async function counterAgreement(agreementId, counter, creds) {
|
|
219
270
|
if (!agreementId)
|
|
@@ -232,7 +283,7 @@ export async function counterAgreement(agreementId, counter, creds) {
|
|
|
232
283
|
if (!data?.['agreement']) {
|
|
233
284
|
throw new Error('Invalid response: expected { agreement } from /agreements/:id/counter');
|
|
234
285
|
}
|
|
235
|
-
return data['agreement'];
|
|
286
|
+
return shapeAgreement(data['agreement']);
|
|
236
287
|
}
|
|
237
288
|
export async function getAgreementStatus(agreementId, creds) {
|
|
238
289
|
if (!agreementId)
|
|
@@ -261,8 +312,7 @@ export async function listAgreements(filters = {}, creds) {
|
|
|
261
312
|
assertCreds(creds, 'list agreements');
|
|
262
313
|
// ZIG-699 — return an empty array only for a genuine empty 200 result. Any
|
|
263
314
|
// failure (non-2xx / network) throws so the MCP tool reports a real error
|
|
264
|
-
// instead of "you have no agreements".
|
|
265
|
-
// so the tool's toolError/classifyToolError maps it to a stable code.
|
|
315
|
+
// instead of "you have no agreements". ZIG-1124 — HTTP failures are ApiError.
|
|
266
316
|
// Canonical query path: GET /agreements?scope=&status=&engagementKind=&...
|
|
267
317
|
const url = new URL(getAgreementBaseUrl());
|
|
268
318
|
if (filters.status)
|
|
@@ -284,10 +334,12 @@ export async function listAgreements(filters = {}, creds) {
|
|
|
284
334
|
if (!res.ok) {
|
|
285
335
|
const body = await res.text().catch(() => '');
|
|
286
336
|
runtimeLog.warn('AgreementClient', `⚠️ List agreements failed: ${res.status} ${res.statusText} ${body?.slice(0, 200)}`);
|
|
287
|
-
|
|
337
|
+
throwApiError(res, body, `GET /agreements failed: ${res.status}`);
|
|
288
338
|
}
|
|
289
339
|
const data = await res.json().catch(() => null);
|
|
290
|
-
return Array.isArray(data?.['agreements'])
|
|
340
|
+
return Array.isArray(data?.['agreements'])
|
|
341
|
+
? data['agreements'].map(shapeAgreement)
|
|
342
|
+
: [];
|
|
291
343
|
}
|
|
292
344
|
export async function getMyAgreements(filters = {}, creds) {
|
|
293
345
|
assertCreds(creds, 'get my agreements');
|
|
@@ -316,20 +368,29 @@ export async function getMyAgreements(filters = {}, creds) {
|
|
|
316
368
|
if (!res.ok) {
|
|
317
369
|
const body = await res.text().catch(() => '');
|
|
318
370
|
runtimeLog.warn('AgreementClient', `⚠️ Get my agreements failed: ${res.status} ${res.statusText} ${body?.slice(0, 200)}`);
|
|
319
|
-
|
|
371
|
+
throwApiError(res, body, `GET /agreements?scope=mine failed: ${res.status}`);
|
|
320
372
|
}
|
|
321
373
|
const data = await res.json().catch(() => null);
|
|
322
|
-
return Array.isArray(data?.['agreements'])
|
|
374
|
+
return Array.isArray(data?.['agreements'])
|
|
375
|
+
? data['agreements'].map(shapeAgreement)
|
|
376
|
+
: [];
|
|
323
377
|
}
|
|
324
|
-
|
|
378
|
+
/**
|
|
379
|
+
* The full agreement document, unshaped — for this client's OWN reads.
|
|
380
|
+
*
|
|
381
|
+
* `respondToAgreement` resolves which approvals slot the caller may fill, and
|
|
382
|
+
* `claimOpenAgreement` routes on the broadcast's open side; both need fields the
|
|
383
|
+
* link summary deliberately drops. Callers outside this module get the shaped
|
|
384
|
+
* `getAgreement`.
|
|
385
|
+
*/
|
|
386
|
+
async function getAgreementDocument(agreementId, creds) {
|
|
325
387
|
if (!agreementId)
|
|
326
388
|
return null;
|
|
327
389
|
assertCreds(creds, 'get agreement');
|
|
328
390
|
// ZIG-698 — a genuine 404 (and 200-with-no-agreement) is a real "not found"
|
|
329
391
|
// and returns null. Every other failure (403/5xx/network) must throw so the
|
|
330
392
|
// caller can tell "does not exist" from "could not fetch" instead of a 403
|
|
331
|
-
// masquerading as not-found.
|
|
332
|
-
// toolError; the message keeps the status whitespace-delimited for that.
|
|
393
|
+
// masquerading as not-found. ZIG-1124 — thrown as ApiError for toolError.
|
|
333
394
|
let res;
|
|
334
395
|
try {
|
|
335
396
|
res = await fetch(`${getAgreementBaseUrl()}/${agreementId}`, {
|
|
@@ -346,11 +407,16 @@ export async function getAgreement(agreementId, creds) {
|
|
|
346
407
|
if (!res.ok) {
|
|
347
408
|
const body = await res.text().catch(() => '');
|
|
348
409
|
runtimeLog.warn('AgreementClient', `⚠️ Get agreement failed: ${res.status} ${res.statusText} ${body?.slice(0, 200)}`);
|
|
349
|
-
|
|
410
|
+
throwApiError(res, body, `GET /agreements/${agreementId} failed: ${res.status}`);
|
|
350
411
|
}
|
|
351
412
|
const data = await res.json().catch(() => null);
|
|
352
413
|
return data?.['agreement'] ?? null;
|
|
353
414
|
}
|
|
415
|
+
/** One agreement, shaped: a link comes back as its summary. */
|
|
416
|
+
export async function getAgreement(agreementId, creds) {
|
|
417
|
+
const agreement = await getAgreementDocument(agreementId, creds);
|
|
418
|
+
return agreement ? shapeAgreement(agreement) : null;
|
|
419
|
+
}
|
|
354
420
|
export async function createAgreement(body, creds) {
|
|
355
421
|
if (!body)
|
|
356
422
|
throw new Error('Body is required for agreement creation');
|
|
@@ -369,7 +435,8 @@ export async function createAgreement(body, creds) {
|
|
|
369
435
|
if (!data?.['agreement']) {
|
|
370
436
|
throw new Error('Invalid response: expected { ok, agreement } from POST /agreements');
|
|
371
437
|
}
|
|
372
|
-
|
|
438
|
+
const envelope = data;
|
|
439
|
+
return { ...envelope, agreement: shapeAgreement(envelope.agreement) };
|
|
373
440
|
}
|
|
374
441
|
export async function revokeAgreement(agreementId, creds) {
|
|
375
442
|
if (!agreementId)
|
|
@@ -389,7 +456,8 @@ export async function revokeAgreement(agreementId, creds) {
|
|
|
389
456
|
if (!data?.['agreement']) {
|
|
390
457
|
throw new Error('Invalid response: expected { ok, agreement } from DELETE /agreements/:id');
|
|
391
458
|
}
|
|
392
|
-
|
|
459
|
+
const envelope = data;
|
|
460
|
+
return { ...envelope, agreement: shapeAgreement(envelope.agreement) };
|
|
393
461
|
}
|
|
394
462
|
/** ZIG-832: mark an agreement fulfilled (complete). A provider closing its own
|
|
395
463
|
* delivered work — party-gated server-side. */
|
|
@@ -409,7 +477,8 @@ export async function fulfillAgreement(agreementId, creds) {
|
|
|
409
477
|
if (!data?.['agreement']) {
|
|
410
478
|
throw new Error('Invalid response: expected { ok, agreement } from POST /agreements/:id/fulfill');
|
|
411
479
|
}
|
|
412
|
-
|
|
480
|
+
const envelope = data;
|
|
481
|
+
return { ...envelope, agreement: shapeAgreement(envelope.agreement) };
|
|
413
482
|
}
|
|
414
483
|
/**
|
|
415
484
|
* Claim an open agreement (ZIG-524 phase 2 / ZIG-525):
|
|
@@ -433,7 +502,12 @@ export async function claimAgreement(agreementId, creds) {
|
|
|
433
502
|
if (!data?.['agreement']) {
|
|
434
503
|
throw new Error('Invalid response: expected { ok, agreement } from POST /agreements/:id/claim');
|
|
435
504
|
}
|
|
436
|
-
|
|
505
|
+
// ZIG-1155: the route reports which kind of broadcast this turned out to be.
|
|
506
|
+
// A caller cannot work it out from the row — post-claim no sentinel is left,
|
|
507
|
+
// and a quest (you do the work) reads the same shape as a standing offer (you
|
|
508
|
+
// pay for it) unless you know which slot you landed in.
|
|
509
|
+
const envelope = data;
|
|
510
|
+
return { ...envelope, agreement: shapeAgreement(envelope.agreement) };
|
|
437
511
|
}
|
|
438
512
|
// ---------------------------------------------------------------------------
|
|
439
513
|
// Chat links
|
package/dist/http/ChatClient.js
CHANGED
|
@@ -104,7 +104,7 @@ export async function listMyChats(creds) {
|
|
|
104
104
|
assertCreds(creds, 'list my chats');
|
|
105
105
|
// ZIG-699 — empty array only for a genuine empty 200; any failure (non-2xx /
|
|
106
106
|
// network) throws so ziggs_chat_list reports a real error instead of "you
|
|
107
|
-
// have no chats".
|
|
107
|
+
// have no chats". ZIG-1124 — HTTP failures are ApiError (status/body/code).
|
|
108
108
|
let res;
|
|
109
109
|
try {
|
|
110
110
|
res = await fetch(`${getBackendUrl()}/chats/mine`, {
|
|
@@ -119,7 +119,7 @@ export async function listMyChats(creds) {
|
|
|
119
119
|
if (!res.ok) {
|
|
120
120
|
const body = await res.text().catch(() => '');
|
|
121
121
|
runtimeLog.warn('ChatClient', `⚠️ listMyChats failed: ${res.status} ${res.statusText} ${body?.slice(0, 200)}`);
|
|
122
|
-
|
|
122
|
+
throwApiError(res, body, `GET /chats/mine failed: ${res.status}`);
|
|
123
123
|
}
|
|
124
124
|
const data = await res.json().catch(() => null);
|
|
125
125
|
return Array.isArray(data?.['chats']) ? data['chats'] : [];
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import 'dotenv/config';
|
|
2
2
|
import { getBackendUrl } from '../utils/urlUtils.js';
|
|
3
|
+
import { throwApiError } from '../shared/apiError.js';
|
|
3
4
|
import { buildOperatorHeaders } from './operatorHeaders.js';
|
|
4
5
|
import { GrantsClient } from './GrantsClient.js';
|
|
5
6
|
// ZIG-569 — defense-in-depth mirror of the backend leak-guard
|
|
@@ -169,10 +170,9 @@ export class ConnectionsClient {
|
|
|
169
170
|
const response = await fetch(`${this.baseUrl}${path}`, init);
|
|
170
171
|
const text = await response.text().catch(() => '');
|
|
171
172
|
if (!response.ok) {
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
throw err;
|
|
173
|
+
// ZIG-1124 — ApiError (status/body/code); ConnectionsError remains the
|
|
174
|
+
// documented duck type for callers that branch on `.status`.
|
|
175
|
+
throwApiError(response, text, `${method} ${path} failed: ${response.status}`);
|
|
176
176
|
}
|
|
177
177
|
return text;
|
|
178
178
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import 'dotenv/config';
|
|
2
2
|
import { getBackendUrl } from '../utils/urlUtils.js';
|
|
3
|
+
import { throwApiError } from '../shared/apiError.js';
|
|
3
4
|
import { buildOperatorHeaders } from './operatorHeaders.js';
|
|
4
5
|
/**
|
|
5
6
|
* P4 discovery — labels-only pointers to context the agent could REQUEST but
|
|
@@ -34,7 +35,7 @@ export class ContextDiscoveryClient {
|
|
|
34
35
|
});
|
|
35
36
|
const body = await res.text().catch(() => '');
|
|
36
37
|
if (!res.ok) {
|
|
37
|
-
|
|
38
|
+
throwApiError(res, body, `ContextDiscoveryClient.discoverGrantable failed: ${res.status}`);
|
|
38
39
|
}
|
|
39
40
|
const parsed = JSON.parse(body);
|
|
40
41
|
return (parsed.items ?? []).map((i) => ({
|
|
@@ -105,18 +105,11 @@ export class ContextReadClient {
|
|
|
105
105
|
const res = await fetch(url.toString(), { headers });
|
|
106
106
|
const body = await res.text().catch(() => '');
|
|
107
107
|
if (!res.ok) {
|
|
108
|
-
// Carry the status like `snapshot()` does, so callers can branch on it.
|
|
109
108
|
// A 403 here is a legitimate outcome, not a transport failure: addressing
|
|
110
109
|
// and authorisation are separate, so an agent can be told about mail it
|
|
111
|
-
// is not (or is no longer) allowed to open.
|
|
112
|
-
//
|
|
113
|
-
|
|
114
|
-
if (res.status === 429) {
|
|
115
|
-
throw pollSurfaceError(`ContextReadClient.read ${type}`, res, body);
|
|
116
|
-
}
|
|
117
|
-
const err = new Error(`ContextReadClient.read ${type} ${res.status} ${body.slice(0, 200)}`);
|
|
118
|
-
err.status = res.status;
|
|
119
|
-
throw err;
|
|
110
|
+
// is not (or is no longer) allowed to open. ZIG-1124 — one ApiError shape
|
|
111
|
+
// (429 → RateLimitedError with Retry-After).
|
|
112
|
+
throw pollSurfaceError(`ContextReadClient.read ${type}`, res, body);
|
|
120
113
|
}
|
|
121
114
|
return JSON.parse(body);
|
|
122
115
|
}
|
|
@@ -151,12 +144,8 @@ export class ContextReadClient {
|
|
|
151
144
|
const res = await fetch(url.toString(), { headers });
|
|
152
145
|
const body = await res.text().catch(() => '');
|
|
153
146
|
if (!res.ok) {
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
}
|
|
157
|
-
const err = new Error(`ContextReadClient.snapshot ${res.status} ${body.slice(0, 200)}`);
|
|
158
|
-
err.status = res.status;
|
|
159
|
-
throw err;
|
|
147
|
+
// ZIG-1124 — ApiError for every non-OK (429 → RateLimitedError).
|
|
148
|
+
throw pollSurfaceError('ContextReadClient.snapshot', res, body);
|
|
160
149
|
}
|
|
161
150
|
return JSON.parse(body);
|
|
162
151
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import 'dotenv/config';
|
|
2
2
|
import { getBackendUrl } from '../utils/urlUtils.js';
|
|
3
|
+
import { throwApiError } from '../shared/apiError.js';
|
|
3
4
|
import { buildOperatorHeaders } from './operatorHeaders.js';
|
|
4
5
|
/**
|
|
5
6
|
* ZIG-648 — unified grant listing across every rail. `GET /grants` returns the
|
|
@@ -41,7 +42,7 @@ export class GrantsClient {
|
|
|
41
42
|
});
|
|
42
43
|
const body = await res.text().catch(() => '');
|
|
43
44
|
if (!res.ok) {
|
|
44
|
-
|
|
45
|
+
throwApiError(res, body, `GrantsClient.listGrants failed: ${res.status}`);
|
|
45
46
|
}
|
|
46
47
|
const parsed = JSON.parse(body);
|
|
47
48
|
return {
|
|
@@ -127,45 +127,6 @@ export interface InboxAckResult {
|
|
|
127
127
|
*/
|
|
128
128
|
export interface InboxReadOptions {
|
|
129
129
|
waitSeconds?: number;
|
|
130
|
-
/**
|
|
131
|
-
* Operator read only (ZIG-965): restrict the sweep to this roster. The
|
|
132
|
-
* server intersects it with the agents the key owner runs — it narrows,
|
|
133
|
-
* never widens. A launcher hosting a subset should always pass the agents
|
|
134
|
-
* it actually registered, or it pays for a sweep of the owner's whole
|
|
135
|
-
* seeded fleet.
|
|
136
|
-
*/
|
|
137
|
-
agents?: string[];
|
|
138
|
-
}
|
|
139
|
-
/**
|
|
140
|
-
* One agent's line in the operator sweep: enough to decide whether to start a
|
|
141
|
-
* host, and nothing more.
|
|
142
|
-
*
|
|
143
|
-
* Deliberately NOT that agent's envelope. The launcher only chooses who to
|
|
144
|
-
* run; the host it starts reads its own inbox on its own credentials a moment
|
|
145
|
-
* later, so building N envelopes here was work thrown away.
|
|
146
|
-
*/
|
|
147
|
-
export interface InboxOperatorAgentEntry {
|
|
148
|
-
agentId: string;
|
|
149
|
-
/** Newest delivery addressed to this agent, or null if it never had one. */
|
|
150
|
-
deliveredUpTo: string | null;
|
|
151
|
-
/** How far it has acked. Mail exists when `deliveredUpTo > ackedUpTo`. */
|
|
152
|
-
ackedUpTo: string | null;
|
|
153
|
-
/**
|
|
154
|
-
* True when this agent holds open assigned work. Independent of the
|
|
155
|
-
* watermark: a host that died mid-task already acked past the task's
|
|
156
|
-
* delivery, and the open task is the only durable trace it needs restarting.
|
|
157
|
-
*/
|
|
158
|
-
hasOpenTasks: boolean;
|
|
159
|
-
}
|
|
160
|
-
/** GET /inbox/operator: which of the key owner's agents have mail or open work. */
|
|
161
|
-
export interface InboxOperatorEnvelope {
|
|
162
|
-
asOf: string;
|
|
163
|
-
/** Agents with mail or open work. */
|
|
164
|
-
agents: InboxOperatorAgentEntry[];
|
|
165
|
-
/** Agents examined that had neither. */
|
|
166
|
-
idleAgents: number;
|
|
167
|
-
/** Owned agents beyond the server's per-pass cap — not examined. */
|
|
168
|
-
truncatedAgents: number;
|
|
169
130
|
}
|
|
170
131
|
/**
|
|
171
132
|
* The doorbell, not the door (ZIG-434): references addressed to this agent
|
|
@@ -184,14 +145,6 @@ export declare class InboxClient {
|
|
|
184
145
|
private headers;
|
|
185
146
|
private inboxUrl;
|
|
186
147
|
getInbox(opts?: InboxReadOptions): Promise<InboxEnvelope>;
|
|
187
|
-
/**
|
|
188
|
-
* Operator-level multiplexed read: which of this key owner's agents have
|
|
189
|
-
* mail or open work (an agent-scoped key collapses to its one agent).
|
|
190
|
-
*
|
|
191
|
-
* Returns who to start, never what they were sent — the host you start
|
|
192
|
-
* reads its own inbox on its own identity. Ack stays per agent.
|
|
193
|
-
*/
|
|
194
|
-
getOperatorInbox(opts?: InboxReadOptions): Promise<InboxOperatorEnvelope>;
|
|
195
148
|
/**
|
|
196
149
|
* Advance this agent's watermark — pass the envelope's `ackTo`. Monotonic
|
|
197
150
|
* server-side: an older value is a no-op, so a replayed ack can never
|
package/dist/http/InboxClient.js
CHANGED
|
@@ -35,9 +35,6 @@ export class InboxClient {
|
|
|
35
35
|
if (opts.waitSeconds != null && opts.waitSeconds > 0) {
|
|
36
36
|
url.searchParams.set('wait', String(opts.waitSeconds));
|
|
37
37
|
}
|
|
38
|
-
if (opts.agents?.length) {
|
|
39
|
-
url.searchParams.set('agents', opts.agents.join(','));
|
|
40
|
-
}
|
|
41
38
|
return url.toString();
|
|
42
39
|
}
|
|
43
40
|
async getInbox(opts = {}) {
|
|
@@ -50,42 +47,6 @@ export class InboxClient {
|
|
|
50
47
|
}
|
|
51
48
|
return JSON.parse(body);
|
|
52
49
|
}
|
|
53
|
-
/**
|
|
54
|
-
* Operator-level multiplexed read: which of this key owner's agents have
|
|
55
|
-
* mail or open work (an agent-scoped key collapses to its one agent).
|
|
56
|
-
*
|
|
57
|
-
* Returns who to start, never what they were sent — the host you start
|
|
58
|
-
* reads its own inbox on its own identity. Ack stays per agent.
|
|
59
|
-
*/
|
|
60
|
-
async getOperatorInbox(opts = {}) {
|
|
61
|
-
// Bounded wait (ZIG-965): the server holds at most `waitSeconds` (+ sweep
|
|
62
|
-
// time); a poll that outlives that by a wide margin is a dead sweep, and
|
|
63
|
-
// without a timeout it blocked the launcher's whole poll loop — lazy wake
|
|
64
|
-
// simply stopped. Abort and let the caller's retry loop take over.
|
|
65
|
-
const timeoutMs = ((opts.waitSeconds ?? 0) + 30) * 1000;
|
|
66
|
-
const ac = new AbortController();
|
|
67
|
-
const timer = setTimeout(() => ac.abort(), timeoutMs);
|
|
68
|
-
let res;
|
|
69
|
-
try {
|
|
70
|
-
res = await fetch(this.inboxUrl('/inbox/operator', opts), {
|
|
71
|
-
headers: this.headers(),
|
|
72
|
-
signal: ac.signal,
|
|
73
|
-
});
|
|
74
|
-
}
|
|
75
|
-
catch (err) {
|
|
76
|
-
throw ac.signal.aborted
|
|
77
|
-
? new Error(`InboxClient.getOperatorInbox timed out after ${timeoutMs}ms`)
|
|
78
|
-
: err;
|
|
79
|
-
}
|
|
80
|
-
finally {
|
|
81
|
-
clearTimeout(timer);
|
|
82
|
-
}
|
|
83
|
-
const body = await res.text().catch(() => '');
|
|
84
|
-
if (!res.ok) {
|
|
85
|
-
throw pollSurfaceError('InboxClient.getOperatorInbox', res, body);
|
|
86
|
-
}
|
|
87
|
-
return JSON.parse(body);
|
|
88
|
-
}
|
|
89
50
|
/**
|
|
90
51
|
* Advance this agent's watermark — pass the envelope's `ackTo`. Monotonic
|
|
91
52
|
* server-side: an older value is a no-op, so a replayed ack can never
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import 'dotenv/config';
|
|
2
|
+
// ZIG-1111: one shaping rule for every agreement this package parses.
|
|
3
|
+
import { shapeAgreement } from './AgreementClient.js';
|
|
2
4
|
import { getBackendUrl } from '../utils/urlUtils.js';
|
|
3
5
|
import { throwApiError } from '../shared/apiError.js';
|
|
4
6
|
function getMarketplaceBaseUrl() { return `${getBackendUrl()}/marketplace`; }
|
|
@@ -30,7 +32,7 @@ export async function publishOffer(payload, creds) {
|
|
|
30
32
|
throwApiError(res, body, `Marketplace offer publish failed: ${res.status}`);
|
|
31
33
|
}
|
|
32
34
|
const data = await res.json().catch(() => null);
|
|
33
|
-
return (data?.['offer'] ?? data);
|
|
35
|
+
return shapeAgreement((data?.['offer'] ?? data));
|
|
34
36
|
}
|
|
35
37
|
export async function pullOffers(options, creds) {
|
|
36
38
|
assertCreds(creds, 'marketplace offers pull');
|
|
@@ -67,7 +69,7 @@ export async function claimOffer(agreementId, creds) {
|
|
|
67
69
|
const data = await res.json().catch(() => null);
|
|
68
70
|
if (!data?.['ok'])
|
|
69
71
|
throw new Error(data?.['error'] || 'Claim failed');
|
|
70
|
-
return data['offer'];
|
|
72
|
+
return shapeAgreement(data['offer']);
|
|
71
73
|
}
|
|
72
74
|
export async function publishQuest(payload, creds) {
|
|
73
75
|
assertCreds(creds, 'quest publish');
|
|
@@ -83,7 +85,7 @@ export async function publishQuest(payload, creds) {
|
|
|
83
85
|
const data = await res.json().catch(() => null);
|
|
84
86
|
if (!data?.['agreement'])
|
|
85
87
|
throw new Error('Quest publish returned no agreement');
|
|
86
|
-
return data['agreement'];
|
|
88
|
+
return shapeAgreement(data['agreement']);
|
|
87
89
|
}
|
|
88
90
|
export async function pullQuests(options, creds) {
|
|
89
91
|
assertCreds(creds, 'quest pull');
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import 'dotenv/config';
|
|
2
2
|
import { getBackendUrl } from '../utils/urlUtils.js';
|
|
3
|
+
import { throwApiError } from '../shared/apiError.js';
|
|
3
4
|
/**
|
|
4
5
|
* Read-side client for forward-delta message reads.
|
|
5
6
|
*
|
|
@@ -35,11 +36,8 @@ export class MessagesClient {
|
|
|
35
36
|
const res = await fetch(url.toString(), { headers: this._headers() });
|
|
36
37
|
if (!res.ok) {
|
|
37
38
|
const body = await res.text().catch(() => '');
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
// without parsing the message string.
|
|
41
|
-
err.status = res.status;
|
|
42
|
-
throw err;
|
|
39
|
+
// Callers branch on ApiError.status (404 = chat deleted/not visible).
|
|
40
|
+
throwApiError(res, body, `MessagesClient.list failed: ${res.status}`);
|
|
43
41
|
}
|
|
44
42
|
return (await res.json());
|
|
45
43
|
}
|
package/dist/http/OrgsClient.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import 'dotenv/config';
|
|
2
2
|
import { getBackendUrl } from '../utils/urlUtils.js';
|
|
3
|
+
import { throwApiError } from '../shared/apiError.js';
|
|
3
4
|
import { buildOperatorHeaders } from './operatorHeaders.js';
|
|
4
5
|
/**
|
|
5
6
|
* ZIG-739 / ZIG-956 — the operator's full org membership (not just granted
|
|
@@ -15,7 +16,7 @@ export async function fetchMyOrgs(creds, baseUrl) {
|
|
|
15
16
|
});
|
|
16
17
|
const body = await res.text().catch(() => '');
|
|
17
18
|
if (!res.ok) {
|
|
18
|
-
|
|
19
|
+
throwApiError(res, body, `GET /orgs/me failed: ${res.status}`);
|
|
19
20
|
}
|
|
20
21
|
const parsed = body ? JSON.parse(body) : {};
|
|
21
22
|
return (parsed.orgs ?? []).map((o) => ({
|
|
@@ -55,7 +56,7 @@ export async function fetchDelegateAccess(creds, baseUrl) {
|
|
|
55
56
|
});
|
|
56
57
|
const body = await res.text().catch(() => '');
|
|
57
58
|
if (!res.ok) {
|
|
58
|
-
|
|
59
|
+
throwApiError(res, body, `GET /agents/claude-delegate/access failed: ${res.status}`);
|
|
59
60
|
}
|
|
60
61
|
return body ? JSON.parse(body) : {};
|
|
61
62
|
}
|
|
@@ -2,7 +2,7 @@ import 'dotenv/config';
|
|
|
2
2
|
import { getBackendUrl } from '../utils/urlUtils.js';
|
|
3
3
|
import { buildOperatorHeaders } from './operatorHeaders.js';
|
|
4
4
|
import { GrantsClient } from './GrantsClient.js';
|
|
5
|
-
import {
|
|
5
|
+
import { throwApiError } from '../shared/apiError.js';
|
|
6
6
|
function randomIdempotencyKey(prefix = 'op') {
|
|
7
7
|
return `${prefix}_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 10)}`;
|
|
8
8
|
}
|
|
@@ -217,10 +217,8 @@ export class PaymentsClient {
|
|
|
217
217
|
const response = await fetch(`${this.baseUrl}${path}`, init);
|
|
218
218
|
const text = await response.text();
|
|
219
219
|
if (!response.ok) {
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
err.body = text;
|
|
223
|
-
throw err;
|
|
220
|
+
// ZIG-1124 — ApiError; PaymentsError remains the duck type for `.status`.
|
|
221
|
+
throwApiError(response, text, `HTTP ${response.status}`);
|
|
224
222
|
}
|
|
225
223
|
return text ? JSON.parse(text) : null;
|
|
226
224
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type ProposeTerms } from './AgreementClient.js';
|
|
1
|
+
import { type ClaimedKind, type ProposeTerms } from './AgreementClient.js';
|
|
2
2
|
import { type Agreement, type Creds, type EngagementKind } from '../types.js';
|
|
3
3
|
/**
|
|
4
4
|
* ZIG-1022 — one propose grammar. Direct, broadcast (quest and standing
|
|
@@ -22,11 +22,19 @@ export declare function proposeUnified(input: UnifiedProposeInput, creds: Creds)
|
|
|
22
22
|
agreement: Agreement;
|
|
23
23
|
shape: ProposeShape;
|
|
24
24
|
}>;
|
|
25
|
-
export type ClaimedKind = 'link' | 'offer' | 'quest' | 'hand-off';
|
|
26
25
|
/**
|
|
27
|
-
* ZIG-1021 — one claim verb for any open broadcast
|
|
28
|
-
*
|
|
29
|
-
*
|
|
26
|
+
* ZIG-1021 — one claim verb for any open broadcast: link invite, quest,
|
|
27
|
+
* hand-off, or standing offer.
|
|
28
|
+
*
|
|
29
|
+
* One request. This used to read the agreement first to decide which endpoint to
|
|
30
|
+
* post to, and `GET /agreements/:id` is party-scoped — a claimer is by definition
|
|
31
|
+
* not yet a party to the broadcast it is claiming, so the routing read 404'd and
|
|
32
|
+
* every standing offer in the store failed with "Agreement not found" before
|
|
33
|
+
* either claim endpoint was called (ZIG-1155). The backend routes it now, where
|
|
34
|
+
* the row is readable without being a party to it.
|
|
35
|
+
*
|
|
36
|
+
* `kind` arrives on the claim response — the route that did the routing reports
|
|
37
|
+
* which broadcast kind this turned out to be.
|
|
30
38
|
*/
|
|
31
39
|
export declare function claimOpenAgreement(agreementId: string, creds: Creds): Promise<{
|
|
32
40
|
agreement: Agreement;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { createAgreement, claimAgreement,
|
|
2
|
-
import {
|
|
1
|
+
import { createAgreement, claimAgreement, proposeBroadcast, proposeDirectTo, } from './AgreementClient.js';
|
|
2
|
+
import { publishOffer } from './MarketplaceClient.js';
|
|
3
3
|
import { isBroadcastTarget, } from '../types.js';
|
|
4
4
|
export async function proposeUnified(input, creds) {
|
|
5
5
|
const { proposedTo, chatId, engagementKind, providerId, ...terms } = input;
|
|
@@ -56,30 +56,24 @@ export async function proposeUnified(input, creds) {
|
|
|
56
56
|
return { agreement, shape: 'direct' };
|
|
57
57
|
}
|
|
58
58
|
/**
|
|
59
|
-
* ZIG-1021 — one claim verb for any open broadcast
|
|
60
|
-
*
|
|
61
|
-
*
|
|
59
|
+
* ZIG-1021 — one claim verb for any open broadcast: link invite, quest,
|
|
60
|
+
* hand-off, or standing offer.
|
|
61
|
+
*
|
|
62
|
+
* One request. This used to read the agreement first to decide which endpoint to
|
|
63
|
+
* post to, and `GET /agreements/:id` is party-scoped — a claimer is by definition
|
|
64
|
+
* not yet a party to the broadcast it is claiming, so the routing read 404'd and
|
|
65
|
+
* every standing offer in the store failed with "Agreement not found" before
|
|
66
|
+
* either claim endpoint was called (ZIG-1155). The backend routes it now, where
|
|
67
|
+
* the row is readable without being a party to it.
|
|
68
|
+
*
|
|
69
|
+
* `kind` arrives on the claim response — the route that did the routing reports
|
|
70
|
+
* which broadcast kind this turned out to be.
|
|
62
71
|
*/
|
|
63
72
|
export async function claimOpenAgreement(agreementId, creds) {
|
|
64
73
|
if (!agreementId)
|
|
65
74
|
throw new Error('agreementId is required');
|
|
66
|
-
const
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
const { agreement } = await claimAgreement(agreementId, creds);
|
|
71
|
-
return { agreement, kind: 'link' };
|
|
72
|
-
}
|
|
73
|
-
if (isBroadcastTarget(existing.parties?.payer)) {
|
|
74
|
-
// Seller-broadcast standing offer: the open side is the payer — you buy.
|
|
75
|
-
const agreement = await claimOffer(agreementId, creds);
|
|
76
|
-
return { agreement, kind: 'offer' };
|
|
77
|
-
}
|
|
78
|
-
const { agreement } = await claimAgreement(agreementId, creds);
|
|
79
|
-
// ZIG-1059: a pinned provider inverts the quest reading — the publisher's
|
|
80
|
-
// hired agent does the work and the claimer is who it is done FOR.
|
|
81
|
-
return {
|
|
82
|
-
agreement,
|
|
83
|
-
kind: existing.providerPinned === true ? 'hand-off' : 'quest',
|
|
84
|
-
};
|
|
75
|
+
const { agreement, kind } = await claimAgreement(agreementId, creds);
|
|
76
|
+
// A server that has not shipped the `kind` field yet still claims correctly;
|
|
77
|
+
// 'quest' is the shape the route has always handled.
|
|
78
|
+
return { agreement, kind: kind ?? 'quest' };
|
|
85
79
|
}
|
package/dist/http/index.d.ts
CHANGED
|
@@ -26,4 +26,4 @@ export type { MyOrg, OrgResolution } from './OrgsClient.js';
|
|
|
26
26
|
export { AgentSearchClient } from './AgentSearchClient.js';
|
|
27
27
|
export { TelemetryClient } from './TelemetryClient.js';
|
|
28
28
|
export { InboxClient } from './InboxClient.js';
|
|
29
|
-
export type { InboxDeliveryKind, InboxDeliveryRef, InboxChatNews, InboxProposalRef, InboxTaskRef, InboxConnectionRequestRef, InboxHumanAttention, InboxEnvelope, InboxAckResult,
|
|
29
|
+
export type { InboxDeliveryKind, InboxDeliveryRef, InboxChatNews, InboxProposalRef, InboxTaskRef, InboxConnectionRequestRef, InboxHumanAttention, InboxEnvelope, InboxAckResult, } from './InboxClient.js';
|