@ziggs-ai/api-client 0.28.0 → 0.29.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.
@@ -66,6 +66,7 @@ export const openConversationCapability = {
66
66
  participantIds: { type: 'array', items: { type: 'string' }, description: '1–20 known ids. Without chatId: people, for a separate group. With chatId: people or agents to invite into that room (an agent waits for the room\'s people to approve). Use instead of participantId. Each invitation is authorized separately.' },
67
67
  chatId: { type: 'string', description: 'Existing room to invite participantIds into. Never creates another room. Omit creation options and agreementId.' },
68
68
  idempotencyKey: { type: 'string', description: 'Required for group creation: a unique key for this conversation, reused unchanged on retries. A different participant set needs a new key.' },
69
+ name: { type: 'string', description: 'Name for a new group, 1–80 characters. Pair rooms and team channels take no name. A retry of the same idempotencyKey keeps the first name. Rename later is PATCH /chats/:chatId by someone who can admit people.' },
69
70
  createIfMissing: { type: 'boolean', description: 'Set false to reuse only an existing room for this acting agent and participant. A missing room is refused without creating a replacement.' },
70
71
  newChat: {
71
72
  type: 'boolean',
@@ -99,7 +100,10 @@ export const openConversationCapability = {
99
100
  else if (args.chatId || args.idempotencyKey) {
100
101
  throw new Error('chatId and idempotencyKey require participantIds');
101
102
  }
102
- const { chatId, reused, invited } = await openConversation({
103
+ if (typeof args.name === 'string' && args.name.trim() && !Array.isArray(args.participantIds)) {
104
+ throw new Error('A name is for a new group. Pair rooms take no name; the person is the name.');
105
+ }
106
+ const { chatId, reused, invited, name } = await openConversation({
103
107
  ...(typeof args.participantId === 'string' ? { participantId: args.participantId } : {}),
104
108
  ...(Array.isArray(args.participantIds) ? { participantIds: args.participantIds } : {}),
105
109
  ...(typeof args.chatId === 'string' ? { chatId: args.chatId } : {}),
@@ -107,6 +111,7 @@ export const openConversationCapability = {
107
111
  ...(args.newChat === true ? { newChat: true } : {}),
108
112
  ...(typeof args.createIfMissing === 'boolean' ? { createIfMissing: args.createIfMissing } : {}),
109
113
  ...(typeof args.agreementId === 'string' && args.agreementId ? { agreementId: args.agreementId } : {}),
114
+ ...(typeof args.name === 'string' && args.name.trim() ? { name: args.name.trim() } : {}),
110
115
  }, fullCreds(env));
111
116
  const lister = env.surface === 'mcp' ? 'ziggs_grant_list' : 'grant_list';
112
117
  const grantNote = `Use ${lister} with scopeKind ["chat"] to inspect your room grants. Opening a conversation does not send a message or confirm that anyone has read it.`;
@@ -126,6 +131,7 @@ export const openConversationCapability = {
126
131
  chatId,
127
132
  actingAgentId: fullCreds(env).agentId,
128
133
  ...(typeof reused === 'boolean' ? { reused } : {}),
134
+ ...(name ? { name } : {}),
129
135
  ...(invited ? { invited } : {}),
130
136
  note: `${outcomeNote} ${grantNote}`,
131
137
  };
@@ -1,3 +1,4 @@
1
+ import { presentResourceActions } from './resourceActions.js';
1
2
  import { ContextReadClient, CONTEXT_READ_TYPES, CONTEXT_READ_VIA, parseVia, viaHint, } from '../http/ContextReadClient.js';
2
3
  import { ContextOpenClient } from '../http/ContextOpenClient.js';
3
4
  import { ContextGrantsClient, } from '../http/ContextGrantsClient.js';
@@ -306,9 +307,16 @@ function presentOpenResult(result, env) {
306
307
  });
307
308
  return {
308
309
  ...result,
309
- actions: result.access.continuation?.request ? [requestAccessAction(result.access.continuation.request, env)] : result.ref.kind === 'artifact' && result.outcome === 'unreadable' ? [requestAccessAction({ scopeKind: 'artifact', scopeId: result.ref.id, permission: 'read', durationHours: 24 }, env)] : resumeActions,
310
+ actions: result.actions
311
+ ? [...presentResourceActions(result.actions, env.surface),
312
+ ...resumeActions.map(a => ({ ...a, action: 'resume', state: 'not_evaluated' }))]
313
+ : result.access.continuation?.request
314
+ ? [requestAccessAction(result.access.continuation.request, env)]
315
+ : result.ref.kind === 'artifact' && result.outcome === 'unreadable'
316
+ ? [requestAccessAction({ scopeKind: 'artifact', scopeId: result.ref.id, permission: 'read', durationHours: 24 }, env)]
317
+ : resumeActions,
310
318
  actingAgentId: fullCreds(env).agentId,
311
- ...(result.ref.kind === 'artifact' && result.outcome !== 'ok' ? {
319
+ ...(!result.actions && result.ref.kind === 'artifact' && result.outcome !== 'ok' ? {
312
320
  requestAccessHint: `If a source you already read supplied this artifact reference and its human owner, ${requestTool} scopeKind=artifact can ask that owner for temporary read access without a prior grant. It needs a conversation with the owner; ${chatTool} can open or reuse one, subject to authorization. Discover the request tool's schema before calling it. This response does not confirm existence or ownership. A request grants no access until the human owner approves.`,
313
321
  } : {}),
314
322
  };
@@ -318,8 +326,8 @@ export const openCapability = {
318
326
  names: { sdk: 'open', mcp: 'ziggs_open' },
319
327
  title: 'Open a returned resource',
320
328
  descriptions: {
321
- sdk: 'Open an artifact, chat, task, or agreement you were given an id for. Pass exactly one of artifactId, chatId, taskId, agreementId. Do not pick a grant id or reconstruct type/via — the server resolves the read path and rechecks authorization every time. Read-only: this never requests access or records an approval. Reading is not reply, share, delegate, approve, or original-file download. Extraction pending/failed is not an empty document.',
322
- mcp: 'Open an artifact, chat, task, or agreement from an ordinary returned id (inbox, find, task result). Pass exactly one of artifactId, chatId, taskId, agreementId. Do not call ziggs_grant_list first, do not pick a grant id, and do not reconstruct type/via — the server resolves the path and rechecks every call. A saved id is not authority. Read-only: never requests access or approves. Hidden and unknown look the same; a discoverable-but-unreadable row names an owner-decision, which this call does not execute. Text access is not a file download. Extraction pending/failed is not an empty document.',
329
+ sdk: 'Read a returned resource: pass exactly one of artifactId, chatId, taskId, agreementId. The backend resolves access and returns scoped actions with required inputs and recovery calls. Opening executes none of those actions. Extraction pending/failed is not an empty document.',
330
+ mcp: 'Read a returned artifact, chat, task, or agreement: pass exactly one corresponding id. No preliminary grant lookup. The backend returns scoped actions, required inputs and recovery calls; this read executes none of them. Hidden and unknown look the same. Extraction pending/failed is not an empty document.',
323
331
  },
324
332
  annotation: 'read-only',
325
333
  params: {
@@ -341,8 +349,8 @@ export const accessExplainCapability = {
341
349
  names: { sdk: 'access_explain', mcp: 'ziggs_access_explain' },
342
350
  title: 'Explain access to a resource',
343
351
  descriptions: {
344
- sdk: 'Read-only access explanation for an ordinary returned id. Same one-of artifactId/chatId/taskId/agreementId as open. Says whether you can read, and that reading does not imply reply, share, delegate, approve, or download. Does not return content and does not request or approve anything.',
345
- mcp: 'Read-only access explanation for an ordinary returned id (exactly one of artifactId, chatId, taskId, agreementId). No grant id. Rechecks authorization. Names the owner-decision route when you cannot read a discoverable resource; this call never executes that request. Hidden and unknown reveal no labels, owners, excerpts, or counts.',
352
+ sdk: 'Read-only access explanation for an ordinary returned id. Same one-of artifactId/chatId/taskId/agreementId as open. Returns scoped action states, required inputs and recovery calls. Does not return content and does not request or approve anything.',
353
+ mcp: 'Read-only access explanation for an ordinary returned id (exactly one of artifactId, chatId, taskId, agreementId). No grant id. Rechecks authorization. Returns scoped actions and recovery calls; this call never executes them. Hidden and unknown reveal no labels, owners, excerpts, or counts.',
346
354
  },
347
355
  annotation: 'read-only',
348
356
  params: OPEN_ID_PARAMS,
@@ -18,3 +18,4 @@ export { ARTIFACT_CAPABILITIES, recordArtifactCapability, listArtifactsCapabilit
18
18
  export { CHAT_CAPABILITIES, openConversationCapability, presentSendResult, type SendPresentation, } from './chat.js';
19
19
  export { EVERYDAY_MCP_TOOLS, EVERYDAY_DESTRUCTIVE_MCP, EVERYDAY_NO_SDK_TWIN, EVERYDAY_SDK_TOOLS, } from './everyday.js';
20
20
  export { annotationForSdkTool } from './toolLane.js';
21
+ export { presentResourceActions } from './resourceActions.js';
@@ -18,3 +18,4 @@ export { ARTIFACT_CAPABILITIES, recordArtifactCapability, listArtifactsCapabilit
18
18
  export { CHAT_CAPABILITIES, openConversationCapability, presentSendResult, } from './chat.js';
19
19
  export { EVERYDAY_MCP_TOOLS, EVERYDAY_DESTRUCTIVE_MCP, EVERYDAY_NO_SDK_TWIN, EVERYDAY_SDK_TOOLS, } from './everyday.js';
20
20
  export { annotationForSdkTool } from './toolLane.js';
21
+ export { presentResourceActions } from './resourceActions.js';
@@ -4,7 +4,9 @@ import type { CapabilityDefinition, CapabilityEnv } from './types.js';
4
4
  * One next step: the tool the calling surface registers, any args already
5
5
  * known, and why.
6
6
  *
7
- * Ready means a caller can run it verbatim. Incomplete means a required
7
+ * Ready means the call has complete arguments and no declared hold; it is
8
+ * not an authorization decision. The server checks permission on execution.
9
+ * Incomplete means a required
8
10
  * argument is missing or an address field is not an address — the suggestion
9
11
  * still names the tool, and `missing` says what has to be filled first.
10
12
  *
@@ -19,7 +21,7 @@ export interface NextCall {
19
21
  args?: Record<string, unknown>;
20
22
  /** One clause: what running this achieves, or what is still missing. */
21
23
  why: string;
22
- /** True only when every required argument is present and addressable. */
24
+ /** Arguments are complete and no hold is declared; does not prove authority. */
23
25
  ready: boolean;
24
26
  /** Required argument names that are absent or not an address. */
25
27
  missing?: readonly string[];
@@ -0,0 +1,3 @@
1
+ import type { ResourceAction } from '../http/ContextOpenClient.js';
2
+ /** Bind canonical tool names without changing the backend's decision or scope. */
3
+ export declare function presentResourceActions(actions: ResourceAction[], surface: 'sdk' | 'mcp'): ResourceAction[];
@@ -0,0 +1,9 @@
1
+ /** Bind canonical tool names without changing the backend's decision or scope. */
2
+ export function presentResourceActions(actions, surface) {
3
+ const toolName = (name) => surface === 'mcp' && !name.startsWith('ziggs_') ? `ziggs_${name}` : name;
4
+ const call = (value) => ({ ...value, tool: toolName(value.tool) });
5
+ return actions.map(action => ({ ...action,
6
+ ...(action.tool ? { tool: toolName(action.tool) } : {}),
7
+ ...(action.recovery ? { recovery: call(action.recovery) } : {}),
8
+ }));
9
+ }
@@ -190,8 +190,8 @@ export declare function resolveMyPendingApprovalPartyId(agreement: Agreement, op
190
190
  * Record this party's approval decision on an agreement
191
191
  * (`PUT /agreements/:agreementId/approvals/:partyId`).
192
192
  *
193
- * `partyId` MUST match the authenticated actor (`actor.id === partyId`): owner
194
- * principal when approving as human, delegate agent id when impersonating.
193
+ * Use the partyId from the server's approvalAction. Agents need covering
194
+ * approval authority even for their own slot; the server rechecks it on PUT.
195
195
  * Canonical for hire, service, link, and request proposals.
196
196
  */
197
197
  export declare function approveAgreementAsParty(agreementId: string, partyId: string, status: 'approved' | 'rejected', creds: Creds): Promise<Agreement>;
@@ -53,6 +53,7 @@ export function linkSummary(a) {
53
53
  // chat with. Dropping it here is what that looked like: the server sent the
54
54
  // peer and the allow-list ate it before any caller saw it.
55
55
  ...(a.peer ? { peer: a.peer } : {}),
56
+ ...(a.approvalAction ? { approvalAction: a.approvalAction } : {}),
56
57
  ...(a.description ? { description: a.description } : {}),
57
58
  // Seat bookkeeping on an open invite — link state, not commerce.
58
59
  ...(a.linkInvite ? { linkInvite: a.linkInvite } : {}),
@@ -222,7 +223,11 @@ export async function respondToAgreement(agreementId, action, creds, opts = {})
222
223
  if (!agreement) {
223
224
  throw new Error(`Agreement ${agreementId} not found`);
224
225
  }
225
- const partyId = resolveMyPendingApprovalPartyId(agreement, {
226
+ const approval = agreement.approvalAction;
227
+ if (approval && (approval.state !== 'available' || !approval.partyId)) {
228
+ throw new Error(`${approval.explanation}${opts.appUrl ? ` Review: ${opts.appUrl}` : ''}`);
229
+ }
230
+ const partyId = approval?.partyId ?? resolveMyPendingApprovalPartyId(agreement, {
226
231
  ownerUserId: opts.ownerUserId,
227
232
  agentId: creds.agentId,
228
233
  });
@@ -288,8 +293,8 @@ export function resolveMyPendingApprovalPartyId(agreement, opts) {
288
293
  * Record this party's approval decision on an agreement
289
294
  * (`PUT /agreements/:agreementId/approvals/:partyId`).
290
295
  *
291
- * `partyId` MUST match the authenticated actor (`actor.id === partyId`): owner
292
- * principal when approving as human, delegate agent id when impersonating.
296
+ * Use the partyId from the server's approvalAction. Agents need covering
297
+ * approval authority even for their own slot; the server rechecks it on PUT.
293
298
  * Canonical for hire, service, link, and request proposals.
294
299
  */
295
300
  export async function approveAgreementAsParty(agreementId, partyId, status, creds) {
@@ -26,6 +26,8 @@ export interface OpenConversationInput {
26
26
  participantIds?: string[];
27
27
  chatId?: string;
28
28
  idempotencyKey?: string;
29
+ /** Name for a new group. Pair rooms take no name. */
30
+ name?: string;
29
31
  newChat?: boolean;
30
32
  agreementId?: string;
31
33
  createIfMissing?: boolean;
@@ -38,6 +40,7 @@ export declare function openConversation(participant: string | OpenConversationI
38
40
  chatId: string;
39
41
  reused?: boolean;
40
42
  invited?: ChatOpenInvite[];
43
+ name?: string;
41
44
  }>;
42
45
  export interface SendChatMessageInput {
43
46
  /**
@@ -158,6 +161,7 @@ export declare class ChatClient {
158
161
  chatId: string;
159
162
  reused?: boolean;
160
163
  invited?: ChatOpenInvite[];
164
+ name?: string;
161
165
  }>;
162
166
  addMember(input: AddChatMemberInput): Promise<AddChatMemberResult>;
163
167
  sendMessage(input: SendChatMessageInput): Promise<SendChatMessageResult>;
@@ -38,12 +38,14 @@ export async function openConversation(participant, creds, { newChat = false, ag
38
38
  // the room — defaulting to false would report a reused conversation as fresh,
39
39
  // which is worse than saying nothing.
40
40
  const reused = data['reused'];
41
+ const name = data['name'];
41
42
  const invited = Array.isArray(data['invited'])
42
43
  ? data['invited']
43
44
  : undefined;
44
45
  return {
45
46
  chatId: data['chatId'],
46
47
  ...(typeof reused === 'boolean' ? { reused } : {}),
48
+ ...(typeof name === 'string' && name ? { name } : {}),
47
49
  ...(invited ? { invited } : {}),
48
50
  };
49
51
  }
@@ -14,16 +14,25 @@ export interface ContextOpenBody {
14
14
  after?: string;
15
15
  limit?: number;
16
16
  }
17
- export interface ContextOpenAbilities {
18
- read: boolean;
19
- reply: boolean;
20
- share: boolean;
21
- delegate: boolean;
22
- approve: boolean;
23
- download: boolean;
17
+ /** Backend-evaluated action, scoped to a resource. Never a permission token. */
18
+ export interface ResourceActionCall {
19
+ tool: string;
20
+ args: Record<string, unknown>;
21
+ required?: string[];
22
+ }
23
+ export interface ResourceAction extends Partial<ResourceActionCall> {
24
+ action: string;
25
+ state: 'available' | 'needs_decision' | 'unavailable' | 'not_evaluated';
26
+ reasonCode?: string;
27
+ reason?: unknown;
28
+ scope?: {
29
+ kind: string;
30
+ id: string;
31
+ agreementId?: string;
32
+ };
33
+ recovery?: ResourceActionCall;
24
34
  }
25
35
  export interface ContextOpenAccess {
26
- abilities: ContextOpenAbilities;
27
36
  summary: string;
28
37
  document?: {
29
38
  empty: boolean;
@@ -42,6 +51,8 @@ export interface ContextOpenAccess {
42
51
  };
43
52
  }
44
53
  export interface ContextOpenResult {
54
+ /** Optional for servers predating resource action projections. */
55
+ actions?: ResourceAction[];
45
56
  outcome: ContextOpenOutcome;
46
57
  ref: ContextOpenRef;
47
58
  access: ContextOpenAccess;
@@ -57,6 +57,8 @@ export interface ContextSnapshotParticipant {
57
57
  [key: string]: unknown;
58
58
  }
59
59
  export interface ContextSnapshotResult {
60
+ /** Server-resolved orientation; writes recheck authority. */
61
+ workBrief?: Record<string, unknown>;
60
62
  work?: Record<string, unknown>;
61
63
  history: unknown[];
62
64
  agreements: unknown[];
@@ -10,7 +10,7 @@ export type { ArtifactVisibility, ListArtifactsOptions, ListArtifactsQuery, List
10
10
  export { ContextReadClient, CONTEXT_READ_TYPES, CONTEXT_READ_VIA, VIA_KINDS, parseVia, viaHint, } from './ContextReadClient.js';
11
11
  export type { ContextReadType, ContextReadQuery, ContextReadEnvelope, ContextSnapshotResult, ContextSnapshotParticipant, ViaKind, } from './ContextReadClient.js';
12
12
  export { ContextOpenClient, CONTEXT_OPEN_KINDS } from './ContextOpenClient.js';
13
- export type { ContextOpenKind, ContextOpenOutcome, ContextOpenRef, ContextOpenBody, ContextOpenAbilities, ContextOpenAccess, ContextOpenResult, ContextExplainResult, } from './ContextOpenClient.js';
13
+ export type { ContextOpenKind, ContextOpenOutcome, ContextOpenRef, ContextOpenBody, ResourceAction, ResourceActionCall, ContextOpenAccess, ContextOpenResult, ContextExplainResult, } from './ContextOpenClient.js';
14
14
  export { ContextDiscoveryClient } from './ContextDiscoveryClient.js';
15
15
  export type { DiscoverableItem } from './ContextDiscoveryClient.js';
16
16
  export { IntroductionsClient } from './IntroductionsClient.js';
package/dist/types.d.ts CHANGED
@@ -176,6 +176,11 @@ export interface AgreementApprovalEntry {
176
176
  heldReason?: string | null;
177
177
  }
178
178
  export interface Agreement {
179
+ approvalAction?: {
180
+ state: 'available' | 'needs_decision' | 'unavailable';
181
+ partyId?: string;
182
+ explanation: string;
183
+ };
179
184
  agreementId: string;
180
185
  parentAgreementId?: string | null;
181
186
  rootAgreementId?: string | null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ziggs-ai/api-client",
3
- "version": "0.28.0",
3
+ "version": "0.29.0",
4
4
  "description": "HTTP and WebSocket client for the Ziggs backend API",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",