@ziggs-ai/api-client 0.3.1 → 0.5.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 (42) hide show
  1. package/dist/ConnectionManager.d.ts +13 -1
  2. package/dist/ConnectionManager.js +52 -9
  3. package/dist/capabilities/artifacts.d.ts +3 -0
  4. package/dist/capabilities/artifacts.js +94 -0
  5. package/dist/capabilities/chat.d.ts +11 -0
  6. package/dist/capabilities/chat.js +38 -0
  7. package/dist/capabilities/connections.d.ts +4 -0
  8. package/dist/capabilities/connections.js +112 -0
  9. package/dist/capabilities/context.d.ts +23 -0
  10. package/dist/capabilities/context.js +220 -0
  11. package/dist/capabilities/discovery.d.ts +4 -0
  12. package/dist/capabilities/discovery.js +77 -0
  13. package/dist/capabilities/grants.d.ts +9 -0
  14. package/dist/capabilities/grants.js +77 -0
  15. package/dist/capabilities/index.d.ts +9 -0
  16. package/dist/capabilities/index.js +9 -0
  17. package/dist/capabilities/links.d.ts +17 -0
  18. package/dist/capabilities/links.js +219 -0
  19. package/dist/capabilities/payments.d.ts +11 -0
  20. package/dist/capabilities/payments.js +404 -0
  21. package/dist/capabilities/types.d.ts +88 -0
  22. package/dist/capabilities/types.js +24 -0
  23. package/dist/http/AgreementClient.d.ts +6 -0
  24. package/dist/http/ConnectionsClient.d.ts +27 -1
  25. package/dist/http/ConnectionsClient.js +29 -0
  26. package/dist/http/ContextReadClient.js +7 -1
  27. package/dist/http/GrantsClient.d.ts +16 -0
  28. package/dist/http/GrantsClient.js +3 -0
  29. package/dist/http/InboxClient.d.ts +95 -65
  30. package/dist/http/InboxClient.js +42 -14
  31. package/dist/http/MarketplaceClient.d.ts +8 -0
  32. package/dist/http/OrgsClient.d.ts +36 -0
  33. package/dist/http/OrgsClient.js +61 -0
  34. package/dist/http/PaymentsClient.d.ts +75 -10
  35. package/dist/http/PaymentsClient.js +26 -14
  36. package/dist/http/index.d.ts +6 -6
  37. package/dist/http/index.js +1 -1
  38. package/dist/index.d.ts +2 -0
  39. package/dist/index.js +1 -0
  40. package/package.json +1 -1
  41. package/dist/http/grantRails.d.ts +0 -20
  42. package/dist/http/grantRails.js +0 -50
