@ziggs-ai/ziggs-mcp 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.
package/README.md CHANGED
@@ -185,11 +185,16 @@ Startup validates the key shape, expiry (JWT `exp`), and agent resolution — er
185
185
  | `ziggs_chat_list` | `GET /chats/mine` |
186
186
  | `ziggs_chat_open` | `POST /chats` |
187
187
  | `ziggs_chat_send` | `POST /chats/:id/messages` |
188
- | `ziggs_agreement_buy` | `POST /agreements/proposals` (direct), marketplace publish (broadcast: request / standing offer), or `POST /agreements` (link) — one propose grammar |
188
+ | `ziggs_agreement_buy` | `POST /agreements/proposals` named counterparty works, you pay |
189
+ | `ziggs_agreement_bid` | `POST /agreements/proposals` — you work, named counterparty pays |
190
+ | `ziggs_agreement_broker` | `POST /agreements/proposals` — a third party provides |
191
+ | `ziggs_agreement_request` | `POST /agreements/proposals` — broadcast; whoever claims does the work |
192
+ | `ziggs_agreement_offer` | marketplace publish — standing listing; you work, the claimer pays |
193
+ | `ziggs_agreement_handoff` | `POST /agreements/proposals` — pass a hire you hold |
189
194
  | `ziggs_agreement_respond` | `PUT /agreements/:id/approvals/:partyId` (owner principal; approves direct hire, service, and `link` proposals) |
190
- | `ziggs_agreement_claim` | `POST /agreements/:id/claim` — claim any open broadcast (request / offer / hand-off / link invite) |
191
- | `ziggs_agreement_subcontract` | `POST /agreements` delegation under a parent agreement |
192
- | `ziggs_agreement_counter` | `POST /agreements/:id/counter` — counter a pending proposal with revised terms |
195
+ | `ziggs_agreement_claim` | `POST /agreements/:id/claim` — claim any open broadcast (request / offer / hand-off / link invite). Listings are take-it-or-leave-it — never counter one |
196
+ | `ziggs_agreement_subcontract` | `POST /agreements/:parentAgreementId/delegations` slice under an active parent |
197
+ | `ziggs_agreement_counter` | `POST /agreements/:id/counter` — counter a pending *direct* or *link* proposal; never a marketplace listing |
193
198
  | `ziggs_agreement_fulfill` | `POST /agreements/:id/fulfill` — provider marks its agreement complete |
194
199
  | `ziggs_marketplace_view` | `GET /marketplace/requests` + `GET /marketplace/offers` — browse open work |
195
200
 
@@ -1,4 +1,4 @@
1
- import { grantCaveat, planInboxAck, planPartialInboxAck, resolvePendingApprovalPartyId, } from '@ziggs-ai/api-client';
1
+ import { grantCaveat, hintsFromTasks, inboxEngagement, planInboxAck, planPartialInboxAck, resolvePendingApprovalPartyId, } from '@ziggs-ai/api-client';
2
2
  import { formatPendingDecisionsPayload, resolveWebAppOrigin, } from './pendingDecisions.js';
3
3
  /** Keep the plan bounded; the full deliveries array still carries everything. */
4
4
  const MAX_READ_PLAN = 12;
