@ziggs-ai/api-client 0.14.0 → 0.14.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -20,12 +20,13 @@ export declare const listArtifactsCapability: CapabilityDefinition;
20
20
  */
21
21
  export declare const shareArtifactCapability: CapabilityDefinition;
22
22
  /**
23
- * attach an artifact you already have to a chat or a task.
23
+ * attach an artifact you already have to a chat, a task, or an agreement.
24
24
  *
25
25
  * The other half of "record now, decide where later". Attaching confers nothing
26
26
  * by itself: it puts the artifact inside the container, and that container's
27
- * audience can read it from then on. Use this to publish to a room or bind a
28
- * deliverable to a task; use artifact_share to hand it to ONE agent instead.
27
+ * audience can read it from then on. Use this to publish to a room, bind a
28
+ * deliverable to a task, or restore agreement scope after a free-standing
29
+ * record; use artifact_share to hand it to ONE agent instead.
29
30
  */
30
31
  export declare const attachArtifactCapability: CapabilityDefinition;
31
32
  /**
@@ -133,15 +133,19 @@ export const recordArtifactCapability = {
133
133
  'Set visibility explicitly. For a finished deliverable, set contentType=result and pass ' +
134
134
  'taskId to bind it to the task. A heavy deliverable belongs in an artifact rather than pasted into a message. ' +
135
135
  ARTIFACT_RECORD_INLINE_CAP,
136
- mcp: 'Write an artifact — text (text) or a file (filename + mime + contentBase64; presign, upload ' +
136
+ mcp:
137
+ // Canonical for ziggs-mcp (tools.ts must not override). Last paragraph is
138
+ // PROTOCOL.reporting copied verbatim — api-client cannot import ziggs-mcp;
139
+ // record-artifact-teaching.test.ts gates the live tool against both.
140
+ 'Write an artifact — text (text) or a file (filename + mime + contentBase64; presign, upload ' +
137
141
  'and completion all happen inside this one call, so there is no separate upload dance). ' +
138
142
  'Scope is optional — pass agreementId or chatId to record it into that ' +
139
143
  'scope, pass taskId alone to bind a deliverable to its task, or pass no scope at all for a ' +
140
144
  'free-standing artifact that is yours until you attach or share it. Never guess a scope: ' +
141
145
  'recording with none always succeeds. Set visibility explicitly. ' +
142
146
  'For a finished deliverable, set contentType=result and pass taskId to bind it to the task. ' +
143
- 'When the work rides a task, close it with ziggs_task_set_result too: that is what the next agent reads from its own inbox. ' +
144
- ARTIFACT_RECORD_INLINE_CAP,
147
+ ARTIFACT_RECORD_INLINE_CAP +
148
+ ' Deliver finished work where the parties agreed it goes: in chat, as a task result, or as an artifact. When the work rides a task, close it with ziggs_task_set_result ({ taskId, state, result: { summary, status, links } }) too, because an agent picking the work up from its own inbox reads that result and not the conversation. Record heavy deliverables as artifacts (ziggs_artifact_record, contentType result, taskId to bind it) rather than pasting them into a message.',
145
149
  },
146
150
  annotation: 'write',
147
151
  params: {
@@ -188,7 +192,8 @@ export const recordArtifactCapability = {
188
192
  },
189
193
  idempotencyKey: {
190
194
  type: 'string',
191
- description: 'Optional dedup key: a redelivered record with the same key no-ops and returns the original artifact. Derive it deterministically (e.g. from the source event + step) — not a random value — so a crash-replay reproduces it.',
195
+ description: 'Optional dedup key: a redelivered record with the same key no-ops and returns the original artifact. Derive it deterministically (e.g. from the source event + step) — not a random value — so a crash-replay reproduces it. ' +
196
+ 'A task-bound result artifact is one deliverable per task: recordReport keys it `task-result:<taskId>`, so a retry after a partial failure reuses that row. A later revision of the same task still returns the original artifact — change the key if you mean a new deliverable.',
192
197
  },
193
198
  },
194
199
  needsAgentId: true,
@@ -350,30 +355,37 @@ export const shareArtifactCapability = {
350
355
  },
351
356
  };
352
357
  /**
353
- * attach an artifact you already have to a chat or a task.
358
+ * attach an artifact you already have to a chat, a task, or an agreement.
354
359
  *
355
360
  * The other half of "record now, decide where later". Attaching confers nothing
356
361
  * by itself: it puts the artifact inside the container, and that container's
357
- * audience can read it from then on. Use this to publish to a room or bind a
358
- * deliverable to a task; use artifact_share to hand it to ONE agent instead.
362
+ * audience can read it from then on. Use this to publish to a room, bind a
363
+ * deliverable to a task, or restore agreement scope after a free-standing
364
+ * record; use artifact_share to hand it to ONE agent instead.
359
365
  */
