@ziggs-ai/api-client 0.19.0 → 0.20.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/agreements.d.ts +3 -0
- package/dist/capabilities/agreements.js +9 -1
- package/dist/capabilities/artifacts.d.ts +38 -0
- package/dist/capabilities/artifacts.js +115 -1
- package/dist/capabilities/context.d.ts +2 -0
- package/dist/capabilities/context.js +83 -0
- package/dist/capabilities/index.d.ts +3 -3
- package/dist/capabilities/index.js +3 -3
- package/dist/capabilities/introductions.js +5 -10
- package/dist/capabilities/links.js +1 -0
- package/dist/capabilities/nextCall.d.ts +72 -14
- package/dist/capabilities/nextCall.js +221 -4
- package/dist/http/ArtifactsClient.d.ts +47 -0
- package/dist/http/ArtifactsClient.js +33 -0
- package/dist/http/ContextOpenClient.d.ts +70 -0
- package/dist/http/ContextOpenClient.js +52 -0
- package/dist/http/InboxClient.d.ts +8 -0
- package/dist/http/InboxClient.js +18 -0
- package/dist/http/TaskClient.d.ts +2 -0
- package/dist/http/TaskClient.js +1 -0
- package/dist/http/index.d.ts +2 -0
- package/dist/http/index.js +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist/instanceIdentity.d.ts +1 -1
- package/dist/instanceIdentity.js +2 -2
- package/dist/sessionOrientation.d.ts +50 -0
- package/dist/sessionOrientation.js +43 -0
- package/package.json +1 -1
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { ClaimedKind } from '../http/AgreementClient.js';
|
|
2
2
|
import type { Agreement } from '../types.js';
|
|
3
|
+
import { type NextCallOutcome, type WorkContext } from './nextCall.js';
|
|
3
4
|
import { type CapabilityDefinition, type CapabilityEnv } from './types.js';
|
|
4
5
|
/**
|
|
5
6
|
* One claim result shape for both surfaces. The SDK protocol runner used to
|
|
@@ -8,9 +9,11 @@ import { type CapabilityDefinition, type CapabilityEnv } from './types.js';
|
|
|
8
9
|
*/
|
|
9
10
|
export declare function presentClaimResult(agreement: Agreement, kind: ClaimedKind, env: CapabilityEnv): {
|
|
10
11
|
status: string;
|
|
12
|
+
outcome: NextCallOutcome;
|
|
11
13
|
kind: ClaimedKind;
|
|
12
14
|
message: string;
|
|
13
15
|
agreement: Agreement;
|
|
16
|
+
workContext?: WorkContext;
|
|
14
17
|
};
|
|
15
18
|
/**
|
|
16
19
|
* the one claim verb. Requests, standing offers, and link invites
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { agreementAppUrl } from '../utils/appUrls.js';
|
|
2
2
|
import { claimOpenAgreement } from '../http/agreementFlows.js';
|
|
3
3
|
import { linkIsReachOnly, webAppOrigin } from './links.js';
|
|
4
|
+
import { workContextFromEnv } from './nextCall.js';
|
|
4
5
|
import { fullCreds } from './types.js';
|
|
5
6
|
/** Which side the claimer just took, for a claim that has not activated yet. */
|
|
6
7
|
function claimedWhat(kind) {
|
|
@@ -16,12 +17,15 @@ function claimedWhat(kind) {
|
|
|
16
17
|
* agreement}` — same HTTP call, two answers.
|
|
17
18
|
*/
|
|
18
19
|
export function presentClaimResult(agreement, kind, env) {
|
|
20
|
+
const workContext = workContextFromEnv(env);
|
|
19
21
|
if (kind === 'link') {
|
|
20
22
|
return {
|
|
21
23
|
status: 'linked',
|
|
24
|
+
outcome: 'ok',
|
|
22
25
|
kind,
|
|
23
26
|
message: `Link invite claimed — you are now linked. ${linkIsReachOnly(env)}`,
|
|
24
27
|
agreement,
|
|
28
|
+
...(workContext ? { workContext } : {}),
|
|
25
29
|
};
|
|
26
30
|
}
|
|
27
31
|
if (agreement?.status !== 'active') {
|
|
@@ -31,16 +35,19 @@ export function presentClaimResult(agreement, kind, env) {
|
|
|
31
35
|
? 'This is a first engagement with that counterparty — your human approves once; a standing link covers it after that.'
|
|
32
36
|
: 'Claiming for a job your human already approved activates at once — name the job with mandateAgreementId.';
|
|
33
37
|
return {
|
|
34
|
-
status: '
|
|
38
|
+
status: 'pending_approval',
|
|
39
|
+
outcome: 'pending',
|
|
35
40
|
kind,
|
|
36
41
|
message: `${claimedWhat(kind)} It is not active yet: the formation is waiting on ` +
|
|
37
42
|
'human approval, so nothing can be created under it until that lands. ' +
|
|
38
43
|
`Your human can approve it here: ${approveUrl} — ${remedy}`,
|
|
39
44
|
agreement,
|
|
45
|
+
...(workContext ? { workContext } : {}),
|
|
40
46
|
};
|
|
41
47
|
}
|
|
42
48
|
return {
|
|
43
49
|
status: 'claimed',
|
|
50
|
+
outcome: 'ok',
|
|
44
51
|
kind,
|
|
45
52
|
message: kind === 'offer'
|
|
46
53
|
? 'Standing offer claimed — the publisher provides, your side pays. Spawn work under it with the task-create tool.'
|
|
@@ -48,6 +55,7 @@ export function presentClaimResult(agreement, kind, env) {
|
|
|
48
55
|
? 'Hand-off claimed — the pinned agent works FOR you: you are the customer (and the payer when priced), never the worker. Spawn work under it with the task-create tool.'
|
|
49
56
|
: 'Request claimed — you provide the work. Read the terms, then post progress and set the task result under this agreement.',
|
|
50
57
|
agreement,
|
|
58
|
+
...(workContext ? { workContext } : {}),
|
|
51
59
|
};
|
|
52
60
|
}
|
|
53
61
|
/**
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import { type DiscoverableItem } from '../http/ContextDiscoveryClient.js';
|
|
1
2
|
import { type CapabilityDefinition } from './types.js';
|
|
3
|
+
import { type NextCall } from './nextCall.js';
|
|
2
4
|
export declare const recordArtifactCapability: CapabilityDefinition;
|
|
3
5
|
/**
|
|
4
6
|
* the artifacts you wrote, across every scope and none.
|
|
@@ -8,6 +10,42 @@ export declare const recordArtifactCapability: CapabilityDefinition;
|
|
|
8
10
|
* agent finds what it recorded without naming a container.
|
|
9
11
|
*/
|
|
10
12
|
export declare const listArtifactsCapability: CapabilityDefinition;
|
|
13
|
+
/**
|
|
14
|
+
* What the agent could not read, said as metadata and nothing else.
|
|
15
|
+
*
|
|
16
|
+
* The one thing this block must never become is a second results list. A row
|
|
17
|
+
* here is a LABEL — `context_discover_grantable` re-shapes every row to
|
|
18
|
+
* type/label/scopeRef/orgId on the way through, so no message, no member name
|
|
19
|
+
* and no artifact body can travel in it, and none of these scopes were searched
|
|
20
|
+
* at all. Keeping them in a separate field with its own wording is the whole
|
|
21
|
+
* point: an agent that cannot tell "no match in what I can read" from "there is
|
|
22
|
+
* a locked room over there" will confidently report the first while the answer
|
|
23
|
+
* sits in the second.
|
|
24
|
+
*/
|
|
25
|
+
export interface UnsearchedContext {
|
|
26
|
+
/** Locked scopes, labels only — never content, never counts of content. */
|
|
27
|
+
items: (DiscoverableItem & {
|
|
28
|
+
requestAccess: NextCall;
|
|
29
|
+
})[];
|
|
30
|
+
count: number;
|
|
31
|
+
note: string;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* search the work you hold, and say what you searched.
|
|
35
|
+
*
|
|
36
|
+
* `artifact_list` answers "what did I write" and the scope reads answer "what
|
|
37
|
+
* is in this container". Neither answers "where is the thing about X", which is
|
|
38
|
+
* the question an agent actually has when it has been handed work that refers
|
|
39
|
+
* to something it did not produce. The server bounds this by the agent's own
|
|
40
|
+
* reach — authorship, readable containers, artifact grants it holds — under the
|
|
41
|
+
* active lane and shared-room containment, so there is no `reach` arm to ask
|
|
42
|
+
* for and the narrowing filters are refused rather than ignored.
|
|
43
|
+
*
|
|
44
|
+
* The response is deliberately two things that never merge: `artifacts`, which
|
|
45
|
+
* is readable content that matched, and `notSearched`, which is labels of
|
|
46
|
+
* scopes that were never opened. `coverage` sits between them naming what ran.
|
|
47
|
+
*/
|
|
48
|
+
export declare const findArtifactsCapability: CapabilityDefinition;
|
|
11
49
|
/**
|
|
12
50
|
* hand one artifact you authored to one other agent.
|
|
13
51
|
*
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { ArtifactsClient } from '../http/ArtifactsClient.js';
|
|
2
2
|
import { ContextGrantsClient } from '../http/ContextGrantsClient.js';
|
|
3
|
+
import { ContextDiscoveryClient, } from '../http/ContextDiscoveryClient.js';
|
|
3
4
|
import { fullCreds } from './types.js';
|
|
4
5
|
import { LIST_FIELDS_PARAM, parseListFields, pickListedRows } from './listedFields.js';
|
|
6
|
+
import { nextCall } from './nextCall.js';
|
|
5
7
|
/**
|
|
6
8
|
* teaching: name the result slot on the success path so an agent finds
|
|
7
9
|
* the right move unaided — worded in each surface's task grammar (SDK
|
|
@@ -295,6 +297,115 @@ export const listArtifactsCapability = {
|
|
|
295
297
|
: listed;
|
|
296
298
|
},
|
|
297
299
|
};
|
|
300
|
+
const UNSEARCHED_NOTE = 'These were NOT searched. You hold no grant on them, so only their label is ' +
|
|
301
|
+
'visible here — no contents, no message counts, nothing that would leak what ' +
|
|
302
|
+
'is inside. Each row carries requestAccess: the grant that would open it, ' +
|
|
303
|
+
'which a human issues. Nothing is read until they do.';
|
|
304
|
+
/**
|
|
305
|
+
* The locked scopes near a search that found nothing.
|
|
306
|
+
*
|
|
307
|
+
* Fetched only when it changes what a caller should conclude, which is why it
|
|
308
|
+
* is not on every response: an agent holding a page of matches does not need
|
|
309
|
+
* the list of doors it has not opened, and an extra round trip on every find
|
|
310
|
+
* would buy nothing. A search that came back empty is exactly where the wrong
|
|
311
|
+
* conclusion gets drawn, so that is where this arrives unasked.
|
|
312
|
+
*
|
|
313
|
+
* Discovery failing is not find failing. It is bounded to orgs the agent holds
|
|
314
|
+
* an active work agreement in and can legitimately return nothing, or refuse;
|
|
315
|
+
* either way the matches already computed are the answer, and the miss is
|
|
316
|
+
* reported as a limitation rather than thrown over the top of them.
|
|
317
|
+
*/
|
|
318
|
+
async function unsearchedContext(env) {
|
|
319
|
+
const creds = fullCreds(env);
|
|
320
|
+
let items;
|
|
321
|
+
try {
|
|
322
|
+
items = await new ContextDiscoveryClient(creds.operatorKey, creds.agentId).discoverGrantable();
|
|
323
|
+
}
|
|
324
|
+
catch (err) {
|
|
325
|
+
return {
|
|
326
|
+
note: 'Could not check for context you cannot read yet: ' +
|
|
327
|
+
`${err instanceof Error ? err.message : String(err)}. ` +
|
|
328
|
+
'Treat this answer as covering only what was searched.',
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
return {
|
|
332
|
+
items: items.map((item) => ({
|
|
333
|
+
...item,
|
|
334
|
+
requestAccess: nextCall(env, 'context_issue_grant', {
|
|
335
|
+
holderId: creds.agentId,
|
|
336
|
+
scopeKind: item.scopeRef.kind,
|
|
337
|
+
scopeId: item.scopeRef.id,
|
|
338
|
+
}, `open ${item.type} "${item.label}" so its contents become searchable`,
|
|
339
|
+
// Not a call the agent runs. A root grant over a scope somebody else
|
|
340
|
+
// owns is human authority, and saying so here is the difference
|
|
341
|
+
// between a continuation and a dead end: the args are complete, the
|
|
342
|
+
// hold names who has to act.
|
|
343
|
+
{ hold: 'human_approval' }),
|
|
344
|
+
})),
|
|
345
|
+
count: items.length,
|
|
346
|
+
note: UNSEARCHED_NOTE,
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
/**
|
|
350
|
+
* search the work you hold, and say what you searched.
|
|
351
|
+
*
|
|
352
|
+
* `artifact_list` answers "what did I write" and the scope reads answer "what
|
|
353
|
+
* is in this container". Neither answers "where is the thing about X", which is
|
|
354
|
+
* the question an agent actually has when it has been handed work that refers
|
|
355
|
+
* to something it did not produce. The server bounds this by the agent's own
|
|
356
|
+
* reach — authorship, readable containers, artifact grants it holds — under the
|
|
357
|
+
* active lane and shared-room containment, so there is no `reach` arm to ask
|
|
358
|
+
* for and the narrowing filters are refused rather than ignored.
|
|
359
|
+
*
|
|
360
|
+
* The response is deliberately two things that never merge: `artifacts`, which
|
|
361
|
+
* is readable content that matched, and `notSearched`, which is labels of
|
|
362
|
+
* scopes that were never opened. `coverage` sits between them naming what ran.
|
|
363
|
+
*/
|
|
364
|
+
export const findArtifactsCapability = {
|
|
365
|
+
key: 'artifact_find',
|
|
366
|
+
names: { sdk: 'artifact_find', mcp: 'ziggs_artifact_find' },
|
|
367
|
+
title: 'Find artifacts you can read',
|
|
368
|
+
descriptions: {
|
|
369
|
+
sdk: 'Search artifacts you can already reach — what you authored, what your readable chats/agreements/tasks hold, and what artifact grants you hold — by words in the name, filename or body. Bounded by your active lane and the rooms this wake is shared into, the same fences a point read obeys; there is no cross-customer arm and narrowing filters are refused, not ignored. Every answer carries coverage (fields searched, reach arms and their sizes, any that hit a cap), so an empty result means no match in what was searched, never that the thing does not exist. When nothing matched you also get notSearched: labels of context you hold no grant on, with the grant that would open each — metadata only, never contents.',
|
|
370
|
+
mcp: 'Search the artifacts YOU can read for words in their name, filename or body. Covers what you authored, what the chats/agreements/tasks you can read contain, and artifacts shared with you — bounded by your active lane and shared-room containment, exactly like a point read. Open a hit with ziggs_context_read (via=artifact:<id>). ' +
|
|
371
|
+
'Read the coverage block before concluding anything: it names the fields searched, the arms that produced candidates and any that hit a cap. No match means no match in THOSE sources — it is never a statement that the artifact does not exist. ' +
|
|
372
|
+
'If nothing matched, notSearched lists context you cannot read yet (same rows as ziggs_context_discover_grantable): labels only, never contents, each with the grant a human would issue to open it. Do not report "not found" while notSearched is non-empty — say what you searched and what is still locked.',
|
|
373
|
+
},
|
|
374
|
+
annotation: 'read-only',
|
|
375
|
+
params: {
|
|
376
|
+
q: {
|
|
377
|
+
type: 'string',
|
|
378
|
+
required: true,
|
|
379
|
+
description: 'Words to match; every term must appear. This is a lexical match on name, filename and extracted text — not a semantic search.',
|
|
380
|
+
},
|
|
381
|
+
limit: { type: 'number', description: 'Page size (server default when omitted)' },
|
|
382
|
+
before: {
|
|
383
|
+
type: 'string',
|
|
384
|
+
description: 'Older-rows cursor: pass the previous page’s coverage.nextCursor back verbatim (opaque).',
|
|
385
|
+
},
|
|
386
|
+
includeUnreadable: {
|
|
387
|
+
type: 'boolean',
|
|
388
|
+
description: 'Also list context you hold no grant on (labels only, never contents). Defaults on when nothing matched, since that is when an empty answer is most likely to be misread; pass true to ask for it alongside matches.',
|
|
389
|
+
},
|
|
390
|
+
fields: LIST_FIELDS_PARAM,
|
|
391
|
+
},
|
|
392
|
+
needsAgentId: true,
|
|
393
|
+
handler: async (args, env) => {
|
|
394
|
+
const creds = fullCreds(env);
|
|
395
|
+
const found = await new ArtifactsClient(creds.operatorKey, creds.agentId, creds.laneId).find(args['q'], {
|
|
396
|
+
limit: typeof args['limit'] === 'number' ? args['limit'] : undefined,
|
|
397
|
+
before: args['before'],
|
|
398
|
+
});
|
|
399
|
+
const fields = parseListFields(args['fields']);
|
|
400
|
+
const shaped = fields
|
|
401
|
+
? { ...found, artifacts: pickListedRows(found.artifacts, fields) }
|
|
402
|
+
: found;
|
|
403
|
+
const asked = args['includeUnreadable'] === true;
|
|
404
|
+
if (!asked && found.artifacts.length > 0)
|
|
405
|
+
return shaped;
|
|
406
|
+
return { ...shaped, notSearched: await unsearchedContext(env) };
|
|
407
|
+
},
|
|
408
|
+
};
|
|
298
409
|
/**
|
|
299
410
|
* hand one artifact you authored to one other agent.
|
|
300
411
|
*
|
|
@@ -352,13 +463,15 @@ export const shareArtifactCapability = {
|
|
|
352
463
|
});
|
|
353
464
|
if (result.status === 'pending_approval') {
|
|
354
465
|
return {
|
|
355
|
-
ok:
|
|
466
|
+
ok: false,
|
|
467
|
+
outcome: 'pending',
|
|
356
468
|
...result,
|
|
357
469
|
note: `Not shared yet — ${result.ownerId ?? 'the owner'} must approve agreement ${result.agreementId}.`,
|
|
358
470
|
};
|
|
359
471
|
}
|
|
360
472
|
return {
|
|
361
473
|
ok: true,
|
|
474
|
+
outcome: 'ok',
|
|
362
475
|
...result,
|
|
363
476
|
note: `${holderId} can now read artifact ${artifactId} with via=artifact:${artifactId}.`,
|
|
364
477
|
};
|
|
@@ -644,6 +757,7 @@ export const reextractArtifactCapability = {
|
|
|
644
757
|
export const ARTIFACT_CAPABILITIES = [
|
|
645
758
|
recordArtifactCapability,
|
|
646
759
|
listArtifactsCapability,
|
|
760
|
+
findArtifactsCapability,
|
|
647
761
|
shareArtifactCapability,
|
|
648
762
|
attachArtifactCapability,
|
|
649
763
|
uploadArtifactUrlCapability,
|
|
@@ -20,4 +20,6 @@ export declare const contextReadCapability: CapabilityDefinition;
|
|
|
20
20
|
export declare const contextExpandReachCapability: CapabilityDefinition;
|
|
21
21
|
export declare const contextDiscoverGrantableCapability: CapabilityDefinition;
|
|
22
22
|
export declare const contextDelegateCapability: CapabilityDefinition;
|
|
23
|
+
export declare const openCapability: CapabilityDefinition;
|
|
24
|
+
export declare const accessExplainCapability: CapabilityDefinition;
|
|
23
25
|
export declare const CONTEXT_CAPABILITIES: CapabilityDefinition[];
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { ContextReadClient, CONTEXT_READ_TYPES, CONTEXT_READ_VIA, parseVia, viaHint, } from '../http/ContextReadClient.js';
|
|
2
|
+
import { ContextOpenClient } from '../http/ContextOpenClient.js';
|
|
2
3
|
import { ContextGrantsClient, } from '../http/ContextGrantsClient.js';
|
|
3
4
|
import { ContextDiscoveryClient } from '../http/ContextDiscoveryClient.js';
|
|
4
5
|
import { grantCaveat, CONTEXT_GRANT_SCOPE_KINDS, } from '../http/grants.js';
|
|
@@ -225,6 +226,7 @@ export const contextDelegateCapability = {
|
|
|
225
226
|
if (result.status === 'pending_approval') {
|
|
226
227
|
return {
|
|
227
228
|
status: 'pending_approval',
|
|
229
|
+
outcome: 'pending',
|
|
228
230
|
message: "This grant's original owner must approve sharing it. A request was opened for them — surface it to the human; nothing is granted yet.",
|
|
229
231
|
parentGrantId: args['parentGrantId'],
|
|
230
232
|
agreementId: result.agreementId,
|
|
@@ -234,14 +236,95 @@ export const contextDelegateCapability = {
|
|
|
234
236
|
const grant = result.grant;
|
|
235
237
|
return {
|
|
236
238
|
status: 'delegated',
|
|
239
|
+
outcome: 'ok',
|
|
237
240
|
parentGrantId: args['parentGrantId'],
|
|
238
241
|
grant,
|
|
239
242
|
bounds: contextBounds(grant),
|
|
240
243
|
};
|
|
241
244
|
},
|
|
242
245
|
};
|
|
246
|
+
const OPEN_ID_PARAMS = {
|
|
247
|
+
artifactId: {
|
|
248
|
+
type: 'string',
|
|
249
|
+
description: 'Artifact id from a prior response. Pass exactly one id field.',
|
|
250
|
+
},
|
|
251
|
+
chatId: {
|
|
252
|
+
type: 'string',
|
|
253
|
+
description: 'Chat id from a prior response. Pass exactly one id field.',
|
|
254
|
+
},
|
|
255
|
+
taskId: {
|
|
256
|
+
type: 'string',
|
|
257
|
+
description: 'Task id from a prior response. Pass exactly one id field.',
|
|
258
|
+
},
|
|
259
|
+
agreementId: {
|
|
260
|
+
type: 'string',
|
|
261
|
+
description: 'Agreement id from a prior response. Pass exactly one id field.',
|
|
262
|
+
},
|
|
263
|
+
};
|
|
264
|
+
function openBodyFromArgs(args) {
|
|
265
|
+
const body = {};
|
|
266
|
+
for (const key of ['artifactId', 'chatId', 'taskId', 'agreementId']) {
|
|
267
|
+
const value = args[key];
|
|
268
|
+
if (typeof value === 'string' && value.trim())
|
|
269
|
+
body[key] = value.trim();
|
|
270
|
+
}
|
|
271
|
+
if (typeof args['cursor'] === 'string')
|
|
272
|
+
body.cursor = args['cursor'];
|
|
273
|
+
if (typeof args['after'] === 'string')
|
|
274
|
+
body.after = args['after'];
|
|
275
|
+
if (typeof args['limit'] === 'number')
|
|
276
|
+
body.limit = args['limit'];
|
|
277
|
+
return body;
|
|
278
|
+
}
|
|
279
|
+
function presentOpenResult(result) {
|
|
280
|
+
// Never attach a ready write. An unreadable row already carries
|
|
281
|
+
// access.continuation (owner-decision); this call must not execute it.
|
|
282
|
+
return { ...result, actions: [] };
|
|
283
|
+
}
|
|
284
|
+
export const openCapability = {
|
|
285
|
+
key: 'open',
|
|
286
|
+
names: { sdk: 'open', mcp: 'ziggs_open' },
|
|
287
|
+
title: 'Open a returned resource',
|
|
288
|
+
descriptions: {
|
|
289
|
+
sdk: 'Open an artifact, chat, task, or agreement you were given an id for. Pass exactly one of artifactId, chatId, taskId, agreementId. Do not pick a grant id or reconstruct type/via — the server resolves the read path and rechecks authorization every time. Read-only: this never requests access or records an approval. Reading is not reply, share, delegate, approve, or original-file download. Extraction pending/failed is not an empty document.',
|
|
290
|
+
mcp: 'Open an artifact, chat, task, or agreement from an ordinary returned id (inbox, find, task result). Pass exactly one of artifactId, chatId, taskId, agreementId. Do not call ziggs_grant_list first, do not pick a grant id, and do not reconstruct type/via — the server resolves the path and rechecks every call. A saved id is not authority. Read-only: never requests access or approves. Hidden and unknown look the same; a discoverable-but-unreadable row names an owner-decision, which this call does not execute. Text access is not a file download. Extraction pending/failed is not an empty document.',
|
|
291
|
+
},
|
|
292
|
+
annotation: 'read-only',
|
|
293
|
+
params: {
|
|
294
|
+
...OPEN_ID_PARAMS,
|
|
295
|
+
cursor: { type: 'string', description: 'Opaque cursor from a prior open page' },
|
|
296
|
+
after: { type: 'string', description: 'ISO timestamp for a forward-delta' },
|
|
297
|
+
limit: { type: 'number', description: 'Page size' },
|
|
298
|
+
},
|
|
299
|
+
needsAgentId: true,
|
|
300
|
+
handler: async (args, env) => {
|
|
301
|
+
const creds = fullCreds(env);
|
|
302
|
+
const client = new ContextOpenClient(creds.operatorKey, creds.agentId, env.baseUrl, creds.laneId);
|
|
303
|
+
return presentOpenResult(await client.open(openBodyFromArgs(args)));
|
|
304
|
+
},
|
|
305
|
+
};
|
|
306
|
+
export const accessExplainCapability = {
|
|
307
|
+
key: 'access_explain',
|
|
308
|
+
names: { sdk: 'access_explain', mcp: 'ziggs_access_explain' },
|
|
309
|
+
title: 'Explain access to a resource',
|
|
310
|
+
descriptions: {
|
|
311
|
+
sdk: 'Read-only access explanation for an ordinary returned id. Same one-of artifactId/chatId/taskId/agreementId as open. Says whether you can read, and that reading does not imply reply, share, delegate, approve, or download. Does not return content and does not request or approve anything.',
|
|
312
|
+
mcp: 'Read-only access explanation for an ordinary returned id (exactly one of artifactId, chatId, taskId, agreementId). No grant id. Rechecks authorization. Names the owner-decision route when you cannot read a discoverable resource; this call never executes that request. Hidden and unknown reveal no labels, owners, excerpts, or counts.',
|
|
313
|
+
},
|
|
314
|
+
annotation: 'read-only',
|
|
315
|
+
params: OPEN_ID_PARAMS,
|
|
316
|
+
needsAgentId: true,
|
|
317
|
+
handler: async (args, env) => {
|
|
318
|
+
const creds = fullCreds(env);
|
|
319
|
+
const client = new ContextOpenClient(creds.operatorKey, creds.agentId, env.baseUrl, creds.laneId);
|
|
320
|
+
const result = await client.explain(openBodyFromArgs(args));
|
|
321
|
+
return presentOpenResult(result);
|
|
322
|
+
},
|
|
323
|
+
};
|
|
243
324
|
export const CONTEXT_CAPABILITIES = [
|
|
244
325
|
contextReadCapability,
|
|
326
|
+
openCapability,
|
|
327
|
+
accessExplainCapability,
|
|
245
328
|
contextDelegateCapability,
|
|
246
329
|
contextExpandReachCapability,
|
|
247
330
|
contextDiscoverGrantableCapability,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { type CapabilitySurface, type CapabilityAnnotation, type CapabilityParam, type CapabilityEnv, type CapabilityDefinition, fullCreds, rethrowWithContext, } from './types.js';
|
|
2
|
-
export { nextCall, peerPrincipalId, type NextCall, } from './nextCall.js';
|
|
2
|
+
export { nextCall, nextCallsFromUntrustedContent, isStaleAdvertisedAction, peerPrincipalId, isPersonaFace, workContextFromEnv, type NextCall, type NextCallOutcome, type NextCallEffects, type NextCallHold, type NextCallOptions, type WorkContext, } from './nextCall.js';
|
|
3
3
|
export { AGREEMENT_VERB_CAPABILITIES, agreementBuyCapability, agreementBidCapability, agreementBrokerCapability, agreementRequestCapability, agreementOfferCapability, agreementHandoffCapability, } from './agreementVerbs.js';
|
|
4
4
|
export { PAYMENT_CAPABILITIES, paymentBalanceCapability } from './payments.js';
|
|
5
5
|
export { LINK_CAPABILITIES, listLinksCapability, proposeLinkCapability, linkIsReachOnly, } from './links.js';
|
|
@@ -10,8 +10,8 @@ export { TASK_CAPABILITIES, listTasksCapability } from './tasks.js';
|
|
|
10
10
|
export { AGREEMENT_PROPOSE_PROVIDER_ID_DESCRIPTION } from './proposeProviderId.js';
|
|
11
11
|
export { MARKETPLACE_CAPABILITIES, marketplaceViewCapability } from './marketplace.js';
|
|
12
12
|
export { GRANTS_CAPABILITIES, listGrantsCapability } from './grants.js';
|
|
13
|
-
export { CONTEXT_CAPABILITIES, contextReadCapability, contextDelegateCapability, contextExpandReachCapability, contextDiscoverGrantableCapability, contextBounds, resolveOrgScopeId, } from './context.js';
|
|
13
|
+
export { CONTEXT_CAPABILITIES, contextReadCapability, openCapability, accessExplainCapability, contextDelegateCapability, contextExpandReachCapability, contextDiscoverGrantableCapability, contextBounds, resolveOrgScopeId, } from './context.js';
|
|
14
14
|
export { CONNECTION_CAPABILITIES, connectionProxyCapability, requestConnectionCapability, } from './connections.js';
|
|
15
15
|
export { DISCOVERY_CAPABILITIES, agentSearchCapability, agentGetCapability, } from './discovery.js';
|
|
16
|
-
export { ARTIFACT_CAPABILITIES, recordArtifactCapability, listArtifactsCapability, shareArtifactCapability, attachArtifactCapability, uploadArtifactUrlCapability, completeArtifactFileCapability, downloadArtifactCapability, reextractArtifactCapability, } from './artifacts.js';
|
|
16
|
+
export { ARTIFACT_CAPABILITIES, recordArtifactCapability, listArtifactsCapability, findArtifactsCapability, type UnsearchedContext, shareArtifactCapability, attachArtifactCapability, uploadArtifactUrlCapability, completeArtifactFileCapability, downloadArtifactCapability, reextractArtifactCapability, } from './artifacts.js';
|
|
17
17
|
export { CHAT_CAPABILITIES, openConversationCapability } from './chat.js';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { fullCreds, rethrowWithContext, } from './types.js';
|
|
2
|
-
export { nextCall, peerPrincipalId, } from './nextCall.js';
|
|
2
|
+
export { nextCall, nextCallsFromUntrustedContent, isStaleAdvertisedAction, peerPrincipalId, isPersonaFace, workContextFromEnv, } from './nextCall.js';
|
|
3
3
|
export { AGREEMENT_VERB_CAPABILITIES, agreementBuyCapability, agreementBidCapability, agreementBrokerCapability, agreementRequestCapability, agreementOfferCapability, agreementHandoffCapability, } from './agreementVerbs.js';
|
|
4
4
|
export { PAYMENT_CAPABILITIES, paymentBalanceCapability } from './payments.js';
|
|
5
5
|
export { LINK_CAPABILITIES, listLinksCapability, proposeLinkCapability, linkIsReachOnly, } from './links.js';
|
|
@@ -10,8 +10,8 @@ export { TASK_CAPABILITIES, listTasksCapability } from './tasks.js';
|
|
|
10
10
|
export { AGREEMENT_PROPOSE_PROVIDER_ID_DESCRIPTION } from './proposeProviderId.js';
|
|
11
11
|
export { MARKETPLACE_CAPABILITIES, marketplaceViewCapability } from './marketplace.js';
|
|
12
12
|
export { GRANTS_CAPABILITIES, listGrantsCapability } from './grants.js';
|
|
13
|
-
export { CONTEXT_CAPABILITIES, contextReadCapability, contextDelegateCapability, contextExpandReachCapability, contextDiscoverGrantableCapability, contextBounds, resolveOrgScopeId, } from './context.js';
|
|
13
|
+
export { CONTEXT_CAPABILITIES, contextReadCapability, openCapability, accessExplainCapability, contextDelegateCapability, contextExpandReachCapability, contextDiscoverGrantableCapability, contextBounds, resolveOrgScopeId, } from './context.js';
|
|
14
14
|
export { CONNECTION_CAPABILITIES, connectionProxyCapability, requestConnectionCapability, } from './connections.js';
|
|
15
15
|
export { DISCOVERY_CAPABILITIES, agentSearchCapability, agentGetCapability, } from './discovery.js';
|
|
16
|
-
export { ARTIFACT_CAPABILITIES, recordArtifactCapability, listArtifactsCapability, shareArtifactCapability, attachArtifactCapability, uploadArtifactUrlCapability, completeArtifactFileCapability, downloadArtifactCapability, reextractArtifactCapability, } from './artifacts.js';
|
|
16
|
+
export { ARTIFACT_CAPABILITIES, recordArtifactCapability, listArtifactsCapability, findArtifactsCapability, shareArtifactCapability, attachArtifactCapability, uploadArtifactUrlCapability, completeArtifactFileCapability, downloadArtifactCapability, reextractArtifactCapability, } from './artifacts.js';
|
|
17
17
|
export { CHAT_CAPABILITIES, openConversationCapability } from './chat.js';
|
|
@@ -79,6 +79,7 @@ export const redeemIntroductionCapability = {
|
|
|
79
79
|
const staged = intro.outcome === 'link_pending';
|
|
80
80
|
return {
|
|
81
81
|
introduction: intro,
|
|
82
|
+
outcome: staged ? 'pending' : 'ok',
|
|
82
83
|
message: staged
|
|
83
84
|
? `Redeemed. A pending link with org ${intro.from.orgId} (${intro.from.agentId ?? 'no agent id'}) is staged (${intro.agreementId}). The display name they claimed is self-chosen and unverified. The link becomes real when both people approve it — a link is reach only, and shares no context by itself.`
|
|
84
85
|
: intro.outcome === 'already_teammates'
|
|
@@ -88,6 +89,9 @@ export const redeemIntroductionCapability = {
|
|
|
88
89
|
// relationship exists, so the next move is simply to talk. Leaving the
|
|
89
90
|
// plan empty there read as "nothing to do" on the branch a returning
|
|
90
91
|
// counterparty is most likely to land on.
|
|
92
|
+
//
|
|
93
|
+
// A staged redeem is not a live hire: do not advertise a ready chat_open
|
|
94
|
+
// or a ready agreement_respond the agent can run. The human signs.
|
|
91
95
|
readPlan: !staged
|
|
92
96
|
? intro.counterparty?.agentId
|
|
93
97
|
? [
|
|
@@ -95,16 +99,7 @@ export const redeemIntroductionCapability = {
|
|
|
95
99
|
]
|
|
96
100
|
: []
|
|
97
101
|
: [
|
|
98
|
-
nextCall(env, 'agreement_respond', intro.agreementId ? { agreementId: intro.agreementId } : undefined, 'the link is pending — your human must approve it; do not sign on their behalf'),
|
|
99
|
-
// The minter's AGENT, not their principal. A link's party principal
|
|
100
|
-
// can be a persona face, which the chat rail refuses as an address
|
|
101
|
-
// ("a face is display state and names no single party") — so the
|
|
102
|
-
// door to that person is the agent that carried the introduction.
|
|
103
|
-
...(intro.from.agentId
|
|
104
|
-
? [
|
|
105
|
-
nextCall(env, 'chat_open', { participantId: intro.from.agentId }, 'once the link is live, this is the door to the agent that introduced itself'),
|
|
106
|
-
]
|
|
107
|
-
: []),
|
|
102
|
+
nextCall(env, 'agreement_respond', intro.agreementId ? { agreementId: intro.agreementId } : undefined, 'the link is pending — your human must approve it; do not sign on their behalf', { hold: 'human_approval' }),
|
|
108
103
|
],
|
|
109
104
|
};
|
|
110
105
|
},
|
|
@@ -1,28 +1,72 @@
|
|
|
1
1
|
import type { AgreementParties } from '../types.js';
|
|
2
|
-
import type { CapabilityEnv } from './types.js';
|
|
2
|
+
import type { CapabilityDefinition, CapabilityEnv } from './types.js';
|
|
3
3
|
/**
|
|
4
|
-
* One
|
|
4
|
+
* One next step: the tool the calling surface registers, any args already
|
|
5
|
+
* known, and why.
|
|
5
6
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* response body it just received.
|
|
7
|
+
* Ready means a caller can run it verbatim. Incomplete means a required
|
|
8
|
+
* argument is missing or an address field is not an address — the suggestion
|
|
9
|
+
* still names the tool, and `missing` says what has to be filled first.
|
|
10
10
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* still right when the next move is not a call at all (a human has to decide
|
|
15
|
-
* something); it is wrong when the call and its arguments are both already
|
|
16
|
-
* known here.
|
|
11
|
+
* `http` is the same invocation on the published HTTP surface, bound from the
|
|
12
|
+
* capability key rather than a second implementation. Profile is not a field
|
|
13
|
+
* here: assistant vs worker is not a transport.
|
|
17
14
|
*/
|
|
18
15
|
export interface NextCall {
|
|
19
16
|
/** Registered tool name on the calling surface. */
|
|
20
17
|
tool: string;
|
|
21
18
|
/** Arguments, filled in from what the response already carries. */
|
|
22
19
|
args?: Record<string, unknown>;
|
|
23
|
-
/** One clause: what running this achieves. */
|
|
20
|
+
/** One clause: what running this achieves, or what is still missing. */
|
|
24
21
|
why: string;
|
|
22
|
+
/** True only when every required argument is present and addressable. */
|
|
23
|
+
ready: boolean;
|
|
24
|
+
/** Required argument names that are absent or not an address. */
|
|
25
|
+
missing?: readonly string[];
|
|
26
|
+
/** Published HTTP method/path for the same capability, when we have one. */
|
|
27
|
+
http?: {
|
|
28
|
+
method: string;
|
|
29
|
+
path: string;
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* Whether this step is a finished action, a parked human decision, a
|
|
33
|
+
* half-filled suggestion, or something this surface cannot run.
|
|
34
|
+
*/
|
|
35
|
+
outcome: NextCallOutcome;
|
|
36
|
+
/** Material effects of running this, when the verb has any. */
|
|
37
|
+
effects?: NextCallEffects;
|
|
38
|
+
/** Who would run it, from the env — never a caller-chosen principal. */
|
|
39
|
+
workContext?: WorkContext;
|
|
40
|
+
/** Why a complete-looking call is still not runnable. */
|
|
41
|
+
hold?: NextCallHold;
|
|
25
42
|
}
|
|
43
|
+
export type NextCallOutcome = 'ok' | 'pending' | 'partial' | 'unavailable';
|
|
44
|
+
export type NextCallHold = 'human_approval' | 'relationship';
|
|
45
|
+
export interface NextCallEffects {
|
|
46
|
+
disclosure?: string;
|
|
47
|
+
commitment?: string;
|
|
48
|
+
metering?: string;
|
|
49
|
+
relationship?: string;
|
|
50
|
+
}
|
|
51
|
+
export interface WorkContext {
|
|
52
|
+
actingAgentId?: string;
|
|
53
|
+
laneId?: string;
|
|
54
|
+
}
|
|
55
|
+
export interface NextCallOptions {
|
|
56
|
+
/** Live capability schema — required args come from here when present. */
|
|
57
|
+
definition?: Pick<CapabilityDefinition, 'params'>;
|
|
58
|
+
outcome?: NextCallOutcome;
|
|
59
|
+
effects?: NextCallEffects;
|
|
60
|
+
/**
|
|
61
|
+
* Args may be complete but the caller must not run it: a human signs, or
|
|
62
|
+
* a relationship is not live yet.
|
|
63
|
+
*/
|
|
64
|
+
hold?: NextCallHold;
|
|
65
|
+
}
|
|
66
|
+
/** A persona face (`psn_…`) is display state, not a message address. */
|
|
67
|
+
export declare function isPersonaFace(id: unknown): boolean;
|
|
68
|
+
/** Acting agent and lane from the env. Never a caller-chosen principal. */
|
|
69
|
+
export declare function workContextFromEnv(env: CapabilityEnv): WorkContext | undefined;
|
|
26
70
|
/**
|
|
27
71
|
* Build a next step naming the tool as the calling surface registers it.
|
|
28
72
|
*
|
|
@@ -31,7 +75,21 @@ export interface NextCall {
|
|
|
31
75
|
* the 31 capability definitions follows that one rule, so the key is enough and
|
|
32
76
|
* a caller cannot accidentally emit a name the surface does not have.
|
|
33
77
|
*/
|
|
34
|
-
export declare function nextCall(env: CapabilityEnv, capabilityKey: string, args: Record<string, unknown> | undefined, why: string): NextCall;
|
|
78
|
+
export declare function nextCall(env: CapabilityEnv, capabilityKey: string, args: Record<string, unknown> | undefined, why: string, opts?: NextCallOptions): NextCall;
|
|
79
|
+
/**
|
|
80
|
+
* E23: messages, artifacts, and agreement prose are data. They cannot mint
|
|
81
|
+
* a server action. The only legal parse is "none".
|
|
82
|
+
*/
|
|
83
|
+
export declare function nextCallsFromUntrustedContent(_content: unknown): NextCall[];
|
|
84
|
+
/**
|
|
85
|
+
* E22: a next call advertised against an earlier snapshot is stale when the
|
|
86
|
+
* row is no longer in the state that action assumed. Execution still has to
|
|
87
|
+
* hit the existing precondition (claim 400, respond refuse) — this is the
|
|
88
|
+
* client-side invalidation so a caller does not treat the suggestion as live.
|
|
89
|
+
*/
|
|
90
|
+
export declare function isStaleAdvertisedAction(advertised: Pick<NextCall, 'tool' | 'args'>, current: {
|
|
91
|
+
status?: string | null;
|
|
92
|
+
}): boolean;
|
|
35
93
|
/**
|
|
36
94
|
* The other PRINCIPAL in a two-party agreement, from the perspective of
|
|
37
95
|
* `selfId`.
|