@ziggs-ai/api-client 0.20.0 → 0.21.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.
@@ -22,3 +22,10 @@ export declare const agreementRequestCapability: CapabilityDefinition;
22
22
  export declare const agreementOfferCapability: CapabilityDefinition;
23
23
  export declare const agreementHandoffCapability: CapabilityDefinition;
24
24
  export declare const AGREEMENT_VERB_CAPABILITIES: CapabilityDefinition[];
25
+ /**
26
+ * Subcontract sits next to the propose verbs, not inside them: the parent
27
+ * already exists, and the conversation is optional. The server uses the
28
+ * parent's own chat when none is named. That list of required arguments is
29
+ * this definition — nextCall, MCP, and the SDK tool all read it.
30
+ */
31
+ export declare const agreementSubcontractCapability: CapabilityDefinition;
@@ -1,4 +1,4 @@
1
- import { getAgreement, proposeBroadcast, proposeDirectTo, } from '../http/AgreementClient.js';
1
+ import { delegateAgreement, getAgreement, proposeBroadcast, proposeDirectTo, } from '../http/AgreementClient.js';
2
2
  import { publishOffer } from '../http/MarketplaceClient.js';
3
3
  import { fullCreds } from './types.js';
4
4
  import { nextCall } from './nextCall.js';
@@ -382,3 +382,86 @@ export const AGREEMENT_VERB_CAPABILITIES = [
382
382
  agreementOfferCapability,
383
383
  agreementHandoffCapability,
384
384
  ];
385
+ /**
386
+ * Subcontract sits next to the propose verbs, not inside them: the parent
387
+ * already exists, and the conversation is optional. The server uses the
388
+ * parent's own chat when none is named. That list of required arguments is
389
+ * this definition — nextCall, MCP, and the SDK tool all read it.
390
+ */
391
+ export const agreementSubcontractCapability = {
392
+ key: 'agreement_subcontract',
393
+ names: { sdk: 'agreement_subcontract', mcp: 'ziggs_agreement_subcontract' },
394
+ title: 'Subcontract part of your work',
395
+ descriptions: {
396
+ sdk: 'Delegate a slice of an active parent agreement to another agent. Name the parent, the worker, and the slice. The worker must approve — never impersonated. A chat is optional: omit it and the slice lands in the parent agreement\'s own room.',
397
+ mcp: 'Delegate a slice of an active parent agreement to another agent. Name the parent, the worker, and the slice. The worker must approve — never impersonated. A chat is optional: omit it and the slice lands in the parent agreement\'s own room. Spawn tasks for the worker under the sub-agreement once it is active.',
398
+ },
399
+ annotation: 'write',
400
+ params: {
401
+ parentAgreementId: {
402
+ type: 'string',
403
+ required: true,
404
+ description: 'The active agreement you are delegating under',
405
+ },
406
+ executorId: {
407
+ type: 'string',
408
+ required: true,
409
+ description: 'Agent doing the delegated work',
410
+ },
411
+ description: {
412
+ type: 'string',
413
+ required: true,
414
+ description: 'What the sub-agreement covers',
415
+ },
416
+ chatId: {
417
+ type: 'string',
418
+ description: "Chat the subcontract is coordinated in. Omit it and the server uses the parent agreement's own chat — which is where a slice with no conversation of its own belongs.",
419
+ },
420
+ price: {
421
+ type: 'number',
422
+ description: 'Price in POINTS, as an integer of hundredths — 500 means ϟ5.00. Recording a price here does not itself move any.',
423
+ },
424
+ lifecycle: {
425
+ type: 'string',
426
+ enum: ['open', 'time-bound', 'count-bound'],
427
+ description: "Usually inferred: expiresAt → 'time-bound', maxExecutions → 'count-bound', neither → 'open' (standing).",
428
+ },
429
+ expiresAt: { type: 'string' },
430
+ maxExecutions: { type: 'number' },
431
+ agreementDescription: { type: 'string' },
432
+ payerId: {
433
+ type: 'string',
434
+ description: 'Who pays for the subcontracted work. Defaults to the delegating side.',
435
+ },
436
+ },
437
+ needsAgentId: true,
438
+ handler: async (args, env) => {
439
+ const chatId = args['chatId'];
440
+ const agreement = await delegateAgreement({
441
+ parentAgreementId: args['parentAgreementId'],
442
+ executorId: args['executorId'],
443
+ description: args['description'],
444
+ ...(typeof chatId === 'string' && chatId.trim()
445
+ ? { chatId: chatId.trim() }
446
+ : {}),
447
+ ...(args['price'] === undefined ? {} : { price: args['price'] }),
448
+ ...(args['lifecycle'] === undefined
449
+ ? {}
450
+ : { lifecycle: args['lifecycle'] }),
451
+ ...(args['expiresAt'] === undefined
452
+ ? {}
453
+ : { expiresAt: args['expiresAt'] }),
454
+ ...(args['maxExecutions'] === undefined
455
+ ? {}
456
+ : { maxExecutions: args['maxExecutions'] }),
457
+ ...(args['agreementDescription'] === undefined
458
+ ? {}
459
+ : { agreementDescription: args['agreementDescription'] }),
460
+ ...(args['payerId'] === undefined
461
+ ? {}
462
+ : { payerId: args['payerId'] }),
463
+ }, fullCreds(env));
464
+ return { agreement };
465
+ },
466
+ sdkOptions: { isAgreementCreation: true },
467
+ };
@@ -18,7 +18,10 @@ function reportingHint(env, contentType, taskId) {
18
18
  ? `Recorded as a task-bound result artifact. Close the task by setting its terminal result with ${close}.`
19
19
  : `Recorded as a result artifact, but not bound to a task — pass taskId to bind it, then close the task with ${close}.`;
20
20
  }
