@ziggs-ai/api-client 0.9.1 → 0.9.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/dist/ConnectionManager.d.ts +2 -2
  2. package/dist/ConnectionManager.js +3 -3
  3. package/dist/capabilities/chat.js +13 -3
  4. package/dist/capabilities/payments.js +5 -0
  5. package/dist/config.d.ts +33 -0
  6. package/dist/config.js +42 -0
  7. package/dist/http/AgentSearchClient.d.ts +0 -1
  8. package/dist/http/AgentSearchClient.js +0 -1
  9. package/dist/http/AgreementClient.d.ts +0 -1
  10. package/dist/http/AgreementClient.js +3 -4
  11. package/dist/http/ArtifactsClient.d.ts +0 -1
  12. package/dist/http/ArtifactsClient.js +0 -1
  13. package/dist/http/ChatClient.d.ts +6 -3
  14. package/dist/http/ChatClient.js +5 -4
  15. package/dist/http/ConnectionsClient.d.ts +0 -1
  16. package/dist/http/ConnectionsClient.js +0 -1
  17. package/dist/http/ContextDiscoveryClient.d.ts +0 -1
  18. package/dist/http/ContextDiscoveryClient.js +0 -1
  19. package/dist/http/ContextGrantsClient.d.ts +0 -1
  20. package/dist/http/ContextGrantsClient.js +0 -1
  21. package/dist/http/ContextReadClient.d.ts +0 -1
  22. package/dist/http/ContextReadClient.js +0 -1
  23. package/dist/http/GrantsClient.d.ts +0 -1
  24. package/dist/http/GrantsClient.js +0 -1
  25. package/dist/http/InboxClient.d.ts +1 -130
  26. package/dist/http/InboxClient.js +0 -1
  27. package/dist/http/MarketplaceClient.d.ts +0 -1
  28. package/dist/http/MarketplaceClient.js +0 -1
  29. package/dist/http/MessagesClient.d.ts +0 -1
  30. package/dist/http/MessagesClient.js +0 -1
  31. package/dist/http/OrgsClient.d.ts +0 -1
  32. package/dist/http/OrgsClient.js +0 -1
  33. package/dist/http/PaymentsClient.d.ts +4 -2
  34. package/dist/http/PaymentsClient.js +24 -2
  35. package/dist/http/TaskClient.d.ts +0 -1
  36. package/dist/http/TaskClient.js +0 -1
  37. package/dist/http/TelemetryClient.d.ts +0 -1
  38. package/dist/http/TelemetryClient.js +0 -1
  39. package/dist/http/index.d.ts +0 -1
  40. package/dist/index.d.ts +3 -1
  41. package/dist/index.js +2 -0
  42. package/dist/shared/runtimeLog.d.ts +0 -6
  43. package/dist/shared/runtimeLog.js +10 -7
  44. package/dist/types.d.ts +152 -0
  45. package/dist/utils/urlUtils.js +3 -2
  46. package/package.json +1 -2
