@ziggs-ai/api-client 0.5.0 → 0.6.1
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.
- package/dist/ConnectionManager.js +11 -0
- package/dist/capabilities/agreements.d.ts +8 -0
- package/dist/capabilities/agreements.js +45 -0
- package/dist/capabilities/artifacts.js +6 -6
- package/dist/capabilities/chat.d.ts +3 -3
- package/dist/capabilities/chat.js +8 -8
- package/dist/capabilities/connections.js +8 -8
- package/dist/capabilities/context.js +11 -11
- package/dist/capabilities/discovery.js +4 -4
- package/dist/capabilities/grants.js +3 -3
- package/dist/capabilities/index.d.ts +3 -1
- package/dist/capabilities/index.js +3 -1
- package/dist/capabilities/links.d.ts +0 -3
- package/dist/capabilities/links.js +38 -102
- package/dist/capabilities/marketplace.d.ts +8 -0
- package/dist/capabilities/marketplace.js +55 -0
- package/dist/capabilities/payments.js +2 -2
- package/dist/http/AgreementClient.d.ts +2 -0
- package/dist/http/AgreementClient.js +6 -12
- package/dist/http/ArtifactsClient.d.ts +19 -3
- package/dist/http/ArtifactsClient.js +22 -4
- package/dist/http/ChatClient.js +1 -1
- package/dist/http/ContextReadClient.js +9 -0
- package/dist/http/InboxClient.js +4 -3
- package/dist/http/agreementFlows.d.ts +34 -0
- package/dist/http/agreementFlows.js +80 -0
- package/dist/http/index.d.ts +2 -1
- package/dist/http/index.js +4 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +2 -0
- package/dist/shared/rateLimit.d.ts +46 -0
- package/dist/shared/rateLimit.js +73 -0
- package/dist/types.d.ts +12 -1
- package/package.json +1 -1
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { InboxClient } from './http/InboxClient.js';
|
|
2
2
|
import { runtimeLog } from './shared/runtimeLog.js';
|
|
3
|
+
import { isRateLimited } from './shared/rateLimit.js';
|
|
3
4
|
const OPERATOR_POLL_WAIT_SECONDS = 25;
|
|
4
5
|
const POLL_BACKOFF_BASE_MS = 1_000;
|
|
5
6
|
const POLL_BACKOFF_MAX_MS = 30_000;
|
|
@@ -99,6 +100,16 @@ export class ConnectionManager {
|
|
|
99
100
|
if (!this._polling)
|
|
100
101
|
break;
|
|
101
102
|
consecutiveErrors++;
|
|
103
|
+
// ZIG-1019: same rule as the per-agent loop — when the server throttles
|
|
104
|
+
// us it also says for how long, and retrying sooner just spends more of
|
|
105
|
+
// the budget we were told we are out of.
|
|
106
|
+
const throttleMs = isRateLimited(err) ? (err.retryAfterMs ?? 30_000) : null;
|
|
107
|
+
if (throttleMs !== null) {
|
|
108
|
+
consecutiveErrors = 0;
|
|
109
|
+
runtimeLog.warn('ConnectionManager', `operator poll throttled: ${err.message} — waiting ${Math.round(throttleMs)}ms as instructed`);
|
|
110
|
+
await sleep(throttleMs);
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
102
113
|
const backoff = Math.min(POLL_BACKOFF_MAX_MS, POLL_BACKOFF_BASE_MS * 2 ** (consecutiveErrors - 1));
|
|
103
114
|
const jitter = backoff * (0.5 + (consecutiveErrors % 7) / 14);
|
|
104
115
|
runtimeLog.warn('ConnectionManager', `operator poll failed (#${consecutiveErrors}): ${err.message} — retrying in ~${Math.round(jitter)}ms`);
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type CapabilityDefinition } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* ZIG-1021 — the one claim verb. Quests, standing offers, and link invites
|
|
4
|
+
* are all open broadcasts; claiming any of them is this call. The respond
|
|
5
|
+
* tool no longer claims — it approves/rejects direct proposals only.
|
|
6
|
+
*/
|
|
7
|
+
export declare const agreementClaimCapability: CapabilityDefinition;
|
|
8
|
+
export declare const AGREEMENT_CAPABILITIES: CapabilityDefinition[];
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { claimOpenAgreement } from '../http/agreementFlows.js';
|
|
2
|
+
import { linkIsReachOnly, linkSummary } from './links.js';
|
|
3
|
+
import { fullCreds } from './types.js';
|
|
4
|
+
/**
|
|
5
|
+
* ZIG-1021 — the one claim verb. Quests, standing offers, and link invites
|
|
6
|
+
* are all open broadcasts; claiming any of them is this call. The respond
|
|
7
|
+
* tool no longer claims — it approves/rejects direct proposals only.
|
|
8
|
+
*/
|
|
9
|
+
export const agreementClaimCapability = {
|
|
10
|
+
key: 'agreement_claim',
|
|
11
|
+
names: { sdk: 'agreement_claim', mcp: 'ziggs_agreement_claim' },
|
|
12
|
+
descriptions: {
|
|
13
|
+
sdk: 'Claim an open broadcast agreement by id — a quest (you do the work, the publisher pays), a standing offer (you buy, the publisher works), or a link invite (bilateral trust forms immediately). You become the open party and the agreement activates. Find quests/offers with marketplace_view; direct proposals are approved with agreement_respond instead, not claimed.',
|
|
14
|
+
mcp: 'Claim an open broadcast agreement by id — a quest (you do the work, the publisher pays), a standing offer (you buy, the publisher works), or a link invite (bilateral trust forms immediately). You become the open party and the agreement activates. Find quests/offers with ziggs_marketplace_view; direct proposals are approved with ziggs_agreement_respond instead, not claimed. You cannot claim your own broadcast.',
|
|
15
|
+
},
|
|
16
|
+
annotation: 'write',
|
|
17
|
+
params: {
|
|
18
|
+
agreementId: {
|
|
19
|
+
type: 'string',
|
|
20
|
+
required: true,
|
|
21
|
+
description: 'The open agreement to claim (quest / offer / link invite id)',
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
needsAgentId: true,
|
|
25
|
+
handler: async (args, env) => {
|
|
26
|
+
const { agreement, kind } = await claimOpenAgreement(args['agreementId'], fullCreds(env));
|
|
27
|
+
if (kind === 'link') {
|
|
28
|
+
return {
|
|
29
|
+
status: 'linked',
|
|
30
|
+
kind,
|
|
31
|
+
message: `Link invite claimed — you are now linked. ${linkIsReachOnly(env)}`,
|
|
32
|
+
agreement: linkSummary(agreement),
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
return {
|
|
36
|
+
status: 'claimed',
|
|
37
|
+
kind,
|
|
38
|
+
message: kind === 'offer'
|
|
39
|
+
? 'Standing offer claimed — the publisher provides, your side pays. Spawn work under it with the task-create tool.'
|
|
40
|
+
: 'Quest claimed — you provide the work. Read the terms, then post progress and set the task result under this agreement.',
|
|
41
|
+
agreement,
|
|
42
|
+
};
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
export const AGREEMENT_CAPABILITIES = [agreementClaimCapability];
|
|
@@ -3,12 +3,12 @@ import { fullCreds } from './types.js';
|
|
|
3
3
|
/**
|
|
4
4
|
* ZIG-560 teaching: name the result slot on the success path so an agent finds
|
|
5
5
|
* the right move unaided — worded in each surface's task grammar (SDK
|
|
6
|
-
*
|
|
6
|
+
* task_set_result carries the terminal result; MCP has ziggs_task_set_result).
|
|
7
7
|
*/
|
|
8
8
|
function reportingHint(env, contentType, taskId) {
|
|
9
9
|
const close = env.surface === 'mcp'
|
|
10
|
-
? '
|
|
11
|
-
: '
|
|
10
|
+
? 'ziggs_task_set_result ({ summary, status, links })'
|
|
11
|
+
: 'task_set_result';
|
|
12
12
|
if (contentType === 'result') {
|
|
13
13
|
return taskId
|
|
14
14
|
? `Recorded as a task-bound result artifact. Close the task by setting its terminal result with ${close}.`
|
|
@@ -17,13 +17,13 @@ function reportingHint(env, contentType, taskId) {
|
|
|
17
17
|
return `Reporting finished work? Record it with content_type=result bound to the task (taskId), then close the task with ${close} — chat messages are conversation only.`;
|
|
18
18
|
}
|
|
19
19
|
export const recordArtifactCapability = {
|
|
20
|
-
key: '
|
|
21
|
-
names: { sdk: '
|
|
20
|
+
key: 'artifact_record',
|
|
21
|
+
names: { sdk: 'artifact_record', mcp: 'ziggs_artifact_record' },
|
|
22
22
|
descriptions: {
|
|
23
23
|
sdk: 'Write an artifact to a chat or agreement scope. Set visibility explicitly. For a finished deliverable, set content_type=result and pass taskId to bind it to the task — heavy results belong in artifacts, not chat messages.',
|
|
24
24
|
mcp: 'Write an artifact to a chat or agreement scope. Set visibility explicitly. ' +
|
|
25
25
|
'For a finished deliverable, set content_type=result and pass taskId to bind it to the task. ' +
|
|
26
|
-
'Finished work is the task result — set it with
|
|
26
|
+
'Finished work is the task result — set it with ziggs_task_set_result; never report finished work as a chat message (chat is conversation only).',
|
|
27
27
|
},
|
|
28
28
|
annotation: 'write',
|
|
29
29
|
params: {
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { type CapabilityDefinition } from './types.js';
|
|
2
2
|
/**
|
|
3
|
-
* ZIG-900 — the decided chat surface for agents:
|
|
3
|
+
* ZIG-900 — the decided chat surface for agents: chat_open only.
|
|
4
4
|
* There is deliberately NO chat-listing tool on the SDK: agents read context
|
|
5
5
|
* only via held grants (ZIG-425) and chat membership auto-mints a chat grant
|
|
6
|
-
* (ZIG-428/426), so "what chats can I see" is already answered by
|
|
6
|
+
* (ZIG-428/426), so "what chats can I see" is already answered by grant_list
|
|
7
7
|
* scopeKind=chat (ZIG-626 rule). MCP keeps its rich session-UX lister
|
|
8
|
-
* (
|
|
8
|
+
* (ziggs_chat_list) for humans in Cursor/Claude.
|
|
9
9
|
*/
|
|
10
10
|
export declare const openConversationCapability: CapabilityDefinition;
|
|
11
11
|
export declare const CHAT_CAPABILITIES: CapabilityDefinition[];
|
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
import { openConversation } from '../http/ChatClient.js';
|
|
2
2
|
import { fullCreds } from './types.js';
|
|
3
3
|
/**
|
|
4
|
-
* ZIG-900 — the decided chat surface for agents:
|
|
4
|
+
* ZIG-900 — the decided chat surface for agents: chat_open only.
|
|
5
5
|
* There is deliberately NO chat-listing tool on the SDK: agents read context
|
|
6
6
|
* only via held grants (ZIG-425) and chat membership auto-mints a chat grant
|
|
7
|
-
* (ZIG-428/426), so "what chats can I see" is already answered by
|
|
7
|
+
* (ZIG-428/426), so "what chats can I see" is already answered by grant_list
|
|
8
8
|
* scopeKind=chat (ZIG-626 rule). MCP keeps its rich session-UX lister
|
|
9
|
-
* (
|
|
9
|
+
* (ziggs_chat_list) for humans in Cursor/Claude.
|
|
10
10
|
*/
|
|
11
11
|
export const openConversationCapability = {
|
|
12
|
-
key: '
|
|
13
|
-
names: { sdk: '
|
|
12
|
+
key: 'chat_open',
|
|
13
|
+
names: { sdk: 'chat_open', mcp: 'ziggs_chat_open' },
|
|
14
14
|
descriptions: {
|
|
15
|
-
sdk: 'Open or reuse a chat with a user or agent participant. To reach an agent in ANOTHER org, establish a link first —
|
|
16
|
-
mcp: 'Open or reuse a chat with a user or agent participant. To reach an agent in ANOTHER org, an unpublished delegate must establish a link first —
|
|
15
|
+
sdk: 'Open or reuse a chat with a user or agent participant. To reach an agent in ANOTHER org, establish a link first — agreement_propose with engagementKind "link" (if you have its agent id) or link_create_invite (if you do not), approved/claimed — otherwise this fails with AGENT_NOT_PUBLISHED. To list chats you can already read, use grant_list scopeKind=chat.',
|
|
16
|
+
mcp: 'Open or reuse a chat with a user or agent participant. To reach an agent in ANOTHER org, an unpublished delegate must establish a link first — ziggs_agreement_propose with engagementKind "link" (if you have its agent id) or ziggs_link_create_invite (if you do not), approved/claimed — otherwise this fails with AGENT_NOT_PUBLISHED.',
|
|
17
17
|
},
|
|
18
18
|
annotation: 'write',
|
|
19
19
|
params: {
|
|
@@ -28,7 +28,7 @@ export const openConversationCapability = {
|
|
|
28
28
|
if (!args['participantId'])
|
|
29
29
|
throw new Error('participantId is required');
|
|
30
30
|
const { chatId } = await openConversation(args['participantId'], fullCreds(env));
|
|
31
|
-
const lister = env.surface === 'mcp' ? '
|
|
31
|
+
const lister = env.surface === 'mcp' ? 'ziggs_grant_list' : 'grant_list';
|
|
32
32
|
return {
|
|
33
33
|
chatId,
|
|
34
34
|
note: `Conversation is open (or reused). Your membership auto-mints a chat grant, so the chat also appears in ${lister} scopeKind=chat for context reads.`,
|
|
@@ -10,11 +10,11 @@ export const connectionProxyCapability = {
|
|
|
10
10
|
key: 'connection_proxy',
|
|
11
11
|
names: { sdk: 'connection_proxy', mcp: 'ziggs_connection_proxy' },
|
|
12
12
|
descriptions: {
|
|
13
|
-
sdk: "Use a stored connection (a third-party credential, e.g. the owner's GitHub/Jira) without ever seeing the credential. Calls the backend connections proxy with a grant the owner issued to this agent: the proxy enforces the grant, decrypts the token server-side, makes the upstream provider call, and returns the result (token-leak guarded on both sides). Provide connectionId, grantId, the provider action (e.g. repo:read), and an optional action-specific payload. Don't know connectionId/grantId yet? Use
|
|
14
|
-
mcp: "Use a stored connection (a third-party credential, e.g. the owner's GitHub/Jira — NOT an agent-to-agent Link, see
|
|
13
|
+
sdk: "Use a stored connection (a third-party credential, e.g. the owner's GitHub/Jira) without ever seeing the credential. Calls the backend connections proxy with a grant the owner issued to this agent: the proxy enforces the grant, decrypts the token server-side, makes the upstream provider call, and returns the result (token-leak guarded on both sides). Provide connectionId, grantId, the provider action (e.g. repo:read), and an optional action-specific payload. Don't know connectionId/grantId yet? Use grant_list (scopeKind=connection) or connection_list_grants first.",
|
|
14
|
+
mcp: "Use a stored connection (a third-party credential, e.g. the owner's GitHub/Jira — NOT an agent-to-agent Link, see ziggs_link_list for that) without ever seeing the credential. " +
|
|
15
15
|
'Calls the backend connections proxy with a grant the owner issued to this agent: the proxy enforces the grant, decrypts the token server-side, makes the upstream provider call, and returns the result (token-leak guarded on both sides). ' +
|
|
16
16
|
'Provide connectionId, grantId, the provider action (e.g. repo:read), and an optional action-specific payload. ' +
|
|
17
|
-
"Don't know connectionId/grantId yet? Call
|
|
17
|
+
"Don't know connectionId/grantId yet? Call ziggs_connection_list first.",
|
|
18
18
|
},
|
|
19
19
|
annotation: 'write',
|
|
20
20
|
params: {
|
|
@@ -50,13 +50,13 @@ export const connectionProxyCapability = {
|
|
|
50
50
|
},
|
|
51
51
|
};
|
|
52
52
|
export const requestConnectionCapability = {
|
|
53
|
-
key: '
|
|
54
|
-
names: { sdk: '
|
|
53
|
+
key: 'connection_request',
|
|
54
|
+
names: { sdk: 'connection_request', mcp: 'ziggs_connection_request' },
|
|
55
55
|
descriptions: {
|
|
56
56
|
sdk: 'Ask your principal (the human) to connect a remote MCP server and grant you the listed tools. Opens a connection-consent agreement as an approvable card in the chat you pass — the human approves it there like any other agreement. On approval the server is connected (browser OAuth if needed) and you are granted the tools; use them via connection_proxy.',
|
|
57
57
|
mcp: 'Ask your principal (the human) to connect a remote MCP server and grant you the listed tools. ' +
|
|
58
58
|
'Opens a connection-consent agreement as an approvable card in the chat you pass — the human approves it there like any other agreement (there is no MCP tool to approve it, so tell them to approve it in the chat). ' +
|
|
59
|
-
'On approval the server is connected (browser OAuth if needed) and you are granted the tools; the result shows up in
|
|
59
|
+
'On approval the server is connected (browser OAuth if needed) and you are granted the tools; the result shows up in ziggs_connection_list for use with ziggs_connection_proxy.',
|
|
60
60
|
},
|
|
61
61
|
annotation: 'write',
|
|
62
62
|
params: {
|
|
@@ -96,9 +96,9 @@ export const requestConnectionCapability = {
|
|
|
96
96
|
...result,
|
|
97
97
|
note: env.surface === 'mcp'
|
|
98
98
|
? '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. ' +
|
|
99
|
-
'Once approved, the connection + grant appear in
|
|
99
|
+
'Once approved, the connection + grant appear in ziggs_connection_list for ziggs_connection_proxy.'
|
|
100
100
|
: 'A connection-consent card is now in the chat awaiting your principal — they approve it right there (ZIG-798 flow). ' +
|
|
101
|
-
'Once approved, the connection + grant appear in
|
|
101
|
+
'Once approved, the connection + grant appear in grant_list (scopeKind=connection) / connection_list_grants for connection_proxy.',
|
|
102
102
|
};
|
|
103
103
|
}
|
|
104
104
|
catch (e) {
|
|
@@ -43,15 +43,15 @@ export async function resolveOrgScopeId(env, scopeId) {
|
|
|
43
43
|
}
|
|
44
44
|
if (scopeId.startsWith('org_'))
|
|
45
45
|
return scopeId;
|
|
46
|
-
const lister = env.surface === 'mcp' ? '
|
|
46
|
+
const lister = env.surface === 'mcp' ? 'ziggs_org_list' : 'your org list';
|
|
47
47
|
throw new Error(`No org named "${scopeId}" in your memberships — use ${lister} to see them, or pass the org id.`);
|
|
48
48
|
}
|
|
49
49
|
export const contextReadCapability = {
|
|
50
50
|
key: 'context_read',
|
|
51
|
-
names: { sdk: 'context_read', mcp: '
|
|
51
|
+
names: { sdk: 'context_read', mcp: 'ziggs_context_read' },
|
|
52
52
|
descriptions: {
|
|
53
|
-
sdk: 'Read the contents of a scope you already hold a grant for: messages | artifacts | agreements | tasks. Use
|
|
54
|
-
mcp: 'Read the contents of a scope you already hold: messages | artifacts | agreements | tasks (the type param), under via=chat:<id>, agreement:<id>, or task:<id>. 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
|
|
53
|
+
sdk: 'Read the contents of a scope you already hold a grant for: messages | artifacts | agreements | tasks. Use grant_list first to see which scopes your grants cover, then read through any of them. Cursored; all access is grant-fenced server-side.',
|
|
54
|
+
mcp: 'Read the contents of a scope you already hold: messages | artifacts | agreements | tasks (the type param), under via=chat:<id>, agreement:<id>, or task:<id>. 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.',
|
|
55
55
|
},
|
|
56
56
|
annotation: 'read-only',
|
|
57
57
|
params: {
|
|
@@ -107,10 +107,10 @@ export const contextReadCapability = {
|
|
|
107
107
|
};
|
|
108
108
|
export const contextExpandReachCapability = {
|
|
109
109
|
key: 'context_expand_reach',
|
|
110
|
-
names: { sdk: 'context_expand_reach', mcp: '
|
|
110
|
+
names: { sdk: 'context_expand_reach', mcp: 'ziggs_context_expand_reach' },
|
|
111
111
|
descriptions: {
|
|
112
|
-
sdk: 'Expand a grant you hold into the chat/agreement ids inside its scope, so you can read through it.
|
|
113
|
-
mcp: 'Expand a grant you hold into the chat/agreement ids inside its scope, so you can actually read through it.
|
|
112
|
+
sdk: 'Expand a grant you hold into the chat/agreement ids inside its scope, so you can read through it. grant_list tells you that you hold e.g. org:acme or agreement:x; this returns the actual { chats, agreements } (ids + labels only, no content) that scope covers — feed an id to context_read (via=chat:<id> / agreement:<id>). Org scope is capped: truncatedChats/truncatedAgreements say how many were left off. Holder-only, grant-fenced.',
|
|
113
|
+
mcp: 'Expand a grant you hold into the chat/agreement ids inside its scope, so you can actually read through it. ziggs_grant_list tells you that you hold e.g. org:acme or agreement:x; this returns the { chats, agreements } (ids + labels only, never content) that scope covers — feed an id to ziggs_context_read (via=chat:<id> / agreement:<id>). Org scope is capped: truncatedChats/truncatedAgreements say how many were left off. Holder-only, grant-fenced.',
|
|
114
114
|
},
|
|
115
115
|
annotation: 'read-only',
|
|
116
116
|
params: {
|
|
@@ -131,10 +131,10 @@ export const contextExpandReachCapability = {
|
|
|
131
131
|
};
|
|
132
132
|
export const contextDiscoverGrantableCapability = {
|
|
133
133
|
key: 'context_discover_grantable',
|
|
134
|
-
names: { sdk: 'context_discover_grantable', mcp: '
|
|
134
|
+
names: { sdk: 'context_discover_grantable', mcp: 'ziggs_context_discover_grantable' },
|
|
135
135
|
descriptions: {
|
|
136
|
-
sdk: 'See what context EXISTS in your orgs that you CANNOT read yet — the inverse of
|
|
137
|
-
mcp: 'See what context EXISTS in your orgs that you CANNOT read yet — so you can ask for it instead of failing blind. Covers chats, agreements, and connections (type is "chat" | "agreement" | "connection"; connection labels are the provider name only). Returns labels only: { type, label, scopeRef, orgId } per item, never content, member names, tokens, or money. Bounded to orgs you have an active agreement in. To act on one, ask your human to grant it, or (if you hold a broader grant of your own) delegate via
|
|
136
|
+
sdk: 'See what context EXISTS in your orgs that you CANNOT read yet — the inverse of grant_list. Covers chats, agreements, and connections (type is one of "chat" | "agreement" | "connection"; connection labels are the provider name only). Returns labels only ({ type, label, scopeRef, orgId } per item), never content, member names, tokens, or money. Bounded to orgs you have an active agreement in, and excludes anything you already hold a grant for. Use it to notice you may be missing context, then either ask your human to grant a scopeRef, or (if you hold a broader grant) context_delegate using that scopeRef. Pair with grant_list (what you hold) and context_expand_reach (what a held grant covers).',
|
|
137
|
+
mcp: 'See what context EXISTS in your orgs that you CANNOT read yet — so you can ask for it instead of failing blind. Covers chats, agreements, and connections (type is "chat" | "agreement" | "connection"; connection labels are the provider name only). Returns labels only: { type, label, scopeRef, orgId } per item, never content, member names, tokens, or money. Bounded to orgs you have an active agreement in. To act on one, ask your human to grant it, or (if you hold a broader grant of your own) delegate via ziggs_context_delegate using the scopeRef. Use ziggs_grant_list for what you already hold; this is what you lack.',
|
|
138
138
|
},
|
|
139
139
|
annotation: 'read-only',
|
|
140
140
|
params: {},
|
|
@@ -147,7 +147,7 @@ export const contextDiscoverGrantableCapability = {
|
|
|
147
147
|
};
|
|
148
148
|
export const contextDelegateCapability = {
|
|
149
149
|
key: 'context_delegate',
|
|
150
|
-
names: { sdk: 'context_delegate', mcp: '
|
|
150
|
+
names: { sdk: 'context_delegate', mcp: 'ziggs_context_delegate' },
|
|
151
151
|
descriptions: {
|
|
152
152
|
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).',
|
|
153
153
|
mcp: 'Delegate a narrower child grant from one you hold (POST /context/grants/:id/delegate). Delegation only narrows scope/expiry/temporal — never broadens. If the grant\'s original owner is a different party, this does NOT grant — it opens a request that owner must approve, and returns { status: "pending_approval", agreementId }; surface that to the human and do not treat it as done.',
|
|
@@ -2,10 +2,10 @@ import { AgentSearchClient } from '../http/AgentSearchClient.js';
|
|
|
2
2
|
import { fullCreds } from './types.js';
|
|
3
3
|
export const agentSearchCapability = {
|
|
4
4
|
key: 'agent_search',
|
|
5
|
-
names: { sdk: 'agent_search', mcp: '
|
|
5
|
+
names: { sdk: 'agent_search', mcp: 'ziggs_agent_search' },
|
|
6
6
|
descriptions: {
|
|
7
7
|
sdk: 'Search for agents by capability, name, or description. Returns ranked results with relevance scores. A keyword/natural-language query searches the published store AND, scoped to your authority, your own org-mates and any delegate you have an active link with. Passing an EXACT agent id resolves that one agent even if unpublished/private. Use before agreement_propose or agreement_subcontract to discover the right agent for a job; use returned agentId in grant/issue tools — do not guess ids.',
|
|
8
|
-
mcp: 'Find agents (AgentSearchClient). A keyword/natural-language query searches the published store AND, scoped to your authority, your own org-mates and any delegate you have an active link with — so you can find a teammate or another user\'s delegate by name and
|
|
8
|
+
mcp: 'Find agents (AgentSearchClient). A keyword/natural-language query searches the published store AND, scoped to your authority, your own org-mates and any delegate you have an active link with — so you can find a teammate or another user\'s delegate by name and ziggs_chat_open with it directly, even if it is unpublished/offline and has never been in a chat with you. Passing an EXACT agent id resolves that one agent even if unpublished/private — use this for a delegate someone shared an id for, then propose a link (ziggs_agreement_propose engagementKind="link") if not yet linked. Each result carries a per-row `reachability` field derived from HOW you can reach it — `published` (store directory), `same-org`, `linked`, or `managed`; it is not a blanket "published" label. If an exact-id lookup matches an unpublished agent you cannot reach, the row is `reachability: "restricted"` and returns the id only with no name/profile. Use returned agentId in grant/issue tools — do not guess ids.',
|
|
9
9
|
},
|
|
10
10
|
annotation: 'read-only',
|
|
11
11
|
params: {
|
|
@@ -48,10 +48,10 @@ export const agentSearchCapability = {
|
|
|
48
48
|
};
|
|
49
49
|
export const agentGetCapability = {
|
|
50
50
|
key: 'agent_get',
|
|
51
|
-
names: { sdk: 'agent_get', mcp: '
|
|
51
|
+
names: { sdk: 'agent_get', mcp: 'ziggs_agent_get' },
|
|
52
52
|
descriptions: {
|
|
53
53
|
sdk: 'Fetch the full profile of a specific agent by ID — name, description, tags, capabilities, reachability, and reliability. Use to confirm capabilities and terms before proposing an agreement, when you already hold the agent id. An id you cannot reach returns reachability "restricted" (id only, no profile). To find an agent by keyword instead, use agent_search.',
|
|
54
|
-
mcp: 'Fetch the full profile of ONE agent by its exact id (GET /agents/:id) — name, description, tags, capabilities, reachability, and reliability. Use to confirm a candidate before
|
|
54
|
+
mcp: 'Fetch the full profile of ONE agent by its exact id (GET /agents/:id) — name, description, tags, capabilities, reachability, and reliability. Use to confirm a candidate before ziggs_agreement_propose (direct, broadcast, or link), when you already hold the agent id (from ziggs_agent_search, a grant, or an agreement party). Grant-scoped: an id you cannot reach returns reachability "restricted" (id only, no profile). To find an agent by keyword instead, use ziggs_agent_search.',
|
|
55
55
|
},
|
|
56
56
|
annotation: 'read-only',
|
|
57
57
|
params: {
|
|
@@ -29,11 +29,11 @@ function parseScopeKinds(raw) {
|
|
|
29
29
|
* list is never presented as complete when the key can't read a rail.
|
|
30
30
|
*/
|
|
31
31
|
export const listGrantsCapability = {
|
|
32
|
-
key: '
|
|
33
|
-
names: { sdk: '
|
|
32
|
+
key: 'grant_list',
|
|
33
|
+
names: { sdk: 'grant_list', mcp: 'ziggs_grant_list' },
|
|
34
34
|
descriptions: {
|
|
35
35
|
sdk: 'List every grant this agent holds across all rails in one call — context (chat/agreement/org), connection, and wallet — as canonical grants (grantId, scope, caveats, expiresAt, health; no message/artifact content or credentials). The single answer to "what grants of mine do you hold?", holder-scoped and cross-session. Filter by scopeKind (rail) and health (defaults to active). Rails you lack the operator-key read scope for are named in unreadableRails, not silently dropped. Cursor-paginated: pass cursor from a prior nextCursor. Pair with context_read to read through a context grant, or context_expand_reach to enumerate a scope.',
|
|
36
|
-
mcp: 'List every grant this delegate holds across all rails in one call — context (chat/agreement/org), connection, and wallet — as canonical grants (grantId, scope, caveats, expiresAt, health; no content or credentials). The single answer to "what grants of mine do you hold?", holder-scoped and cross-session. Filter by scopeKind (rail) and health (defaults to active). Rails you lack the operator-key read scope for are named in unreadableRails, not silently dropped. Cursor-paginated: pass cursor from a prior nextCursor to page. Pass a grantId to
|
|
36
|
+
mcp: 'List every grant this delegate holds across all rails in one call — context (chat/agreement/org), connection, and wallet — as canonical grants (grantId, scope, caveats, expiresAt, health; no content or credentials). The single answer to "what grants of mine do you hold?", holder-scoped and cross-session. Filter by scopeKind (rail) and health (defaults to active). Rails you lack the operator-key read scope for are named in unreadableRails, not silently dropped. Cursor-paginated: pass cursor from a prior nextCursor to page. Pass a grantId to ziggs_context_read to pin a specific grant, or ziggs_context_expand_reach to enumerate a scope.',
|
|
37
37
|
},
|
|
38
38
|
annotation: 'read-only',
|
|
39
39
|
params: {
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
export { type CapabilitySurface, type CapabilityAnnotation, type CapabilityParam, type CapabilityEnv, type CapabilityDefinition, fullCreds, rethrowWithContext, } from './types.js';
|
|
2
2
|
export { PAYMENT_CAPABILITIES, paymentBalanceCapability, paymentTransferCapability, paymentWaitForApprovalCapability, paymentHoldCapability, paymentReleaseCapability, paymentResolveWalletCapability, paymentIssueGrantCapability, paymentAttenuateGrantCapability, paymentRevokeGrantCapability, } from './payments.js';
|
|
3
|
-
export { LINK_CAPABILITIES,
|
|
3
|
+
export { LINK_CAPABILITIES, createLinkInviteCapability, listLinksCapability, linkSummary, linkIsReachOnly, } from './links.js';
|
|
4
|
+
export { AGREEMENT_CAPABILITIES, agreementClaimCapability } from './agreements.js';
|
|
5
|
+
export { MARKETPLACE_CAPABILITIES, marketplaceViewCapability } from './marketplace.js';
|
|
4
6
|
export { GRANTS_CAPABILITIES, listGrantsCapability } from './grants.js';
|
|
5
7
|
export { CONTEXT_CAPABILITIES, contextReadCapability, contextDelegateCapability, contextExpandReachCapability, contextDiscoverGrantableCapability, contextBounds, resolveOrgScopeId, } from './context.js';
|
|
6
8
|
export { CONNECTION_CAPABILITIES, connectionProxyCapability, requestConnectionCapability, } from './connections.js';
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
export { fullCreds, rethrowWithContext, } from './types.js';
|
|
2
2
|
export { PAYMENT_CAPABILITIES, paymentBalanceCapability, paymentTransferCapability, paymentWaitForApprovalCapability, paymentHoldCapability, paymentReleaseCapability, paymentResolveWalletCapability, paymentIssueGrantCapability, paymentAttenuateGrantCapability, paymentRevokeGrantCapability, } from './payments.js';
|
|
3
|
-
export { LINK_CAPABILITIES,
|
|
3
|
+
export { LINK_CAPABILITIES, createLinkInviteCapability, listLinksCapability, linkSummary, linkIsReachOnly, } from './links.js';
|
|
4
|
+
export { AGREEMENT_CAPABILITIES, agreementClaimCapability } from './agreements.js';
|
|
5
|
+
export { MARKETPLACE_CAPABILITIES, marketplaceViewCapability } from './marketplace.js';
|
|
4
6
|
export { GRANTS_CAPABILITIES, listGrantsCapability } from './grants.js';
|
|
5
7
|
export { CONTEXT_CAPABILITIES, contextReadCapability, contextDelegateCapability, contextExpandReachCapability, contextDiscoverGrantableCapability, contextBounds, resolveOrgScopeId, } from './context.js';
|
|
6
8
|
export { CONNECTION_CAPABILITIES, connectionProxyCapability, requestConnectionCapability, } from './connections.js';
|
|
@@ -9,9 +9,6 @@ import { type CapabilityDefinition, type CapabilityEnv } from './types.js';
|
|
|
9
9
|
export declare function linkSummary(a: Agreement): Record<string, unknown>;
|
|
10
10
|
/** A link is reach-only — the follow-up move differs by surface tool names. */
|
|
11
11
|
export declare function linkIsReachOnly(env: CapabilityEnv): string;
|
|
12
|
-
export declare const requestLinkCapability: CapabilityDefinition;
|
|
13
12
|
export declare const createLinkInviteCapability: CapabilityDefinition;
|
|
14
|
-
export declare const claimLinkInviteCapability: CapabilityDefinition;
|
|
15
13
|
export declare const listLinksCapability: CapabilityDefinition;
|
|
16
|
-
export declare const revokeLinkCapability: CapabilityDefinition;
|
|
17
14
|
export declare const LINK_CAPABILITIES: CapabilityDefinition[];
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createAgreement,
|
|
1
|
+
import { createAgreement, listAgreements } from '../http/AgreementClient.js';
|
|
2
2
|
import { fullCreds } from './types.js';
|
|
3
3
|
const DEFAULT_WEB_URL = 'https://ziggsai.com';
|
|
4
4
|
/** Public Streamable-HTTP MCP endpoint; OAuth is discovered from it (RFC 9728). */
|
|
@@ -19,7 +19,7 @@ function invitePasteText(agreementId, claimUrl) {
|
|
|
19
19
|
`1. Add this MCP server: ${ZIGGS_MCP_URL}`,
|
|
20
20
|
' It speaks Streamable HTTP and uses OAuth — pasting the URL is enough,',
|
|
21
21
|
' but I may need to approve a consent screen in my browser.',
|
|
22
|
-
`2. Once connected, call the tool
|
|
22
|
+
`2. Once connected, call the tool ziggs_agreement_claim with agreementId "${agreementId}".`,
|
|
23
23
|
'3. Then tell me who I am linked with, and what they can and cannot see.',
|
|
24
24
|
'',
|
|
25
25
|
'If you cannot add MCP servers yourself, tell me exactly where to paste that',
|
|
@@ -52,108 +52,73 @@ export function linkSummary(a) {
|
|
|
52
52
|
/** A link is reach-only — the follow-up move differs by surface tool names. */
|
|
53
53
|
export function linkIsReachOnly(env) {
|
|
54
54
|
return env.surface === 'mcp'
|
|
55
|
-
? 'A link is reach-only. Open a chat with the peer (
|
|
55
|
+
? 'A link is reach-only. Open a chat with the peer (ziggs_chat_open, participantId = peer agent id) and grant it chat access with ziggs_context_issue_grant, or share a slice of a grant you hold with ziggs_context_delegate, before reading context.'
|
|
56
56
|
: 'A link is reach-only: it does not grant context. Share a slice of a grant you hold with context_delegate, or ask the peer owner to issue one, before reading.';
|
|
57
57
|
}
|
|
58
58
|
const LINK_STATUSES = ['active', 'open', 'cancelled', 'all'];
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
59
|
+
/**
|
|
60
|
+
* Seat ceiling for one invite. ⚠️ SYNC: backend
|
|
61
|
+
* src/agreements/agreements.service.ts MAX_LINK_INVITE_CLAIMS — the backend
|
|
62
|
+
* rejects anything past it; this is only so the tool description says the
|
|
63
|
+
* limit instead of letting an agent discover it by getting a 400.
|
|
64
|
+
*/
|
|
65
|
+
const MAX_LINK_INVITE_CLAIMS = 25;
|
|
66
|
+
// ZIG-1021/1022 — the link rail shrank to two tools. Links are agreements, so
|
|
67
|
+
// the agreement verbs carry the rest: request a direct link with
|
|
68
|
+
// agreement_propose (engagementKind 'link', proposedTo = the agent id), claim
|
|
69
|
+
// an invite with agreement_claim, end a link with agreement_revoke.
|
|
70
|
+
export const createLinkInviteCapability = {
|
|
71
|
+
key: 'link_create_invite',
|
|
72
|
+
names: { sdk: 'link_create_invite', mcp: 'ziggs_link_create_invite' },
|
|
62
73
|
descriptions: {
|
|
63
|
-
sdk:
|
|
64
|
-
mcp: '
|
|
74
|
+
sdk: "Create a shareable OPEN link invite (bilateral agent-to-agent trust) when you do NOT have the counterparty's agent id (e.g. connecting across orgs). Creates an open link agreement proposed to everyone; the recipient forms the link by claiming the returned inviteId with agreement_claim. Set maxClaims to let several people claim the same link — each gets their own separate connection. When you DO have the agent id, propose the link directly instead: agreement_propose with engagementKind \"link\" and proposedTo = that id.",
|
|
75
|
+
mcp: 'Create a shareable OPEN link invite (bilateral agent-to-agent trust, NOT a third-party service connection — see ziggs_connection_list for that) when you do NOT have the counterparty\'s agent id (e.g. connecting across orgs). Creates an open link agreement (POST /agreements {engagementKind:"link"}, proposedTo:"everyone"). Share the returned inviteId (agreementId) out-of-band; the recipient forms the link by calling ziggs_agreement_claim — neither side pastes an agent id. Set maxClaims to share ONE link with several people; each claimer gets their own separate connection, not a group. Revoke via ziggs_agreement_revoke to disable. When you DO have the agent id, propose the link directly instead: ziggs_agreement_propose with engagementKind "link" and proposedTo = that id.',
|
|
65
76
|
},
|
|
66
77
|
annotation: 'write',
|
|
67
78
|
params: {
|
|
68
|
-
providerId: {
|
|
69
|
-
type: 'string',
|
|
70
|
-
required: true,
|
|
71
|
-
description: 'Bare agent id to link with (the target delegate). Use agent search or a known delegate id — do not guess.',
|
|
72
|
-
},
|
|
73
79
|
message: {
|
|
74
80
|
type: 'string',
|
|
75
|
-
description: 'Optional note shown to
|
|
81
|
+
description: 'Optional note shown to whoever opens the invite (agreement description)',
|
|
82
|
+
},
|
|
83
|
+
maxClaims: {
|
|
84
|
+
type: 'number',
|
|
85
|
+
description: `How many people may claim this one link (default 1, max ${MAX_LINK_INVITE_CLAIMS}). Each claimer forms their own separate connection with you — this does not create a group.`,
|
|
76
86
|
},
|
|
77
87
|
},
|
|
78
88
|
needsAgentId: true,
|
|
79
89
|
handler: async (args, env) => {
|
|
90
|
+
const maxClaims = args['maxClaims'];
|
|
80
91
|
const { agreement } = await createAgreement({
|
|
81
92
|
engagementKind: 'link',
|
|
82
|
-
providerId: args['providerId'],
|
|
83
93
|
description: args['message'],
|
|
94
|
+
...(maxClaims == null ? {} : { maxClaims }),
|
|
84
95
|
}, fullCreds(env));
|
|
85
|
-
return {
|
|
86
|
-
status: 'pending',
|
|
87
|
-
message: env.surface === 'mcp'
|
|
88
|
-
? 'Link agreement created — the counterparty owner must approve (ziggs_respond_to_agreement) before cross-org reach. Surface pending state to the human.'
|
|
89
|
-
: 'Link agreement created — the counterparty owner must approve (agreement_respond on their side) before cross-org reach. Surface pending state to your principal.',
|
|
90
|
-
agreement: linkSummary(agreement),
|
|
91
|
-
};
|
|
92
|
-
},
|
|
93
|
-
sdkOptions: { isAgreementCreation: true },
|
|
94
|
-
};
|
|
95
|
-
export const createLinkInviteCapability = {
|
|
96
|
-
key: 'create_link_invite',
|
|
97
|
-
names: { sdk: 'create_link_invite', mcp: 'ziggs_create_link_invite' },
|
|
98
|
-
descriptions: {
|
|
99
|
-
sdk: "Create a shareable OPEN link invite (bilateral agent-to-agent trust) when you do NOT have the counterparty's agent id (e.g. connecting across orgs). Creates an open link agreement proposed to everyone; the recipient forms the link by claiming the returned inviteId.",
|
|
100
|
-
mcp: 'Create a shareable OPEN link invite (bilateral agent-to-agent trust, NOT a third-party service connection — see ziggs_list_my_connections for that) when you do NOT have the counterparty\'s agent id (e.g. connecting across orgs). Creates an open link agreement (POST /agreements {engagementKind:"link"}, proposedTo:"everyone"). Share the returned inviteId (agreementId) out-of-band; the recipient forms the link by calling ziggs_claim_link_invite — neither side pastes an agent id. Revoke via ziggs_revoke_link to disable.',
|
|
101
|
-
},
|
|
102
|
-
annotation: 'write',
|
|
103
|
-
params: {
|
|
104
|
-
message: {
|
|
105
|
-
type: 'string',
|
|
106
|
-
description: 'Optional note shown to whoever opens the invite (agreement description)',
|
|
107
|
-
},
|
|
108
|
-
},
|
|
109
|
-
needsAgentId: true,
|
|
110
|
-
handler: async (args, env) => {
|
|
111
|
-
const { agreement } = await createAgreement({ engagementKind: 'link', description: args['message'] }, fullCreds(env));
|
|
112
96
|
const claimUrl = `${webAppOrigin(env)}/app/link-invites/${agreement.agreementId}`;
|
|
97
|
+
const seats = agreement.linkInvite?.maxClaims ?? 1;
|
|
98
|
+
const seatNote = seats > 1
|
|
99
|
+
? `valid 7 days and claimable by up to ${seats} people (each gets their own separate connection — not a group)`
|
|
100
|
+
: 'single-use and valid 7 days';
|
|
113
101
|
return {
|
|
114
102
|
status: 'open',
|
|
115
103
|
inviteId: agreement.agreementId,
|
|
116
104
|
claimUrl,
|
|
105
|
+
maxClaims: seats,
|
|
106
|
+
seatsRemaining: seats - (agreement.linkInvite?.claimsUsed ?? 0),
|
|
117
107
|
pasteText: invitePasteText(agreement.agreementId, claimUrl),
|
|
118
108
|
message: env.surface === 'mcp'
|
|
119
|
-
?
|
|
120
|
-
:
|
|
109
|
+
? `Open link invite created, ${seatNote}. Give the human BOTH forms and say which is which: claimUrl for a recipient who already uses Ziggs, pasteText for one who has an AI assistant but no Ziggs account — pasting it makes their assistant connect and claim the invite itself. A recipient with no Ziggs account can sign up straight from the link; no beta code needed. No agent id needed on either side.`
|
|
110
|
+
: `Open link invite created, ${seatNote}. Share claimUrl with a counterparty who already uses Ziggs, or pasteText with one who has an assistant but no Ziggs account yet — their assistant connects and claims it. A recipient with no Ziggs account can sign up straight from the link; no beta code needed. No agent id needed on either side. Revoke with agreement_revoke to disable.`,
|
|
121
111
|
agreement: linkSummary(agreement),
|
|
122
112
|
};
|
|
123
113
|
},
|
|
124
114
|
sdkOptions: { isAgreementCreation: true },
|
|
125
115
|
};
|
|
126
|
-
export const claimLinkInviteCapability = {
|
|
127
|
-
key: 'claim_link_invite',
|
|
128
|
-
names: { sdk: 'claim_link_invite', mcp: 'ziggs_claim_link_invite' },
|
|
129
|
-
descriptions: {
|
|
130
|
-
sdk: 'Claim an open link invite by its id to form a bilateral link. You become the counterparty and the link activates immediately (cross-org reach). You cannot claim your own invite.',
|
|
131
|
-
mcp: 'Claim an open link invite by its id to form a bilateral link (agent-to-agent trust, NOT a third-party service connection — see ziggs_list_my_connections for that) (POST /agreements/:id/claim). You become the counterparty and the link activates immediately (cross-org reach + bilateral context grants). You cannot claim your own invite.',
|
|
132
|
-
},
|
|
133
|
-
annotation: 'write',
|
|
134
|
-
params: {
|
|
135
|
-
agreementId: {
|
|
136
|
-
type: 'string',
|
|
137
|
-
required: true,
|
|
138
|
-
description: 'The invite id (agreementId) shared by the issuer',
|
|
139
|
-
},
|
|
140
|
-
},
|
|
141
|
-
needsAgentId: true,
|
|
142
|
-
handler: async (args, env) => {
|
|
143
|
-
const { agreement } = await claimAgreement(args['agreementId'], fullCreds(env));
|
|
144
|
-
return {
|
|
145
|
-
status: 'linked',
|
|
146
|
-
message: `Link invite claimed — you are now linked. ${linkIsReachOnly(env)}`,
|
|
147
|
-
agreement: linkSummary(agreement),
|
|
148
|
-
};
|
|
149
|
-
},
|
|
150
|
-
};
|
|
151
116
|
export const listLinksCapability = {
|
|
152
|
-
key: '
|
|
153
|
-
names: { sdk: '
|
|
117
|
+
key: 'link_list',
|
|
118
|
+
names: { sdk: 'link_list', mcp: 'ziggs_link_list' },
|
|
154
119
|
descriptions: {
|
|
155
|
-
sdk: 'List link agreements for this agent — bilateral agent-to-agent trust relationships (GET /agreements?engagementKind=link). Defaults to ACTIVE links only; pass status to see pending proposals ("open") or revoked ones ("cancelled"). Each item is a link summary: agreementId, status, proposalStatus, parties.',
|
|
156
|
-
mcp: 'List link agreements for this delegate — bilateral agent-to-agent trust relationships, NOT third-party service connections (see
|
|
120
|
+
sdk: 'List link agreements for this agent — bilateral agent-to-agent trust relationships (GET /agreements?engagementKind=link). Defaults to ACTIVE links only; pass status to see pending proposals ("open") or revoked ones ("cancelled"). Each item is a link summary: agreementId, status, proposalStatus, parties. Request a new link with agreement_propose (engagementKind "link"), or link_create_invite when you lack the agent id; end one with agreement_revoke.',
|
|
121
|
+
mcp: 'List link agreements for this delegate — bilateral agent-to-agent trust relationships, NOT third-party service connections (see ziggs_connection_list for those) (GET /agreements?engagementKind=link). Defaults to ACTIVE links only; pass status to see pending proposals ("open") or revoked ones ("cancelled"). Each item is a link summary: agreementId, status, proposalStatus, parties.creatorAgent (requester), parties.providerAgent (target), parties.proposedTo (target owner). Approve pending links via ziggs_agreement_respond; request a new one with ziggs_agreement_propose (engagementKind "link"), or ziggs_link_create_invite when you lack the agent id; end one with ziggs_agreement_revoke.',
|
|
157
122
|
},
|
|
158
123
|
annotation: 'read-only',
|
|
159
124
|
params: {
|
|
@@ -184,36 +149,7 @@ export const listLinksCapability = {
|
|
|
184
149
|
},
|
|
185
150
|
sdkOptions: { isGenericFallback: true },
|
|
186
151
|
};
|
|
187
|
-
export const revokeLinkCapability = {
|
|
188
|
-
key: 'revoke_link',
|
|
189
|
-
names: { sdk: 'revoke_link', mcp: 'ziggs_revoke_link' },
|
|
190
|
-
descriptions: {
|
|
191
|
-
sdk: 'Revoke a bilateral link agreement (DELETE /agreements/:agreementId). Either party may revoke; cross-org reach ends immediately. For non-link agreements (hire/service/quest), use agreement_revoke — same endpoint, different messaging.',
|
|
192
|
-
mcp: 'Revoke a bilateral link agreement — agent-to-agent trust, NOT a third-party service connection (DELETE /agreements/:agreementId). Either party may revoke; cross-org reach ends immediately. For non-link agreements (hire/service/quest), use ziggs_revoke_agreement — same endpoint, different messaging.',
|
|
193
|
-
},
|
|
194
|
-
annotation: 'destructive',
|
|
195
|
-
params: {
|
|
196
|
-
agreementId: {
|
|
197
|
-
type: 'string',
|
|
198
|
-
required: true,
|
|
199
|
-
description: 'agreementId of the link agreement (from the link lister)',
|
|
200
|
-
},
|
|
201
|
-
},
|
|
202
|
-
needsAgentId: true,
|
|
203
|
-
handler: async (args, env) => {
|
|
204
|
-
const result = await revokeAgreement(args['agreementId'], fullCreds(env));
|
|
205
|
-
return {
|
|
206
|
-
status: 'revoked',
|
|
207
|
-
message: 'Link revoked — unpublished cross-org reach to this peer is blocked again.',
|
|
208
|
-
agreementId: args['agreementId'],
|
|
209
|
-
agreement: result.agreement ? linkSummary(result.agreement) : null,
|
|
210
|
-
};
|
|
211
|
-
},
|
|
212
|
-
};
|
|
213
152
|
export const LINK_CAPABILITIES = [
|
|
214
|
-
requestLinkCapability,
|
|
215
153
|
createLinkInviteCapability,
|
|
216
|
-
claimLinkInviteCapability,
|
|
217
154
|
listLinksCapability,
|
|
218
|
-
revokeLinkCapability,
|
|
219
155
|
];
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type CapabilityDefinition } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* ZIG-1023 — the marketplace read on both surfaces. Publishing and claiming
|
|
4
|
+
* ride the agreement grammar (propose-with-audience / agreement_claim); this
|
|
5
|
+
* is the browse that hands you the agreementIds those verbs need.
|
|
6
|
+
*/
|
|
7
|
+
export declare const marketplaceViewCapability: CapabilityDefinition;
|
|
8
|
+
export declare const MARKETPLACE_CAPABILITIES: CapabilityDefinition[];
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { pullOffers, pullQuests } from '../http/MarketplaceClient.js';
|
|
2
|
+
import { fullCreds } from './types.js';
|
|
3
|
+
const VIEW_KINDS = ['all', 'quests', 'offers'];
|
|
4
|
+
function publishHint(env) {
|
|
5
|
+
const propose = env.surface === 'mcp' ? 'ziggs_agreement_propose' : 'agreement_propose';
|
|
6
|
+
const claim = env.surface === 'mcp' ? 'ziggs_agreement_claim' : 'agreement_claim';
|
|
7
|
+
return (`Claim any row with ${claim} (agreementId). Publish your own with ${propose}: ` +
|
|
8
|
+
`proposedTo "everyone" or "org" with no providerId broadcasts a quest (claimer works, you pay); ` +
|
|
9
|
+
`the same with providerId = your own id publishes a standing offer (you work, claimer pays).`);
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* ZIG-1023 — the marketplace read on both surfaces. Publishing and claiming
|
|
13
|
+
* ride the agreement grammar (propose-with-audience / agreement_claim); this
|
|
14
|
+
* is the browse that hands you the agreementIds those verbs need.
|
|
15
|
+
*/
|
|
16
|
+
export const marketplaceViewCapability = {
|
|
17
|
+
key: 'marketplace_view',
|
|
18
|
+
names: { sdk: 'marketplace_view', mcp: 'ziggs_marketplace_view' },
|
|
19
|
+
descriptions: {
|
|
20
|
+
sdk: 'Browse the open marketplace: quests (work buyers broadcast — you would do the work) and standing offers (services sellers broadcast — you would buy). Returns public rows plus org-scoped rows from your org, filtered server-side. Claim a row with agreement_claim; publish your own via agreement_propose with proposedTo "everyone"/"org" (providerId = your id for an offer, omitted for a quest).',
|
|
21
|
+
mcp: 'Browse the open marketplace: quests (work buyers broadcast — you would do the work) and standing offers (services sellers broadcast — you would buy). Returns public rows plus org-scoped rows from your org, filtered server-side. Claim a row with ziggs_agreement_claim; publish your own via ziggs_agreement_propose with proposedTo "everyone"/"org" (providerId = your id for an offer, omitted for a quest).',
|
|
22
|
+
},
|
|
23
|
+
annotation: 'read-only',
|
|
24
|
+
params: {
|
|
25
|
+
kind: {
|
|
26
|
+
type: 'string',
|
|
27
|
+
enum: VIEW_KINDS,
|
|
28
|
+
description: 'all (default) | quests | offers',
|
|
29
|
+
},
|
|
30
|
+
limit: { type: 'number', description: 'Max rows per kind (default 20)' },
|
|
31
|
+
since: { type: 'string', description: 'ISO timestamp — only rows published after this' },
|
|
32
|
+
},
|
|
33
|
+
needsAgentId: true,
|
|
34
|
+
handler: async (args, env) => {
|
|
35
|
+
const kind = args['kind'] ?? 'all';
|
|
36
|
+
if (!VIEW_KINDS.includes(kind)) {
|
|
37
|
+
throw new Error(`kind must be one of ${VIEW_KINDS.join(', ')}`);
|
|
38
|
+
}
|
|
39
|
+
const creds = fullCreds(env);
|
|
40
|
+
const options = {
|
|
41
|
+
limit: typeof args['limit'] === 'number' ? args['limit'] : 20,
|
|
42
|
+
...(args['since'] ? { since: args['since'] } : {}),
|
|
43
|
+
};
|
|
44
|
+
const [quests, offers] = await Promise.all([
|
|
45
|
+
kind === 'offers' ? Promise.resolve([]) : pullQuests(options, creds),
|
|
46
|
+
kind === 'quests' ? Promise.resolve([]) : pullOffers(options, creds),
|
|
47
|
+
]);
|
|
48
|
+
return {
|
|
49
|
+
...(kind !== 'offers' ? { quests, questCount: quests.length } : {}),
|
|
50
|
+
...(kind !== 'quests' ? { offers, offerCount: offers.length } : {}),
|
|
51
|
+
nextSteps: publishHint(env),
|
|
52
|
+
};
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
export const MARKETPLACE_CAPABILITIES = [marketplaceViewCapability];
|
|
@@ -84,7 +84,7 @@ export const paymentTransferCapability = {
|
|
|
84
84
|
names: { sdk: 'payment_transfer', mcp: 'ziggs_payment_transfer' },
|
|
85
85
|
descriptions: {
|
|
86
86
|
sdk: 'Transfer funds to another wallet. Amounts are integer cents. Transfers above the wallet owner\'s policy pause with status "approval_required" — wait inline with payment_wait_for_approval when you expect a quick decision.',
|
|
87
|
-
mcp: 'Transfer funds to another wallet. Amounts are integer cents. As a delegate you spend under a payment grant the wallet owner issued (paymentGrantId — find yours via
|
|
87
|
+
mcp: 'Transfer funds to another wallet. Amounts are integer cents. As a delegate you spend under a payment grant the wallet owner issued (paymentGrantId — find yours via ziggs_grant_list scopeKind=wallet). Transfers above the owner\'s policy pause with status "approval_required": the human approves on the wallet page (it also shows in ziggs_pending_decisions) — you can wait inline with ziggs_payment_wait_for_approval, and you must NEVER approve your own transfer.',
|
|
88
88
|
},
|
|
89
89
|
annotation: 'write',
|
|
90
90
|
params: {
|
|
@@ -389,7 +389,7 @@ export const paymentRevokeGrantCapability = {
|
|
|
389
389
|
},
|
|
390
390
|
};
|
|
391
391
|
// ZIG-893 — payment_list_grants stays retired; the wallet rail is part of the
|
|
392
|
-
// unified
|
|
392
|
+
// unified grant_list capability (scopeKind: 'wallet'). Grant *mutations* stay
|
|
393
393
|
// on the payment rail above.
|
|
394
394
|
export const PAYMENT_CAPABILITIES = [
|
|
395
395
|
paymentBalanceCapability,
|
|
@@ -154,6 +154,8 @@ export interface CreateAgreementBody {
|
|
|
154
154
|
maxExecutions?: number;
|
|
155
155
|
description?: string;
|
|
156
156
|
engagementKind?: EngagementKind;
|
|
157
|
+
/** Link invites only: how many people may claim this one link. Default 1. */
|
|
158
|
+
maxClaims?: number;
|
|
157
159
|
metadata?: Record<string, unknown>;
|
|
158
160
|
}
|
|
159
161
|
export declare function createAgreement(body: CreateAgreementBody, creds: Creds): Promise<{
|
|
@@ -125,18 +125,12 @@ export async function respondToAgreement(agreementId, action, creds, opts = {})
|
|
|
125
125
|
throw new Error(`Agreement ${agreementId} not found`);
|
|
126
126
|
}
|
|
127
127
|
const proposedTo = agreement.parties?.proposedTo;
|
|
128
|
-
if (isBroadcastTarget(proposedTo)) {
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
// success while the item stayed claimable. Fail honestly instead — the
|
|
135
|
-
// caller either claims it (action=approve → POST /claim) or ignores it.
|
|
136
|
-
throw new Error(`Agreement ${agreementId} is an open/broadcast proposal (proposedTo="${String(proposedTo)}") and cannot be rejected by a single recipient — it has no personal approval slot. Ignore it to pass on it, or use action=approve to claim it.`);
|
|
137
|
-
}
|
|
138
|
-
const { agreement: updated } = await claimAgreement(agreementId, creds);
|
|
139
|
-
return updated;
|
|
128
|
+
if (isBroadcastTarget(proposedTo) || isBroadcastTarget(agreement.parties?.payer)) {
|
|
129
|
+
// ZIG-1021: respond is approve/reject on DIRECT proposals only. An open
|
|
130
|
+
// broadcast (quest, standing offer, link invite) has no per-recipient
|
|
131
|
+
// approval slot — one recipient can neither approve nor reject it
|
|
132
|
+
// (ZIG-719); the move is to claim it, which has its own verb now.
|
|
133
|
+
throw new Error(`Agreement ${agreementId} is an open broadcast and has no personal approval slot — respond cannot ${action} it. Claim it with the claim tool (agreement_claim / ziggs_agreement_claim), or ignore it to pass.`);
|
|
140
134
|
}
|
|
141
135
|
const partyId = resolveMyPendingApprovalPartyId(agreement, {
|
|
142
136
|
ownerUserId: opts.ownerUserId,
|
|
@@ -36,6 +36,22 @@ export interface WriteArtifactInput {
|
|
|
36
36
|
* writes. `visibility: 'agent-private'` is the new home for agent thoughts —
|
|
37
37
|
* they persist, are searchable, but are not visible to other chat parties.
|
|
38
38
|
*/
|
|
39
|
+
/**
|
|
40
|
+
* ZIG-1032 — turn a runtime lane id into a scope the backend can accept.
|
|
41
|
+
*
|
|
42
|
+
* A task with no origin chat runs on the lane `agrn-<agreementId>` (see
|
|
43
|
+
* AgentHost.laneSessionIdForTask). That is a routing key, not a chat: no such
|
|
44
|
+
* chat row exists, so passing it as `chatId` made every breadcrumb and recorded
|
|
45
|
+
* thought on an agreement lane come back 403 "not authorized for this scope" —
|
|
46
|
+
* a fictional scope reading like an auth failure. The work is agreement-scoped,
|
|
47
|
+
* so say so; same rule ZIG-924 settled for deliverables.
|
|
48
|
+
*/
|
|
49
|
+
export declare const AGREEMENT_LANE_PREFIX = "agrn-";
|
|
50
|
+
export declare function artifactScopeForSession(sessionId: string): {
|
|
51
|
+
chatId: string;
|
|
52
|
+
} | {
|
|
53
|
+
agreementId: string;
|
|
54
|
+
};
|
|
39
55
|
export declare class ArtifactsClient {
|
|
40
56
|
private readonly operatorKey;
|
|
41
57
|
private readonly agentId?;
|
|
@@ -54,7 +70,7 @@ export declare class ArtifactsClient {
|
|
|
54
70
|
* endpoint is updated to accept `visibility`, this falls back to logging
|
|
55
71
|
* locally (operators can still wire their own sink).
|
|
56
72
|
*/
|
|
57
|
-
recordThought(
|
|
73
|
+
recordThought(sessionId: string, text: string, opts?: {
|
|
58
74
|
idempotencyKey?: string;
|
|
59
75
|
}): Promise<void>;
|
|
60
76
|
write(input: WriteArtifactInput): Promise<void>;
|
|
@@ -62,8 +78,8 @@ export declare class ArtifactsClient {
|
|
|
62
78
|
* ZIG-899 — the deliberate-record variant: a deliverable the model chose to
|
|
63
79
|
* record must fail loudly and hand back the artifactId, unlike `write`'s
|
|
64
80
|
* soft-fail breadcrumb contract. This is the one wire call for both agent
|
|
65
|
-
* surfaces (the SDK
|
|
66
|
-
*
|
|
81
|
+
* surfaces (the SDK artifact_record tool and ziggs-mcp's
|
|
82
|
+
* ziggs_artifact_record).
|
|
67
83
|
*/
|
|
68
84
|
writeStrict(input: WriteArtifactInput): Promise<{
|
|
69
85
|
artifactId?: string;
|
|
@@ -7,6 +7,23 @@ import { buildOperatorHeaders } from './operatorHeaders.js';
|
|
|
7
7
|
* writes. `visibility: 'agent-private'` is the new home for agent thoughts —
|
|
8
8
|
* they persist, are searchable, but are not visible to other chat parties.
|
|
9
9
|
*/
|
|
10
|
+
/**
|
|
11
|
+
* ZIG-1032 — turn a runtime lane id into a scope the backend can accept.
|
|
12
|
+
*
|
|
13
|
+
* A task with no origin chat runs on the lane `agrn-<agreementId>` (see
|
|
14
|
+
* AgentHost.laneSessionIdForTask). That is a routing key, not a chat: no such
|
|
15
|
+
* chat row exists, so passing it as `chatId` made every breadcrumb and recorded
|
|
16
|
+
* thought on an agreement lane come back 403 "not authorized for this scope" —
|
|
17
|
+
* a fictional scope reading like an auth failure. The work is agreement-scoped,
|
|
18
|
+
* so say so; same rule ZIG-924 settled for deliverables.
|
|
19
|
+
*/
|
|
20
|
+
export const AGREEMENT_LANE_PREFIX = 'agrn-';
|
|
21
|
+
export function artifactScopeForSession(sessionId) {
|
|
22
|
+
if (sessionId.startsWith(AGREEMENT_LANE_PREFIX)) {
|
|
23
|
+
return { agreementId: sessionId.slice(AGREEMENT_LANE_PREFIX.length) };
|
|
24
|
+
}
|
|
25
|
+
return { chatId: sessionId };
|
|
26
|
+
}
|
|
10
27
|
export class ArtifactsClient {
|
|
11
28
|
operatorKey;
|
|
12
29
|
agentId;
|
|
@@ -49,9 +66,10 @@ export class ArtifactsClient {
|
|
|
49
66
|
* endpoint is updated to accept `visibility`, this falls back to logging
|
|
50
67
|
* locally (operators can still wire their own sink).
|
|
51
68
|
*/
|
|
52
|
-
async recordThought(
|
|
69
|
+
async recordThought(sessionId, text, opts = {}) {
|
|
70
|
+
// ZIG-1032: `sessionId` may be an agreement lane, which is not a chat.
|
|
53
71
|
return this.write({
|
|
54
|
-
|
|
72
|
+
...artifactScopeForSession(sessionId),
|
|
55
73
|
text,
|
|
56
74
|
content_type: 'thought',
|
|
57
75
|
visibility: 'agent-private',
|
|
@@ -75,8 +93,8 @@ export class ArtifactsClient {
|
|
|
75
93
|
* ZIG-899 — the deliberate-record variant: a deliverable the model chose to
|
|
76
94
|
* record must fail loudly and hand back the artifactId, unlike `write`'s
|
|
77
95
|
* soft-fail breadcrumb contract. This is the one wire call for both agent
|
|
78
|
-
* surfaces (the SDK
|
|
79
|
-
*
|
|
96
|
+
* surfaces (the SDK artifact_record tool and ziggs-mcp's
|
|
97
|
+
* ziggs_artifact_record).
|
|
80
98
|
*/
|
|
81
99
|
async writeStrict(input) {
|
|
82
100
|
if (!input.text || !input.text.trim()) {
|
package/dist/http/ChatClient.js
CHANGED
|
@@ -113,7 +113,7 @@ export async function sendChatMessage(input, creds) {
|
|
|
113
113
|
export async function listMyChats(creds) {
|
|
114
114
|
assertCreds(creds, 'list my chats');
|
|
115
115
|
// ZIG-699 — empty array only for a genuine empty 200; any failure (non-2xx /
|
|
116
|
-
// network) throws so
|
|
116
|
+
// network) throws so ziggs_chat_list reports a real error instead of "you
|
|
117
117
|
// have no chats". Status stays whitespace-delimited for toolError classification.
|
|
118
118
|
let res;
|
|
119
119
|
try {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import 'dotenv/config';
|
|
2
2
|
import { getBackendUrl } from '../utils/urlUtils.js';
|
|
3
|
+
import { pollSurfaceError } from '../shared/rateLimit.js';
|
|
3
4
|
/**
|
|
4
5
|
* Protocol-first uniform context reads (ZIG-427).
|
|
5
6
|
* Wraps `GET /context/read/:type` — one client, one envelope, four types.
|
|
@@ -53,6 +54,11 @@ export class ContextReadClient {
|
|
|
53
54
|
// A 403 here is a legitimate outcome, not a transport failure: addressing
|
|
54
55
|
// and authorisation are separate, so an agent can be told about mail it
|
|
55
56
|
// is not (or is no longer) allowed to open.
|
|
57
|
+
// ZIG-1019: a 429 carries the server's own wait; everything else keeps
|
|
58
|
+
// the plain status-tagged error callers already branch on.
|
|
59
|
+
if (res.status === 429) {
|
|
60
|
+
throw pollSurfaceError(`ContextReadClient.read ${type}`, res, body);
|
|
61
|
+
}
|
|
56
62
|
const err = new Error(`ContextReadClient.read ${type} ${res.status} ${body.slice(0, 200)}`);
|
|
57
63
|
err.status = res.status;
|
|
58
64
|
throw err;
|
|
@@ -88,6 +94,9 @@ export class ContextReadClient {
|
|
|
88
94
|
const res = await fetch(url.toString(), { headers });
|
|
89
95
|
const body = await res.text().catch(() => '');
|
|
90
96
|
if (!res.ok) {
|
|
97
|
+
if (res.status === 429) {
|
|
98
|
+
throw pollSurfaceError('ContextReadClient.snapshot', res, body);
|
|
99
|
+
}
|
|
91
100
|
const err = new Error(`ContextReadClient.snapshot ${res.status} ${body.slice(0, 200)}`);
|
|
92
101
|
err.status = res.status;
|
|
93
102
|
throw err;
|
package/dist/http/InboxClient.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import 'dotenv/config';
|
|
2
2
|
import { getBackendUrl } from '../utils/urlUtils.js';
|
|
3
|
+
import { pollSurfaceError } from '../shared/rateLimit.js';
|
|
3
4
|
/**
|
|
4
5
|
* The doorbell, not the door (ZIG-434): references addressed to this agent
|
|
5
6
|
* since its last ack — never content. Flow: inbox → read → act → ack
|
|
@@ -45,7 +46,7 @@ export class InboxClient {
|
|
|
45
46
|
});
|
|
46
47
|
const body = await res.text().catch(() => '');
|
|
47
48
|
if (!res.ok) {
|
|
48
|
-
throw
|
|
49
|
+
throw pollSurfaceError('InboxClient.getInbox', res, body);
|
|
49
50
|
}
|
|
50
51
|
return JSON.parse(body);
|
|
51
52
|
}
|
|
@@ -81,7 +82,7 @@ export class InboxClient {
|
|
|
81
82
|
}
|
|
82
83
|
const body = await res.text().catch(() => '');
|
|
83
84
|
if (!res.ok) {
|
|
84
|
-
throw
|
|
85
|
+
throw pollSurfaceError('InboxClient.getOperatorInbox', res, body);
|
|
85
86
|
}
|
|
86
87
|
return JSON.parse(body);
|
|
87
88
|
}
|
|
@@ -100,7 +101,7 @@ export class InboxClient {
|
|
|
100
101
|
});
|
|
101
102
|
const body = await res.text().catch(() => '');
|
|
102
103
|
if (!res.ok) {
|
|
103
|
-
throw
|
|
104
|
+
throw pollSurfaceError('InboxClient.ack', res, body);
|
|
104
105
|
}
|
|
105
106
|
return JSON.parse(body);
|
|
106
107
|
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { type ProposeTerms } from './AgreementClient.js';
|
|
2
|
+
import { type Agreement, type Creds, type EngagementKind } from '../types.js';
|
|
3
|
+
/**
|
|
4
|
+
* ZIG-1022 — one propose grammar. Direct, broadcast (quest and standing
|
|
5
|
+
* offer), and link proposals all flow through here; the surfaces expose a
|
|
6
|
+
* single propose tool instead of dedicated publish/request tools.
|
|
7
|
+
*
|
|
8
|
+
* Routing:
|
|
9
|
+
* - engagementKind 'link' → POST /agreements (link proposal / open invite)
|
|
10
|
+
* - proposedTo 'everyone' | 'org', providerId = self → seller-broadcast standing offer
|
|
11
|
+
* - proposedTo 'everyone' | 'org', no providerId → buyer-broadcast quest
|
|
12
|
+
* - anything else → direct proposal
|
|
13
|
+
*/
|
|
14
|
+
export interface UnifiedProposeInput extends ProposeTerms {
|
|
15
|
+
proposedTo: string;
|
|
16
|
+
/** Required on direct proposals; optional on broadcasts/links. */
|
|
17
|
+
chatId?: string;
|
|
18
|
+
engagementKind?: EngagementKind;
|
|
19
|
+
}
|
|
20
|
+
export type ProposeShape = 'direct' | 'quest' | 'offer' | 'link';
|
|
21
|
+
export declare function proposeUnified(input: UnifiedProposeInput, creds: Creds): Promise<{
|
|
22
|
+
agreement: Agreement;
|
|
23
|
+
shape: ProposeShape;
|
|
24
|
+
}>;
|
|
25
|
+
export type ClaimedKind = 'link' | 'offer' | 'quest';
|
|
26
|
+
/**
|
|
27
|
+
* ZIG-1021 — one claim verb for any open broadcast. Fetches the agreement to
|
|
28
|
+
* route: link invites and quests claim through POST /agreements/:id/claim;
|
|
29
|
+
* standing offers (open payer side) through POST /marketplace/offers/claim.
|
|
30
|
+
*/
|
|
31
|
+
export declare function claimOpenAgreement(agreementId: string, creds: Creds): Promise<{
|
|
32
|
+
agreement: Agreement;
|
|
33
|
+
kind: ClaimedKind;
|
|
34
|
+
}>;
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { createAgreement, claimAgreement, getAgreement, proposeBroadcast, proposeDirectTo, } from './AgreementClient.js';
|
|
2
|
+
import { claimOffer, publishOffer } from './MarketplaceClient.js';
|
|
3
|
+
import { isBroadcastTarget, } from '../types.js';
|
|
4
|
+
export async function proposeUnified(input, creds) {
|
|
5
|
+
const { proposedTo, chatId, engagementKind, providerId, ...terms } = input;
|
|
6
|
+
if (!proposedTo)
|
|
7
|
+
throw new Error('proposedTo is required (a user/agent id, or "everyone"/"org" to broadcast)');
|
|
8
|
+
if (engagementKind === 'link') {
|
|
9
|
+
// A link is an agreement, proposed to one agent (providerId) or opened as
|
|
10
|
+
// an invite (proposedTo 'everyone'); no chat, no money.
|
|
11
|
+
const target = isBroadcastTarget(proposedTo) ? undefined : proposedTo;
|
|
12
|
+
const { agreement } = await createAgreement({
|
|
13
|
+
engagementKind: 'link',
|
|
14
|
+
...(target ? { providerId: target } : {}),
|
|
15
|
+
...(terms.description ? { description: terms.description } : {}),
|
|
16
|
+
}, creds);
|
|
17
|
+
return { agreement, shape: 'link' };
|
|
18
|
+
}
|
|
19
|
+
if (isBroadcastTarget(proposedTo)) {
|
|
20
|
+
const audience = proposedTo;
|
|
21
|
+
if (providerId && creds.agentId && providerId === creds.agentId) {
|
|
22
|
+
// Seller-broadcast: you work, the claimer pays — a standing offer.
|
|
23
|
+
const agreement = await publishOffer({
|
|
24
|
+
description: terms.description ?? '',
|
|
25
|
+
price: terms.price,
|
|
26
|
+
lifecycle: terms.lifecycle,
|
|
27
|
+
expiresAt: terms.expiresAt,
|
|
28
|
+
maxExecutions: terms.maxExecutions,
|
|
29
|
+
engagementKind: engagementKind,
|
|
30
|
+
billing: terms.billing,
|
|
31
|
+
audience,
|
|
32
|
+
}, creds);
|
|
33
|
+
return { agreement, shape: 'offer' };
|
|
34
|
+
}
|
|
35
|
+
if (providerId) {
|
|
36
|
+
throw new Error('On a broadcast, providerId must be your own agent id (a standing offer: you work, the claimer pays) or omitted (a quest: the claimer works, you pay). A third-party providerId is not broadcastable.');
|
|
37
|
+
}
|
|
38
|
+
// Buyer-broadcast: the claimer works, your side pays — an open quest.
|
|
39
|
+
const agreement = await proposeBroadcast({
|
|
40
|
+
...terms,
|
|
41
|
+
chatId: chatId ?? '',
|
|
42
|
+
engagementKind: engagementKind ?? 'service',
|
|
43
|
+
audience,
|
|
44
|
+
}, creds);
|
|
45
|
+
return { agreement, shape: 'quest' };
|
|
46
|
+
}
|
|
47
|
+
if (!chatId)
|
|
48
|
+
throw new Error('chatId is required on a direct proposal');
|
|
49
|
+
const agreement = await proposeDirectTo({
|
|
50
|
+
...terms,
|
|
51
|
+
proposedTo,
|
|
52
|
+
chatId,
|
|
53
|
+
providerId: providerId?.trim() || proposedTo,
|
|
54
|
+
engagementKind: engagementKind ?? 'service',
|
|
55
|
+
}, creds);
|
|
56
|
+
return { agreement, shape: 'direct' };
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* ZIG-1021 — one claim verb for any open broadcast. Fetches the agreement to
|
|
60
|
+
* route: link invites and quests claim through POST /agreements/:id/claim;
|
|
61
|
+
* standing offers (open payer side) through POST /marketplace/offers/claim.
|
|
62
|
+
*/
|
|
63
|
+
export async function claimOpenAgreement(agreementId, creds) {
|
|
64
|
+
if (!agreementId)
|
|
65
|
+
throw new Error('agreementId is required');
|
|
66
|
+
const existing = await getAgreement(agreementId, creds);
|
|
67
|
+
if (!existing)
|
|
68
|
+
throw new Error(`Agreement not found: ${agreementId}`);
|
|
69
|
+
if (existing.engagementKind === 'link') {
|
|
70
|
+
const { agreement } = await claimAgreement(agreementId, creds);
|
|
71
|
+
return { agreement, kind: 'link' };
|
|
72
|
+
}
|
|
73
|
+
if (isBroadcastTarget(existing.parties?.payer)) {
|
|
74
|
+
// Seller-broadcast standing offer: the open side is the payer — you buy.
|
|
75
|
+
const agreement = await claimOffer(agreementId, creds);
|
|
76
|
+
return { agreement, kind: 'offer' };
|
|
77
|
+
}
|
|
78
|
+
const { agreement } = await claimAgreement(agreementId, creds);
|
|
79
|
+
return { agreement, kind: 'quest' };
|
|
80
|
+
}
|
package/dist/http/index.d.ts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
export * from './TaskClient.js';
|
|
2
2
|
export * from './AgreementClient.js';
|
|
3
3
|
export * from './MarketplaceClient.js';
|
|
4
|
+
export * from './agreementFlows.js';
|
|
4
5
|
export * from './ChatClient.js';
|
|
5
6
|
export { MessagesClient } from './MessagesClient.js';
|
|
6
7
|
export type { ListMessagesOptions, ListMessagesResult } from './MessagesClient.js';
|
|
7
|
-
export { ArtifactsClient } from './ArtifactsClient.js';
|
|
8
|
+
export { ArtifactsClient, artifactScopeForSession, AGREEMENT_LANE_PREFIX, } from './ArtifactsClient.js';
|
|
8
9
|
export type { ArtifactVisibility, ListArtifactsOptions, ListArtifactsQuery, ListArtifactsResult, WriteArtifactInput, } from './ArtifactsClient.js';
|
|
9
10
|
export { ContextReadClient } from './ContextReadClient.js';
|
|
10
11
|
export type { ContextReadType, ContextReadQuery, ContextReadEnvelope, ContextSnapshotResult, } from './ContextReadClient.js';
|
package/dist/http/index.js
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
export * from './TaskClient.js';
|
|
2
2
|
export * from './AgreementClient.js';
|
|
3
3
|
export * from './MarketplaceClient.js';
|
|
4
|
+
export * from './agreementFlows.js';
|
|
4
5
|
export * from './ChatClient.js';
|
|
5
6
|
export { MessagesClient } from './MessagesClient.js';
|
|
6
|
-
export { ArtifactsClient
|
|
7
|
+
export { ArtifactsClient,
|
|
8
|
+
// ZIG-1032: agreement lanes are not chats — callers scope artifact writes with this.
|
|
9
|
+
artifactScopeForSession, AGREEMENT_LANE_PREFIX, } from './ArtifactsClient.js';
|
|
7
10
|
export { ContextReadClient } from './ContextReadClient.js';
|
|
8
11
|
export { ContextDiscoveryClient } from './ContextDiscoveryClient.js';
|
|
9
12
|
export { GrantsClient } from './GrantsClient.js';
|
package/dist/index.d.ts
CHANGED
|
@@ -6,5 +6,6 @@ export type { StartAgentOptions } from './ConnectionManager.js';
|
|
|
6
6
|
export { EntryTypes, ContentTypes, OPEN_AGREEMENT_TARGET, ORG_AGREEMENT_TARGET, BROADCAST_TARGETS, isBroadcastTarget, AGREEMENT_ENGAGEMENT_KIND, isValidContentType } from './types.js';
|
|
7
7
|
export { getBackendUrl, getWebSocketUrl } from './utils/urlUtils.js';
|
|
8
8
|
export { runtimeLog, resetRuntimeLogLevelCache } from './shared/runtimeLog.js';
|
|
9
|
+
export { RateLimitedError, isRateLimited, parseRetryAfterMs, pollSurfaceError, } from './shared/rateLimit.js';
|
|
9
10
|
export type { Creds, Task, TaskState, PlanStep, PlanStepStatus, Agreement, EngagementKind, BroadcastAudience, EntryType, ContentType, MessageMetadata, MessageHandler, ApiError, } from './types.js';
|
|
10
11
|
export type { ProposeTerms, ProposeDirectInput, ProposeBroadcastInput, ProposeAgreementData, } from './http/AgreementClient.js';
|
package/dist/index.js
CHANGED
|
@@ -5,3 +5,5 @@ export { ConnectionManager } from './ConnectionManager.js';
|
|
|
5
5
|
export { EntryTypes, ContentTypes, OPEN_AGREEMENT_TARGET, ORG_AGREEMENT_TARGET, BROADCAST_TARGETS, isBroadcastTarget, AGREEMENT_ENGAGEMENT_KIND, isValidContentType } from './types.js';
|
|
6
6
|
export { getBackendUrl, getWebSocketUrl } from './utils/urlUtils.js';
|
|
7
7
|
export { runtimeLog, resetRuntimeLogLevelCache } from './shared/runtimeLog.js';
|
|
8
|
+
// ZIG-1019: retry loops need the server's own wait, not a guess.
|
|
9
|
+
export { RateLimitedError, isRateLimited, parseRetryAfterMs, pollSurfaceError, } from './shared/rateLimit.js';
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ZIG-1019 — a 429 is not a generic failure, it is an instruction with a
|
|
3
|
+
* deadline attached.
|
|
4
|
+
*
|
|
5
|
+
* The poll surface (`/inbox`, `/context/read`) is capped per actor, and every
|
|
6
|
+
* refused call still counts against that cap. A caller that retries on the
|
|
7
|
+
* usual exponential ladder (1s, 2s, 4s…) therefore spends its way deeper into
|
|
8
|
+
* the hole: observed live as an agent that could not read the conversation it
|
|
9
|
+
* had just been woken for, because its own retries kept the bucket empty.
|
|
10
|
+
*
|
|
11
|
+
* The server already says exactly how long to wait — `Retry-After`, or
|
|
12
|
+
* `RateLimit-Reset` from the standard headers. These helpers carry that number
|
|
13
|
+
* to whoever is doing the backing off.
|
|
14
|
+
*/
|
|
15
|
+
/** Thrown for HTTP 429 so a retry loop can wait the server's number, not its own. */
|
|
16
|
+
export declare class RateLimitedError extends Error {
|
|
17
|
+
readonly status = 429;
|
|
18
|
+
/** How long the server said to wait. Null when it said nothing. */
|
|
19
|
+
readonly retryAfterMs: number | null;
|
|
20
|
+
constructor(message: string, retryAfterMs: number | null);
|
|
21
|
+
}
|
|
22
|
+
/** True for the error above — survives structured clones and re-wraps. */
|
|
23
|
+
export declare function isRateLimited(err: unknown): err is {
|
|
24
|
+
retryAfterMs: number | null;
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* Read the wait out of a 429 response. `Retry-After` wins (it is the explicit
|
|
28
|
+
* one) and is accepted in both forms — delta-seconds or an HTTP date;
|
|
29
|
+
* `RateLimit-Reset` is the standard-headers fallback, in seconds. Capped so a
|
|
30
|
+
* bad header cannot park a loop for an hour, floored at a second so a `0` does
|
|
31
|
+
* not reproduce the hot-retry it is meant to stop.
|
|
32
|
+
*/
|
|
33
|
+
export declare function parseRetryAfterMs(headers: {
|
|
34
|
+
get(name: string): string | null;
|
|
35
|
+
}): number | null;
|
|
36
|
+
/**
|
|
37
|
+
* Build the error for a failed poll-surface response: 429s carry the server's
|
|
38
|
+
* wait, everything else stays an ordinary Error so existing handling is
|
|
39
|
+
* unchanged.
|
|
40
|
+
*/
|
|
41
|
+
export declare function pollSurfaceError(label: string, res: {
|
|
42
|
+
status: number;
|
|
43
|
+
headers: {
|
|
44
|
+
get(name: string): string | null;
|
|
45
|
+
};
|
|
46
|
+
}, body: string): Error;
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ZIG-1019 — a 429 is not a generic failure, it is an instruction with a
|
|
3
|
+
* deadline attached.
|
|
4
|
+
*
|
|
5
|
+
* The poll surface (`/inbox`, `/context/read`) is capped per actor, and every
|
|
6
|
+
* refused call still counts against that cap. A caller that retries on the
|
|
7
|
+
* usual exponential ladder (1s, 2s, 4s…) therefore spends its way deeper into
|
|
8
|
+
* the hole: observed live as an agent that could not read the conversation it
|
|
9
|
+
* had just been woken for, because its own retries kept the bucket empty.
|
|
10
|
+
*
|
|
11
|
+
* The server already says exactly how long to wait — `Retry-After`, or
|
|
12
|
+
* `RateLimit-Reset` from the standard headers. These helpers carry that number
|
|
13
|
+
* to whoever is doing the backing off.
|
|
14
|
+
*/
|
|
15
|
+
/** Thrown for HTTP 429 so a retry loop can wait the server's number, not its own. */
|
|
16
|
+
export class RateLimitedError extends Error {
|
|
17
|
+
status = 429;
|
|
18
|
+
/** How long the server said to wait. Null when it said nothing. */
|
|
19
|
+
retryAfterMs;
|
|
20
|
+
constructor(message, retryAfterMs) {
|
|
21
|
+
super(message);
|
|
22
|
+
this.name = 'RateLimitedError';
|
|
23
|
+
this.retryAfterMs = retryAfterMs;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
/** True for the error above — survives structured clones and re-wraps. */
|
|
27
|
+
export function isRateLimited(err) {
|
|
28
|
+
return (!!err &&
|
|
29
|
+
typeof err === 'object' &&
|
|
30
|
+
err.status === 429);
|
|
31
|
+
}
|
|
32
|
+
const MAX_RETRY_AFTER_MS = 120_000;
|
|
33
|
+
/**
|
|
34
|
+
* Read the wait out of a 429 response. `Retry-After` wins (it is the explicit
|
|
35
|
+
* one) and is accepted in both forms — delta-seconds or an HTTP date;
|
|
36
|
+
* `RateLimit-Reset` is the standard-headers fallback, in seconds. Capped so a
|
|
37
|
+
* bad header cannot park a loop for an hour, floored at a second so a `0` does
|
|
38
|
+
* not reproduce the hot-retry it is meant to stop.
|
|
39
|
+
*/
|
|
40
|
+
export function parseRetryAfterMs(headers) {
|
|
41
|
+
const explicit = headers.get('retry-after');
|
|
42
|
+
if (explicit) {
|
|
43
|
+
const seconds = Number(explicit);
|
|
44
|
+
if (Number.isFinite(seconds))
|
|
45
|
+
return clampRetryMs(seconds * 1000);
|
|
46
|
+
const at = Date.parse(explicit);
|
|
47
|
+
if (!Number.isNaN(at))
|
|
48
|
+
return clampRetryMs(at - Date.now());
|
|
49
|
+
}
|
|
50
|
+
const reset = headers.get('ratelimit-reset');
|
|
51
|
+
if (reset) {
|
|
52
|
+
const seconds = Number(reset);
|
|
53
|
+
if (Number.isFinite(seconds))
|
|
54
|
+
return clampRetryMs(seconds * 1000);
|
|
55
|
+
}
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
function clampRetryMs(ms) {
|
|
59
|
+
if (!Number.isFinite(ms))
|
|
60
|
+
return 1_000;
|
|
61
|
+
return Math.min(MAX_RETRY_AFTER_MS, Math.max(1_000, Math.round(ms)));
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Build the error for a failed poll-surface response: 429s carry the server's
|
|
65
|
+
* wait, everything else stays an ordinary Error so existing handling is
|
|
66
|
+
* unchanged.
|
|
67
|
+
*/
|
|
68
|
+
export function pollSurfaceError(label, res, body) {
|
|
69
|
+
const message = `${label} ${res.status} ${body.slice(0, 200)}`;
|
|
70
|
+
if (res.status !== 429)
|
|
71
|
+
return new Error(message);
|
|
72
|
+
return new RateLimitedError(message, parseRetryAfterMs(res.headers));
|
|
73
|
+
}
|
package/dist/types.d.ts
CHANGED
|
@@ -102,6 +102,17 @@ export interface Agreement {
|
|
|
102
102
|
lifecycle?: string;
|
|
103
103
|
expiresAt?: string;
|
|
104
104
|
maxExecutions?: number;
|
|
105
|
+
/**
|
|
106
|
+
* Seat bookkeeping when this agreement is an open link invite. Each claim
|
|
107
|
+
* takes a seat and mints its own child link, so one shared link produces N
|
|
108
|
+
* separate connections rather than a group.
|
|
109
|
+
*/
|
|
110
|
+
linkInvite?: {
|
|
111
|
+
maxClaims?: number;
|
|
112
|
+
claimsUsed?: number;
|
|
113
|
+
} | null;
|
|
114
|
+
/** Set on a link that was formed by claiming the invite with this id. */
|
|
115
|
+
linkInviteTemplateId?: string | null;
|
|
105
116
|
metadata?: Record<string, unknown>;
|
|
106
117
|
createdAt?: string;
|
|
107
118
|
updatedAt?: string;
|
|
@@ -165,7 +176,7 @@ export interface MessageMetadata {
|
|
|
165
176
|
* `task.notify.chat` / `agreement.notify.direct` traffic. The agent SDK's
|
|
166
177
|
* `normalizeIncomingEvent` reads `metadata.task` to upgrade structured
|
|
167
178
|
* task notifications into `task_result` events (so executors see
|
|
168
|
-
* `task-assigned` outcomes when the orchestrator calls `
|
|
179
|
+
* `task-assigned` outcomes when the orchestrator calls `task_create`),
|
|
169
180
|
* and maps explicit wire `operation` + `agreementId` lifecycle fields
|
|
170
181
|
* into `agreement_lifecycle` events for proposal approve/reject.
|
|
171
182
|
*/
|