@ziggs-ai/ziggs-mcp 0.1.25 → 0.1.26
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/pendingDecisions.d.ts +14 -0
- package/dist/pendingDecisions.js +26 -0
- package/dist/toolError.d.ts +25 -0
- package/dist/toolError.js +81 -0
- package/dist/tools.js +29 -13
- 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
|
|
|
@@ -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;
|
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')
|
|
@@ -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,6 +246,20 @@ 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);
|
|
@@ -256,7 +267,7 @@ async function loadSessionActionsPayload(creds, cfg) {
|
|
|
256
267
|
let activeTasks = [];
|
|
257
268
|
try {
|
|
258
269
|
const listed = await listTasks({ state: 'active', limit: 20 }, creds);
|
|
259
|
-
activeTasks = listed.tasks ?? [];
|
|
270
|
+
activeTasks = filterTasksForDelegate(listed.tasks ?? [], delegateSelfIds(creds, cfg));
|
|
260
271
|
}
|
|
261
272
|
catch {
|
|
262
273
|
// Inbox is still useful when task listing fails.
|
|
@@ -477,7 +488,7 @@ export function registerZiggsTools(server, creds, cfg) {
|
|
|
477
488
|
return toolError(e.message);
|
|
478
489
|
}
|
|
479
490
|
});
|
|
480
|
-
server.tool('ziggs_propose_agreement', 'Propose a direct
|
|
491
|
+
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
492
|
proposedTo: z.string(),
|
|
482
493
|
chatId: z.string(),
|
|
483
494
|
description: z.string(),
|
|
@@ -486,7 +497,11 @@ export function registerZiggsTools(server, creds, cfg) {
|
|
|
486
497
|
.optional()
|
|
487
498
|
.describe('Human user id = payer (your userId)'),
|
|
488
499
|
price: z.number().optional().describe('Optional; does not trigger transfer by itself'),
|
|
489
|
-
|
|
500
|
+
engagementKind: z
|
|
501
|
+
.enum(['hire', 'service'])
|
|
502
|
+
.optional()
|
|
503
|
+
.describe("'service' (default) = one-off deliverable; 'hire' = ongoing engagement"),
|
|
504
|
+
}, WRITE, async ({ proposedTo, chatId, description, payerId, price, engagementKind }) => {
|
|
490
505
|
try {
|
|
491
506
|
const resolvedPayer = payerId ?? cfg.ZIGGS_OWNER_USER_ID;
|
|
492
507
|
if (!resolvedPayer) {
|
|
@@ -498,7 +513,7 @@ export function registerZiggsTools(server, creds, cfg) {
|
|
|
498
513
|
description,
|
|
499
514
|
payerId: resolvedPayer,
|
|
500
515
|
price,
|
|
501
|
-
engagementKind: 'service',
|
|
516
|
+
engagementKind: engagementKind ?? 'service',
|
|
502
517
|
}, creds);
|
|
503
518
|
return textResult({ agreement });
|
|
504
519
|
}
|
|
@@ -606,7 +621,7 @@ export function registerZiggsTools(server, creds, cfg) {
|
|
|
606
621
|
let activeTasks = [];
|
|
607
622
|
try {
|
|
608
623
|
const listed = await listTasks({ state: 'active', limit: 20 }, creds);
|
|
609
|
-
activeTasks = listed.tasks ?? [];
|
|
624
|
+
activeTasks = filterTasksForDelegate(listed.tasks ?? [], delegateSelfIds(creds, cfg));
|
|
610
625
|
}
|
|
611
626
|
catch {
|
|
612
627
|
// omit work card when tasks fail
|
|
@@ -693,7 +708,7 @@ export function registerZiggsTools(server, creds, cfg) {
|
|
|
693
708
|
if ((chatId && agreementId) || (!chatId && !agreementId)) {
|
|
694
709
|
return toolError('Pass exactly one of chatId or agreementId');
|
|
695
710
|
}
|
|
696
|
-
await writeArtifactStrict(creds, {
|
|
711
|
+
const { artifactId } = await writeArtifactStrict(creds, {
|
|
697
712
|
text,
|
|
698
713
|
visibility,
|
|
699
714
|
chatId,
|
|
@@ -703,6 +718,7 @@ export function registerZiggsTools(server, creds, cfg) {
|
|
|
703
718
|
});
|
|
704
719
|
return textResult({
|
|
705
720
|
ok: true,
|
|
721
|
+
artifactId,
|
|
706
722
|
visibility,
|
|
707
723
|
chatId,
|
|
708
724
|
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.',
|