@@ -40,8 +40,8 @@ export declare class ConnectionManager {
40
40
  */
41
41
  connectAll(): Promise<void>;
42
42
  startAgent(id: string): Promise<unknown>;
43
- sleep(id: string): Promise<void>;
44
- sleepAll(): Promise<void>;
43
+ stopAgent(id: string): Promise<void>;
44
+ stopAll(): Promise<void>;
45
45
  list(): string[];
46
46
  listActive(): string[];
47
47
  get size(): number;
@@ -73,7 +73,7 @@ export class ConnectionManager {
73
73
  this._starting.set(id, startPromise);
74
74
  return startPromise;
75
75
  }
76
- async sleep(id) {
76
+ async stopAgent(id) {
77
77
  if (!this._active.has(id))
78
78
  return;
79
79
  const handle = this._active.get(id);
@@ -82,8 +82,8 @@ export class ConnectionManager {
82
82
  if (reg)
83
83
  await reg.closeFn(handle);
84
84
  }
85
- async sleepAll() {
86
- await Promise.all([...this._active.keys()].map(id => this.sleep(id)));
85
+ async stopAll() {
86
+ await Promise.all([...this._active.keys()].map(id => this.stopAgent(id)));
87
87
  }
88
88
  list() { return [...this._entries.keys()]; }
89
89
  listActive() { return [...this._active.keys()]; }
@@ -12,8 +12,8 @@ export const openConversationCapability = {
12
12
  key: 'chat_open',
13
13
  names: { sdk: 'chat_open', mcp: 'ziggs_chat_open' },
14
14
  descriptions: {
15
- sdk: 'Open or reuse a chat with a user or agent participant. To reach an agent in ANOTHER org, establish a link first — agreement_propose with engagementKind "link" (if you have its agent id) or link_create_invite (if you do not), approved/claimed — otherwise this fails with AGENT_NOT_PUBLISHED. To list chats you can already read, use grant_list scopeKind=chat.',
16
- mcp: 'Open or reuse a chat with a user or agent participant. To reach an agent in ANOTHER org, an unpublished delegate must establish a link first — ziggs_agreement_propose with engagementKind "link" (if you have its agent id) or ziggs_link_create_invite (if you do not), approved/claimed — otherwise this fails with AGENT_NOT_PUBLISHED.',
15
+ sdk: 'Open or reuse a chat with a user or agent participant. Calling it again for the same participant returns the SAME chat, so it is how you find the conversation you already have with someone — pass newChat only when this really is a separate subject. To reach an agent in ANOTHER org, establish a link first — agreement_propose with engagementKind "link" (if you have its agent id) or link_create_invite (if you do not), approved/claimed — otherwise this fails with AGENT_NOT_PUBLISHED. To list chats you can already read, use grant_list scopeKind=chat.',
16
+ mcp: 'Open or reuse a chat with a user or agent participant. Calling it again for the same participant returns the SAME chat; pass newChat only when this really is a separate subject. To reach an agent in ANOTHER org, an unpublished delegate must establish a link first — ziggs_agreement_propose with engagementKind "link" (if you have its agent id) or ziggs_link_create_invite (if you do not), approved/claimed — otherwise this fails with AGENT_NOT_PUBLISHED.',
17
17
  },
18
18
  annotation: 'write',
19
19
  params: {
@@ -22,12 +22,22 @@ export const openConversationCapability = {
22
22
  required: true,
23
23
  description: 'User or agent id to converse with (from agent search — do not guess ids)',
24
24
  },
25
+ newChat: {
26
+ type: 'boolean',
27
+ description: 'Open a separate chat even though one is already open with this participant. ' +
28
+ 'For when the conversation is genuinely its own subject and would confuse an ' +
29
+ 'existing thread. The separate chat does not become the main one, so a later ' +
30
+ 'call without this still returns the original. Leave unset to continue where ' +
31
+ 'you left off.',
32
+ },
25
33
  },
26
34
  needsAgentId: true,
27
35
  handler: async (args, env) => {
28
36
  if (!args['participantId'])
29
37
  throw new Error('participantId is required');
30
- const { chatId } = await openConversation(args['participantId'], fullCreds(env));
38
+ const { chatId } = await openConversation(args['participantId'], fullCreds(env), {
39
+ newChat: args['newChat'] === true,
40
+ });
31
41
  const lister = env.surface === 'mcp' ? 'ziggs_grant_list' : 'grant_list';
32
42
  return {
33
43
  chatId,
@@ -216,6 +216,10 @@ export const paymentHoldCapability = {
216
216
  amount: { type: 'number', required: true, description: 'Amount in integer cents, > 0' },
217
217
  description: { type: 'string', description: 'Human-readable hold memo' },
218
218
  idempotencyKey: { type: 'string', description: 'Client-supplied retry-safety key' },
219
+ paymentGrantId: {
220
+ type: 'string',
221
+ description: 'Payment grant authorizing this hold (required for agent actors; auto-picks an active wallet grant when omitted)',
222
+ },
219
223
  },
220
224
  handler: async (args, env) => {
221
225
  const amount = args['amount'];
@@ -226,6 +230,7 @@ export const paymentHoldCapability = {
226
230
  amount: Math.round(amount),
227
231
  description: args['description'] || 'Agent escrow hold',
228
232
  idempotencyKey: args['idempotencyKey'],
233
+ paymentGrantId: args['paymentGrantId'],
229
234
  });
230
235
  return {
231
236
  status: 'held',
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Runtime configuration for this package, injected by whoever owns the process.
3
+ *
4
+ * This package used to read `process.env` directly, which is why every http
5
+ * client began with `import 'dotenv/config'`. Metro (React Native's bundler)
6
+ * cannot resolve dotenv, so a mobile consumer could not import api-client at
7
+ * all. The environment is now read by the host — `env-init.ts` in this repo,
8
+ * the MCP server's config loader, a mobile app's bootstrap — and handed here.
9
+ *
10
+ * Configure before constructing clients: `getBackendUrl()` is called in their
11
+ * constructors, so a client built first keeps the URL that was current then.
12
+ */
13
+ /** Threshold names accepted by {@link ApiClientConfig.logLevel}. */
14
+ export type ApiClientLogLevel = 'debug' | 'trace' | 'info' | 'warn' | 'error' | 'silent' | 'none';
15
+ export interface ApiClientConfig {
16
+ /** Backend base URL. Falls back to production when nothing sets it. */
17
+ httpUrl?: string;
18
+ /** WebSocket base URL. Falls back to production when nothing sets it. */
19
+ wsUrl?: string;
20
+ /** `runtimeLog` threshold. Falls back to `info`. */
21
+ logLevel?: ApiClientLogLevel;
22
+ }
23
+ /**
24
+ * Merge settings into the package config. Partial: passing only `logLevel`
25
+ * leaves a previously configured `httpUrl` alone. Explicit `undefined` is
26
+ * ignored rather than treated as a clear, so a host can spread a half-filled
27
+ * options object without wiping what it already set.
28
+ */
29
+ export declare function configureApiClient(next: ApiClientConfig): void;
30
+ /** Current config. Read-only — mutate through {@link configureApiClient}. */
31
+ export declare function apiClientConfig(): Readonly<ApiClientConfig>;
32
+ /** Generation counter for cache invalidation. */
33
+ export declare function apiClientConfigVersion(): number;
package/dist/config.js ADDED
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Runtime configuration for this package, injected by whoever owns the process.
3
+ *
4
+ * This package used to read `process.env` directly, which is why every http
5
+ * client began with `import 'dotenv/config'`. Metro (React Native's bundler)
6
+ * cannot resolve dotenv, so a mobile consumer could not import api-client at
7
+ * all. The environment is now read by the host — `env-init.ts` in this repo,
8
+ * the MCP server's config loader, a mobile app's bootstrap — and handed here.
9
+ *
10
+ * Configure before constructing clients: `getBackendUrl()` is called in their
11
+ * constructors, so a client built first keeps the URL that was current then.
12
+ */
13
+ const config = {};
14
+ /**
15
+ * Bumped on every configure so derived caches (the log threshold) know to
16
+ * recompute. A version counter rather than a reset callback because
17
+ * `runtimeLog` reads config — a callback the other way would be a cycle.
18
+ */
19
+ let version = 0;
20
+ /**
21
+ * Merge settings into the package config. Partial: passing only `logLevel`
22
+ * leaves a previously configured `httpUrl` alone. Explicit `undefined` is
23
+ * ignored rather than treated as a clear, so a host can spread a half-filled
24
+ * options object without wiping what it already set.
25
+ */
26
+ export function configureApiClient(next) {
27
+ if (next.httpUrl !== undefined)
28
+ config.httpUrl = next.httpUrl;
29
+ if (next.wsUrl !== undefined)
30
+ config.wsUrl = next.wsUrl;
31
+ if (next.logLevel !== undefined)
32
+ config.logLevel = next.logLevel;
33
+ version += 1;
34
+ }
35
+ /** Current config. Read-only — mutate through {@link configureApiClient}. */
36
+ export function apiClientConfig() {
37
+ return config;
38
+ }
39
+ /** Generation counter for cache invalidation. */
40
+ export function apiClientConfigVersion() {
41
+ return version;
42
+ }
@@ -1,4 +1,3 @@
1
- import 'dotenv/config';
2
1
  export interface AgentSearchOptions {
3
2
  limit?: number;
4
3
  minScore?: number;
@@ -1,4 +1,3 @@
1
- import 'dotenv/config';
2
1
  import { runtimeLog } from '../shared/runtimeLog.js';
3
2
  import { getBackendUrl } from '../utils/urlUtils.js';
4
3
  import { buildOperatorHeaders } from './operatorHeaders.js';
@@ -1,4 +1,3 @@
1
- import 'dotenv/config';
2
1
  import { type Creds, type Agreement, type EngagementKind, type BroadcastAudience } from '../types.js';
3
2
  /**
4
3
  * Shared proposal terms. When `engagementKind` is omitted the server defaults to `service`.
@@ -1,11 +1,10 @@
1
- import 'dotenv/config';
2
1
  import { runtimeLog } from '../shared/runtimeLog.js';
3
2
  import { getBackendUrl } from '../utils/urlUtils.js';
4
3
  import { OPEN_AGREEMENT_TARGET, ORG_AGREEMENT_TARGET, isBroadcastTarget } from '../types.js';
5
4
  import { throwApiError } from '../shared/apiError.js';
6
- // Lazy: read at call time so dotenv loaded after this module is imported
7
- // still takes effect. Baking it at module-load time would freeze the URL
8
- // before the caller's dotenv.config() runs.
5
+ // Lazy: read at call time so a `configureApiClient` call that lands after this
6
+ // module is imported still takes effect. Baking it at module-load time would
7
+ // freeze the URL before the host has configured one.
9
8
  function getAgreementBaseUrl() {
10
9
  return `${getBackendUrl()}/agreements`;
11
10
  }
@@ -1,4 +1,3 @@
1
- import 'dotenv/config';
2
1
  export type ArtifactVisibility = 'chat' | 'agent-private';
3
2
  export interface ListArtifactsOptions {
4
3
  after?: string;
@@ -1,4 +1,3 @@
1
- import 'dotenv/config';
2
1
  import { runtimeLog } from '../shared/runtimeLog.js';
3
2
  import { throwApiError } from '../shared/apiError.js';
4
3
  import { getBackendUrl } from '../utils/urlUtils.js';
@@ -1,4 +1,3 @@
1
- import 'dotenv/config';
2
1
  import { type Creds } from '../types.js';
3
2
  /** Shape of GET /chats/mine items — the backend's ChatReadDto (ZIG-665). */
4
3
  export interface ChatSummary {
@@ -10,7 +9,9 @@ export interface ChatSummary {
10
9
  orgId: string | null;
11
10
  isOrgChat: boolean;
12
11
  }
13
- export declare function openConversation(participantId: string, creds: Creds): Promise<{
12
+ export declare function openConversation(participantId: string, creds: Creds, { newChat }?: {
13
+ newChat?: boolean;
14
+ }): Promise<{
14
15
  chatId: string;
15
16
  }>;
16
17
  export interface SendChatMessageInput {
@@ -75,7 +76,9 @@ export declare class ChatClient {
75
76
  * @param agentId Required for fleet keys (sent as X-Agent-Id). Omit for agent-scoped keys.
76
77
  */
77
78
  constructor(operatorKey: string, agentId?: string);
78
- open(participantId: string): Promise<{
79
+ open(participantId: string, opts?: {
80
+ newChat?: boolean;
81
+ }): Promise<{
79
82
  chatId: string;
80
83
  }>;
81
84
  addMember(input: AddChatMemberInput): Promise<AddChatMemberResult>;
@@ -1,4 +1,3 @@
1
- import 'dotenv/config';
2
1
  import { runtimeLog } from '../shared/runtimeLog.js';
3
2
  import { getBackendUrl } from '../utils/urlUtils.js';
4
3
  import { throwApiError } from '../shared/apiError.js';
@@ -18,14 +17,16 @@ function assertCreds(creds, op) {
18
17
  if (!creds?.agentId)
19
18
  throw new Error(`agentId is required for ${op}`);
20
19
  }
21
- export async function openConversation(participantId, creds) {
20
+ export async function openConversation(participantId, creds, { newChat = false } = {}) {
22
21
  if (!participantId)
23
22
  throw new Error('participantId is required for openConversation');
24
23
  assertCreds(creds, 'open conversation');
25
24
  const res = await fetch(`${getBackendUrl()}/chats`, {
26
25
  method: 'POST',
27
26
  headers: buildHeaders(creds),
28
- body: JSON.stringify({ participantId }),
27
+ // Only sent when asked for: an older backend ignores the field, so a
28
+ // caller that never wants a separate room behaves identically either way.
29
+ body: JSON.stringify({ participantId, ...(newChat ? { newChat: true } : {}) }),
29
30
  });
30
31
  if (!res.ok) {
31
32
  const body = await res.text().catch(() => '');
@@ -137,7 +138,7 @@ export class ChatClient {
137
138
  // standalone functions still assert it per call.
138
139
  this.creds = { operatorKey, agentId };
139
140
  }
140
- open(participantId) { return openConversation(participantId, this.creds); }
141
+ open(participantId, opts) { return openConversation(participantId, this.creds, opts); }
141
142
  addMember(input) { return addChatMember(input, this.creds); }
142
143
  sendMessage(input) { return sendChatMessage(input, this.creds); }
143
144
  listMine() { return listMyChats(this.creds); }
@@ -1,4 +1,3 @@
1
- import 'dotenv/config';
2
1
  import type { GrantView } from './grants.js';
3
2
  export declare function assertNoLeakedConnectionSecret(serialized: string): void;
4
3
  /** Thrown by ConnectionsClient with the HTTP status and raw body attached. */
@@ -1,4 +1,3 @@
1
- import 'dotenv/config';
2
1
  import { getBackendUrl } from '../utils/urlUtils.js';
3
2
  import { throwApiError } from '../shared/apiError.js';
4
3
  import { buildOperatorHeaders } from './operatorHeaders.js';
@@ -1,4 +1,3 @@
1
- import 'dotenv/config';
2
1
  /**
3
2
  * Labels-only pointer to context the agent could request access to (P4).
4
3
  * ZIG-927: covers chats, agreements, and connections; connection labels are the
@@ -1,4 +1,3 @@
1
- import 'dotenv/config';
2
1
  import { getBackendUrl } from '../utils/urlUtils.js';
3
2
  import { throwApiError } from '../shared/apiError.js';
4
3
  import { buildOperatorHeaders } from './operatorHeaders.js';
@@ -1,4 +1,3 @@
1
- import 'dotenv/config';
2
1
  import type { GrantView } from './grants.js';
3
2
  /**
4
3
  * ZIG-1037 added `artifact` — the narrowest context scope: one specific artifact,
@@ -1,4 +1,3 @@
1
- import 'dotenv/config';
2
1
  import { getBackendUrl } from '../utils/urlUtils.js';
3
2
  import { buildOperatorHeaders } from './operatorHeaders.js';
4
3
  import { throwApiError } from '../shared/apiError.js';
@@ -1,4 +1,3 @@
1
- import 'dotenv/config';
2
1
  export declare const CONTEXT_READ_TYPES: readonly ["messages", "artifacts", "agreements", "tasks"];
3
2
  export type ContextReadType = (typeof CONTEXT_READ_TYPES)[number];
4
3
  /**
@@ -1,4 +1,3 @@
1
- import 'dotenv/config';
2
1
  import { getBackendUrl } from '../utils/urlUtils.js';
3
2
  import { pollSurfaceError } from '../shared/rateLimit.js';
4
3
  export const CONTEXT_READ_TYPES = [
@@ -1,4 +1,3 @@
1
- import 'dotenv/config';
2
1
  import type { GrantView, GrantScopeKind, GrantHealth } from './grants.js';
3
2
  export interface ListGrantsQuery {
4
3
  /**
@@ -1,4 +1,3 @@
1
- import 'dotenv/config';
2
1
  import { getBackendUrl } from '../utils/urlUtils.js';
3
2
  import { throwApiError } from '../shared/apiError.js';
4
3
  import { buildOperatorHeaders } from './operatorHeaders.js';
@@ -1,133 +1,4 @@
1
- import 'dotenv/config';
2
- /**
3
- * What a delivery can be about. A closed union, not a comment: a consumer that
4
- * dispatches on `kind` (the MCP read plan) is only safe if the compiler can tell
5
- * it a case is missing. The task-only deliverable that was acked unread got
6
- * through precisely because this was `string`.
7
- *
8
- * A type and not a value list, unlike the backend's `RESOURCE_KINDS` — nothing
9
- * on this side validates a delivery kind at runtime (the server does that on the
10
- * way in), and exhaustiveness checking is purely type-level.
11
- */
12
- export type InboxDeliveryKind = 'message' | 'artifact' | 'task-state' | 'agreement';
13
- /**
14
- * One thing addressed to this agent. A reference, never content — following it
15
- * (a chat read, a task read) is where this agent's grants are enforced.
16
- */
17
- export interface InboxDeliveryRef {
18
- kind: InboxDeliveryKind;
19
- resourceId: string;
20
- chatId: string | null;
21
- agreementId: string | null;
22
- taskId: string | null;
23
- /** Who wrote it. Never this agent — you are not woken by your own writes. */
24
- actorId: string | null;
25
- ts: string;
26
- }
27
- /** Message/artifact deliveries folded by chat, so you can open chats directly. */
28
- export interface InboxChatNews {
29
- chatId: string;
30
- count: number;
31
- latestAt: string;
32
- }
33
- export interface InboxProposalRef {
34
- agreementId: string;
35
- title: string;
36
- proposedAt: string | null;
37
- /**
38
- * ZIG-1087 — party ids still owing a decision, and the named responder slot.
39
- * The inbox lists proposals awaiting the agent OR its human, and only the
40
- * agent's own slot is one it can submit; these say which is which.
41
- *
42
- * Optional because a backend deployed before ZIG-1087 omits them, and this
43
- * client is installed independently of the server it talks to. Absent reads
44
- * as "no slot of mine", which routes the decision to the human — the safe
45
- * direction: it withholds a call, it never invents authority.
46
- */
47
- pendingApprovalPartyIds?: string[];
48
- proposedTo?: string | null;
49
- }
50
- export interface InboxConnectionRequestRef {
51
- requestId: string;
52
- /**
53
- * Non-addressable persona reference for the requester (`psn_*`).
54
- * Never use as an account id for lookup / wake / pay (ZIG-1137).
55
- */
56
- requesterRef: string;
57
- /** ZIG-1039 — human-readable name for consent cards. */
58
- requesterDisplayName?: string | null;
59
- /** ZIG-1039 — org label for consent cards. */
60
- requesterOrgName?: string | null;
61
- message: string | null;
62
- requestedAt: string | null;
63
- /** ZIG-1087 — see InboxProposalRef; a link request uses the same gate. */
64
- pendingApprovalPartyIds?: string[];
65
- proposedTo?: string | null;
66
- }
67
- /** Pull-only MCP: prompt the human when proposals need a decision (ZIG-482 / ZIG-481). */
68
- export interface InboxHumanAttention {
69
- required: true;
70
- reason: 'proposals_awaiting_me' | 'connection_requests_awaiting_me' | 'multiple';
71
- proposalCount: number;
72
- truncatedProposals: number;
73
- connectionRequestCount: number;
74
- truncatedConnectionRequests: number;
75
- promptUser: string;
76
- }
77
- /**
78
- * An open task assigned to this agent (ZIG-973). References only — read the
79
- * task for its description/plan/inputs.
80
- *
81
- * Tasks ride their own channel because assignment IS their delivery: before
82
- * this, a task wake was a synthetic chat row, so work under a chat-less
83
- * agreement (or self-assigned) reached nobody.
84
- */
85
- export interface InboxTaskRef {
86
- taskId: string;
87
- agreementId: string | null;
88
- title: string;
89
- state: string;
90
- updatedAt: string | null;
91
- }
92
- export interface InboxEnvelope {
93
- asOf: string;
94
- /**
95
- * Unacked deliveries addressed to this agent, newest first. This IS the
96
- * inbox — read straight out of the delivery log, not derived from grants.
97
- */
98
- deliveries: InboxDeliveryRef[];
99
- /** True when there was more than one envelope's worth; the rest stay unacked. */
100
- deliveriesCapped: boolean;
101
- /** The chat-bearing deliveries above, folded by chat. */
102
- chats: InboxChatNews[];
103
- /**
104
- * Pass to `ack()` after acting. Null when there is nothing to ack. Ack after
105
- * acting, not after reading: a crash in between redelivers.
106
- */
107
- ackTo: string | null;
108
- /** Open tasks assigned to this agent — the work channel (ZIG-973). */
109
- tasksAwaitingMe: InboxTaskRef[];
110
- truncatedTasks: number;
111
- proposalsAwaitingMe: InboxProposalRef[];
112
- truncatedProposals: number;
113
- connectionRequestsAwaitingMe: InboxConnectionRequestRef[];
114
- truncatedConnectionRequests: number;
115
- humanAttention?: InboxHumanAttention;
116
- }
117
- export interface InboxAckResult {
118
- /** Where the watermark now sits. Monotonic — a rewind is a no-op, not an error. */
119
- ackedUpTo: string | null;
120
- }
121
- /**
122
- * Long-poll option shared by the inbox reads. The server holds the request up
123
- * to this many seconds (server-clamped, ~25s ceiling) and returns as soon as
124
- * anything actionable exists. Omit for an immediate snapshot — the response
125
- * shape is identical either way, so `wait` only changes how long an EMPTY
126
- * answer is withheld.
127
- */
128
- export interface InboxReadOptions {
129
- waitSeconds?: number;
130
- }
1
+ import type { InboxAckResult, InboxEnvelope, InboxReadOptions } from '../types.js';
131
2
  /**
132
3
  * The doorbell, not the door (ZIG-434): references addressed to this agent
133
4
  * since its last ack — never content. Flow: inbox → read → act → ack
@@ -1,4 +1,3 @@
1
- import 'dotenv/config';
2
1
  import { getBackendUrl } from '../utils/urlUtils.js';
3
2
  import { pollSurfaceError } from '../shared/rateLimit.js';
4
3
  /**
@@ -1,4 +1,3 @@
1
- import 'dotenv/config';
2
1
  import { type Creds, type Agreement, type BroadcastAudience } from '../types.js';
3
2
  export interface PublishOfferPayload {
4
3
  description: string;
@@ -1,4 +1,3 @@
1
- import 'dotenv/config';
2
1
  // ZIG-1111: one shaping rule for every agreement this package parses.
3
2
  import { shapeAgreement } from './AgreementClient.js';
4
3
  import { getBackendUrl } from '../utils/urlUtils.js';
@@ -1,4 +1,3 @@
1
- import 'dotenv/config';
2
1
  export interface ListMessagesOptions {
3
2
  /** ISO timestamp; only entries with `timestamp > after` are returned. */
4
3
  after?: string;
@@ -1,4 +1,3 @@
1
- import 'dotenv/config';
2
1
  import { getBackendUrl } from '../utils/urlUtils.js';
3
2
  import { throwApiError } from '../shared/apiError.js';
4
3
  /**
@@ -1,4 +1,3 @@
1
- import 'dotenv/config';
2
1
  import type { Creds } from '../types.js';
3
2
  export interface MyOrg {
4
3
  orgId: string;
@@ -1,4 +1,3 @@
1
- import 'dotenv/config';
2
1
  import { getBackendUrl } from '../utils/urlUtils.js';
3
2
  import { throwApiError } from '../shared/apiError.js';
4
3
  import { buildOperatorHeaders } from './operatorHeaders.js';
@@ -1,4 +1,3 @@
1
- import 'dotenv/config';
2
1
  import type { GrantView } from './grants.js';
3
2
  /** Thrown by PaymentsClient with the HTTP status and raw body attached. */
4
3
  export interface PaymentsError extends Error {
@@ -101,11 +100,14 @@ export declare class PaymentsClient {
101
100
  description?: string;
102
101
  paymentGrantId?: string;
103
102
  }): Promise<TransferResult>;
104
- hold({ amount, idempotencyKey, description, }: {
103
+ hold({ amount, idempotencyKey, description, paymentGrantId, }: {
105
104
  amount: number;
106
105
  idempotencyKey?: string;
107
106
  description?: string;
107
+ paymentGrantId?: string;
108
108
  }): Promise<HoldResult>;
109
+ /** Prefer a single active wallet grant the agent already holds (ZIG-1182). */
110
+ private resolveAgentPaymentGrantId;
109
111
  release({ holdId, action, toWalletId, idempotencyKey, }: {
110
112
  holdId: string;
111
113
  action?: string;
@@ -1,4 +1,3 @@
1
- import 'dotenv/config';
2
1
  import { getBackendUrl } from '../utils/urlUtils.js';
3
2
  import { buildOperatorHeaders } from './operatorHeaders.js';
4
3
  import { GrantsClient } from './GrantsClient.js';
@@ -51,6 +50,11 @@ export class PaymentsClient {
51
50
  throw new Error('transfer: `to` is required');
52
51
  if (!(Number.isInteger(amount) && amount > 0))
53
52
  throw new Error('transfer: `amount` must be a positive integer (cents)');
53
+ // ZIG-1182 — resolve a standing/active grant when the caller omitted one
54
+ // (Claude/MCP ensureForUser mints it). Explicit id still wins.
55
+ if (this.agentId && !paymentGrantId) {
56
+ paymentGrantId = (await this.resolveAgentPaymentGrantId()) ?? undefined;
57
+ }
54
58
  if (this.agentId && !paymentGrantId)
55
59
  throw new Error('transfer: paymentGrantId is required for agent-impersonated transfers. The wallet owner must have issued a payment grant to this agentId.');
56
60
  let toWalletId = to;
@@ -86,15 +90,33 @@ export class PaymentsClient {
86
90
  amount,
87
91
  };
88
92
  }
89
- async hold({ amount, idempotencyKey, description, }) {
93
+ async hold({ amount, idempotencyKey, description, paymentGrantId, }) {
90
94
  if (!(Number.isInteger(amount) && amount > 0))
91
95
  throw new Error('hold: `amount` must be a positive integer (cents)');
96
+ // ZIG-1182 — agent holds reserve the owner's wallet; require a grant.
97
+ if (this.agentId && !paymentGrantId) {
98
+ paymentGrantId = (await this.resolveAgentPaymentGrantId()) ?? undefined;
99
+ }
100
+ if (this.agentId && !paymentGrantId)
101
+ throw new Error('hold: paymentGrantId is required for agent-impersonated holds. The wallet owner must have issued a payment grant to this agentId.');
92
102
  return (await this._post('/payments/hold', {
93
103
  amount,
94
104
  idempotencyKey: idempotencyKey || randomIdempotencyKey('hold'),
95
105
  description,
106
+ paymentGrantId,
96
107
  }));
97
108
  }
109
+ /** Prefer a single active wallet grant the agent already holds (ZIG-1182). */
110
+ async resolveAgentPaymentGrantId() {
111
+ try {
112
+ const grants = await this.listGrants();
113
+ const id = grants[0]?.grantId;
114
+ return typeof id === 'string' && id.length > 0 ? id : null;
115
+ }
116
+ catch {
117
+ return null;
118
+ }
119
+ }
98
120
  async release({ holdId, action = 'complete', toWalletId, idempotencyKey, }) {
99
121
  return (await this._post(`/payments/release/${holdId}`, {
100
122
  idempotencyKey: idempotencyKey || randomIdempotencyKey('rel'),
@@ -1,4 +1,3 @@
1
- import 'dotenv/config';
2
1
  import { type Creds, type Task, type TaskState } from '../types.js';
3
2
  /**
4
3
  * When the buyer reviews a task's plan. Task-rail only — an agreement has no
@@ -1,4 +1,3 @@
1
- import 'dotenv/config';
2
1
  import { getBackendUrl } from '../utils/urlUtils.js';
3
2
  import { throwApiError } from '../shared/apiError.js';
4
3
  function getTaskBaseUrl() { return `${getBackendUrl()}/tasks`; }
@@ -1,4 +1,3 @@
1
- import 'dotenv/config';
2
1
  export declare class TelemetryClient {
3
2
  private readonly operatorKey;
4
3
  private readonly agentId?;
@@ -1,4 +1,3 @@
1
- import 'dotenv/config';
2
1
  import { runtimeLog } from '../shared/runtimeLog.js';
3
2
  import { getBackendUrl } from '../utils/urlUtils.js';
4
3
  import { buildOperatorHeaders } from './operatorHeaders.js';
@@ -26,4 +26,3 @@ 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, } from './InboxClient.js';
package/dist/index.d.ts CHANGED
@@ -5,9 +5,11 @@ export { ConnectionManager } from './ConnectionManager.js';
5
5
  export { EntryTypes, ContentTypes, OPEN_AGREEMENT_TARGET, ORG_AGREEMENT_TARGET, BROADCAST_TARGETS, isBroadcastTarget, isPersonaRef, isRoomPresentationRef, isOpaquePresentationRef, AGREEMENT_ENGAGEMENT_KIND, isValidContentType, } from './types.js';
6
6
  export type { PrincipalPresentation } from './types.js';
7
7
  export { getBackendUrl, getWebSocketUrl } from './utils/urlUtils.js';
8
+ export { configureApiClient, apiClientConfig } from './config.js';
9
+ export type { ApiClientConfig, ApiClientLogLevel } from './config.js';
8
10
  export { runtimeLog, resetRuntimeLogLevelCache } from './shared/runtimeLog.js';
9
11
  export { RateLimitedError, isRateLimited, parseRetryAfterMs, pollSurfaceError, } from './shared/rateLimit.js';
10
12
  export { parseErrorMessage, parseErrorCode, throwApiError, } from './shared/apiError.js';
11
13
  export { ApiError } from './types.js';
12
- export type { Creds, Task, TaskState, PlanStep, PlanStepStatus, Agreement, EngagementKind, BroadcastAudience, EntryType, ContentType, MessageMetadata, MessageHandler, } from './types.js';
14
+ export type { Creds, Task, TaskState, PlanStep, PlanStepStatus, Agreement, EngagementKind, BroadcastAudience, EntryType, ContentType, MessageMetadata, MessageHandler, InboxDeliveryKind, InboxDeliveryRef, InboxChatNews, InboxProposalRef, InboxTaskRef, InboxQuestRef, InboxConnectionRequestRef, InboxHumanAttention, InboxEnvelope, InboxAckResult, InboxReadOptions, } from './types.js';
13
15
  export type { ProposeTerms, ProposeDirectInput, ProposeBroadcastInput, ProposeAgreementData, } from './http/AgreementClient.js';
package/dist/index.js CHANGED
@@ -4,6 +4,8 @@ export * from './relay/provisionRelayWorkers.js';
4
4
  export { ConnectionManager } from './ConnectionManager.js';
5
5
  export { EntryTypes, ContentTypes, OPEN_AGREEMENT_TARGET, ORG_AGREEMENT_TARGET, BROADCAST_TARGETS, isBroadcastTarget, isPersonaRef, isRoomPresentationRef, isOpaquePresentationRef, AGREEMENT_ENGAGEMENT_KIND, isValidContentType, } from './types.js';
6
6
  export { getBackendUrl, getWebSocketUrl } from './utils/urlUtils.js';
7
+ // ZIG-652: the host injects the environment; this package never reads it.
8
+ export { configureApiClient, apiClientConfig } from './config.js';
7
9
  export { runtimeLog, resetRuntimeLogLevelCache } from './shared/runtimeLog.js';
8
10
  // ZIG-1019 / ZIG-1124: one ApiError shape; 429s carry the server's wait.
9
11
  export { RateLimitedError, isRateLimited, parseRetryAfterMs, pollSurfaceError, } from './shared/rateLimit.js';
@@ -1,9 +1,3 @@
1
- /**
2
- * Same semantics as `@ziggs-ai/agent-sdk` `shared/runtimeLog.ts` (duplicated
3
- * here so this package stays dependency-free).
4
- *
5
- * @see agent-sdk/src/shared/runtimeLog.ts
6
- */
7
1
  export type RuntimeLogLevel = 'debug' | 'info' | 'warn' | 'error';
8
2
  export declare function resetRuntimeLogLevelCache(): void;
9
3
  export declare const runtimeLog: {
@@ -4,6 +4,7 @@
4
4
  *
5
5
  * @see agent-sdk/src/shared/runtimeLog.ts
6
6
  */
7
+ import { apiClientConfig, apiClientConfigVersion } from '../config.js';
7
8
  const SEVERITY = {
8
9
  debug: 0,
9
10
  info: 1,
@@ -11,12 +12,7 @@ const SEVERITY = {
11
12
  error: 3,
12
13
  };
13
14
  function parseThreshold() {
14
- if (process.env.DEBUG_AGENTPLUS === '1' || process.env.AGENTPLUS_DEBUG === '1') {
15
- return SEVERITY.debug;
16
- }
17
- const raw = (process.env.LOG_LEVEL ||
18
- process.env.AGENTPLUS_LOG_LEVEL ||
19
- 'info').toLowerCase();
15
+ const raw = (apiClientConfig().logLevel || 'info').toLowerCase();
20
16
  if (raw === 'silent' || raw === 'none')
21
17
  return SEVERITY.error;
22
18
  if (raw === 'debug' || raw === 'trace')
@@ -28,13 +24,20 @@ function parseThreshold() {
28
24
  return SEVERITY.info;
29
25
  }
30
26
  let cachedThreshold = null;
27
+ let cachedVersion = -1;
31
28
  function threshold() {
32
- if (cachedThreshold === null)
29
+ // Recompute when the host reconfigures, so a `configureApiClient` call after
30
+ // the first log line still takes effect.
31
+ const v = apiClientConfigVersion();
32
+ if (cachedThreshold === null || cachedVersion !== v) {
33
33
  cachedThreshold = parseThreshold();
34
+ cachedVersion = v;
35
+ }
34
36
  return cachedThreshold;
35
37
  }
36
38
  export function resetRuntimeLogLevelCache() {
37
39
  cachedThreshold = null;
40
+ cachedVersion = -1;
38
41
  }
39
42
  function shouldEmit(level) {
40
43
  return SEVERITY[level] >= threshold();
package/dist/types.d.ts CHANGED
@@ -269,3 +269,155 @@ export interface MessageMetadata {
269
269
  sentTimestamp?: string;
270
270
  }
271
271
  export type MessageHandler = (text: string, metadata: MessageMetadata) => Promise<void>;
272
+ /**
273
+ * What a delivery can be about. A closed union, not a comment: a consumer that
274
+ * dispatches on `kind` (the MCP read plan) is only safe if the compiler can tell
275
+ * it a case is missing. The task-only deliverable that was acked unread got
276
+ * through precisely because this was `string`.
277
+ *
278
+ * A type and not a value list, unlike the backend's `RESOURCE_KINDS` — nothing
279
+ * on this side validates a delivery kind at runtime (the server does that on the
280
+ * way in), and exhaustiveness checking is purely type-level.
281
+ */
282
+ export type InboxDeliveryKind = 'message' | 'artifact' | 'task-state' | 'agreement' | 'quest';
283
+ /**
284
+ * One thing addressed to this agent. A reference, never content — following it
285
+ * (a chat read, a task read) is where this agent's grants are enforced.
286
+ */
287
+ export interface InboxDeliveryRef {
288
+ kind: InboxDeliveryKind;
289
+ resourceId: string;
290
+ chatId: string | null;
291
+ agreementId: string | null;
292
+ taskId: string | null;
293
+ /** Who wrote it. Never this agent — you are not woken by your own writes. */
294
+ actorId: string | null;
295
+ ts: string;
296
+ /**
297
+ * ZIG-703 — lifecycle hint when the server includes one (e.g.
298
+ * `connection_request_fulfilled`). Absent on ordinary doorbells / older servers.
299
+ */
300
+ reason?: string | null;
301
+ /** ZIG-703 — MCP connection after a fulfilled first-hop request. */
302
+ connectionId?: string | null;
303
+ /** ZIG-703 — grant minted for this agent on fulfill. */
304
+ grantId?: string | null;
305
+ }
306
+ /** Message/artifact deliveries folded by chat, so you can open chats directly. */
307
+ export interface InboxChatNews {
308
+ chatId: string;
309
+ count: number;
310
+ latestAt: string;
311
+ }
312
+ export interface InboxProposalRef {
313
+ agreementId: string;
314
+ title: string;
315
+ proposedAt: string | null;
316
+ /**
317
+ * ZIG-1087 — party ids still owing a decision, and the named responder slot.
318
+ * The inbox lists proposals awaiting the agent OR its human, and only the
319
+ * agent's own slot is one it can submit; these say which is which.
320
+ *
321
+ * Optional because a backend deployed before ZIG-1087 omits them, and this
322
+ * client is installed independently of the server it talks to. Absent reads
323
+ * as "no slot of mine", which routes the decision to the human — the safe
324
+ * direction: it withholds a call, it never invents authority.
325
+ */
326
+ pendingApprovalPartyIds?: string[];
327
+ proposedTo?: string | null;
328
+ }
329
+ export interface InboxConnectionRequestRef {
330
+ requestId: string;
331
+ /**
332
+ * Non-addressable persona reference for the requester (`psn_*`).
333
+ * Never use as an account id for lookup / wake / pay (ZIG-1137).
334
+ */
335
+ requesterRef: string;
336
+ /** ZIG-1039 — human-readable name for consent cards. */
337
+ requesterDisplayName?: string | null;
338
+ /** ZIG-1039 — org label for consent cards. */
339
+ requesterOrgName?: string | null;
340
+ message: string | null;
341
+ requestedAt: string | null;
342
+ /** ZIG-1087 — see InboxProposalRef; a link request uses the same gate. */
343
+ pendingApprovalPartyIds?: string[];
344
+ proposedTo?: string | null;
345
+ }
346
+ /** Pull-only MCP: prompt the human when proposals need a decision (ZIG-482 / ZIG-481). */
347
+ export interface InboxHumanAttention {
348
+ required: true;
349
+ reason: 'proposals_awaiting_me' | 'connection_requests_awaiting_me' | 'multiple';
350
+ proposalCount: number;
351
+ truncatedProposals: number;
352
+ connectionRequestCount: number;
353
+ truncatedConnectionRequests: number;
354
+ promptUser: string;
355
+ }
356
+ /**
357
+ * An open task assigned to this agent (ZIG-973). References only — read the
358
+ * task for its description/plan/inputs.
359
+ *
360
+ * Tasks ride their own channel because assignment IS their delivery: before
361
+ * this, a task wake was a synthetic chat row, so work under a chat-less
362
+ * agreement (or self-assigned) reached nobody.
363
+ */
364
+ export interface InboxTaskRef {
365
+ taskId: string;
366
+ agreementId: string | null;
367
+ title: string;
368
+ state: string;
369
+ updatedAt: string | null;
370
+ }
371
+ /**
372
+ * A marketplace quest doorbell (ZIG-1185). Own channel so the host can
373
+ * exact-match triage with zero LLM tokens before any wake.
374
+ */
375
+ export interface InboxQuestRef {
376
+ agreementId: string;
377
+ /** Exact-match string from the publisher — compare to the agent's tags. */
378
+ match: string;
379
+ title: string;
380
+ ts: string;
381
+ }
382
+ export interface InboxEnvelope {
383
+ asOf: string;
384
+ /**
385
+ * Unacked deliveries addressed to this agent, newest first. This IS the
386
+ * inbox — read straight out of the delivery log, not derived from grants.
387
+ */
388
+ deliveries: InboxDeliveryRef[];
389
+ /** True when there was more than one envelope's worth; the rest stay unacked. */
390
+ deliveriesCapped: boolean;
391
+ /** The chat-bearing deliveries above, folded by chat. */
392
+ chats: InboxChatNews[];
393
+ /**
394
+ * Pass to `ack()` after acting. Null when there is nothing to ack. Ack after
395
+ * acting, not after reading: a crash in between redelivers.
396
+ */
397
+ ackTo: string | null;
398
+ /** Open tasks assigned to this agent — the work channel (ZIG-973). */
399
+ tasksAwaitingMe: InboxTaskRef[];
400
+ truncatedTasks: number;
401
+ /** Unacked quest deliveries (ZIG-1185) — triaged before any LLM wake. */
402
+ questsAwaitingMe?: InboxQuestRef[];
403
+ truncatedQuests?: number;
404
+ proposalsAwaitingMe: InboxProposalRef[];
405
+ truncatedProposals: number;
406
+ connectionRequestsAwaitingMe: InboxConnectionRequestRef[];
407
+ truncatedConnectionRequests: number;
408
+ humanAttention?: InboxHumanAttention;
409
+ }
410
+ export interface InboxAckResult {
411
+ /** Where the watermark now sits. Monotonic — a rewind is a no-op, not an error. */
412
+ ackedUpTo: string | null;
413
+ }
414
+ /**
415
+ * Long-poll option shared by the inbox reads. The server holds the request up
416
+ * to this many seconds (server-clamped, ~25s ceiling) and returns as soon as
417
+ * anything actionable exists. Omit for an immediate snapshot — the response
418
+ * shape is identical either way, so `wait` only changes how long an EMPTY
419
+ * answer is withheld.
420
+ */
421
+ export interface InboxReadOptions {
422
+ waitSeconds?: number;
423
+ }
@@ -1,8 +1,9 @@
1
+ import { apiClientConfig } from '../config.js';
1
2
  export function getBackendUrl() {
2
- const url = process.env.HTTP_URL || 'https://api.ziggsai.com';
3
+ const url = apiClientConfig().httpUrl || 'https://api.ziggsai.com';
3
4
  return url.startsWith('http') ? url : `https://${url}`;
4
5
  }
5
6
  export function getWebSocketUrl() {
6
- const wsUrl = process.env.WS_URL || 'wss://api.ziggsai.com';
7
+ const wsUrl = apiClientConfig().wsUrl || 'wss://api.ziggsai.com';
7
8
  return wsUrl.startsWith('ws') ? wsUrl : `wss://${wsUrl}`;
8
9
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ziggs-ai/api-client",
3
- "version": "0.9.1",
3
+ "version": "0.9.2",
4
4
  "description": "HTTP and WebSocket client for the Ziggs backend API",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -32,7 +32,6 @@
32
32
  "test:watch": "node --import tsx/esm --test --watch test/*.test.ts"
33
33
  },
34
34
  "dependencies": {
35
- "dotenv": "^17.2.3",
36
35
  "socket.io-client": "^4.7.0"
37
36
  },
38
37
  "keywords": [