@ziggs-ai/api-client 0.40.4 → 0.40.6

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.
@@ -173,18 +173,21 @@ export const contextDiscoverGrantableCapability = {
173
173
  })) };
174
174
  },
175
175
  };
176
+ /** What a grant may be narrowed to when passed on. An artifact is not among
177
+ * them: it reaches people only by being added to a bucket. */
178
+ const DELEGABLE_SCOPE_KINDS = GRANT_SCOPE_KINDS.filter((kind) => kind !== 'artifact');
176
179
  export const contextDelegateCapability = {
177
180
  key: 'context_delegate',
178
181
  names: { sdk: 'context_delegate', mcp: 'ziggs_context_delegate' },
179
182
  title: 'Pass on a narrower grant',
180
183
  descriptions: {
181
- sdk: 'Delegate a narrower child context grant (tighter scope, shorter expiry, or from-now). Holder-only. Delegating a grant whose original owner is another party opens an approval request rather than minting immediately (status: pending_approval).',
184
+ sdk: 'Delegate a narrower child context grant (tighter scope, shorter expiry, or from-now). Holder-only. Delegating a grant whose original owner is another party opens an approval request rather than minting immediately (status: pending_approval). An artifact is not passed on this way: add it to a bucket.',
182
185
  },
183
186
  annotation: 'write',
184
187
  params: {
185
188
  parentGrantId: { type: 'string', required: true },
186
189
  holderId: { type: 'string', required: true, description: 'Agent receiving the delegated grant' },
187
- scopeKind: { type: 'string', required: true, enum: GRANT_SCOPE_KINDS },
190
+ scopeKind: { type: 'string', required: true, enum: DELEGABLE_SCOPE_KINDS },
188
191
  scopeId: { type: 'string', required: true },
189
192
  temporal: {
190
193
  type: 'string',
@@ -210,8 +213,8 @@ export const contextDelegateCapability = {
210
213
  throw new Error('temporal must be from-now or from-start');
211
214
  }
212
215
  const scopeKind = args['scopeKind'];
213
- if (!GRANT_SCOPE_KINDS.includes(scopeKind)) {
214
- throw new Error(`scopeKind must be one of ${GRANT_SCOPE_KINDS.join(', ')}`);
216
+ if (!DELEGABLE_SCOPE_KINDS.includes(scopeKind)) {
217
+ throw new Error(`scopeKind must be one of ${DELEGABLE_SCOPE_KINDS.join(', ')}; an artifact is shared by adding it to a bucket`);
215
218
  }
216
219
  // #6 — an org scope may be named rather than pasted as org_… id.
217
220
  let scopeId = args['scopeId'];
@@ -42,7 +42,7 @@ export const listGrantsCapability = {
42
42
  names: { sdk: 'grant_list', mcp: 'ziggs_grant_list' },
43
43
  title: 'List grants you hold',
44
44
  descriptions: {
45
- sdk: 'List grants this agent holds — or, with role=issuer, grants this agent caused (author-shares and delegate children). Context (chat/agreement/org/artifact/task), connection, wallet, consent, and inbox — canonical grants (grantId, scope, caveats, expiresAt, health; no content or credentials). A `task` scope is a BRANCH of a work graph: the named task and everything under it, which is what holding a pass on a job looks like. Default role=holder answers "what grants do I HOLD?", which is narrower than "what can I reach?": your own authorship, and grants held by an org you belong to rather than by you, leave nothing under your own id — an empty holder list means "no grants of your own", never "no access". role=issuer answers "who did I share with / what did I mint?" after artifact_share. Filter by scopeKind, scopeId, and health (defaults to active). Rails you lack the operator-key read scope for are named in unreadableRails. Cursor-paginated.',
45
+ sdk: 'List grants this agent holds — or, with role=issuer, grants this agent caused (author-shares and delegate children). Context (chat/agreement/org/artifact/task), connection, wallet, consent, and inbox — canonical grants (grantId, scope, caveats, expiresAt, health; no content or credentials). A `task` scope is a BRANCH of a work graph: the named task and everything under it, which is what holding a pass on a job looks like. Default role=holder answers "what grants do I HOLD?", which is narrower than "what can I reach?": your own authorship, and grants held by an org you belong to rather than by you, leave nothing under your own id — an empty holder list means "no grants of your own", never "no access". role=issuer answers "who did I share with / what did I mint?" after a bucket share. Filter by scopeKind, scopeId, and health (defaults to active). Rails you lack the operator-key read scope for are named in unreadableRails. Cursor-paginated.',
46
46
  },
47
47
  annotation: 'read-only',
48
48
  params: {
@@ -13,10 +13,11 @@ export { TASK_CAPABILITIES, listTasksCapability, cancelTaskCapability, delegateT
13
13
  export { AGREEMENT_PROPOSE_PROVIDER_ID_DESCRIPTION } from './proposeProviderId.js';
14
14
  export { MARKETPLACE_CAPABILITIES, marketplaceViewCapability } from './marketplace.js';
15
15
  export { GRANTS_CAPABILITIES, listGrantsCapability } from './grants.js';
16
+ export { BUCKET_CAPABILITIES, bucketCreateCapability, bucketListCapability, bucketReadCapability, bucketInsertCapability, bucketRemoveCapability, bucketMoveCapability, bucketAddMembersCapability, bucketJoinCapability, bucketSendCapability, bucketRemoveMemberCapability, bucketDuplicateCapability, bucketRenameCapability, bucketDeleteCapability, } from './buckets.js';
16
17
  export { CONTEXT_CAPABILITIES, contextReadCapability, openCapability, accessExplainCapability, contextDelegateCapability, accessRequestCapability, contextExpandReachCapability, contextDiscoverGrantableCapability, contextBounds, resolveOrgScopeId, explainRefusedRead, RESOURCE_HIDDEN_MESSAGE, RESOURCE_UNREADABLE_MESSAGE, type RefusedRead, } from './context.js';
17
18
  export { CONNECTION_CAPABILITIES, connectionProxyCapability, requestConnectionCapability, } from './connections.js';
18
19
  export { DISCOVERY_CAPABILITIES, agentSearchCapability, agentGetCapability, } from './discovery.js';
19
- export { ARTIFACT_CAPABILITIES, recordArtifactCapability, listArtifactsCapability, findArtifactsCapability, type UnsearchedContext, shareArtifactCapability, attachArtifactCapability, reviewArtifactContributionCapability, contributeArtifactCapability, uploadArtifactUrlCapability, completeArtifactFileCapability, downloadArtifactCapability, reextractArtifactCapability, } from './artifacts.js';
20
+ export { ARTIFACT_CAPABILITIES, recordArtifactCapability, listArtifactsCapability, findArtifactsCapability, type UnsearchedContext, attachArtifactCapability, reviewArtifactContributionCapability, contributeArtifactCapability, uploadArtifactUrlCapability, completeArtifactFileCapability, downloadArtifactCapability, reextractArtifactCapability, } from './artifacts.js';
20
21
  export { EVERYDAY_MCP_TOOLS, EVERYDAY_NO_SDK_TWIN, EVERYDAY_SDK_TOOLS, } from './everyday.js';
21
22
  export { annotationForSdkTool } from './toolLane.js';
22
23
  export { presentResourceActions } from './resourceActions.js';
@@ -13,10 +13,11 @@ export { TASK_CAPABILITIES, listTasksCapability, cancelTaskCapability, delegateT
13
13
  export { AGREEMENT_PROPOSE_PROVIDER_ID_DESCRIPTION } from './proposeProviderId.js';
14
14
  export { MARKETPLACE_CAPABILITIES, marketplaceViewCapability } from './marketplace.js';
15
15
  export { GRANTS_CAPABILITIES, listGrantsCapability } from './grants.js';
16
+ export { BUCKET_CAPABILITIES, bucketCreateCapability, bucketListCapability, bucketReadCapability, bucketInsertCapability, bucketRemoveCapability, bucketMoveCapability, bucketAddMembersCapability, bucketJoinCapability, bucketSendCapability, bucketRemoveMemberCapability, bucketDuplicateCapability, bucketRenameCapability, bucketDeleteCapability, } from './buckets.js';
16
17
  export { CONTEXT_CAPABILITIES, contextReadCapability, openCapability, accessExplainCapability, contextDelegateCapability, accessRequestCapability, contextExpandReachCapability, contextDiscoverGrantableCapability, contextBounds, resolveOrgScopeId, explainRefusedRead, RESOURCE_HIDDEN_MESSAGE, RESOURCE_UNREADABLE_MESSAGE, } from './context.js';
17
18
  export { CONNECTION_CAPABILITIES, connectionProxyCapability, requestConnectionCapability, } from './connections.js';
18
19
  export { DISCOVERY_CAPABILITIES, agentSearchCapability, agentGetCapability, } from './discovery.js';
19
- export { ARTIFACT_CAPABILITIES, recordArtifactCapability, listArtifactsCapability, findArtifactsCapability, shareArtifactCapability, attachArtifactCapability, reviewArtifactContributionCapability, contributeArtifactCapability, uploadArtifactUrlCapability, completeArtifactFileCapability, downloadArtifactCapability, reextractArtifactCapability, } from './artifacts.js';
20
+ export { ARTIFACT_CAPABILITIES, recordArtifactCapability, listArtifactsCapability, findArtifactsCapability, attachArtifactCapability, reviewArtifactContributionCapability, contributeArtifactCapability, uploadArtifactUrlCapability, completeArtifactFileCapability, downloadArtifactCapability, reextractArtifactCapability, } from './artifacts.js';
20
21
  export { EVERYDAY_MCP_TOOLS, EVERYDAY_NO_SDK_TWIN, EVERYDAY_SDK_TOOLS, } from './everyday.js';
21
22
  export { annotationForSdkTool } from './toolLane.js';
22
23
  export { presentResourceActions } from './resourceActions.js';
@@ -66,7 +66,6 @@ export const listLinksCapability = {
66
66
  // is now, as `peer`, resolved relative to whoever asked.
67
67
  const readPlan = active.length ? [
68
68
  nextCall(env, 'message_send', undefined, 'Send an explicit message command to a returned peer.id; choose readers, delivery recipients and the intent (fyi or request) separately. A link grants no history access.'),
69
- nextCall(env, 'artifact_share', undefined, 'Share only the selected artifact under its grant and consent policy.'),
70
69
  ] : [];
71
70
  return {
72
71
  count: links.length,
@@ -165,10 +165,6 @@ const BRIEFS = {
165
165
  ziggs_artifact_find: {
166
166
  does: 'Searches artifacts you can read by name, filename or body. coverage and notSearched say what was and was not searched.',
167
167
  },
168
- ziggs_artifact_share: {
169
- does: 'Shares one artifact you authored with another agent.',
170
- changes: 'grants it at once to a connected agent; otherwise asks your owner to approve (pending_approval)',
171
- },
172
168
  ziggs_message_send: { does: 'Sends a canonical command with independent readers, recipients, an intent (fyi or request) and optional mandate.', changes: 'delivers a message and grants its readers access; a request wakes the agents it reaches' },
173
169
  ziggs_message_replies: { does: 'Reads authorized causal replies by messageId; a reference grants no access and receipt expiry does not erase history.' },
174
170
  ziggs_message_read: { does: 'Reads an exact message by messageId under current access and execution policy. Does not acknowledge its receipt.' },
@@ -291,6 +287,57 @@ const BRIEFS = {
291
287
  does: 'Calls one tool on a connected MCP server through the Ziggs gateway, within your grant. ziggs_connection_proxy refuses MCP servers (Unknown provider: mcp).',
292
288
  changes: 'whatever that tool changes',
293
289
  },
290
+ // Buckets: pointers to items and people; access is computed from the items' grants.
291
+ ziggs_bucket_create: {
292
+ does: 'Creates a bucket, with items if given.',
293
+ changes: 'creates it with what fits',
294
+ },
295
+ ziggs_bucket_list: {
296
+ does: 'Lists the buckets you are in.',
297
+ },
298
+ ziggs_bucket_read: {
299
+ does: 'Reads a bucket and a page of its items; those someone cannot read are set apart.',
300
+ },
301
+ ziggs_bucket_insert: {
302
+ does: 'Puts items in a bucket if everyone in it can read them.',
303
+ changes: 'adds what fits; share may share',
304
+ },
305
+ ziggs_bucket_remove: {
306
+ does: 'Takes items out of a bucket. The items are untouched.',
307
+ changes: 'removes them',
308
+ },
309
+ ziggs_bucket_duplicate: {
310
+ does: 'Copies items into a new bucket for another audience. The original stays.',
311
+ changes: 'creates it with what fits',
312
+ },
313
+ ziggs_bucket_move: {
314
+ does: 'Moves items to another or a new bucket, if its audience can read them.',
315
+ changes: 'moves what fits',
316
+ },
317
+ ziggs_bucket_add_members: {
318
+ does: 'Adds to a bucket audience. Who cannot read an item needs onMisfit share or split.',
319
+ changes: 'adds them',
320
+ },
321
+ ziggs_bucket_join: {
322
+ does: "Brings another bucket's people and items in. Who cannot read an item needs onMisfit share.",
323
+ changes: 'adds them',
324
+ },
325
+ ziggs_bucket_send: {
326
+ does: "Sends a message inside a bucket, to its people.",
327
+ changes: 'sends it',
328
+ },
329
+ ziggs_bucket_rename: {
330
+ does: 'Renames a bucket. Its creator does this.',
331
+ changes: 'the name',
332
+ },
333
+ ziggs_bucket_delete: {
334
+ does: 'Deletes a bucket. The items are untouched. Its creator does this.',
335
+ changes: 'deletes it',
336
+ },
337
+ ziggs_bucket_remove_member: {
338
+ does: 'Takes someone out of a bucket. Its creator does this.',
339
+ changes: 'removes them',
340
+ },
294
341
  ziggs_payment_balance: {
295
342
  does: 'Shows your points balance and what is available after holds. Points settle through work; here they cannot be bought, cashed out or transferred.',
296
343
  },
@@ -30,7 +30,6 @@ const REQUIRED_ARGS = {
30
30
  agreement_subcontract: ['parentAgreementId', 'executorId', 'description'],
31
31
  agreement_respond: ['agreementId'],
32
32
  introduction_redeem: ['token'],
33
- artifact_share: ['artifactId', 'holderId'],
34
33
  context_delegate: ['parentGrantId', 'holderId', 'scopeKind', 'scopeId', 'temporal'],
35
34
  task_create: ['agreementId', 'description'],
36
35
  task_cancel: ['taskId'],
@@ -61,7 +60,6 @@ const HTTP_BINDINGS = {
61
60
  path: '/agreements/:agreementId/approvals/:partyId',
62
61
  },
63
62
  marketplace_view: { method: 'GET', path: '/marketplace/offers' },
64
- artifact_share: { method: 'POST', path: '/context/artifacts/:artifactId/share' },
65
63
  context_delegate: { method: 'POST', path: '/context/grants/:parentGrantId/delegate' },
66
64
  open: { method: 'POST', path: '/context/open' },
67
65
  access_explain: { method: 'POST', path: '/context/access/explain' },
@@ -45,6 +45,11 @@ export interface ProposeDirectInput extends ProposeTerms {
45
45
  proposedTo: string;
46
46
  /** Optional display-reference context for resolving a masked party; does not publish or share. */
47
47
  addressId?: string;
48
+ /**
49
+ * The bucket the agreement goes into, one you are in. Left out: the bucket
50
+ * your run works in, else a new one. The other side comes in when it starts.
51
+ */
52
+ bucketId?: string;
48
53
  }
49
54
  /**
50
55
  * Open buyer-broadcast. `audience` selects who may see/claim it:
@@ -134,6 +139,11 @@ export interface DelegateAgreementData {
134
139
  interactionMode?: 'messages' | 'tasks' | 'both';
135
140
  agreementDescription?: string;
136
141
  payerId?: string;
142
+ /**
143
+ * The bucket the sub-agreement goes into, one you are in. Left out: the
144
+ * bucket your run works in, else the parent's buckets, else a new one.
145
+ */
146
+ bucketId?: string;
137
147
  idempotencyKey?: string;
138
148
  }
139
149
  export declare function delegateAgreement(proposalData: DelegateAgreementData, creds: Creds): Promise<Agreement>;
@@ -328,6 +338,11 @@ export type ClaimedKind = 'link' | 'offer' | 'request' | 'hand-off';
328
338
  */
329
339
  export interface ClaimOptions {
330
340
  parentAgreementId?: string;
341
+ /**
342
+ * The bucket the hire goes into, one you are in. Left out: the bucket your
343
+ * run works in, else a new one. The other side comes in when it starts.
344
+ */
345
+ bucketId?: string;
331
346
  /**
332
347
  * The active agreement whose work this claim is part of. An agent acting
333
348
  * inside a job its human approved does not need a second consent for the
@@ -559,7 +559,7 @@ export async function claimAgreement(agreementId, creds, opts = {}) {
559
559
  const res = await fetch(`${getAgreementBaseUrl()}/${encodeURIComponent(agreementId)}/claim`, {
560
560
  method: 'POST',
561
561
  headers: buildHeaders(creds),
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' } : {}) }),
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' } : {}), ...(opts.bucketId ? { bucketId: opts.bucketId } : {}) }),
563
563
  });
564
564
  if (!res.ok) {
565
565
  const body = await res.text().catch(() => '');
@@ -0,0 +1,219 @@
1
+ import type { MessageCommand, MessageCommandResult } from '@ziggs-ai/contracts';
2
+ /** What a bucket can hold. Each item is a pointer, never a copy. */
3
+ export declare const BUCKET_ITEM_KINDS: readonly ["message", "artifact", "task", "agreement"];
4
+ export type BucketItemKind = (typeof BUCKET_ITEM_KINDS)[number];
5
+ export interface BucketItemRef {
6
+ kind: BucketItemKind;
7
+ id: string;
8
+ }
9
+ /**
10
+ * An item that does not fit where it was asked to go, or no longer fits where
11
+ * it is. Other members are named only when the caller can read the item.
12
+ */
13
+ export type BucketMisfit = {
14
+ item: BucketItemRef;
15
+ reason: 'you-cannot-read';
16
+ } | {
17
+ item: BucketItemRef;
18
+ reason: 'members-cannot-read';
19
+ memberIds: string[];
20
+ };
21
+ /**
22
+ * What to do when someone being added cannot read an item: share it with
23
+ * them, or split: start a narrower bucket with them and only what everyone
24
+ * can read, leaving this one as it is.
25
+ */
26
+ export type BucketMisfitChoice = 'share' | 'split';
27
+ export interface BucketView {
28
+ bucketId: string;
29
+ name: string;
30
+ createdBy: string;
31
+ /** Who it is for, as given: people, agents, workspaces. */
32
+ audience: string[];
33
+ /** Goes up when an item leaves or the audience changes. */
34
+ revision: number;
35
+ /** Items held; whether each still fits is computed when the items are read. */
36
+ itemCount: number;
37
+ createdAt: string | null;
38
+ }
39
+ /** One share made because the caller chose to share. */
40
+ export interface BucketShare {
41
+ item: BucketItemRef;
42
+ holderId: string;
43
+ /** `pending` waits for the item owner's yes; the item stays out until then. */
44
+ outcome: 'shared' | 'pending' | 'refused';
45
+ }
46
+ export interface BucketInsertResult {
47
+ bucket: BucketView;
48
+ inserted: BucketItemRef[];
49
+ alreadyIn: BucketItemRef[];
50
+ misfits: BucketMisfit[];
51
+ shared: BucketShare[];
52
+ }
53
+ export interface BucketMoveResult {
54
+ source: BucketView;
55
+ bucket: BucketView;
56
+ moved: BucketItemRef[];
57
+ notIn: BucketItemRef[];
58
+ misfits: BucketMisfit[];
59
+ }
60
+ export interface BucketAddMembersResult {
61
+ bucket: BucketView;
62
+ added: string[];
63
+ shared: BucketShare[];
64
+ /** The narrower bucket a split started, and what it holds. This bucket is left as it was. */
65
+ split: {
66
+ bucket: BucketView;
67
+ items: BucketItemRef[];
68
+ } | null;
69
+ }
70
+ export interface BucketJoinResult {
71
+ bucket: BucketView;
72
+ added: string[];
73
+ joined: BucketItemRef[];
74
+ shared: BucketShare[];
75
+ }
76
+ /** One thread in a bucket, as the reader sees it. */
77
+ export interface BucketThread {
78
+ rootMessageId: string;
79
+ title: string;
80
+ messageCount: number;
81
+ lastAt: string | null;
82
+ /** The reader wrote in it or it was delivered to them. */
83
+ yours: boolean;
84
+ /** The thread holding the message asked about as `current`. */
85
+ current: boolean;
86
+ /** The reader's own threads, other than the current one, come with their latest messages. */
87
+ recent?: Array<{
88
+ messageId: string;
89
+ from: string;
90
+ text: string;
91
+ at: string;
92
+ }>;
93
+ }
94
+ /** What is in a bucket as the reader can read it now: what a run starts from. */
95
+ export interface BucketContents {
96
+ bucket: {
97
+ bucketId: string;
98
+ name: string;
99
+ audience: string[];
100
+ revision: number;
101
+ };
102
+ threads: BucketThread[];
103
+ artifacts: Array<{
104
+ artifactId: string;
105
+ name: string | null;
106
+ }>;
107
+ tasks: Array<{
108
+ taskId: string;
109
+ title: string;
110
+ state: string;
111
+ }>;
112
+ agreements: Array<{
113
+ agreementId: string;
114
+ title: string;
115
+ status: string;
116
+ }>;
117
+ /** Items held that the reader cannot read here, as they ask now. */
118
+ outOfReach: number;
119
+ }
120
+ /** A message sent inside a bucket: its readers are the bucket's people. */
121
+ export type BucketMessage = Omit<MessageCommand, 'audience'>;
122
+ export interface BucketItemPage {
123
+ /** Items everyone it is for can read right now. */
124
+ items: Array<BucketItemRef & {
125
+ addedBy: string;
126
+ addedAt: string | null;
127
+ }>;
128
+ /** Items held that someone it is for can no longer read. */
129
+ outOfReach: BucketMisfit[];
130
+ nextCursor: string | null;
131
+ }
132
+ /**
133
+ * The bucket operations (`/buckets`). A bucket is pointers to items and to who
134
+ * it is for; it stores no permission. Whether everyone can read each item is
135
+ * computed from the items' own grants whenever it is asked.
136
+ *
137
+ * Every message is sent through a bucket and the bucket points at it; no
138
+ * message names its bucket. `listMine` with an item finds the buckets holding
139
+ * it, the first being where a message was sent.
140
+ *
141
+ * Changing a bucket is not part of a run, so those calls never carry the
142
+ * selected execution. Sending inside a bucket is: it carries the run, and the
143
+ * run's own guards apply to it.
144
+ */
145
+ export declare class BucketsClient {
146
+ private readonly operatorKey;
147
+ private readonly agentId?;
148
+ private readonly baseUrl?;
149
+ constructor(operatorKey: string, agentId?: string | undefined, baseUrl?: string | undefined);
150
+ create(input: {
151
+ name: string;
152
+ audience?: string[];
153
+ items?: BucketItemRef[];
154
+ share?: boolean;
155
+ }): Promise<BucketInsertResult>;
156
+ /** The buckets you are in; given an item, those holding it, in the order it went in. */
157
+ listMine(item?: BucketItemRef): Promise<{
158
+ buckets: BucketView[];
159
+ }>;
160
+ /** Send a message inside a bucket, under the selected run when there is one. */
161
+ sendMessage(bucketId: string, message: BucketMessage): Promise<MessageCommandResult>;
162
+ /**
163
+ * What is in a bucket as you can read it now, under the selected run when
164
+ * there is one: your threads with their latest messages, the rest by title.
165
+ * `current` names the message being answered; its thread is marked.
166
+ */
167
+ contents(bucketId: string, opts?: {
168
+ current?: string;
169
+ }): Promise<BucketContents>;
170
+ /** Bring another bucket's people and items in. The other bucket stays. */
171
+ join(bucketId: string, input: {
172
+ from: string;
173
+ onMisfit?: 'share';
174
+ }): Promise<BucketJoinResult>;
175
+ get(bucketId: string): Promise<BucketView>;
176
+ items(bucketId: string, opts?: {
177
+ before?: string;
178
+ limit?: number;
179
+ }): Promise<BucketItemPage>;
180
+ insert(bucketId: string, items: BucketItemRef[], opts?: {
181
+ share?: boolean;
182
+ }): Promise<BucketInsertResult>;
183
+ remove(bucketId: string, items: BucketItemRef[]): Promise<{
184
+ bucket: BucketView;
185
+ removed: BucketItemRef[];
186
+ notIn: BucketItemRef[];
187
+ }>;
188
+ duplicate(bucketId: string, input: {
189
+ name: string;
190
+ audience?: string[];
191
+ items?: BucketItemRef[];
192
+ share?: boolean;
193
+ }): Promise<BucketInsertResult & {
194
+ notIn: BucketItemRef[];
195
+ }>;
196
+ move(bucketId: string, input: {
197
+ to: string;
198
+ items: BucketItemRef[];
199
+ }): Promise<BucketMoveResult>;
200
+ split(bucketId: string, input: {
201
+ items: BucketItemRef[];
202
+ name: string;
203
+ audience?: string[];
204
+ }): Promise<BucketMoveResult>;
205
+ addMembers(bucketId: string, principalIds: string[], opts?: {
206
+ onMisfit?: BucketMisfitChoice;
207
+ }): Promise<BucketAddMembersResult>;
208
+ removeMember(bucketId: string, principalId: string): Promise<{
209
+ bucket: BucketView;
210
+ removed: boolean;
211
+ }>;
212
+ rename(bucketId: string, name: string): Promise<BucketView>;
213
+ delete(bucketId: string): Promise<{
214
+ bucketId: string;
215
+ deleted: boolean;
216
+ }>;
217
+ private path;
218
+ private request;
219
+ }
@@ -0,0 +1,109 @@
1
+ import { getBackendUrl } from '../utils/urlUtils.js';
2
+ import { throwApiError } from '../shared/apiError.js';
3
+ import { buildOperatorHeaders, withoutExecution } from './operatorHeaders.js';
4
+ /** What a bucket can hold. Each item is a pointer, never a copy. */
5
+ export const BUCKET_ITEM_KINDS = ['message', 'artifact', 'task', 'agreement'];
6
+ /**
7
+ * The bucket operations (`/buckets`). A bucket is pointers to items and to who
8
+ * it is for; it stores no permission. Whether everyone can read each item is
9
+ * computed from the items' own grants whenever it is asked.
10
+ *
11
+ * Every message is sent through a bucket and the bucket points at it; no
12
+ * message names its bucket. `listMine` with an item finds the buckets holding
13
+ * it, the first being where a message was sent.
14
+ *
15
+ * Changing a bucket is not part of a run, so those calls never carry the
16
+ * selected execution. Sending inside a bucket is: it carries the run, and the
17
+ * run's own guards apply to it.
18
+ */
19
+ export class BucketsClient {
20
+ operatorKey;
21
+ agentId;
22
+ baseUrl;
23
+ constructor(operatorKey, agentId, baseUrl) {
24
+ this.operatorKey = operatorKey;
25
+ this.agentId = agentId;
26
+ this.baseUrl = baseUrl;
27
+ if (!operatorKey)
28
+ throw new Error('BucketsClient: operatorKey is required');
29
+ }
30
+ create(input) {
31
+ return this.request('POST', '/buckets', input, 'bucket create');
32
+ }
33
+ /** The buckets you are in; given an item, those holding it, in the order it went in. */
34
+ listMine(item) {
35
+ const query = item ? `?${new URLSearchParams({ kind: item.kind, id: item.id })}` : '';
36
+ return this.request('GET', `/buckets${query}`, undefined, 'bucket list');
37
+ }
38
+ /** Send a message inside a bucket, under the selected run when there is one. */
39
+ sendMessage(bucketId, message) {
40
+ return this.request('POST', `${this.path(bucketId)}/send`, message, 'bucket send', { inRun: true });
41
+ }
42
+ /**
43
+ * What is in a bucket as you can read it now, under the selected run when
44
+ * there is one: your threads with their latest messages, the rest by title.
45
+ * `current` names the message being answered; its thread is marked.
46
+ */
47
+ contents(bucketId, opts = {}) {
48
+ const query = opts.current ? `?${new URLSearchParams({ current: opts.current })}` : '';
49
+ return this.request('GET', `${this.path(bucketId)}/contents${query}`, undefined, 'bucket contents', { inRun: true });
50
+ }
51
+ /** Bring another bucket's people and items in. The other bucket stays. */
52
+ join(bucketId, input) {
53
+ return this.request('POST', `${this.path(bucketId)}/join`, input, 'bucket join');
54
+ }
55
+ get(bucketId) {
56
+ return this.request('GET', this.path(bucketId), undefined, 'bucket read');
57
+ }
58
+ items(bucketId, opts = {}) {
59
+ const query = new URLSearchParams();
60
+ if (opts.before)
61
+ query.set('before', opts.before);
62
+ if (opts.limit !== undefined)
63
+ query.set('limit', String(opts.limit));
64
+ const suffix = query.size ? `?${query}` : '';
65
+ return this.request('GET', `${this.path(bucketId)}/items${suffix}`, undefined, 'bucket items');
66
+ }
67
+ insert(bucketId, items, opts = {}) {
68
+ return this.request('POST', `${this.path(bucketId)}/insert`, { items, ...(opts.share ? { share: true } : {}) }, 'bucket insert');
69
+ }
70
+ remove(bucketId, items) {
71
+ return this.request('POST', `${this.path(bucketId)}/remove`, { items }, 'bucket remove');
72
+ }
73
+ duplicate(bucketId, input) {
74
+ return this.request('POST', `${this.path(bucketId)}/duplicate`, input, 'bucket duplicate');
75
+ }
76
+ move(bucketId, input) {
77
+ return this.request('POST', `${this.path(bucketId)}/move`, input, 'bucket move');
78
+ }
79
+ split(bucketId, input) {
80
+ return this.request('POST', `${this.path(bucketId)}/split`, input, 'bucket split');
81
+ }
82
+ addMembers(bucketId, principalIds, opts = {}) {
83
+ return this.request('POST', `${this.path(bucketId)}/members`, { principalIds, ...(opts.onMisfit ? { onMisfit: opts.onMisfit } : {}) }, 'bucket add members');
84
+ }
85
+ removeMember(bucketId, principalId) {
86
+ return this.request('DELETE', `${this.path(bucketId)}/members/${encodeURIComponent(principalId)}`, undefined, 'bucket remove member');
87
+ }
88
+ rename(bucketId, name) {
89
+ return this.request('PATCH', this.path(bucketId), { name }, 'bucket rename');
90
+ }
91
+ delete(bucketId) {
92
+ return this.request('DELETE', this.path(bucketId), undefined, 'bucket delete');
93
+ }
94
+ path(bucketId) {
95
+ return `/buckets/${encodeURIComponent(bucketId)}`;
96
+ }
97
+ async request(method, path, body, label, opts = {}) {
98
+ const headers = buildOperatorHeaders(this.operatorKey, this.agentId, body === undefined ? undefined : { 'content-type': 'application/json' });
99
+ const res = await fetch(`${this.baseUrl ?? getBackendUrl()}${path}`, {
100
+ method,
101
+ headers: opts.inRun ? headers : withoutExecution(headers),
102
+ ...(body === undefined ? {} : { body: JSON.stringify(body) }),
103
+ });
104
+ const text = await res.text().catch(() => '');
105
+ if (!res.ok)
106
+ throwApiError(res, text, `${label} failed: ${res.status} ${res.statusText}`);
107
+ return (text ? JSON.parse(text) : {});
108
+ }
109
+ }
@@ -124,22 +124,6 @@ export declare class ContextGrantsClient {
124
124
  * labels-only, grant-fenced server-side.
125
125
  */
126
126
  getReach(grantId: string): Promise<GrantReachResult>;
127
- /**
128
- * share an artifact THIS agent authored with another agent.
129
- *
130
- * Not a delegation: an agent holds no grant over its own output, so there is
131
- * no parent to attenuate. Authorship is the authority, held by the agent's
132
- * owner — which is why the receiver decides the outcome. A receiver already
133
- * inside the sharer's trust boundary (same owner, same org, or an active link)
134
- * is granted immediately; anyone else opens a request the owner approves,
135
- * exactly like `delegateGrant`'s new-party path.
136
- */
137
- shareArtifact(artifactId: string, input: {
138
- holderId: string;
139
- expiresAt?: string | null;
140
- }, opts?: {
141
- idempotencyKey?: string;
142
- }): Promise<DelegateContextGrantResult>;
143
127
  revokeGrant(grantId: string): Promise<{
144
128
  status: string;
145
129
  revokedCount?: number;
@@ -136,33 +136,6 @@ export class ContextGrantsClient {
136
136
  truncatedAgreements: parsed.truncatedAgreements ?? 0,
137
137
  };
138
138
  }
139
- /**
140
- * share an artifact THIS agent authored with another agent.
141
- *
142
- * Not a delegation: an agent holds no grant over its own output, so there is
143
- * no parent to attenuate. Authorship is the authority, held by the agent's
144
- * owner — which is why the receiver decides the outcome. A receiver already
145
- * inside the sharer's trust boundary (same owner, same org, or an active link)
146
- * is granted immediately; anyone else opens a request the owner approves,
147
- * exactly like `delegateGrant`'s new-party path.
148
- */
149
- async shareArtifact(artifactId, input, opts) {
150
- const res = await fetch(`${this.baseUrl}/context/artifacts/${encodeURIComponent(artifactId)}/share`, {
151
- method: 'POST',
152
- headers: buildOperatorHeaders(this.operatorKey, this.agentId, {
153
- 'content-type': 'application/json',
154
- ...(opts?.idempotencyKey
155
- ? { 'Idempotency-Key': opts.idempotencyKey }
156
- : {}),
157
- }),
158
- body: JSON.stringify(input),
159
- });
160
- const body = await res.text().catch(() => '');
161
- if (!res.ok) {
162
- throwApiError(res, body, `shareArtifact failed: ${res.status} ${res.statusText}`);
163
- }
164
- return parseGrantOrPending(body, 'POST /context/artifacts/:id/share');
165
- }
166
139
  async revokeGrant(grantId) {
167
140
  const res = await fetch(`${this.baseUrl}/context/grants/${encodeURIComponent(grantId)}`, {
168
141
  method: 'DELETE',
@@ -28,6 +28,8 @@ export type { MyOrg, OrgResolution } from './OrgsClient.js';
28
28
  export { AgentSearchClient } from './AgentSearchClient.js';
29
29
  export { TelemetryClient } from './TelemetryClient.js';
30
30
  export { InboxClient } from './InboxClient.js';
31
+ export { BucketsClient, BUCKET_ITEM_KINDS } from './BucketsClient.js';
32
+ export type { BucketItemKind, BucketItemRef, BucketItemPage, BucketAddMembersResult, BucketContents, BucketThread, BucketJoinResult, BucketMessage, BucketInsertResult, BucketMisfit, BucketMisfitChoice, BucketMoveResult, BucketShare, BucketView, } from './BucketsClient.js';
31
33
  export { SessionsClient } from './SessionsClient.js';
32
34
  export type { AssistantSessionRow } from './SessionsClient.js';
33
35
  export { planInboxAck, planPartialInboxAck, inboxAckAllowed, inboxAckHandledIds, } from '../inboxAck.js';
@@ -19,6 +19,7 @@ export { fetchMyOrgs, resolveOrgSelector, fetchDelegateAccess, fetchHostedAgentA
19
19
  export { AgentSearchClient } from './AgentSearchClient.js';
20
20
  export { TelemetryClient } from './TelemetryClient.js';
21
21
  export { InboxClient } from './InboxClient.js';
22
+ export { BucketsClient, BUCKET_ITEM_KINDS } from './BucketsClient.js';
22
23
  export { SessionsClient } from './SessionsClient.js';
23
24
  export { planInboxAck, planPartialInboxAck, inboxAckAllowed, inboxAckHandledIds, } from '../inboxAck.js';
24
25
  // The lane header, built in one place — see operatorHeaders.ts. Exported
@@ -12,7 +12,8 @@ export interface OperationError {
12
12
  /**
13
13
  * What the caller may pick from, when the refusal is a choice: the
14
14
  * agreements of WORK_SELECTION_REQUIRED, the workspaces of
15
- * WORKSPACE_SELECTION_REQUIRED. Carried through verbatim so an assistant can
15
+ * WORKSPACE_SELECTION_REQUIRED, the onMisfit values (share, split) of
16
+ * BUCKET_MEMBERS_CANNOT_READ. Carried through verbatim so an assistant can
16
17
  * ask its human instead of re-reading the message.
17
18
  */
18
19
  choices?: unknown[];
@@ -22,6 +23,15 @@ export interface OperationError {
22
23
  * the same shape an open's action recovery has.
23
24
  */
24
25
  recovery?: Record<string, unknown>;
26
+ /**
27
+ * What the refusal is about, when the server said: the misfits of
28
+ * BUCKET_MEMBERS_CANNOT_READ (with any shares made before it stopped), the
29
+ * bucket and missing readers of BUCKET_READERS_MISSING, the live outside
30
+ * engagements that stop a workspace blocking outside agents. Carried
31
+ * through verbatim so an assistant can list
32
+ * them instead of re-reading the message.
33
+ */
34
+ impact?: Record<string, unknown>;
25
35
  /** Scoped restart position returned when too many observation streams exist. */
26
36
  recoveryCursor?: string;
27
37
  /** hidden | unreadable, the same outcome open and access_explain give. */