@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.
@@ -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: 'claimed',
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: true,
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
  },
@@ -177,6 +177,7 @@ export const proposeLinkCapability = {
177
177
  // in both directions, because none of them could ask.
178
178
  return {
179
179
  status: 'sent',
180
+ outcome: 'pending',
180
181
  shareUrl,
181
182
  agreementId,
182
183
  message: to
@@ -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 runnable next step: a tool name, pre-filled arguments, and why.
4
+ * One next step: the tool the calling surface registers, any args already
5
+ * known, and why.
5
6
  *
6
- * The inbox and the context read already answer this way, and it is the part of
7
- * those tools that works best: a caller runs the entries verbatim instead of
8
- * parsing a paragraph for a tool name and then digging the ids out of the
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
- * Everywhere else said the same thing in prose, under two other field names, so
12
- * an agent had to read English to find out that "open a chat with the peer"
13
- * meant `chat_open` with a `participantId` it had to locate itself. Prose is
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`.