@@ -0,0 +1,88 @@
1
+ /**
2
+ * ZIG-956 — one shared definition per SDK/MCP tool capability.
3
+ *
4
+ * The tool-surface-parity epic (ZIG-893→903) unified the HTTP layer here in
5
+ * api-client but left ~25 shared capabilities hand-written twice: once in the
6
+ * agent-sdk's defineTool DSL, once in ziggs-mcp zod. Each capability below is
7
+ * the single source for the tool's schema, validation, response shaping, and
8
+ * guidance; the two surfaces register it through thin adapters
9
+ * (agent-sdk `toolFromCapability`, ziggs-mcp `registerCapability`).
10
+ *
11
+ * Params are a neutral JSON-schema-flavoured DSL rather than zod because the
12
+ * two surfaces cannot share one zod: agent-sdk is on zod v4, ziggs-mcp on
13
+ * zod v3 (pinned by the MCP SDK's ZodRawShape), and api-client deliberately
14
+ * has no zod dependency. The SDK adapter feeds params straight into
15
+ * defineTool's converter; the MCP adapter lowers them to zod v3.
16
+ */
17
+ import type { Creds } from '../types.js';
18
+ export type CapabilitySurface = 'sdk' | 'mcp';
19
+ /** read-only / write / destructive — lowered to MCP tool annotations. */
20
+ export type CapabilityAnnotation = 'read-only' | 'write' | 'destructive';
21
+ export interface CapabilityParam {
22
+ type: 'string' | 'number' | 'boolean' | 'object' | 'array';
23
+ required?: boolean;
24
+ description?: string;
25
+ /** Only for type 'string'. */
26
+ enum?: readonly string[];
27
+ /** Only for type 'array'. */
28
+ items?: {
29
+ type: 'string';
30
+ enum?: readonly string[];
31
+ } | {
32
+ type: 'object';
33
+ properties?: Record<string, unknown>;
34
+ required?: string[];
35
+ };
36
+ }
37
+ /**
38
+ * Runtime context a surface adapter hands the shared handler. `creds.agentId`
39
+ * may be absent for agent-scoped operator keys (payments rail); capabilities
40
+ * that impersonate set `needsAgentId` so the adapter fails early instead.
41
+ */
42
+ export interface CapabilityEnv {
43
+ creds: {
44
+ operatorKey: string;
45
+ agentId?: string;
46
+ };
47
+ /** SDK runner base-URL override (BACKEND_URL / ZIGGS_BACKEND_URL). */
48
+ baseUrl?: string;
49
+ /** Web-app origin for human-facing URLs (claim links etc.). */
50
+ webUrl?: string;
51
+ surface: CapabilitySurface;
52
+ }
53
+ export interface CapabilityDefinition {
54
+ /** Stable capability key (the parity-table base name), e.g. 'payment_transfer'. */
55
+ key: string;
56
+ /** Registered tool name per surface — the ZIG-903 parity table is the authority. */
57
+ names: {
58
+ sdk: string;
59
+ mcp: string;
60
+ };
61
+ /**
62
+ * Tool description per surface. Kept side by side deliberately: wording may
63
+ * reference surface-local tool names and MCP delegate-protocol guidance, but
64
+ * schema + handler can no longer drift.
65
+ */
66
+ descriptions: {
67
+ sdk: string;
68
+ mcp: string;
69
+ };
70
+ annotation: CapabilityAnnotation;
71
+ params: Record<string, CapabilityParam>;
72
+ /** True when the handler impersonates an agent (X-Agent-Id required). */
73
+ needsAgentId?: boolean;
74
+ handler: (args: Record<string, unknown>, env: CapabilityEnv) => Promise<unknown>;
75
+ /** Pass-through flags for the SDK's defineTool options. */
76
+ sdkOptions?: {
77
+ isAgreementCreation?: boolean;
78
+ isGenericFallback?: boolean;
79
+ };
80
+ }
81
+ /** Creds for impersonated calls; adapters guarantee agentId when needsAgentId. */
82
+ export declare function fullCreds(env: CapabilityEnv): Creds;
83
+ /**
84
+ * Re-throw a client error with a capability-level prefix, preserving the HTTP
85
+ * status/body the clients attach (the SDK runtime and toolError classification
86
+ * both read them).
87
+ */
88
+ export declare function rethrowWithContext(error: unknown, prefix: string): never;
@@ -0,0 +1,24 @@
1
+ /** Creds for impersonated calls; adapters guarantee agentId when needsAgentId. */
2
+ export function fullCreds(env) {
3
+ const { operatorKey, agentId } = env.creds;
4
+ if (!operatorKey)
5
+ throw new Error('operatorKey missing from tool context');
6
+ if (!agentId)
7
+ throw new Error('agentId missing from tool context');
8
+ return { operatorKey, agentId };
9
+ }
10
+ /**
11
+ * Re-throw a client error with a capability-level prefix, preserving the HTTP
12
+ * status/body the clients attach (the SDK runtime and toolError classification
13
+ * both read them).
14
+ */
15
+ export function rethrowWithContext(error, prefix) {
16
+ const e = error;
17
+ const wrapped = new Error(`${prefix}: ${e.message}`);
18
+ if (e.status !== undefined)
19
+ wrapped.status = e.status;
20
+ if (e.body !== undefined)
21
+ wrapped.body = e.body;
22
+ wrapped['cause'] = error;
23
+ throw wrapped;
24
+ }
@@ -14,6 +14,12 @@ export interface ProposeTerms {
14
14
  lifecycle?: string;
15
15
  expiresAt?: string;
16
16
  maxExecutions?: number;
17
+ /**
18
+ * How `price` reads. `total` (default) escrows one price for the whole
19
+ * engagement and pays at fulfillment; `per_task` is a RATE settled as each
20
+ * task completes (standing/open agreements only, and the default for a hire).
21
+ */
22
+ billing?: 'total' | 'per_task';
17
23
  agreementDescription?: string;
18
24
  parentAgreementId?: string;
19
25
  parentTaskId?: string;
@@ -1,4 +1,5 @@
1
1
  import 'dotenv/config';
2
+ import type { GrantView } from './grants.js';
2
3
  export declare function assertNoLeakedConnectionSecret(serialized: string): void;
3
4
  /** Thrown by ConnectionsClient with the HTTP status and raw body attached. */
4
5
  export interface ConnectionsError extends Error {
@@ -11,6 +12,23 @@ export interface ConnectionProxyParams {
11
12
  action: string;
12
13
  payload?: unknown;
13
14
  }
15
+ /** A grant row from the per-connection lister (GET /connections/:id/grants). */
16
+ export interface ConnectionGrant {
17
+ grantId?: string;
18
+ holderId?: string;
19
+ caveats?: unknown[];
20
+ [key: string]: unknown;
21
+ }
22
+ /**
23
+ * ZIG-641 / ZIG-648 — every connection grant the acting agent holds, grouped by
24
+ * connection, via the unified GET /grants. `provider` comes from the grant's
25
+ * resolved scope label.
26
+ */
27
+ export interface ConnectionWithGrants {
28
+ connectionId: string;
29
+ provider: string | null;
30
+ grants: GrantView[];
31
+ }
14
32
  export interface McpConnectionRequestParams {
15
33
  serverUrl: string;
16
34
  tools: string[];
@@ -53,7 +71,15 @@ export declare class ConnectionsClient {
53
71
  */
54
72
  listGrants({ connectionId }: {
55
73
  connectionId: string;
56
- }): Promise<unknown[]>;
74
+ }): Promise<ConnectionGrant[]>;
75
+ /**
76
+ * ZIG-641 / ZIG-956 — cross-connection discovery over the unified GET /grants:
77
+ * every live connection grant this agent holds, grouped by connection, so a
78
+ * proxy caller's connectionId/grantId no longer has to arrive out of band.
79
+ * Moved here from ziggs-mcp's inline helper. The response is scanned
80
+ * defensively for leaked secrets, as `proxy` does.
81
+ */
82
+ listForHolder(): Promise<ConnectionWithGrants[]>;
57
83
  /** Issue a connection grant to an agent holder (connection owner side). */
58
84
  issueGrant({ connectionId, holderId, caveats, }: {
59
85
  connectionId: string;
@@ -1,6 +1,7 @@
1
1
  import 'dotenv/config';
2
2
  import { getBackendUrl } from '../utils/urlUtils.js';
3
3
  import { buildOperatorHeaders } from './operatorHeaders.js';
4
+ import { GrantsClient } from './GrantsClient.js';
4
5
  // ZIG-569 — defense-in-depth mirror of the backend leak-guard
5
6
  // (assertProxyResponseDoesNotLeakTokens). The backend strips the *specific*
6
7
  // vault token from the response; clients never see that token, so this layer
@@ -82,6 +83,34 @@ export class ConnectionsClient {
82
83
  const grants = res['grants'] || [];
83
84
  return grants.filter((g) => g['holderId'] === this.agentId);
84
85
  }
86
+ /**
87
+ * ZIG-641 / ZIG-956 — cross-connection discovery over the unified GET /grants:
88
+ * every live connection grant this agent holds, grouped by connection, so a
89
+ * proxy caller's connectionId/grantId no longer has to arrive out of band.
90
+ * Moved here from ziggs-mcp's inline helper. The response is scanned
91
+ * defensively for leaked secrets, as `proxy` does.
92
+ */
93
+ async listForHolder() {
94
+ const grantsClient = new GrantsClient(this.operatorKey, this.agentId, this.baseUrl);
95
+ // All pages of the agent's live connection grants (not just the first page).
96
+ const items = await grantsClient.listAllGrants({
97
+ scopeKind: 'connection',
98
+ health: 'active',
99
+ });
100
+ const byConnection = new Map();
101
+ for (const g of items) {
102
+ const connectionId = g.scope.id;
103
+ let group = byConnection.get(connectionId);
104
+ if (!group) {
105
+ group = { connectionId, provider: g.scope.label ?? null, grants: [] };
106
+ byConnection.set(connectionId, group);
107
+ }
108
+ group.grants.push(g);
109
+ }
110
+ const result = [...byConnection.values()];
111
+ assertNoLeakedConnectionSecret(JSON.stringify(result));
112
+ return result;
113
+ }
85
114
  /** Issue a connection grant to an agent holder (connection owner side). */
86
115
  async issueGrant({ connectionId, holderId, caveats, }) {
87
116
  if (!connectionId)
@@ -49,7 +49,13 @@ export class ContextReadClient {
49
49
  const res = await fetch(url.toString(), { headers });
50
50
  const body = await res.text().catch(() => '');
51
51
  if (!res.ok) {
52
- throw new Error(`ContextReadClient.read ${type} ${res.status} ${body.slice(0, 200)}`);
52
+ // Carry the status like `snapshot()` does, so callers can branch on it.
53
+ // A 403 here is a legitimate outcome, not a transport failure: addressing
54
+ // and authorisation are separate, so an agent can be told about mail it
55
+ // is not (or is no longer) allowed to open.
56
+ const err = new Error(`ContextReadClient.read ${type} ${res.status} ${body.slice(0, 200)}`);
57
+ err.status = res.status;
58
+ throw err;
53
59
  }
54
60
  return JSON.parse(body);
55
61
  }
@@ -16,10 +16,26 @@ export interface ListGrantsQuery {
16
16
  cursor?: string;
17
17
  limit?: number;
18
18
  }
19
+ /**
20
+ * ZIG-893 / ZIG-956 — a grant rail the caller's operator key cannot read, named
21
+ * by the backend on `GET /grants` (it silently drops those rails from `items`,
22
+ * so the unified list tool names what it isn't entitled to instead of
23
+ * presenting a short list as if it were complete). Replaces the client-side
24
+ * mirror of the backend scope table + JWT decode (the retired grantRails.ts).
25
+ */
26
+ export interface UnreadableRail {
27
+ rail: 'context' | 'connection' | 'wallet';
28
+ requiredScope: string;
29
+ }
19
30
  export interface ListGrantsResult {
20
31
  items: GrantView[];
21
32
  nextCursor: string | null;
22
33
  hasMore: boolean;
34
+ /**
35
+ * Rails the caller can't read, per the backend. Absent when the backend
36
+ * predates ZIG-956 or every requested rail was readable.
37
+ */
38
+ unreadableRails?: UnreadableRail[];
23
39
  }
24
40
  /**
25
41
  * ZIG-648 — unified grant listing across every rail. `GET /grants` returns the
@@ -48,6 +48,9 @@ export class GrantsClient {
48
48
  items: parsed.items ?? [],
49
49
  nextCursor: parsed.nextCursor ?? null,
50
50
  hasMore: parsed.hasMore ?? false,
51
+ ...(parsed.unreadableRails?.length
52
+ ? { unreadableRails: parsed.unreadableRails }
53
+ : {}),
51
54
  };
52
55
  }
53
56
  /**
@@ -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,9 @@
1
1
  import 'dotenv/config';
2
2
  import { getBackendUrl } from '../utils/urlUtils.js';
3
3
  /**
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`.
4
+ * The doorbell, not the door (ZIG-434): references addressed to this agent
5
+ * since its last ack — never content. Flow: inbox → read → act → ack
6
+ * (ZIG-446). Wraps `GET /inbox` and `POST /inbox/ack`.
7
7
  */
8
8
  export class InboxClient {
9
9
  operatorKey;
@@ -34,6 +34,9 @@ export class InboxClient {
34
34
  if (opts.waitSeconds != null && opts.waitSeconds > 0) {
35
35
  url.searchParams.set('wait', String(opts.waitSeconds));
36
36
  }
37
+ if (opts.agents?.length) {
38
+ url.searchParams.set('agents', opts.agents.join(','));
39
+ }
37
40
  return url.toString();
38
41
  }
39
42
  async getInbox(opts = {}) {
@@ -47,28 +50,53 @@ export class InboxClient {
47
50
  return JSON.parse(body);
48
51
  }
49
52
  /**
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.
53
+ * Operator-level multiplexed read: which of this key owner's agents have
54
+ * mail or open work (an agent-scoped key collapses to its one agent).
55
+ *
56
+ * Returns who to start, never what they were sent — the host you start
57
+ * reads its own inbox on its own identity. Ack stays per agent.
54
58
  */
55
59
  async getOperatorInbox(opts = {}) {
56
- const res = await fetch(this.inboxUrl('/inbox/operator', opts), {
57
- headers: this.headers(),
58
- });
60
+ // Bounded wait (ZIG-965): the server holds at most `waitSeconds` (+ sweep
61
+ // time); a poll that outlives that by a wide margin is a dead sweep, and
62
+ // without a timeout it blocked the launcher's whole poll loop — lazy wake
63
+ // simply stopped. Abort and let the caller's retry loop take over.
64
+ const timeoutMs = ((opts.waitSeconds ?? 0) + 30) * 1000;
65
+ const ac = new AbortController();
66
+ const timer = setTimeout(() => ac.abort(), timeoutMs);
67
+ let res;
68
+ try {
69
+ res = await fetch(this.inboxUrl('/inbox/operator', opts), {
70
+ headers: this.headers(),
71
+ signal: ac.signal,
72
+ });
73
+ }
74
+ catch (err) {
75
+ throw ac.signal.aborted
76
+ ? new Error(`InboxClient.getOperatorInbox timed out after ${timeoutMs}ms`)
77
+ : err;
78
+ }
79
+ finally {
80
+ clearTimeout(timer);
81
+ }
59
82
  const body = await res.text().catch(() => '');
60
83
  if (!res.ok) {
61
84
  throw new Error(`InboxClient.getOperatorInbox ${res.status} ${body.slice(0, 200)}`);
62
85
  }
63
86
  return JSON.parse(body);
64
87
  }
65
- async ack(scopes) {
66
- if (!scopes.length)
67
- throw new Error('InboxClient.ack: scopes are required');
88
+ /**
89
+ * Advance this agent's watermark — pass the envelope's `ackTo`. Monotonic
90
+ * server-side: an older value is a no-op, so a replayed ack can never
91
+ * redeliver handled work.
92
+ */
93
+ async ack(upTo) {
94
+ if (!upTo)
95
+ throw new Error('InboxClient.ack: upTo is required');
68
96
  const res = await fetch(`${this.baseUrl}/inbox/ack`, {
69
97
  method: 'POST',
70
98
  headers: this.headers(),
71
- body: JSON.stringify({ scopes }),
99
+ body: JSON.stringify({ upTo }),
72
100
  });
73
101
  const body = await res.text().catch(() => '');
74
102
  if (!res.ok) {
@@ -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,36 @@
1
+ import 'dotenv/config';
2
+ import type { Creds } from '../types.js';
3
+ export interface MyOrg {
4
+ orgId: string;
5
+ name: string;
6
+ kind: string;
7
+ role?: string;
8
+ }
9
+ /**
10
+ * ZIG-739 / ZIG-956 — the operator's full org membership (not just granted
11
+ * scopes, which is all the grant listers see). Lets a delegate resolve an org
12
+ * name to an id and offer a pick-list instead of demanding a pasted org_... id.
13
+ * Moved here from ziggs-mcp so every surface rides the one client (ZIG-894).
14
+ */
15
+ export declare function fetchMyOrgs(creds: Creds, baseUrl?: string): Promise<MyOrg[]>;
16
+ export type OrgResolution = {
17
+ status: 'ok';
18
+ orgId: string;
19
+ } | {
20
+ status: 'ambiguous';
21
+ matches: MyOrg[];
22
+ } | {
23
+ status: 'not-found';
24
+ };
25
+ /**
26
+ * ZIG-739 — resolve an org selector (exact org_... id OR a name/handle) against
27
+ * the operator's memberships. Exact id wins; otherwise case-insensitive name
28
+ * match. Ambiguous names return the candidates rather than guessing.
29
+ */
30
+ export declare function resolveOrgSelector(orgs: MyOrg[], selector: string): OrgResolution;
31
+ /**
32
+ * ZIG-640 / ZIG-956 — runtime acting org from the server (self-hire / agent
33
+ * row): GET /agents/claude-delegate/access. Moved here from ziggs-mcp's inline
34
+ * fetch (ZIG-894 "one client for every surface").
35
+ */
36
+ export declare function fetchDelegateAccess(creds: Creds, baseUrl?: string): Promise<Record<string, unknown>>;