21
- return `Reporting finished work? Record it with contentType=result bound to the task (taskId), then close the task with ${close} — chat messages are conversation only.`;
21
+ // The record and the delivery are different acts. A chat message
22
+ // is not where the next agent collects the work, and saying that as "chat is
23
+ // conversation only" read as a ban on telling the person who asked.
24
+ return `Reporting finished work? Record it with contentType=result bound to the task (taskId), then close the task with ${close} — that record is what the next agent reads. Telling the person who asked is a separate chat message, and still worth sending.`;
22
25
  }
23
26
  /** Inline body cap + escape hatch, shared by SDK/MCP descriptions. */
24
27
  const ARTIFACT_RECORD_INLINE_CAP = 'Inline text max 50000 characters. Over that: pass the body as a file ' +
@@ -269,8 +272,8 @@ export const listArtifactsCapability = {
269
272
  names: { sdk: 'artifact_list', mcp: 'ziggs_artifact_list' },
270
273
  title: 'List artifacts you wrote',
271
274
  descriptions: {
272
- sdk: 'List artifacts you authored, in any scope or none — including free-standing ones no scope read can reach. Forward-delta with `after`.',
273
- mcp: 'List artifacts YOU authored, across every scope and none. Use this to find something you ' +
275
+ sdk: 'List business artifacts you authored, in any scope or none — including free-standing ones no scope read can reach. Tool-operation and thought traces are excluded. Forward-delta with `after`.',
276
+ mcp: 'List business artifacts YOU authored, across every scope and none; tool-operation and thought traces are excluded. Use this to find something you ' +
274
277
  'recorded free-standing (no chat/agreement/task), which the scope reads cannot return. ' +
275
278
  'To read an artifact someone shared WITH you, use ziggs_context_read with via=artifact:<id>; ' +
276
279
  'to see what has been shared with you, use ziggs_grant_list with scopeKind=artifact.',
@@ -299,8 +302,27 @@ export const listArtifactsCapability = {
299
302
  };
300
303
  const UNSEARCHED_NOTE = 'These were NOT searched. You hold no grant on them, so only their label is ' +
301
304
  '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.';
305
+ 'is inside. Each row carries requestAccess: an access explanation, not a ' +
306
+ 'root grant. A human issues the grant; this never widens the scope to the ' +
307
+ 'chat, agreement, or org around one artifact.';
308
+ const OPEN_ID_FIELD = {
309
+ chat: 'chatId',
310
+ agreement: 'agreementId',
311
+ artifact: 'artifactId',
312
+ task: 'taskId',
313
+ };
314
+ function pendingAccessReceipt(result, extra) {
315
+ return {
316
+ ...extra,
317
+ ok: false,
318
+ outcome: 'pending',
319
+ status: 'pending_approval',
320
+ state: 'requested',
321
+ recoverable: true,
322
+ agreementId: result.agreementId,
323
+ ownerId: result.ownerId,
324
+ };
325
+ }
304
326
  /**
305
327
  * The locked scopes near a search that found nothing.
306
328
  *
@@ -329,19 +351,18 @@ async function unsearchedContext(env) {
329
351
  };
330
352
  }
331
353
  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
- })),
354
+ items: items.map((item) => {
355
+ const idField = OPEN_ID_FIELD[item.scopeRef.kind];
356
+ return {
357
+ ...item,
358
+ requestAccess: nextCall(env, 'access_explain', idField ? { [idField]: item.scopeRef.id } : undefined, idField
359
+ ? `explain access to locked ${item.type} "${item.label}" — a human issues a root grant; this call does not request or widen`
360
+ : `locked ${item.type} "${item.label}" cannot be requested from this surface — a human issues a root grant; this never widens the scope`, {
361
+ hold: 'human_approval',
362
+ ...(idField ? {} : { outcome: 'unavailable' }),
363
+ }),
364
+ };
365
+ }),
345
366
  count: items.length,
346
367
  note: UNSEARCHED_NOTE,
347
368
  };
@@ -366,10 +387,10 @@ export const findArtifactsCapability = {
366
387
  names: { sdk: 'artifact_find', mcp: 'ziggs_artifact_find' },
367
388
  title: 'Find artifacts you can read',
368
389
  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>). ' +
390
+ sdk: 'Search business artifacts you can already reach (excluding tool-operation and thought traces) — 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, each with an access explanation — a human issues a root grant; this never widens. Metadata only, never contents.',
391
+ mcp: 'Search the business artifacts YOU can read for words in their name, filename or body; tool-operation and thought traces are excluded. 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
392
  '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.',
393
+ 'If nothing matched, notSearched lists context you cannot read yet (same rows as ziggs_context_discover_grantable): labels only, never contents, each with an access explanation. A human issues a root grant; do not treat that as a share or a wider container grant. Do not report "not found" while notSearched is non-empty — say what you searched and what is still locked.',
373
394
  },
374
395
  annotation: 'read-only',
375
396
  params: {
@@ -446,6 +467,10 @@ export const shareArtifactCapability = {
446
467
  type: 'string',
447
468
  description: 'Chat to surface the approval request in, when the receiver’s owner has to approve',
448
469
  },
470
+ idempotencyKey: {
471
+ type: 'string',
472
+ description: 'Replay the same share. Same key and payload recover the original request; the same key with a different payload is a conflict, not a second share.',
473
+ },
449
474
  },
450
475
  needsAgentId: true,
451
476
  handler: async (args, env) => {
@@ -460,14 +485,16 @@ export const shareArtifactCapability = {
460
485
  holderId,
461
486
  expiresAt: args['expiresAt'],
462
487
  chatId: args['chatId'],
488
+ }, {
489
+ idempotencyKey: typeof args['idempotencyKey'] === 'string'
490
+ ? args['idempotencyKey']
491
+ : undefined,
463
492
  });
464
493
  if (result.status === 'pending_approval') {
465
- return {
466
- ok: false,
467
- outcome: 'pending',
494
+ return pendingAccessReceipt(result, {
468
495
  ...result,
469
- note: `Not shared yet — ${result.ownerId ?? 'the owner'} must approve agreement ${result.agreementId}.`,
470
- };
496
+ note: `Requested, not granted — ${result.ownerId ?? 'the owner'} must approve agreement ${result.agreementId}. Recover that id; do not open a second card.`,
497
+ });
471
498
  }
472
499
  return {
473
500
  ok: true,
@@ -1,4 +1,34 @@
1
- import { type CapabilityDefinition } from './types.js';
1
+ import { type SendChatMessageResult } from '../http/ChatClient.js';
2
+ import { type CapabilityDefinition, type CapabilityEnv } from './types.js';
3
+ import { type WorkContext } from './nextCall.js';
4
+ export interface SendPresentation {
5
+ ok: true;
6
+ chatId: string;
7
+ messageId: string;
8
+ /** What the server did with this send, stated so it cannot be over-read. */
9
+ delivered: string;
10
+ /** Who it was addressed to, exactly as the server resolved it. */
11
+ to?: {
12
+ id: string;
13
+ type: string;
14
+ };
15
+ workContext?: WorkContext;
16
+ }
17
+ /**
18
+ * Present a send as what it is: a message that now exists, addressed to
19
+ * somebody, which may or may not wake anyone.
20
+ *
21
+ * An agent that sent an answer and read `success: true` had every reason to
22
+ * report the answer delivered. Three different things hide behind that word.
23
+ * The message exists — that much is certain. Who it reached is the server's
24
+ * decision, not the caller's, because an omitted recipient may be inferred.
25
+ * Mailbox routing is a separate asynchronous decision, including coverage by
26
+ * an assistant when the addressee is a person.
27
+ *
28
+ * A server that does not report the destination gets one honest line about the
29
+ * message existing, and nothing invented about where it went.
30
+ */
31
+ export declare function presentSendResult(result: SendChatMessageResult, env: CapabilityEnv): SendPresentation;
2
32
  /**
3
33
  * the decided chat surface for agents: chat_open only.
4
34
  * There is deliberately NO chat-listing tool on the SDK: taking part in a room
@@ -1,5 +1,44 @@
1
1
  import { openConversation } from '../http/ChatClient.js';
2
2
  import { fullCreds } from './types.js';
3
+ import { workContextFromEnv } from './nextCall.js';
4
+ /**
5
+ * Present a send as what it is: a message that now exists, addressed to
6
+ * somebody, which may or may not wake anyone.
7
+ *
8
+ * An agent that sent an answer and read `success: true` had every reason to
9
+ * report the answer delivered. Three different things hide behind that word.
10
+ * The message exists — that much is certain. Who it reached is the server's
11
+ * decision, not the caller's, because an omitted recipient may be inferred.
12
+ * Mailbox routing is a separate asynchronous decision, including coverage by
13
+ * an assistant when the addressee is a person.
14
+ *
15
+ * A server that does not report the destination gets one honest line about the
16
+ * message existing, and nothing invented about where it went.
17
+ */
18
+ export function presentSendResult(result, env) {
19
+ const workContext = workContextFromEnv(env);
20
+ const delivery = result.delivery;
21
+ const base = {
22
+ ok: true,
23
+ chatId: result.chatId,
24
+ messageId: result.messageId,
25
+ ...(workContext ? { workContext } : {}),
26
+ };
27
+ if (!delivery) {
28
+ return {
29
+ ...base,
30
+ delivered: `Message ${result.messageId} was accepted into chat ${result.chatId}. Accepted is not read: nothing here says anyone has seen it.`,
31
+ };
32
+ }
33
+ const destination = delivery.broadcast
34
+ ? 'the humans in the room, not one addressee'
35
+ : `${delivery.to.id} (${delivery.to.type})`;
36
+ return {
37
+ ...base,
38
+ delivered: `Message ${result.messageId} was accepted into chat ${result.chatId}, addressed to ${destination}. Mailbox routing is unconfirmed; a person may be covered by an assistant. Accepted is not read and does not confirm a wake or reply.`,
39
+ to: delivery.to,
40
+ };
41
+ }
3
42
  /**
4
43
  * the decided chat surface for agents: chat_open only.
5
44
  * There is deliberately NO chat-listing tool on the SDK: taking part in a room
@@ -23,3 +23,4 @@ export declare const contextDelegateCapability: CapabilityDefinition;
23
23
  export declare const openCapability: CapabilityDefinition;
24
24
  export declare const accessExplainCapability: CapabilityDefinition;
25
25
  export declare const CONTEXT_CAPABILITIES: CapabilityDefinition[];
26
+ export declare const contextRequestCapability: CapabilityDefinition;
@@ -195,6 +195,10 @@ export const contextDelegateCapability = {
195
195
  type: 'string',
196
196
  description: 'from-now watermark ISO-8601 (optional; server may default)',
197
197
  },
198
+ idempotencyKey: {
199
+ type: 'string',
200
+ description: 'Replay the same delegate. Same key and payload recover the original request; the same key with a different payload is a conflict, not a second card.',
201
+ },
198
202
  },
199
203
  needsAgentId: true,
200
204
  handler: async (args, env) => {
@@ -222,12 +226,18 @@ export const contextDelegateCapability = {
222
226
  temporal: temporal,
223
227
  expiresAt: args['expiresAt'] ?? undefined,
224
228
  watermarkAt: args['watermarkAt'],
229
+ }, {
230
+ idempotencyKey: typeof args['idempotencyKey'] === 'string'
231
+ ? args['idempotencyKey']
232
+ : undefined,
225
233
  });
226
234
  if (result.status === 'pending_approval') {
227
235
  return {
228
236
  status: 'pending_approval',
237
+ state: 'requested',
229
238
  outcome: 'pending',
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.",
239
+ recoverable: true,
240
+ message: "Requested, not granted — this grant's original owner must approve. Recover agreementId; do not treat this as a share and do not widen the scope.",
231
241
  parentGrantId: args['parentGrantId'],
232
242
  agreementId: result.agreementId,
233
243
  ownerId: result.ownerId,
@@ -329,3 +339,34 @@ export const CONTEXT_CAPABILITIES = [
329
339
  contextExpandReachCapability,
330
340
  contextDiscoverGrantableCapability,
331
341
  ];
342
+ export const contextRequestCapability = {
343
+ key: 'context_request',
344
+ names: { sdk: 'context_request', mcp: 'ziggs_context_request' },
345
+ title: 'Ask an owner for artifact access',
346
+ descriptions: {
347
+ sdk: 'Ask a human owner for temporary read-only access to one artifact you were given a reference for. Use your existing chat with that owner. Creates an approval agreement; gives no access until the owner approves. You are the only recipient. After approval, open the original artifact. Never use a generic service agreement as a substitute for this access request.',
348
+ mcp: 'Request access to a restricted artifact directly from its human owner. Pass the artifact id you were given, your conversation with the owner, and the owner account id or participant reference shown in that chat. This creates a pending approval agreement, NOT a grant. No prior grant is needed. The human approves at the agreement link. After approval, ziggs_open the original artifact. Scope is read-only, one artifact, for 1–168 hours (default 24); no other artifacts or chats. Include the intended summary recipient in reason. Keep polling for the decision when asked to wait. A request does not confirm that the artifact exists or belongs to the named owner; ownership is checked at approval.',
349
+ },
350
+ annotation: 'write', needsAgentId: true,
351
+ params: {
352
+ artifactId: { type: 'string', required: true },
353
+ chatId: { type: 'string', required: true },
354
+ ownerId: { type: 'string', required: true, description: 'Human account id or room participant reference, not a persona face' },
355
+ reason: { type: 'string', required: true, description: 'Purpose, including whom you will summarize the artifact to' },
356
+ durationHours: { type: 'number', description: 'Integer 1–168 hours after approval; default 24' },
357
+ idempotencyKey: { type: 'string', description: 'Reuse for the same request; do not create duplicate approval cards' },
358
+ },
359
+ handler: async (args, env) => {
360
+ for (const key of ['artifactId', 'chatId', 'ownerId', 'reason'])
361
+ if (typeof args[key] !== 'string' || !String(args[key]).trim())
362
+ throw new Error(`${key} is required`);
363
+ const durationHours = args.durationHours === undefined ? 24 : Number(args.durationHours);
364
+ if (!Number.isInteger(durationHours) || durationHours < 1 || durationHours > 168)
365
+ throw new Error('durationHours must be an integer between 1 and 168');
366
+ const creds = fullCreds(env);
367
+ const result = await new ContextGrantsClient(creds.operatorKey, creds.agentId, env.baseUrl).requestArtifactAccess(args.artifactId, {
368
+ chatId: args.chatId, ownerId: args.ownerId, reason: args.reason, durationHours,
369
+ }, { idempotencyKey: args.idempotencyKey, laneId: creds.laneId });
370
+ return { ...result, outcome: 'pending', state: 'requested', appUrl: `${(env.webUrl ?? 'https://ziggsai.com').replace(/\/$/, '')}/app/agreements/${encodeURIComponent(result.agreementId)}`, note: 'Waiting for the artifact owner. No access granted yet; open the original artifact only after approval.' };
371
+ },
372
+ };
@@ -1,17 +1,17 @@
1
1
  export { type CapabilitySurface, type CapabilityAnnotation, type CapabilityParam, type CapabilityEnv, type CapabilityDefinition, fullCreds, rethrowWithContext, } from './types.js';
2
2
  export { nextCall, nextCallsFromUntrustedContent, isStaleAdvertisedAction, peerPrincipalId, isPersonaFace, workContextFromEnv, type NextCall, type NextCallOutcome, type NextCallEffects, type NextCallHold, type NextCallOptions, type WorkContext, } from './nextCall.js';
3
- export { AGREEMENT_VERB_CAPABILITIES, agreementBuyCapability, agreementBidCapability, agreementBrokerCapability, agreementRequestCapability, agreementOfferCapability, agreementHandoffCapability, } from './agreementVerbs.js';
3
+ export { AGREEMENT_VERB_CAPABILITIES, agreementBuyCapability, agreementBidCapability, agreementBrokerCapability, agreementRequestCapability, agreementOfferCapability, agreementHandoffCapability, agreementSubcontractCapability, } from './agreementVerbs.js';
4
4
  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
8
  export { LIST_FIELDS_PARAM, parseListFields, pickListedRow, pickListedRows, } from './listedFields.js';
9
- export { TASK_CAPABILITIES, listTasksCapability } from './tasks.js';
9
+ export { TASK_CAPABILITIES, listTasksCapability, cancelTaskCapability, presentTaskOutcome, type TaskOutcomeEffects, type TaskOutcomePresentation, } 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, openCapability, accessExplainCapability, contextDelegateCapability, contextExpandReachCapability, contextDiscoverGrantableCapability, contextBounds, resolveOrgScopeId, } from './context.js';
13
+ export { CONTEXT_CAPABILITIES, contextReadCapability, openCapability, accessExplainCapability, contextDelegateCapability, contextRequestCapability, 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
16
  export { ARTIFACT_CAPABILITIES, recordArtifactCapability, listArtifactsCapability, findArtifactsCapability, type UnsearchedContext, shareArtifactCapability, attachArtifactCapability, uploadArtifactUrlCapability, completeArtifactFileCapability, downloadArtifactCapability, reextractArtifactCapability, } from './artifacts.js';
17
- export { CHAT_CAPABILITIES, openConversationCapability } from './chat.js';
17
+ export { CHAT_CAPABILITIES, openConversationCapability, presentSendResult, type SendPresentation, } from './chat.js';
@@ -1,17 +1,17 @@
1
1
  export { fullCreds, rethrowWithContext, } from './types.js';
2
2
  export { nextCall, nextCallsFromUntrustedContent, isStaleAdvertisedAction, peerPrincipalId, isPersonaFace, workContextFromEnv, } from './nextCall.js';
3
- export { AGREEMENT_VERB_CAPABILITIES, agreementBuyCapability, agreementBidCapability, agreementBrokerCapability, agreementRequestCapability, agreementOfferCapability, agreementHandoffCapability, } from './agreementVerbs.js';
3
+ export { AGREEMENT_VERB_CAPABILITIES, agreementBuyCapability, agreementBidCapability, agreementBrokerCapability, agreementRequestCapability, agreementOfferCapability, agreementHandoffCapability, agreementSubcontractCapability, } from './agreementVerbs.js';
4
4
  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
8
  export { LIST_FIELDS_PARAM, parseListFields, pickListedRow, pickListedRows, } from './listedFields.js';
9
- export { TASK_CAPABILITIES, listTasksCapability } from './tasks.js';
9
+ export { TASK_CAPABILITIES, listTasksCapability, cancelTaskCapability, presentTaskOutcome, } 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, openCapability, accessExplainCapability, contextDelegateCapability, contextExpandReachCapability, contextDiscoverGrantableCapability, contextBounds, resolveOrgScopeId, } from './context.js';
13
+ export { CONTEXT_CAPABILITIES, contextReadCapability, openCapability, accessExplainCapability, contextDelegateCapability, contextRequestCapability, 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
16
  export { ARTIFACT_CAPABILITIES, recordArtifactCapability, listArtifactsCapability, findArtifactsCapability, shareArtifactCapability, attachArtifactCapability, uploadArtifactUrlCapability, completeArtifactFileCapability, downloadArtifactCapability, reextractArtifactCapability, } from './artifacts.js';
17
- export { CHAT_CAPABILITIES, openConversationCapability } from './chat.js';
17
+ export { CHAT_CAPABILITIES, openConversationCapability, presentSendResult, } from './chat.js';
@@ -1,14 +1,16 @@
1
+ import { marketplaceFormation } from '../engagementGuide.js';
1
2
  import { pullOffers, pullRequests } from '../http/MarketplaceClient.js';
2
3
  import { fullCreds } from './types.js';
3
4
  import { nextCall } from './nextCall.js';
4
5
  import { LIST_FIELDS_PARAM, parseListFields, pickListedRows } from './listedFields.js';
5
6
  const VIEW_KINDS = ['all', 'requests', 'offers'];
6
7
  function publishHint(env) {
7
- const propose = env.surface === 'mcp' ? 'ziggs_agreement_request' : 'agreement_request';
8
8
  const claim = env.surface === 'mcp' ? 'ziggs_agreement_claim' : 'agreement_claim';
9
- return (`Claim any row with ${claim} (agreementId). Publish your own with ${propose}: ` +
10
- `proposedTo "everyone" or "org" with no providerId broadcasts a request (claimer works, you pay); ` +
11
- `the same with providerId = your own id publishes a standing offer (you work, claimer pays).`);
9
+ const publish = env.surface === 'mcp'
10
+ ? 'Publish a request with ziggs_agreement_request (claimer works, you pay) or a standing offer with ziggs_agreement_offer (you work, claimer pays).'
11
+ : 'Publish your own via agreement_propose with proposedTo "everyone"/"org" (providerId = your id for an offer, omitted for a request).';
12
+ return (`Reuse an active agreement that already covers the work. Otherwise claim any row with ${claim} ` +
13
+ `(agreementId) — listings are take-it-or-leave-it, never counter one. ${publish}`);
12
14
  }
13
15
  /** A broadcast sentinel in a party slot means "open", not a counterparty. */
14
16
  const BROADCASTS = new Set(['everyone', 'org']);
@@ -98,6 +100,10 @@ export const marketplaceViewCapability = {
98
100
  const fields = parseListFields(args['fields']);
99
101
  const requestRows = pickListedRows(requests.map((q) => toListingRow(q, 'request')), fields);
100
102
  const offerRows = pickListedRows(offers.map((o) => toListingRow(o, 'offer')), fields);
103
+ const listingAgreementIds = [...requests, ...offers]
104
+ .map((row) => row.agreementId)
105
+ .filter((id) => typeof id === 'string' && id.length > 0);
106
+ const engagement = marketplaceFormation(env, { listingAgreementIds });
101
107
  return {
102
108
  ...(kind !== 'offers'
103
109
  ? { requests: requestRows, requestCount: requests.length }
@@ -105,13 +111,16 @@ export const marketplaceViewCapability = {
105
111
  ...(kind !== 'requests'
106
112
  ? { offers: offerRows, offerCount: offers.length }
107
113
  : {}),
114
+ engagement,
108
115
  // Claiming is the move after browsing, and the id is in the row the
109
116
  // caller just received. The prose hint stays for the publish side, which
110
117
  // is a choice rather than a call.
111
118
  readPlan: [
112
- ...(requests.length || offers.length
119
+ ...(listingAgreementIds.length
113
120
  ? [
114
- nextCall(env, 'agreement_claim', undefined, 'claim a listing from this view by passing its agreementId — listings are take-it-or-leave-it, never countered'),
121
+ nextCall(env, 'agreement_claim', listingAgreementIds.length === 1
122
+ ? { agreementId: listingAgreementIds[0] }
123
+ : undefined, 'claim a listing from this view by passing its agreementId — listings are take-it-or-leave-it, never countered'),
115
124
  ]
116
125
  : []),
117
126
  ],
@@ -3,7 +3,13 @@
3
3
  * and must never be offered here — chat_open / chat_send refuse it, and the
4
4
  * baseline's provider learned that only by being refused.
5
5
  */
6
- const ADDRESS_FIELDS = ['participantId', 'to', 'receiverId'];
6
+ const ADDRESS_FIELDS = [
7
+ 'participantId',
8
+ 'to',
9
+ 'receiverId',
10
+ 'counterparty',
11
+ 'executorId',
12
+ ];
7
13
  /**
8
14
  * Required arguments for the capability keys this helper is asked to emit.
9
15
  * Kept next to the HTTP bindings so a ready call cannot advertise a schema
@@ -15,9 +21,18 @@ const REQUIRED_ARGS = {
15
21
  chat_open: ['participantId'],
16
22
  chat_send: ['chatId'],
17
23
  agreement_claim: ['agreementId'],
24
+ agreement_get: ['agreementId'],
25
+ agreement_buy: ['counterparty', 'description'],
26
+ agreement_bid: ['counterparty', 'description'],
27
+ agreement_request: ['audience', 'description'],
28
+ agreement_subcontract: ['parentAgreementId', 'executorId', 'description'],
18
29
  agreement_respond: ['agreementId'],
19
30
  introduction_redeem: ['token'],
20
31
  context_issue_grant: ['holderId', 'scopeKind', 'scopeId'],
32
+ artifact_share: ['artifactId', 'holderId'],
33
+ context_delegate: ['parentGrantId', 'holderId', 'scopeKind', 'scopeId', 'temporal'],
34
+ task_create: ['agreementId', 'description'],
35
+ task_cancel: ['taskId'],
21
36
  };
22
37
  /**
23
38
  * Published HTTP invocation per capability key. Paths match the api-client
@@ -31,17 +46,31 @@ const HTTP_BINDINGS = {
31
46
  introduction_mint: { method: 'POST', path: '/introductions' },
32
47
  introduction_redeem: { method: 'POST', path: '/introductions/:token/redeem' },
33
48
  agreement_claim: { method: 'POST', path: '/agreements/:agreementId/claim' },
49
+ agreement_get: { method: 'GET', path: '/agreements/:agreementId' },
50
+ agreement_buy: { method: 'POST', path: '/agreements/proposals' },
51
+ agreement_bid: { method: 'POST', path: '/agreements/proposals' },
52
+ agreement_request: { method: 'POST', path: '/agreements/proposals' },
53
+ agreement_subcontract: {
54
+ method: 'POST',
55
+ path: '/agreements/:parentAgreementId/delegations',
56
+ },
34
57
  agreement_respond: {
35
58
  method: 'PUT',
36
59
  path: '/agreements/:agreementId/approvals/:partyId',
37
60
  },
38
61
  marketplace_view: { method: 'GET', path: '/marketplace/offers' },
39
62
  context_issue_grant: { method: 'POST', path: '/context/grants' },
63
+ artifact_share: { method: 'POST', path: '/context/artifacts/:artifactId/share' },
64
+ context_delegate: { method: 'POST', path: '/context/grants/:parentGrantId/delegate' },
40
65
  open: { method: 'POST', path: '/context/open' },
41
66
  access_explain: { method: 'POST', path: '/context/access/explain' },
42
67
  inbox_peek: { method: 'GET', path: '/inbox/peek' },
68
+ task_get: { method: 'GET', path: '/tasks/:taskId' },
69
+ task_list: { method: 'GET', path: '/tasks' },
43
70
  link_list: { method: 'GET', path: '/agreements?engagementKind=link' },
44
71
  link_propose: { method: 'POST', path: '/agreements/links' },
72
+ task_create: { method: 'POST', path: '/tasks' },
73
+ task_cancel: { method: 'PATCH', path: '/tasks/:taskId/cancel' },
45
74
  };
46
75
  /**
47
76
  * Intentional surface exclusions. A ready next call has to exist as a tool
@@ -50,8 +79,15 @@ const HTTP_BINDINGS = {
50
79
  */
51
80
  const SURFACE_EXPOSURE = {
52
81
  context_issue_grant: ['mcp'],
82
+ // The hosted SDK surface enumerates its own work with task_list and reads a
83
+ // task from the wake it arrived on; ziggs_task_get is the MCP delegate's
84
+ // one-task read. Naming it to an SDK agent would offer a tool it cannot call.
85
+ task_get: ['mcp'],
53
86
  };
54
87
  const MATERIAL_EFFECTS = {
88
+ task_get: {
89
+ disclosure: 'reads back what was recorded; it changes nothing',
90
+ },
55
91
  agreement_claim: {
56
92
  commitment: 'claiming makes you a party to the posted terms',
57
93
  relationship: 'you become the open party on that broadcast',
@@ -71,6 +107,26 @@ const MATERIAL_EFFECTS = {
71
107
  chat_open: {
72
108
  disclosure: 'opening a room issues the other side a grant on it',
73
109
  },
110
+ task_create: {
111
+ commitment: 'creates a task under the named agreement',
112
+ },
113
+ task_cancel: {
114
+ commitment: 'cancel ends this task; a held graph withdraws by cancelling the root',
115
+ },
116
+ agreement_buy: {
117
+ commitment: 'proposes that they work and your side pays',
118
+ relationship: 'a named counterparty — not a listing claim',
119
+ },
120
+ agreement_bid: {
121
+ commitment: 'proposes that you work and they pay',
122
+ },
123
+ agreement_request: {
124
+ commitment: 'posts work anyone may claim; you pay the claimer',
125
+ },
126
+ agreement_subcontract: {
127
+ commitment: 'opens a sub-agreement under the parent; the worker must approve — never impersonated',
128
+ relationship: 'the slice sits under the parent you already hold',
129
+ },
74
130
  };
75
131
  /** A persona face (`psn_…`) is display state, not a message address. */
76
132
  export function isPersonaFace(id) {
@@ -155,6 +211,7 @@ export function nextCall(env, capabilityKey, args, why, opts) {
155
211
  };
156
212
  }
157
213
  const missing = [];
214
+ const strippedFaces = [];
158
215
  const filled = {};
159
216
  for (const [key, value] of Object.entries(args ?? {})) {
160
217
  if (!isFilled(value))
@@ -162,6 +219,7 @@ export function nextCall(env, capabilityKey, args, why, opts) {
162
219
  if (ADDRESS_FIELDS.includes(key) &&
163
220
  isPersonaFace(value)) {
164
221
  missing.push(key);
222
+ strippedFaces.push(key);
165
223
  continue;
166
224
  }
167
225
  filled[key] = value;
@@ -183,11 +241,11 @@ export function nextCall(env, capabilityKey, args, why, opts) {
183
241
  ? 'partial'
184
242
  : 'unavailable');
185
243
  const http = HTTP_BINDINGS[capabilityKey];
186
- const faceMissing = missing.some((name) => ADDRESS_FIELDS.includes(name));
244
+ const faceWasPassed = strippedFaces.length > 0;
187
245
  const reason = hold && missing.length === 0
188
246
  ? why
189
- : !ready && faceMissing
190
- ? `${why} — a next call for reaching a person names the user id or their agent, never a psn_ face`
247
+ : !ready && faceWasPassed
248
+ ? `${why} — missing ${missing.join(', ')}. a next call for reaching a person names the user id or their agent, never a psn_ face`
191
249
  : !ready
192
250
  ? `${why} — missing ${missing.join(', ')}`
193
251
  : why;