360
366
  export const attachArtifactCapability = {
361
367
  key: 'artifact_attach',
362
368
  names: { sdk: 'artifact_attach', mcp: 'ziggs_artifact_attach' },
363
- title: 'Attach an artifact to a chat or task',
369
+ title: 'Attach to a chat, task, or agreement',
364
370
  descriptions: {
365
- sdk: 'Attach an artifact you can read to a chat or a task. Everyone in that chat / party to that task can read it from then on. Use artifact_share to give it to one specific agent instead.',
366
- mcp: 'Attach an existing artifact to a chat or a task — the way a free-standing artifact (one you ' +
367
- 'recorded with no scope) reaches an audience. Pass exactly one of chatId or taskId. Everyone ' +
368
- 'in that chat, or party to that task, can read it from then on; attaching gives you nothing ' +
369
- 'new yourself. You must already be able to read the artifact AND belong to the container. ' +
370
- 'To hand it to ONE specific agent without opening a chat, use ziggs_artifact_share instead.',
371
+ sdk: 'Attach an artifact you can read to a chat, a task, or an agreement. Everyone in that chat / party to that task or agreement can read it from then on. Use artifact_share to give it to one specific agent instead.',
372
+ mcp: 'Attach an existing artifact to a chat, a task, or an agreement — the way a free-standing ' +
373
+ 'artifact (one you recorded with no scope) reaches an audience, including restoring ' +
374
+ 'agreement scope after a drop. Pass exactly one of chatId, taskId, or agreementId. Everyone ' +
375
+ 'in that chat, or party to that task or agreement, can read it from then on; attaching ' +
376
+ 'gives you nothing new yourself. You must already be able to read the artifact AND belong ' +
377
+ 'to the container. To hand it to ONE specific agent without opening a chat, use ' +
378
+ 'ziggs_artifact_share instead.',
371
379
  },
372
380
  annotation: 'write',
373
381
  params: {
374
382
  artifactId: { type: 'string', required: true, description: 'Artifact to attach' },
375
383
  chatId: { type: 'string', description: 'Chat to attach it to' },
376
384
  taskId: { type: 'string', description: 'Task to attach it to' },
385
+ agreementId: {
386
+ type: 'string',
387
+ description: 'Agreement to attach it to (POST /agreements/:id/artifacts)',
388
+ },
377
389
  role: {
378
390
  type: 'string',
379
391
  enum: ['input', 'output'],
@@ -388,8 +400,10 @@ export const attachArtifactCapability = {
388
400
  throw new Error('artifactId is required');
389
401
  const chatId = args['chatId'];
390
402
  const taskId = args['taskId'];
391
- if (!!chatId === !!taskId) {
392
- throw new Error('Pass exactly one of chatId or taskId');
403
+ const agreementId = args['agreementId'];
404
+ const named = [chatId, taskId, agreementId].filter(Boolean).length;
405
+ if (named !== 1) {
406
+ throw new Error('Pass exactly one of chatId, taskId, or agreementId');
393
407
  }
394
408
  const role = args['role'] ?? 'output';
395
409
  if (role !== 'input' && role !== 'output') {
@@ -406,6 +420,15 @@ export const attachArtifactCapability = {
406
420
  note: `Everyone in chat ${chatId} can now read artifact ${artifactId}.`,
407
421
  };
408
422
  }
423
+ if (agreementId) {
424
+ await client.attachToAgreement(artifactId, agreementId);
425
+ return {
426
+ ok: true,
427
+ artifactId,
428
+ agreementId,
429
+ note: `Artifact ${artifactId} is attached to agreement ${agreementId}; the agreement's parties can read it.`,
430
+ };
431
+ }
409
432
  await client.attachToTask(artifactId, taskId, role);
410
433
  return {
411
434
  ok: true,
@@ -78,7 +78,7 @@ export const redeemIntroductionCapability = {
78
78
  return {
79
79
  introduction: intro,
80
80
  message: staged
81
- ? `Redeemed. ${intro.from.label} now has a pending link with you (${intro.agreementId}). It becomes real when both people approve it — a link is reach only, and shares no context by itself.`
81
+ ? `Redeemed. A pending link with org ${intro.from.orgId} (${intro.from.agentId ?? 'no agent id'}) is staged (${intro.agreementId}). The display name they claimed is self-chosen and unverified. The link becomes real when both people approve it — a link is reach only, and shares no context by itself.`
82
82
  : intro.outcome === 'already_teammates'
83
83
  ? 'Redeemed, and there was nothing to link: you already answer to the same org or the same person. Talk to them directly.'
84
84
  : 'Redeemed. You two are already linked, so nothing new was proposed.',
@@ -140,14 +140,12 @@ export declare class ArtifactsClient {
140
140
  artifactId?: string;
141
141
  }>;
142
142
  /**
143
- * attach an existing artifact to a chat or a task.
143
+ * attach an existing artifact to a chat, a task, or an agreement.
144
144
  *
145
145
  * Attaching confers nothing on its own: it places the artifact inside the
146
- * container, and that container's audience (chat members / task parties) can
147
- * read it from then on. This is how a free-standing artifact reaches anyone
148
- * without granting it to one specific agent.
149
- *
150
- * The agreement equivalent already existed as POST /agreements/:id/artifacts.
146
+ * container, and that container's audience (chat members / task or agreement
147
+ * parties) can read it from then on. This is how a free-standing artifact
148
+ * reaches anyone without granting it to one specific agent.
151
149
  */
152
150
  attachToChat(artifactId: string, chatId: string): Promise<{
153
151
  success: boolean;
@@ -155,6 +153,9 @@ export declare class ArtifactsClient {
155
153
  attachToTask(artifactId: string, taskId: string, role?: 'input' | 'output'): Promise<{
156
154
  success: boolean;
157
155
  }>;
156
+ attachToAgreement(artifactId: string, agreementId: string): Promise<{
157
+ success: boolean;
158
+ }>;
158
159
  /**
159
160
  * File artifact rail — presign PUT, then client uploads bytes to `uploadUrl`,
160
161
  * then {@link completeFile}. Optional `content` / `contentBase64` performs the
@@ -167,14 +167,12 @@ export class ArtifactsClient {
167
167
  }
168
168
  }
169
169
  /**
170
- * attach an existing artifact to a chat or a task.
170
+ * attach an existing artifact to a chat, a task, or an agreement.
171
171
  *
172
172
  * Attaching confers nothing on its own: it places the artifact inside the
173
- * container, and that container's audience (chat members / task parties) can
174
- * read it from then on. This is how a free-standing artifact reaches anyone
175
- * without granting it to one specific agent.
176
- *
177
- * The agreement equivalent already existed as POST /agreements/:id/artifacts.
173
+ * container, and that container's audience (chat members / task or agreement
174
+ * parties) can read it from then on. This is how a free-standing artifact
175
+ * reaches anyone without granting it to one specific agent.
178
176
  */
179
177
  async attachToChat(artifactId, chatId) {
180
178
  return this._attach(`/chats/${encodeURIComponent(chatId)}/artifacts`, {
@@ -187,6 +185,9 @@ export class ArtifactsClient {
187
185
  role,
188
186
  });
189
187
  }
188
+ async attachToAgreement(artifactId, agreementId) {
189
+ return this._attach(`/agreements/${encodeURIComponent(agreementId)}/artifacts`, { artifactId });
190
+ }
190
191
  /**
191
192
  * File artifact rail — presign PUT, then client uploads bytes to `uploadUrl`,
192
193
  * then {@link completeFile}. Optional `content` / `contentBase64` performs the
@@ -1,6 +1,24 @@
1
1
  import { getBackendUrl } from '../utils/urlUtils.js';
2
2
  import { buildOperatorHeaders } from './operatorHeaders.js';
3
3
  import { throwApiError } from '../shared/apiError.js';
4
+ /**
5
+ * Shared post-parse for delegate and author-share: both endpoints return either
6
+ * a minted grant or a pending_approval request the owner must approve.
7
+ */
8
+ function parseGrantOrPending(body, expectedFrom) {
9
+ const parsed = JSON.parse(body);
10
+ if (parsed.status === 'pending_approval' && parsed.agreementId) {
11
+ return {
12
+ status: 'pending_approval',
13
+ agreementId: parsed.agreementId,
14
+ ownerId: parsed.ownerId,
15
+ };
16
+ }
17
+ if (!parsed.grant?.grantId) {
18
+ throw new Error(`Invalid response: expected { grant } or { status: "pending_approval" } from ${expectedFrom}`);
19
+ }
20
+ return { status: 'granted', grant: parsed.grant };
21
+ }
4
22
  /**
5
23
  * context grant management — list / issue / delegate / revoke.
6
24
  */
@@ -57,20 +75,9 @@ export class ContextGrantsClient {
57
75
  if (!res.ok) {
58
76
  throwApiError(res, body, `delegateGrant failed: ${res.status} ${res.statusText}`);
59
77
  }
60
- const parsed = JSON.parse(body);
61
78
  // Re-granting a grant whose original owner is a different party does not
62
79
  // mint — it opens a request that owner must approve.
63
- if (parsed.status === 'pending_approval' && parsed.agreementId) {
64
- return {
65
- status: 'pending_approval',
66
- agreementId: parsed.agreementId,
67
- ownerId: parsed.ownerId,
68
- };
69
- }
70
- if (!parsed.grant?.grantId) {
71
- throw new Error('Invalid response: expected { grant } or { status: "pending_approval" } from POST /context/grants/:id/delegate');
72
- }
73
- return { status: 'granted', grant: parsed.grant };
80
+ return parseGrantOrPending(body, 'POST /context/grants/:id/delegate');
74
81
  }
75
82
  /**
76
83
  * expand a grant you hold into the chat/agreement ids inside its
@@ -119,18 +126,7 @@ export class ContextGrantsClient {
119
126
  if (!res.ok) {
120
127
  throwApiError(res, body, `shareArtifact failed: ${res.status} ${res.statusText}`);
121
128
  }
122
- const parsed = JSON.parse(body);
123
- if (parsed.status === 'pending_approval' && parsed.agreementId) {
124
- return {
125
- status: 'pending_approval',
126
- agreementId: parsed.agreementId,
127
- ownerId: parsed.ownerId,
128
- };
129
- }
130
- if (!parsed.grant?.grantId) {
131
- throw new Error('Invalid response: expected { grant } or { status: "pending_approval" } from POST /context/artifacts/:id/share');
132
- }
133
- return { status: 'granted', grant: parsed.grant };
129
+ return parseGrantOrPending(body, 'POST /context/artifacts/:id/share');
134
130
  }
135
131
  async revokeGrant(grantId) {
136
132
  const res = await fetch(`${this.baseUrl}/context/grants/${encodeURIComponent(grantId)}`, {
@@ -1,15 +1,26 @@
1
1
  /** What became of one hello. `expired` is derived by the server, not stored. */
2
2
  export type IntroductionStatus = 'open' | 'redeemed' | 'declined' | 'revoked' | 'expired';
3
3
  export type IntroductionOutcome = 'link_pending' | 'already_teammates' | 'already_linked';
4
+ export interface IntroductionFrom {
5
+ orgId: string;
6
+ agentId: string | null;
7
+ agentCreatedAt?: string | null;
8
+ claimedLabel?: {
9
+ text: string;
10
+ selfChosen: boolean;
11
+ verified: boolean;
12
+ };
13
+ /**
14
+ * Same text as claimedLabel.text. Not identity — anyone can pick it.
15
+ */
16
+ label: string;
17
+ }
4
18
  export interface IntroductionView {
5
19
  token: string;
6
20
  status: IntroductionStatus;
7
21
  outcome: IntroductionOutcome | null;
8
- from: {
9
- label: string;
10
- agentId: string | null;
11
- orgId: string;
12
- };
22
+ notice?: string;
23
+ from: IntroductionFrom;
13
24
  venue: string | null;
14
25
  venueRef: string | null;
15
26
  note: string | null;
@@ -12,7 +12,7 @@ export type { ContextReadType, ContextReadQuery, ContextReadEnvelope, ContextSna
12
12
  export { ContextDiscoveryClient } from './ContextDiscoveryClient.js';
13
13
  export type { DiscoverableItem } from './ContextDiscoveryClient.js';
14
14
  export { IntroductionsClient } from './IntroductionsClient.js';
15
- export type { IntroductionView, IntroductionStatus, IntroductionOutcome, MintIntroductionInput, } from './IntroductionsClient.js';
15
+ export type { IntroductionView, IntroductionFrom, IntroductionStatus, IntroductionOutcome, MintIntroductionInput, } from './IntroductionsClient.js';
16
16
  export { GrantsClient } from './GrantsClient.js';
17
17
  export type { ListGrantsQuery, ListGrantsResult, UnreadableRail } from './GrantsClient.js';
18
18
  export { ContextGrantsClient } from './ContextGrantsClient.js';
package/dist/types.d.ts CHANGED
@@ -175,6 +175,17 @@ export interface Agreement {
175
175
  money?: {
176
176
  price?: number | null;
177
177
  paymentStatus?: string;
178
+ transactionId?: string | null;
179
+ /**
180
+ * Ledger hold for this agreement, or null if paymentStatus says held but
181
+ * no hold row exists. Amount is cents. Never includes a wallet id.
182
+ */
183
+ held?: {
184
+ amount: number;
185
+ currency: string;
186
+ state: 'held' | 'released' | 'refunded';
187
+ agreementId: string;
188
+ } | null;
178
189
  };
179
190
  lifecycle?: string;
180
191
  expiresAt?: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ziggs-ai/api-client",
3
- "version": "0.14.0",
3
+ "version": "0.14.1",
4
4
  "description": "HTTP and WebSocket client for the Ziggs backend API",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",