@ziggs-ai/api-client 0.14.3 → 0.16.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.
- package/dist/capabilities/agreementVerbs.d.ts +9 -0
- package/dist/capabilities/agreementVerbs.js +29 -3
- package/dist/capabilities/agreements.js +2 -1
- package/dist/capabilities/artifacts.js +16 -6
- package/dist/capabilities/connections.d.ts +1 -1
- package/dist/capabilities/connections.js +82 -59
- package/dist/capabilities/grants.js +8 -2
- package/dist/capabilities/index.d.ts +1 -0
- package/dist/capabilities/index.js +1 -0
- package/dist/capabilities/links.js +12 -4
- package/dist/capabilities/listedFields.d.ts +17 -0
- package/dist/capabilities/listedFields.js +45 -0
- package/dist/capabilities/marketplace.js +7 -2
- package/dist/capabilities/payments.js +7 -2
- package/dist/capabilities/tasks.js +9 -3
- package/dist/http/AgreementClient.d.ts +29 -6
- package/dist/http/AgreementClient.js +2 -0
- package/dist/http/ArtifactsClient.d.ts +5 -16
- package/dist/http/ArtifactsClient.js +8 -5
- package/dist/http/ChatClient.d.ts +2 -6
- package/dist/http/ConnectionsClient.d.ts +15 -4
- package/dist/http/ConnectionsClient.js +53 -39
- package/dist/http/GrantsClient.d.ts +10 -1
- package/dist/http/GrantsClient.js +12 -2
- package/dist/http/IntroductionsClient.d.ts +11 -4
- package/dist/http/MarketplaceClient.d.ts +7 -0
- package/dist/http/MarketplaceClient.js +12 -3
- package/dist/http/OrgsClient.d.ts +0 -14
- package/dist/http/OrgsClient.js +2 -17
- package/dist/http/PaymentsClient.d.ts +47 -5
- package/dist/http/PaymentsClient.js +66 -22
- package/dist/http/TaskClient.js +1 -1
- package/dist/http/agreementFlows.js +9 -3
- package/dist/http/index.d.ts +1 -1
- package/dist/http/index.js +1 -1
- package/dist/http/paymentGrantSelection.d.ts +58 -0
- package/dist/http/paymentGrantSelection.js +121 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/shared/operatorKey.d.ts +47 -0
- package/dist/shared/operatorKey.js +64 -0
- package/dist/types.d.ts +6 -26
- package/dist/types.js +1 -19
- package/dist/utils/appUrls.d.ts +7 -0
- package/dist/utils/appUrls.js +38 -0
- package/package.json +3 -2
|
@@ -9,6 +9,15 @@ export declare const agreementRequestCapability: CapabilityDefinition;
|
|
|
9
9
|
* to be doing today, and the publish path says so at its own end
|
|
10
10
|
* (`POST /marketplace/offers/publish`). Hanging one under a job would kill the
|
|
11
11
|
* listing when the job ended.
|
|
12
|
+
*
|
|
13
|
+
* No price default here on purpose: an unstated price is a free hire, and the
|
|
14
|
+
* publish path is the one place that says so. Stating a price publishes a paid
|
|
15
|
+
* listing.
|
|
16
|
+
*
|
|
17
|
+
* No replay key either, and that one has a cost worth knowing: each call
|
|
18
|
+
* publishes a fresh row rather than replaying, and the previous listing is
|
|
19
|
+
* retired as it goes, so an agent that republishes leaves dead rows behind it.
|
|
20
|
+
* One listing per agent still holds; the churn does not.
|
|
12
21
|
*/
|
|
13
22
|
export declare const agreementOfferCapability: CapabilityDefinition;
|
|
14
23
|
export declare const agreementHandoffCapability: CapabilityDefinition;
|
|
@@ -92,7 +92,7 @@ function engagementKindFrom(args) {
|
|
|
92
92
|
*/
|
|
93
93
|
const MANDATE_PARAM = {
|
|
94
94
|
type: 'string',
|
|
95
|
-
description:
|
|
95
|
+
description: "The active agreement whose work this is part of — the job you are doing. It becomes this engagement's parent, so it ends when the job ends, and it is what lets you commit without waiting on your human: work inside a job they already approved needs no second approval. Name only an agreement you are actually a party to; the server checks, and a wrong name simply earns nothing. Leave it out for work that belongs to no job. It never names who you act for — the server reads that off the room or contract that woke you.",
|
|
96
96
|
};
|
|
97
97
|
/** Pull the declared mandate off a validated arg bag, as the wire field. */
|
|
98
98
|
function mandateFrom(args) {
|
|
@@ -122,10 +122,27 @@ const COUNTERPARTY_PARAM = {
|
|
|
122
122
|
required: true,
|
|
123
123
|
description: 'Agent or user id of the counterparty.',
|
|
124
124
|
};
|
|
125
|
+
/**
|
|
126
|
+
* Optional, because the server has always accepted a proposal without one and
|
|
127
|
+
* the client was the only thing refusing.
|
|
128
|
+
*
|
|
129
|
+
* The room is where a conversation about the terms happens; it is not how the
|
|
130
|
+
* counterparty is TOLD. A directed proposal is delivered to the party slots
|
|
131
|
+
* (the create-time agreement event targets parties, not a chat), so a chatless
|
|
132
|
+
* proposal lands in their inbox exactly like any other. What is lost is
|
|
133
|
+
* narrower than it sounds: the notice posted INTO a room, which is worth
|
|
134
|
+
* nothing when there is no room.
|
|
135
|
+
*
|
|
136
|
+
* Requiring it here meant a bookkeeping agreement between parties who have no
|
|
137
|
+
* conversation had to invent a chat id to exist. The lab's own self-hire did:
|
|
138
|
+
* it passed `lab-hire-<run>`, a chat that does not exist, which the server
|
|
139
|
+
* tolerates by falling back to the credential's tenant. A fiction the caller
|
|
140
|
+
* was forced to write down is worse than an absent field.
|
|
141
|
+
*/
|
|
125
142
|
const CHAT_PARAM = {
|
|
126
143
|
type: 'string',
|
|
127
|
-
required:
|
|
128
|
-
description: 'The room this is proposed in — the counterparty
|
|
144
|
+
required: false,
|
|
145
|
+
description: 'The room this is proposed in, when there is one — the counterparty can read and discuss the terms there. Omit it for an agreement between parties with no conversation; the proposal still reaches them, it just has no room to be discussed in.',
|
|
129
146
|
};
|
|
130
147
|
/** Marketplace follow-up: a published listing is claimed, never countered. */
|
|
131
148
|
function publishedNext(env) {
|
|
@@ -269,6 +286,15 @@ export const agreementRequestCapability = {
|
|
|
269
286
|
* to be doing today, and the publish path says so at its own end
|
|
270
287
|
* (`POST /marketplace/offers/publish`). Hanging one under a job would kill the
|
|
271
288
|
* listing when the job ended.
|
|
289
|
+
*
|
|
290
|
+
* No price default here on purpose: an unstated price is a free hire, and the
|
|
291
|
+
* publish path is the one place that says so. Stating a price publishes a paid
|
|
292
|
+
* listing.
|
|
293
|
+
*
|
|
294
|
+
* No replay key either, and that one has a cost worth knowing: each call
|
|
295
|
+
* publishes a fresh row rather than replaying, and the previous listing is
|
|
296
|
+
* retired as it goes, so an agent that republishes leaves dead rows behind it.
|
|
297
|
+
* One listing per agent still holds; the churn does not.
|
|
272
298
|
*/
|
|
273
299
|
export const agreementOfferCapability = {
|
|
274
300
|
key: 'agreement_offer',
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { agreementAppUrl } from '../utils/appUrls.js';
|
|
1
2
|
import { claimOpenAgreement } from '../http/agreementFlows.js';
|
|
2
3
|
import { linkIsReachOnly, webAppOrigin } from './links.js';
|
|
3
4
|
import { fullCreds } from './types.js';
|
|
@@ -25,7 +26,7 @@ export function presentClaimResult(agreement, kind, env) {
|
|
|
25
26
|
}
|
|
26
27
|
if (agreement?.status !== 'active') {
|
|
27
28
|
const held = (agreement?.approvals ?? []).find((a) => a.status === 'pending');
|
|
28
|
-
const approveUrl =
|
|
29
|
+
const approveUrl = agreementAppUrl(webAppOrigin(env), agreement.agreementId);
|
|
29
30
|
const remedy = held?.heldReason === 'contact-basis'
|
|
30
31
|
? 'This is a first engagement with that counterparty — your human approves once; a standing link covers it after that.'
|
|
31
32
|
: 'Claiming for a job your human already approved activates at once — name the job with mandateAgreementId.';
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { ArtifactsClient } from '../http/ArtifactsClient.js';
|
|
2
2
|
import { ContextGrantsClient } from '../http/ContextGrantsClient.js';
|
|
3
3
|
import { fullCreds } from './types.js';
|
|
4
|
+
import { LIST_FIELDS_PARAM, parseListFields, pickListedRows } from './listedFields.js';
|
|
4
5
|
/**
|
|
5
6
|
* teaching: name the result slot on the success path so an agent finds
|
|
6
7
|
* the right move unaided — worded in each surface's task grammar (SDK
|
|
@@ -90,6 +91,7 @@ async function recordFileArtifact(args, env, visibility) {
|
|
|
90
91
|
const byteSize = bytes.byteLength;
|
|
91
92
|
const uploaded = await client.uploadUrl({
|
|
92
93
|
filename,
|
|
94
|
+
name: args['name']?.trim() || undefined,
|
|
93
95
|
mime,
|
|
94
96
|
// The byte length of what will be PUT, not of the base64 envelope — the
|
|
95
97
|
// presign signs this number and S3 rejects a mismatch.
|
|
@@ -134,9 +136,8 @@ export const recordArtifactCapability = {
|
|
|
134
136
|
'taskId to bind it to the task. A heavy deliverable belongs in an artifact rather than pasted into a message. ' +
|
|
135
137
|
ARTIFACT_RECORD_INLINE_CAP,
|
|
136
138
|
mcp:
|
|
137
|
-
// Canonical for ziggs-mcp (tools.ts must not override).
|
|
138
|
-
//
|
|
139
|
-
// record-artifact-teaching.test.ts gates the live tool against both.
|
|
139
|
+
// Canonical for ziggs-mcp (tools.ts must not override). Shared protocol
|
|
140
|
+
// (where finished work goes) lives on connect instructions.
|
|
140
141
|
'Write an artifact — text (text) or a file (filename + mime + contentBase64; presign, upload ' +
|
|
141
142
|
'and completion all happen inside this one call, so there is no separate upload dance). ' +
|
|
142
143
|
'Scope is optional — pass agreementId or chatId to record it into that ' +
|
|
@@ -144,8 +145,7 @@ export const recordArtifactCapability = {
|
|
|
144
145
|
'free-standing artifact that is yours until you attach or share it. Never guess a scope: ' +
|
|
145
146
|
'recording with none always succeeds. Set visibility explicitly. ' +
|
|
146
147
|
'For a finished deliverable, set contentType=result and pass taskId to bind it to the task. ' +
|
|
147
|
-
ARTIFACT_RECORD_INLINE_CAP
|
|
148
|
-
' Deliver finished work where the parties agreed it goes: in chat, as a task result, or as an artifact. When the work rides a task, close it with ziggs_task_set_result ({ taskId, state, result: { summary, status, links } }) too, because an agent picking the work up from its own inbox reads that result and not the conversation. Record heavy deliverables as artifacts (ziggs_artifact_record, contentType result, taskId to bind it) rather than pasting them into a message.',
|
|
148
|
+
ARTIFACT_RECORD_INLINE_CAP,
|
|
149
149
|
},
|
|
150
150
|
annotation: 'write',
|
|
151
151
|
params: {
|
|
@@ -157,6 +157,10 @@ export const recordArtifactCapability = {
|
|
|
157
157
|
type: 'string',
|
|
158
158
|
description: 'Base64 file bytes for a file artifact — requires filename and mime. This call presigns, uploads and completes; you never compute a checksum. Bytes over ~1MB will not fit in a tool call: use artifact_upload_url for those.',
|
|
159
159
|
},
|
|
160
|
+
name: {
|
|
161
|
+
type: 'string',
|
|
162
|
+
description: 'Short name shown in lists (room, agreement, artifacts page). Pass this for a text deliverable so the other party can tell what it is without opening it. A file defaults to filename when omitted.',
|
|
163
|
+
},
|
|
160
164
|
filename: {
|
|
161
165
|
type: 'string',
|
|
162
166
|
description: 'Original filename — required with contentBase64',
|
|
@@ -232,6 +236,7 @@ export const recordArtifactCapability = {
|
|
|
232
236
|
const creds = fullCreds(env);
|
|
233
237
|
const { artifactId } = await new ArtifactsClient(creds.operatorKey, creds.agentId, creds.laneId).writeStrict({
|
|
234
238
|
text,
|
|
239
|
+
name: args['name']?.trim() || undefined,
|
|
235
240
|
visibility,
|
|
236
241
|
chatId,
|
|
237
242
|
agreementId,
|
|
@@ -275,14 +280,19 @@ export const listArtifactsCapability = {
|
|
|
275
280
|
description: 'ISO timestamp — return only artifacts written strictly after this',
|
|
276
281
|
},
|
|
277
282
|
limit: { type: 'number', description: 'Page size (server default when omitted)' },
|
|
283
|
+
fields: LIST_FIELDS_PARAM,
|
|
278
284
|
},
|
|
279
285
|
needsAgentId: true,
|
|
280
286
|
handler: async (args, env) => {
|
|
281
287
|
const creds = fullCreds(env);
|
|
282
|
-
|
|
288
|
+
const listed = await new ArtifactsClient(creds.operatorKey, creds.agentId, creds.laneId).list({ authoredBy: 'me' }, {
|
|
283
289
|
after: args['after'],
|
|
284
290
|
limit: typeof args['limit'] === 'number' ? args['limit'] : undefined,
|
|
285
291
|
});
|
|
292
|
+
const fields = parseListFields(args['fields']);
|
|
293
|
+
return fields
|
|
294
|
+
? { ...listed, artifacts: pickListedRows(listed.artifacts, fields) }
|
|
295
|
+
: listed;
|
|
286
296
|
},
|
|
287
297
|
};
|
|
288
298
|
/**
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type CapabilityDefinition } from
|
|
1
|
+
import { type CapabilityDefinition } from "./types.js";
|
|
2
2
|
export declare const connectionProxyCapability: CapabilityDefinition;
|
|
3
3
|
export declare const requestConnectionCapability: CapabilityDefinition;
|
|
4
4
|
export declare const CONNECTION_CAPABILITIES: CapabilityDefinition[];
|
|
@@ -1,115 +1,138 @@
|
|
|
1
|
-
import { ConnectionsClient } from
|
|
2
|
-
import { rethrowWithContext, } from
|
|
1
|
+
import { ConnectionsClient } from "../http/ConnectionsClient.js";
|
|
2
|
+
import { rethrowWithContext, } from "./types.js";
|
|
3
3
|
function client(env) {
|
|
4
|
-
|
|
4
|
+
// laneId, not just the key and the agent. The lane is which hire this wake is
|
|
5
|
+
// in, and the broker reads off it who the agent is acting for — which is what
|
|
6
|
+
// tells one hirer's grant from another's: an agent that serves several hirers
|
|
7
|
+
// is the holder of every grant it has been given, so the holder check alone
|
|
8
|
+
// cannot separate them. Every other Creds-based client already carries it.
|
|
9
|
+
const { operatorKey, agentId, laneId } = env.creds;
|
|
5
10
|
if (!operatorKey)
|
|
6
|
-
throw new Error(
|
|
7
|
-
return new ConnectionsClient(operatorKey, agentId, env.baseUrl);
|
|
11
|
+
throw new Error("operatorKey missing from tool context");
|
|
12
|
+
return new ConnectionsClient(operatorKey, agentId, env.baseUrl, laneId);
|
|
8
13
|
}
|
|
9
14
|
export const connectionProxyCapability = {
|
|
10
|
-
key:
|
|
11
|
-
names: { sdk:
|
|
12
|
-
title:
|
|
15
|
+
key: "connection_proxy",
|
|
16
|
+
names: { sdk: "connection_proxy", mcp: "ziggs_connection_proxy" },
|
|
17
|
+
title: "Use a stored connection",
|
|
13
18
|
descriptions: {
|
|
14
19
|
sdk: "Use a named-connector stored connection (e.g. the owner's GitHub/Jira) without ever seeing the credential. " +
|
|
15
|
-
|
|
20
|
+
"Calls the backend connections proxy with a grant the owner issued to this agent. " +
|
|
16
21
|
'Not for remote MCP servers (provider "mcp") — those use mcp_tool_call / mcp_tools_list; proxy refuses them with "Unknown provider: mcp". ' +
|
|
17
22
|
"Don't know connectionId/grantId yet? Use grant_list (scopeKind=connection) or connection_list_grants first.",
|
|
18
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. " +
|
|
19
|
-
|
|
24
|
+
"Calls the backend connections proxy with a grant the owner issued to this agent. " +
|
|
20
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". ' +
|
|
21
26
|
"Don't know connectionId/grantId yet? Call ziggs_connection_list first.",
|
|
22
27
|
},
|
|
23
|
-
annotation:
|
|
28
|
+
annotation: "write",
|
|
24
29
|
params: {
|
|
25
|
-
connectionId: {
|
|
30
|
+
connectionId: {
|
|
31
|
+
type: "string",
|
|
32
|
+
required: true,
|
|
33
|
+
description: "Connection to act on",
|
|
34
|
+
},
|
|
26
35
|
grantId: {
|
|
27
|
-
type:
|
|
36
|
+
type: "string",
|
|
37
|
+
required: true,
|
|
38
|
+
description: "Grant the owner issued to this agent for the connection",
|
|
39
|
+
},
|
|
40
|
+
action: {
|
|
41
|
+
type: "string",
|
|
28
42
|
required: true,
|
|
29
|
-
description:
|
|
43
|
+
description: "Provider action, e.g. repo:read",
|
|
44
|
+
},
|
|
45
|
+
payload: {
|
|
46
|
+
type: "object",
|
|
47
|
+
description: "Action-specific arguments (provider-defined)",
|
|
30
48
|
},
|
|
31
|
-
action: { type: 'string', required: true, description: 'Provider action, e.g. repo:read' },
|
|
32
|
-
payload: { type: 'object', description: 'Action-specific arguments (provider-defined)' },
|
|
33
49
|
},
|
|
34
50
|
needsAgentId: true,
|
|
35
51
|
handler: async (args, env) => {
|
|
36
|
-
if (!args[
|
|
37
|
-
throw new Error(
|
|
38
|
-
if (!args[
|
|
39
|
-
throw new Error(
|
|
40
|
-
if (!args[
|
|
41
|
-
throw new Error(
|
|
52
|
+
if (!args["connectionId"])
|
|
53
|
+
throw new Error("connectionId is required");
|
|
54
|
+
if (!args["grantId"])
|
|
55
|
+
throw new Error("grantId is required");
|
|
56
|
+
if (!args["action"])
|
|
57
|
+
throw new Error("action is required");
|
|
42
58
|
try {
|
|
43
59
|
const result = await client(env).proxy({
|
|
44
|
-
connectionId: args[
|
|
45
|
-
grantId: args[
|
|
46
|
-
action: args[
|
|
47
|
-
payload: args[
|
|
60
|
+
connectionId: args["connectionId"],
|
|
61
|
+
grantId: args["grantId"],
|
|
62
|
+
action: args["action"],
|
|
63
|
+
payload: args["payload"],
|
|
48
64
|
});
|
|
49
|
-
return { ok: true, action: args[
|
|
65
|
+
return { ok: true, action: args["action"], result };
|
|
50
66
|
}
|
|
51
67
|
catch (e) {
|
|
52
|
-
rethrowWithContext(e,
|
|
68
|
+
rethrowWithContext(e, "Connection proxy failed");
|
|
53
69
|
}
|
|
54
70
|
},
|
|
55
71
|
};
|
|
56
72
|
export const requestConnectionCapability = {
|
|
57
|
-
key:
|
|
58
|
-
names: { sdk:
|
|
59
|
-
title:
|
|
73
|
+
key: "connection_request",
|
|
74
|
+
names: { sdk: "connection_request", mcp: "ziggs_connection_request" },
|
|
75
|
+
title: "Ask your principal for a connection",
|
|
60
76
|
descriptions: {
|
|
61
|
-
sdk:
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
mcp:
|
|
65
|
-
|
|
66
|
-
|
|
77
|
+
sdk: "Ask your principal (the human) to connect a remote MCP server and grant you the listed tools. " +
|
|
78
|
+
"Opens a connection-consent agreement as an approvable card in the chat you pass — the human approves it there like any other agreement. " +
|
|
79
|
+
"On approval the server is connected (browser OAuth if needed) and you are granted the tools; call them with mcp_tool_call / mcp_tools_list (not connection_proxy).",
|
|
80
|
+
mcp: "Ask your principal (the human) to connect a remote MCP server and grant you the listed tools. " +
|
|
81
|
+
"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). " +
|
|
82
|
+
"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_mcp_tools_list / ziggs_mcp_tool_call.",
|
|
67
83
|
},
|
|
68
|
-
annotation:
|
|
84
|
+
annotation: "write",
|
|
69
85
|
params: {
|
|
70
86
|
chatId: {
|
|
71
|
-
type:
|
|
87
|
+
type: "string",
|
|
88
|
+
required: true,
|
|
89
|
+
description: "The chat you are working in — the consent card is opened there",
|
|
90
|
+
},
|
|
91
|
+
serverUrl: {
|
|
92
|
+
type: "string",
|
|
72
93
|
required: true,
|
|
73
|
-
description:
|
|
94
|
+
description: "Remote MCP server URL (https)",
|
|
74
95
|
},
|
|
75
|
-
serverUrl: { type: 'string', required: true, description: 'Remote MCP server URL (https)' },
|
|
76
96
|
tools: {
|
|
77
|
-
type:
|
|
78
|
-
items: { type:
|
|
97
|
+
type: "array",
|
|
98
|
+
items: { type: "string" },
|
|
79
99
|
required: true,
|
|
80
100
|
description: "Tool names you want — become the grant's allowed_actions caveats",
|
|
81
101
|
},
|
|
82
|
-
reason: {
|
|
102
|
+
reason: {
|
|
103
|
+
type: "string",
|
|
104
|
+
description: "Plain-language reason shown to the human deciding",
|
|
105
|
+
},
|
|
83
106
|
},
|
|
84
107
|
needsAgentId: true,
|
|
85
108
|
handler: async (args, env) => {
|
|
86
|
-
if (!args[
|
|
87
|
-
throw new Error(
|
|
88
|
-
if (!args[
|
|
89
|
-
throw new Error(
|
|
90
|
-
const tools = args[
|
|
109
|
+
if (!args["chatId"])
|
|
110
|
+
throw new Error("chatId is required");
|
|
111
|
+
if (!args["serverUrl"])
|
|
112
|
+
throw new Error("serverUrl is required");
|
|
113
|
+
const tools = args["tools"];
|
|
91
114
|
if (!Array.isArray(tools) || tools.length === 0) {
|
|
92
|
-
throw new Error(
|
|
115
|
+
throw new Error("tools must be a non-empty array of tool names");
|
|
93
116
|
}
|
|
94
117
|
try {
|
|
95
118
|
const result = await client(env).requestMcpConnection({
|
|
96
|
-
chatId: args[
|
|
97
|
-
serverUrl: args[
|
|
119
|
+
chatId: args["chatId"],
|
|
120
|
+
serverUrl: args["serverUrl"],
|
|
98
121
|
tools: tools,
|
|
99
|
-
reason: args[
|
|
122
|
+
reason: args["reason"],
|
|
100
123
|
});
|
|
101
124
|
return {
|
|
102
125
|
ok: true,
|
|
103
126
|
...result,
|
|
104
|
-
note: env.surface ===
|
|
105
|
-
?
|
|
106
|
-
|
|
107
|
-
:
|
|
108
|
-
|
|
127
|
+
note: env.surface === "mcp"
|
|
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
|
+
"Once approved, the connection + grant appear in ziggs_connection_list for ziggs_mcp_tools_list / ziggs_mcp_tool_call."
|
|
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.",
|
|
109
132
|
};
|
|
110
133
|
}
|
|
111
134
|
catch (e) {
|
|
112
|
-
rethrowWithContext(e,
|
|
135
|
+
rethrowWithContext(e, "Connection request failed");
|
|
113
136
|
}
|
|
114
137
|
},
|
|
115
138
|
};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { GrantsClient } from '../http/GrantsClient.js';
|
|
2
2
|
import { GRANT_SCOPE_KINDS, } from '../http/grants.js';
|
|
3
3
|
import { fullCreds } from './types.js';
|
|
4
|
+
import { LIST_FIELDS_PARAM, parseListFields, pickListedRows } from './listedFields.js';
|
|
4
5
|
// BOTH the tool's param enum and its validator, so a missing kind makes the
|
|
5
6
|
// filter the description advertises fail validation. Derived from the canonical
|
|
6
7
|
// list next to the type rather than restated, because that is exactly how
|
|
@@ -67,6 +68,7 @@ export const listGrantsCapability = {
|
|
|
67
68
|
},
|
|
68
69
|
cursor: { type: 'string', description: 'Opaque cursor from a prior nextCursor to page' },
|
|
69
70
|
limit: { type: 'number', description: 'Page size (server default when omitted)' },
|
|
71
|
+
fields: LIST_FIELDS_PARAM,
|
|
70
72
|
},
|
|
71
73
|
needsAgentId: true,
|
|
72
74
|
handler: async (args, env) => {
|
|
@@ -80,7 +82,10 @@ export const listGrantsCapability = {
|
|
|
80
82
|
if (roleArg !== undefined && roleArg !== 'holder' && roleArg !== 'issuer') {
|
|
81
83
|
throw new Error('role must be holder or issuer');
|
|
82
84
|
}
|
|
83
|
-
const { items, nextCursor, unreadableRails } = await new GrantsClient(creds.operatorKey, creds.agentId, env.baseUrl
|
|
85
|
+
const { items, nextCursor, unreadableRails } = await new GrantsClient(creds.operatorKey, creds.agentId, env.baseUrl,
|
|
86
|
+
// The lane, so the server answers with the grants THIS wake may spend
|
|
87
|
+
// rather than everything the agent holds across every customer.
|
|
88
|
+
creds.laneId).listGrants({
|
|
84
89
|
scopeKind,
|
|
85
90
|
scopeId: typeof args['scopeId'] === 'string' ? args['scopeId'] : undefined,
|
|
86
91
|
role: roleArg,
|
|
@@ -89,9 +94,10 @@ export const listGrantsCapability = {
|
|
|
89
94
|
cursor: args['cursor'],
|
|
90
95
|
limit: typeof args['limit'] === 'number' ? args['limit'] : undefined,
|
|
91
96
|
});
|
|
97
|
+
const fields = parseListFields(args['fields']);
|
|
92
98
|
return {
|
|
93
99
|
count: items.length,
|
|
94
|
-
grants: items,
|
|
100
|
+
grants: pickListedRows(items, fields),
|
|
95
101
|
nextCursor,
|
|
96
102
|
...(unreadableRails?.length ? { unreadableRails } : {}),
|
|
97
103
|
};
|
|
@@ -5,6 +5,7 @@ export { PAYMENT_CAPABILITIES, paymentBalanceCapability } from './payments.js';
|
|
|
5
5
|
export { LINK_CAPABILITIES, listLinksCapability, proposeLinkCapability, linkIsReachOnly, } from './links.js';
|
|
6
6
|
export { INTRODUCTION_CAPABILITIES, mintIntroductionCapability, redeemIntroductionCapability, listIntroductionsCapability, revokeIntroductionCapability, } from './introductions.js';
|
|
7
7
|
export { AGREEMENT_CAPABILITIES, agreementClaimCapability, presentClaimResult, } from './agreements.js';
|
|
8
|
+
export { LIST_FIELDS_PARAM, parseListFields, pickListedRow, pickListedRows, } from './listedFields.js';
|
|
8
9
|
export { TASK_CAPABILITIES, listTasksCapability } from './tasks.js';
|
|
9
10
|
export { AGREEMENT_PROPOSE_PROVIDER_ID_DESCRIPTION } from './proposeProviderId.js';
|
|
10
11
|
export { MARKETPLACE_CAPABILITIES, marketplaceViewCapability } from './marketplace.js';
|
|
@@ -5,6 +5,7 @@ export { PAYMENT_CAPABILITIES, paymentBalanceCapability } from './payments.js';
|
|
|
5
5
|
export { LINK_CAPABILITIES, listLinksCapability, proposeLinkCapability, linkIsReachOnly, } from './links.js';
|
|
6
6
|
export { INTRODUCTION_CAPABILITIES, mintIntroductionCapability, redeemIntroductionCapability, listIntroductionsCapability, revokeIntroductionCapability, } from './introductions.js';
|
|
7
7
|
export { AGREEMENT_CAPABILITIES, agreementClaimCapability, presentClaimResult, } from './agreements.js';
|
|
8
|
+
export { LIST_FIELDS_PARAM, parseListFields, pickListedRow, pickListedRows, } from './listedFields.js';
|
|
8
9
|
export { TASK_CAPABILITIES, listTasksCapability } from './tasks.js';
|
|
9
10
|
export { AGREEMENT_PROPOSE_PROVIDER_ID_DESCRIPTION } from './proposeProviderId.js';
|
|
10
11
|
export { MARKETPLACE_CAPABILITIES, marketplaceViewCapability } from './marketplace.js';
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
+
import { resolveWebAppOrigin, connectInviteAppUrl } from '../utils/appUrls.js';
|
|
1
2
|
import { createLink, listAgreements } from '../http/AgreementClient.js';
|
|
2
3
|
import { nextCall } from './nextCall.js';
|
|
3
4
|
import { fullCreds } from './types.js';
|
|
4
|
-
const DEFAULT_WEB_URL = 'https://ziggsai.com';
|
|
5
5
|
export function webAppOrigin(env) {
|
|
6
|
-
return (env.webUrl
|
|
6
|
+
return resolveWebAppOrigin(env.webUrl);
|
|
7
7
|
}
|
|
8
8
|
/**
|
|
9
9
|
* The one thing an invite travels as: `/connect/<inviteId>`.
|
|
@@ -17,7 +17,7 @@ export function webAppOrigin(env) {
|
|
|
17
17
|
* across two repos.
|
|
18
18
|
*/
|
|
19
19
|
function inviteShareUrl(env, agreementId) {
|
|
20
|
-
return
|
|
20
|
+
return connectInviteAppUrl(webAppOrigin(env), agreementId);
|
|
21
21
|
}
|
|
22
22
|
/**
|
|
23
23
|
* A link is reach-only — the follow-up move differs by surface tool names.
|
|
@@ -159,7 +159,7 @@ export const proposeLinkCapability = {
|
|
|
159
159
|
handler: async (args, env) => {
|
|
160
160
|
const to = args['to']?.trim();
|
|
161
161
|
const maxClaims = args['maxClaims'];
|
|
162
|
-
const { shareUrl } = await createLink({
|
|
162
|
+
const { shareUrl, agreementId } = await createLink({
|
|
163
163
|
...(to ? { to } : {}),
|
|
164
164
|
...(args['message'] ? { description: args['message'] } : {}),
|
|
165
165
|
...(maxClaims == null || to ? {} : { maxClaims }),
|
|
@@ -168,9 +168,17 @@ export const proposeLinkCapability = {
|
|
|
168
168
|
// back. That is the whole discipline: the caller knows whether it named
|
|
169
169
|
// somebody, so branching on it discloses nothing, while branching on
|
|
170
170
|
// anything the server learned about the target would.
|
|
171
|
+
//
|
|
172
|
+
// `agreementId` follows the same discipline: the route carries it for an
|
|
173
|
+
// agent id and an open invite and never for an email, so which of the two it
|
|
174
|
+
// is depends on what the caller passed and on nothing the server learned. It
|
|
175
|
+
// is what lets a caller reference the row and check on it; without it,
|
|
176
|
+
// several agents connecting in one run each minted a link toward the others
|
|
177
|
+
// in both directions, because none of them could ask.
|
|
171
178
|
return {
|
|
172
179
|
status: 'sent',
|
|
173
180
|
shareUrl,
|
|
181
|
+
agreementId,
|
|
174
182
|
message: to
|
|
175
183
|
? `Invitation sent to ${to}. You will not be told whether they already had an account, whether you were already connected, or whether this repeated an earlier invitation — the answer is the same in every case, on purpose. It becomes a live connection when they accept. ${linkIsReachOnly(env)}`
|
|
176
184
|
: `Share link created, valid 7 days. Give your human shareUrl and nothing else: it is the whole invite. ${linkIsReachOnly(env)}`,
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Opt-in row projection for list tools.
|
|
3
|
+
*
|
|
4
|
+
* List endpoints return the full row by default. Pass `fields` to keep only
|
|
5
|
+
* those keys — the same shape as Linear's `list_issues`. Envelope keys
|
|
6
|
+
* (count, nextCursor, hasMore) are never filtered.
|
|
7
|
+
*/
|
|
8
|
+
export declare const LIST_FIELDS_PARAM: {
|
|
9
|
+
type: "array";
|
|
10
|
+
items: {
|
|
11
|
+
type: "string";
|
|
12
|
+
};
|
|
13
|
+
description: string;
|
|
14
|
+
};
|
|
15
|
+
export declare function parseListFields(raw: unknown): string[] | undefined;
|
|
16
|
+
export declare function pickListedRow(row: Record<string, unknown>, fields: string[] | undefined): Record<string, unknown>;
|
|
17
|
+
export declare function pickListedRows(rows: unknown[], fields: string[] | undefined): unknown[];
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Opt-in row projection for list tools.
|
|
3
|
+
*
|
|
4
|
+
* List endpoints return the full row by default. Pass `fields` to keep only
|
|
5
|
+
* those keys — the same shape as Linear's `list_issues`. Envelope keys
|
|
6
|
+
* (count, nextCursor, hasMore) are never filtered.
|
|
7
|
+
*/
|
|
8
|
+
export const LIST_FIELDS_PARAM = {
|
|
9
|
+
type: 'array',
|
|
10
|
+
items: { type: 'string' },
|
|
11
|
+
description: 'Return only these keys on each row. Omit for the full row. Keep the id you will use next (agreementId, taskId, artifactId, grantId).',
|
|
12
|
+
};
|
|
13
|
+
export function parseListFields(raw) {
|
|
14
|
+
if (raw == null)
|
|
15
|
+
return undefined;
|
|
16
|
+
if (!Array.isArray(raw) || raw.length === 0) {
|
|
17
|
+
throw new Error('fields must be a non-empty array of row key names');
|
|
18
|
+
}
|
|
19
|
+
const out = [];
|
|
20
|
+
for (const item of raw) {
|
|
21
|
+
if (typeof item !== 'string' || !item.trim()) {
|
|
22
|
+
throw new Error('fields entries must be non-empty strings');
|
|
23
|
+
}
|
|
24
|
+
if (!out.includes(item))
|
|
25
|
+
out.push(item);
|
|
26
|
+
}
|
|
27
|
+
return out;
|
|
28
|
+
}
|
|
29
|
+
export function pickListedRow(row, fields) {
|
|
30
|
+
if (!fields)
|
|
31
|
+
return row;
|
|
32
|
+
const picked = {};
|
|
33
|
+
for (const key of fields) {
|
|
34
|
+
if (key in row)
|
|
35
|
+
picked[key] = row[key];
|
|
36
|
+
}
|
|
37
|
+
return picked;
|
|
38
|
+
}
|
|
39
|
+
export function pickListedRows(rows, fields) {
|
|
40
|
+
if (!fields)
|
|
41
|
+
return rows;
|
|
42
|
+
return rows.map((row) => row && typeof row === 'object' && !Array.isArray(row)
|
|
43
|
+
? pickListedRow(row, fields)
|
|
44
|
+
: row);
|
|
45
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { pullOffers, pullRequests } from '../http/MarketplaceClient.js';
|
|
2
2
|
import { fullCreds } from './types.js';
|
|
3
3
|
import { nextCall } from './nextCall.js';
|
|
4
|
+
import { LIST_FIELDS_PARAM, parseListFields, pickListedRows } from './listedFields.js';
|
|
4
5
|
const VIEW_KINDS = ['all', 'requests', 'offers'];
|
|
5
6
|
function publishHint(env) {
|
|
6
7
|
const propose = env.surface === 'mcp' ? 'ziggs_agreement_request' : 'agreement_request';
|
|
@@ -77,6 +78,7 @@ export const marketplaceViewCapability = {
|
|
|
77
78
|
},
|
|
78
79
|
limit: { type: 'number', description: 'Max rows per kind (default 20)' },
|
|
79
80
|
since: { type: 'string', description: 'ISO timestamp — only rows published after this' },
|
|
81
|
+
fields: LIST_FIELDS_PARAM,
|
|
80
82
|
},
|
|
81
83
|
needsAgentId: true,
|
|
82
84
|
handler: async (args, env) => {
|
|
@@ -93,12 +95,15 @@ export const marketplaceViewCapability = {
|
|
|
93
95
|
kind === 'offers' ? Promise.resolve([]) : pullRequests(options, creds),
|
|
94
96
|
kind === 'requests' ? Promise.resolve([]) : pullOffers(options, creds),
|
|
95
97
|
]);
|
|
98
|
+
const fields = parseListFields(args['fields']);
|
|
99
|
+
const requestRows = pickListedRows(requests.map((q) => toListingRow(q, 'request')), fields);
|
|
100
|
+
const offerRows = pickListedRows(offers.map((o) => toListingRow(o, 'offer')), fields);
|
|
96
101
|
return {
|
|
97
102
|
...(kind !== 'offers'
|
|
98
|
-
? { requests:
|
|
103
|
+
? { requests: requestRows, requestCount: requests.length }
|
|
99
104
|
: {}),
|
|
100
105
|
...(kind !== 'requests'
|
|
101
|
-
? { offers:
|
|
106
|
+
? { offers: offerRows, offerCount: offers.length }
|
|
102
107
|
: {}),
|
|
103
108
|
// Claiming is the move after browsing, and the id is in the row the
|
|
104
109
|
// caller just received. The prose hint stays for the publish side, which
|
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
import { PaymentsClient } from '../http/PaymentsClient.js';
|
|
2
2
|
import { rethrowWithContext, } from './types.js';
|
|
3
3
|
function client(env) {
|
|
4
|
-
|
|
4
|
+
// laneId too, like every other Creds-based client. Only the balance read is
|
|
5
|
+
// on this surface today, and a read does not spend — but the lane is what
|
|
6
|
+
// fences a spend to the job it was authorised in, so it is wired here rather
|
|
7
|
+
// than left for whoever brings a spending verb back to remember. Omitting it
|
|
8
|
+
// can only leave a spend unfenced; it can never widen one.
|
|
9
|
+
const { operatorKey, agentId, laneId } = env.creds;
|
|
5
10
|
if (!operatorKey)
|
|
6
11
|
throw new Error('operatorKey missing from tool context');
|
|
7
|
-
return new PaymentsClient(operatorKey, agentId, env.baseUrl);
|
|
12
|
+
return new PaymentsClient(operatorKey, agentId, env.baseUrl, laneId);
|
|
8
13
|
}
|
|
9
14
|
/**
|
|
10
15
|
* Reading the balance is the whole rail on an agent surface.
|