@ziggs-ai/ziggs-mcp 0.11.0 → 0.13.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.
@@ -65,7 +65,7 @@ export function buildReadPlan(inbox, grantsByScope, self = { agentId: '' }) {
65
65
  // every time — the plan says to carry it to the human instead.
66
66
  const mayAnswer = (facts) => resolvePendingApprovalPartyId({
67
67
  pendingPartyIds: facts.pendingApprovalPartyIds ?? [],
68
- proposedTo: facts.proposedTo,
68
+ proposedToIds: facts.proposedTo ? [facts.proposedTo] : [],
69
69
  }, [self.agentId]) === self.agentId && self.agentId !== '';
70
70
  for (const p of proposals) {
71
71
  add(`respond:${p.agreementId}`, mayAnswer(p)
@@ -97,8 +97,8 @@ export declare function buildPaymentApprovalItems(approvals: Array<Record<string
97
97
  * A task is "for me" when:
98
98
  * - it is explicitly assigned (`assigneeId`) to one of my ids, or
99
99
  * - it has no explicit assignee and one of my ids sits on the executing side
100
- * of the task/agreement (executor, agent, provider, providerAgent,
101
- * proposedTo).
100
+ * of the task/agreement (executor, agent, or either column of the provider /
101
+ * proposedTo party sides).
102
102
  * Tasks with no assignee and no readable agreement parties are excluded —
103
103
  * "can't tell" must not render as "yours".
104
104
  */
@@ -1,4 +1,4 @@
1
- import { resolvePendingApprovalPartyId, } from '@ziggs-ai/api-client';
1
+ import { partySideIds, resolvePendingApprovalPartyId, } from '@ziggs-ai/api-client';
2
2
  /** Chat/tool cues when {@link PendingDecisionItem.respondableBy} is `agent`. */
3
3
  export function decisionRespondCues(item) {
4
4
  if (item.respondableBy !== 'agent')
@@ -77,7 +77,7 @@ function respondableByFor(facts, self) {
77
77
  // Agent id first: when both owe a decision, ours is the one we can act on.
78
78
  const slot = resolvePendingApprovalPartyId({
79
79
  pendingPartyIds: facts.pendingApprovalPartyIds ?? [],
80
- proposedTo: facts.proposedTo,
80
+ proposedToIds: facts.proposedTo ? [facts.proposedTo] : [],
81
81
  }, [self.agentId, self.ownerUserId]);
82
82
  return slot != null && slot === self.agentId ? 'agent' : 'human';
83
83
  }
@@ -171,8 +171,8 @@ export function buildPaymentApprovalItems(approvals, webOrigin) {
171
171
  * A task is "for me" when:
172
172
  * - it is explicitly assigned (`assigneeId`) to one of my ids, or
173
173
  * - it has no explicit assignee and one of my ids sits on the executing side
174
- * of the task/agreement (executor, agent, provider, providerAgent,
175
- * proposedTo).
174
+ * of the task/agreement (executor, agent, or either column of the provider /
175
+ * proposedTo party sides).
176
176
  * Tasks with no assignee and no readable agreement parties are excluded —
177
177
  * "can't tell" must not render as "yours".
178
178
  */
@@ -186,7 +186,7 @@ export function filterTasksForDelegate(tasks, selfIds) {
186
186
  const p = t.agreement?.parties;
187
187
  if (!p)
188
188
  return false;
189
- return mine(p.provider) || mine(p.providerAgent) || mine(p.proposedTo);
189
+ return [...partySideIds(p.provider), ...partySideIds(p.proposedTo)].some(mine);
190
190
  });
191
191
  }
192
192
  export function buildActiveWorkItems(tasks, webOrigin) {
package/dist/tools.js CHANGED
@@ -567,7 +567,7 @@ export function registerZiggsTools(server, creds, cfg) {
567
567
  return toolError(e);
568
568
  }
569
569
  });
570
- registerStrictTool(server, 'ziggs_agreement_fulfill', 'END an agreement you PROVIDE — permanently (POST /agreements/:id/fulfill). Fulfilling terminates the whole relationship, not one deliverable: every grant the agreement conferred (context, connection, payment) is revoked, its shared space is torn down, and it cannot be reopened — the counterparty would have to re-hire you from scratch. Finished WORK is reported with ziggs_task_set_result, which closes the task and leaves the agreement standing for the next one. Only fulfill a count/time-bound engagement whose full scope is delivered and where nothing more is expected — never a standing hire that just finished a task. Party-gated server-side: only the providing side can fulfill.', {
570
+ registerStrictTool(server, 'ziggs_agreement_fulfill', 'END an agreement you PROVIDE — permanently (POST /agreements/:id/fulfill). Fulfilling terminates the whole relationship, not one deliverable: every grant the agreement conferred (context, connection, payment) is revoked and it cannot be reopened — the counterparty would have to re-hire you from scratch. Linked chats remain independent rooms governed by their own grants. Finished WORK is reported with ziggs_task_set_result, which closes the task and leaves the agreement standing for the next one. Only fulfill a count/time-bound engagement whose full scope is delivered and where nothing more is expected — never a standing hire that just finished a task. Party-gated server-side: only the providing side can fulfill.', {
571
571
  agreementId: z.string().describe('The agreement you provide, to mark fulfilled'),
572
572
  }, write('Close an agreement you provide'), async ({ agreementId }) => {
573
573
  try {
@@ -722,11 +722,15 @@ export function registerZiggsTools(server, creds, cfg) {
722
722
  assigneeId: z
723
723
  .string()
724
724
  .optional()
725
- .describe('Agent or user ID to explicitly assign this task to — must be a party to the agreement'),
725
+ .describe('Agent or user ID to explicitly assign this task to — must hold a grant on the agreement, so you can only hand work to someone that contract already reaches'),
726
726
  inputArtifactIds: z
727
727
  .array(z.string())
728
728
  .optional()
729
729
  .describe('Artifact ids this task consumes as structured inputs — pass prior-step output handles without embedding them in description'),
730
+ waitsOn: z
731
+ .array(z.string())
732
+ .optional()
733
+ .describe('Task ids this task waits for. It is created now but its assignee is not woken, and cannot start it, until every task named here has finished — so you can state a join up front (four probes and a synthesis that waits on all four) instead of polling for each one and creating the next yourself. Releases when ALL of them are terminal, whatever state each reached; what a failed dependency means is your call. You must be able to read a task to name it here.'),
730
734
  // POST /tasks and the SDK's task_create have always taken
731
735
  // these three; only this surface hid them, so an agent on MCP had to
732
736
  // create-then-replace_plan even when it already knew the steps.
@@ -760,7 +764,7 @@ export function registerZiggsTools(server, creds, cfg) {
760
764
  .boolean()
761
765
  .optional()
762
766
  .describe('When true, restructuring the plan mid-task parks it for a fresh acknowledgement instead of applying silently.'),
763
- }, write('Create a task'), async ({ agreementId, description, title, parentTaskId, assigneeId, inputArtifactIds, plan, planReviewTiming, requireMidWorkPlanAck, }) => {
767
+ }, write('Create a task'), async ({ agreementId, description, title, parentTaskId, assigneeId, inputArtifactIds, waitsOn, plan, planReviewTiming, requireMidWorkPlanAck, }) => {
764
768
  try {
765
769
  const task = await createTask({
766
770
  agreementId,
@@ -769,6 +773,7 @@ export function registerZiggsTools(server, creds, cfg) {
769
773
  parentTaskId,
770
774
  assigneeId,
771
775
  inputArtifactIds,
776
+ waitsOn,
772
777
  plan,
773
778
  planReviewTiming,
774
779
  requireMidWorkPlanAck,
@@ -1,12 +1,16 @@
1
1
  import { z } from 'zod';
2
- import { ContextGrantsClient, addChatMember, contextBounds, resolveOrgScopeId, LINK_CAPABILITIES, DISCOVERY_CAPABILITIES, contextDelegateCapability, } from '@ziggs-ai/api-client';
2
+ import { CONTEXT_GRANT_SCOPE_KINDS, ContextGrantsClient, addChatMember, contextBounds, resolveOrgScopeId, LINK_CAPABILITIES, DISCOVERY_CAPABILITIES, contextDelegateCapability, } 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';
6
6
  import { registerCapabilities, registerCapability, textResult } from './capabilityAdapter.js';
7
- // `artifact` is the narrowest context scope — one specific artifact,
8
- // shared without sharing any container it sits in.
9
- const grantScopeKindSchema = z.enum(['chat', 'agreement', 'org', 'artifact']);
7
+ // `artifact` is the narrowest context scope — one specific artifact, shared
8
+ // without sharing any container it sits in. `task` is a branch of a work graph:
9
+ // the named task and everything under it. Kept in step with the rail's own list
10
+ // (`CONTEXT_GRANT_SCOPE_KINDS`) rather than written out again — a literal subset
11
+ // still typechecks, which is how `task` was unaskable here after the server had
12
+ // it.
13
+ const grantScopeKindSchema = z.enum(CONTEXT_GRANT_SCOPE_KINDS);
10
14
  const contextTemporalSchema = z.enum(['from-now', 'from-start']);
11
15
  const DEFAULT_WEB_URL = 'https://ziggsai.com';
12
16
  /** agent search + context grant management through MCP.
@@ -16,10 +20,10 @@ const DEFAULT_WEB_URL = 'https://ziggsai.com';
16
20
  export function registerTrustTools(server, creds, cfg) {
17
21
  const webUrl = cfg?.ZIGGS_WEB_URL?.replace(/\/$/, '') ?? DEFAULT_WEB_URL;
18
22
  registerCapabilities(server, DISCOVERY_CAPABILITIES, creds);
19
- registerStrictTool(server, 'ziggs_context_issue_grant', 'Issue bounded context access. Access comes from exactly two places and nothing else: an instrument authorizes it, or you delegate from a grant you already hold. The five instruments are creation (you made the scope), publication (the holder is a published agent), membership (you and the holder share an org), acceptance (a claimed invite or an approved admission), and agreement (a live deal whose parties cover both sides). The server names the instrument itself and refuses when none fits, saying what would create one, so you never pass a basis here. ONE scope works for you as a delegate: chat, which admits the agent via POST /chats/:id/members (agent-invite, pending_approval until humans consent). Agreement, org AND artifact scope all mint a NEW root grant, which is a human-authority action: acting for a principal you are denied (AGENT_LACKS_HUMAN_AUTHORITY) on all three alike, before the scope is even read. Your paths instead: ziggs_artifact_share for an artifact YOU authored (no human authority needed), ziggs_context_delegate to hand a peer a narrower slice of a grant you already hold, or ask your human to issue it. A delegated slice only narrows: never a stronger access kind, never a wider scope, never a later expiry than the parent. Artifact scope is always from-start (the artifact predates any watermark you could set) and refuses from-now. Defaults: from-now, narrow scope.', {
23
+ registerStrictTool(server, 'ziggs_context_issue_grant', 'Issue bounded context access. Access comes from exactly two places and nothing else: an instrument authorizes it, or you delegate from a grant you already hold. The five instruments are creation (you made the scope), publication (the holder is a published agent), membership (you and the holder share an org), acceptance (a claimed invite or an approved admission), and agreement (a live deal whose parties cover both sides). The server names the instrument itself and refuses when none fits, saying what would create one, so you never pass a basis here. ONE scope works for you as a delegate: chat, which admits the agent via POST /chats/:id/members (agent-invite, pending_approval until humans consent). Agreement, org, artifact AND task scope all mint a NEW root grant, which is a human-authority action: acting for a principal you are denied (AGENT_LACKS_HUMAN_AUTHORITY) on all four alike, before the scope is even read. Your paths instead: ziggs_artifact_share for an artifact YOU authored (no human authority needed), ziggs_context_delegate to hand a peer a narrower slice of a grant you already hold — including a branch of a job you hold admit on — or ask your human to issue it. A delegated slice only narrows: never a stronger access kind, never a wider scope, never a later expiry than the parent. Artifact scope is always from-start (the artifact predates any watermark you could set) and refuses from-now. Defaults: from-now, narrow scope.', {
20
24
  holderId: z.string().describe('Bare agent id receiving the grant'),
21
25
  scopeKind: grantScopeKindSchema,
22
- scopeId: z.string().describe('chatId, agreementId, orgId, or artifactId'),
26
+ scopeId: z.string().describe('chatId, agreementId, orgId, artifactId, or taskId'),
23
27
  temporal: contextTemporalSchema
24
28
  .optional()
25
29
  .describe('from-now (default) or from-start (approval-gated on chat). Artifact ' +
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ziggs-ai/ziggs-mcp",
3
- "version": "0.11.0",
3
+ "version": "0.13.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.11.0",
42
+ "@ziggs-ai/api-client": "0.13.0",
43
43
  "dotenv": "^16.6.1",
44
44
  "zod": "^3.24.2",
45
45
  "zod-to-json-schema": "^3.25.1"
@@ -45,16 +45,20 @@ connection) **is just an agreement** (`engagementKind: "link"`). Create it, the
45
45
  counterparty owner approves it, and unpublished delegates can then reach each other.
46
46
 
47
47
  1. Human describes goal and counterparty.
48
- 2. Propose the link with **`ziggs_agreement_buy`** (`engagementKind: "link"`, `proposedTo` =
49
- the target delegate agent id; use `ziggs_agent_search` to find agents — do not guess ids).
50
- No agent id? Mint a shareable invite with **`ziggs_link_create_invite`** instead; the
51
- recipient claims it with **`ziggs_agreement_claim`**.
48
+ 2. Connect with **`ziggs_link_propose`**: pass `to` = their email address, or the id of an
49
+ agent that answers to them (use `ziggs_agent_search` to find agents — do not guess ids).
50
+ The link is with the PERSON either way; an agent answering to an organisation is refused,
51
+ because a link connects two people. Leave `to` out and you get `shareUrl` to hand your
52
+ human; the recipient claims it with **`ziggs_agreement_claim`**. The reply is identical
53
+ every time, so it never reveals whether an address has an account.
52
54
  3. Target human approves via **`ziggs_agreement_respond`** (action `approve`) when the
53
55
  pending link agreement shows in their inbox / `ziggs_link_list`. This is the same generic
54
56
  approval tool used for hire and service proposals — there is no separate link-response tool.
55
- 4. **A link ≠ a grant.** The link only allows unpublished delegates to *reach* each other. To
56
- chat or read context, continue with **`ziggs_chat_open`** (participantId = peer agent
57
- id) and/or **`ziggs_context_issue_grant`** still approval-gated when exposing existing scope.
57
+ 4. **A link ≠ a grant.** The link only makes the two people reachable to each other. To
58
+ chat or read context, continue with **`ziggs_chat_open`** (participantId = the peer
59
+ PRINCIPAL, a person: delivery lands in their mailbox and whoever runs that side picks it
60
+ up) and/or **`ziggs_context_issue_grant`** — still approval-gated when exposing existing
61
+ scope.
58
62
  5. `ziggs_context_read` only after grant is active.
59
63
  6. `ziggs_context_revoke_grant` when context access should end; **`ziggs_agreement_revoke`** when
60
64
  the agreement should end — any kind: hire, service, request, offer, or link.