@ziggs-ai/api-client 0.15.1 → 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 +10 -1
- package/dist/capabilities/agreements.js +2 -1
- package/dist/capabilities/artifacts.js +16 -6
- package/dist/capabilities/connections.js +5 -5
- 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 +3 -3
- 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/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 +6 -4
- package/dist/http/ConnectionsClient.js +7 -5
- 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/OrgsClient.d.ts +0 -14
- package/dist/http/OrgsClient.js +2 -17
- package/dist/http/PaymentsClient.d.ts +27 -3
- package/dist/http/PaymentsClient.js +18 -3
- package/dist/http/TaskClient.js +1 -1
- package/dist/http/index.d.ts +1 -1
- package/dist/http/index.js +1 -1
- package/dist/http/paymentGrantSelection.d.ts +5 -0
- package/dist/http/paymentGrantSelection.js +26 -16
- 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) {
|
|
@@ -286,6 +286,15 @@ export const agreementRequestCapability = {
|
|
|
286
286
|
* to be doing today, and the publish path says so at its own end
|
|
287
287
|
* (`POST /marketplace/offers/publish`). Hanging one under a job would kill the
|
|
288
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.
|
|
289
298
|
*/
|
|
290
299
|
export const agreementOfferCapability = {
|
|
291
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,11 +1,11 @@
|
|
|
1
1
|
import { ConnectionsClient } from "../http/ConnectionsClient.js";
|
|
2
2
|
import { rethrowWithContext, } from "./types.js";
|
|
3
3
|
function client(env) {
|
|
4
|
-
// laneId, not just the key and the agent. The lane is which
|
|
5
|
-
//
|
|
6
|
-
// another's: an agent that serves several hirers
|
|
7
|
-
// it has been given, so the holder check alone
|
|
8
|
-
// other Creds-based client already carries it.
|
|
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
9
|
const { operatorKey, agentId, laneId } = env.creds;
|
|
10
10
|
if (!operatorKey)
|
|
11
11
|
throw new Error("operatorKey missing from tool context");
|
|
@@ -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.
|
|
@@ -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.
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { listTasks } from '../http/TaskClient.js';
|
|
2
2
|
import { fullCreds } from './types.js';
|
|
3
|
+
import { LIST_FIELDS_PARAM, parseListFields, pickListedRows } from './listedFields.js';
|
|
3
4
|
/**
|
|
4
5
|
* One list-tasks verb. MCP and the hosted SDK both used to re-declare the
|
|
5
6
|
* same GET /tasks filters; assignedToMe vs assignedTo precedence then
|
|
@@ -11,8 +12,8 @@ export const listTasksCapability = {
|
|
|
11
12
|
names: { sdk: 'task_list', mcp: 'ziggs_task_list' },
|
|
12
13
|
title: 'List tasks',
|
|
13
14
|
descriptions: {
|
|
14
|
-
sdk: 'List tasks reachable by this agent (GET /tasks). Scope follows the operator key — same reach as chats and agreements. Optional state / assignee filters and cursor pagination. Use assignedToMe to list your own open work, and createdByMe to list the work you handed to someone else.',
|
|
15
|
-
mcp: 'List tasks reachable by the acting agent (GET /tasks). Scope is determined by the operator key — same reach as chats and agreements. Optional state / assignee filters and cursor pagination. Use assignedToMe to list your own open work, and createdByMe to list the work you handed to someone else.',
|
|
15
|
+
sdk: 'List tasks reachable by this agent (GET /tasks). Scope follows the operator key — same reach as chats and agreements. Optional state / assignee filters and cursor pagination. Use assignedToMe to list your own open work, and createdByMe to list the work you handed to someone else. Pass fields to keep only those keys on each row.',
|
|
16
|
+
mcp: 'List tasks reachable by the acting agent (GET /tasks). Scope is determined by the operator key — same reach as chats and agreements. Optional state / assignee filters and cursor pagination. Use assignedToMe to list your own open work, and createdByMe to list the work you handed to someone else. Pass fields to keep only those keys on each row.',
|
|
16
17
|
},
|
|
17
18
|
annotation: 'read-only',
|
|
18
19
|
params: {
|
|
@@ -40,6 +41,7 @@ export const listTasksCapability = {
|
|
|
40
41
|
type: 'boolean',
|
|
41
42
|
description: "Shorthand for assignedTo=<this agent's id>. Takes precedence over assignedTo when both are set.",
|
|
42
43
|
},
|
|
44
|
+
fields: LIST_FIELDS_PARAM,
|
|
43
45
|
},
|
|
44
46
|
needsAgentId: true,
|
|
45
47
|
handler: async (args, env) => {
|
|
@@ -48,13 +50,17 @@ export const listTasksCapability = {
|
|
|
48
50
|
? creds.agentId
|
|
49
51
|
: args['assignedTo'];
|
|
50
52
|
const createdBy = args['createdByMe'] ? creds.agentId : undefined;
|
|
51
|
-
|
|
53
|
+
const listed = await listTasks({
|
|
52
54
|
state: args['state'],
|
|
53
55
|
cursor: args['cursor'],
|
|
54
56
|
limit: typeof args['limit'] === 'number' ? args['limit'] : undefined,
|
|
55
57
|
assignedTo,
|
|
56
58
|
...(createdBy ? { createdBy } : {}),
|
|
57
59
|
}, creds);
|
|
60
|
+
const fields = parseListFields(args['fields']);
|
|
61
|
+
return fields
|
|
62
|
+
? { ...listed, tasks: pickListedRows(listed.tasks, fields) }
|
|
63
|
+
: listed;
|
|
58
64
|
},
|
|
59
65
|
};
|
|
60
66
|
export const TASK_CAPABILITIES = [listTasksCapability];
|
|
@@ -28,6 +28,8 @@ export interface ListArtifactsResult {
|
|
|
28
28
|
}
|
|
29
29
|
export interface WriteArtifactInput {
|
|
30
30
|
text: string;
|
|
31
|
+
/** Short list name. An upload defaults to filename when omitted. */
|
|
32
|
+
name?: string;
|
|
31
33
|
contentType?: string;
|
|
32
34
|
visibility?: ArtifactVisibility;
|
|
33
35
|
/** When the artifact is associated with a chat. */
|
|
@@ -47,6 +49,8 @@ export interface WriteArtifactInput {
|
|
|
47
49
|
}
|
|
48
50
|
export type ArtifactFileFormat = 'pdf' | 'docx' | 'hwpx' | 'md' | 'txt' | 'html';
|
|
49
51
|
export interface ArtifactUploadUrlInput {
|
|
52
|
+
/** Short list name. Defaults to filename on the server when omitted. */
|
|
53
|
+
name?: string;
|
|
50
54
|
filename: string;
|
|
51
55
|
mime: string;
|
|
52
56
|
/** Exact UTF-8 / file byte length of the object that will be PUT. */
|
|
@@ -82,22 +86,7 @@ export type ArtifactFileView = Record<string, unknown> & {
|
|
|
82
86
|
extractionError?: string | null;
|
|
83
87
|
filename?: string | null;
|
|
84
88
|
};
|
|
85
|
-
|
|
86
|
-
* Replaces `ContextReader`'s artifact reads + `ContextWriter`'s breadcrumb
|
|
87
|
-
* writes. `visibility: 'agent-private'` is the new home for agent thoughts —
|
|
88
|
-
* they persist, are searchable, but are not visible to other chat parties.
|
|
89
|
-
*/
|
|
90
|
-
/**
|
|
91
|
-
* turn a runtime lane id into a scope the backend can accept.
|
|
92
|
-
*
|
|
93
|
-
* A task with no origin chat runs on the lane `agrn-<agreementId>` (see
|
|
94
|
-
* AgentHost.laneSessionIdForTask). That is a routing key, not a chat: no such
|
|
95
|
-
* chat row exists, so passing it as `chatId` made every breadcrumb and recorded
|
|
96
|
-
* thought on an agreement lane come back 403 "not authorized for this scope" —
|
|
97
|
-
* a fictional scope reading like an auth failure. The work is agreement-scoped,
|
|
98
|
-
* so say so; same rule settled for deliverables.
|
|
99
|
-
*/
|
|
100
|
-
export declare const AGREEMENT_LANE_PREFIX = "agrn-";
|
|
89
|
+
export { AGREEMENT_LANE_PREFIX } from '@ziggs-ai/contracts';
|
|
101
90
|
export declare function artifactScopeForSession(sessionId: string): {
|
|
102
91
|
chatId: string;
|
|
103
92
|
} | {
|
|
@@ -33,17 +33,18 @@ function resolveUploadBytes(input) {
|
|
|
33
33
|
* turn a runtime lane id into a scope the backend can accept.
|
|
34
34
|
*
|
|
35
35
|
* A task with no origin chat runs on the lane `agrn-<agreementId>` (see
|
|
36
|
-
*
|
|
36
|
+
* Agent.laneSessionIdForTask). That is a routing key, not a chat: no such
|
|
37
37
|
* chat row exists, so passing it as `chatId` made every breadcrumb and recorded
|
|
38
38
|
* thought on an agreement lane come back 403 "not authorized for this scope" —
|
|
39
39
|
* a fictional scope reading like an auth failure. The work is agreement-scoped,
|
|
40
40
|
* so say so; same rule settled for deliverables.
|
|
41
41
|
*/
|
|
42
|
-
|
|
42
|
+
import { agreementIdFromLane } from '@ziggs-ai/contracts';
|
|
43
|
+
export { AGREEMENT_LANE_PREFIX } from '@ziggs-ai/contracts';
|
|
43
44
|
export function artifactScopeForSession(sessionId) {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
45
|
+
const agreementId = agreementIdFromLane(sessionId);
|
|
46
|
+
if (agreementId)
|
|
47
|
+
return { agreementId };
|
|
47
48
|
return { chatId: sessionId };
|
|
48
49
|
}
|
|
49
50
|
export class ArtifactsClient {
|
|
@@ -145,6 +146,7 @@ export class ArtifactsClient {
|
|
|
145
146
|
headers: this._headers(),
|
|
146
147
|
body: JSON.stringify({
|
|
147
148
|
text,
|
|
149
|
+
...(input.name?.trim() ? { name: input.name.trim() } : {}),
|
|
148
150
|
contentType: input.contentType ?? 'text',
|
|
149
151
|
visibility: input.visibility ?? 'chat',
|
|
150
152
|
chatId: input.chatId,
|
|
@@ -214,6 +216,7 @@ export class ArtifactsClient {
|
|
|
214
216
|
}
|
|
215
217
|
const parsed = await this._post('/artifacts/upload-url', {
|
|
216
218
|
filename: input.filename.trim(),
|
|
219
|
+
...(input.name?.trim() ? { name: input.name.trim() } : {}),
|
|
217
220
|
mime: input.mime.trim(),
|
|
218
221
|
byteSize: input.byteSize,
|
|
219
222
|
format: input.format,
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { SendChatMessageResult } from '@ziggs-ai/contracts';
|
|
1
2
|
import { type Creds } from '../types.js';
|
|
2
3
|
/** Shape of GET /chats/mine items — the backend's ChatReadDto. */
|
|
3
4
|
export interface ChatSummary {
|
|
@@ -41,12 +42,7 @@ export interface SendChatMessageInput {
|
|
|
41
42
|
contentType?: string;
|
|
42
43
|
underAgreementId?: string;
|
|
43
44
|
}
|
|
44
|
-
export
|
|
45
|
-
success: boolean;
|
|
46
|
-
message: string;
|
|
47
|
-
messageId: string;
|
|
48
|
-
chatId: string;
|
|
49
|
-
}
|
|
45
|
+
export type { SendChatMessageResult } from '@ziggs-ai/contracts';
|
|
50
46
|
export type ContextTemporal = 'from-now' | 'from-start';
|
|
51
47
|
export interface AddChatMemberInput {
|
|
52
48
|
chatId: string;
|
|
@@ -59,10 +59,12 @@ export declare class ConnectionsClient {
|
|
|
59
59
|
private readonly laneId?;
|
|
60
60
|
/**
|
|
61
61
|
* @param laneId the wake's lane (a chat id, or `agrn-<agreementId>`), sent as
|
|
62
|
-
* X-Ziggs-Lane. It tells the broker which
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
62
|
+
* X-Ziggs-Lane. It tells the broker which hire this wake is in, and the
|
|
63
|
+
* broker reads off it WHO the agent is acting for: an agent that serves
|
|
64
|
+
* several hirers holds all of their grants at once and is the holder of
|
|
65
|
+
* every one, so without a lane one hirer's grant is indistinguishable from
|
|
66
|
+
* another's, and a grant one customer gave can be spent while working for
|
|
67
|
+
* somebody else.
|
|
66
68
|
* ArtifactsClient sends the same header for the same reason.
|
|
67
69
|
*/
|
|
68
70
|
constructor(operatorKey: string, agentId?: string, baseUrl?: string, laneId?: string);
|
|
@@ -37,10 +37,12 @@ export class ConnectionsClient {
|
|
|
37
37
|
laneId;
|
|
38
38
|
/**
|
|
39
39
|
* @param laneId the wake's lane (a chat id, or `agrn-<agreementId>`), sent as
|
|
40
|
-
* X-Ziggs-Lane. It tells the broker which
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
40
|
+
* X-Ziggs-Lane. It tells the broker which hire this wake is in, and the
|
|
41
|
+
* broker reads off it WHO the agent is acting for: an agent that serves
|
|
42
|
+
* several hirers holds all of their grants at once and is the holder of
|
|
43
|
+
* every one, so without a lane one hirer's grant is indistinguishable from
|
|
44
|
+
* another's, and a grant one customer gave can be spent while working for
|
|
45
|
+
* somebody else.
|
|
44
46
|
* ArtifactsClient sends the same header for the same reason.
|
|
45
47
|
*/
|
|
46
48
|
constructor(operatorKey, agentId, baseUrl, laneId) {
|
|
@@ -101,7 +103,7 @@ export class ConnectionsClient {
|
|
|
101
103
|
* defensively for leaked secrets, as `proxy` does.
|
|
102
104
|
*/
|
|
103
105
|
async listForHolder() {
|
|
104
|
-
const grantsClient = new GrantsClient(this.operatorKey, this.agentId, this.baseUrl);
|
|
106
|
+
const grantsClient = new GrantsClient(this.operatorKey, this.agentId, this.baseUrl, this.laneId);
|
|
105
107
|
// All pages of the agent's live connection grants (not just the first page).
|
|
106
108
|
const { items, unreadableRails } = await grantsClient.listAllGrantsWithRails({
|
|
107
109
|
scopeKind: "connection",
|
|
@@ -56,7 +56,16 @@ export declare class GrantsClient {
|
|
|
56
56
|
private readonly operatorKey;
|
|
57
57
|
private readonly agentId?;
|
|
58
58
|
private readonly baseUrl;
|
|
59
|
-
|
|
59
|
+
private readonly laneId?;
|
|
60
|
+
/**
|
|
61
|
+
* @param laneId the wake's lane (a chat id, or `agrn-<agreementId>`), sent as
|
|
62
|
+
* X-Ziggs-Lane. The server reads off it who the agent is acting for, and
|
|
63
|
+
* answers the spendable rails with only the grants this wake may spend —
|
|
64
|
+
* so an agent serving several customers is never handed a list it has to
|
|
65
|
+
* choose between. Without it the list is everything the agent holds, and
|
|
66
|
+
* the grant it picks may then be refused with nothing saying why.
|
|
67
|
+
*/
|
|
68
|
+
constructor(operatorKey: string, agentId?: string, baseUrl?: string, laneId?: string);
|
|
60
69
|
listGrants(query?: ListGrantsQuery): Promise<ListGrantsResult>;
|
|
61
70
|
/**
|
|
62
71
|
* Every grant matching `query`, following the cursor to completion. Use when a
|
|
@@ -12,12 +12,22 @@ export class GrantsClient {
|
|
|
12
12
|
operatorKey;
|
|
13
13
|
agentId;
|
|
14
14
|
baseUrl;
|
|
15
|
-
|
|
15
|
+
laneId;
|
|
16
|
+
/**
|
|
17
|
+
* @param laneId the wake's lane (a chat id, or `agrn-<agreementId>`), sent as
|
|
18
|
+
* X-Ziggs-Lane. The server reads off it who the agent is acting for, and
|
|
19
|
+
* answers the spendable rails with only the grants this wake may spend —
|
|
20
|
+
* so an agent serving several customers is never handed a list it has to
|
|
21
|
+
* choose between. Without it the list is everything the agent holds, and
|
|
22
|
+
* the grant it picks may then be refused with nothing saying why.
|
|
23
|
+
*/
|
|
24
|
+
constructor(operatorKey, agentId, baseUrl, laneId) {
|
|
16
25
|
if (!operatorKey)
|
|
17
26
|
throw new Error('GrantsClient: operatorKey is required');
|
|
18
27
|
this.operatorKey = operatorKey;
|
|
19
28
|
this.agentId = agentId;
|
|
20
29
|
this.baseUrl = baseUrl || getBackendUrl();
|
|
30
|
+
this.laneId = laneId;
|
|
21
31
|
}
|
|
22
32
|
async listGrants(query = {}) {
|
|
23
33
|
const url = new URL(`${this.baseUrl}/grants`);
|
|
@@ -41,7 +51,7 @@ export class GrantsClient {
|
|
|
41
51
|
if (query.limit != null)
|
|
42
52
|
url.searchParams.set('limit', String(query.limit));
|
|
43
53
|
const res = await fetch(url.toString(), {
|
|
44
|
-
headers: buildOperatorHeaders(this.operatorKey, this.agentId),
|
|
54
|
+
headers: buildOperatorHeaders(this.operatorKey, this.agentId, undefined, this.laneId),
|
|
45
55
|
});
|
|
46
56
|
const body = await res.text().catch(() => '');
|
|
47
57
|
if (!res.ok) {
|
|
@@ -5,15 +5,16 @@ export interface IntroductionFrom {
|
|
|
5
5
|
orgId: string;
|
|
6
6
|
agentId: string | null;
|
|
7
7
|
agentCreatedAt?: string | null;
|
|
8
|
-
claimedLabel
|
|
8
|
+
claimedLabel: {
|
|
9
9
|
text: string;
|
|
10
10
|
selfChosen: boolean;
|
|
11
11
|
verified: boolean;
|
|
12
12
|
};
|
|
13
13
|
/**
|
|
14
|
-
* Same text as claimedLabel.text. Not identity
|
|
14
|
+
* Same text as claimedLabel.text. Not identity. Remove after 2026-10-07;
|
|
15
|
+
* readers must use claimedLabel.
|
|
15
16
|
*/
|
|
16
|
-
label
|
|
17
|
+
label?: string;
|
|
17
18
|
}
|
|
18
19
|
export interface IntroductionView {
|
|
19
20
|
token: string;
|
|
@@ -41,7 +42,13 @@ export interface IntroductionView {
|
|
|
41
42
|
counterparty?: {
|
|
42
43
|
principal: string;
|
|
43
44
|
agentId: string | null;
|
|
44
|
-
|
|
45
|
+
claimedLabel: {
|
|
46
|
+
text: string;
|
|
47
|
+
selfChosen: boolean;
|
|
48
|
+
verified: boolean;
|
|
49
|
+
};
|
|
50
|
+
/** Same text as claimedLabel.text. Remove after 2026-10-07. */
|
|
51
|
+
label?: string;
|
|
45
52
|
nextStep: string;
|
|
46
53
|
};
|
|
47
54
|
}
|
|
@@ -27,20 +27,6 @@ export type OrgResolution = {
|
|
|
27
27
|
* match. Ambiguous names return the candidates rather than guessing.
|
|
28
28
|
*/
|
|
29
29
|
export declare function resolveOrgSelector(orgs: MyOrg[], selector: string): OrgResolution;
|
|
30
|
-
/**
|
|
31
|
-
* MCP OAuth auto-provisioned delegate id prefix.
|
|
32
|
-
*
|
|
33
|
-
* The backend stopped classifying delegates by their id and now reads a stamp
|
|
34
|
-
* on the agent row, which is what freed the prefix to drop the vendor name. A
|
|
35
|
-
* client holds credentials, not rows, so this is still a shape test — but it is
|
|
36
|
-
* a shape test against a NAME, and the only thing it decides is which access
|
|
37
|
-
* endpoint to read. It said `delegate--` until the ids were renamed;
|
|
38
|
-
* left alone, every delegate session would have fallen through to the hosted
|
|
39
|
-
* reader and reported the wrong org and connection state.
|
|
40
|
-
*/
|
|
41
|
-
export declare const MCP_OAUTH_DELEGATE_AGENT_ID_PREFIX = "delegate--";
|
|
42
|
-
/** True when `agentId` is an inbound MCP OAuth auto-provisioned delegate. */
|
|
43
|
-
export declare function isMcpOAuthDelegateAgentId(agentId: string): boolean;
|
|
44
30
|
/**
|
|
45
31
|
* runtime acting org from the server (self-hire / agent
|
|
46
32
|
* row): GET /agents/delegate/access. Moved here from ziggs-mcp's inline
|
package/dist/http/OrgsClient.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isMcpOAuthDelegateSession } from '../shared/operatorKey.js';
|
|
1
2
|
import { getBackendUrl } from '../utils/urlUtils.js';
|
|
2
3
|
import { throwApiError } from '../shared/apiError.js';
|
|
3
4
|
import { buildOperatorHeaders } from './operatorHeaders.js';
|
|
@@ -42,22 +43,6 @@ export function resolveOrgSelector(orgs, selector) {
|
|
|
42
43
|
return { status: 'ambiguous', matches: byName };
|
|
43
44
|
return { status: 'not-found' };
|
|
44
45
|
}
|
|
45
|
-
/**
|
|
46
|
-
* MCP OAuth auto-provisioned delegate id prefix.
|
|
47
|
-
*
|
|
48
|
-
* The backend stopped classifying delegates by their id and now reads a stamp
|
|
49
|
-
* on the agent row, which is what freed the prefix to drop the vendor name. A
|
|
50
|
-
* client holds credentials, not rows, so this is still a shape test — but it is
|
|
51
|
-
* a shape test against a NAME, and the only thing it decides is which access
|
|
52
|
-
* endpoint to read. It said `delegate--` until the ids were renamed;
|
|
53
|
-
* left alone, every delegate session would have fallen through to the hosted
|
|
54
|
-
* reader and reported the wrong org and connection state.
|
|
55
|
-
*/
|
|
56
|
-
export const MCP_OAUTH_DELEGATE_AGENT_ID_PREFIX = 'delegate--';
|
|
57
|
-
/** True when `agentId` is an inbound MCP OAuth auto-provisioned delegate. */
|
|
58
|
-
export function isMcpOAuthDelegateAgentId(agentId) {
|
|
59
|
-
return !!agentId && agentId.startsWith(MCP_OAUTH_DELEGATE_AGENT_ID_PREFIX);
|
|
60
|
-
}
|
|
61
46
|
/**
|
|
62
47
|
* runtime acting org from the server (self-hire / agent
|
|
63
48
|
* row): GET /agents/delegate/access. Moved here from ziggs-mcp's inline
|
|
@@ -125,7 +110,7 @@ export async function fetchHostedAgentAccess(creds, baseUrl) {
|
|
|
125
110
|
* Claude OAuth delegates → self-hire status; everything else → hosted path.
|
|
126
111
|
*/
|
|
127
112
|
export async function fetchSessionAccess(creds, baseUrl) {
|
|
128
|
-
if (
|
|
113
|
+
if (isMcpOAuthDelegateSession(creds)) {
|
|
129
114
|
return fetchDelegateAccess(creds, baseUrl);
|
|
130
115
|
}
|
|
131
116
|
return fetchHostedAgentAccess(creds, baseUrl);
|
|
@@ -87,7 +87,21 @@ export declare class PaymentsClient {
|
|
|
87
87
|
private readonly operatorKey;
|
|
88
88
|
private readonly agentId?;
|
|
89
89
|
private readonly baseUrl;
|
|
90
|
-
|
|
90
|
+
private readonly laneId?;
|
|
91
|
+
/**
|
|
92
|
+
* @param laneId the wake's lane (a chat id, or `agrn-<agreementId>`), sent as
|
|
93
|
+
* X-Ziggs-Lane. It tells the backend which hire this wake is in, and the
|
|
94
|
+
* backend reads off it WHO the spend is for. An agent that serves several
|
|
95
|
+
* hirers holds all of their grants at once and the holder is the same agent
|
|
96
|
+
* in every one, so without a lane, spend authority one customer gave is
|
|
97
|
+
* spendable while the agent works for another. ConnectionsClient and
|
|
98
|
+
* ArtifactsClient send the same header for the same reason.
|
|
99
|
+
*
|
|
100
|
+
* Omitting it cannot widen anything — a stated lane can only cause a
|
|
101
|
+
* refusal — but it does leave the spend unfenced, so a caller that knows
|
|
102
|
+
* its lane should pass it.
|
|
103
|
+
*/
|
|
104
|
+
constructor(operatorKey: string, agentId?: string, baseUrl?: string, laneId?: string);
|
|
91
105
|
balance(): Promise<WalletBalance>;
|
|
92
106
|
resolve({ userId, agentId }?: {
|
|
93
107
|
userId?: string;
|
|
@@ -99,7 +113,12 @@ export declare class PaymentsClient {
|
|
|
99
113
|
idempotencyKey?: string;
|
|
100
114
|
description?: string;
|
|
101
115
|
paymentGrantId?: string;
|
|
102
|
-
/**
|
|
116
|
+
/**
|
|
117
|
+
* The hire this spend belongs to. Used for attribution, and to prefer that
|
|
118
|
+
* hire's own grant over a standing one — a preference about which budget is
|
|
119
|
+
* billed, never about which grants may be spent. That answer is the
|
|
120
|
+
* server's, and it turns on who gave the grant.
|
|
121
|
+
*/
|
|
103
122
|
agreementId?: string;
|
|
104
123
|
}): Promise<TransferResult>;
|
|
105
124
|
hold({ amount, idempotencyKey, description, paymentGrantId, agreementId, }: {
|
|
@@ -107,7 +126,12 @@ export declare class PaymentsClient {
|
|
|
107
126
|
idempotencyKey?: string;
|
|
108
127
|
description?: string;
|
|
109
128
|
paymentGrantId?: string;
|
|
110
|
-
/**
|
|
129
|
+
/**
|
|
130
|
+
* The hire this spend belongs to. Used for attribution, and to prefer that
|
|
131
|
+
* hire's own grant over a standing one — a preference about which budget is
|
|
132
|
+
* billed, never about which grants may be spent. That answer is the
|
|
133
|
+
* server's, and it turns on who gave the grant.
|
|
134
|
+
*/
|
|
111
135
|
agreementId?: string;
|
|
112
136
|
}): Promise<HoldResult>;
|
|
113
137
|
/**
|
|
@@ -18,12 +18,27 @@ export class PaymentsClient {
|
|
|
18
18
|
operatorKey;
|
|
19
19
|
agentId;
|
|
20
20
|
baseUrl;
|
|
21
|
-
|
|
21
|
+
laneId;
|
|
22
|
+
/**
|
|
23
|
+
* @param laneId the wake's lane (a chat id, or `agrn-<agreementId>`), sent as
|
|
24
|
+
* X-Ziggs-Lane. It tells the backend which hire this wake is in, and the
|
|
25
|
+
* backend reads off it WHO the spend is for. An agent that serves several
|
|
26
|
+
* hirers holds all of their grants at once and the holder is the same agent
|
|
27
|
+
* in every one, so without a lane, spend authority one customer gave is
|
|
28
|
+
* spendable while the agent works for another. ConnectionsClient and
|
|
29
|
+
* ArtifactsClient send the same header for the same reason.
|
|
30
|
+
*
|
|
31
|
+
* Omitting it cannot widen anything — a stated lane can only cause a
|
|
32
|
+
* refusal — but it does leave the spend unfenced, so a caller that knows
|
|
33
|
+
* its lane should pass it.
|
|
34
|
+
*/
|
|
35
|
+
constructor(operatorKey, agentId, baseUrl, laneId) {
|
|
22
36
|
if (!operatorKey)
|
|
23
37
|
throw new Error('PaymentsClient: operatorKey is required');
|
|
24
38
|
this.operatorKey = operatorKey;
|
|
25
39
|
this.agentId = agentId;
|
|
26
40
|
this.baseUrl = baseUrl || getBackendUrl();
|
|
41
|
+
this.laneId = laneId;
|
|
27
42
|
}
|
|
28
43
|
async balance() {
|
|
29
44
|
const w = (await this._get('/payments/wallet'));
|
|
@@ -180,7 +195,7 @@ export class PaymentsClient {
|
|
|
180
195
|
* (retired GET /payments/grants), following the cursor to completion.
|
|
181
196
|
*/
|
|
182
197
|
async listGrants() {
|
|
183
|
-
const grants = new GrantsClient(this.operatorKey, this.agentId, this.baseUrl);
|
|
198
|
+
const grants = new GrantsClient(this.operatorKey, this.agentId, this.baseUrl, this.laneId);
|
|
184
199
|
return grants.listAllGrants({ scopeKind: 'wallet', health: 'active' });
|
|
185
200
|
}
|
|
186
201
|
async createTopUpIntent({ amount, description, currency, } = {}) {
|
|
@@ -261,7 +276,7 @@ export class PaymentsClient {
|
|
|
261
276
|
async _request(method, path, body) {
|
|
262
277
|
const init = {
|
|
263
278
|
method,
|
|
264
|
-
headers: buildOperatorHeaders(this.operatorKey, this.agentId, body !== undefined ? { 'content-type': 'application/json' } : {}),
|
|
279
|
+
headers: buildOperatorHeaders(this.operatorKey, this.agentId, body !== undefined ? { 'content-type': 'application/json' } : {}, this.laneId),
|
|
265
280
|
};
|
|
266
281
|
if (body !== undefined)
|
|
267
282
|
init.body = JSON.stringify(body);
|
package/dist/http/TaskClient.js
CHANGED
|
@@ -165,7 +165,7 @@ export async function getActiveTasksForAgent(agentId, creds) {
|
|
|
165
165
|
* assignee's own work list hands over a task nobody woke it for. Every caller of
|
|
166
166
|
* this function is asking "what can I act on now", and for a withheld row the
|
|
167
167
|
* answer is nothing — `updateTaskState` refuses it with `dependencies_unmet`.
|
|
168
|
-
* Two of
|
|
168
|
+
* Two of Agent's three callers do worse than waste a wake: the needs gate
|
|
169
169
|
* and the unmet-access sweep both FAIL the tasks they find, so a withheld node
|
|
170
170
|
* was one missing grant away from being failed before its turn came.
|
|
171
171
|
*
|
package/dist/http/index.d.ts
CHANGED
|
@@ -23,7 +23,7 @@ export { PaymentsClient } from './PaymentsClient.js';
|
|
|
23
23
|
export type { PaymentsError, WalletBalance, WalletRef, PaymentTransactionView, TransferResult, HoldResult, ReleaseResult, PaymentGrantView, PaymentGrantEnvelope, RevokeGrantResult, PaymentApproval, WaitForApprovalResult, } from './PaymentsClient.js';
|
|
24
24
|
export { ConnectionsClient, assertNoLeakedConnectionSecret, } from './ConnectionsClient.js';
|
|
25
25
|
export type { ConnectionsError, ConnectionProxyParams, ConnectionGrant, ConnectionWithGrants, McpConnectionRequestParams, } from './ConnectionsClient.js';
|
|
26
|
-
export { fetchMyOrgs, resolveOrgSelector, fetchDelegateAccess, fetchHostedAgentAccess, fetchSessionAccess,
|
|
26
|
+
export { fetchMyOrgs, resolveOrgSelector, fetchDelegateAccess, fetchHostedAgentAccess, fetchSessionAccess, } from './OrgsClient.js';
|
|
27
27
|
export type { MyOrg, OrgResolution } from './OrgsClient.js';
|
|
28
28
|
export { AgentSearchClient } from './AgentSearchClient.js';
|
|
29
29
|
export { TelemetryClient } from './TelemetryClient.js';
|
package/dist/http/index.js
CHANGED
|
@@ -15,7 +15,7 @@ export { ContextGrantsClient } from './ContextGrantsClient.js';
|
|
|
15
15
|
export { grantCaveat, CONTEXT_GRANT_SCOPE_KINDS, GRANT_SCOPE_KINDS, } from './grants.js';
|
|
16
16
|
export { PaymentsClient } from './PaymentsClient.js';
|
|
17
17
|
export { ConnectionsClient, assertNoLeakedConnectionSecret, } from './ConnectionsClient.js';
|
|
18
|
-
export { fetchMyOrgs, resolveOrgSelector, fetchDelegateAccess, fetchHostedAgentAccess, fetchSessionAccess,
|
|
18
|
+
export { fetchMyOrgs, resolveOrgSelector, fetchDelegateAccess, fetchHostedAgentAccess, fetchSessionAccess, } from './OrgsClient.js';
|
|
19
19
|
export { AgentSearchClient } from './AgentSearchClient.js';
|
|
20
20
|
export { TelemetryClient } from './TelemetryClient.js';
|
|
21
21
|
export { InboxClient } from './InboxClient.js';
|
|
@@ -39,6 +39,11 @@ export interface GrantSelection {
|
|
|
39
39
|
* spend names one, then the standing grant. A standing grant is the buyer
|
|
40
40
|
* bootstrap and the right default; reaching for it while an agreement grant
|
|
41
41
|
* covers the same spend would bill the wrong budget.
|
|
42
|
+
*
|
|
43
|
+
* This is a preference, never a fence. Every grant here is already one the
|
|
44
|
+
* server said this wake may spend, so picking a different one cannot reach
|
|
45
|
+
* another party's authority — it would only bill the wrong budget of the same
|
|
46
|
+
* party. That is why the mismatch is no longer a rejection above.
|
|
42
47
|
*/
|
|
43
48
|
export declare function selectPaymentGrant(grants: readonly GrantView[], spend: SpendShape): GrantSelection;
|
|
44
49
|
/**
|
|
@@ -52,18 +52,18 @@ function whyNot(grant, spend) {
|
|
|
52
52
|
if (allowed && spend.toWalletId && !allowed.includes(spend.toWalletId)) {
|
|
53
53
|
return `may only pay ${allowed.join(', ')}, and this one pays ${spend.toWalletId}`;
|
|
54
54
|
}
|
|
55
|
-
//
|
|
56
|
-
//
|
|
57
|
-
//
|
|
58
|
-
//
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
55
|
+
// A grant's agreement is NOT checked here, deliberately.
|
|
56
|
+
//
|
|
57
|
+
// It used to be refused when it did not match the spend's agreement, which
|
|
58
|
+
// made a customer's own grant unusable inside their second hire of the same
|
|
59
|
+
// agent, and made a standing grant unusable on any spend that named a deal.
|
|
60
|
+
// Whether a grant may be spent at all is the server's answer, and it turns on
|
|
61
|
+
// who GAVE the grant against who the agent is acting for — not on which of
|
|
62
|
+
// that party's deals is running. A list the server answered for this wake
|
|
63
|
+
// holds only grants it may spend, so there is nothing left to refuse here.
|
|
64
|
+
//
|
|
65
|
+
// The agreement still decides the ORDER below, which is a budget preference
|
|
66
|
+
// rather than a question of authority.
|
|
67
67
|
return null;
|
|
68
68
|
}
|
|
69
69
|
/**
|
|
@@ -73,6 +73,11 @@ function whyNot(grant, spend) {
|
|
|
73
73
|
* spend names one, then the standing grant. A standing grant is the buyer
|
|
74
74
|
* bootstrap and the right default; reaching for it while an agreement grant
|
|
75
75
|
* covers the same spend would bill the wrong budget.
|
|
76
|
+
*
|
|
77
|
+
* This is a preference, never a fence. Every grant here is already one the
|
|
78
|
+
* server said this wake may spend, so picking a different one cannot reach
|
|
79
|
+
* another party's authority — it would only bill the wrong budget of the same
|
|
80
|
+
* party. That is why the mismatch is no longer a rejection above.
|
|
76
81
|
*/
|
|
77
82
|
export function selectPaymentGrant(grants, spend) {
|
|
78
83
|
const rejected = [];
|
|
@@ -86,10 +91,15 @@ export function selectPaymentGrant(grants, spend) {
|
|
|
86
91
|
}
|
|
87
92
|
if (fitting.length === 0)
|
|
88
93
|
return { grant: null, rejected };
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
94
|
+
// Explicit both ways, because the deal is no longer a fence: until it was
|
|
95
|
+
// removed, a deal-bound grant simply never reached this list on a spend that
|
|
96
|
+
// named nothing, so "prefer the standing grant" happened by accident of the
|
|
97
|
+
// rejection. Now it has to be said.
|
|
98
|
+
const standing = fitting.find((g) => !g.agreementId);
|
|
99
|
+
const preferred = spend.agreementId
|
|
100
|
+
? (fitting.find((g) => g.agreementId === spend.agreementId) ?? standing)
|
|
101
|
+
: standing;
|
|
102
|
+
return { grant: preferred ?? fitting[0], rejected };
|
|
93
103
|
}
|
|
94
104
|
/**
|
|
95
105
|
* The sentence an agent gets when nothing fits.
|
package/dist/index.d.ts
CHANGED
|
@@ -12,3 +12,6 @@ export { parseErrorMessage, parseErrorCode, throwApiError, } from './shared/apiE
|
|
|
12
12
|
export { ApiError } from './types.js';
|
|
13
13
|
export type { Creds, Task, TaskState, PlanStep, PlanStepStatus, Agreement, AgreementParties, AgreementPartySide, EngagementKind, BroadcastAudience, EntryType, ContentType, MessageMetadata, MessageHandler, InboxDeliveryKind, InboxDeliveryRef, InboxChatNews, InboxProposalRef, InboxTaskRef, InboxRequestRef, InboxConnectionRequestRef, InboxHumanAttention, InboxEnvelope, InboxAckResult, InboxReadOptions, } from './types.js';
|
|
14
14
|
export type { ProposeTerms, ProposeDirectInput, ProposeBroadcastInput, ProposeAgreementData, ClaimOptions, } from './http/AgreementClient.js';
|
|
15
|
+
export { agreementLaneId, agreementIdFromLane, isAgreementLaneId, isAgreementId } from '@ziggs-ai/contracts';
|
|
16
|
+
export * from './shared/operatorKey.js';
|
|
17
|
+
export * from './utils/appUrls.js';
|
package/dist/index.js
CHANGED
|
@@ -13,3 +13,6 @@ export { RateLimitedError, isRateLimited, parseRetryAfterMs, pollSurfaceError, }
|
|
|
13
13
|
// half of them had drifted).
|
|
14
14
|
export { parseErrorMessage, parseErrorCode, throwApiError, } from './shared/apiError.js';
|
|
15
15
|
export { ApiError } from './types.js';
|
|
16
|
+
export { agreementLaneId, agreementIdFromLane, isAgreementLaneId, isAgreementId } from '@ziggs-ai/contracts';
|
|
17
|
+
export * from './shared/operatorKey.js';
|
|
18
|
+
export * from './utils/appUrls.js';
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/** JWT payload fields we read client-side (signature not verified — identity hint only). */
|
|
2
|
+
export interface OperatorKeyClaims {
|
|
3
|
+
type?: string;
|
|
4
|
+
keyId?: string;
|
|
5
|
+
ownerId?: string;
|
|
6
|
+
boundAgentId?: string | null;
|
|
7
|
+
/**
|
|
8
|
+
* How the key was authorized. MCP consent uses `mcp_oauth` or `device_code`;
|
|
9
|
+
* provisioning and other mint paths carry their own provenance.
|
|
10
|
+
*/
|
|
11
|
+
issuedVia?: string;
|
|
12
|
+
exp?: number;
|
|
13
|
+
}
|
|
14
|
+
/** Decode operator JWT payload without verifying signature (boundAgentId). */
|
|
15
|
+
export declare function decodeOperatorKeyClaims(token: string): OperatorKeyClaims | null;
|
|
16
|
+
export declare function isOperatorKeyExpired(claims: OperatorKeyClaims | null): boolean;
|
|
17
|
+
/** The stamp the MCP OAuth authorization-code consent flow puts on its tokens. */
|
|
18
|
+
export declare const ISSUED_VIA_MCP_OAUTH = "mcp_oauth";
|
|
19
|
+
/** The stamp the MCP OAuth device-code flow puts on its tokens. */
|
|
20
|
+
export declare const ISSUED_VIA_DEVICE_CODE = "device_code";
|
|
21
|
+
/**
|
|
22
|
+
* Did a person board this credential through the connector directory?
|
|
23
|
+
*
|
|
24
|
+
* That is the question the tool surface turns on: a connected assistant
|
|
25
|
+
* (Claude, ChatGPT, Cursor) gets the listed, directory-reviewed surface, and
|
|
26
|
+
* anyone who configured this server themselves with an operator key gets the
|
|
27
|
+
* whole thing.
|
|
28
|
+
*
|
|
29
|
+
* Ask it as a predicate, never as `issuedVia === 'mcp_oauth'`. Two consent rails
|
|
30
|
+
* board an assistant through that door — the authorization-code flow and the
|
|
31
|
+
* device-code flow — and both want the same narrowed surface, but the server
|
|
32
|
+
* records them as the different acts they are. An equality test against one
|
|
33
|
+
* value serves the whole catalogue to every credential from the other one.
|
|
34
|
+
*
|
|
35
|
+
* Read off the unverified payload. On the remote endpoint the backend verified
|
|
36
|
+
* the signature before this ran; on stdio the key is the caller's own. Either
|
|
37
|
+
* way the payload is the one the backend signed. And what hangs on the answer
|
|
38
|
+
* is which tools are registered, not what a call may do: every call is still
|
|
39
|
+
* authorized by the backend. Provenance may decide what is OFFERED, never what
|
|
40
|
+
* is PERMITTED.
|
|
41
|
+
*/
|
|
42
|
+
export declare function isDirectoryBoarded(claims: OperatorKeyClaims | null): boolean;
|
|
43
|
+
/** Routing hint only. Every request is still authenticated by the backend. */
|
|
44
|
+
export declare function isMcpOAuthDelegateSession(creds: {
|
|
45
|
+
operatorKey: string;
|
|
46
|
+
agentId: string;
|
|
47
|
+
}): boolean;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/** Decode operator JWT payload without verifying signature (boundAgentId). */
|
|
2
|
+
export function decodeOperatorKeyClaims(token) {
|
|
3
|
+
const trimmed = token.trim();
|
|
4
|
+
const parts = trimmed.split('.');
|
|
5
|
+
if (parts.length !== 3)
|
|
6
|
+
return null;
|
|
7
|
+
try {
|
|
8
|
+
const encoded = parts[1].replace(/-/g, '+').replace(/_/g, '/');
|
|
9
|
+
const json = new TextDecoder().decode(Uint8Array.from(atob(encoded), (c) => c.charCodeAt(0)));
|
|
10
|
+
const payload = JSON.parse(json);
|
|
11
|
+
return {
|
|
12
|
+
type: payload.type,
|
|
13
|
+
keyId: payload.keyId,
|
|
14
|
+
ownerId: payload.ownerId,
|
|
15
|
+
boundAgentId: typeof payload.boundAgentId === 'string' ? payload.boundAgentId : null,
|
|
16
|
+
issuedVia: typeof payload.issuedVia === 'string' ? payload.issuedVia : undefined,
|
|
17
|
+
exp: payload.exp,
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
export function isOperatorKeyExpired(claims) {
|
|
25
|
+
if (!claims?.exp)
|
|
26
|
+
return false;
|
|
27
|
+
return claims.exp * 1000 <= Date.now();
|
|
28
|
+
}
|
|
29
|
+
/** The stamp the MCP OAuth authorization-code consent flow puts on its tokens. */
|
|
30
|
+
export const ISSUED_VIA_MCP_OAUTH = 'mcp_oauth';
|
|
31
|
+
/** The stamp the MCP OAuth device-code flow puts on its tokens. */
|
|
32
|
+
export const ISSUED_VIA_DEVICE_CODE = 'device_code';
|
|
33
|
+
/**
|
|
34
|
+
* Did a person board this credential through the connector directory?
|
|
35
|
+
*
|
|
36
|
+
* That is the question the tool surface turns on: a connected assistant
|
|
37
|
+
* (Claude, ChatGPT, Cursor) gets the listed, directory-reviewed surface, and
|
|
38
|
+
* anyone who configured this server themselves with an operator key gets the
|
|
39
|
+
* whole thing.
|
|
40
|
+
*
|
|
41
|
+
* Ask it as a predicate, never as `issuedVia === 'mcp_oauth'`. Two consent rails
|
|
42
|
+
* board an assistant through that door — the authorization-code flow and the
|
|
43
|
+
* device-code flow — and both want the same narrowed surface, but the server
|
|
44
|
+
* records them as the different acts they are. An equality test against one
|
|
45
|
+
* value serves the whole catalogue to every credential from the other one.
|
|
46
|
+
*
|
|
47
|
+
* Read off the unverified payload. On the remote endpoint the backend verified
|
|
48
|
+
* the signature before this ran; on stdio the key is the caller's own. Either
|
|
49
|
+
* way the payload is the one the backend signed. And what hangs on the answer
|
|
50
|
+
* is which tools are registered, not what a call may do: every call is still
|
|
51
|
+
* authorized by the backend. Provenance may decide what is OFFERED, never what
|
|
52
|
+
* is PERMITTED.
|
|
53
|
+
*/
|
|
54
|
+
export function isDirectoryBoarded(claims) {
|
|
55
|
+
return (claims?.issuedVia === ISSUED_VIA_MCP_OAUTH ||
|
|
56
|
+
claims?.issuedVia === ISSUED_VIA_DEVICE_CODE);
|
|
57
|
+
}
|
|
58
|
+
/** Routing hint only. Every request is still authenticated by the backend. */
|
|
59
|
+
export function isMcpOAuthDelegateSession(creds) {
|
|
60
|
+
const claims = decodeOperatorKeyClaims(creds.operatorKey);
|
|
61
|
+
return (!!creds.agentId &&
|
|
62
|
+
claims?.boundAgentId === creds.agentId &&
|
|
63
|
+
isDirectoryBoarded(claims));
|
|
64
|
+
}
|
package/dist/types.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { InboxRequestChannel } from '@ziggs-ai/contracts';
|
|
1
2
|
/**
|
|
2
3
|
* One failure shape for every HTTP client path.
|
|
3
4
|
*
|
|
@@ -259,19 +260,7 @@ export type BroadcastAudience = typeof OPEN_AGREEMENT_TARGET | typeof ORG_AGREEM
|
|
|
259
260
|
export declare const BROADCAST_TARGETS: readonly ["everyone", "org"];
|
|
260
261
|
/** True when `id` is a broadcast sentinel ('everyone' | 'org') rather than a concrete principal id. */
|
|
261
262
|
export declare function isBroadcastTarget(id: string | null | undefined): boolean;
|
|
262
|
-
|
|
263
|
-
* Persona face id (`psn_*`). Non-addressable — never use for agent lookup,
|
|
264
|
-
* wake, or payment parties.
|
|
265
|
-
*/
|
|
266
|
-
export declare function isPersonaRef(id: string | null | undefined): boolean;
|
|
267
|
-
/**
|
|
268
|
-
* Room presentation binding id (`rpb_*`). Opaque to account lookup / wake /
|
|
269
|
-
* pay. Chat sends may echo it as `receiverId` — the backend resolves it in-room
|
|
270
|
-
*.
|
|
271
|
-
*/
|
|
272
|
-
export declare function isRoomPresentationRef(id: string | null | undefined): boolean;
|
|
273
|
-
/** Either opaque presentation ref (`psn_*` | `rpb_*`). */
|
|
274
|
-
export declare function isOpaquePresentationRef(id: string | null | undefined): boolean;
|
|
263
|
+
export { isPersonaFaceRef as isPersonaRef, isRoomBindingRef as isRoomPresentationRef, isPresentationRef as isOpaquePresentationRef, } from '@ziggs-ai/contracts';
|
|
275
264
|
/** Display + entitlement face for a principal on the message/roster wire. */
|
|
276
265
|
export interface PrincipalPresentation {
|
|
277
266
|
/** Public, non-addressable reference (`psn_*` or `rpb_*`). */
|
|
@@ -332,7 +321,7 @@ export interface MessageMetadata {
|
|
|
332
321
|
agreementId?: string | null;
|
|
333
322
|
/**
|
|
334
323
|
* Message send time from the wire payload. The live-path inbox
|
|
335
|
-
* watermark ack (
|
|
324
|
+
* watermark ack (Agent, P4) needs it — without a timestamp the
|
|
336
325
|
* ack never fires and catch-up re-delivers the chat's whole backlog after a
|
|
337
326
|
* host eviction, re-running already-answered goals.
|
|
338
327
|
*/
|
|
@@ -472,14 +461,8 @@ export interface InboxTaskRef {
|
|
|
472
461
|
* A marketplace request doorbell. Own channel so the host can
|
|
473
462
|
* exact-match triage with zero LLM tokens before any wake.
|
|
474
463
|
*/
|
|
475
|
-
export
|
|
476
|
-
|
|
477
|
-
/** Exact-match string from the publisher — compare to the agent's tags. */
|
|
478
|
-
match: string;
|
|
479
|
-
title: string;
|
|
480
|
-
ts: string;
|
|
481
|
-
}
|
|
482
|
-
export interface InboxEnvelope {
|
|
464
|
+
export type { InboxOpenRequestRef as InboxRequestRef } from '@ziggs-ai/contracts';
|
|
465
|
+
export interface InboxEnvelope extends InboxRequestChannel {
|
|
483
466
|
asOf: string;
|
|
484
467
|
/**
|
|
485
468
|
* The mailboxes this reader's view merges — its owner's, plus whatever
|
|
@@ -487,7 +470,7 @@ export interface InboxEnvelope {
|
|
|
487
470
|
*/
|
|
488
471
|
sources?: InboxSourceRef[];
|
|
489
472
|
/**
|
|
490
|
-
* Unacked rows across every source,
|
|
473
|
+
* Unacked rows across every source, oldest first, copies of one event
|
|
491
474
|
* collapsed. This IS the inbox — read straight out of the party delivery
|
|
492
475
|
* logs, not derived from grants. Rows with `assigneeId === me` are mine to
|
|
493
476
|
* act on; the rest are readable context.
|
|
@@ -509,9 +492,6 @@ export interface InboxEnvelope {
|
|
|
509
492
|
/** Open tasks assigned to this agent — the work channel. */
|
|
510
493
|
tasksAwaitingMe: InboxTaskRef[];
|
|
511
494
|
truncatedTasks: number;
|
|
512
|
-
/** Unacked request deliveries — triaged before any LLM wake. */
|
|
513
|
-
requestsAwaitingMe?: InboxRequestRef[];
|
|
514
|
-
truncatedRequests?: number;
|
|
515
495
|
proposalsAwaitingMe: InboxProposalRef[];
|
|
516
496
|
truncatedProposals: number;
|
|
517
497
|
connectionRequestsAwaitingMe: InboxConnectionRequestRef[];
|
package/dist/types.js
CHANGED
|
@@ -95,22 +95,4 @@ export const BROADCAST_TARGETS = [OPEN_AGREEMENT_TARGET, ORG_AGREEMENT_TARGET];
|
|
|
95
95
|
export function isBroadcastTarget(id) {
|
|
96
96
|
return id === OPEN_AGREEMENT_TARGET || id === ORG_AGREEMENT_TARGET;
|
|
97
97
|
}
|
|
98
|
-
|
|
99
|
-
* Persona face id (`psn_*`). Non-addressable — never use for agent lookup,
|
|
100
|
-
* wake, or payment parties.
|
|
101
|
-
*/
|
|
102
|
-
export function isPersonaRef(id) {
|
|
103
|
-
return typeof id === 'string' && id.startsWith('psn_');
|
|
104
|
-
}
|
|
105
|
-
/**
|
|
106
|
-
* Room presentation binding id (`rpb_*`). Opaque to account lookup / wake /
|
|
107
|
-
* pay. Chat sends may echo it as `receiverId` — the backend resolves it in-room
|
|
108
|
-
*.
|
|
109
|
-
*/
|
|
110
|
-
export function isRoomPresentationRef(id) {
|
|
111
|
-
return typeof id === 'string' && id.startsWith('rpb_');
|
|
112
|
-
}
|
|
113
|
-
/** Either opaque presentation ref (`psn_*` | `rpb_*`). */
|
|
114
|
-
export function isOpaquePresentationRef(id) {
|
|
115
|
-
return isPersonaRef(id) || isRoomPresentationRef(id);
|
|
116
|
-
}
|
|
98
|
+
export { isPersonaFaceRef as isPersonaRef, isRoomBindingRef as isRoomPresentationRef, isPresentationRef as isOpaquePresentationRef, } from '@ziggs-ai/contracts';
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/** Resolve the app origin once for SDK, MCP, and capability links. */
|
|
2
|
+
export declare function resolveWebAppOrigin(webUrl?: string | null, backendUrl?: string): string;
|
|
3
|
+
export declare function agreementAppUrl(origin: string, id: string): string;
|
|
4
|
+
export declare function agreementsListAppUrl(origin: string): string;
|
|
5
|
+
export declare function connectInviteAppUrl(origin: string, id: string): string;
|
|
6
|
+
export declare function connectionsSettingsAppUrl(origin: string, requestId?: string | null): string;
|
|
7
|
+
export declare function walletAppUrl(origin: string): string;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/** Resolve the app origin once for SDK, MCP, and capability links. */
|
|
2
|
+
export function resolveWebAppOrigin(webUrl, backendUrl) {
|
|
3
|
+
const explicit = webUrl
|
|
4
|
+
?.split(';')
|
|
5
|
+
.map((value) => value.trim())
|
|
6
|
+
.find(Boolean);
|
|
7
|
+
if (explicit)
|
|
8
|
+
return explicit.replace(/\/+$/, '');
|
|
9
|
+
if (backendUrl) {
|
|
10
|
+
const backend = new URL(backendUrl);
|
|
11
|
+
if (backend.hostname === 'localhost' || backend.hostname === '127.0.0.1')
|
|
12
|
+
return 'http://localhost:1234';
|
|
13
|
+
backend.hostname = backend.hostname.replace(/^api\./, '');
|
|
14
|
+
return backend.origin;
|
|
15
|
+
}
|
|
16
|
+
return 'https://ziggsai.com';
|
|
17
|
+
}
|
|
18
|
+
function appUrl(origin, path) {
|
|
19
|
+
return new URL(path, `${resolveWebAppOrigin(origin)}/`).toString();
|
|
20
|
+
}
|
|
21
|
+
export function agreementAppUrl(origin, id) {
|
|
22
|
+
return appUrl(origin, `/app/agreements/${encodeURIComponent(id)}`);
|
|
23
|
+
}
|
|
24
|
+
export function agreementsListAppUrl(origin) {
|
|
25
|
+
return appUrl(origin, '/app/agreements');
|
|
26
|
+
}
|
|
27
|
+
export function connectInviteAppUrl(origin, id) {
|
|
28
|
+
return appUrl(origin, `/connect/${encodeURIComponent(id)}`);
|
|
29
|
+
}
|
|
30
|
+
export function connectionsSettingsAppUrl(origin, requestId) {
|
|
31
|
+
const url = new URL(appUrl(origin, '/app/access'));
|
|
32
|
+
if (requestId)
|
|
33
|
+
url.searchParams.set('request', requestId);
|
|
34
|
+
return url.toString();
|
|
35
|
+
}
|
|
36
|
+
export function walletAppUrl(origin) {
|
|
37
|
+
return appUrl(origin, '/app/settings/organization/billing');
|
|
38
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ziggs-ai/api-client",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.16.0",
|
|
4
4
|
"description": "HTTP and WebSocket client for the Ziggs backend API",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -27,7 +27,8 @@
|
|
|
27
27
|
"test:watch": "node --import tsx/esm --test --watch test/*.test.ts"
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"socket.io-client": "^4.7.0"
|
|
30
|
+
"socket.io-client": "^4.7.0",
|
|
31
|
+
"@ziggs-ai/contracts": "^0.6.0"
|
|
31
32
|
},
|
|
32
33
|
"keywords": [
|
|
33
34
|
"ziggs",
|