@ziggs-ai/ziggs-mcp 0.37.3 → 0.37.5
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 +0 -1
- package/dist/directorySurface.js +2 -2
- package/dist/inboxCompact.d.ts +49 -5
- package/dist/inboxCompact.js +158 -21
- package/dist/server.d.ts +1 -0
- package/dist/toolAliases.js +1 -2
- package/dist/tools.js +18 -6
- package/dist/trustTools.d.ts +4 -2
- package/dist/trustTools.js +6 -89
- package/dist/workspaceRouting.js +2 -2
- package/package.json +2 -2
- package/skills/ziggs/SKILL.md +2 -2
- package/skills/ziggs/references/grants-and-approvals.md +2 -2
package/README.md
CHANGED
|
@@ -165,7 +165,6 @@ Startup validates the key shape, expiry (JWT `exp`), and agent resolution — er
|
|
|
165
165
|
| `ziggs_artifact_download` | `GET /artifacts/:id/download` |
|
|
166
166
|
| `ziggs_agent_search` | Agent search |
|
|
167
167
|
| `ziggs_agent_get` | `GET /agents/:id` — full profile of one agent by exact id |
|
|
168
|
-
| `ziggs_context_issue_grant` | Chat admission or `POST /context/grants` |
|
|
169
168
|
| `ziggs_context_delegate` | `POST /context/grants/:id/delegate` |
|
|
170
169
|
| `ziggs_context_revoke_grant` | `DELETE /context/grants/:id` |
|
|
171
170
|
| `ziggs_link_propose` | `POST /agreements/links` — connect with someone by email or agent id, or mint a share link (claimUrl + paste text) |
|
package/dist/directorySurface.js
CHANGED
|
@@ -15,7 +15,7 @@ ziggs_artifact_attach ziggs_artifact_upload_url ziggs_artifact_complete_file zig
|
|
|
15
15
|
ziggs_task_create ziggs_task_set_result ziggs_task_replace_plan ziggs_task_update_steps
|
|
16
16
|
ziggs_task_acknowledge_control ziggs_task_list ziggs_task_cancel ziggs_task_get
|
|
17
17
|
ziggs_connection_list ziggs_agent_search ziggs_agent_get
|
|
18
|
-
|
|
18
|
+
ziggs_context_delegate ziggs_access_request
|
|
19
19
|
ziggs_link_propose ziggs_link_list ziggs_introduction_mint ziggs_introduction_redeem
|
|
20
20
|
ziggs_introduction_list ziggs_introduction_revoke ziggs_context_revoke_grant ziggs_payment_balance
|
|
21
21
|
`.trim().split(/\s+/));
|
|
@@ -32,7 +32,7 @@ ziggs_agent_search ziggs_agent_get ziggs_marketplace_view
|
|
|
32
32
|
ziggs_agreement_buy ziggs_agreement_bid ziggs_agreement_broker ziggs_agreement_request
|
|
33
33
|
ziggs_agreement_handoff ziggs_agreement_claim ziggs_agreement_subcontract
|
|
34
34
|
ziggs_chat_open ziggs_chat_send ziggs_artifact_share ziggs_artifact_attach
|
|
35
|
-
|
|
35
|
+
ziggs_context_delegate ziggs_access_request
|
|
36
36
|
ziggs_link_propose ziggs_introduction_mint ziggs_introduction_redeem
|
|
37
37
|
`.trim().split(/\s+/));
|
|
38
38
|
export function installDirectorySurface(server) {
|
package/dist/inboxCompact.d.ts
CHANGED
|
@@ -4,9 +4,9 @@
|
|
|
4
4
|
* Every assistant start-up pays for one inbox read, and it was several times
|
|
5
5
|
* the size of the connect text before any content was opened. This changes how
|
|
6
6
|
* the result is written, never what it says: which deliveries are visible,
|
|
7
|
-
* their order, their reach, the acknowledgement tokens, the
|
|
8
|
-
* lease all come from the server unchanged. What goes is only
|
|
9
|
-
* back from what stays:
|
|
7
|
+
* their order within a thread, their reach, the acknowledgement tokens, the
|
|
8
|
+
* counters and the lease all come from the server unchanged. What goes is only
|
|
9
|
+
* what can be read back from what stays:
|
|
10
10
|
*
|
|
11
11
|
* - a delivery field that is null, false or empty (absent reads the same);
|
|
12
12
|
* - `partyKind` on a delivery, which `sources` already gives for its `partyId`,
|
|
@@ -24,17 +24,61 @@
|
|
|
24
24
|
*
|
|
25
25
|
* Acknowledgement stays exact: every `ackTo` — the envelope's, each row's and
|
|
26
26
|
* the ack step's — is passed through byte for byte.
|
|
27
|
+
*
|
|
28
|
+
* The rows are written grouped rather than as one flat list: by who they are
|
|
29
|
+
* for, then by who sent them, then by the chat, task or agreement they are
|
|
30
|
+
* about. A flat list mixed the assistant's own work with mail it
|
|
31
|
+
* only passes along to its person and mail for the person's other
|
|
32
|
+
* assistants, and the split lived in one sentence of counts. Every row is
|
|
33
|
+
* still there, oldest first inside its thread; see {@link groupDeliveries}.
|
|
27
34
|
*/
|
|
28
35
|
type Json = Record<string, unknown>;
|
|
29
36
|
/** One delivery row, without what it would say by default or elsewhere. */
|
|
30
37
|
export declare function compactDelivery(row: Json, kindOfParty: Map<string, unknown>, usualPartyId?: unknown): Json;
|
|
38
|
+
/** Who an inbox read belongs to: the connection's assistants and their person. */
|
|
39
|
+
export interface InboxReader {
|
|
40
|
+
agentIds: readonly string[];
|
|
41
|
+
/** The person the assistants work for; read off the human source when absent. */
|
|
42
|
+
personId?: string | null;
|
|
43
|
+
}
|
|
44
|
+
/** The sections of a grouped inbox, in the order they are written. */
|
|
45
|
+
export declare const INBOX_SECTIONS: readonly ["forYou", "forYourPerson", "forOtherAssistants", "context"];
|
|
46
|
+
export type InboxSection = (typeof INBOX_SECTIONS)[number];
|
|
47
|
+
/**
|
|
48
|
+
* Who a row is for. One rule for the grouping and for the counts in the
|
|
49
|
+
* notice, so the two cannot disagree: assigned to one of the reader's
|
|
50
|
+
* assistants is theirs to handle; unassigned and flagged for a human is the
|
|
51
|
+
* person's, which the assistant passes along; assigned to anyone else is
|
|
52
|
+
* another assistant's; the rest nobody is asked to act on.
|
|
53
|
+
*/
|
|
54
|
+
export declare function sectionOf(row: Json, agentIds: readonly string[]): InboxSection;
|
|
55
|
+
/**
|
|
56
|
+
* The window's rows, grouped: section, then sender, then thread.
|
|
57
|
+
*
|
|
58
|
+
* Nothing is hidden and nothing is reordered within a thread. Senders and
|
|
59
|
+
* threads are written in the order their oldest row arrived, which is the
|
|
60
|
+
* order the server sends (oldest first), except that the reader's own actions
|
|
61
|
+
* and their person's come last in a section: they are a record of what this
|
|
62
|
+
* side did, not news from anyone.
|
|
63
|
+
*
|
|
64
|
+
* A row drops only what its place in the grouping says: the sender (the
|
|
65
|
+
* group's `from`), the thread's own id, the assignee in `forYou` when the
|
|
66
|
+
* connection has one assistant, and `needsHuman` in `forYourPerson`. A thread
|
|
67
|
+
* names the first agreement its rows sit under; a row under another one keeps
|
|
68
|
+
* its own, and a row under none says `agreementId: null`, so every row reads
|
|
69
|
+
* back exactly.
|
|
70
|
+
*/
|
|
71
|
+
export declare function groupDeliveries(rows: readonly Json[], reader: InboxReader, compactRow?: (row: Json) => Json): Partial<Record<InboxSection, Json[]>>;
|
|
31
72
|
/**
|
|
32
73
|
* The compact inbox result. Takes the result exactly as it would otherwise be
|
|
33
74
|
* returned and gives back the same information in fewer characters.
|
|
75
|
+
*
|
|
76
|
+
* With a reader, `deliveries` is written grouped ({@link groupDeliveries});
|
|
77
|
+
* without one there is no telling whose a row is, so it stays one list.
|
|
34
78
|
*/
|
|
35
|
-
export declare function compactInboxResult(result: Json): Json;
|
|
79
|
+
export declare function compactInboxResult(result: Json, reader?: InboxReader): Json;
|
|
36
80
|
/** The inbox result as one line of JSON: indentation is most of nothing. */
|
|
37
|
-
export declare function compactTextResult(data: Json): {
|
|
81
|
+
export declare function compactTextResult(data: Json, reader?: InboxReader): {
|
|
38
82
|
content: {
|
|
39
83
|
type: "text";
|
|
40
84
|
text: string;
|
package/dist/inboxCompact.js
CHANGED
|
@@ -4,9 +4,9 @@
|
|
|
4
4
|
* Every assistant start-up pays for one inbox read, and it was several times
|
|
5
5
|
* the size of the connect text before any content was opened. This changes how
|
|
6
6
|
* the result is written, never what it says: which deliveries are visible,
|
|
7
|
-
* their order, their reach, the acknowledgement tokens, the
|
|
8
|
-
* lease all come from the server unchanged. What goes is only
|
|
9
|
-
* back from what stays:
|
|
7
|
+
* their order within a thread, their reach, the acknowledgement tokens, the
|
|
8
|
+
* counters and the lease all come from the server unchanged. What goes is only
|
|
9
|
+
* what can be read back from what stays:
|
|
10
10
|
*
|
|
11
11
|
* - a delivery field that is null, false or empty (absent reads the same);
|
|
12
12
|
* - `partyKind` on a delivery, which `sources` already gives for its `partyId`,
|
|
@@ -24,6 +24,13 @@
|
|
|
24
24
|
*
|
|
25
25
|
* Acknowledgement stays exact: every `ackTo` — the envelope's, each row's and
|
|
26
26
|
* the ack step's — is passed through byte for byte.
|
|
27
|
+
*
|
|
28
|
+
* The rows are written grouped rather than as one flat list: by who they are
|
|
29
|
+
* for, then by who sent them, then by the chat, task or agreement they are
|
|
30
|
+
* about. A flat list mixed the assistant's own work with mail it
|
|
31
|
+
* only passes along to its person and mail for the person's other
|
|
32
|
+
* assistants, and the split lived in one sentence of counts. Every row is
|
|
33
|
+
* still there, oldest first inside its thread; see {@link groupDeliveries}.
|
|
27
34
|
*/
|
|
28
35
|
const isRecord = (v) => !!v && typeof v === 'object' && !Array.isArray(v);
|
|
29
36
|
/** Session fields `ziggs_auth_status` carries; the inbox does not repeat them. */
|
|
@@ -84,11 +91,140 @@ function usualPartyOf(rows) {
|
|
|
84
91
|
[best, most] = [id, n];
|
|
85
92
|
return best;
|
|
86
93
|
}
|
|
94
|
+
/** The sections of a grouped inbox, in the order they are written. */
|
|
95
|
+
export const INBOX_SECTIONS = ['forYou', 'forYourPerson', 'forOtherAssistants', 'context'];
|
|
96
|
+
/**
|
|
97
|
+
* Who a row is for. One rule for the grouping and for the counts in the
|
|
98
|
+
* notice, so the two cannot disagree: assigned to one of the reader's
|
|
99
|
+
* assistants is theirs to handle; unassigned and flagged for a human is the
|
|
100
|
+
* person's, which the assistant passes along; assigned to anyone else is
|
|
101
|
+
* another assistant's; the rest nobody is asked to act on.
|
|
102
|
+
*/
|
|
103
|
+
export function sectionOf(row, agentIds) {
|
|
104
|
+
const assignee = row.assigneeId ? String(row.assigneeId) : '';
|
|
105
|
+
if (assignee && agentIds.includes(assignee))
|
|
106
|
+
return 'forYou';
|
|
107
|
+
if (!assignee && row.needsHuman === true)
|
|
108
|
+
return 'forYourPerson';
|
|
109
|
+
if (assignee)
|
|
110
|
+
return 'forOtherAssistants';
|
|
111
|
+
return 'context';
|
|
112
|
+
}
|
|
113
|
+
/** What a row is about: its chat, else its task, else its agreement, else itself. */
|
|
114
|
+
function anchorOf(row) {
|
|
115
|
+
if (row.chatId)
|
|
116
|
+
return ['chatId', String(row.chatId)];
|
|
117
|
+
if (row.taskId)
|
|
118
|
+
return ['taskId', String(row.taskId)];
|
|
119
|
+
if (row.agreementId)
|
|
120
|
+
return ['agreementId', String(row.agreementId)];
|
|
121
|
+
return [row.kind === 'artifact' ? 'artifactId' : 'resourceId', String(row.resourceId)];
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* The window's rows, grouped: section, then sender, then thread.
|
|
125
|
+
*
|
|
126
|
+
* Nothing is hidden and nothing is reordered within a thread. Senders and
|
|
127
|
+
* threads are written in the order their oldest row arrived, which is the
|
|
128
|
+
* order the server sends (oldest first), except that the reader's own actions
|
|
129
|
+
* and their person's come last in a section: they are a record of what this
|
|
130
|
+
* side did, not news from anyone.
|
|
131
|
+
*
|
|
132
|
+
* A row drops only what its place in the grouping says: the sender (the
|
|
133
|
+
* group's `from`), the thread's own id, the assignee in `forYou` when the
|
|
134
|
+
* connection has one assistant, and `needsHuman` in `forYourPerson`. A thread
|
|
135
|
+
* names the first agreement its rows sit under; a row under another one keeps
|
|
136
|
+
* its own, and a row under none says `agreementId: null`, so every row reads
|
|
137
|
+
* back exactly.
|
|
138
|
+
*/
|
|
139
|
+
export function groupDeliveries(rows, reader, compactRow = (row) => ({ ...row })) {
|
|
140
|
+
const sections = new Map();
|
|
141
|
+
for (const row of rows) {
|
|
142
|
+
const section = sectionOf(row, reader.agentIds);
|
|
143
|
+
const from = row.actorId ? String(row.actorId) : null;
|
|
144
|
+
const anchor = anchorOf(row);
|
|
145
|
+
let senders = sections.get(section);
|
|
146
|
+
if (!senders)
|
|
147
|
+
sections.set(section, (senders = new Map()));
|
|
148
|
+
let group = senders.get(String(from));
|
|
149
|
+
if (!group)
|
|
150
|
+
senders.set(String(from), (group = { from, threads: new Map() }));
|
|
151
|
+
const key = anchor.join(':');
|
|
152
|
+
let thread = group.threads.get(key);
|
|
153
|
+
if (!thread)
|
|
154
|
+
group.threads.set(key, (thread = { anchor, rows: [] }));
|
|
155
|
+
thread.rows.push(row);
|
|
156
|
+
}
|
|
157
|
+
const own = (from) => from !== null && (reader.agentIds.includes(from) || from === reader.personId);
|
|
158
|
+
const out = {};
|
|
159
|
+
for (const section of INBOX_SECTIONS) {
|
|
160
|
+
const senders = sections.get(section);
|
|
161
|
+
if (!senders)
|
|
162
|
+
continue;
|
|
163
|
+
const ordered = [...senders.values()];
|
|
164
|
+
out[section] = [
|
|
165
|
+
...ordered.filter((g) => !own(g.from)),
|
|
166
|
+
...ordered.filter((g) => own(g.from)),
|
|
167
|
+
].map((g) => {
|
|
168
|
+
const threads = [...g.threads.values()].map((t) => writeThread(t, section, reader, compactRow));
|
|
169
|
+
const count = [...g.threads.values()].reduce((n, t) => n + t.rows.length, 0);
|
|
170
|
+
return {
|
|
171
|
+
from: g.from,
|
|
172
|
+
...(g.from !== null && reader.agentIds.includes(g.from) ? { you: true } : {}),
|
|
173
|
+
...(g.from !== null && g.from === reader.personId ? { yourPerson: true } : {}),
|
|
174
|
+
count,
|
|
175
|
+
threads,
|
|
176
|
+
};
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
return out;
|
|
180
|
+
}
|
|
181
|
+
function writeThread(thread, section, reader, compactRow) {
|
|
182
|
+
const [field, id] = thread.anchor;
|
|
183
|
+
const lifted = field === 'agreementId'
|
|
184
|
+
? null
|
|
185
|
+
: thread.rows.map((r) => r.agreementId).find(Boolean) ?? null;
|
|
186
|
+
const rows = thread.rows.map((original) => {
|
|
187
|
+
const row = compactRow(original);
|
|
188
|
+
delete row.actorId;
|
|
189
|
+
if (field !== 'resourceId' && field !== 'artifactId')
|
|
190
|
+
delete row[field];
|
|
191
|
+
if (lifted) {
|
|
192
|
+
if (row.agreementId === lifted)
|
|
193
|
+
delete row.agreementId;
|
|
194
|
+
else if (!row.agreementId)
|
|
195
|
+
row.agreementId = null;
|
|
196
|
+
}
|
|
197
|
+
if (section === 'forYou') {
|
|
198
|
+
if (reader.agentIds.length === 1)
|
|
199
|
+
delete row.assigneeId;
|
|
200
|
+
if (row.assignedToConnection === true)
|
|
201
|
+
delete row.assignedToConnection;
|
|
202
|
+
}
|
|
203
|
+
if (section === 'forYourPerson')
|
|
204
|
+
delete row.needsHuman;
|
|
205
|
+
return row;
|
|
206
|
+
});
|
|
207
|
+
return { [field]: id, ...(lifted ? { agreementId: lifted } : {}), rows };
|
|
208
|
+
}
|
|
209
|
+
/** The reader's assistants, without empty ids, and their person. */
|
|
210
|
+
function readerOf(result, reader) {
|
|
211
|
+
const agentIds = (reader?.agentIds ?? []).filter(Boolean);
|
|
212
|
+
if (!agentIds.length)
|
|
213
|
+
return null;
|
|
214
|
+
const human = Array.isArray(result.sources)
|
|
215
|
+
? result.sources.find((s) => isRecord(s) && s.partyKind === 'human')
|
|
216
|
+
: undefined;
|
|
217
|
+
const personId = reader?.personId ?? (isRecord(human) ? String(human.partyId) : null);
|
|
218
|
+
return { agentIds, personId };
|
|
219
|
+
}
|
|
87
220
|
/**
|
|
88
221
|
* The compact inbox result. Takes the result exactly as it would otherwise be
|
|
89
222
|
* returned and gives back the same information in fewer characters.
|
|
223
|
+
*
|
|
224
|
+
* With a reader, `deliveries` is written grouped ({@link groupDeliveries});
|
|
225
|
+
* without one there is no telling whose a row is, so it stays one list.
|
|
90
226
|
*/
|
|
91
|
-
export function compactInboxResult(result) {
|
|
227
|
+
export function compactInboxResult(result, reader) {
|
|
92
228
|
const kindOfParty = new Map();
|
|
93
229
|
if (Array.isArray(result.sources))
|
|
94
230
|
for (const s of result.sources)
|
|
@@ -101,7 +237,10 @@ export function compactInboxResult(result) {
|
|
|
101
237
|
const usual = usualPartyOf(result.deliveries);
|
|
102
238
|
if (usual !== undefined)
|
|
103
239
|
out.deliveriesPartyId = usual;
|
|
104
|
-
|
|
240
|
+
const whose = readerOf(result, reader);
|
|
241
|
+
out.deliveries = whose
|
|
242
|
+
? groupDeliveries(result.deliveries.filter(isRecord), whose, (d) => compactDelivery(d, kindOfParty, usual))
|
|
243
|
+
: result.deliveries.map((d) => isRecord(d) ? compactDelivery(d, kindOfParty, usual) : d);
|
|
105
244
|
}
|
|
106
245
|
if (isRecord(result.session))
|
|
107
246
|
out.session = compactSession(result.session, result.connectionNotices);
|
|
@@ -135,9 +274,9 @@ export function compactInboxResult(result) {
|
|
|
135
274
|
return out;
|
|
136
275
|
}
|
|
137
276
|
/** The inbox result as one line of JSON: indentation is most of nothing. */
|
|
138
|
-
export function compactTextResult(data) {
|
|
277
|
+
export function compactTextResult(data, reader) {
|
|
139
278
|
return {
|
|
140
|
-
content: [{ type: 'text', text: JSON.stringify(compactInboxResult(data)) }],
|
|
279
|
+
content: [{ type: 'text', text: JSON.stringify(compactInboxResult(data, reader)) }],
|
|
141
280
|
};
|
|
142
281
|
}
|
|
143
282
|
/**
|
|
@@ -152,19 +291,17 @@ export function windowNote(deliveries, selfAgentIds, outOfReach) {
|
|
|
152
291
|
const rows = Array.isArray(deliveries) ? deliveries.filter(isRecord) : [];
|
|
153
292
|
if (!rows.length)
|
|
154
293
|
return '';
|
|
155
|
-
|
|
156
|
-
const
|
|
157
|
-
const
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
parts.push(`${others} ${others === 1 ? 'is' : 'are'} for other assistants`);
|
|
166
|
-
if (context)
|
|
167
|
-
parts.push(`${context} ${context === 1 ? 'is' : 'are'} context`);
|
|
294
|
+
// The same rule the grouping uses, so each count is its section's size.
|
|
295
|
+
const count = (s) => rows.filter((d) => sectionOf(d, selfAgentIds) === s).length;
|
|
296
|
+
const words = {
|
|
297
|
+
forYou: 'yours to handle',
|
|
298
|
+
forYourPerson: 'pass these along to your person',
|
|
299
|
+
forOtherAssistants: 'your person’s other assistants',
|
|
300
|
+
context: 'nobody is asked to act',
|
|
301
|
+
};
|
|
302
|
+
// forYou is always named, so "nothing for you" is said rather than implied.
|
|
303
|
+
const parts = INBOX_SECTIONS.filter((s) => s === 'forYou' || count(s) > 0).map((s) => `${s} ${count(s)} (${words[s]})`);
|
|
168
304
|
const reach = outOfReach ? ` ${outOfReach} of them ${outOfReach === 1 ? 'is' : 'are'} out of reach.` : '';
|
|
169
|
-
|
|
305
|
+
const total = `${rows.length} ${rows.length === 1 ? 'delivery' : 'deliveries'}`;
|
|
306
|
+
return `Those counts are decisions and tasks, not messages. This window has ${total}, grouped by who they are for, then by sender: ${parts.join(', ')}.${reach}`;
|
|
170
307
|
}
|
package/dist/server.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
2
|
import { type Creds } from '@ziggs-ai/api-client';
|
|
3
3
|
import type { ZiggsMcpConfig } from './config.js';
|
|
4
|
+
export type { ZiggsMcpConfig };
|
|
4
5
|
/** Shared MCP server factory — stdio (local) and remote HTTP (backend) reuse this. */
|
|
5
6
|
export declare function createZiggsMcpServer(creds: Creds, cfg: ZiggsMcpConfig): McpServer;
|
|
6
7
|
/** Drop this process's inbox host. Best-effort — shutdown must not fail on it. */
|
package/dist/toolAliases.js
CHANGED
|
@@ -38,8 +38,7 @@ export const SEARCH_ALIASES = {
|
|
|
38
38
|
'add people',
|
|
39
39
|
],
|
|
40
40
|
ziggs_agent_search: ['teammate', 'colleague', 'directory', 'find person'],
|
|
41
|
-
ziggs_access_request: ['access', 'permission', 'request access', 'chat', 'approval', 'grant'],
|
|
42
|
-
ziggs_context_request: ['grant', 'permission', 'restricted', 'request access', 'unlock document'],
|
|
41
|
+
ziggs_access_request: ['access', 'permission', 'request access', 'chat', 'approval', 'grant', 'restricted', 'unlock document'],
|
|
43
42
|
ziggs_agreement_claim: ['hire'],
|
|
44
43
|
ziggs_agreement_buy: [
|
|
45
44
|
'hire',
|
package/dist/tools.js
CHANGED
|
@@ -692,12 +692,14 @@ export function registerZiggsTools(server, creds, cfg) {
|
|
|
692
692
|
}
|
|
693
693
|
}
|
|
694
694
|
const news = formatInboxToolResult(inbox, null, cfg.ZIGGS_WEB_URL, undefined, reach, undefined, { agentId: creds.agentId, ownerUserId: ownerPrincipalId(creds, cfg) });
|
|
695
|
+
// Whose rows are whose, so the deliveries come back grouped.
|
|
696
|
+
const reader = { agentIds: [creds.agentId], personId: ownerPrincipalId(creds, cfg) };
|
|
695
697
|
// A long-poll is a continuation of a session that already oriented
|
|
696
698
|
// itself, so it returns news only. A cold call is the session start, and
|
|
697
699
|
// carries what the other two orientation tools used to each be called
|
|
698
700
|
// for: the binding, the decisions, and the card.
|
|
699
701
|
if (waitSeconds != null)
|
|
700
|
-
return compactTextResult(news);
|
|
702
|
+
return compactTextResult(news, reader);
|
|
701
703
|
const [bindingSettled, readsSettled] = await sessionStart;
|
|
702
704
|
const session = bindingSettled.status === 'fulfilled'
|
|
703
705
|
? bindingSettled.value
|
|
@@ -712,7 +714,7 @@ export function registerZiggsTools(server, creds, cfg) {
|
|
|
712
714
|
...news,
|
|
713
715
|
session,
|
|
714
716
|
sessionActionsFetchError: readsSettled.reason.message,
|
|
715
|
-
});
|
|
717
|
+
}, reader);
|
|
716
718
|
}
|
|
717
719
|
const actions = buildSessionActions(inbox, readsSettled.value, creds, cfg);
|
|
718
720
|
// No prose list of next actions rides here: the calls are in
|
|
@@ -729,7 +731,7 @@ export function registerZiggsTools(server, creds, cfg) {
|
|
|
729
731
|
...(note && typeof actions.notice === 'string'
|
|
730
732
|
? { notice: `${actions.notice} ${note}` }
|
|
731
733
|
: {}),
|
|
732
|
-
});
|
|
734
|
+
}, reader);
|
|
733
735
|
}
|
|
734
736
|
catch (e) {
|
|
735
737
|
return toolError(e);
|
|
@@ -754,7 +756,10 @@ export function registerZiggsTools(server, creds, cfg) {
|
|
|
754
756
|
registerStrictTool(server, 'ziggs_inbox_claim', {}, destructive('Claim the inbox for this worker'), async () => {
|
|
755
757
|
try {
|
|
756
758
|
const client = inboxFor(creds);
|
|
757
|
-
return compactTextResult(await client.getInbox()
|
|
759
|
+
return compactTextResult(await client.getInbox(), {
|
|
760
|
+
agentIds: [creds.agentId],
|
|
761
|
+
personId: ownerPrincipalId(creds, cfg),
|
|
762
|
+
});
|
|
758
763
|
}
|
|
759
764
|
catch (e) {
|
|
760
765
|
return toolError(e);
|
|
@@ -860,6 +865,12 @@ export function registerZiggsTools(server, creds, cfg) {
|
|
|
860
865
|
assigneeId: z.string().optional(),
|
|
861
866
|
waitsOn: z.array(z.string()).optional(),
|
|
862
867
|
joinKind: z.enum(['all', 'any']).optional(),
|
|
868
|
+
runIf: z
|
|
869
|
+
.string()
|
|
870
|
+
.min(1)
|
|
871
|
+
.max(500)
|
|
872
|
+
.optional()
|
|
873
|
+
.describe('A yes/no question over the results of the nodes this one waits on, such as "Does the review report at least one bug?". On yes the node runs; on no it is skipped, and nodes waiting on it go ahead. When the answer is unclear it waits for the buyer. Needs waitsOn.'),
|
|
863
874
|
}))
|
|
864
875
|
.min(1)
|
|
865
876
|
.max(100),
|
|
@@ -867,7 +878,7 @@ export function registerZiggsTools(server, creds, cfg) {
|
|
|
867
878
|
review: z.literal('before_execution').optional(),
|
|
868
879
|
})
|
|
869
880
|
.optional()
|
|
870
|
-
.describe('Native atomic graph. waitsOn values name nodeId values in this payload; all nodes are created or none are. Root nodes receive graph inputArtifactIds. review holds every node until the root acknowledgement.'),
|
|
881
|
+
.describe('Native atomic graph. waitsOn values name nodeId values in this payload; all nodes are created or none are. Root nodes receive graph inputArtifactIds. review holds every node until the root acknowledgement. runIf makes a node conditional on what the nodes before it found.'),
|
|
871
882
|
// POST /tasks and the SDK's task_create have always taken
|
|
872
883
|
// these three; only this surface hid them, so an agent on MCP had to
|
|
873
884
|
// create-then-replace_plan even when it already knew the steps.
|
|
@@ -949,7 +960,8 @@ export function registerZiggsTools(server, creds, cfg) {
|
|
|
949
960
|
});
|
|
950
961
|
registerStrictTool(server, 'ziggs_task_set_result', {
|
|
951
962
|
taskId: z.string(),
|
|
952
|
-
|
|
963
|
+
// Cancelling has its own tool, ziggs_task_cancel.
|
|
964
|
+
state: z.enum(['completed', 'failed']),
|
|
953
965
|
result: z
|
|
954
966
|
.object({
|
|
955
967
|
summary: z.string().optional(),
|
package/dist/trustTools.d.ts
CHANGED
|
@@ -3,6 +3,8 @@ import { type Creds } from '@ziggs-ai/api-client';
|
|
|
3
3
|
import type { ZiggsMcpConfig } from './config.js';
|
|
4
4
|
/** agent search + context grant management through MCP.
|
|
5
5
|
* The SDK-twin tools (search/get, links, delegate) come from the shared
|
|
6
|
-
* capability layer; only
|
|
7
|
-
*
|
|
6
|
+
* capability layer; only revoking a grant stays MCP-local. Issuing one is a
|
|
7
|
+
* person's act (POST /context/grants takes a signed-in session), so there is
|
|
8
|
+
* no agent tool for it: an agent shares with artifact_share, a chat_open
|
|
9
|
+
* invite, or context_delegate. */
|
|
8
10
|
export declare function registerTrustTools(server: McpServer, creds: Creds, cfg?: ZiggsMcpConfig): void;
|
package/dist/trustTools.js
CHANGED
|
@@ -1,103 +1,20 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { ContextGrantsClient, LINK_CAPABILITIES, INTRODUCTION_CAPABILITIES, DISCOVERY_CAPABILITIES, contextDelegateCapability, accessRequestCapability, } from '@ziggs-ai/api-client';
|
|
3
|
+
import { 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, 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);
|
|
14
|
-
const contextTemporalSchema = z.enum(['from-now', 'from-start']);
|
|
15
7
|
const DEFAULT_WEB_URL = 'https://ziggsai.com';
|
|
16
8
|
/** agent search + context grant management through MCP.
|
|
17
9
|
* The SDK-twin tools (search/get, links, delegate) come from the shared
|
|
18
|
-
* capability layer; only
|
|
19
|
-
*
|
|
10
|
+
* capability layer; only revoking a grant stays MCP-local. Issuing one is a
|
|
11
|
+
* person's act (POST /context/grants takes a signed-in session), so there is
|
|
12
|
+
* no agent tool for it: an agent shares with artifact_share, a chat_open
|
|
13
|
+
* invite, or context_delegate. */
|
|
20
14
|
export function registerTrustTools(server, creds, cfg) {
|
|
21
15
|
const webUrl = cfg?.ZIGGS_WEB_URL?.replace(/\/$/, '') ?? DEFAULT_WEB_URL;
|
|
22
16
|
registerCapabilities(server, DISCOVERY_CAPABILITIES, creds);
|
|
23
|
-
registerStrictTool(server, 'ziggs_context_issue_grant', {
|
|
24
|
-
holderId: z.string().describe('Bare agent id receiving the grant'),
|
|
25
|
-
scopeKind: grantScopeKindSchema,
|
|
26
|
-
scopeId: z.string().describe('chatId, agreementId, orgId, artifactId, or taskId'),
|
|
27
|
-
temporal: contextTemporalSchema
|
|
28
|
-
.optional()
|
|
29
|
-
.describe('from-now (default) or from-start (approval-gated on chat). Artifact ' +
|
|
30
|
-
'scope defaults to from-start and rejects from-now.'),
|
|
31
|
-
expiresAt: z
|
|
32
|
-
.string()
|
|
33
|
-
.optional()
|
|
34
|
-
.nullable()
|
|
35
|
-
.describe('ISO-8601 expiry; omit for platform default TTL'),
|
|
36
|
-
}, write('Give another agent access'), async ({ holderId, scopeKind, scopeId, temporal, expiresAt }) => {
|
|
37
|
-
// An artifact predates any watermark you could set, so from-now would
|
|
38
|
-
// validate and then read empty — the server refuses it outright. Defaulting
|
|
39
|
-
// artifact scope to from-now here made the tool's own default invocation
|
|
40
|
-
// fail, while the description promised it was always from-start. An
|
|
41
|
-
// EXPLICIT from-now still goes through and is refused loudly: silently
|
|
42
|
-
// rewriting what the caller asked for would hide the mistake.
|
|
43
|
-
const resolvedTemporal = temporal ?? (scopeKind === 'artifact' ? 'from-start' : 'from-now');
|
|
44
|
-
try {
|
|
45
|
-
if (scopeKind === 'chat') {
|
|
46
|
-
const result = await addChatMember({
|
|
47
|
-
chatId: scopeId,
|
|
48
|
-
memberId: holderId,
|
|
49
|
-
memberType: 'agent',
|
|
50
|
-
temporal: resolvedTemporal,
|
|
51
|
-
}, creds);
|
|
52
|
-
if ('status' in result && result.status === 'pending') {
|
|
53
|
-
return textResult({
|
|
54
|
-
status: 'pending_approval',
|
|
55
|
-
outcome: 'pending',
|
|
56
|
-
message: 'Human approval required before the grant is issued. Surface this to the user — do not treat as success.',
|
|
57
|
-
scope: { kind: 'chat', id: scopeId },
|
|
58
|
-
holderId,
|
|
59
|
-
temporal: resolvedTemporal,
|
|
60
|
-
pendingAdmission: result.pendingAdmission,
|
|
61
|
-
});
|
|
62
|
-
}
|
|
63
|
-
return textResult({
|
|
64
|
-
status: 'granted',
|
|
65
|
-
scope: { kind: 'chat', id: scopeId },
|
|
66
|
-
holderId,
|
|
67
|
-
temporal: resolvedTemporal,
|
|
68
|
-
bounds: {
|
|
69
|
-
temporal: resolvedTemporal,
|
|
70
|
-
note: 'from-now: readable content starts at grant watermark; from-start requires unanimous approval',
|
|
71
|
-
},
|
|
72
|
-
chat: 'chat' in result ? result.chat : undefined,
|
|
73
|
-
});
|
|
74
|
-
}
|
|
75
|
-
// #6 — an org scope may be named rather than pasted as org_… id.
|
|
76
|
-
let resolvedScopeId = scopeId;
|
|
77
|
-
if (scopeKind === 'org') {
|
|
78
|
-
resolvedScopeId = await resolveOrgScopeId({ creds, surface: 'mcp' }, scopeId);
|
|
79
|
-
}
|
|
80
|
-
const client = new ContextGrantsClient(creds.operatorKey, creds.agentId);
|
|
81
|
-
const grant = await client.issueGrant({
|
|
82
|
-
holderId,
|
|
83
|
-
// This tool only ever hands access to another agent.
|
|
84
|
-
holderKind: 'agent',
|
|
85
|
-
scope: { kind: scopeKind, id: resolvedScopeId },
|
|
86
|
-
temporal: resolvedTemporal,
|
|
87
|
-
expiresAt,
|
|
88
|
-
});
|
|
89
|
-
return textResult({
|
|
90
|
-
status: 'granted',
|
|
91
|
-
grant,
|
|
92
|
-
bounds: contextBounds(grant),
|
|
93
|
-
});
|
|
94
|
-
}
|
|
95
|
-
catch (e) {
|
|
96
|
-
return toolError(e);
|
|
97
|
-
}
|
|
98
|
-
});
|
|
99
17
|
registerCapability(server, contextDelegateCapability, creds);
|
|
100
|
-
registerCapability(server, contextRequestCapability, creds, { webUrl });
|
|
101
18
|
registerCapability(server, accessRequestCapability, creds, { webUrl });
|
|
102
19
|
if (!cfg?.coreOnly) {
|
|
103
20
|
// #7 — the link tool group is skipped by the lean session-start
|
package/dist/workspaceRouting.js
CHANGED
|
@@ -235,8 +235,8 @@ export class WorkspaceRouter {
|
|
|
235
235
|
...(name === 'ziggs_inbox' && args.waitSeconds == null ? { rules: sessionRules('directory') } : {}),
|
|
236
236
|
...(note && typeof pending.notice === 'string' ? { notice: `${pending.notice} ${note}` } : {}),
|
|
237
237
|
session: { workspaces },
|
|
238
|
-
instruction: `
|
|
239
|
-
});
|
|
238
|
+
instruction: `deliveries.forYou is your work; deliveries.forYourPerson is for your person to see. ${ASK_YOUR_HUMAN}`,
|
|
239
|
+
}, { agentIds: self.agentIds ?? [] });
|
|
240
240
|
}
|
|
241
241
|
if (name === 'ziggs_inbox_peek') {
|
|
242
242
|
return result(await this.client.request(`inbox/peek?wait=${Number(args.waitSeconds ?? 0)}`));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ziggs-ai/ziggs-mcp",
|
|
3
|
-
"version": "0.37.
|
|
3
|
+
"version": "0.37.5",
|
|
4
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": {
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
},
|
|
48
48
|
"dependencies": {
|
|
49
49
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
50
|
-
"@ziggs-ai/api-client": "0.37.
|
|
50
|
+
"@ziggs-ai/api-client": "0.37.5",
|
|
51
51
|
"dotenv": "^16.6.1",
|
|
52
52
|
"zod": "^3.24.2",
|
|
53
53
|
"zod-to-json-schema": "^3.25.1"
|
package/skills/ziggs/SKILL.md
CHANGED
|
@@ -85,8 +85,8 @@ See [references/inbox-rhythm.md](references/inbox-rhythm.md) for a full catch-up
|
|
|
85
85
|
- **`ziggs_inbox`** (cold, no `waitSeconds`): at session start, if anything awaits approve/reject, show **`sessionChatCard`** (includes app links + exact respond commands). Do not auto-approve.
|
|
86
86
|
- **`pending_approval`** (grants, admissions, from-start history, agreement steps): **stop and show the human** — do not auto-approve on their behalf unless they explicitly asked for that action in this session.
|
|
87
87
|
- **`humanAttention` on inbox**: when present, **tell the human immediately** — list each pending agreement proposal and ask approve/reject before other work.
|
|
88
|
-
- Before **`
|
|
89
|
-
- Trust tools (`ziggs_agent_search`, grant
|
|
88
|
+
- Before **`ziggs_context_delegate`**, a **`ziggs_chat_open`** invite into an existing room, or any grant that exposes **existing** org/chat/agreement context: **ask the human** what scope and temporal bound they want (`from-now` vs `from-start`).
|
|
89
|
+
- Trust tools (`ziggs_agent_search`, grant delegate/revoke): use for cross-org collaboration only when the human’s goal requires it.
|
|
90
90
|
|
|
91
91
|
See [references/grants-and-approvals.md](references/grants-and-approvals.md).
|
|
92
92
|
|
|
@@ -51,7 +51,7 @@ counterparty owner approves it, and unpublished delegates can then reach each ot
|
|
|
51
51
|
3. Target human approves via **`ziggs_agreement_respond`** (action `approve`) when the
|
|
52
52
|
pending link agreement shows in their inbox / `ziggs_link_list`. This is the same generic
|
|
53
53
|
approval tool used for hire and service proposals — there is no separate link-response tool.
|
|
54
|
-
4. **`ziggs_chat_open`** with the person to talk
|
|
54
|
+
4. **`ziggs_chat_open`** with the person to talk. To let them read existing context: **`ziggs_artifact_share`** for one artifact, a **`ziggs_chat_open`** invite (chatId plus participantIds) for a room, or **`ziggs_context_delegate`** for a slice of a grant you hold.
|
|
55
55
|
5. To read their context, they grant it or you ask with **`ziggs_access_request`**.
|
|
56
56
|
6. `ziggs_context_revoke_grant` when context access should end; **`ziggs_agreement_revoke`** when
|
|
57
57
|
the agreement should end — any kind: hire, service, request, offer, or link.
|
|
@@ -61,4 +61,4 @@ counterparty owner approves it, and unpublished delegates can then reach each ot
|
|
|
61
61
|
## Web UI (Settings → Connections)
|
|
62
62
|
|
|
63
63
|
Confusingly named after the *other* "connection" (this is the agent-link handshake UI, not
|
|
64
|
-
`ziggs_connection_proxy` third-party connections). After an agent link appears under **Connected**, use your Claude delegate (MCP) for **`ziggs_chat_open`** or **`
|
|
64
|
+
`ziggs_connection_proxy` third-party connections). After an agent link appears under **Connected**, use your Claude delegate (MCP) for **`ziggs_chat_open`**, **`ziggs_artifact_share`** or **`ziggs_context_delegate`**. The web UI manages handshake only — not chat or context grants.
|