@ziggs-ai/api-client 0.38.2 → 0.39.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 (72) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/README.md +14 -36
  3. package/dist/capabilities/agreementVerbs.js +13 -21
  4. package/dist/capabilities/agreements.d.ts +2 -3
  5. package/dist/capabilities/agreements.js +7 -42
  6. package/dist/capabilities/artifacts.js +41 -43
  7. package/dist/capabilities/connections.js +10 -7
  8. package/dist/capabilities/context.d.ts +1 -1
  9. package/dist/capabilities/context.js +41 -45
  10. package/dist/capabilities/discovery.js +5 -5
  11. package/dist/capabilities/everyday.d.ts +1 -1
  12. package/dist/capabilities/everyday.js +1 -2
  13. package/dist/capabilities/index.d.ts +1 -2
  14. package/dist/capabilities/index.js +1 -2
  15. package/dist/capabilities/introductions.d.ts +1 -1
  16. package/dist/capabilities/introductions.js +3 -11
  17. package/dist/capabilities/links.d.ts +1 -9
  18. package/dist/capabilities/links.js +7 -28
  19. package/dist/capabilities/mcpBriefs.js +22 -28
  20. package/dist/capabilities/messaging.d.ts +9 -0
  21. package/dist/capabilities/messaging.js +11 -0
  22. package/dist/capabilities/nextCall.js +8 -17
  23. package/dist/capabilities/sources.d.ts +2 -3
  24. package/dist/capabilities/sources.js +1 -2
  25. package/dist/capabilities/tasks.js +1 -1
  26. package/dist/capabilities/toolLane.js +2 -2
  27. package/dist/engagementGuide.d.ts +2 -2
  28. package/dist/engagementGuide.js +6 -6
  29. package/dist/handles.d.ts +1 -1
  30. package/dist/handles.js +2 -2
  31. package/dist/http/AgreementClient.d.ts +17 -64
  32. package/dist/http/AgreementClient.js +14 -65
  33. package/dist/http/ArtifactsClient.d.ts +17 -19
  34. package/dist/http/ArtifactsClient.js +30 -62
  35. package/dist/http/ConnectionsClient.d.ts +6 -7
  36. package/dist/http/ConnectionsClient.js +5 -3
  37. package/dist/http/ContextDiscoveryClient.d.ts +2 -2
  38. package/dist/http/ContextGrantsClient.d.ts +8 -15
  39. package/dist/http/ContextGrantsClient.js +3 -5
  40. package/dist/http/ContextOpenClient.d.ts +5 -6
  41. package/dist/http/ContextOpenClient.js +11 -4
  42. package/dist/http/ContextReadClient.d.ts +23 -27
  43. package/dist/http/ContextReadClient.js +24 -23
  44. package/dist/http/ExecutionContextClient.d.ts +5 -1
  45. package/dist/http/ExecutionContextClient.js +48 -1
  46. package/dist/http/IntroductionsClient.d.ts +6 -4
  47. package/dist/http/MessageRecordsClient.d.ts +36 -0
  48. package/dist/http/MessageRecordsClient.js +30 -0
  49. package/dist/http/SessionsClient.d.ts +5 -5
  50. package/dist/http/SessionsClient.js +6 -6
  51. package/dist/http/TaskClient.d.ts +11 -5
  52. package/dist/http/TaskClient.js +11 -34
  53. package/dist/http/agreementFlows.d.ts +2 -3
  54. package/dist/http/agreementFlows.js +5 -5
  55. package/dist/http/index.d.ts +3 -5
  56. package/dist/http/index.js +3 -4
  57. package/dist/http/operatorHeaders.d.ts +3 -0
  58. package/dist/http/operatorHeaders.js +14 -5
  59. package/dist/inboxAck.d.ts +2 -2
  60. package/dist/inboxAck.js +2 -2
  61. package/dist/index.d.ts +4 -4
  62. package/dist/index.js +2 -2
  63. package/dist/types.d.ts +15 -55
  64. package/package.json +2 -2
  65. package/dist/capabilities/chat.d.ts +0 -43
  66. package/dist/capabilities/chat.js +0 -196
  67. package/dist/capabilities/chatListFields.d.ts +0 -14
  68. package/dist/capabilities/chatListFields.js +0 -61
  69. package/dist/http/ChatClient.d.ts +0 -187
  70. package/dist/http/ChatClient.js +0 -219
  71. package/dist/http/MessagesClient.d.ts +0 -40
  72. package/dist/http/MessagesClient.js +0 -81
@@ -472,6 +472,10 @@ export async function getAgreement(agreementId, creds) {
472
472
  const agreement = await getAgreementDocument(agreementId, creds);
473
473
  return agreement ? shapeAgreement(agreement) : null;
474
474
  }
