@ziggs-ai/api-client 0.4.0 → 0.6.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.
Files changed (37) hide show
  1. package/dist/ConnectionManager.d.ts +13 -1
  2. package/dist/ConnectionManager.js +63 -9
  3. package/dist/capabilities/agreements.d.ts +8 -0
  4. package/dist/capabilities/agreements.js +45 -0
  5. package/dist/capabilities/artifacts.js +19 -11
  6. package/dist/capabilities/chat.d.ts +3 -3
  7. package/dist/capabilities/chat.js +8 -8
  8. package/dist/capabilities/connections.js +8 -8
  9. package/dist/capabilities/context.js +11 -11
  10. package/dist/capabilities/discovery.js +4 -4
  11. package/dist/capabilities/grants.js +3 -3
  12. package/dist/capabilities/index.d.ts +3 -1
  13. package/dist/capabilities/index.js +3 -1
  14. package/dist/capabilities/links.d.ts +0 -3
  15. package/dist/capabilities/links.js +64 -102
  16. package/dist/capabilities/marketplace.d.ts +8 -0
  17. package/dist/capabilities/marketplace.js +55 -0
  18. package/dist/capabilities/payments.js +2 -2
  19. package/dist/http/AgreementClient.d.ts +8 -0
  20. package/dist/http/AgreementClient.js +6 -12
  21. package/dist/http/ArtifactsClient.d.ts +2 -2
  22. package/dist/http/ArtifactsClient.js +2 -2
  23. package/dist/http/ChatClient.js +1 -1
  24. package/dist/http/ContextReadClient.js +16 -1
  25. package/dist/http/InboxClient.d.ts +95 -65
  26. package/dist/http/InboxClient.js +46 -17
  27. package/dist/http/MarketplaceClient.d.ts +8 -0
  28. package/dist/http/agreementFlows.d.ts +34 -0
  29. package/dist/http/agreementFlows.js +80 -0
  30. package/dist/http/index.d.ts +2 -1
  31. package/dist/http/index.js +1 -0
  32. package/dist/index.d.ts +2 -0
  33. package/dist/index.js +2 -0
  34. package/dist/shared/rateLimit.d.ts +46 -0
  35. package/dist/shared/rateLimit.js +73 -0
  36. package/dist/types.d.ts +12 -1
  37. package/package.json +1 -1
@@ -1,28 +1,24 @@
1
1
  import 'dotenv/config';
2
- export type InboxScopeKind = 'chat' | 'agreement' | 'org';
3
2
  /**
4
- * ZIG-543: which chats a multi-chat scope's news is in. Lets a scope-granted
5
- * agent open the conversations behind the count (e.g. GET /chats/:chatId/messages).
3
+ * One thing addressed to this agent. A reference, never content — following it
4
+ * (a chat read, a task read) is where this agent's grants are enforced.
6
5
  */
