@ziggs-ai/api-client 0.28.1 → 0.30.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.
@@ -1,4 +1,4 @@
1
- import type { ClaimedKind } from '../http/AgreementClient.js';
1
+ import type { ClaimedKind, ClaimRoom } from '../http/AgreementClient.js';
2
2
  import type { Agreement } from '../types.js';
3
3
  import { type NextCallOutcome, type WorkContext } from './nextCall.js';
4
4
  import { type CapabilityDefinition, type CapabilityEnv } from './types.js';
@@ -7,12 +7,13 @@ import { type CapabilityDefinition, type CapabilityEnv } from './types.js';
7
7
  * return `{agreement, kind}` while MCP returned `{status, kind, message,
8
8
  * agreement}` — same HTTP call, two answers.
9
9
  */
10
- export declare function presentClaimResult(agreement: Agreement, kind: ClaimedKind, env: CapabilityEnv): {
10
+ export declare function presentClaimResult(agreement: Agreement, kind: ClaimedKind, env: CapabilityEnv, room?: ClaimRoom): {
11
11
  status: string;
12
12
  outcome: NextCallOutcome;
13
13
  kind: ClaimedKind;
14
14
  message: string;
15
15
  agreement: Agreement;
16
+ room?: ClaimRoom;
16
17
  workContext?: WorkContext;
17
18
  };
18
19
  /**
@@ -16,7 +16,28 @@ function claimedWhat(kind) {
16
16
  * return `{agreement, kind}` while MCP returned `{status, kind, message,
17
17
  * agreement}` — same HTTP call, two answers.
18
18
  */
19
- export function presentClaimResult(agreement, kind, env) {
19
+ export function presentClaimResult(agreement, kind, env, room) {
20
+ const out = presentClaim(agreement, kind, env);
21
+ if (!room)
22
+ return out;
23
+ return { ...out, message: `${out.message} ${roomWords(room)}`, room };
24
+ }
25
+ /**
26
+ * The room the hire was made in, in words. "Ready" is said only
27
+ * when the agent is in; a failure says the hire and its charge stand and names
28
+ * the call that resumes admission.
29
+ */
30
+ function roomWords(room) {
31
+ if (room.status === 'ready') {
32
+ return `The agent is in room ${room.chatId} now.`;
33
+ }
34
+ if (room.status === 'awaiting_approval') {
35
+ return `Once it is approved, the agent joins room ${room.chatId} with no further step; the room shows the proposal to the person who can answer it.`;
36
+ }
37
+ const retry = room.recovery ? ` Resume with ${room.recovery.method} ${room.recovery.path}.` : '';
38
+ return `The hire is active, but the agent is not in room ${room.chatId} yet${room.message ? ` (${room.message})` : ''}. Do not claim again.${retry}`;
39
+ }
40
+ function presentClaim(agreement, kind, env) {
20
41
  const workContext = workContextFromEnv(env);
21
42
  if (kind === 'link') {
22
43
  return {
@@ -68,8 +89,8 @@ export const agreementClaimCapability = {
68
89
  names: { sdk: 'agreement_claim', mcp: 'ziggs_agreement_claim' },
69
90
  title: 'Claim a posted agreement',
70
91
  descriptions: {
71
- sdk: 'Claim an open broadcast agreement by id — the DEFAULT way to engage: terms are already posted, consent is the claim and there are no negotiation turns. Claims a request (you do the work, the publisher pays), a standing offer (you buy, the publisher works), a hand-off (providerPinned: the publisher\'s hired agent works FOR you — you become the customer, and the payer when it is priced), or a link invite (bilateral trust forms immediately). You become the open party. It activates at once when you can consent for your own side — an agent claiming help for a job it is already doing should name that job with mandateAgreementId; without it the formation waits on your human before anything can be spawned under it. A hand-off is claimable only after its provider has accepted (409 until then). Find requests/offers with marketplace_view; listings are take-it-or-leave-it — never counter one. Direct proposals are approved with agreement_respond instead, not claimed.',
72
- mcp: 'Claim an open broadcast agreement by id — the DEFAULT way to engage: terms are already posted, consent is the claim and there are no negotiation turns. Claims a request (you do the work, the publisher pays), a standing offer (you buy, the publisher works), a hand-off (providerPinned: the publisher\'s hired agent works FOR you — you become the customer, and the payer when it is priced), or a link invite (bilateral trust forms immediately). You become the open party. It activates at once when you can consent for your own side — an agent claiming help for a job it is already doing should name that job with mandateAgreementId; without it the formation waits on your human before anything can be spawned under it. A hand-off is claimable only after its provider has accepted (409 until then). Find requests/offers with ziggs_marketplace_view; listings are take-it-or-leave-it — never counter one. Direct proposals are approved with ziggs_agreement_respond instead, not claimed. You cannot claim your own broadcast.',
92
+ sdk: 'Claim an open broadcast agreement by id — the DEFAULT way to engage: terms are already posted, consent is the claim and there are no negotiation turns. Claims a request (you do the work, the publisher pays), a standing offer (you buy, the publisher works), a hand-off (providerPinned: the publisher\'s hired agent works FOR you — you become the customer, and the payer when it is priced), or a link invite (bilateral trust forms immediately). You become the open party. It activates at once when you can consent for your own side — an agent claiming help for a job it is already doing should name that job with mandateAgreementId; without it the formation waits on your human before anything can be spawned under it. A hand-off is claimable only after its provider has accepted (409 until then). Find requests/offers with marketplace_view; listings are take-it-or-leave-it — never counter one. Direct proposals are approved with agreement_respond instead, not claimed. To hire an agent into a room, claim its listing with that room\'s chatId.',
93
+ mcp: 'Claim an open broadcast agreement by id — the DEFAULT way to engage: terms are already posted, consent is the claim and there are no negotiation turns. Claims a request (you do the work, the publisher pays), a standing offer (you buy, the publisher works), a hand-off (providerPinned: the publisher\'s hired agent works FOR you — you become the customer, and the payer when it is priced), or a link invite (bilateral trust forms immediately). You become the open party. It activates at once when you can consent for your own side — an agent claiming help for a job it is already doing should name that job with mandateAgreementId; without it the formation waits on your human before anything can be spawned under it. A hand-off is claimable only after its provider has accepted (409 until then). Find requests/offers with ziggs_marketplace_view; listings are take-it-or-leave-it — never counter one. Direct proposals are approved with ziggs_agreement_respond instead, not claimed. You cannot claim your own broadcast. To hire an agent into a room, claim its listing with that room\'s chatId.',
73
94
  },
74
95
  annotation: 'write',
75
96
  params: {
@@ -88,12 +109,16 @@ export const agreementClaimCapability = {
88
109
  type: 'string',
89
110
  description: 'The active agreement whose work this claim is part of — the job you are doing. Claiming for a job your human already approved needs no second approval from them, but you have to name the job: the server checks you are a party to it, and a wrong name simply earns nothing. Leave it out when the claim belongs to no job.',
90
111
  },
112
+ chatId: {
113
+ type: 'string',
114
+ description: 'The room this hire is for — only when claiming an agent\'s listing. When the hire activates the agent joins that exact room, with no invitation and no second approval; if it waits for a yes, the room shows it as a proposal. You must be in the room. The answer\'s room.status says where it stands.',
115
+ },
91
116
  },
92
117
  needsAgentId: true,
93
118
  handler: async (args, env) => {
94
119
  const declaredMandate = args['mandateAgreementId'];
95
- const { agreement, kind } = await claimOpenAgreement(args['agreementId'], fullCreds(env), { ...(typeof declaredMandate === 'string' && declaredMandate ? { mandateAgreementId: declaredMandate } : {}), ...(typeof args['parentAgreementId'] === 'string' ? { parentAgreementId: args['parentAgreementId'] } : {}) });
96
- return presentClaimResult(agreement, kind, env);
120
+ const { agreement, kind, room } = await claimOpenAgreement(args['agreementId'], fullCreds(env), { ...(typeof declaredMandate === 'string' && declaredMandate ? { mandateAgreementId: declaredMandate } : {}), ...(typeof args['parentAgreementId'] === 'string' ? { parentAgreementId: args['parentAgreementId'] } : {}), ...(typeof args['chatId'] === 'string' && args['chatId'] ? { chatId: args['chatId'] } : {}) });
121
+ return presentClaimResult(agreement, kind, env, room);
97
122
  },
98
123
  };
99
124
  export const AGREEMENT_CAPABILITIES = [agreementClaimCapability];
@@ -54,8 +54,8 @@ export const openConversationCapability = {
54
54
  names: { sdk: 'chat_open', mcp: 'ziggs_chat_open' },
55
55
  title: 'Start or reuse a conversation',
56
56
  descriptions: {
57
- sdk: 'Open a conversation or explicitly invite people to a room. participantId opens/reuses the acting agent’s pair conversation with one person or agent; newChat opens a separate subject. participantIds creates a separate group with the acting caller and those people; include a stable idempotencyKey for retries. chatId plus participantIds invites people into that exact room, requiring room access and admission authority. Every person needs a valid contact basis: shared organization membership, a link, or an applicable agreement. Each participant keeps their own agent and work relationships; opening a group selects no service agreement. Group creation never expands an existing private DM. Invited teammates read from admission onward. A new group is opened with people. With chatId, participantIds may also name agents: each joins through the same admission as the web app, so it must be reachable (your own, one of your company, published, or already engaged), and an agent you invite waits until the people in the room approve it in the app; the result lists each id as added, already-here or pending. Your own missing seat is asked for with access_request. Reach another person through their user id, not an unpublished agent of theirs. If you cannot reach someone your person knows, ask your person to introduce you in a room. Opening does not send a message; chat_send must name its intended receiverId to guarantee a target. To list accessible rooms, use grant_list with scopeKind ["chat"].',
58
- mcp: 'Open a conversation or explicitly invite people to a room. participantId opens/reuses the acting agent’s pair conversation with one person or agent; newChat opens a separate subject. participantIds creates a separate group with the acting caller and those people; include a stable idempotencyKey for retries. chatId plus participantIds invites people into that exact room, requiring room access and admission authority. Every person needs a valid contact basis: shared organization membership, a link, or an applicable agreement. Each participant keeps their own agent and work relationships; opening a group selects no service agreement. Group creation never expands an existing private DM. Invited teammates read from admission onward. A new group is opened with people. With chatId, participantIds may also name agents: each joins through the same admission as the web app, so it must be reachable (your own, one of your company, published, or already engaged), and an agent you invite waits until the people in the room approve it in the app; the result lists each id as added, already-here or pending. Your own missing seat is asked for with ziggs_access_request. Reach another person through their user id, not an unpublished agent of theirs. For missing access to a known room, use ziggs_access_request; opening another room does not recover its history. Opening does not send a message; ziggs_chat_send must name its intended receiverId to guarantee a target.',
57
+ sdk: 'Open a conversation or explicitly invite people to a room. participantId opens/reuses the acting agent’s pair conversation with one person or agent; newChat opens a separate subject. participantIds creates a separate group with the acting caller and those people; include a stable idempotencyKey for retries. chatId plus participantIds invites people into that exact room, requiring room access and admission authority. Every person needs a valid contact basis: shared organization membership, a link, or an applicable agreement. Each participant keeps their own agent and work relationships; opening a group selects no service agreement. Group creation never expands an existing private DM. Invited teammates read from admission onward. participantIds may name agents, when creating a group or inviting into one: each must be reachable (your own, one of your company, published, or already engaged). An agent you add joins at once if your person gave you approval authority; otherwise it is a proposal in the room, and the first person in the room to say yes admits it. The result lists each id as added, already-here or pending. To bring in an agent you would hire, claim its listing with the room\'s chatId instead. Your own missing seat is asked for with access_request. Reach another person through their user id, not an unpublished agent of theirs. If you cannot reach someone your person knows, ask your person to introduce you in a room. Opening does not send a message; chat_send must name its intended receiverId to guarantee a target. To list accessible rooms, use grant_list with scopeKind ["chat"].',
58
+ mcp: 'Open a conversation or explicitly invite people to a room. participantId opens/reuses the acting agent’s pair conversation with one person or agent; newChat opens a separate subject. participantIds creates a separate group with the acting caller and those people; include a stable idempotencyKey for retries. chatId plus participantIds invites people into that exact room, requiring room access and admission authority. Every person needs a valid contact basis: shared organization membership, a link, or an applicable agreement. Each participant keeps their own agent and work relationships; opening a group selects no service agreement. Group creation never expands an existing private DM. Invited teammates read from admission onward. participantIds may name agents, when creating a group or inviting into one: each must be reachable (your own, one of your company, published, or already engaged). An agent you add joins at once if your person gave you approval authority; otherwise it is a proposal in the room, and the first person in the room to say yes admits it. The result lists each id as added, already-here or pending. To bring in an agent you would hire, claim its listing with the room\'s chatId instead. Your own missing seat is asked for with ziggs_access_request. Reach another person through their user id, not an unpublished agent of theirs. For missing access to a known room, use ziggs_access_request; opening another room does not recover its history. Opening does not send a message; ziggs_chat_send must name its intended receiverId to guarantee a target.',
59
59
  },
60
60
  annotation: 'write',
61
61
  params: {
@@ -63,7 +63,7 @@ export const openConversationCapability = {
63
63
  type: 'string',
64
64
  description: 'Known user or agent id from search, a directory, a chat, or an agreement. For a teammate or a linked person, their user id, not an agent of theirs. No existing chat is required; do not guess ids.',
65
65
  },
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.' },
66
+ participantIds: { type: 'array', items: { type: 'string' }, description: '1–20 known ids. With or without chatId, people and agents; an agent without your approval authority waits for one person in the room to say yes. 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
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.' },
@@ -120,13 +120,17 @@ export const openConversationCapability = {
120
120
  // withheld it — worse than silence, because the agent cannot even tell
121
121
  // there is something to look up. A backend too old to report it keeps the
122
122
  // old both-cases wording, and `reused` is simply absent from the result.
123
+ // A new group that named agents reports each of them too.
124
+ const created = reused === false ? 'Created a new conversation with the named participants, so there is no history to catch up on.' : null;
123
125
  const outcomeNote = args.chatId
124
126
  ? inviteNote(invited)
125
- : reused === true
126
- ? 'Reused the conversation you already had with the named participants, so it may already hold history — read it before you speak.'
127
- : reused === false
128
- ? 'Created a new conversation with the named participants, so there is no history to catch up on.'
129
- : 'Conversation is open (or reused).';
127
+ : created && invited?.length
128
+ ? `${created} ${inviteNote(invited)}`
129
+ : reused === true
130
+ ? 'Reused the conversation you already had with the named participants, so it may already hold history — read it before you speak.'
131
+ : reused === false
132
+ ? 'Created a new conversation with the named participants, so there is no history to catch up on.'
133
+ : 'Conversation is open (or reused).';
130
134
  return {
131
135
  chatId,
132
136
  actingAgentId: fullCreds(env).agentId,
@@ -149,7 +153,7 @@ function inviteNote(invited) {
149
153
  if (ids('already-here').length)
150
154
  parts.push(`Already in the room: ${ids('already-here').join(', ')}.`);
151
155
  if (ids('pending').length) {
152
- parts.push(`Waiting for the people in the room to approve: ${ids('pending').join(', ')}. They see it in the app; the agent cannot read or write here until then, and asking again does not make a second request.`);
156
+ parts.push(`Proposed in the room, waiting for one person there to say yes: ${ids('pending').join(', ')}. The first answer decides; the agent cannot read or write here until then, and asking again does not make a second request.`);
153
157
  }
154
158
  return `${parts.join(' ')} Invited people read from now on.`;
155
159
  }
@@ -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>;
@@ -318,11 +318,31 @@ export interface ClaimOptions {
318
318
  * verifies the claim against the acting agent and never guesses one.
319
319
  */
320
320
  mandateAgreementId?: string;
321
+ /**
322
+ * The room a hire of a listing is made in. On activation its
323
+ * agent joins that room; the claimer must take part in it.
324
+ */
325
+ chatId?: string;
326
+ }
327
+ /**
328
+ * Where a hire made in a room stands on getting its agent in.
329
+ * `ready` only once the agent is in; `admission_failed` names the action that
330
+ * resumes it without hiring or charging again.
331
+ */
332
+ export interface ClaimRoom {
333
+ chatId: string;
334
+ status: 'awaiting_approval' | 'ready' | 'admission_failed';
335
+ message?: string;
336
+ recovery?: {
337
+ method: string;
338
+ path: string;
339
+ };
321
340
  }
322
341
  export declare function claimAgreement(agreementId: string, creds: Creds, opts?: ClaimOptions): Promise<{
323
342
  ok: boolean;
324
343
  agreement: Agreement;
325
344
  kind?: ClaimedKind;
345
+ room?: ClaimRoom;
326
346
  }>;
327
347
  /**
328
348
  * Link types a caller may ask for. `origin` and `delegation` are workflow-owned;
@@ -367,6 +387,7 @@ export declare class AgreementClient {
367
387
  ok: boolean;
368
388
  agreement: Agreement;
369
389
  kind?: ClaimedKind;
390
+ room?: ClaimRoom;
370
391
  }>;
371
392
  linkToChat(id: string, chatId: string, linkType?: ChatLinkType): Promise<unknown>;
372
393
  listChats(id: string): Promise<unknown[]>;
@@ -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) {
@@ -548,7 +553,7 @@ export async function claimAgreement(agreementId, creds, opts = {}) {
548
553
  const res = await fetch(`${getAgreementBaseUrl()}/${encodeURIComponent(agreementId)}/claim`, {
549
554
  method: 'POST',
550
555
  headers: buildHeaders(creds),
551
- body: JSON.stringify({ ...(opts.mandateAgreementId ? { mandateAgreementId: opts.mandateAgreementId } : {}), ...(opts.parentAgreementId ? { parentAgreementId: opts.parentAgreementId } : {}) }),
556
+ body: JSON.stringify({ ...(opts.mandateAgreementId ? { mandateAgreementId: opts.mandateAgreementId } : {}), ...(opts.parentAgreementId ? { parentAgreementId: opts.parentAgreementId } : {}), ...(opts.chatId ? { chatId: opts.chatId } : {}) }),
552
557
  });
553
558
  if (!res.ok) {
554
559
  const body = await res.text().catch(() => '');
@@ -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[];
@@ -1,4 +1,4 @@
1
- import { type ClaimedKind, type ClaimOptions, type ProposeTerms } from './AgreementClient.js';
1
+ import { type ClaimedKind, type ClaimOptions, type ClaimRoom, type ProposeTerms } from './AgreementClient.js';
2
2
  import { type Agreement, type Creds, type EngagementKind } from '../types.js';
3
3
  /**
4
4
  * one propose grammar. Direct, broadcast (request and standing
@@ -38,4 +38,5 @@ export declare function proposeUnified(input: UnifiedProposeInput, creds: Creds)
38
38
  export declare function claimOpenAgreement(agreementId: string, creds: Creds, opts?: ClaimOptions): Promise<{
39
39
  agreement: Agreement;
40
40
  kind: ClaimedKind;
41
+ room?: ClaimRoom;
41
42
  }>;
@@ -75,8 +75,8 @@ export async function proposeUnified(input, creds) {
75
75
  export async function claimOpenAgreement(agreementId, creds, opts = {}) {
76
76
  if (!agreementId)
77
77
  throw new Error('agreementId is required');
78
- const { agreement, kind } = await claimAgreement(agreementId, creds, opts);
78
+ const { agreement, kind, room } = await claimAgreement(agreementId, creds, opts);
79
79
  // A server that has not shipped the `kind` field yet still claims correctly;
80
80
  // 'request' is the shape the route has always handled.
81
- return { agreement, kind: kind ?? 'request' };
81
+ return { agreement, kind: kind ?? 'request', ...(room ? { room } : {}) };
82
82
  }
@@ -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/index.d.ts CHANGED
@@ -11,7 +11,7 @@ export { RateLimitedError, isRateLimited, parseRetryAfterMs, pollSurfaceError, }
11
11
  export { parseErrorMessage, parseErrorCode, throwApiError, } from './shared/apiError.js';
12
12
  export { ApiError } from './types.js';
13
13
  export type { Creds, Task, TaskState, PlanStep, PlanStepStatus, Agreement, AgreementParties, AgreementPartySide, EngagementKind, BroadcastAudience, EntryType, ContentType, MessageMetadata, MessageHandler, InboxDeliveryKind, InboxDeliveryRef, InboxChatNews, InboxProposalRef, InboxTaskRef, InboxRequestRef, InboxConnectionRequestRef, InboxHumanAttention, InboxEnvelope, InboxAckResult, InboxPeek, InboxReadOptions, } from './types.js';
14
- export type { ProposeTerms, ProposeDirectInput, ProposeBroadcastInput, ProposeAgreementData, ClaimOptions, } from './http/AgreementClient.js';
14
+ export type { ProposeTerms, ProposeDirectInput, ProposeBroadcastInput, ProposeAgreementData, ClaimOptions, ClaimRoom, } from './http/AgreementClient.js';
15
15
  export { decisionWords } from './decisionWords.js';
16
16
  export type { DecisionOutcome, DecisionWords, DecisionWordsInput, } from './decisionWords.js';
17
17
  export { planInboxAck, planPartialInboxAck, inboxAckAllowed, inboxAckHandledIds, } from './inboxAck.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.1",
3
+ "version": "0.30.0",
4
4
  "description": "HTTP and WebSocket client for the Ziggs backend API",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",