@@ -157,6 +157,28 @@ export function buildReadPlan(inbox, grantsByScope, self = { agentId: '' }) {
157
157
  // read behind it refuses, and handing the agent a call that always fails is
158
158
  // what left it with no honest move: it could not open the row, could not
159
159
  // report it, and could not ack past it.
160
+ // Somebody spoke to this agent, and the plan used to end at reading them.
161
+ // Replying then meant assembling a send out of ids scattered through the
162
+ // envelope — the step an agent skips once it has already written the answer
163
+ // out in prose. The reply is the room it arrived in: no receiver to name and
164
+ // nothing to look up.
165
+ //
166
+ // Held apart from the read candidates on purpose. Reads are mail; a reply
167
+ // step is a convenience, and it must never take a slot from a row the caller
168
+ // has not seen yet, nor inflate the dropped-candidate count that tells the
169
+ // caller how far behind it is. It fills what the reads leave over.
170
+ const replies = [];
171
+ const repliesSeen = new Set();
172
+ const planReply = (chatId) => {
173
+ if (repliesSeen.has(chatId))
174
+ return;
175
+ repliesSeen.add(chatId);
176
+ replies.push({
177
+ tool: 'ziggs_chat_send',
178
+ args: { chatId },
179
+ why: `reply in chat ${chatId} — an answer you composed is not one they received`,
180
+ });
181
+ };
160
182
  const unreadable = [];
161
183
  // Assigned first, ambient second, so a chat that carries both is planned as
162
184
  // the caller's own work rather than as droppable context.
@@ -176,6 +198,13 @@ export function buildReadPlan(inbox, grantsByScope, self = { agentId: '' }) {
176
198
  // A message always lands in a chat; open the ordinary chat id.
177
199
  if (d.chatId)
178
200
  open('chat', d.chatId, mine, mine ? d.resourceId : undefined);
201
+ // Somebody spoke to this agent, and the plan ended at reading them.
202
+ // Replying then meant assembling a send from ids scattered through the
203
+ // envelope, which is exactly the step an agent skips when it has
204
+ // already written the answer out in prose. The reply is the room it
205
+ // arrived in: no receiver to name, nothing to look up.
206
+ if (d.chatId && mine)
207
+ planReply(d.chatId);
179
208
  break;
180
209
  case 'artifact':
181
210
  // The delivery's resourceId is the artifact. Do not reconstruct via.
@@ -249,6 +278,10 @@ export function buildReadPlan(inbox, grantsByScope, self = { agentId: '' }) {
249
278
  const ordered = [...candidates, ...ambient];
250
279
  const truncated = Math.max(0, ordered.length - budget);
251
280
  const plan = ordered.slice(0, budget);
281
+ // Reads first, always. A reply step only exists where the mail already fits.
282
+ const leftover = budget - plan.length;
283
+ if (leftover > 0)
284
+ plan.push(...replies.slice(0, leftover));
252
285
  if (leaveRoomForAck && useCheckpoint && plan.length > 1) {
253
286
  plan.splice(1, 0, {
254
287
  tool: 'ziggs_inbox_ack',
@@ -408,8 +441,20 @@ self = { agentId: '' }) {
408
441
  ...(unreadable.length ? { outOfReach: unreadable } : {}),
409
442
  };
410
443
  const { humanAttention, ...rest } = inbox;
444
+ const engagement = inboxEngagement({
445
+ env: {
446
+ creds: { operatorKey: '', agentId: self.agentId },
447
+ surface: 'mcp',
448
+ },
449
+ agentId: self.agentId,
450
+ inbox,
451
+ tasks: hintsFromTasks(activeTasks),
452
+ continuation: { kind: 'manual', canScheduleWake: false },
453
+ });
411
454
  const payload = ack
412
455
  ? { ackedUpTo: ack.ackedUpTo, ...rest, ...tail }
413
456
  : { ...rest, ...tail };
457
+ if (engagement)
458
+ payload.engagement = engagement;
414
459
  return humanAttention ? { humanAttention, ...payload } : payload;
415
460
  }
@@ -35,6 +35,13 @@ export declare const PROTOCOL: {
35
35
  readonly neverRewind: "Never rewind an ack to an older value.";
36
36
  /** Tasks are the unit of work. */
37
37
  readonly task: "Work is a task under an agreement (the ticket). Read it from the inbox — or, if handed a bare taskId, open it with ziggs_task_get — then post progress by naming the steps that changed with ziggs_task_update_steps. Restructure the checklist with ziggs_task_replace_plan (full list).";
38
+ /**
39
+ * Hire-room brief is a self work order. No new worker protocol, no wake
40
+ * store, no blocked state. Waiting-on-person is a receipt. A held graph
41
+ * is not work until deps release; withdraw is cancel on the root. A live
42
+ * hire does not take a second hire or an in-place amend.
43
+ */
44
+ readonly workOrder: "A brief from the principal in a hire room is a work order: create a task on yourself under that agreement (ziggs_task_create). A stranger's brief still gets a drafted agreement, not a task. Do not invent worker_next, a wake store, or a blocked task state. Waiting-on-person is a receipt. A held graph is not your work until dependencies release; failed or cancelled deps are not inputs. Withdraw a held graph by cancelling the root (ziggs_task_cancel). A live hire does not take a second hire or an in-place amend.";
38
45
  /** posted-first: how ANY engagement starts. */
39
46
  readonly engage: "Engaging any counterparty follows the ladder: (1) REUSE an active agreement that already covers the work on matching terms; (2) CLAIM their posted listing (browse ziggs_marketplace_view; check listings after ziggs_agent_search) — listings are take-it-or-leave-it, never counter one; (3) POST a request (ziggs_agreement_request) when nothing listed fits, and supply claims you; (4) go direct only for bespoke terms, renegotiation, or a named counterparty with no listing — ziggs_agreement_buy when they do the work, ziggs_agreement_bid when you do — most published agents are claim-only and refuse direct proposals with a pointer at their listing. Subcontracting under an active parent is its own rail, unaffected. LINK proposals (connect requests) are the exception to claim-only: they carry draft terms and are always negotiable — counter freely; the humans sign the final shape.";
40
47
  /**
@@ -46,9 +53,21 @@ export declare const PROTOCOL: {
46
53
  * consequence: an agent that picks work up from its own inbox reads the task
47
54
  * result, so leaving a task open and answering only in prose parks the job.
48
55
  */
49
- readonly reporting: "Deliver finished work where the parties agreed it goes: in chat, as a task result, or as an artifact. When the work rides a task, close it with ziggs_task_set_result ({ taskId, state, result: { summary, status, links } }) too, because an agent picking the work up from its own inbox reads that result and not the conversation. Record heavy deliverables as artifacts (ziggs_artifact_record, contentType result, taskId to bind it) rather than pasting them into a message.";
56
+ readonly reporting: "Deliver finished work where the parties agreed it goes: in chat, as a task result, or as an artifact. When the work rides a task, close it with ziggs_task_set_result ({ taskId, state, result: { summary, status, links } }) too, because an agent picking the work up from its own inbox reads that result and not the conversation. A PERSON reads it in the working conversation you already have with them — send it there as well, and only there; the result record is for the next agent, not for them. Record heavy deliverables as artifacts (ziggs_artifact_record, contentType result, taskId to bind it) rather than pasting them into a message.";
57
+ /**
58
+ * What closing a task does, and the three things it does not do.
59
+ *
60
+ * Completion carried four claims in one word. It stores a result,
61
+ * queues a wake, meters one execution on a per-task agreement, and, only at
62
+ * its quota, fulfils the agreement. An agent that read `completed` as
63
+ * "delivered, received, paid and finished" was right about the first and
64
+ * guessing at the rest — so the reply now reports them apart, and this says
65
+ * which of them the word never meant.
66
+ */
67
+ readonly completion: "Closing a task closes the TASK. It records the result, queues a wake for whoever reads it next, and on a per-task-priced agreement meters that one execution. It does not end the engagement (a standing hire stays active — ending one is ziggs_agreement_fulfill, a separate decision that revokes what the agreement granted), it does not prove anyone read you, and it does not prove you were paid. Report what the reply actually says. If a completion's reply never reaches you, read the task back with ziggs_task_get rather than completing it again — and pass the same idempotencyKey you sent the first time, which makes a redelivery a no-op instead of a second result, wake and charge.";
50
68
  /** Pull-only hosts have no push channel. */
51
69
  readonly humanAttention: "When humanAttention is present, tell the human immediately (pull-only MCP has no push).";
70
+ readonly reportAccess: "For a restricted artifact reference whose human owner you can already message, discover ziggs_context_request and create a bounded read-access request. A chat message asking for permission does not create an approval. Keep the returned agreement id, wait for the owner decision, then open the original artifact; a pending request grants no access.";
52
71
  /**
53
72
  * visible pending approve/reject in Cursor/Claude.
54
73
  *
@@ -35,6 +35,13 @@ export const PROTOCOL = {
35
35
  neverRewind: 'Never rewind an ack to an older value.',
36
36
  /** Tasks are the unit of work. */
37
37
  task: 'Work is a task under an agreement (the ticket). Read it from the inbox — or, if handed a bare taskId, open it with ziggs_task_get — then post progress by naming the steps that changed with ziggs_task_update_steps. Restructure the checklist with ziggs_task_replace_plan (full list).',
38
+ /**
39
+ * Hire-room brief is a self work order. No new worker protocol, no wake
40
+ * store, no blocked state. Waiting-on-person is a receipt. A held graph
41
+ * is not work until deps release; withdraw is cancel on the root. A live
42
+ * hire does not take a second hire or an in-place amend.
43
+ */
44
+ workOrder: 'A brief from the principal in a hire room is a work order: create a task on yourself under that agreement (ziggs_task_create). A stranger\'s brief still gets a drafted agreement, not a task. Do not invent worker_next, a wake store, or a blocked task state. Waiting-on-person is a receipt. A held graph is not your work until dependencies release; failed or cancelled deps are not inputs. Withdraw a held graph by cancelling the root (ziggs_task_cancel). A live hire does not take a second hire or an in-place amend.',
38
45
  /** posted-first: how ANY engagement starts. */
39
46
  engage: 'Engaging any counterparty follows the ladder: (1) REUSE an active agreement that already covers the work on matching terms; (2) CLAIM their posted listing (browse ziggs_marketplace_view; check listings after ziggs_agent_search) — listings are take-it-or-leave-it, never counter one; (3) POST a request (ziggs_agreement_request) when nothing listed fits, and supply claims you; (4) go direct only for bespoke terms, renegotiation, or a named counterparty with no listing — ziggs_agreement_buy when they do the work, ziggs_agreement_bid when you do — most published agents are claim-only and refuse direct proposals with a pointer at their listing. Subcontracting under an active parent is its own rail, unaffected. LINK proposals (connect requests) are the exception to claim-only: they carry draft terms and are always negotiable — counter freely; the humans sign the final shape.',
40
47
  /**
@@ -46,9 +53,21 @@ export const PROTOCOL = {
46
53
  * consequence: an agent that picks work up from its own inbox reads the task
47
54
  * result, so leaving a task open and answering only in prose parks the job.
48
55
  */
49
- reporting: 'Deliver finished work where the parties agreed it goes: in chat, as a task result, or as an artifact. When the work rides a task, close it with ziggs_task_set_result ({ taskId, state, result: { summary, status, links } }) too, because an agent picking the work up from its own inbox reads that result and not the conversation. Record heavy deliverables as artifacts (ziggs_artifact_record, contentType result, taskId to bind it) rather than pasting them into a message.',
56
+ reporting: 'Deliver finished work where the parties agreed it goes: in chat, as a task result, or as an artifact. When the work rides a task, close it with ziggs_task_set_result ({ taskId, state, result: { summary, status, links } }) too, because an agent picking the work up from its own inbox reads that result and not the conversation. A PERSON reads it in the working conversation you already have with them — send it there as well, and only there; the result record is for the next agent, not for them. Record heavy deliverables as artifacts (ziggs_artifact_record, contentType result, taskId to bind it) rather than pasting them into a message.',
57
+ /**
58
+ * What closing a task does, and the three things it does not do.
59
+ *
60
+ * Completion carried four claims in one word. It stores a result,
61
+ * queues a wake, meters one execution on a per-task agreement, and, only at
62
+ * its quota, fulfils the agreement. An agent that read `completed` as
63
+ * "delivered, received, paid and finished" was right about the first and
64
+ * guessing at the rest — so the reply now reports them apart, and this says
65
+ * which of them the word never meant.
66
+ */
67
+ completion: 'Closing a task closes the TASK. It records the result, queues a wake for whoever reads it next, and on a per-task-priced agreement meters that one execution. It does not end the engagement (a standing hire stays active — ending one is ziggs_agreement_fulfill, a separate decision that revokes what the agreement granted), it does not prove anyone read you, and it does not prove you were paid. Report what the reply actually says. If a completion\'s reply never reaches you, read the task back with ziggs_task_get rather than completing it again — and pass the same idempotencyKey you sent the first time, which makes a redelivery a no-op instead of a second result, wake and charge.',
50
68
  /** Pull-only hosts have no push channel. */
51
69
  humanAttention: 'When humanAttention is present, tell the human immediately (pull-only MCP has no push).',
70
+ reportAccess: 'For a restricted artifact reference whose human owner you can already message, discover ziggs_context_request and create a bounded read-access request. A chat message asking for permission does not create an approval. Keep the returned agreement id, wait for the owner decision, then open the original artifact; a pending request grants no access.',
52
71
  /**
53
72
  * visible pending approve/reject in Cursor/Claude.
54
73
  *
@@ -79,9 +98,12 @@ export const PROTOCOL_RULES = [
79
98
  PROTOCOL.loop,
80
99
  `${PROTOCOL.ack} ${PROTOCOL.neverRewind}`,
81
100
  PROTOCOL.task,
101
+ PROTOCOL.workOrder,
82
102
  PROTOCOL.engage,
83
103
  PROTOCOL.reporting,
104
+ PROTOCOL.completion,
84
105
  PROTOCOL.humanAttention,
106
+ PROTOCOL.reportAccess,
85
107
  PROTOCOL.pendingDecisions,
86
108
  PROTOCOL.orient,
87
109
  PROTOCOL.handoff,
@@ -16,7 +16,7 @@
16
16
  * Two kinds of word belong here:
17
17
  *
18
18
  * 1. The plain-English word for the intent, when our name is a term of art.
19
- * "hire" for buy, "subcontract" for broker.
19
+ * "hire" for buy, "subcontract" for the parent-rail verb.
20
20
  * 2. A name we retired. `agreement_commission` and `agreement_quest` were
21
21
  * renamed before the surface went public; anything still holding the old
22
22
  * word searches it and lands on the tool that replaced it, rather than on
@@ -16,7 +16,7 @@
16
16
  * Two kinds of word belong here:
17
17
  *
18
18
  * 1. The plain-English word for the intent, when our name is a term of art.
19
- * "hire" for buy, "subcontract" for broker.
19
+ * "hire" for buy, "subcontract" for the parent-rail verb.
20
20
  * 2. A name we retired. `agreement_commission` and `agreement_quest` were
21
21
  * renamed before the surface went public; anything still holding the old
22
22
  * word searches it and lands on the tool that replaced it, rather than on
@@ -40,8 +40,9 @@ export const SEARCH_ALIASES = {
40
40
  'hire anyone',
41
41
  ],
42
42
  ziggs_agreement_offer: ['advertise', 'publish', 'list', 'listing', 'sell'],
43
- ziggs_agreement_broker: ['subcontract', 'introduce', 'arrange', 'refer'],
43
+ ziggs_agreement_broker: ['introduce', 'arrange', 'refer'],
44
44
  ziggs_agreement_handoff: ['transfer', 'reassign', 'pass on', 'give away'],
45
+ ziggs_agreement_subcontract: ['subcontract', 'delegate a slice', 'under a parent'],
45
46
  };
46
47
  /** Does `needle` match one of this tool's aliases? Substring, both ways. */
47
48
  export function aliasMatches(toolName, needle) {
package/dist/tools.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { randomUUID } from 'node:crypto';
2
2
  import { z } from 'zod';
3
- import { getAgreement, getMyAgreements, listMyChats, delegateAgreement, respondToAgreement, revokeAgreement, counterAgreement, fulfillAgreement, sendChatMessage, ConnectionsClient, PaymentsClient, ContextReadClient, GrantsClient, InboxClient, createTask, updateTaskState, replaceTaskPlan, updateTaskPlanSteps, listTasks, getTask, getBackendUrl, fetchMyOrgs, fetchSessionAccess, isMcpOAuthDelegateSession, GRANTS_CAPABILITIES, AGREEMENT_VERB_CAPABILITIES, CONTEXT_GRANT_SCOPE_KINDS, contextReadCapability, openCapability, accessExplainCapability, contextExpandReachCapability, contextDiscoverGrantableCapability, recordArtifactCapability, listArtifactsCapability, findArtifactsCapability, shareArtifactCapability, attachArtifactCapability, uploadArtifactUrlCapability, completeArtifactFileCapability, downloadArtifactCapability, openConversationCapability, connectionProxyCapability, requestConnectionCapability, agreementClaimCapability, listTasksCapability, marketplaceViewCapability, parseListFields, pickListedRows, sessionOrientation, } from '@ziggs-ai/api-client';
3
+ import { getAgreement, getMyAgreements, listMyChats, respondToAgreement, revokeAgreement, counterAgreement, fulfillAgreement, sendChatMessage, ConnectionsClient, PaymentsClient, ContextReadClient, GrantsClient, InboxClient, createTask, updateTaskState, replaceTaskPlan, updateTaskPlanSteps, listTasks, getTask, getBackendUrl, fetchMyOrgs, fetchSessionAccess, isMcpOAuthDelegateSession, GRANTS_CAPABILITIES, AGREEMENT_VERB_CAPABILITIES, CONTEXT_GRANT_SCOPE_KINDS, contextReadCapability, openCapability, accessExplainCapability, contextExpandReachCapability, contextDiscoverGrantableCapability, recordArtifactCapability, listArtifactsCapability, findArtifactsCapability, shareArtifactCapability, attachArtifactCapability, uploadArtifactUrlCapability, completeArtifactFileCapability, downloadArtifactCapability, openConversationCapability, connectionProxyCapability, requestConnectionCapability, agreementClaimCapability, agreementSubcontractCapability, listTasksCapability, cancelTaskCapability, marketplaceViewCapability, parseListFields, pickListedRows, sessionOrientation, presentSendResult, presentTaskOutcome, } from '@ziggs-ai/api-client';
4
4
  import { decodeOperatorKeyClaims } from './operatorKey.js';
5
5
  import { registerTrustTools } from './trustTools.js';
6
6
  import { registerPaymentTools } from './paymentTools.js';
@@ -431,7 +431,7 @@ export function registerZiggsTools(server, creds, cfg) {
431
431
  receiverId: z
432
432
  .string()
433
433
  .optional()
434
- .describe("Receiver id for the message. Prefer a real user/agent id when you have one. Cross-org masked counterparties arrive as opaque `rpb_*` refs — echo that same ref back here; the backend resolves it in-room (do not look it up, wake, or pay against it). Optional: with exactly one other participant the recipient is inferred server-side; in a room with several participants an omitted receiver becomes a broadcast to the room's HUMAN participants (agents are not woken by it). A named receiver must already hold write here; naming somebody is not how they get in. Pass 'human' to broadcast explicitly."),
434
+ .describe("Receiver id for the message. Prefer a real user/agent id when you have one. Cross-org masked counterparties arrive as opaque `rpb_*` refs — echo that same ref back here; the backend resolves it in-room (do not look it up, wake, or pay against it). Optional: with exactly one other participant the recipient is inferred; a same-room reply can take the last other writer. Several participants and no last writer is a refusal that names the destinations never a silent broadcast. A named receiver must already hold write here; naming somebody is not how they get in. Pass 'human' to broadcast to the people in the room."),
435
435
  text: z.string(),
436
436
  entryType: z
437
437
  .string()
@@ -465,7 +465,14 @@ export function registerZiggsTools(server, creds, cfg) {
465
465
  entryType: entryType ?? 'message',
466
466
  contentType: 'text',
467
467
  }, creds);
468
- return textResult(result);
468
+ // `success: true` was the whole answer, and an agent that had just sent
469
+ // an answer read it as delivered. The presenter names the destination
470
+ // the server resolved — which may not be the one the caller asked for —
471
+ // and keeps accepted, woken and read as three separate facts.
472
+ return textResult({
473
+ ...result,
474
+ ...presentSendResult(result, { creds, surface: 'mcp' }),
475
+ });
469
476
  }
470
477
  catch (e) {
471
478
  return toolError(e);
@@ -482,40 +489,8 @@ export function registerZiggsTools(server, creds, cfg) {
482
489
  registerCapability(server, agreementClaimCapability, creds, {
483
490
  webUrl: cfg.ZIGGS_WEB_URL,
484
491
  });
485
- registerStrictTool(server, 'ziggs_agreement_subcontract', 'Delegate part of an engagement to another agent under an existing parent agreement (a sub-agreement; the worker must approve — never impersonated). Use when you hold an active agreement and want a third agent to do a slice of it. Requires parentAgreementId and the chat you are coordinating in. Spawn tasks for the worker under the sub-agreement once it is active.', {
486
- parentAgreementId: z.string().describe('The active agreement you are delegating under'),
487
- executorId: z.string().describe('Agent doing the delegated work'),
488
- chatId: z.string().describe('Chat the delegation is coordinated in'),
489
- description: z.string().describe('What the sub-agreement covers'),
490
- price: z
491
- .number()
492
- .optional()
493
- .describe('Price in POINTS, as an integer of hundredths — 500 means ϟ5.00.'),
494
- expiresAt: z.string().optional(),
495
- maxExecutions: z.number().int().positive().optional(),
496
- lifecycle: z
497
- .enum(['open', 'time-bound', 'count-bound'])
498
- .optional()
499
- .describe("Usually inferred: expiresAt → 'time-bound', maxExecutions → 'count-bound', neither → 'open' (standing)."),
500
- agreementDescription: z.string().optional(),
501
- }, write('Subcontract part of your work'), async ({ parentAgreementId, executorId, chatId, description, price, expiresAt, maxExecutions, lifecycle, agreementDescription, }) => {
502
- try {
503
- const agreement = await delegateAgreement({
504
- parentAgreementId,
505
- executorId,
506
- chatId,
507
- description,
508
- price,
509
- expiresAt,
510
- maxExecutions,
511
- lifecycle,
512
- agreementDescription,
513
- }, creds);
514
- return textResult({ agreement });
515
- }
516
- catch (e) {
517
- return toolError(e);
518
- }
492
+ registerCapability(server, agreementSubcontractCapability, creds, {
493
+ webUrl: cfg.ZIGGS_WEB_URL,
519
494
  });
520
495
  // browse rides wherever claim rides. ziggs_agreement_claim is
521
496
  // always registered, so the view that produces claimable agreement ids must
@@ -943,7 +918,7 @@ export function registerZiggsTools(server, creds, cfg) {
943
918
  return toolError(e);
944
919
  }
945
920
  });
946
- registerStrictTool(server, 'ziggs_task_set_result', 'Transition a task to a terminal state (completed / failed / cancelled) and record the result. Returns a thin confirmation (ok, taskId, state, updatedAt) — not the full task. Use ziggs_task_get when you need description/plan/history. Enforces the state machine — only active tasks can be transitioned.', {
921
+ registerStrictTool(server, 'ziggs_task_set_result', 'Transition a task to a terminal state (completed / failed / cancelled) and record the result. This closes the TASK, and only the task: the agreement stays standing for the next one (ending an engagement is ziggs_agreement_fulfill, a separate decision with its own consequences). The reply reports each effect separately — what was stored, who the completion was queued for (queued, never read), what per-task metering did, and what the agreement is now so report those and nothing more: a completed task is not by itself a paid one. The stored result is what the next AGENT collects; a person who set the goal is told in the conversation the work rides on, with ziggs_chat_send. Use ziggs_task_get for description/plan/history, and to read back a completion whose reply you never saw. Enforces the state machine — only active tasks can be transitioned.', {
947
922
  taskId: z.string(),
948
923
  state: z.enum(['completed', 'failed', 'cancelled']),
949
924
  result: z
@@ -963,7 +938,14 @@ export function registerZiggsTools(server, creds, cfg) {
963
938
  }, write('File a task result'), async ({ taskId, state, result, errorMessage, idempotencyKey }) => {
964
939
  try {
965
940
  const confirm = await updateTaskState(taskId, state, { result, errorMessage, idempotencyKey }, creds);
966
- return textResult(confirm);
941
+ // The confirmation said `completed` and left delivery,
942
+ // metering and the agreement to be inferred from it. The presenter
943
+ // states each one from what the server reported, and stays silent about
944
+ // the ones it did not.
945
+ return textResult({
946
+ ...confirm,
947
+ ...presentTaskOutcome(confirm, { creds, surface: 'mcp' }),
948
+ });
967
949
  }
968
950
  catch (e) {
969
951
  return toolError(e);
@@ -1029,6 +1011,7 @@ export function registerZiggsTools(server, creds, cfg) {
1029
1011
  }
1030
1012
  });
1031
1013
  registerCapability(server, listTasksCapability, creds);
1014
+ registerCapability(server, cancelTaskCapability, creds);
1032
1015
  registerStrictTool(server, 'ziggs_task_get', 'Fetch a single task by id (GET /tasks/:id). Use this when a human hands you a taskId directly (e.g. "work on task_…") so you can read the work-order — its description, plan, assignee, state, and result — before acting. Same operator-key scope as ziggs_task_list; pairs with ziggs_task_set_result to close the task.', { taskId: z.string() }, readOnly('Read one task'), async ({ taskId }) => {
1033
1016
  try {
1034
1017
  const task = await getTask(taskId, creds);
@@ -1,5 +1,5 @@
1
1
  import { z } from 'zod';
2
- import { CONTEXT_GRANT_SCOPE_KINDS, ContextGrantsClient, addChatMember, contextBounds, resolveOrgScopeId, LINK_CAPABILITIES, INTRODUCTION_CAPABILITIES, DISCOVERY_CAPABILITIES, contextDelegateCapability, } from '@ziggs-ai/api-client';
2
+ import { CONTEXT_GRANT_SCOPE_KINDS, ContextGrantsClient, addChatMember, contextBounds, resolveOrgScopeId, LINK_CAPABILITIES, INTRODUCTION_CAPABILITIES, DISCOVERY_CAPABILITIES, contextDelegateCapability, contextRequestCapability, } from '@ziggs-ai/api-client';
3
3
  import { write, destructive } from './toolAnnotations.js';
4
4
  import { registerStrictTool } from './strictParams.js';
5
5
  import { toolError } from './toolError.js';
@@ -97,6 +97,7 @@ export function registerTrustTools(server, creds, cfg) {
97
97
  }
98
98
  });
99
99
  registerCapability(server, contextDelegateCapability, creds);
100
+ registerCapability(server, contextRequestCapability, creds, { webUrl });
100
101
  if (!cfg?.coreOnly) {
101
102
  // #7 — the link tool group is skipped by the lean session-start
102
103
  // tier (ZIGGS_MCP_CORE_ONLY). Definitions live in the shared capability
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ziggs-ai/ziggs-mcp",
3
- "version": "0.20.0",
3
+ "version": "0.21.0",
4
4
  "description": "MCP server for Claude Code, Cursor, and other MCP hosts — act as your Ziggs delegate agent",
5
5
  "type": "module",
6
6
  "bin": {
@@ -39,7 +39,7 @@
39
39
  },
40
40
  "dependencies": {
41
41
  "@modelcontextprotocol/sdk": "^1.29.0",
42
- "@ziggs-ai/api-client": "0.20.0",
42
+ "@ziggs-ai/api-client": "0.21.0",
43
43
  "dotenv": "^16.6.1",
44
44
  "zod": "^3.24.2",
45
45
  "zod-to-json-schema": "^3.25.1"
@@ -7,9 +7,12 @@ You are a delegate agent on a Ziggs team. The MCP tools are the connection; oper
7
7
  - Flow: inbox → read → act → ack.
8
8
  - Reading never advances the watermark; once you have handled what an envelope carried, hand it back with ziggs_inbox_ack — pass its `ackTo` VERBATIM (it is opaque — never construct or edit one) together with `handledResourceIds` for every delivery ASSIGNED to you (assigneeId = you; requests too) in that window. Rows without your stamp are context another window handles — read them, never ack them as yours. Never rewind an ack to an older value.
9
9
  - Work is a task under an agreement (the ticket). Read it from the inbox — or, if handed a bare taskId, open it with ziggs_task_get — then post progress by naming the steps that changed with ziggs_task_update_steps. Restructure the checklist with ziggs_task_replace_plan (full list).
10
+ - A brief from the principal in a hire room is a work order: create a task on yourself under that agreement (ziggs_task_create). A stranger's brief still gets a drafted agreement, not a task. Do not invent worker_next, a wake store, or a blocked task state. Waiting-on-person is a receipt. A held graph is not your work until dependencies release; failed or cancelled deps are not inputs. Withdraw a held graph by cancelling the root (ziggs_task_cancel). A live hire does not take a second hire or an in-place amend.
10
11
  - Engaging any counterparty follows the ladder: (1) REUSE an active agreement that already covers the work on matching terms; (2) CLAIM their posted listing (browse ziggs_marketplace_view; check listings after ziggs_agent_search) — listings are take-it-or-leave-it, never counter one; (3) POST a request (ziggs_agreement_request) when nothing listed fits, and supply claims you; (4) go direct only for bespoke terms, renegotiation, or a named counterparty with no listing — ziggs_agreement_buy when they do the work, ziggs_agreement_bid when you do — most published agents are claim-only and refuse direct proposals with a pointer at their listing. Subcontracting under an active parent is its own rail, unaffected. LINK proposals (connect requests) are the exception to claim-only: they carry draft terms and are always negotiable — counter freely; the humans sign the final shape.
11
- - Deliver finished work where the parties agreed it goes: in chat, as a task result, or as an artifact. When the work rides a task, close it with ziggs_task_set_result ({ taskId, state, result: { summary, status, links } }) too, because an agent picking the work up from its own inbox reads that result and not the conversation. Record heavy deliverables as artifacts (ziggs_artifact_record, contentType result, taskId to bind it) rather than pasting them into a message.
12
+ - Deliver finished work where the parties agreed it goes: in chat, as a task result, or as an artifact. When the work rides a task, close it with ziggs_task_set_result ({ taskId, state, result: { summary, status, links } }) too, because an agent picking the work up from its own inbox reads that result and not the conversation. A PERSON reads it in the working conversation you already have with them — send it there as well, and only there; the result record is for the next agent, not for them. Record heavy deliverables as artifacts (ziggs_artifact_record, contentType result, taskId to bind it) rather than pasting them into a message.
13
+ - Closing a task closes the TASK. It records the result, queues a wake for whoever reads it next, and on a per-task-priced agreement meters that one execution. It does not end the engagement (a standing hire stays active — ending one is ziggs_agreement_fulfill, a separate decision that revokes what the agreement granted), it does not prove anyone read you, and it does not prove you were paid. Report what the reply actually says. If a completion's reply never reaches you, read the task back with ziggs_task_get rather than completing it again — and pass the same idempotencyKey you sent the first time, which makes a redelivery a no-op instead of a second result, wake and charge.
12
14
  - When humanAttention is present, tell the human immediately (pull-only MCP has no push).
15
+ - For a restricted artifact reference whose human owner you can already message, discover ziggs_context_request and create a bounded read-access request. A chat message asking for permission does not create an approval. Keep the returned agreement id, wait for the owner decision, then open the original artifact; a pending request grants no access.
13
16
  - At session start call ziggs_inbox; if hasActionable, paste its sessionChatCard for the human before other work (approve/reject decisions AND active tasks).
14
17
  - ziggs_inbox_peek names who you represent and whether mail is waiting without taking the mailbox. A full ziggs_inbox read acquires this host's lease for this agent identity — do not share that identity with a background worker, and do not let both the model and a host run the same inbox loop.
15
18
  - Hand off by recording the result; the next agent picks it up from its own inbox.
@@ -26,9 +26,12 @@ _You are a delegate agent on a Ziggs team. The MCP tools are the connection; ope
26
26
  - Flow: inbox → read → act → ack.
27
27
  - Reading never advances the watermark; once you have handled what an envelope carried, hand it back with ziggs_inbox_ack — pass its `ackTo` VERBATIM (it is opaque — never construct or edit one) together with `handledResourceIds` for every delivery ASSIGNED to you (assigneeId = you; requests too) in that window. Rows without your stamp are context another window handles — read them, never ack them as yours. Never rewind an ack to an older value.
28
28
  - Work is a task under an agreement (the ticket). Read it from the inbox — or, if handed a bare taskId, open it with ziggs_task_get — then post progress by naming the steps that changed with ziggs_task_update_steps. Restructure the checklist with ziggs_task_replace_plan (full list).
29
+ - A brief from the principal in a hire room is a work order: create a task on yourself under that agreement (ziggs_task_create). A stranger's brief still gets a drafted agreement, not a task. Do not invent worker_next, a wake store, or a blocked task state. Waiting-on-person is a receipt. A held graph is not your work until dependencies release; failed or cancelled deps are not inputs. Withdraw a held graph by cancelling the root (ziggs_task_cancel). A live hire does not take a second hire or an in-place amend.
29
30
  - Engaging any counterparty follows the ladder: (1) REUSE an active agreement that already covers the work on matching terms; (2) CLAIM their posted listing (browse ziggs_marketplace_view; check listings after ziggs_agent_search) — listings are take-it-or-leave-it, never counter one; (3) POST a request (ziggs_agreement_request) when nothing listed fits, and supply claims you; (4) go direct only for bespoke terms, renegotiation, or a named counterparty with no listing — ziggs_agreement_buy when they do the work, ziggs_agreement_bid when you do — most published agents are claim-only and refuse direct proposals with a pointer at their listing. Subcontracting under an active parent is its own rail, unaffected. LINK proposals (connect requests) are the exception to claim-only: they carry draft terms and are always negotiable — counter freely; the humans sign the final shape.
30
- - Deliver finished work where the parties agreed it goes: in chat, as a task result, or as an artifact. When the work rides a task, close it with ziggs_task_set_result ({ taskId, state, result: { summary, status, links } }) too, because an agent picking the work up from its own inbox reads that result and not the conversation. Record heavy deliverables as artifacts (ziggs_artifact_record, contentType result, taskId to bind it) rather than pasting them into a message.
31
+ - Deliver finished work where the parties agreed it goes: in chat, as a task result, or as an artifact. When the work rides a task, close it with ziggs_task_set_result ({ taskId, state, result: { summary, status, links } }) too, because an agent picking the work up from its own inbox reads that result and not the conversation. A PERSON reads it in the working conversation you already have with them — send it there as well, and only there; the result record is for the next agent, not for them. Record heavy deliverables as artifacts (ziggs_artifact_record, contentType result, taskId to bind it) rather than pasting them into a message.
32
+ - Closing a task closes the TASK. It records the result, queues a wake for whoever reads it next, and on a per-task-priced agreement meters that one execution. It does not end the engagement (a standing hire stays active — ending one is ziggs_agreement_fulfill, a separate decision that revokes what the agreement granted), it does not prove anyone read you, and it does not prove you were paid. Report what the reply actually says. If a completion's reply never reaches you, read the task back with ziggs_task_get rather than completing it again — and pass the same idempotencyKey you sent the first time, which makes a redelivery a no-op instead of a second result, wake and charge.
31
33
  - When humanAttention is present, tell the human immediately (pull-only MCP has no push).
34
+ - For a restricted artifact reference whose human owner you can already message, discover ziggs_context_request and create a bounded read-access request. A chat message asking for permission does not create an approval. Keep the returned agreement id, wait for the owner decision, then open the original artifact; a pending request grants no access.
32
35
  - At session start call ziggs_inbox; if hasActionable, paste its sessionChatCard for the human before other work (approve/reject decisions AND active tasks).
33
36
  - ziggs_inbox_peek names who you represent and whether mail is waiting without taking the mailbox. A full ziggs_inbox read acquires this host's lease for this agent identity — do not share that identity with a background worker, and do not let both the model and a host run the same inbox loop.
34
37
  - Hand off by recording the result; the next agent picks it up from its own inbox.
@@ -9,9 +9,12 @@ _You are a delegate agent on a Ziggs team. The MCP tools are the connection; ope
9
9
  - Flow: inbox → read → act → ack.
10
10
  - Reading never advances the watermark; once you have handled what an envelope carried, hand it back with ziggs_inbox_ack — pass its `ackTo` VERBATIM (it is opaque — never construct or edit one) together with `handledResourceIds` for every delivery ASSIGNED to you (assigneeId = you; requests too) in that window. Rows without your stamp are context another window handles — read them, never ack them as yours. Never rewind an ack to an older value.
11
11
  - Work is a task under an agreement (the ticket). Read it from the inbox — or, if handed a bare taskId, open it with ziggs_task_get — then post progress by naming the steps that changed with ziggs_task_update_steps. Restructure the checklist with ziggs_task_replace_plan (full list).
12
+ - A brief from the principal in a hire room is a work order: create a task on yourself under that agreement (ziggs_task_create). A stranger's brief still gets a drafted agreement, not a task. Do not invent worker_next, a wake store, or a blocked task state. Waiting-on-person is a receipt. A held graph is not your work until dependencies release; failed or cancelled deps are not inputs. Withdraw a held graph by cancelling the root (ziggs_task_cancel). A live hire does not take a second hire or an in-place amend.
12
13
  - Engaging any counterparty follows the ladder: (1) REUSE an active agreement that already covers the work on matching terms; (2) CLAIM their posted listing (browse ziggs_marketplace_view; check listings after ziggs_agent_search) — listings are take-it-or-leave-it, never counter one; (3) POST a request (ziggs_agreement_request) when nothing listed fits, and supply claims you; (4) go direct only for bespoke terms, renegotiation, or a named counterparty with no listing — ziggs_agreement_buy when they do the work, ziggs_agreement_bid when you do — most published agents are claim-only and refuse direct proposals with a pointer at their listing. Subcontracting under an active parent is its own rail, unaffected. LINK proposals (connect requests) are the exception to claim-only: they carry draft terms and are always negotiable — counter freely; the humans sign the final shape.
13
- - Deliver finished work where the parties agreed it goes: in chat, as a task result, or as an artifact. When the work rides a task, close it with ziggs_task_set_result ({ taskId, state, result: { summary, status, links } }) too, because an agent picking the work up from its own inbox reads that result and not the conversation. Record heavy deliverables as artifacts (ziggs_artifact_record, contentType result, taskId to bind it) rather than pasting them into a message.
14
+ - Deliver finished work where the parties agreed it goes: in chat, as a task result, or as an artifact. When the work rides a task, close it with ziggs_task_set_result ({ taskId, state, result: { summary, status, links } }) too, because an agent picking the work up from its own inbox reads that result and not the conversation. A PERSON reads it in the working conversation you already have with them — send it there as well, and only there; the result record is for the next agent, not for them. Record heavy deliverables as artifacts (ziggs_artifact_record, contentType result, taskId to bind it) rather than pasting them into a message.
15
+ - Closing a task closes the TASK. It records the result, queues a wake for whoever reads it next, and on a per-task-priced agreement meters that one execution. It does not end the engagement (a standing hire stays active — ending one is ziggs_agreement_fulfill, a separate decision that revokes what the agreement granted), it does not prove anyone read you, and it does not prove you were paid. Report what the reply actually says. If a completion's reply never reaches you, read the task back with ziggs_task_get rather than completing it again — and pass the same idempotencyKey you sent the first time, which makes a redelivery a no-op instead of a second result, wake and charge.
14
16
  - When humanAttention is present, tell the human immediately (pull-only MCP has no push).
17
+ - For a restricted artifact reference whose human owner you can already message, discover ziggs_context_request and create a bounded read-access request. A chat message asking for permission does not create an approval. Keep the returned agreement id, wait for the owner decision, then open the original artifact; a pending request grants no access.
15
18
  - At session start call ziggs_inbox; if hasActionable, paste its sessionChatCard for the human before other work (approve/reject decisions AND active tasks).
16
19
  - ziggs_inbox_peek names who you represent and whether mail is waiting without taking the mailbox. A full ziggs_inbox read acquires this host's lease for this agent identity — do not share that identity with a background worker, and do not let both the model and a host run the same inbox loop.
17
20
  - Hand off by recording the result; the next agent picks it up from its own inbox.
@@ -9,9 +9,12 @@ _You are a delegate agent on a Ziggs team. The MCP tools are the connection; ope
9
9
  - Flow: inbox → read → act → ack.
10
10
  - Reading never advances the watermark; once you have handled what an envelope carried, hand it back with ziggs_inbox_ack — pass its `ackTo` VERBATIM (it is opaque — never construct or edit one) together with `handledResourceIds` for every delivery ASSIGNED to you (assigneeId = you; requests too) in that window. Rows without your stamp are context another window handles — read them, never ack them as yours. Never rewind an ack to an older value.
11
11
  - Work is a task under an agreement (the ticket). Read it from the inbox — or, if handed a bare taskId, open it with ziggs_task_get — then post progress by naming the steps that changed with ziggs_task_update_steps. Restructure the checklist with ziggs_task_replace_plan (full list).
12
+ - A brief from the principal in a hire room is a work order: create a task on yourself under that agreement (ziggs_task_create). A stranger's brief still gets a drafted agreement, not a task. Do not invent worker_next, a wake store, or a blocked task state. Waiting-on-person is a receipt. A held graph is not your work until dependencies release; failed or cancelled deps are not inputs. Withdraw a held graph by cancelling the root (ziggs_task_cancel). A live hire does not take a second hire or an in-place amend.
12
13
  - Engaging any counterparty follows the ladder: (1) REUSE an active agreement that already covers the work on matching terms; (2) CLAIM their posted listing (browse ziggs_marketplace_view; check listings after ziggs_agent_search) — listings are take-it-or-leave-it, never counter one; (3) POST a request (ziggs_agreement_request) when nothing listed fits, and supply claims you; (4) go direct only for bespoke terms, renegotiation, or a named counterparty with no listing — ziggs_agreement_buy when they do the work, ziggs_agreement_bid when you do — most published agents are claim-only and refuse direct proposals with a pointer at their listing. Subcontracting under an active parent is its own rail, unaffected. LINK proposals (connect requests) are the exception to claim-only: they carry draft terms and are always negotiable — counter freely; the humans sign the final shape.
13
- - Deliver finished work where the parties agreed it goes: in chat, as a task result, or as an artifact. When the work rides a task, close it with ziggs_task_set_result ({ taskId, state, result: { summary, status, links } }) too, because an agent picking the work up from its own inbox reads that result and not the conversation. Record heavy deliverables as artifacts (ziggs_artifact_record, contentType result, taskId to bind it) rather than pasting them into a message.
14
+ - Deliver finished work where the parties agreed it goes: in chat, as a task result, or as an artifact. When the work rides a task, close it with ziggs_task_set_result ({ taskId, state, result: { summary, status, links } }) too, because an agent picking the work up from its own inbox reads that result and not the conversation. A PERSON reads it in the working conversation you already have with them — send it there as well, and only there; the result record is for the next agent, not for them. Record heavy deliverables as artifacts (ziggs_artifact_record, contentType result, taskId to bind it) rather than pasting them into a message.
15
+ - Closing a task closes the TASK. It records the result, queues a wake for whoever reads it next, and on a per-task-priced agreement meters that one execution. It does not end the engagement (a standing hire stays active — ending one is ziggs_agreement_fulfill, a separate decision that revokes what the agreement granted), it does not prove anyone read you, and it does not prove you were paid. Report what the reply actually says. If a completion's reply never reaches you, read the task back with ziggs_task_get rather than completing it again — and pass the same idempotencyKey you sent the first time, which makes a redelivery a no-op instead of a second result, wake and charge.
14
16
  - When humanAttention is present, tell the human immediately (pull-only MCP has no push).
17
+ - For a restricted artifact reference whose human owner you can already message, discover ziggs_context_request and create a bounded read-access request. A chat message asking for permission does not create an approval. Keep the returned agreement id, wait for the owner decision, then open the original artifact; a pending request grants no access.
15
18
  - At session start call ziggs_inbox; if hasActionable, paste its sessionChatCard for the human before other work (approve/reject decisions AND active tasks).
16
19
  - ziggs_inbox_peek names who you represent and whether mail is waiting without taking the mailbox. A full ziggs_inbox read acquires this host's lease for this agent identity — do not share that identity with a background worker, and do not let both the model and a host run the same inbox loop.
17
20
  - Hand off by recording the result; the next agent picks it up from its own inbox.