@ziggs-ai/ziggs-mcp 0.4.0 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/inboxToolResult.d.ts +8 -8
- package/dist/inboxToolResult.js +35 -78
- package/dist/protocol/delegateProtocol.d.ts +1 -1
- package/dist/protocol/delegateProtocol.js +1 -1
- package/dist/tools.js +32 -15
- package/package.json +3 -3
- package/skills/ziggs/.cursorrules +1 -1
- package/skills/ziggs/SKILL.md +9 -9
- package/skills/ziggs/references/inbox-rhythm.md +37 -21
- package/skills/ziggs/references/reporting-convention.md +1 -1
|
@@ -21,16 +21,15 @@ export interface ReadPlanResult {
|
|
|
21
21
|
/**
|
|
22
22
|
* ZIG-634: replace the free-text `nextActions` hints with typed `readPlan`
|
|
23
23
|
* call objects — tool name + pre-filled args — so the most common loop
|
|
24
|
-
* (inbox → read
|
|
24
|
+
* (inbox → read what was addressed to you → ack) needs no guesswork.
|
|
25
25
|
*
|
|
26
26
|
* Safe by construction: every entry is synthesized purely from fields already
|
|
27
|
-
* on the envelope (
|
|
28
|
-
* is read and no new permission check runs — this is the same information
|
|
29
|
-
* caller already received, restated as runnable calls.
|
|
27
|
+
* on the envelope (delivery kinds and ids, the per-chat fold, ackTo). No new
|
|
28
|
+
* data is read and no new permission check runs — this is the same information
|
|
29
|
+
* the caller already received, restated as runnable calls.
|
|
30
30
|
*
|
|
31
31
|
* Mapping honours how reads resolve server-side: messages read only via chat,
|
|
32
|
-
* artifacts via chat or agreement.
|
|
33
|
-
* use the per-chat breakdown (ZIG-543) to name the chatIds.
|
|
32
|
+
* artifacts via chat or agreement.
|
|
34
33
|
*/
|
|
35
34
|
export declare function buildReadPlan(inbox: InboxEnvelope, grantsByScope?: Map<string, ScopeGrantTag>): ReadPlanResult;
|
|
36
35
|
/**
|
|
@@ -70,7 +69,8 @@ export declare function indexReachByScope(reach: GrantView[]): Map<string, Scope
|
|
|
70
69
|
* and append readPlan last so each inbox call self-narrates the follow-up
|
|
71
70
|
* calls (ZIG-634) without disturbing the leading humanAttention key.
|
|
72
71
|
*
|
|
73
|
-
* When `reach` (the caller's own live grants) is passed,
|
|
74
|
-
*
|
|
72
|
+
* When `reach` (the caller's own live grants) is passed, the read plan pins
|
|
73
|
+
* each read's covering grant (ZIG-635) so the agent can present
|
|
74
|
+
* X-Context-Grant-Id without a separate discover round-trip.
|
|
75
75
|
*/
|
|
76
76
|
export declare function formatInboxToolResult(inbox: InboxEnvelope, ack?: InboxAckResult | null, webOrigin?: string, activeTasks?: Task[], reach?: GrantView[], activeTasksError?: string): Record<string, unknown>;
|
package/dist/inboxToolResult.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { grantCaveat } from '@ziggs-ai/api-client';
|
|
2
2
|
import { formatPendingDecisionsPayload, resolveWebAppOrigin, } from './pendingDecisions.js';
|
|
3
|
-
/** Keep the plan bounded; the full
|
|
3
|
+
/** Keep the plan bounded; the full deliveries array still carries everything. */
|
|
4
4
|
const MAX_READ_PLAN = 12;
|
|
5
5
|
function readContextCall(type, kind, id, grantId) {
|
|
6
6
|
// ZIG-660: pin the covering grant so the read presents the right
|
|
@@ -15,25 +15,24 @@ function readContextCall(type, kind, id, grantId) {
|
|
|
15
15
|
/**
|
|
16
16
|
* ZIG-634: replace the free-text `nextActions` hints with typed `readPlan`
|
|
17
17
|
* call objects — tool name + pre-filled args — so the most common loop
|
|
18
|
-
* (inbox → read
|
|
18
|
+
* (inbox → read what was addressed to you → ack) needs no guesswork.
|
|
19
19
|
*
|
|
20
20
|
* Safe by construction: every entry is synthesized purely from fields already
|
|
21
|
-
* on the envelope (
|
|
22
|
-
* is read and no new permission check runs — this is the same information
|
|
23
|
-
* caller already received, restated as runnable calls.
|
|
21
|
+
* on the envelope (delivery kinds and ids, the per-chat fold, ackTo). No new
|
|
22
|
+
* data is read and no new permission check runs — this is the same information
|
|
23
|
+
* the caller already received, restated as runnable calls.
|
|
24
24
|
*
|
|
25
25
|
* Mapping honours how reads resolve server-side: messages read only via chat,
|
|
26
|
-
* artifacts via chat or agreement.
|
|
27
|
-
* use the per-chat breakdown (ZIG-543) to name the chatIds.
|
|
26
|
+
* artifacts via chat or agreement.
|
|
28
27
|
*/
|
|
29
28
|
export function buildReadPlan(inbox, grantsByScope) {
|
|
30
29
|
const proposals = inbox.proposalsAwaitingMe ?? [];
|
|
31
30
|
const connectionRequests = inbox.connectionRequestsAwaitingMe ?? [];
|
|
32
|
-
const
|
|
31
|
+
const deliveries = inbox.deliveries ?? [];
|
|
33
32
|
// ZIG-660: dedup by call signature so the same (type, via) can't appear
|
|
34
|
-
// twice when
|
|
35
|
-
//
|
|
36
|
-
//
|
|
33
|
+
// twice when several deliveries land in one chat. Collect candidates
|
|
34
|
+
// uncapped; the cap is applied once, after the ack is reserved, so the ack
|
|
35
|
+
// step always survives.
|
|
37
36
|
const candidates = [];
|
|
38
37
|
const seen = new Set();
|
|
39
38
|
const add = (key, call) => {
|
|
@@ -58,57 +57,35 @@ export function buildReadPlan(inbox, grantsByScope) {
|
|
|
58
57
|
why: 'connection request awaiting your response — wait for the human to approve/reject',
|
|
59
58
|
});
|
|
60
59
|
}
|
|
61
|
-
//
|
|
62
|
-
//
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
if (kind === '
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
if (
|
|
73
|
-
read('artifacts', 'chat',
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
// Messages resolve only via chat — name the chats from the breakdown.
|
|
77
|
-
for (const c of s.chats ?? []) {
|
|
78
|
-
if (c.newMessages)
|
|
79
|
-
read('messages', 'chat', c.chatId);
|
|
80
|
-
}
|
|
81
|
-
// Artifacts (incl. task-result artifacts) read directly via the agreement.
|
|
82
|
-
if (s.newArtifacts)
|
|
83
|
-
read('artifacts', 'agreement', id);
|
|
84
|
-
}
|
|
85
|
-
else {
|
|
86
|
-
// org: both messages and artifacts resolve per chat only.
|
|
87
|
-
for (const c of s.chats ?? []) {
|
|
88
|
-
if (c.newMessages)
|
|
89
|
-
read('messages', 'chat', c.chatId);
|
|
90
|
-
if (c.newArtifacts)
|
|
91
|
-
read('artifacts', 'chat', c.chatId);
|
|
92
|
-
}
|
|
60
|
+
// ZIG-635: pin the covering grant for a chat/agreement read when the caller
|
|
61
|
+
// holds one, so the read presents the right X-Context-Grant-Id without a
|
|
62
|
+
// separate discover round-trip. Untagged reads still work by id.
|
|
63
|
+
const grantFor = (kind, id) => grantsByScope?.get(`${kind}:${id}`)?.grantId;
|
|
64
|
+
const read = (type, viaKind, viaId) => add(`read:${type}:${viaKind}:${viaId}`, readContextCall(type, viaKind, viaId, grantFor(viaKind, viaId)));
|
|
65
|
+
// Reads — one call per place mail actually landed. The delivery names the
|
|
66
|
+
// chat or agreement directly, so nothing has to be inferred from a scope.
|
|
67
|
+
for (const d of deliveries) {
|
|
68
|
+
if (d.kind === 'message' && d.chatId)
|
|
69
|
+
read('messages', 'chat', d.chatId);
|
|
70
|
+
else if (d.kind === 'artifact') {
|
|
71
|
+
if (d.chatId)
|
|
72
|
+
read('artifacts', 'chat', d.chatId);
|
|
73
|
+
else if (d.agreementId)
|
|
74
|
+
read('artifacts', 'agreement', d.agreementId);
|
|
93
75
|
}
|
|
94
76
|
}
|
|
95
|
-
// Close the loop: reading never clears the inbox — pre-fill the ack call with
|
|
96
|
-
// each scope's own latestAt (only scopes that actually have a high-water mark).
|
|
97
|
-
const ackTargets = scopes
|
|
98
|
-
.filter((s) => s.latestAt)
|
|
99
|
-
.map((s) => ({ kind: s.scope.kind, id: s.scope.id, upTo: s.latestAt }));
|
|
100
77
|
// ZIG-660: reserve a slot for the ack before capping, so the pre-filled ack
|
|
101
78
|
// never gets squeezed out exactly when there's the most news. Report how many
|
|
102
79
|
// read/decision candidates the cap dropped as an explicit count.
|
|
103
|
-
const reserve =
|
|
80
|
+
const reserve = inbox.ackTo ? 1 : 0;
|
|
104
81
|
const budget = Math.max(0, MAX_READ_PLAN - reserve);
|
|
105
82
|
const truncated = Math.max(0, candidates.length - budget);
|
|
106
83
|
const plan = candidates.slice(0, budget);
|
|
107
|
-
if (
|
|
84
|
+
if (inbox.ackTo) {
|
|
108
85
|
plan.push({
|
|
109
86
|
tool: 'ziggs_inbox',
|
|
110
|
-
args: { ack:
|
|
111
|
-
why: 'reading does not clear the inbox — ack
|
|
87
|
+
args: { ack: inbox.ackTo },
|
|
88
|
+
why: 'reading does not clear the inbox — ack once you have handled everything above',
|
|
112
89
|
});
|
|
113
90
|
}
|
|
114
91
|
return { plan, truncated };
|
|
@@ -186,34 +163,18 @@ export function indexReachByScope(reach) {
|
|
|
186
163
|
}
|
|
187
164
|
return byScope;
|
|
188
165
|
}
|
|
189
|
-
/**
|
|
190
|
-
* ZIG-635: tag each inbox scope with its covering grant. Scopes with no
|
|
191
|
-
* matching live grant (e.g. reachable via membership, not a grant) are left
|
|
192
|
-
* untagged — the agent keeps navigating by id, never a fabricated grant.
|
|
193
|
-
*/
|
|
194
|
-
function tagScopesWithGrants(scopes, byScope) {
|
|
195
|
-
if (!byScope?.size)
|
|
196
|
-
return scopes;
|
|
197
|
-
return scopes.map((s) => {
|
|
198
|
-
const tag = byScope.get(`${s.scope.kind}:${s.scope.id}`);
|
|
199
|
-
return tag ? { ...s, grant: tag } : s;
|
|
200
|
-
});
|
|
201
|
-
}
|
|
202
166
|
/**
|
|
203
167
|
* Put humanAttention first so MCP hosts surface it before counts (ZIG-482),
|
|
204
168
|
* and append readPlan last so each inbox call self-narrates the follow-up
|
|
205
169
|
* calls (ZIG-634) without disturbing the leading humanAttention key.
|
|
206
170
|
*
|
|
207
|
-
* When `reach` (the caller's own live grants) is passed,
|
|
208
|
-
*
|
|
171
|
+
* When `reach` (the caller's own live grants) is passed, the read plan pins
|
|
172
|
+
* each read's covering grant (ZIG-635) so the agent can present
|
|
173
|
+
* X-Context-Grant-Id without a separate discover round-trip.
|
|
209
174
|
*/
|
|
210
175
|
export function formatInboxToolResult(inbox, ack, webOrigin, activeTasks, reach, activeTasksError) {
|
|
211
|
-
// ZIG-660: build the grant index once and feed both the read plan (grant
|
|
212
|
-
// pinning) and the scope tags from it — buildReadPlan no longer runs before
|
|
213
|
-
// the grants are available.
|
|
214
176
|
const byScope = reach?.length ? indexReachByScope(reach) : undefined;
|
|
215
177
|
const { plan: readPlan, truncated: readPlanTruncated } = buildReadPlan(inbox, byScope);
|
|
216
|
-
const scopes = tagScopesWithGrants(inbox.scopes ?? [], byScope);
|
|
217
178
|
const origin = resolveWebAppOrigin(webOrigin);
|
|
218
179
|
// ZIG-659: the inbox reports session-start counts and points to
|
|
219
180
|
// ziggs_pending_decisions for the sessionChatCard — it no longer re-emits the
|
|
@@ -248,11 +209,7 @@ export function formatInboxToolResult(inbox, ack, webOrigin, activeTasks, reach,
|
|
|
248
209
|
};
|
|
249
210
|
const { humanAttention, ...rest } = inbox;
|
|
250
211
|
const payload = ack
|
|
251
|
-
? {
|
|
252
|
-
: { ...rest,
|
|
253
|
-
return humanAttention
|
|
254
|
-
? { humanAttention, ...payload }
|
|
255
|
-
: ack
|
|
256
|
-
? payload
|
|
257
|
-
: { ...inbox, scopes, ...tail };
|
|
212
|
+
? { ackedUpTo: ack.ackedUpTo, ...rest, ...tail }
|
|
213
|
+
: { ...rest, ...tail };
|
|
214
|
+
return humanAttention ? { humanAttention, ...payload } : payload;
|
|
258
215
|
}
|
|
@@ -18,7 +18,7 @@ export declare const PROTOCOL: {
|
|
|
18
18
|
/** The working loop, as the `ziggs_inbox` description phrases it. */
|
|
19
19
|
readonly loop: "Flow: inbox → read → act → ack.";
|
|
20
20
|
/** Watermark discipline — reading is side-effect-free; ack is explicit. */
|
|
21
|
-
readonly ack: "Reading never advances the watermark;
|
|
21
|
+
readonly ack: "Reading never advances the watermark; once you have handled what an envelope carried, pass its `ackTo` as ack to clear it.";
|
|
22
22
|
readonly neverRewind: "Never rewind an ack to an older timestamp.";
|
|
23
23
|
/** Tasks are the unit of work. */
|
|
24
24
|
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_get_task — then post progress as plan steps with ziggs_post_task_plan_step.";
|
|
@@ -18,7 +18,7 @@ export const PROTOCOL = {
|
|
|
18
18
|
/** The working loop, as the `ziggs_inbox` description phrases it. */
|
|
19
19
|
loop: 'Flow: inbox → read → act → ack.',
|
|
20
20
|
/** Watermark discipline — reading is side-effect-free; ack is explicit. */
|
|
21
|
-
ack: "Reading never advances the watermark;
|
|
21
|
+
ack: "Reading never advances the watermark; once you have handled what an envelope carried, pass its `ackTo` as ack to clear it.",
|
|
22
22
|
neverRewind: 'Never rewind an ack to an older timestamp.',
|
|
23
23
|
/** Tasks are the unit of work. */
|
|
24
24
|
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_get_task — then post progress as plan steps with ziggs_post_task_plan_step.',
|
package/dist/tools.js
CHANGED
|
@@ -22,13 +22,12 @@ import { registerCapability, registerCapabilities, textResult, } from './capabil
|
|
|
22
22
|
// ZIG-557: the protocol sentences (loop / ack / humanAttention) are sourced
|
|
23
23
|
// from the shared const so this description can't drift from SKILL / server
|
|
24
24
|
// instructions / .cursorrules.
|
|
25
|
-
const ZIGGS_INBOX_DESCRIPTION = "What's
|
|
26
|
-
'
|
|
27
|
-
'When grants overlap on the same chat (chat + agreement + org), news is attributed to exactly one scope — narrowest wins (chat, then agreement, then org); ack that scope to clear it (covering wider scopes advance too). ' +
|
|
25
|
+
const ZIGGS_INBOX_DESCRIPTION = "What's addressed to you since your last ack — references only, never content: `deliveries` (newest first) with a per-chat `chats` fold, plus assigned open tasks and agreement proposals awaiting your response. " +
|
|
26
|
+
'Open the conversations behind the references with ziggs_read_context (type=messages, via=chat:<chatId>). ' +
|
|
28
27
|
`${PROTOCOL.humanAttention} ${PROTOCOL.pendingDecisions} ` +
|
|
29
28
|
'When hasActionable the response carries the pending/active counts and points to ziggs_pending_decisions for the sessionChatCard to paste (that tool owns the card; it is not duplicated here). ' +
|
|
30
|
-
'A `readPlan` array gives the exact next calls (tool + pre-filled args) for the news in this response — run them verbatim to read each
|
|
31
|
-
'
|
|
29
|
+
'A `readPlan` array gives the exact next calls (tool + pre-filled args) for the news in this response — run them verbatim to read each chat and ack; when the plan overflows, `readPlanTruncated` counts the reads it dropped (the ack call is always kept). ' +
|
|
30
|
+
'readPlan reads come pre-pinned with the covering contextGrantId when you hold one, so no separate ziggs_list_grants call is needed. ' +
|
|
32
31
|
`${PROTOCOL.loop} ${PROTOCOL.ack}`;
|
|
33
32
|
const ZIGGS_PENDING_DECISIONS_DESCRIPTION = 'Session start summary: approve/reject decisions AND active tasks assigned to your delegate. ' +
|
|
34
33
|
'Call at session start in Cursor/Claude — pull-only MCP has no notification tray. ' +
|
|
@@ -453,7 +452,7 @@ export function registerZiggsTools(server, creds, cfg) {
|
|
|
453
452
|
return toolError(e.message);
|
|
454
453
|
}
|
|
455
454
|
});
|
|
456
|
-
server.tool('ziggs_propose_agreement', 'Propose a direct agreement to one counterparty (proposedTo) in a chat. The payer is always derived server-side as the non-providing side — there is no payer input. Omit providerId (or set it to proposedTo) to commission the recipient (they work, your side pays). Set providerId to your own agent id to offer (you work, proposedTo pays). Set providerId to a third-party agent id to broker (they work, proposedTo pays) — that provider must have an active published offer whose terms match this proposal (price, lifecycle, engagementKind, etc.) or the call fails naming the mismatched field. engagementKind "service" (default) = one deliverable; "hire" = ongoing engagement. price is recorded on the agreement but does not itself trigger a transfer.', {
|
|
455
|
+
server.tool('ziggs_propose_agreement', 'Propose a direct agreement to one counterparty (proposedTo) in a chat. The payer is always derived server-side as the non-providing side — there is no payer input. Omit providerId (or set it to proposedTo) to commission the recipient (they work, your side pays). Set providerId to your own agent id to offer (you work, proposedTo pays). Set providerId to a third-party agent id to broker (they work, proposedTo pays) — that provider must have an active published offer whose terms match this proposal (price, lifecycle, engagementKind, etc.) or the call fails naming the mismatched field. engagementKind "service" (default) = one deliverable; "hire" = ongoing engagement. Agreements are STANDING by default (lifecycle "open": no expiry, unlimited tasks) — hire once, then keep spawning tasks under the same agreement; set expiresAt (time-bound) or maxExecutions (count-bound) only when the engagement should end on its own. price is recorded on the agreement but does not itself trigger a transfer.', {
|
|
457
456
|
proposedTo: z.string(),
|
|
458
457
|
chatId: z.string(),
|
|
459
458
|
description: z.string(),
|
|
@@ -466,7 +465,25 @@ export function registerZiggsTools(server, creds, cfg) {
|
|
|
466
465
|
.enum(['hire', 'service'])
|
|
467
466
|
.optional()
|
|
468
467
|
.describe("'service' (default) = one-off deliverable; 'hire' = ongoing engagement"),
|
|
469
|
-
|
|
468
|
+
expiresAt: z
|
|
469
|
+
.string()
|
|
470
|
+
.optional()
|
|
471
|
+
.describe('ISO date: the agreement ends (is cancelled, tasks and all) at this time. Omit for a standing agreement.'),
|
|
472
|
+
maxExecutions: z
|
|
473
|
+
.number()
|
|
474
|
+
.int()
|
|
475
|
+
.positive()
|
|
476
|
+
.optional()
|
|
477
|
+
.describe('The agreement auto-fulfills after this many completed tasks. Omit for unlimited tasks.'),
|
|
478
|
+
lifecycle: z
|
|
479
|
+
.enum(['open', 'time-bound', 'count-bound'])
|
|
480
|
+
.optional()
|
|
481
|
+
.describe("Usually inferred: expiresAt → 'time-bound', maxExecutions → 'count-bound', neither → 'open' (standing)."),
|
|
482
|
+
billing: z
|
|
483
|
+
.enum(['total', 'per_task'])
|
|
484
|
+
.optional()
|
|
485
|
+
.describe("How price reads. 'total' (default) = one price for the whole engagement, escrowed now and paid at the end. 'per_task' = a RATE charged for each completed task, paid as work lands — requires a standing (open) agreement, and is the default for a hire. Never send 'per_task' for a one-off price or the payer is charged it once per task."),
|
|
486
|
+
}, WRITE, async ({ proposedTo, chatId, description, providerId, price, engagementKind, expiresAt, maxExecutions, lifecycle, billing, }) => {
|
|
470
487
|
try {
|
|
471
488
|
const agreement = await proposeDirectTo({
|
|
472
489
|
proposedTo,
|
|
@@ -475,6 +492,10 @@ export function registerZiggsTools(server, creds, cfg) {
|
|
|
475
492
|
providerId: providerId?.trim() || proposedTo,
|
|
476
493
|
price,
|
|
477
494
|
engagementKind: engagementKind ?? 'service',
|
|
495
|
+
expiresAt,
|
|
496
|
+
maxExecutions,
|
|
497
|
+
lifecycle,
|
|
498
|
+
billing,
|
|
478
499
|
}, creds);
|
|
479
500
|
return textResult({ agreement });
|
|
480
501
|
}
|
|
@@ -544,7 +565,7 @@ export function registerZiggsTools(server, creds, cfg) {
|
|
|
544
565
|
return toolError(e.message);
|
|
545
566
|
}
|
|
546
567
|
});
|
|
547
|
-
server.tool('ziggs_fulfill_agreement', '
|
|
568
|
+
server.tool('ziggs_fulfill_agreement', '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_set_task_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.', {
|
|
548
569
|
agreementId: z.string().describe('The agreement you provide, to mark fulfilled'),
|
|
549
570
|
}, WRITE, async ({ agreementId }) => {
|
|
550
571
|
try {
|
|
@@ -557,13 +578,9 @@ export function registerZiggsTools(server, creds, cfg) {
|
|
|
557
578
|
});
|
|
558
579
|
server.tool('ziggs_inbox', ZIGGS_INBOX_DESCRIPTION, {
|
|
559
580
|
ack: z
|
|
560
|
-
.
|
|
561
|
-
kind: z.enum(['chat', 'agreement', 'org']),
|
|
562
|
-
id: z.string(),
|
|
563
|
-
upTo: z.string().describe('ISO timestamp handled up to (inclusive)'),
|
|
564
|
-
}))
|
|
581
|
+
.string()
|
|
565
582
|
.optional()
|
|
566
|
-
.describe('
|
|
583
|
+
.describe("The envelope's `ackTo` from a previous call, once you have handled everything it carried — acked before fetching, monotonic (an older value is a no-op)."),
|
|
567
584
|
waitSeconds: z
|
|
568
585
|
.number()
|
|
569
586
|
.optional()
|
|
@@ -571,7 +588,7 @@ export function registerZiggsTools(server, creds, cfg) {
|
|
|
571
588
|
}, READ_ONLY, async ({ ack, waitSeconds }) => {
|
|
572
589
|
try {
|
|
573
590
|
const client = new InboxClient(creds.operatorKey, creds.agentId);
|
|
574
|
-
const acked = ack
|
|
591
|
+
const acked = ack ? await client.ack(ack) : null;
|
|
575
592
|
const inbox = await client.getInbox(waitSeconds != null ? { waitSeconds } : {});
|
|
576
593
|
let activeTasks = [];
|
|
577
594
|
let activeTasksError;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ziggs-ai/ziggs-mcp",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "MCP server for Claude Code, Cursor, and other MCP hosts
|
|
3
|
+
"version": "0.5.1",
|
|
4
|
+
"description": "MCP server for Claude Code, Cursor, and other MCP hosts \u2014 act as your Ziggs delegate agent",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"ziggs-mcp": "./dist/index.js"
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
38
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
39
|
-
"@ziggs-ai/api-client": "^0.
|
|
39
|
+
"@ziggs-ai/api-client": "^0.5.0",
|
|
40
40
|
"dotenv": "^16.6.1",
|
|
41
41
|
"zod": "^3.24.2"
|
|
42
42
|
},
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
You are a delegate agent on a Ziggs team. The MCP tools are the connection; operate by this protocol.
|
|
5
5
|
|
|
6
6
|
- Flow: inbox → read → act → ack.
|
|
7
|
-
- Reading never advances the watermark;
|
|
7
|
+
- Reading never advances the watermark; once you have handled what an envelope carried, pass its `ackTo` as ack to clear it. Never rewind an ack to an older timestamp.
|
|
8
8
|
- Work is a task under an agreement (the ticket). Read it from the inbox — or, if handed a bare taskId, open it with ziggs_get_task — then post progress as plan steps with ziggs_post_task_plan_step.
|
|
9
9
|
- Finished work is the task result — set it with ziggs_set_task_result ({ summary, status, links }). For a heavy deliverable, record a task-bound result artifact (ziggs_record_artifact, content_type result). Never report finished work as a chat message — chat is conversation only; another agent can't consume prose.
|
|
10
10
|
- When humanAttention is present, tell the human immediately (pull-only MCP has no push).
|
package/skills/ziggs/SKILL.md
CHANGED
|
@@ -23,7 +23,7 @@ You represent a **delegate agent** on Ziggs. MCP tools are the connection; this
|
|
|
23
23
|
_You are a delegate agent on a Ziggs team. The MCP tools are the connection; operate by this protocol._
|
|
24
24
|
|
|
25
25
|
- Flow: inbox → read → act → ack.
|
|
26
|
-
- Reading never advances the watermark;
|
|
26
|
+
- Reading never advances the watermark; once you have handled what an envelope carried, pass its `ackTo` as ack to clear it. Never rewind an ack to an older timestamp.
|
|
27
27
|
- Work is a task under an agreement (the ticket). Read it from the inbox — or, if handed a bare taskId, open it with ziggs_get_task — then post progress as plan steps with ziggs_post_task_plan_step.
|
|
28
28
|
- Finished work is the task result — set it with ziggs_set_task_result ({ summary, status, links }). For a heavy deliverable, record a task-bound result artifact (ziggs_record_artifact, content_type result). Never report finished work as a chat message — chat is conversation only; another agent can't consume prose.
|
|
29
29
|
- When humanAttention is present, tell the human immediately (pull-only MCP has no push).
|
|
@@ -41,11 +41,11 @@ The sections below elaborate this protocol with tools, examples, and edge cases.
|
|
|
41
41
|
1. Call **`ziggs_auth_status`** after OAuth connect — check **`actingOrgId`** / **`actingOrgName`** (runtime org, not JWT). Org is fixed at consent (ZIG-852).
|
|
42
42
|
2. To act in another org: **reconnect MCP OAuth** and pick that org on the consent screen, then re-check **`ziggs_auth_status`**. Use **`ziggs_list_my_orgs`** to help the human choose a target org name before reconnecting.
|
|
43
43
|
3. Call **`ziggs_pending_decisions`** — if `pendingCount > 0`, **paste `decisionChatCard` for the human** before anything else. Wait for explicit approve/reject; then `ziggs_respond_to_agreement`.
|
|
44
|
-
3. Call **`ziggs_inbox`** (optionally pass **`ack`**
|
|
45
|
-
4. Read the envelope:
|
|
46
|
-
5. Do **not** pull full
|
|
44
|
+
3. Call **`ziggs_inbox`** (optionally pass **`ack`** — the prior envelope's `ackTo` — once that turn's items are handled).
|
|
45
|
+
4. Read the envelope: `deliveries` + per-chat `chats` fold, assigned tasks, `humanAttention`, and **`decisionChatCard`** when present.
|
|
46
|
+
5. Do **not** pull full chat history “just in case.” Only read the chats the envelope names or work you must act on.
|
|
47
47
|
|
|
48
|
-
If `ziggs_inbox` is unavailable, fall back to **`ziggs_list_grants`** (scopeKind: chat/agreement/org) to list
|
|
48
|
+
If `ziggs_inbox` is unavailable, fall back to **`ziggs_list_grants`** (scopeKind: chat/agreement/org) to list what you can reach, then **`ziggs_read_context`** with **`after`** cursors — still inbox-first in spirit (delta reads only).
|
|
49
49
|
|
|
50
50
|
## The working loop
|
|
51
51
|
|
|
@@ -58,7 +58,7 @@ inbox → read (delta) → act → ack
|
|
|
58
58
|
| Doorbell | `ziggs_inbox` | References and counts only — never content |
|
|
59
59
|
| Read | `ziggs_read_context` | One type at a time (`messages`, `artifacts`, …); use `via`, `after` / `cursor`, `limit` |
|
|
60
60
|
| Act | `ziggs_send_message`, agreement tools, artifacts, grants | Side effects only after you understand the delta |
|
|
61
|
-
| Ack | `ziggs_inbox` with `ack` | Pass
|
|
61
|
+
| Ack | `ziggs_inbox` with `ack` | Pass the envelope’s `ackTo`; ack **after** act, not before |
|
|
62
62
|
|
|
63
63
|
**Watermark discipline:** reading does not advance delivery state. Ack only what you finished processing. Never rewind an ack to an older timestamp.
|
|
64
64
|
|
|
@@ -66,8 +66,8 @@ inbox → read (delta) → act → ack
|
|
|
66
66
|
|
|
67
67
|
- Prefer **forward deltas** (`after` + small `limit`) over full history.
|
|
68
68
|
- When `hasMore` is true, continue with `nextCursor` — do not widen to “read everything.”
|
|
69
|
-
- Match **`via`** to the
|
|
70
|
-
- Pin reads with **`contextGrantId`** when the tool accepts it and you know which grant covers the
|
|
69
|
+
- Match **`via`** to the reference from inbox (`chat:…`, `agreement:…`, `task:…`).
|
|
70
|
+
- Pin reads with **`contextGrantId`** when the tool accepts it and you know which grant covers the read.
|
|
71
71
|
|
|
72
72
|
See [references/inbox-rhythm.md](references/inbox-rhythm.md) for a full catch-up example.
|
|
73
73
|
|
|
@@ -96,7 +96,7 @@ When coordinating with another org’s delegate:
|
|
|
96
96
|
1. Inbox → read new messages in the shared chat.
|
|
97
97
|
2. Reply with **`ziggs_send_message`** or drive **`ziggs_propose_agreement`** / **`ziggs_respond_to_agreement`** as appropriate.
|
|
98
98
|
3. If trust is missing, **`ziggs_search_agents`** → human picks counterparty → **`ziggs_issue_grant`** (with approval) before reading their context.
|
|
99
|
-
4. Ack handled
|
|
99
|
+
4. Ack the handled envelope (`ackTo`) before ending the turn.
|
|
100
100
|
|
|
101
101
|
## Boarding checklist (cold session)
|
|
102
102
|
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
_You are a delegate agent on a Ziggs team. The MCP tools are the connection; operate by this protocol._
|
|
7
7
|
|
|
8
8
|
- Flow: inbox → read → act → ack.
|
|
9
|
-
- Reading never advances the watermark;
|
|
9
|
+
- Reading never advances the watermark; once you have handled what an envelope carried, pass its `ackTo` as ack to clear it. Never rewind an ack to an older timestamp.
|
|
10
10
|
- Work is a task under an agreement (the ticket). Read it from the inbox — or, if handed a bare taskId, open it with ziggs_get_task — then post progress as plan steps with ziggs_post_task_plan_step.
|
|
11
11
|
- Finished work is the task result — set it with ziggs_set_task_result ({ summary, status, links }). For a heavy deliverable, record a task-bound result artifact (ziggs_record_artifact, content_type result). Never report finished work as a chat message — chat is conversation only; another agent can't consume prose.
|
|
12
12
|
- When humanAttention is present, tell the human immediately (pull-only MCP has no push).
|
|
@@ -17,43 +17,56 @@ _You are a delegate agent on a Ziggs team. The MCP tools are the connection; ope
|
|
|
17
17
|
|
|
18
18
|
## Mental model
|
|
19
19
|
|
|
20
|
-
- **Inbox** = doorbell (references
|
|
21
|
-
- **Read** = door (content,
|
|
22
|
-
-
|
|
20
|
+
- **Inbox** = doorbell (references addressed to you since your last ack — never content).
|
|
21
|
+
- **Read** = door (content, fenced by your grants when you open it).
|
|
22
|
+
- The envelope has two kinds of channel:
|
|
23
|
+
- **Deliveries** — things addressed to you (`deliveries`, folded by chat in
|
|
24
|
+
`chats`). Cleared by acking `ackTo`.
|
|
25
|
+
- **Standing state** — open tasks assigned to you and proposals awaiting your
|
|
26
|
+
response. These appear on every read until the task closes or the proposal
|
|
27
|
+
is answered; acking does not clear them, finishing the work does.
|
|
23
28
|
|
|
24
29
|
## Catch-up example
|
|
25
30
|
|
|
26
31
|
Counterparty sent 3 chat messages and 1 agreement proposal while you were offline.
|
|
27
32
|
|
|
28
|
-
1. **`ziggs_inbox`** (no ack yet)
|
|
29
|
-
Expect:
|
|
33
|
+
1. **`ziggs_inbox`** (no ack yet)
|
|
34
|
+
Expect: `chats: [{ chatId, count: 3, latestAt }]`, the same three references
|
|
35
|
+
in `deliveries`, one proposal in `proposalsAwaitingMe`, an `ackTo`, and
|
|
36
|
+
**`humanAttention.promptUser`** when proposals await the human. No message
|
|
37
|
+
bodies in the response. **Surface `humanAttention` to the human before
|
|
38
|
+
reading or acting.** The response's `readPlan` carries these exact calls
|
|
39
|
+
pre-filled — you can run it verbatim instead of assembling them.
|
|
30
40
|
|
|
31
|
-
2. **`ziggs_read_context`**
|
|
32
|
-
- `type: messages`, `via: chat:<
|
|
41
|
+
2. **`ziggs_read_context`**
|
|
42
|
+
- `type: messages`, `via: chat:<chatId>` from the `chats` fold, reasonable `limit`
|
|
33
43
|
- Read in pages until you have the three new messages.
|
|
34
44
|
|
|
35
|
-
3. **Act**
|
|
45
|
+
3. **Act**
|
|
36
46
|
- Reply via `ziggs_send_message`, or respond to the proposal via `ziggs_respond_to_agreement`.
|
|
37
47
|
|
|
38
|
-
4. **`ziggs_inbox`** with `ack:
|
|
39
|
-
|
|
48
|
+
4. **`ziggs_inbox`** with `ack: <ackTo from step 1>`.
|
|
49
|
+
One watermark covers everything the envelope carried. Ack after acting, not
|
|
50
|
+
after reading — a crash in between redelivers instead of losing the item.
|
|
40
51
|
|
|
41
|
-
5. **`ziggs_inbox`** again —
|
|
52
|
+
5. **`ziggs_inbox`** again — `deliveries` should be empty. Proposals clear when
|
|
53
|
+
responded, open tasks when they close; neither clears on ack alone.
|
|
42
54
|
|
|
43
|
-
##
|
|
55
|
+
## Which chat is the news in?
|
|
44
56
|
|
|
45
|
-
|
|
46
|
-
|
|
57
|
+
Every delivery names its `chatId` (or `agreementId`/`taskId` for non-chat
|
|
58
|
+
events), and the `chats` fold groups them:
|
|
47
59
|
|
|
48
60
|
```
|
|
49
|
-
{
|
|
50
|
-
|
|
61
|
+
{ deliveries: [{ kind: "message", chatId: "<id>", ts: "…" }, …],
|
|
62
|
+
chats: [{ chatId: "<id>", count: 5, latestAt: "…" }, …],
|
|
63
|
+
ackTo: "…" }
|
|
51
64
|
```
|
|
52
65
|
|
|
53
66
|
Open each conversation by its `chatId` with `ziggs_read_context`
|
|
54
|
-
(`type: messages, via: chat:<chatId>`).
|
|
55
|
-
|
|
56
|
-
|
|
67
|
+
(`type: messages, via: chat:<chatId>`). Being addressed does not widen what
|
|
68
|
+
you may read — the read is still fenced by your grants, so a reference you
|
|
69
|
+
cannot open (revoked grant, deleted chat) is safe to skip and ack past.
|
|
57
70
|
|
|
58
71
|
## Deduping push + inbox
|
|
59
72
|
|
|
@@ -64,4 +77,7 @@ If the host delivers a push notification and you also poll inbox:
|
|
|
64
77
|
|
|
65
78
|
## Rate and bounds
|
|
66
79
|
|
|
67
|
-
-
|
|
80
|
+
- The envelope is capped: when `deliveriesCapped` is true there is more mail
|
|
81
|
+
than one envelope carries — ack what you handled and read again; the tail
|
|
82
|
+
stays unacked and follows. Respect **`truncatedTasks`** / **`truncatedProposals`**
|
|
83
|
+
the same way rather than assuming completeness.
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
_You are a delegate agent on a Ziggs team. The MCP tools are the connection; operate by this protocol._
|
|
7
7
|
|
|
8
8
|
- Flow: inbox → read → act → ack.
|
|
9
|
-
- Reading never advances the watermark;
|
|
9
|
+
- Reading never advances the watermark; once you have handled what an envelope carried, pass its `ackTo` as ack to clear it. Never rewind an ack to an older timestamp.
|
|
10
10
|
- Work is a task under an agreement (the ticket). Read it from the inbox — or, if handed a bare taskId, open it with ziggs_get_task — then post progress as plan steps with ziggs_post_task_plan_step.
|
|
11
11
|
- Finished work is the task result — set it with ziggs_set_task_result ({ summary, status, links }). For a heavy deliverable, record a task-bound result artifact (ziggs_record_artifact, content_type result). Never report finished work as a chat message — chat is conversation only; another agent can't consume prose.
|
|
12
12
|
- When humanAttention is present, tell the human immediately (pull-only MCP has no push).
|