475
+ function writeSuccess(data) {
476
+ const success = data['success'] === true || (data['success'] === undefined && data['ok'] === true);
477
+ return { success, ok: success };
478
+ }
475
479
  /**
476
480
  * Connect with someone — by email, by agent id, or with a shareable invite.
477
481
  *
@@ -496,12 +500,12 @@ export async function createLink(body, creds) {
496
500
  }
497
501
  const data = await res.json().catch(() => null);
498
502
  if (data?.['status'] !== 'sent') {
499
- throw new Error('Invalid response: expected { ok, status: "sent", shareUrl } from POST /agreements/links');
503
+ throw new Error('Invalid response: expected { success, status: "sent", shareUrl } from POST /agreements/links');
500
504
  }
501
505
  const shareUrl = data['shareUrl'];
502
506
  const agreementId = data['agreementId'];
503
507
  return {
504
- ok: data['ok'] === true,
508
+ ...writeSuccess(data),
505
509
  status: 'sent',
506
510
  agreementId: typeof agreementId === 'string' ? agreementId : null,
507
511
  shareUrl: typeof shareUrl === 'string' ? shareUrl : null,
@@ -511,8 +515,7 @@ export async function revokeAgreement(agreementId, creds) {
511
515
  if (!agreementId)
512
516
  throw new Error('agreementId is required for revocation');
513
517
  assertCreds(creds, 'agreement revocation');
514
- // canonical REST verb. Legacy POST /agreements/:id/revoke stays
515
- // serving the identical handler (with Deprecation headers) until PR-F.
518
+ // Revocation uses the resource DELETE operation.
516
519
  const res = await fetch(`${getAgreementBaseUrl()}/${encodeURIComponent(agreementId)}`, {
517
520
  method: 'DELETE',
518
521
  headers: buildHeaders(creds),
@@ -523,10 +526,10 @@ export async function revokeAgreement(agreementId, creds) {
523
526
  }
524
527
  const data = await res.json().catch(() => null);
525
528
  if (!data?.['agreement']) {
526
- throw new Error('Invalid response: expected { ok, agreement } from DELETE /agreements/:id');
529
+ throw new Error('Invalid response: expected { success, agreement } from DELETE /agreements/:id');
527
530
  }
528
531
  const envelope = data;
529
- return { ...envelope, agreement: shapeAgreement(envelope.agreement) };
532
+ return { ...envelope, ...writeSuccess(data), agreement: shapeAgreement(envelope.agreement) };
530
533
  }
531
534
  /** mark an agreement fulfilled (complete). A provider closing its own
532
535
  * delivered work — party-gated server-side. */
@@ -544,10 +547,10 @@ export async function fulfillAgreement(agreementId, creds) {
544
547
  }
545
548
  const data = await res.json().catch(() => null);
546
549
  if (!data?.['agreement']) {
547
- throw new Error('Invalid response: expected { ok, agreement } from POST /agreements/:id/fulfill');
550
+ throw new Error('Invalid response: expected { success, agreement } from POST /agreements/:id/fulfill');
548
551
  }
549
552
  const envelope = data;
550
- return { ...envelope, agreement: shapeAgreement(envelope.agreement) };
553
+ return { ...envelope, ...writeSuccess(data), agreement: shapeAgreement(envelope.agreement) };
551
554
  }
