@ziggs-ai/api-client 0.26.0 → 0.28.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.
@@ -304,7 +304,7 @@ export const listArtifactsCapability = {
304
304
  mcp: 'List business artifacts YOU authored, across every scope and none; tool-operation and thought traces are excluded. Use this to find something you ' +
305
305
  'recorded free-standing (no chat/agreement/task), which the scope reads cannot return. ' +
306
306
  'To read an artifact someone shared WITH you, use ziggs_context_read with via=artifact:<id>; ' +
307
- 'to see what has been shared with you, use ziggs_grant_list with scopeKind=artifact.',
307
+ 'to see what has been shared with you, use ziggs_grant_list with scopeKind ["artifact"].',
308
308
  },
309
309
  annotation: 'read-only',
310
310
  params: {
@@ -33,9 +33,12 @@ export function presentSendResult(result, env) {
33
33
  const destination = delivery.broadcast
34
34
  ? 'the humans in the room, not one addressee'
35
35
  : `${delivery.to.id} (${delivery.to.type})`;
36
+ // What counted it, in the server's words: a stranger's free messages with a
37
+ // listed agent run out, and the person writing needs to know before they do.
38
+ const counted = result.coverage?.message ? ` ${result.coverage.message}` : '';
36
39
  return {
37
40
  ...base,
38
- delivered: `Message ${result.messageId} was accepted into chat ${result.chatId}, addressed to ${destination}. Mailbox routing is unconfirmed; a person may be covered by an assistant. Accepted is not read and does not confirm a wake or reply.`,
41
+ delivered: `Message ${result.messageId} was accepted into chat ${result.chatId}, addressed to ${destination}. Mailbox routing is unconfirmed; a person may be covered by an assistant. Accepted is not read and does not confirm a wake or reply.${counted}`,
39
42
  to: delivery.to,
40
43
  };
41
44
  }
@@ -51,8 +54,8 @@ export const openConversationCapability = {
51
54
  names: { sdk: 'chat_open', mcp: 'ziggs_chat_open' },
52
55
  title: 'Start or reuse a conversation',
53
56
  descriptions: {
54
- 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 takes people only: another agent joins a room when a person adds it in the web app, where its approval rules apply, and 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 scopeKind=chat.',
55
- 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 takes people only: another agent joins a room when a person adds it in the web app, where its approval rules apply, and 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. 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.',
56
59
  },
57
60
  annotation: 'write',
58
61
  params: {
@@ -60,7 +63,7 @@ export const openConversationCapability = {
60
63
  type: 'string',
61
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.',
62
65
  },
63
- participantIds: { type: 'array', items: { type: 'string' }, description: '1–20 known user ids. Creates a separate group, or invites these people to chatId. Use instead of participantId. Each invitation is authorized separately.' },
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.' },
64
67
  chatId: { type: 'string', description: 'Existing room to invite participantIds into. Never creates another room. Omit creation options and agreementId.' },
65
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.' },
66
69
  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.' },
@@ -87,7 +90,7 @@ export const openConversationCapability = {
87
90
  if (args.participantId !== undefined || !Array.isArray(args.participantIds) ||
88
91
  args.participantIds.length < 1 || args.participantIds.length > 20 ||
89
92
  args.participantIds.some(id => typeof id !== 'string' || !id.trim())) {
90
- throw new Error('Use participantIds containing 1–20 people, without participantId');
93
+ throw new Error('Use participantIds containing 1–20 ids, without participantId');
91
94
  }
92
95
  if (!args.chatId && (typeof args.idempotencyKey !== 'string' || !args.idempotencyKey.trim())) {
93
96
  throw new Error('Group creation requires an idempotencyKey; reuse it when retrying');
@@ -96,7 +99,7 @@ export const openConversationCapability = {
96
99
  else if (args.chatId || args.idempotencyKey) {
97
100
  throw new Error('chatId and idempotencyKey require participantIds');
98
101
  }
99
- const { chatId, reused } = await openConversation({
102
+ const { chatId, reused, invited } = await openConversation({
100
103
  ...(typeof args.participantId === 'string' ? { participantId: args.participantId } : {}),
101
104
  ...(Array.isArray(args.participantIds) ? { participantIds: args.participantIds } : {}),
102
105
  ...(typeof args.chatId === 'string' ? { chatId: args.chatId } : {}),
@@ -106,14 +109,14 @@ export const openConversationCapability = {
106
109
  ...(typeof args.agreementId === 'string' && args.agreementId ? { agreementId: args.agreementId } : {}),
107
110
  }, fullCreds(env));
108
111
  const lister = env.surface === 'mcp' ? 'ziggs_grant_list' : 'grant_list';
109
- const grantNote = `Use ${lister} scopeKind=chat to inspect your room grants. Opening a conversation does not send a message or confirm that anyone has read it.`;
112
+ 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.`;
110
113
  // word the note from the outcome instead of covering both cases.
111
114
  // "Open (or reused)" told the agent the distinction existed and then
112
115
  // withheld it — worse than silence, because the agent cannot even tell
113
116
  // there is something to look up. A backend too old to report it keeps the
114
117
  // old both-cases wording, and `reused` is simply absent from the result.
115
118
  const outcomeNote = args.chatId
116
- ? 'The named people can participate in this room. Their history access remains bounded by their grants.'
119
+ ? inviteNote(invited)
117
120
  : reused === true
118
121
  ? 'Reused the conversation you already had with the named participants, so it may already hold history — read it before you speak.'
119
122
  : reused === false
@@ -123,8 +126,25 @@ export const openConversationCapability = {
123
126
  chatId,
124
127
  actingAgentId: fullCreds(env).agentId,
125
128
  ...(typeof reused === 'boolean' ? { reused } : {}),
129
+ ...(invited ? { invited } : {}),
126
130
  note: `${outcomeNote} ${grantNote}`,
127
131
  };
128
132
  },
129
133
  };
134
+ /** Say what happened to each invited id, so the agent reports it truthfully. */
135
+ function inviteNote(invited) {
136
+ if (!invited?.length) {
137
+ return 'The named people can participate in this room. Their history access remains bounded by their grants.';
138
+ }
139
+ const ids = (status) => invited.filter((row) => row.status === status).map((row) => row.id);
140
+ const parts = [];
141
+ if (ids('added').length)
142
+ parts.push(`Added: ${ids('added').join(', ')}.`);
143
+ if (ids('already-here').length)
144
+ parts.push(`Already in the room: ${ids('already-here').join(', ')}.`);
145
+ if (ids('pending').length) {
146
+ 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.`);
147
+ }
148
+ return `${parts.join(' ')} Invited people read from now on.`;
149
+ }
130
150
  export const CHAT_CAPABILITIES = [openConversationCapability];
@@ -19,7 +19,7 @@ export const connectionProxyCapability = {
19
19
  sdk: "Use a named-connector stored connection (e.g. the owner's GitHub/Jira) without ever seeing the credential. " +
20
20
  "Calls the backend connections proxy with a grant the owner issued to this agent. " +
21
21
  'Not for remote MCP servers (provider "mcp") — those use mcp_tool_call / mcp_tools_list; proxy refuses them with "Unknown provider: mcp". ' +
22
- "Don't know connectionId/grantId yet? Use grant_list (scopeKind=connection) or connection_list_grants first.",
22
+ "Don't know connectionId/grantId yet? Use grant_list (scopeKind [\"connection\"]) or connection_list_grants first.",
23
23
  mcp: "Use a named-connector stored connection (e.g. the owner's GitHub/Jira — NOT an agent-to-agent Link, see ziggs_link_list) without ever seeing the credential. " +
24
24
  "Calls the backend connections proxy with a grant the owner issued to this agent. " +
25
25
  'Not for remote MCP servers (provider "mcp") — those use ziggs_mcp_tool_call / ziggs_mcp_tools_list; proxy refuses them with "Unknown provider: mcp". ' +
@@ -128,7 +128,7 @@ export const requestConnectionCapability = {
128
128
  ? "A connection-consent card is now in the chat awaiting your principal. Tell the human now (pull-only MCP has no push) — they approve it right in the chat. " +
129
129
  "Once approved, the connection + grant appear in ziggs_connection_list for ziggs_mcp_tools_list / ziggs_mcp_tool_call."
130
130
  : "A connection-consent card is now in the chat awaiting your principal — they approve it right there. " +
131
- "Once approved, the connection + grant appear in grant_list (scopeKind=connection) / connection_list_grants for mcp_tools_list / mcp_tool_call.",
131
+ "Once approved, the connection + grant appear in grant_list (scopeKind [\"connection\"]) / connection_list_grants for mcp_tools_list / mcp_tool_call.",
132
132
  };
133
133
  }
134
134
  catch (e) {
@@ -64,7 +64,7 @@ export const contextReadCapability = {
64
64
  title: 'Read context you hold',
65
65
  descriptions: {
66
66
  sdk: `Read the contents of a scope you already hold a grant for: ${CONTEXT_READ_TYPES.join(' | ')}. Each type reads through its own entries — ${VIA_BY_TYPE}. Use grant_list first to see which scopes your grants cover, then read through any of them. ${ARTIFACT_VIA_NOTE}. Cursored; all access is grant-fenced server-side.`,
67
- mcp: `Read the contents of a scope you already hold: ${CONTEXT_READ_TYPES.join(' | ')} (the type param). Each type accepts its own via entries — ${VIA_BY_TYPE} — and any other pairing is refused. ${ARTIFACT_VIA_NOTE} (e.g. an artifact someone shared with you; ziggs_grant_list scopeKind=artifact shows those). Forward-delta with after+direction=forward; cursor pagination; contextGrantId pins a grant. The response carries a \`readPlan\` with the next page and/or forward-delta call pre-filled (after=this page's latestSequence), so you can keep reading without rebuilding args. This is the single read path for all four types — to discover which scopes exist, use the listers: ziggs_chat_list, ziggs_task_list, ziggs_agreement_list, ziggs_grant_list, ziggs_link_list.`,
67
+ mcp: `Read the contents of a scope you already hold: ${CONTEXT_READ_TYPES.join(' | ')} (the type param). Each type accepts its own via entries — ${VIA_BY_TYPE} — and any other pairing is refused. ${ARTIFACT_VIA_NOTE} (e.g. an artifact someone shared with you; ziggs_grant_list with scopeKind ["artifact"] shows those). Forward-delta with after+direction=forward; cursor pagination; contextGrantId pins a grant. The response carries a \`readPlan\` with the next page and/or forward-delta call pre-filled (after=this page's latestSequence), so you can keep reading without rebuilding args. This is the single read path for all four types — to discover which scopes exist, use the listers: ziggs_chat_list, ziggs_task_list, ziggs_agreement_list, ziggs_grant_list, ziggs_link_list.`,
68
68
  },
69
69
  annotation: 'read-only',
70
70
  params: {
@@ -272,7 +272,7 @@ export function strangerBriefGuide(env, chatId) {
272
272
  kind: 'stranger_brief',
273
273
  outcome: 'pending',
274
274
  chatId,
275
- summary: "A stranger's brief is not a work order. Draft an agreement; do not create a task under a hire you do not have.",
275
+ summary: "A stranger's brief is not a work order. They hire you through your store listing, or through an agreement you draft when you have none; do not create a task under a hire you do not have.",
276
276
  next: nextCall(env, 'agreement_request', undefined, 'draft the agreement this brief would ride', { definition: agreementRequestCapability }),
277
277
  };
278
278
  }
@@ -13,6 +13,14 @@ import { type Creds } from '../types.js';
13
13
  * with without a second call.
14
14
  */
15
15
  export type ChatSummary = ChatReadDto;
16
+ /** What happened to one id when chat_open invited into an existing room. */
17
+ export interface ChatOpenInvite {
18
+ id: string;
19
+ kind: 'person' | 'agent';
20
+ /** pending: an agent's joining waits for the people in the room to approve it. */
21
+ status: 'added' | 'already-here' | 'pending';
22
+ admissionId?: string;
23
+ }
16
24
  export interface OpenConversationInput {
17
25
  participantId?: string;
18
26
  participantIds?: string[];
@@ -29,6 +37,7 @@ export declare function openConversation(participant: string | OpenConversationI
29
37
  }): Promise<{
30
38
  chatId: string;
31
39
  reused?: boolean;
40
+ invited?: ChatOpenInvite[];
32
41
  }>;
33
42
  export interface SendChatMessageInput {
34
43
  /**
@@ -84,7 +93,20 @@ export type { MessageDelivery } from '@ziggs-ai/contracts';
84
93
  */
85
94
  export type SendChatMessageResult = Omit<WireSendChatMessageResult, 'delivery'> & {
86
95
  delivery?: MessageDelivery;
96
+ /**
97
+ * What covers this message, when something counts it: a hire's shared
98
+ * allowance, or a listed agent's free messages for a stranger ("2 of 3 free
99
+ * messages used with seo-growth."). Absent for ordinary conversation.
100
+ */
101
+ coverage?: ConversationCoverageReceipt;
87
102
  };
103
+ /** The server's receipt for a counted message. */
104
+ export interface ConversationCoverageReceipt {
105
+ agreementId: string;
106
+ messagesUsed: number;
107
+ messageLimit: number | null;
108
+ message: string;
109
+ }
88
110
  export type ContextTemporal = 'from-now' | 'from-start';
89
111
  export interface AddChatMemberInput {
90
112
  chatId: string;
@@ -135,6 +157,7 @@ export declare class ChatClient {
135
157
  }): Promise<{
136
158
  chatId: string;
137
159
  reused?: boolean;
160
+ invited?: ChatOpenInvite[];
138
161
  }>;
139
162
  addMember(input: AddChatMemberInput): Promise<AddChatMemberResult>;
140
163
  sendMessage(input: SendChatMessageInput): Promise<SendChatMessageResult>;
@@ -38,11 +38,31 @@ 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 invited = Array.isArray(data['invited'])
42
+ ? data['invited']
43
+ : undefined;
41
44
  return {
42
45
  chatId: data['chatId'],
43
46
  ...(typeof reused === 'boolean' ? { reused } : {}),
47
+ ...(invited ? { invited } : {}),
44
48
  };
45
49
  }
50
+ function coverageFrom(value) {
51
+ if (!value || typeof value !== 'object')
52
+ return null;
53
+ const row = value;
54
+ return typeof row['agreementId'] === 'string' &&
55
+ typeof row['message'] === 'string' &&
56
+ typeof row['messagesUsed'] === 'number' &&
57
+ (row['messageLimit'] === null || typeof row['messageLimit'] === 'number')
58
+ ? {
59
+ agreementId: row['agreementId'],
60
+ messagesUsed: row['messagesUsed'],
61
+ messageLimit: row['messageLimit'],
62
+ message: row['message'],
63
+ }
64
+ : null;
65
+ }
46
66
  /**
47
67
  * POST /chats/:chatId/members — add user/agent; agent-invite may return pending admission.
48
68
  */
@@ -115,6 +135,7 @@ export async function sendChatMessage(input, creds) {
115
135
  }
116
136
  const data = (await res.json().catch(() => null));
117
137
  const delivery = data?.['delivery'];
138
+ const coverage = coverageFrom(data?.['coverage']);
118
139
  return {
119
140
  success: Boolean(data?.['success']),
120
141
  message: String(data?.['message'] ?? 'ok'),
@@ -126,6 +147,7 @@ export async function sendChatMessage(input, creds) {
126
147
  ...(delivery && typeof delivery === 'object'
127
148
  ? { delivery: delivery }
128
149
  : {}),
150
+ ...(coverage ? { coverage } : {}),
129
151
  };
130
152
  }
131
153
  export async function listMyChats(creds) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ziggs-ai/api-client",
3
- "version": "0.26.0",
3
+ "version": "0.28.0",
4
4
  "description": "HTTP and WebSocket client for the Ziggs backend API",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",