7
- export interface InboxScopeChat {
8
- chatId: string;
9
- newMessages: number;
10
- newArtifacts: number;
11
- latestAt: string | null;
6
+ export interface InboxDeliveryRef {
7
+ /** 'message' | 'artifact' | 'task-state' | 'agreement'. */
8
+ kind: string;
9
+ resourceId: string;
10
+ chatId: string | null;
11
+ agreementId: string | null;
12
+ taskId: string | null;
13
+ /** Who wrote it. Never this agent — you are not woken by your own writes. */
14
+ actorId: string | null;
15
+ ts: string;
12
16
  }
13
- export interface InboxScopeEntry {
14
- scope: {
15
- kind: InboxScopeKind;
16
- id: string;
17
- };
18
- newMessages: number;
19
- newArtifacts: number;
20
- latestAt: string | null;
21
- since: string;
22
- /** Per-chat breakdown for org / agreement scopes (ZIG-543). Absent for a chat scope. */
23
- chats?: InboxScopeChat[];
24
- /** Chats with news beyond the per-scope cap, not listed in `chats`. */
25
- truncatedChats?: number;
17
+ /** Message/artifact deliveries folded by chat, so you can open chats directly. */
18
+ export interface InboxChatNews {
19
+ chatId: string;
20
+ count: number;
21
+ latestAt: string;
26
22
  }
27
23
  export interface InboxProposalRef {
28
24
  agreementId: string;
@@ -35,54 +31,59 @@ export interface InboxConnectionRequestRef {
35
31
  message: string | null;
36
32
  requestedAt: string | null;
37
33
  }
38
- /** Agent asking its principal to connect an MCP server + grant tools (ZIG-686). */
39
- export interface InboxMcpServerRequestRef {
40
- requestId: string;
41
- requesterAgentId: string;
42
- serverUrl: string;
43
- tools: string[];
44
- reason: string | null;
45
- requestedAt: string | null;
46
- }
47
34
  /** Pull-only MCP: prompt the human when proposals need a decision (ZIG-482 / ZIG-481). */
48
35
  export interface InboxHumanAttention {
49
36
  required: true;
50
- reason: 'proposals_awaiting_me' | 'connection_requests_awaiting_me' | 'mcp_server_requests_awaiting_me' | 'multiple';
37
+ reason: 'proposals_awaiting_me' | 'connection_requests_awaiting_me' | 'multiple';
51
38
  proposalCount: number;
52
39
  truncatedProposals: number;
53
40
  connectionRequestCount: number;
54
41
  truncatedConnectionRequests: number;
55
- mcpServerRequestCount: number;
56
- truncatedMcpServerRequests: number;
57
42
  promptUser: string;
58
43
  }
44
+ /**
45
+ * An open task assigned to this agent (ZIG-973). References only — read the
46
+ * task for its description/plan/inputs.
47
+ *
48
+ * Tasks ride their own channel because assignment IS their delivery: before
49
+ * this, a task wake was a synthetic chat row, so work under a chat-less
50
+ * agreement (or self-assigned) reached nobody.
51
+ */
52
+ export interface InboxTaskRef {
53
+ taskId: string;
54
+ agreementId: string | null;
55
+ title: string;
56
+ state: string;
57
+ updatedAt: string | null;
58
+ }
59
59
  export interface InboxEnvelope {
60
60
  asOf: string;
61
- scopes: InboxScopeEntry[];
62
- truncatedScopes: number;
63
- countCap: number;
61
+ /**
62
+ * Unacked deliveries addressed to this agent, newest first. This IS the
63
+ * inbox — read straight out of the delivery log, not derived from grants.
64
+ */
65
+ deliveries: InboxDeliveryRef[];
66
+ /** True when there was more than one envelope's worth; the rest stay unacked. */
67
+ deliveriesCapped: boolean;
68
+ /** The chat-bearing deliveries above, folded by chat. */
69
+ chats: InboxChatNews[];
70
+ /**
71
+ * Pass to `ack()` after acting. Null when there is nothing to ack. Ack after
72
+ * acting, not after reading: a crash in between redelivers.
73
+ */
74
+ ackTo: string | null;
75
+ /** Open tasks assigned to this agent — the work channel (ZIG-973). */
76
+ tasksAwaitingMe: InboxTaskRef[];
77
+ truncatedTasks: number;
64
78
  proposalsAwaitingMe: InboxProposalRef[];
65
79
  truncatedProposals: number;
66
80
  connectionRequestsAwaitingMe: InboxConnectionRequestRef[];
67
81
  truncatedConnectionRequests: number;
68
- /** Agent requests to connect an MCP server awaiting the user (ZIG-686). */
69
- mcpServerRequestsAwaitingMe: InboxMcpServerRequestRef[];
70
- truncatedMcpServerRequests: number;
71
82
  humanAttention?: InboxHumanAttention;
72
83
  }
73
- export interface InboxAck {
74
- kind: InboxScopeKind;
75
- id: string;
76
- upTo: string;
77
- }
78
84
  export interface InboxAckResult {
79
- acked: Array<{
80
- scope: {
81
- kind: InboxScopeKind;
82
- id: string;
83
- };
84
- ackedUpTo: string;
85
- }>;
85
+ /** Where the watermark now sits. Monotonic — a rewind is a no-op, not an error. */
86
+ ackedUpTo: string | null;
86
87
  }
87
88
  /**
88
89
  * Long-poll option shared by the inbox reads. The server holds the request up
@@ -93,27 +94,50 @@ export interface InboxAckResult {
93
94
  */
94
95
  export interface InboxReadOptions {
95
96
  waitSeconds?: number;
97
+ /**
98
+ * Operator read only (ZIG-965): restrict the sweep to this roster. The
99
+ * server intersects it with the agents the key owner runs — it narrows,
100
+ * never widens. A launcher hosting a subset should always pass the agents
101
+ * it actually registered, or it pays for a sweep of the owner's whole
102
+ * seeded fleet.
103
+ */
104
+ agents?: string[];
96
105
  }
97
- /** One agent's slice of the operator-level multiplexed read. */
106
+ /**
107
+ * One agent's line in the operator sweep: enough to decide whether to start a
108
+ * host, and nothing more.
109
+ *
110
+ * Deliberately NOT that agent's envelope. The launcher only chooses who to
111
+ * run; the host it starts reads its own inbox on its own credentials a moment
112
+ * later, so building N envelopes here was work thrown away.
113
+ */
98
114
  export interface InboxOperatorAgentEntry {
99
115
  agentId: string;
100
- /** That agent's own inbox — fenced to its grants, floored by its cursors. */
101
- inbox: InboxEnvelope;
116
+ /** Newest delivery addressed to this agent, or null if it never had one. */
117
+ deliveredUpTo: string | null;
118
+ /** How far it has acked. Mail exists when `deliveredUpTo > ackedUpTo`. */
119
+ ackedUpTo: string | null;
120
+ /**
121
+ * True when this agent holds open assigned work. Independent of the
122
+ * watermark: a host that died mid-task already acked past the task's
123
+ * delivery, and the open task is the only durable trace it needs restarting.
124
+ */
125
+ hasOpenTasks: boolean;
102
126
  }
103
- /** GET /inbox/operator: one poll across every agent the key's owner runs. */
127
+ /** GET /inbox/operator: which of the key owner's agents have mail or open work. */
104
128
  export interface InboxOperatorEnvelope {
105
129
  asOf: string;
106
- /** Agents with actionable content only. */
130
+ /** Agents with mail or open work. */
107
131
  agents: InboxOperatorAgentEntry[];
108
- /** Agents examined whose inboxes were empty. */
132
+ /** Agents examined that had neither. */
109
133
  idleAgents: number;
110
134
  /** Owned agents beyond the server's per-pass cap — not examined. */
111
135
  truncatedAgents: number;
112
136
  }
113
137
  /**
114
- * The doorbell, not the door (ZIG-434): references and counts since the
115
- * agent's last ack — never content. Flow: inbox → read → act → ack (ZIG-446).
116
- * Wraps `GET /inbox` and `POST /inbox/ack`.
138
+ * The doorbell, not the door (ZIG-434): references addressed to this agent
139
+ * since its last ack — never content. Flow: inbox → read → act → ack
140
+ * (ZIG-446). Wraps `GET /inbox` and `POST /inbox/ack`.
117
141
  */
118
142
  export declare class InboxClient {
119
143
  private readonly operatorKey;
@@ -128,11 +152,17 @@ export declare class InboxClient {
128
152
  private inboxUrl;
129
153
  getInbox(opts?: InboxReadOptions): Promise<InboxEnvelope>;
130
154
  /**
131
- * Operator-level multiplexed read: one poll for every agent this key's
132
- * owner runs, each slice fenced to its own agent (an agent-scoped key
133
- * collapses to its one agent). Ack stays per agent — use a per-agent
134
- * client's `ack()` after acting on that agent's slice.
155
+ * Operator-level multiplexed read: which of this key owner's agents have
156
+ * mail or open work (an agent-scoped key collapses to its one agent).
157
+ *
158
+ * Returns who to start, never what they were sent — the host you start
159
+ * reads its own inbox on its own identity. Ack stays per agent.
135
160
  */
136
161
  getOperatorInbox(opts?: InboxReadOptions): Promise<InboxOperatorEnvelope>;
137
- ack(scopes: InboxAck[]): Promise<InboxAckResult>;
162
+ /**
163
+ * Advance this agent's watermark — pass the envelope's `ackTo`. Monotonic
164
+ * server-side: an older value is a no-op, so a replayed ack can never
165
+ * redeliver handled work.
166
+ */
167
+ ack(upTo: string): Promise<InboxAckResult>;
138
168
  }
@@ -1,9 +1,10 @@
1
1
  import 'dotenv/config';
2
2
  import { getBackendUrl } from '../utils/urlUtils.js';
3
+ import { pollSurfaceError } from '../shared/rateLimit.js';
3
4
  /**
4
- * The doorbell, not the door (ZIG-434): references and counts since the
5
- * agent's last ack — never content. Flow: inbox → read → act → ack (ZIG-446).
6
- * Wraps `GET /inbox` and `POST /inbox/ack`.
5
+ * The doorbell, not the door (ZIG-434): references addressed to this agent
6
+ * since its last ack — never content. Flow: inbox → read → act → ack
7
+ * (ZIG-446). Wraps `GET /inbox` and `POST /inbox/ack`.
7
8
  */
8
9
  export class InboxClient {
9
10
  operatorKey;
@@ -34,6 +35,9 @@ export class InboxClient {
34
35
  if (opts.waitSeconds != null && opts.waitSeconds > 0) {
35
36
  url.searchParams.set('wait', String(opts.waitSeconds));
36
37
  }
38
+ if (opts.agents?.length) {
39
+ url.searchParams.set('agents', opts.agents.join(','));
40
+ }
37
41
  return url.toString();
38
42
  }
39
43
  async getInbox(opts = {}) {
@@ -42,37 +46,62 @@ export class InboxClient {
42
46
  });
43
47
  const body = await res.text().catch(() => '');
44
48
  if (!res.ok) {
45
- throw new Error(`InboxClient.getInbox ${res.status} ${body.slice(0, 200)}`);
49
+ throw pollSurfaceError('InboxClient.getInbox', res, body);
46
50
  }
47
51
  return JSON.parse(body);
48
52
  }
49
53
  /**
50
- * Operator-level multiplexed read: one poll for every agent this key's
51
- * owner runs, each slice fenced to its own agent (an agent-scoped key
52
- * collapses to its one agent). Ack stays per agent — use a per-agent
53
- * client's `ack()` after acting on that agent's slice.
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.
54
59
  */
55
60
  async getOperatorInbox(opts = {}) {
56
- const res = await fetch(this.inboxUrl('/inbox/operator', opts), {
57
- headers: this.headers(),
58
- });
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
+ }
59
83
  const body = await res.text().catch(() => '');
60
84
  if (!res.ok) {
61
- throw new Error(`InboxClient.getOperatorInbox ${res.status} ${body.slice(0, 200)}`);
85
+ throw pollSurfaceError('InboxClient.getOperatorInbox', res, body);
62
86
  }
63
87
  return JSON.parse(body);
64
88
  }
65
- async ack(scopes) {
66
- if (!scopes.length)
67
- throw new Error('InboxClient.ack: scopes are required');
89
+ /**
90
+ * Advance this agent's watermark — pass the envelope's `ackTo`. Monotonic
91
+ * server-side: an older value is a no-op, so a replayed ack can never
92
+ * redeliver handled work.
93
+ */
94
+ async ack(upTo) {
95
+ if (!upTo)
96
+ throw new Error('InboxClient.ack: upTo is required');
68
97
  const res = await fetch(`${this.baseUrl}/inbox/ack`, {
69
98
  method: 'POST',
70
99
  headers: this.headers(),
71
- body: JSON.stringify({ scopes }),
100
+ body: JSON.stringify({ upTo }),
72
101
  });
73
102
  const body = await res.text().catch(() => '');
74
103
  if (!res.ok) {
75
- throw new Error(`InboxClient.ack ${res.status} ${body.slice(0, 200)}`);
104
+ throw pollSurfaceError('InboxClient.ack', res, body);
76
105
  }
77
106
  return JSON.parse(body);
78
107
  }
@@ -8,6 +8,12 @@ export interface PublishOfferPayload {
8
8
  maxExecutions?: number;
9
9
  /** `hire` = claimer becomes the provider's principal on claim. Defaults to `service` server-side. */
10
10
  engagementKind?: 'hire' | 'service';
11
+ /**
12
+ * How `price` reads. `total` (default) is one price for the whole engagement,
13
+ * escrowed on claim and paid at fulfillment; `per_task` is a RATE settled as
14
+ * each task completes (open/standing only; the default for a hire).
15
+ */
16
+ billing?: 'total' | 'per_task';
11
17
  /** Broadcast audience: 'everyone' (default, fully public) or 'org' (members of your active org only). */
12
18
  audience?: BroadcastAudience;
13
19
  metadata?: Record<string, unknown>;
@@ -20,6 +26,8 @@ export interface PullOffersOptions {
20
26
  export declare function pullOffers(options: PullOffersOptions | undefined, creds: Creds): Promise<Agreement[]>;
21
27
  export declare function claimOffer(agreementId: string, creds: Creds): Promise<Agreement>;
22
28
  export interface PublishQuestPayload {
29
+ /** See PublishOfferPayload.billing. */
30
+ billing?: 'total' | 'per_task';
23
31
  description: string;
24
32
  chatId?: string;
25
33
  payerId?: string;
@@ -0,0 +1,34 @@
1
+ import { type ProposeTerms } from './AgreementClient.js';
2
+ import { type Agreement, type Creds, type EngagementKind } from '../types.js';
3
+ /**
4
+ * ZIG-1022 — one propose grammar. Direct, broadcast (quest and standing
5
+ * offer), and link proposals all flow through here; the surfaces expose a
6
+ * single propose tool instead of dedicated publish/request tools.
7
+ *
8
+ * Routing:
9
+ * - engagementKind 'link' → POST /agreements (link proposal / open invite)
10
+ * - proposedTo 'everyone' | 'org', providerId = self → seller-broadcast standing offer
11
+ * - proposedTo 'everyone' | 'org', no providerId → buyer-broadcast quest
12
+ * - anything else → direct proposal
13
+ */
14
+ export interface UnifiedProposeInput extends ProposeTerms {
15
+ proposedTo: string;
16
+ /** Required on direct proposals; optional on broadcasts/links. */
17
+ chatId?: string;
18
+ engagementKind?: EngagementKind;
19
+ }
20
+ export type ProposeShape = 'direct' | 'quest' | 'offer' | 'link';
21
+ export declare function proposeUnified(input: UnifiedProposeInput, creds: Creds): Promise<{
22
+ agreement: Agreement;
23
+ shape: ProposeShape;
24
+ }>;
25
+ export type ClaimedKind = 'link' | 'offer' | 'quest';
26
+ /**
27
+ * ZIG-1021 — one claim verb for any open broadcast. Fetches the agreement to
28
+ * route: link invites and quests claim through POST /agreements/:id/claim;
29
+ * standing offers (open payer side) through POST /marketplace/offers/claim.
30
+ */
31
+ export declare function claimOpenAgreement(agreementId: string, creds: Creds): Promise<{
32
+ agreement: Agreement;
33
+ kind: ClaimedKind;
34
+ }>;
@@ -0,0 +1,80 @@
1
+ import { createAgreement, claimAgreement, getAgreement, proposeBroadcast, proposeDirectTo, } from './AgreementClient.js';
2
+ import { claimOffer, publishOffer } from './MarketplaceClient.js';
3
+ import { isBroadcastTarget, } from '../types.js';
4
+ export async function proposeUnified(input, creds) {
5
+ const { proposedTo, chatId, engagementKind, providerId, ...terms } = input;
6
+ if (!proposedTo)
7
+ throw new Error('proposedTo is required (a user/agent id, or "everyone"/"org" to broadcast)');
8
+ if (engagementKind === 'link') {
9
+ // A link is an agreement, proposed to one agent (providerId) or opened as
10
+ // an invite (proposedTo 'everyone'); no chat, no money.
11
+ const target = isBroadcastTarget(proposedTo) ? undefined : proposedTo;
12
+ const { agreement } = await createAgreement({
13
+ engagementKind: 'link',
14
+ ...(target ? { providerId: target } : {}),
15
+ ...(terms.description ? { description: terms.description } : {}),
16
+ }, creds);
17
+ return { agreement, shape: 'link' };
18
+ }
19
+ if (isBroadcastTarget(proposedTo)) {
20
+ const audience = proposedTo;
21
+ if (providerId && creds.agentId && providerId === creds.agentId) {
22
+ // Seller-broadcast: you work, the claimer pays — a standing offer.
23
+ const agreement = await publishOffer({
24
+ description: terms.description ?? '',
25
+ price: terms.price,
26
+ lifecycle: terms.lifecycle,
27
+ expiresAt: terms.expiresAt,
28
+ maxExecutions: terms.maxExecutions,
29
+ engagementKind: engagementKind,
30
+ billing: terms.billing,
31
+ audience,
32
+ }, creds);
33
+ return { agreement, shape: 'offer' };
34
+ }
35
+ if (providerId) {
36
+ throw new Error('On a broadcast, providerId must be your own agent id (a standing offer: you work, the claimer pays) or omitted (a quest: the claimer works, you pay). A third-party providerId is not broadcastable.');
37
+ }
38
+ // Buyer-broadcast: the claimer works, your side pays — an open quest.
39
+ const agreement = await proposeBroadcast({
40
+ ...terms,
41
+ chatId: chatId ?? '',
42
+ engagementKind: engagementKind ?? 'service',
43
+ audience,
44
+ }, creds);
45
+ return { agreement, shape: 'quest' };
46
+ }
47
+ if (!chatId)
48
+ throw new Error('chatId is required on a direct proposal');
49
+ const agreement = await proposeDirectTo({
50
+ ...terms,
51
+ proposedTo,
52
+ chatId,
53
+ providerId: providerId?.trim() || proposedTo,
54
+ engagementKind: engagementKind ?? 'service',
55
+ }, creds);
56
+ return { agreement, shape: 'direct' };
57
+ }
58
+ /**
59
+ * ZIG-1021 — one claim verb for any open broadcast. Fetches the agreement to
60
+ * route: link invites and quests claim through POST /agreements/:id/claim;
61
+ * standing offers (open payer side) through POST /marketplace/offers/claim.
62
+ */
63
+ export async function claimOpenAgreement(agreementId, creds) {
64
+ if (!agreementId)
65
+ throw new Error('agreementId is required');
66
+ const existing = await getAgreement(agreementId, creds);
67
+ if (!existing)
68
+ throw new Error(`Agreement not found: ${agreementId}`);
69
+ if (existing.engagementKind === 'link') {
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
+ return { agreement, kind: 'quest' };
80
+ }
@@ -1,6 +1,7 @@
1
1
  export * from './TaskClient.js';
2
2
  export * from './AgreementClient.js';
3
3
  export * from './MarketplaceClient.js';
4
+ export * from './agreementFlows.js';
4
5
  export * from './ChatClient.js';
5
6
  export { MessagesClient } from './MessagesClient.js';
6
7
  export type { ListMessagesOptions, ListMessagesResult } from './MessagesClient.js';
@@ -25,4 +26,4 @@ export type { MyOrg, OrgResolution } from './OrgsClient.js';
25
26
  export { AgentSearchClient } from './AgentSearchClient.js';
26
27
  export { TelemetryClient } from './TelemetryClient.js';
27
28
  export { InboxClient } from './InboxClient.js';
28
- export type { InboxScopeKind, InboxScopeEntry, InboxProposalRef, InboxConnectionRequestRef, InboxMcpServerRequestRef, InboxHumanAttention, InboxEnvelope, InboxAck, InboxAckResult, InboxOperatorAgentEntry, InboxOperatorEnvelope, } from './InboxClient.js';
29
+ export type { InboxDeliveryRef, InboxChatNews, InboxProposalRef, InboxTaskRef, InboxConnectionRequestRef, InboxHumanAttention, InboxEnvelope, InboxAckResult, InboxOperatorAgentEntry, InboxOperatorEnvelope, } from './InboxClient.js';
@@ -1,6 +1,7 @@
1
1
  export * from './TaskClient.js';
2
2
  export * from './AgreementClient.js';
3
3
  export * from './MarketplaceClient.js';
4
+ export * from './agreementFlows.js';
4
5
  export * from './ChatClient.js';
5
6
  export { MessagesClient } from './MessagesClient.js';
6
7
  export { ArtifactsClient } from './ArtifactsClient.js';
package/dist/index.d.ts CHANGED
@@ -2,8 +2,10 @@ export * from './http/index.js';
2
2
  export * from './capabilities/index.js';
3
3
  export * from './relay/provisionRelayWorkers.js';
4
4
  export { ConnectionManager } from './ConnectionManager.js';
5
+ export type { StartAgentOptions } from './ConnectionManager.js';
5
6
  export { EntryTypes, ContentTypes, OPEN_AGREEMENT_TARGET, ORG_AGREEMENT_TARGET, BROADCAST_TARGETS, isBroadcastTarget, AGREEMENT_ENGAGEMENT_KIND, isValidContentType } from './types.js';
6
7
  export { getBackendUrl, getWebSocketUrl } from './utils/urlUtils.js';
7
8
  export { runtimeLog, resetRuntimeLogLevelCache } from './shared/runtimeLog.js';
9
+ export { RateLimitedError, isRateLimited, parseRetryAfterMs, pollSurfaceError, } from './shared/rateLimit.js';
8
10
  export type { Creds, Task, TaskState, PlanStep, PlanStepStatus, Agreement, EngagementKind, BroadcastAudience, EntryType, ContentType, MessageMetadata, MessageHandler, ApiError, } from './types.js';
9
11
  export type { ProposeTerms, ProposeDirectInput, ProposeBroadcastInput, ProposeAgreementData, } from './http/AgreementClient.js';
package/dist/index.js CHANGED
@@ -5,3 +5,5 @@ export { ConnectionManager } from './ConnectionManager.js';
5
5
  export { EntryTypes, ContentTypes, OPEN_AGREEMENT_TARGET, ORG_AGREEMENT_TARGET, BROADCAST_TARGETS, isBroadcastTarget, AGREEMENT_ENGAGEMENT_KIND, isValidContentType } from './types.js';
6
6
  export { getBackendUrl, getWebSocketUrl } from './utils/urlUtils.js';
7
7
  export { runtimeLog, resetRuntimeLogLevelCache } from './shared/runtimeLog.js';
8
+ // ZIG-1019: retry loops need the server's own wait, not a guess.
9
+ export { RateLimitedError, isRateLimited, parseRetryAfterMs, pollSurfaceError, } from './shared/rateLimit.js';
@@ -0,0 +1,46 @@
1
+ /**
2
+ * ZIG-1019 — a 429 is not a generic failure, it is an instruction with a
3
+ * deadline attached.
4
+ *
5
+ * The poll surface (`/inbox`, `/context/read`) is capped per actor, and every
6
+ * refused call still counts against that cap. A caller that retries on the
7
+ * usual exponential ladder (1s, 2s, 4s…) therefore spends its way deeper into
8
+ * the hole: observed live as an agent that could not read the conversation it
9
+ * had just been woken for, because its own retries kept the bucket empty.
10
+ *
11
+ * The server already says exactly how long to wait — `Retry-After`, or
12
+ * `RateLimit-Reset` from the standard headers. These helpers carry that number
13
+ * to whoever is doing the backing off.
14
+ */
15
+ /** Thrown for HTTP 429 so a retry loop can wait the server's number, not its own. */
16
+ export declare class RateLimitedError extends Error {
17
+ readonly status = 429;
18
+ /** How long the server said to wait. Null when it said nothing. */
19
+ readonly retryAfterMs: number | null;
20
+ constructor(message: string, retryAfterMs: number | null);
21
+ }
22
+ /** True for the error above — survives structured clones and re-wraps. */
23
+ export declare function isRateLimited(err: unknown): err is {
24
+ retryAfterMs: number | null;
25
+ };
26
+ /**
27
+ * Read the wait out of a 429 response. `Retry-After` wins (it is the explicit
28
+ * one) and is accepted in both forms — delta-seconds or an HTTP date;
29
+ * `RateLimit-Reset` is the standard-headers fallback, in seconds. Capped so a
30
+ * bad header cannot park a loop for an hour, floored at a second so a `0` does
31
+ * not reproduce the hot-retry it is meant to stop.
32
+ */
33
+ export declare function parseRetryAfterMs(headers: {
34
+ get(name: string): string | null;
35
+ }): number | null;
36
+ /**
37
+ * Build the error for a failed poll-surface response: 429s carry the server's
38
+ * wait, everything else stays an ordinary Error so existing handling is
39
+ * unchanged.
40
+ */
41
+ export declare function pollSurfaceError(label: string, res: {
42
+ status: number;
43
+ headers: {
44
+ get(name: string): string | null;
45
+ };
46
+ }, body: string): Error;
@@ -0,0 +1,73 @@
1
+ /**
2
+ * ZIG-1019 — a 429 is not a generic failure, it is an instruction with a
3
+ * deadline attached.
4
+ *
5
+ * The poll surface (`/inbox`, `/context/read`) is capped per actor, and every
6
+ * refused call still counts against that cap. A caller that retries on the
7
+ * usual exponential ladder (1s, 2s, 4s…) therefore spends its way deeper into
8
+ * the hole: observed live as an agent that could not read the conversation it
9
+ * had just been woken for, because its own retries kept the bucket empty.
10
+ *
11
+ * The server already says exactly how long to wait — `Retry-After`, or
12
+ * `RateLimit-Reset` from the standard headers. These helpers carry that number
13
+ * to whoever is doing the backing off.
14
+ */
15
+ /** Thrown for HTTP 429 so a retry loop can wait the server's number, not its own. */
16
+ export class RateLimitedError extends Error {
17
+ status = 429;
18
+ /** How long the server said to wait. Null when it said nothing. */
19
+ retryAfterMs;
20
+ constructor(message, retryAfterMs) {
21
+ super(message);
22
+ this.name = 'RateLimitedError';
23
+ this.retryAfterMs = retryAfterMs;
24
+ }
25
+ }
26
+ /** True for the error above — survives structured clones and re-wraps. */
27
+ export function isRateLimited(err) {
28
+ return (!!err &&
29
+ typeof err === 'object' &&
30
+ err.status === 429);
31
+ }
32
+ const MAX_RETRY_AFTER_MS = 120_000;
33
+ /**
34
+ * Read the wait out of a 429 response. `Retry-After` wins (it is the explicit
35
+ * one) and is accepted in both forms — delta-seconds or an HTTP date;
36
+ * `RateLimit-Reset` is the standard-headers fallback, in seconds. Capped so a
37
+ * bad header cannot park a loop for an hour, floored at a second so a `0` does
38
+ * not reproduce the hot-retry it is meant to stop.
39
+ */
40
+ export function parseRetryAfterMs(headers) {
41
+ const explicit = headers.get('retry-after');
42
+ if (explicit) {
43
+ const seconds = Number(explicit);
44
+ if (Number.isFinite(seconds))
45
+ return clampRetryMs(seconds * 1000);
46
+ const at = Date.parse(explicit);
47
+ if (!Number.isNaN(at))
48
+ return clampRetryMs(at - Date.now());
49
+ }
50
+ const reset = headers.get('ratelimit-reset');
51
+ if (reset) {
52
+ const seconds = Number(reset);
53
+ if (Number.isFinite(seconds))
54
+ return clampRetryMs(seconds * 1000);
55
+ }
56
+ return null;
57
+ }
58
+ function clampRetryMs(ms) {
59
+ if (!Number.isFinite(ms))
60
+ return 1_000;
61
+ return Math.min(MAX_RETRY_AFTER_MS, Math.max(1_000, Math.round(ms)));
62
+ }
63
+ /**
64
+ * Build the error for a failed poll-surface response: 429s carry the server's
65
+ * wait, everything else stays an ordinary Error so existing handling is
66
+ * unchanged.
67
+ */
68
+ export function pollSurfaceError(label, res, body) {
69
+ const message = `${label} ${res.status} ${body.slice(0, 200)}`;
70
+ if (res.status !== 429)
71
+ return new Error(message);
72
+ return new RateLimitedError(message, parseRetryAfterMs(res.headers));
73
+ }
package/dist/types.d.ts CHANGED
@@ -102,6 +102,17 @@ export interface Agreement {
102
102
  lifecycle?: string;
103
103
  expiresAt?: string;
104
104
  maxExecutions?: number;
105
+ /**
106
+ * Seat bookkeeping when this agreement is an open link invite. Each claim
107
+ * takes a seat and mints its own child link, so one shared link produces N
108
+ * separate connections rather than a group.
109
+ */
110
+ linkInvite?: {
111
+ maxClaims?: number;
112
+ claimsUsed?: number;
113
+ } | null;
114
+ /** Set on a link that was formed by claiming the invite with this id. */
115
+ linkInviteTemplateId?: string | null;
105
116
  metadata?: Record<string, unknown>;
106
117
  createdAt?: string;
107
118
  updatedAt?: string;
@@ -165,7 +176,7 @@ export interface MessageMetadata {
165
176
  * `task.notify.chat` / `agreement.notify.direct` traffic. The agent SDK's
166
177
  * `normalizeIncomingEvent` reads `metadata.task` to upgrade structured
167
178
  * task notifications into `task_result` events (so executors see
168
- * `task-assigned` outcomes when the orchestrator calls `task_spawn`),
179
+ * `task-assigned` outcomes when the orchestrator calls `task_create`),
169
180
  * and maps explicit wire `operation` + `agreementId` lifecycle fields
170
181
  * into `agreement_lifecycle` events for proposal approve/reject.
171
182
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ziggs-ai/api-client",
3
- "version": "0.4.0",
3
+ "version": "0.6.0",
4
4
  "description": "HTTP and WebSocket client for the Ziggs backend API",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",