552
555
  export async function claimAgreement(agreementId, creds, opts = {}) {
553
556
  if (!agreementId)
@@ -556,7 +559,7 @@ export async function claimAgreement(agreementId, creds, opts = {}) {
556
559
  const res = await fetch(`${getAgreementBaseUrl()}/${encodeURIComponent(agreementId)}/claim`, {
557
560
  method: 'POST',
558
561
  headers: buildHeaders(creds),
559
- body: JSON.stringify({ ...(opts.mandateAgreementId ? { mandateAgreementId: opts.mandateAgreementId } : {}), ...(opts.parentAgreementId ? { parentAgreementId: opts.parentAgreementId } : {}), ...(opts.chatId ? { chatId: opts.chatId } : {}), ...(opts.connections && Object.keys(opts.connections).length ? { connections: opts.connections } : {}), ...(opts.hireFor === 'workspace' ? { hireFor: 'workspace' } : {}) }),
562
+ body: JSON.stringify({ ...(opts.mandateAgreementId ? { mandateAgreementId: opts.mandateAgreementId } : {}), ...(opts.parentAgreementId ? { parentAgreementId: opts.parentAgreementId } : {}), ...(opts.connections && Object.keys(opts.connections).length ? { connections: opts.connections } : {}), ...(opts.hireFor === 'workspace' ? { hireFor: 'workspace' } : {}) }),
560
563
  });
561
564
  if (!res.ok) {
562
565
  const body = await res.text().catch(() => '');
@@ -564,66 +567,14 @@ export async function claimAgreement(agreementId, creds, opts = {}) {
564
567
  }
565
568
  const data = await res.json().catch(() => null);
566
569
  if (!data?.['agreement']) {
567
- throw new Error('Invalid response: expected { ok, agreement } from POST /agreements/:id/claim');
570
+ throw new Error('Invalid response: expected { success, agreement } from POST /agreements/:id/claim');
568
571
  }
569
572
  // the route reports which kind of broadcast this turned out to be.
570
573
  // A caller cannot work it out from the row — post-claim no sentinel is left,
571
574
  // and a request (you do the work) reads the same shape as a standing offer (you
572
575
  // pay for it) unless you know which slot you landed in.
573
576
  const envelope = data;
574
- return { ...envelope, agreement: shapeAgreement(envelope.agreement) };
575
- }
576
- // ---------------------------------------------------------------------------
577
- // Chat links
578
- // ---------------------------------------------------------------------------
579
- /**
580
- * Link types a caller may ask for. `origin` and `delegation` are workflow-owned;
581
- * callers may only pin an agreement into a chat as a reference (`mention`).
582
- */
583
- export const CHAT_LINK_TYPES = ['mention'];
584
- export async function linkAgreementToChat(agreementId, chatId, linkType = 'mention', creds) {
585
- if (!agreementId || !chatId)
586
- return null;
587
- assertCreds(creds, 'link agreement to chat');
588
- try {
589
- const res = await fetch(`${getAgreementBaseUrl()}/${agreementId}/link`, {
590
- method: 'POST',
591
- headers: buildHeaders(creds),
592
- body: JSON.stringify({ chatId, linkType }),
593
- });
594
- if (!res.ok) {
595
- const body = await res.text().catch(() => '');
596
- runtimeLog.warn('AgreementClient', `⚠️ Link agreement to chat failed: ${res.status} ${res.statusText} ${body?.slice(0, 200)}`);
597
- return null;
598
- }
599
- return await res.json().catch(() => null);
600
- }
601
- catch (e) {
602
- runtimeLog.warn('AgreementClient', `⚠️ Link agreement to chat failed: ${e.message}`);
603
- return null;
604
- }
605
- }
606
- export async function getChatsForAgreement(agreementId, creds) {
607
- if (!agreementId)
608
- return [];
609
- assertCreds(creds, 'get chats for agreement');
610
- try {
611
- const res = await fetch(`${getAgreementBaseUrl()}/${agreementId}/chats`, {
612
- method: 'GET',
613
- headers: buildHeaders(creds),
614
- });
615
- if (!res.ok) {
616
- const body = await res.text().catch(() => '');
617
- runtimeLog.warn('AgreementClient', `⚠️ Get chats for agreement failed: ${res.status} ${res.statusText} ${body?.slice(0, 200)}`);
618
- return [];
619
- }
620
- const data = await res.json().catch(() => null);
621
- return Array.isArray(data?.['links']) ? data['links'] : [];
622
- }
623
- catch (e) {
624
- runtimeLog.warn('AgreementClient', `⚠️ Get chats for agreement failed: ${e.message}`);
625
- return [];
626
- }
577
+ return { ...envelope, ...writeSuccess(data), agreement: shapeAgreement(envelope.agreement) };
627
578
  }
628
579
  // ---------------------------------------------------------------------------
629
580
  // Artifact links
@@ -701,8 +652,6 @@ export class AgreementClient {
701
652
  revoke(id) { return revokeAgreement(id, this.creds); }
702
653
  fulfill(id) { return fulfillAgreement(id, this.creds); }
703
654
  claimAgreement(id) { return claimAgreement(id, this.creds); }
704
- linkToChat(id, chatId, linkType) { return linkAgreementToChat(id, chatId, linkType ?? 'mention', this.creds); }
705
- listChats(id) { return getChatsForAgreement(id, this.creds); }
706
655
  linkArtifact(id, artifactId, linkType) { return linkArtifactToAgreement(id, artifactId, linkType ?? 'produced', this.creds); }
707
656
  listArtifacts(id) { return getArtifactsForAgreement(id, this.creds); }
708
657
  }
@@ -8,13 +8,12 @@ import type { ListReachableArtifactsResult } from '@ziggs-ai/contracts';
8
8
  export declare const ARTIFACT_INLINE_TEXT_MAX_CHARS = 50000;
9
9
  /** Named escape hatch for agents that hit the inline cap. */
10
10
  export declare const ARTIFACT_INLINE_TEXT_OVER_LIMIT_HINT: string;
11
- export type ArtifactVisibility = 'chat' | 'agent-private';
11
+ export type ArtifactVisibility = 'address' | 'agent-private';
12
12
  export interface ListArtifactsOptions {
13
13
  after?: string;
14
14
  limit?: number;
15
15
  }
16
16
  export interface ListArtifactsQuery {
17
- chatId?: string;
18
17
  agreementId?: string;
19
18
  taskId?: string;
20
19
  serviceAgreementId?: string;
@@ -73,7 +72,6 @@ export interface WriteArtifactInput {
73
72
  contentType?: string;
74
73
  visibility?: ArtifactVisibility;
75
74
  /** When the artifact is associated with a chat. */
76
- chatId?: string;
77
75
  /** When the artifact is associated with an agreement. */
78
76
  agreementId?: string;
79
77
  /** Optional task binding — creates a TaskArtifactLink alongside the primary scope link. */
@@ -106,7 +104,6 @@ export interface ArtifactUploadUrlInput {
106
104
  byteSize: number;
107
105
  format?: ArtifactFileFormat;
108
106
  visibility?: ArtifactVisibility;
109
- chatId?: string;
110
107
  agreementId?: string;
111
108
  taskId?: string;
112
109
  serviceAgreementId?: string;
@@ -136,12 +133,18 @@ export type ArtifactFileView = Record<string, unknown> & {
136
133
  extractionError?: string | null;
137
134
  filename?: string | null;
138
135
  };
136
+ /**
137
+ * Replaces `ContextReader`'s artifact reads + `ContextWriter`'s breadcrumb
138
+ * writes. `visibility: 'agent-private'` is the new home for agent thoughts —
139
+ * they persist, are searchable, but are not visible to other chat parties.
140
+ */
141
+ /** Artifact scope is explicit selected work. */
139
142
  import type { ArtifactExternalRef, ArtifactFileFormat } from '@ziggs-ai/contracts';
140
143
  export { AGREEMENT_LANE_PREFIX } from '@ziggs-ai/contracts';
141
- export declare function artifactScopeForSession(sessionId: string): {
142
- chatId: string;
143
- } | {
144
- agreementId: string;
144
+ export declare function artifactScope(target?: {
145
+ agreementId?: string;
146
+ }): {
147
+ agreementId?: string;
145
148
  };
146
149
  export declare class ArtifactsClient {
147
150
  private readonly operatorKey;
@@ -158,7 +161,7 @@ export declare class ArtifactsClient {
158
161
  * This is not `list()` with a query string. `list()` answers "what is in this
159
162
  * container"; a person's cross-container view lives behind `reach` arms that
160
163
  * an agent has no business asking for. The server refuses `reach`,
161
- * `authoredBy` and `chatId` here rather than ignoring them, so a narrowing
164
+ * `authoredBy` and `addressId` here rather than ignoring them, so a narrowing
162
165
  * filter can never be silently dropped — hence no options for them.
163
166
  *
164
167
  * The result always carries `coverage`. An empty `artifacts` is a statement
@@ -174,7 +177,7 @@ export declare class ArtifactsClient {
174
177
  * endpoint is updated to accept `visibility`, this falls back to logging
175
178
  * locally (operators can still wire their own sink).
176
179
  */
177
- recordThought(sessionId: string, text: string, opts?: {
180
+ recordThought(text: string, opts?: {
178
181
  idempotencyKey?: string;
179
182
  serviceAgreementId?: string;
180
183
  }): Promise<void>;
@@ -190,16 +193,12 @@ export declare class ArtifactsClient {
190
193
  artifactId?: string;
191
194
  }>;
192
195
  /**
193
- * attach an existing artifact to a chat, a task, or an agreement.
196
+ * attach an existing artifact to a task or agreement.
194
197
  *
195
- * Attaching confers nothing on its own: it places the artifact inside the
196
- * container, and that container's audience (chat members / task or agreement
197
- * parties) can read it from then on. This is how a free-standing artifact
198
- * reaches anyone without granting it to one specific agent.
198
+ * A chat link is a presentation reference, never an access grant. Task and
199
+ * agreement associations use their work authority and source-disclosure policy.
200
+ * Explicit artifact sharing grants access to the selected holder.
199
201
  */
200
- attachToChat(artifactId: string, chatId: string): Promise<{
201
- success: boolean;
202
- }>;
203
202
  attachToTask(artifactId: string, taskId: string, role?: 'input' | 'output'): Promise<{
204
203
  success: boolean;
205
204
  }>;
@@ -246,6 +245,5 @@ export declare class ArtifactsClient {
246
245
  * binds it to a task. Both containers at once is still a mistake worth naming,
247
246
  * since only one of them would take effect.
248
247
  */
249
- private _assertScopeXor;
250
248
  private _headers;
251
249
  }
@@ -1,7 +1,7 @@
1
1
  import { runtimeLog } from '../shared/runtimeLog.js';
2
2
  import { throwApiError } from '../shared/apiError.js';
3
3
  import { getBackendUrl } from '../utils/urlUtils.js';
4
- import { buildOperatorHeaders } from './operatorHeaders.js';
4
+ import { buildOperatorHeaders, currentWorkExecution } from './operatorHeaders.js';
5
5
  /**
6
6
  * Inline `POST /artifacts` body cap (matches backend
7
7
  * `ARTIFACT_PUBLIC_TEXT_MAX_CHARS`). Over this → file rail or multi-part; the
@@ -26,28 +26,22 @@ function resolveUploadBytes(input) {
26
26
  }
27
27
  return null;
28
28
  }
29
- /**
30
- * Replaces `ContextReader`'s artifact reads + `ContextWriter`'s breadcrumb
31
- * writes. `visibility: 'agent-private'` is the new home for agent thoughts —
32
- * they persist, are searchable, but are not visible to other chat parties.
33
- */
34
- /**
35
- * turn a runtime lane id into a scope the backend can accept.
36
- *
37
- * A task with no origin chat runs on the lane `agrn-<agreementId>` (see
38
- * Agent.laneSessionIdForTask). That is a routing key, not a chat: no such
39
- * chat row exists, so passing it as `chatId` made every breadcrumb and recorded
40
- * thought on an agreement lane come back 403 "not authorized for this scope" —
41
- * a fictional scope reading like an auth failure. The work is agreement-scoped,
42
- * so say so; same rule settled for deliverables.
43
- */
44
- import { agreementIdFromLane } from '@ziggs-ai/contracts';
45
29
  export { AGREEMENT_LANE_PREFIX } from '@ziggs-ai/contracts';
46
- export function artifactScopeForSession(sessionId) {
47
- const agreementId = agreementIdFromLane(sessionId);
48
- if (agreementId)
49
- return { agreementId };
50
- return { chatId: sessionId };
30
+ function rejectRoomScope(input) {
31
+ if ('addressId' in input)
32
+ throw new Error('addressId is retired; use explicit task/agreement work or an exact message artifact reference');
33
+ }
34
+ export function artifactScope(target = {}) {
35
+ rejectRoomScope(target);
36
+ const execution = currentWorkExecution();
37
+ if (execution?.agreementId)
38
+ return { agreementId: execution.agreementId };
39
+ if (execution)
40
+ return {};
41
+ if (target.agreementId)
42
+ return { agreementId: target.agreementId };
43
+ // An unattached artifact is private to its owner; a container is optional.
44
+ return {};
51
45
  }
52
46
  export class ArtifactsClient {
53
47
  operatorKey;
@@ -60,13 +54,12 @@ export class ArtifactsClient {
60
54
  this.agentId = agentId;
61
55
  }
62
56
  async list(q, opts = {}) {
63
- const selectors = [q.chatId, q.agreementId, q.taskId, q.authoredBy].filter(Boolean);
57
+ rejectRoomScope(q);
58
+ const selectors = [q.agreementId, q.taskId, q.authoredBy].filter(Boolean);
64
59
  if (selectors.length !== 1) {
65
- throw new Error('ArtifactsClient.list: pass exactly one of chatId, agreementId, taskId, or authoredBy');
60
+ throw new Error('ArtifactsClient.list: pass exactly one of agreementId, taskId, or authoredBy');
66
61
  }
67
62
  const url = new URL(`${getBackendUrl()}/artifacts`);
68
- if (q.chatId)
69
- url.searchParams.set('chatId', q.chatId);
70
63
  if (q.agreementId)
71
64
  url.searchParams.set('agreementId', q.agreementId);
72
65
  if (q.taskId)
@@ -93,7 +86,7 @@ export class ArtifactsClient {
93
86
  * This is not `list()` with a query string. `list()` answers "what is in this
94
87
  * container"; a person's cross-container view lives behind `reach` arms that
95
88
  * an agent has no business asking for. The server refuses `reach`,
96
- * `authoredBy` and `chatId` here rather than ignoring them, so a narrowing
89
+ * `authoredBy` and `addressId` here rather than ignoring them, so a narrowing
97
90
  * filter can never be silently dropped — hence no options for them.
98
91
  *
99
92
  * The result always carries `coverage`. An empty `artifacts` is a statement
@@ -126,10 +119,9 @@ export class ArtifactsClient {
126
119
  * endpoint is updated to accept `visibility`, this falls back to logging
127
120
  * locally (operators can still wire their own sink).
128
121
  */
129
- async recordThought(sessionId, text, opts = {}) {
130
- // `sessionId` may be an agreement lane, which is not a chat.
122
+ async recordThought(text, opts = {}) {
131
123
  return this.write({
132
- ...artifactScopeForSession(sessionId),
124
+ ...artifactScope({ agreementId: opts.serviceAgreementId }),
133
125
  text,
134
126
  contentType: 'thought',
135
127
  visibility: 'agent-private',
@@ -138,9 +130,9 @@ export class ArtifactsClient {
138
130
  });
139
131
  }
140
132
  async write(input) {
133
+ rejectRoomScope(input);
141
134
  if (!input.text || !input.text.trim())
142
135
  return;
143
- this._assertScopeXor(input);
144
136
  try {
145
137
  await this.writeStrict(input);
146
138
  }
@@ -161,7 +153,7 @@ export class ArtifactsClient {
161
153
  if (!input.text || !input.text.trim()) {
162
154
  throw new Error('ArtifactsClient.writeStrict: text is required');
163
155
  }
164
- this._assertScopeXor(input);
156
+ rejectRoomScope(input);
165
157
  const text = input.text.trim();
166
158
  // Refuse before the wire so MCP/SDK get a named escape hatch
167
159
  // instead of a bare class-validator string.
@@ -176,8 +168,7 @@ export class ArtifactsClient {
176
168
  text,
177
169
  ...(input.name?.trim() ? { name: input.name.trim() } : {}),
178
170
  contentType: input.contentType ?? 'text',
179
- visibility: input.visibility ?? 'chat',
180
- chatId: input.chatId,
171
+ visibility: input.visibility ?? 'address',
181
172
  agreementId: input.agreementId,
182
173
  taskId: input.taskId,
183
174
  serviceAgreementId: input.serviceAgreementId,
@@ -202,18 +193,12 @@ export class ArtifactsClient {
202
193
  }
203
194
  }
204
195
  /**
205
- * attach an existing artifact to a chat, a task, or an agreement.
196
+ * attach an existing artifact to a task or agreement.
206
197
  *
207
- * Attaching confers nothing on its own: it places the artifact inside the
208
- * container, and that container's audience (chat members / task or agreement
209
- * parties) can read it from then on. This is how a free-standing artifact
210
- * reaches anyone without granting it to one specific agent.
198
+ * A chat link is a presentation reference, never an access grant. Task and
199
+ * agreement associations use their work authority and source-disclosure policy.
200
+ * Explicit artifact sharing grants access to the selected holder.
211
201
  */
212
- async attachToChat(artifactId, chatId) {
213
- return this._attach(`/chats/${encodeURIComponent(chatId)}/artifacts`, {
214
- artifactId,
215
- });
216
- }
217
202
  async attachToTask(artifactId, taskId, role = 'output') {
218
203
  return this._attach(`/tasks/${encodeURIComponent(taskId)}/artifacts`, {
219
204
  artifactId,
@@ -242,6 +227,7 @@ export class ArtifactsClient {
242
227
  * S3 PUT here and returns a sha256 so complete can follow immediately.
243
228
  */
244
229
  async uploadUrl(input) {
230
+ rejectRoomScope(input);
245
231
  if (!input.filename?.trim()) {
246
232
  throw new Error('ArtifactsClient.uploadUrl: filename is required');
247
233
  }
@@ -251,11 +237,6 @@ export class ArtifactsClient {
251
237
  if (!Number.isInteger(input.byteSize) || input.byteSize < 1) {
252
238
  throw new Error('ArtifactsClient.uploadUrl: byteSize must be a positive integer');
253
239
  }
254
- this._assertScopeXor({
255
- text: '.',
256
- chatId: input.chatId,
257
- agreementId: input.agreementId,
258
- });
259
240
  const bytes = resolveUploadBytes(input);
260
241
  if (bytes && bytes.byteLength !== input.byteSize) {
261
242
  throw new Error(`ArtifactsClient.uploadUrl: byteSize ${input.byteSize} does not match content length ${bytes.byteLength}`);
@@ -267,7 +248,6 @@ export class ArtifactsClient {
267
248
  byteSize: input.byteSize,
268
249
  format: input.format,
269
250
  visibility: input.visibility,
270
- chatId: input.chatId,
271
251
  agreementId: input.agreementId,
272
252
  taskId: input.taskId,
273
253
  serviceAgreementId: input.serviceAgreementId,
@@ -356,18 +336,6 @@ export class ArtifactsClient {
356
336
  * binds it to a task. Both containers at once is still a mistake worth naming,
357
337
  * since only one of them would take effect.
358
338
  */
359
- _assertScopeXor(input) {
360
- if (input.chatId && input.agreementId) {
361
- // the refusal names the recovery, because this is the one gate
362
- // for every caller — the exposed tool surfaces inherit it rather than each
363
- // wording their own. Wording matters: this fires at the last step of a
364
- // finished task, where a bare "pick one" leaves the deliverable unfiled.
365
- throw new Error('pass at most one of chatId or agreementId — a deliverable under an ' +
366
- 'agreement wants agreementId alone (its parties see it); use chatId ' +
367
- 'only for a chat-scoped note. To put it in both places, record it ' +
368
- 'with agreementId, then attach it to the chat.');
369
- }
370
- }
371
339
  _headers() {
372
340
  return buildOperatorHeaders(this.operatorKey, this.agentId, { 'content-type': 'application/json' });
373
341
  }
@@ -38,11 +38,10 @@ export interface McpConnectionRequestParams {
38
38
  * per-user relay may send it as `onBehalfOfUserId` instead.
39
39
  */
40
40
  recipientId?: string;
41
- /**
42
- * Working chat the consent card is opened into. Required by the backend for
43
- * the agent-tool path; the per-user gateway trigger omits it.
44
- */
45
- chatId?: string;
41
+ context: {
42
+ kind: 'message' | 'task' | 'agreement';
43
+ id: string;
44
+ };
46
45
  /**
47
46
  * Target the consent request at this end-user (X-On-Behalf-Of-User) instead
48
47
  * of the operator — the per-user MCP gateway's connect prompt.
@@ -112,10 +111,10 @@ export declare class ConnectionsClient {
112
111
  /**
113
112
  * agent-initiated MCP connection request: ask the principal to
114
113
  * connect a remote MCP server and grant this agent the listed tools. Opens a
115
- * connection-consent agreement in the working chat; on approval the
114
+ * connection-consent agreement for explicit work/message context; on approval the
116
115
  * server is connected (if needed) and the agent is granted the tools.
117
116
  */
118
- requestMcpConnection({ serverUrl, tools, reason, chatId, recipientId, onBehalfOfUserId, }: McpConnectionRequestParams): Promise<Record<string, unknown>>;
117
+ requestMcpConnection({ serverUrl, tools, reason, context, recipientId, onBehalfOfUserId, }: McpConnectionRequestParams): Promise<Record<string, unknown>>;
119
118
  private _requestRaw;
120
119
  private _request;
121
120
  }
@@ -161,13 +161,15 @@ export class ConnectionsClient {
161
161
  /**
162
162
  * agent-initiated MCP connection request: ask the principal to
163
163
  * connect a remote MCP server and grant this agent the listed tools. Opens a
164
- * connection-consent agreement in the working chat; on approval the
164
+ * connection-consent agreement for explicit work/message context; on approval the
165
165
  * server is connected (if needed) and the agent is granted the tools.
166
166
  */
167
- async requestMcpConnection({ serverUrl, tools, reason, chatId, recipientId, onBehalfOfUserId, }) {
167
+ async requestMcpConnection({ serverUrl, tools, reason, context, recipientId, onBehalfOfUserId, }) {
168
168
  if (!serverUrl)
169
169
  throw new Error("requestMcpConnection: serverUrl is required");
170
- return (await this._request("POST", "/connections/mcp/requests", { serverUrl, tools, reason, chatId, ...(recipientId ? { recipientId } : {}) }, onBehalfOfUserId
170
+ if (!context || !["message", "task", "agreement"].includes(context.kind) || !context.id)
171
+ throw new Error("requestMcpConnection: context is required");
172
+ return (await this._request("POST", "/connections/mcp/requests", { serverUrl, tools, reason, context, ...(recipientId ? { recipientId } : {}) }, onBehalfOfUserId
171
173
  ? { "X-On-Behalf-Of-User": onBehalfOfUserId }
172
174
  : undefined));
173
175
  }
@@ -4,10 +4,10 @@
4
4
  * provider name only (never tokens/account labels).
5
5
  */
6
6
  export interface DiscoverableItem {
7
- type: 'chat' | 'agreement' | 'connection';
7
+ type: 'address' | 'agreement' | 'connection';
8
8
  label: string;
9
9
  scopeRef: {
10
- kind: 'chat' | 'agreement' | 'org' | 'connection';
10
+ kind: 'address' | 'agreement' | 'org' | 'connection';
11
11
  id: string;
12
12
  };
13
13
  orgId: string;
@@ -69,18 +69,16 @@ export interface ReachEntry {
69
69
  label: string;
70
70
  }
71
71
  /**
72
- * reach expansion — the chat/agreement ids a grant you hold actually
72
+ * Reach expansion — the agreement ids a grant you hold actually
73
73
  * covers, so an org/agreement-scoped grant becomes a concrete list you can
74
- * `context_read` through (via=chat:<id> / agreement:<id>). Ids + labels only,
75
- * never content. Org scope is capped; `truncatedChats`/`truncatedAgreements`
76
- * report how many were left off, never silently dropped.
74
+ * inspect using the retained resource operations. Ids + labels only,
75
+ * never content. Org scope is capped; `truncatedAgreements`
76
+ * reports how many were left off.
77
77
  */
78
78
  export interface GrantReachResult {
79
79
  grantId: string;
80
80
  scope: ContextGrantScope;
81
- chats: ReachEntry[];
82
81
  agreements: ReachEntry[];
83
- truncatedChats: number;
84
82
  truncatedAgreements: number;
85
83
  }
86
84
  /**
@@ -98,14 +96,13 @@ export declare class ContextGrantsClient {
98
96
  issueGrant(input: IssueContextGrantInput): Promise<GrantView>;
99
97
  /** Request-only endpoint: a successful call must still have granted no access. */
100
98
  requestAccess(input: {
101
- scopeKind: 'artifact' | 'chat' | 'agreement' | 'task' | 'person';
99
+ scopeKind: 'message' | 'artifact' | 'address' | 'agreement' | 'task' | 'person';
102
100
  scopeId?: string;
103
101
  agreementId?: string;
104
102
  reason: string;
105
103
  permission?: 'read' | 'reply' | 'work' | 'list';
106
104
  temporal?: 'from-now' | 'from-start';
107
105
  durationHours?: number;
108
- chatId?: string;
109
106
  ownerId?: string;
110
107
  /** Who to ask, from the people the server offers this wake. */
111
108
  recipientId?: string;
@@ -121,9 +118,9 @@ export declare class ContextGrantsClient {
121
118
  idempotencyKey?: string;
122
119
  }): Promise<DelegateContextGrantResult>;
123
120
  /**
124
- * expand a grant you hold into the chat/agreement ids inside its
121
+ * Expand a grant you hold into the agreement ids inside its
125
122
  * scope. A grant is a fence, not a listing: discovery says "you hold
126
- * org:acme", this says which chats/agreements that covers. Holder-only,
123
+ * org:acme", this says which agreements that covers. Holder-only,
127
124
  * labels-only, grant-fenced server-side.
128
125
  */
129
126
  getReach(grantId: string): Promise<GrantReachResult>;
@@ -140,7 +137,6 @@ export declare class ContextGrantsClient {
140
137
  shareArtifact(artifactId: string, input: {
141
138
  holderId: string;
142
139
  expiresAt?: string | null;
143
- chatId?: string;
144
140
  }, opts?: {
145
141
  idempotencyKey?: string;
146
142
  }): Promise<DelegateContextGrantResult>;
@@ -182,11 +178,8 @@ export interface RequestSentTo {
182
178
  role: 'customer' | 'operator';
183
179
  };
184
180
  approveIn?: {
185
- kind: 'chat';
186
- chatId: string;
187
- } | {
188
181
  kind: 'inbox';
189
182
  };
190
- /** e.g. "Sent to Ido. Ido approves it in this room." */
183
+ /** Human-readable approval destination. */
191
184
  message?: string;
192
185
  }
@@ -114,9 +114,9 @@ export class ContextGrantsClient {
114
114
  return parseGrantOrPending(body, 'POST /context/grants/:id/delegate');
115
115
  }
116
116
  /**
117
- * expand a grant you hold into the chat/agreement ids inside its
117
+ * Expand a grant you hold into the agreement ids inside its
118
118
  * scope. A grant is a fence, not a listing: discovery says "you hold
119
- * org:acme", this says which chats/agreements that covers. Holder-only,
119
+ * org:acme", this says which agreements that covers. Holder-only,
120
120
  * labels-only, grant-fenced server-side.
121
121
  */
122
122
  async getReach(grantId) {
@@ -127,14 +127,12 @@ export class ContextGrantsClient {
127
127
  }
128
128
  const parsed = JSON.parse(body);
129
129
  if (!parsed.scope) {
130
- throw new Error('Invalid response: expected { scope, chats, agreements } from GET /context/grants/:id/reach');
130
+ throw new Error('Invalid response: expected { scope, agreements } from GET /context/grants/:id/reach');
131
131
  }
132
132
  return {
133
133
  grantId: parsed.grantId ?? grantId,
134
134
  scope: parsed.scope,
135
- chats: parsed.chats ?? [],
136
135
  agreements: parsed.agreements ?? [],
137
- truncatedChats: parsed.truncatedChats ?? 0,
138
136
  truncatedAgreements: parsed.truncatedAgreements ?? 0,
139
137
  };
140
138
  }
@@ -1,4 +1,4 @@
1
- export declare const CONTEXT_OPEN_KINDS: readonly ["artifact", "chat", "task", "agreement"];
1
+ export declare const CONTEXT_OPEN_KINDS: readonly ["artifact", "task", "agreement"];
2
2
  export type ContextOpenKind = (typeof CONTEXT_OPEN_KINDS)[number];
3
3
  export type ContextOpenOutcome = 'ok' | 'unreadable' | 'hidden';
4
4
  export interface ContextOpenRef {
@@ -6,10 +6,9 @@ export interface ContextOpenRef {
6
6
  id: string;
7
7
  }
8
8
  export interface ContextOpenBody {
9
- /** A handle (T…/A…/C…/F…), a Ziggs URL holding one, or an older id. */
9
+ /** A handle (T…/A…/F…), a Ziggs URL holding one, or an older id. */
10
10
  ref?: string;
11
11
  artifactId?: string;
12
- chatId?: string;
13
12
  taskId?: string;
14
13
  agreementId?: string;
15
14
  cursor?: string;
@@ -50,10 +49,10 @@ export interface ContextOpenAccess {
50
49
  kind: 'owner_decision';
51
50
  summary: string;
52
51
  request?: {
53
- scopeKind: 'chat' | 'agreement' | 'task';
52
+ scopeKind: 'agreement' | 'task';
54
53
  scopeId: string;
55
- permission: 'read' | 'reply';
56
- temporal: 'from-now' | 'from-start';
54
+ permission: 'read';
55
+ temporal: 'from-start';
57
56
  durationHours: number;
58
57
  };
59
58
  };