@ziggs-ai/ziggs-mcp 0.1.25 → 0.1.27
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 +2 -2
- package/dist/inboxToolResult.d.ts +1 -1
- package/dist/inboxToolResult.js +10 -2
- package/dist/pendingDecisions.d.ts +15 -0
- package/dist/pendingDecisions.js +34 -0
- package/dist/toolError.d.ts +25 -0
- package/dist/toolError.js +81 -0
- package/dist/tools.js +40 -19
- package/dist/trustTools.js +44 -12
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
MCP (stdio) server for **Claude Code**, **Cursor**, and other MCP hosts.
|
|
4
4
|
|
|
5
|
-
**In scope:** chat, agreements, scope, context discovery/reads, artifacts.
|
|
6
|
-
**Out of scope (by design):** agent `transfer`,
|
|
5
|
+
**In scope:** chat, agreements (service and hire, direct or published), scope, context discovery/reads, artifacts.
|
|
6
|
+
**Out of scope (by design):** agent `transfer`, payment grants / bounded spend.
|
|
7
7
|
|
|
8
8
|
---
|
|
9
9
|
|
|
@@ -73,4 +73,4 @@ export declare function indexReachByScope(reach: ContextReachDescriptor[]): Map<
|
|
|
73
73
|
* When `reach` (the caller's own live grants) is passed, each scope is tagged
|
|
74
74
|
* with its covering grant (ZIG-635) so the agent can pin X-Context-Grant-Id.
|
|
75
75
|
*/
|
|
76
|
-
export declare function formatInboxToolResult(inbox: InboxEnvelope, ack?: InboxAckResult | null, webOrigin?: string, activeTasks?: Task[], reach?: ContextReachDescriptor[]): Record<string, unknown>;
|
|
76
|
+
export declare function formatInboxToolResult(inbox: InboxEnvelope, ack?: InboxAckResult | null, webOrigin?: string, activeTasks?: Task[], reach?: ContextReachDescriptor[], activeTasksError?: string): Record<string, unknown>;
|
package/dist/inboxToolResult.js
CHANGED
|
@@ -205,7 +205,7 @@ function tagScopesWithGrants(scopes, byScope) {
|
|
|
205
205
|
* When `reach` (the caller's own live grants) is passed, each scope is tagged
|
|
206
206
|
* with its covering grant (ZIG-635) so the agent can pin X-Context-Grant-Id.
|
|
207
207
|
*/
|
|
208
|
-
export function formatInboxToolResult(inbox, ack, webOrigin, activeTasks, reach) {
|
|
208
|
+
export function formatInboxToolResult(inbox, ack, webOrigin, activeTasks, reach, activeTasksError) {
|
|
209
209
|
// ZIG-660: build the grant index once and feed both the read plan (grant
|
|
210
210
|
// pinning) and the scope tags from it — buildReadPlan no longer runs before
|
|
211
211
|
// the grants are available.
|
|
@@ -213,7 +213,7 @@ export function formatInboxToolResult(inbox, ack, webOrigin, activeTasks, reach)
|
|
|
213
213
|
const { plan: readPlan, truncated: readPlanTruncated } = buildReadPlan(inbox, byScope);
|
|
214
214
|
const scopes = tagScopesWithGrants(inbox.scopes ?? [], byScope);
|
|
215
215
|
const origin = resolveWebAppOrigin(webOrigin);
|
|
216
|
-
const pending = formatPendingDecisionsPayload(inbox, origin, { activeTasks });
|
|
216
|
+
const pending = formatPendingDecisionsPayload(inbox, origin, { activeTasks, activeTasksError });
|
|
217
217
|
const pendingTail = pending.hasActionable === true
|
|
218
218
|
? {
|
|
219
219
|
pendingCount: pending.pendingCount,
|
|
@@ -226,6 +226,14 @@ export function formatInboxToolResult(inbox, ack, webOrigin, activeTasks, reach)
|
|
|
226
226
|
: {};
|
|
227
227
|
const tail = {
|
|
228
228
|
...pendingTail,
|
|
229
|
+
// ZIG-700 — always surface a task-fetch failure, even when there is nothing
|
|
230
|
+
// else actionable, so hasActiveWork:false is not read as "no tasks".
|
|
231
|
+
...(pending.activeTasksFetchError
|
|
232
|
+
? {
|
|
233
|
+
activeTasksFetchError: pending.activeTasksFetchError,
|
|
234
|
+
workCardOmitted: true,
|
|
235
|
+
}
|
|
236
|
+
: {}),
|
|
229
237
|
...(readPlan.length ? { readPlan } : {}),
|
|
230
238
|
...(readPlanTruncated ? { readPlanTruncated } : {}),
|
|
231
239
|
};
|
|
@@ -34,6 +34,20 @@ export declare function agreementsListAppUrl(origin: string): string;
|
|
|
34
34
|
/** Where the human connects MCP servers and grants tools (ZIG-686). */
|
|
35
35
|
export declare function connectionsSettingsAppUrl(origin: string): string;
|
|
36
36
|
export declare function buildPendingDecisionItems(inbox: InboxEnvelope, webOrigin: string): PendingDecisionItem[];
|
|
37
|
+
/**
|
|
38
|
+
* ZIG-658: keep only tasks this delegate (or its principal) is expected to
|
|
39
|
+
* execute. The backend's GET /tasks returns every task reachable in the org,
|
|
40
|
+
* so the session card must not present all of them as "your work".
|
|
41
|
+
*
|
|
42
|
+
* A task is "for me" when:
|
|
43
|
+
* - it is explicitly assigned (`assigneeId`) to one of my ids, or
|
|
44
|
+
* - it has no explicit assignee and one of my ids sits on the executing side
|
|
45
|
+
* of the task/agreement (executor, agent, provider, providerAgent,
|
|
46
|
+
* proposedTo).
|
|
47
|
+
* Tasks with no assignee and no readable agreement parties are excluded —
|
|
48
|
+
* "can't tell" must not render as "yours".
|
|
49
|
+
*/
|
|
50
|
+
export declare function filterTasksForDelegate(tasks: Task[], selfIds: ReadonlySet<string>): Task[];
|
|
37
51
|
export declare function buildActiveWorkItems(tasks: Task[], webOrigin: string): ActiveWorkItem[];
|
|
38
52
|
export declare function buildDecisionChatCard(items: PendingDecisionItem[], opts: {
|
|
39
53
|
truncatedProposals?: number;
|
|
@@ -52,5 +66,6 @@ export declare function buildSessionChatCard(decisions: PendingDecisionItem[], w
|
|
|
52
66
|
/** Structured session payload for MCP tools (ZIG-625 + active work). */
|
|
53
67
|
export declare function formatPendingDecisionsPayload(inbox: InboxEnvelope, webOrigin: string, opts?: {
|
|
54
68
|
activeTasks?: Task[];
|
|
69
|
+
activeTasksError?: string;
|
|
55
70
|
}): Record<string, unknown>;
|
|
56
71
|
export declare function buildPendingNextActions(decisions: PendingDecisionItem[], work?: ActiveWorkItem[]): string[];
|
package/dist/pendingDecisions.js
CHANGED
|
@@ -109,6 +109,32 @@ export function buildPendingDecisionItems(inbox, webOrigin) {
|
|
|
109
109
|
}
|
|
110
110
|
return items;
|
|
111
111
|
}
|
|
112
|
+
/**
|
|
113
|
+
* ZIG-658: keep only tasks this delegate (or its principal) is expected to
|
|
114
|
+
* execute. The backend's GET /tasks returns every task reachable in the org,
|
|
115
|
+
* so the session card must not present all of them as "your work".
|
|
116
|
+
*
|
|
117
|
+
* A task is "for me" when:
|
|
118
|
+
* - it is explicitly assigned (`assigneeId`) to one of my ids, or
|
|
119
|
+
* - it has no explicit assignee and one of my ids sits on the executing side
|
|
120
|
+
* of the task/agreement (executor, agent, provider, providerAgent,
|
|
121
|
+
* proposedTo).
|
|
122
|
+
* Tasks with no assignee and no readable agreement parties are excluded —
|
|
123
|
+
* "can't tell" must not render as "yours".
|
|
124
|
+
*/
|
|
125
|
+
export function filterTasksForDelegate(tasks, selfIds) {
|
|
126
|
+
const mine = (id) => typeof id === 'string' && id.length > 0 && selfIds.has(id);
|
|
127
|
+
return tasks.filter((t) => {
|
|
128
|
+
if (t.assigneeId)
|
|
129
|
+
return mine(t.assigneeId);
|
|
130
|
+
if (mine(t.executorId) || mine(t.agentId))
|
|
131
|
+
return true;
|
|
132
|
+
const p = t.agreement?.parties;
|
|
133
|
+
if (!p)
|
|
134
|
+
return false;
|
|
135
|
+
return mine(p.provider) || mine(p.providerAgent) || mine(p.proposedTo);
|
|
136
|
+
});
|
|
137
|
+
}
|
|
112
138
|
export function buildActiveWorkItems(tasks, webOrigin) {
|
|
113
139
|
return tasks
|
|
114
140
|
.filter((t) => t.state === 'active')
|
|
@@ -381,6 +407,14 @@ export function formatPendingDecisionsPayload(inbox, webOrigin, opts) {
|
|
|
381
407
|
...(workChatCard ? { workChatCard } : {}),
|
|
382
408
|
...(sessionChatCard ? { sessionChatCard } : {}),
|
|
383
409
|
...(inbox.humanAttention ? { humanAttention: inbox.humanAttention } : {}),
|
|
410
|
+
// ZIG-700 — when the active-task fetch failed, say so instead of letting
|
|
411
|
+
// hasActiveWork:false read as "no tasks". Mirrors the inbox fetchError signal.
|
|
412
|
+
...(opts?.activeTasksError
|
|
413
|
+
? {
|
|
414
|
+
activeTasksFetchError: `Could not load active tasks: ${opts.activeTasksError}`,
|
|
415
|
+
workCardOmitted: true,
|
|
416
|
+
}
|
|
417
|
+
: {}),
|
|
384
418
|
instruction,
|
|
385
419
|
};
|
|
386
420
|
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ZIG-667 — one error surface for every MCP tool.
|
|
3
|
+
*
|
|
4
|
+
* Raw client exceptions read like
|
|
5
|
+
* `ContextReadClient.read messages 403 {"error":"not authorized for this scope"}`
|
|
6
|
+
* — an internal class.method name, a raw HTTP status, and a raw backend body.
|
|
7
|
+
* LLM callers stall on stack-trace prose; they recover from errors they can
|
|
8
|
+
* parse. This module strips the internals, keeps a stable machine-readable
|
|
9
|
+
* code, and for authorization denials says what to do next.
|
|
10
|
+
*/
|
|
11
|
+
export interface ToolErrorShape {
|
|
12
|
+
code: string;
|
|
13
|
+
message: string;
|
|
14
|
+
hint?: string;
|
|
15
|
+
}
|
|
16
|
+
/** Classify a raw client error message into a stable shape. */
|
|
17
|
+
export declare function classifyToolError(rawMessage: string): ToolErrorShape;
|
|
18
|
+
/** MCP tool error result: machine-readable code + cleaned message (+ hint). */
|
|
19
|
+
export declare function toolError(message: string): {
|
|
20
|
+
content: {
|
|
21
|
+
type: "text";
|
|
22
|
+
text: string;
|
|
23
|
+
}[];
|
|
24
|
+
isError: boolean;
|
|
25
|
+
};
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ZIG-667 — one error surface for every MCP tool.
|
|
3
|
+
*
|
|
4
|
+
* Raw client exceptions read like
|
|
5
|
+
* `ContextReadClient.read messages 403 {"error":"not authorized for this scope"}`
|
|
6
|
+
* — an internal class.method name, a raw HTTP status, and a raw backend body.
|
|
7
|
+
* LLM callers stall on stack-trace prose; they recover from errors they can
|
|
8
|
+
* parse. This module strips the internals, keeps a stable machine-readable
|
|
9
|
+
* code, and for authorization denials says what to do next.
|
|
10
|
+
*/
|
|
11
|
+
const CLIENT_PREFIX = /^[A-Z][A-Za-z0-9]*Client\.[A-Za-z0-9_]+\s+/;
|
|
12
|
+
const HTTP_STATUS = /(?:^|\s)([1-5]\d{2})(?=\s|$)/;
|
|
13
|
+
const SCOPE_DENIED_HINT = 'You are not authorized for this scope. To get access: ask the counterparty ' +
|
|
14
|
+
'to issue you a context grant (they run ziggs_issue_grant), or request a ' +
|
|
15
|
+
'bilateral link first (ziggs_request_link). Check what you can already ' +
|
|
16
|
+
'reach with ziggs_discover_context / ziggs_get_scope.';
|
|
17
|
+
function codeForStatus(status) {
|
|
18
|
+
if (status === 401)
|
|
19
|
+
return 'NOT_AUTHENTICATED';
|
|
20
|
+
if (status === 403)
|
|
21
|
+
return 'NOT_AUTHORIZED';
|
|
22
|
+
if (status === 404)
|
|
23
|
+
return 'NOT_FOUND';
|
|
24
|
+
if (status === 409)
|
|
25
|
+
return 'CONFLICT';
|
|
26
|
+
if (status === 429)
|
|
27
|
+
return 'RATE_LIMITED';
|
|
28
|
+
if (status >= 500)
|
|
29
|
+
return 'UPSTREAM_ERROR';
|
|
30
|
+
if (status >= 400)
|
|
31
|
+
return 'BAD_REQUEST';
|
|
32
|
+
return 'TOOL_ERROR';
|
|
33
|
+
}
|
|
34
|
+
/** Pull a human reason out of an embedded backend JSON body, if any. */
|
|
35
|
+
function extractBodyReason(raw) {
|
|
36
|
+
const start = raw.indexOf('{');
|
|
37
|
+
if (start === -1)
|
|
38
|
+
return null;
|
|
39
|
+
try {
|
|
40
|
+
const parsed = JSON.parse(raw.slice(start));
|
|
41
|
+
const reason = parsed['error'] ?? parsed['message'];
|
|
42
|
+
return typeof reason === 'string' && reason.trim() ? reason.trim() : null;
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
/** Classify a raw client error message into a stable shape. */
|
|
49
|
+
export function classifyToolError(rawMessage) {
|
|
50
|
+
const cleaned = rawMessage.replace(CLIENT_PREFIX, '').trim();
|
|
51
|
+
const statusMatch = HTTP_STATUS.exec(cleaned);
|
|
52
|
+
const status = statusMatch ? Number(statusMatch[1]) : null;
|
|
53
|
+
const bodyReason = extractBodyReason(cleaned);
|
|
54
|
+
if (status === null) {
|
|
55
|
+
return { code: 'TOOL_ERROR', message: cleaned || rawMessage };
|
|
56
|
+
}
|
|
57
|
+
const code = codeForStatus(status);
|
|
58
|
+
// Prefer the backend's own reason over the transport framing.
|
|
59
|
+
const message = bodyReason ?? cleaned;
|
|
60
|
+
// Context-scope denials (the backend says "…for this scope") get the scope
|
|
61
|
+
// code and a recovery path. Other 403s (connection grants, party checks)
|
|
62
|
+
// keep the generic code — their fixes live in other domains.
|
|
63
|
+
if (code === 'NOT_AUTHORIZED' && /\bscope\b/i.test(message)) {
|
|
64
|
+
return {
|
|
65
|
+
code: 'NOT_AUTHORIZED_FOR_SCOPE',
|
|
66
|
+
message,
|
|
67
|
+
hint: SCOPE_DENIED_HINT,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
return { code, message };
|
|
71
|
+
}
|
|
72
|
+
/** MCP tool error result: machine-readable code + cleaned message (+ hint). */
|
|
73
|
+
export function toolError(message) {
|
|
74
|
+
const shape = classifyToolError(message);
|
|
75
|
+
return {
|
|
76
|
+
content: [
|
|
77
|
+
{ type: 'text', text: JSON.stringify({ error: shape }, null, 2) },
|
|
78
|
+
],
|
|
79
|
+
isError: true,
|
|
80
|
+
};
|
|
81
|
+
}
|
package/dist/tools.js
CHANGED
|
@@ -4,9 +4,10 @@ import { getAgreement, getMyAgreements, listMyChats, openConversation, proposeDi
|
|
|
4
4
|
import { decodeOperatorKeyClaims } from './operatorKey.js';
|
|
5
5
|
import { registerTrustTools } from './trustTools.js';
|
|
6
6
|
import { formatInboxToolResult, buildReadContextReadPlan, } from './inboxToolResult.js';
|
|
7
|
-
import { connectionsSettingsAppUrl, formatPendingDecisionsPayload, resolveWebAppOrigin, buildPendingNextActions, } from './pendingDecisions.js';
|
|
7
|
+
import { connectionsSettingsAppUrl, filterTasksForDelegate, formatPendingDecisionsPayload, resolveWebAppOrigin, buildPendingNextActions, } from './pendingDecisions.js';
|
|
8
8
|
import { PROTOCOL } from './protocol/delegateProtocol.js';
|
|
9
9
|
import { READ_ONLY, WRITE, DESTRUCTIVE } from './toolAnnotations.js';
|
|
10
|
+
import { toolError } from './toolError.js';
|
|
10
11
|
// ZIG-557: the protocol sentences (loop / ack / humanAttention) are sourced
|
|
11
12
|
// from the shared const so this description can't drift from SKILL / server
|
|
12
13
|
// instructions / .cursorrules.
|
|
@@ -48,12 +49,6 @@ function textResult(data) {
|
|
|
48
49
|
content: [{ type: 'text', text: JSON.stringify(data, null, 2) }],
|
|
49
50
|
};
|
|
50
51
|
}
|
|
51
|
-
function toolError(message) {
|
|
52
|
-
return {
|
|
53
|
-
content: [{ type: 'text', text: message }],
|
|
54
|
-
isError: true,
|
|
55
|
-
};
|
|
56
|
-
}
|
|
57
52
|
const scopeKindSchema = z.enum(['chat', 'agreement', 'task', 'counterparty']);
|
|
58
53
|
const contextReadTypeSchema = z.enum([
|
|
59
54
|
'messages',
|
|
@@ -84,6 +79,8 @@ async function writeArtifactStrict(creds, input) {
|
|
|
84
79
|
const body = await res.text().catch(() => '');
|
|
85
80
|
throw new Error(`POST /artifacts ${res.status} ${body.slice(0, 200)}`);
|
|
86
81
|
}
|
|
82
|
+
const body = (await res.json().catch(() => ({})));
|
|
83
|
+
return { artifactId: body.artifactId };
|
|
87
84
|
}
|
|
88
85
|
// ZIG-569 — defense-in-depth mirror of the backend leak-guard
|
|
89
86
|
// (assertProxyResponseDoesNotLeakTokens). The backend strips the *specific*
|
|
@@ -249,19 +246,36 @@ async function listMcpConnectionRequests(creds) {
|
|
|
249
246
|
const parsed = body ? JSON.parse(body) : null;
|
|
250
247
|
return parsed?.['requests'] ?? [];
|
|
251
248
|
}
|
|
249
|
+
/**
|
|
250
|
+
* Ids this delegate answers for: its own agent id plus its principal's user
|
|
251
|
+
* id (operator-key ownerId / ZIGGS_OWNER_USER_ID). Used to decide which
|
|
252
|
+
* active tasks count as "your work" on session cards (ZIG-658).
|
|
253
|
+
*/
|
|
254
|
+
function delegateSelfIds(creds, cfg) {
|
|
255
|
+
const ids = new Set([creds.agentId]);
|
|
256
|
+
const ownerId = decodeOperatorKeyClaims(creds.operatorKey)?.ownerId;
|
|
257
|
+
if (ownerId)
|
|
258
|
+
ids.add(ownerId);
|
|
259
|
+
if (cfg.ZIGGS_OWNER_USER_ID)
|
|
260
|
+
ids.add(cfg.ZIGGS_OWNER_USER_ID);
|
|
261
|
+
return ids;
|
|
262
|
+
}
|
|
252
263
|
async function loadSessionActionsPayload(creds, cfg) {
|
|
253
264
|
const webOrigin = resolveWebAppOrigin(cfg.ZIGGS_WEB_URL);
|
|
254
265
|
const client = new InboxClient(creds.operatorKey, creds.agentId);
|
|
255
266
|
const inbox = await client.getInbox();
|
|
256
267
|
let activeTasks = [];
|
|
268
|
+
let activeTasksError;
|
|
257
269
|
try {
|
|
258
270
|
const listed = await listTasks({ state: 'active', limit: 20 }, creds);
|
|
259
|
-
activeTasks = listed.tasks ?? [];
|
|
271
|
+
activeTasks = filterTasksForDelegate(listed.tasks ?? [], delegateSelfIds(creds, cfg));
|
|
260
272
|
}
|
|
261
|
-
catch {
|
|
262
|
-
//
|
|
273
|
+
catch (e) {
|
|
274
|
+
// ZIG-700 — inbox is still useful when task listing fails, but surface the
|
|
275
|
+
// failure so hasActiveWork:false is not mistaken for "no tasks".
|
|
276
|
+
activeTasksError = e.message;
|
|
263
277
|
}
|
|
264
|
-
return formatPendingDecisionsPayload(inbox, webOrigin, { activeTasks });
|
|
278
|
+
return formatPendingDecisionsPayload(inbox, webOrigin, { activeTasks, activeTasksError });
|
|
265
279
|
}
|
|
266
280
|
export function registerZiggsTools(server, creds, cfg) {
|
|
267
281
|
server.tool('ziggs_auth_status', 'Verify MCP OAuth binding: delegate agent id, owner user id, and org scope. Call after connect before inbox/chats. Includes pendingDecisions summary when approve/reject is waiting. (Renamed from ziggs_connection_status — "connection" now refers only to third-party credential connections, see ziggs_connection_proxy.)', {}, READ_ONLY, async () => {
|
|
@@ -477,7 +491,7 @@ export function registerZiggsTools(server, creds, cfg) {
|
|
|
477
491
|
return toolError(e.message);
|
|
478
492
|
}
|
|
479
493
|
});
|
|
480
|
-
server.tool('ziggs_propose_agreement', 'Propose a direct
|
|
494
|
+
server.tool('ziggs_propose_agreement', 'Propose a direct agreement to one counterparty in a chat, as the payer-side delegate. engagementKind "service" (default) = one deliverable; "hire" = an ongoing engagement (same kinds ziggs_publish_offer accepts). Requires payerId (or ZIGGS_OWNER_USER_ID). price is recorded on the agreement but does not itself trigger a transfer — V1 has no real payment rail yet.', {
|
|
481
495
|
proposedTo: z.string(),
|
|
482
496
|
chatId: z.string(),
|
|
483
497
|
description: z.string(),
|
|
@@ -486,7 +500,11 @@ export function registerZiggsTools(server, creds, cfg) {
|
|
|
486
500
|
.optional()
|
|
487
501
|
.describe('Human user id = payer (your userId)'),
|
|
488
502
|
price: z.number().optional().describe('Optional; does not trigger transfer by itself'),
|
|
489
|
-
|
|
503
|
+
engagementKind: z
|
|
504
|
+
.enum(['hire', 'service'])
|
|
505
|
+
.optional()
|
|
506
|
+
.describe("'service' (default) = one-off deliverable; 'hire' = ongoing engagement"),
|
|
507
|
+
}, WRITE, async ({ proposedTo, chatId, description, payerId, price, engagementKind }) => {
|
|
490
508
|
try {
|
|
491
509
|
const resolvedPayer = payerId ?? cfg.ZIGGS_OWNER_USER_ID;
|
|
492
510
|
if (!resolvedPayer) {
|
|
@@ -498,7 +516,7 @@ export function registerZiggsTools(server, creds, cfg) {
|
|
|
498
516
|
description,
|
|
499
517
|
payerId: resolvedPayer,
|
|
500
518
|
price,
|
|
501
|
-
engagementKind: 'service',
|
|
519
|
+
engagementKind: engagementKind ?? 'service',
|
|
502
520
|
}, creds);
|
|
503
521
|
return textResult({ agreement });
|
|
504
522
|
}
|
|
@@ -604,12 +622,14 @@ export function registerZiggsTools(server, creds, cfg) {
|
|
|
604
622
|
const acked = ack?.length ? await client.ack(ack) : null;
|
|
605
623
|
const inbox = await client.getInbox();
|
|
606
624
|
let activeTasks = [];
|
|
625
|
+
let activeTasksError;
|
|
607
626
|
try {
|
|
608
627
|
const listed = await listTasks({ state: 'active', limit: 20 }, creds);
|
|
609
|
-
activeTasks = listed.tasks ?? [];
|
|
628
|
+
activeTasks = filterTasksForDelegate(listed.tasks ?? [], delegateSelfIds(creds, cfg));
|
|
610
629
|
}
|
|
611
|
-
catch {
|
|
612
|
-
//
|
|
630
|
+
catch (e) {
|
|
631
|
+
// ZIG-700 — surface the failure instead of silently omitting the work card.
|
|
632
|
+
activeTasksError = e.message;
|
|
613
633
|
}
|
|
614
634
|
// ZIG-635: tag each scope with the covering grant from the caller's own
|
|
615
635
|
// live reach descriptors. Best-effort — the inbox still works untagged.
|
|
@@ -620,7 +640,7 @@ export function registerZiggsTools(server, creds, cfg) {
|
|
|
620
640
|
catch {
|
|
621
641
|
// omit grant tags when discovery fails
|
|
622
642
|
}
|
|
623
|
-
return textResult(formatInboxToolResult(inbox, acked, cfg.ZIGGS_WEB_URL, activeTasks, reach));
|
|
643
|
+
return textResult(formatInboxToolResult(inbox, acked, cfg.ZIGGS_WEB_URL, activeTasks, reach, activeTasksError));
|
|
624
644
|
}
|
|
625
645
|
catch (e) {
|
|
626
646
|
return toolError(e.message);
|
|
@@ -693,7 +713,7 @@ export function registerZiggsTools(server, creds, cfg) {
|
|
|
693
713
|
if ((chatId && agreementId) || (!chatId && !agreementId)) {
|
|
694
714
|
return toolError('Pass exactly one of chatId or agreementId');
|
|
695
715
|
}
|
|
696
|
-
await writeArtifactStrict(creds, {
|
|
716
|
+
const { artifactId } = await writeArtifactStrict(creds, {
|
|
697
717
|
text,
|
|
698
718
|
visibility,
|
|
699
719
|
chatId,
|
|
@@ -703,6 +723,7 @@ export function registerZiggsTools(server, creds, cfg) {
|
|
|
703
723
|
});
|
|
704
724
|
return textResult({
|
|
705
725
|
ok: true,
|
|
726
|
+
artifactId,
|
|
706
727
|
visibility,
|
|
707
728
|
chatId,
|
|
708
729
|
agreementId,
|
package/dist/trustTools.js
CHANGED
|
@@ -1,17 +1,12 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import { AgentSearchClient, ContextGrantsClient, createAgreement, listAgreements, revokeAgreement, claimAgreement, addChatMember, } from '@ziggs-ai/api-client';
|
|
3
3
|
import { READ_ONLY, WRITE, DESTRUCTIVE } from './toolAnnotations.js';
|
|
4
|
+
import { toolError } from './toolError.js';
|
|
4
5
|
function textResult(data) {
|
|
5
6
|
return {
|
|
6
7
|
content: [{ type: 'text', text: JSON.stringify(data, null, 2) }],
|
|
7
8
|
};
|
|
8
9
|
}
|
|
9
|
-
function toolError(message) {
|
|
10
|
-
return {
|
|
11
|
-
content: [{ type: 'text', text: message }],
|
|
12
|
-
isError: true,
|
|
13
|
-
};
|
|
14
|
-
}
|
|
15
10
|
const grantScopeKindSchema = z.enum(['chat', 'agreement', 'org']);
|
|
16
11
|
const contextTemporalSchema = z.enum(['from-now', 'from-start']);
|
|
17
12
|
const DEFAULT_WEB_URL = 'https://ziggsai.com';
|
|
@@ -29,9 +24,21 @@ export function registerTrustTools(server, creds, cfg) {
|
|
|
29
24
|
if (!result.success) {
|
|
30
25
|
return toolError(result.error ?? result.message ?? 'search failed');
|
|
31
26
|
}
|
|
27
|
+
if (!result.agents?.length) {
|
|
28
|
+
// ZIG-664: a bare {count: 0} reads as "discovery is down" to LLM
|
|
29
|
+
// callers — say what was searched and how to recover instead.
|
|
30
|
+
return textResult({
|
|
31
|
+
count: 0,
|
|
32
|
+
agents: [],
|
|
33
|
+
searched: ['published store', 'your org-mates', 'your linked delegates'],
|
|
34
|
+
hint: 'Zero hits means no agent profile matched these terms — discovery itself is up. ' +
|
|
35
|
+
'Matching is lexical against agent name/description/tags, so try shorter or different keywords. ' +
|
|
36
|
+
'If you already know the agent, pass its exact agent id as the query to resolve it directly.',
|
|
37
|
+
});
|
|
38
|
+
}
|
|
32
39
|
return textResult({
|
|
33
|
-
count: result.agents
|
|
34
|
-
agents: result.agents
|
|
40
|
+
count: result.agents.length,
|
|
41
|
+
agents: result.agents,
|
|
35
42
|
});
|
|
36
43
|
}
|
|
37
44
|
catch (e) {
|
|
@@ -197,13 +204,38 @@ export function registerTrustTools(server, creds, cfg) {
|
|
|
197
204
|
return toolError(e.message);
|
|
198
205
|
}
|
|
199
206
|
});
|
|
200
|
-
server.tool('ziggs_list_links', 'List link agreements for this delegate — bilateral agent-to-agent trust relationships, NOT third-party service connections (see ziggs_list_my_connections for those) (GET /agreements?engagementKind=link). Each item
|
|
207
|
+
server.tool('ziggs_list_links', 'List link agreements for this delegate — bilateral agent-to-agent trust relationships, NOT third-party service connections (see ziggs_list_my_connections for those) (GET /agreements?engagementKind=link). Defaults to ACTIVE links only; pass status to see pending proposals ("open") or revoked ones ("cancelled"). Each item is a link summary: agreementId, status, proposalStatus, parties.creatorAgent (requester), parties.providerAgent (target), parties.proposedTo (target owner). Approve pending links via ziggs_respond_to_agreement.', {
|
|
208
|
+
status: z
|
|
209
|
+
.enum(['active', 'open', 'cancelled', 'all'])
|
|
210
|
+
.optional()
|
|
211
|
+
.describe('active (default) = established links; open = pending proposals/invites awaiting approval or claim; cancelled = revoked; all = every link regardless of status'),
|
|
212
|
+
}, READ_ONLY, async ({ status }) => {
|
|
201
213
|
try {
|
|
202
|
-
const
|
|
214
|
+
const resolvedStatus = status ?? 'active';
|
|
215
|
+
const links = await listAgreements({
|
|
216
|
+
engagementKind: 'link',
|
|
217
|
+
...(resolvedStatus === 'all' ? {} : { status: resolvedStatus }),
|
|
218
|
+
}, creds);
|
|
219
|
+
// ZIG-670: link-shaped summaries, not raw agreement documents — the
|
|
220
|
+
// money block, approvals array, and Mongo internals are noise here.
|
|
221
|
+
const summaries = links.map((a) => ({
|
|
222
|
+
agreementId: a.agreementId,
|
|
223
|
+
status: a.status,
|
|
224
|
+
proposalStatus: a.proposalStatus,
|
|
225
|
+
parties: {
|
|
226
|
+
creatorAgent: a.parties?.creatorAgent ?? null,
|
|
227
|
+
providerAgent: a.parties?.providerAgent ?? null,
|
|
228
|
+
creator: a.parties?.creator ?? null,
|
|
229
|
+
proposedTo: a.parties?.proposedTo ?? null,
|
|
230
|
+
},
|
|
231
|
+
...(a.description ? { description: a.description } : {}),
|
|
232
|
+
createdAt: a.createdAt,
|
|
233
|
+
}));
|
|
203
234
|
const hasActive = links.some((a) => a.status === 'active');
|
|
204
235
|
return textResult({
|
|
205
|
-
count:
|
|
206
|
-
|
|
236
|
+
count: summaries.length,
|
|
237
|
+
status: resolvedStatus,
|
|
238
|
+
links: summaries,
|
|
207
239
|
...(hasActive
|
|
208
240
|
? {
|
|
209
241
|
nextSteps: 'A link is reach-only. Use ziggs_open_conversation (participantId = peer agent id) and/or ziggs_issue_grant before reading context.',
|