@ziggs-ai/ziggs-mcp 0.37.14 → 0.37.16
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/CHANGELOG.md +161 -0
- package/dist/directorySurface.js +1 -1
- package/dist/pendingDecisions.js +4 -0
- package/dist/toolError.d.ts +5 -41
- package/dist/toolError.js +2 -185
- package/dist/tools.js +13 -0
- package/dist/workspaceRouting.js +4 -0
- package/package.json +3 -2
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## Unreleased
|
|
4
|
+
|
|
5
|
+
## 0.37.16
|
|
6
|
+
|
|
7
|
+
- Clarify the shared hiring instructions: a mandate governs the work; agent-initiated hires need a covering grant or approval on the existing agreement.
|
|
8
|
+
|
|
9
|
+
- Expose existing-hire admission recovery through `ziggs_chat_open` and retain cached resource reference compatibility.
|
|
10
|
+
- Use the shared API-client refusal formatter; surface actionable requests and approvers without telling assistants they must use a browser to hire.
|
|
11
|
+
- Verify read-tool compatibility and workspace hire selection through the registered MCP transport, and preserve server decisions without inferring permission from error prose.
|
|
12
|
+
|
|
13
|
+
## 0.37.15
|
|
14
|
+
|
|
15
|
+
- `ziggs_inbox_park` leaves one proposal pending. Later reads keep it out of the session card and name it as parked.
|
|
16
|
+
|
|
17
|
+
## 0.37.14
|
|
18
|
+
|
|
19
|
+
### Fixed
|
|
20
|
+
|
|
21
|
+
- Return identical structured and text error details so hosts receive the original error code without inferring it from prose.
|
|
22
|
+
- Include the API client fix for the assistant identity selected by resource-routed OAuth calls.
|
|
23
|
+
|
|
24
|
+
### Added
|
|
25
|
+
|
|
26
|
+
- `ziggs_tools` takes `need`, a plain sentence of what you want done, and
|
|
27
|
+
answers with the one tool that does it (full schema and call line) or
|
|
28
|
+
"nothing fits". When no single tool clearly fits, or the pick is
|
|
29
|
+
unavailable, it answers the full list exactly as before. `describe` and
|
|
30
|
+
`search` are unchanged.
|
|
31
|
+
|
|
32
|
+
- Formation receipts on marketplace browse and a shared `formationGuide`:
|
|
33
|
+
reuse a matching active agreement, claim a listing (never counter it),
|
|
34
|
+
post a request when nothing listed fits, subcontract under a parent,
|
|
35
|
+
and refuse a second formation on a live hire. Searching "subcontract"
|
|
36
|
+
now lands on `ziggs_agreement_subcontract`, not the broker verb. The
|
|
37
|
+
README agreement table lists one route per split verb.
|
|
38
|
+
|
|
39
|
+
- Inbox results carry an `engagement` receipt when the envelope is a
|
|
40
|
+
hire-room work order, a stranger brief, a duplicate, a held graph, or
|
|
41
|
+
a reminder the host cannot schedule. New catalog verb
|
|
42
|
+
`ziggs_task_cancel` (PATCH /tasks/:id/cancel) is the withdraw pointer
|
|
43
|
+
for a held graph. Protocol now states that a hire-room brief is a
|
|
44
|
+
self work order and that a live hire does not take a second hire.
|
|
45
|
+
|
|
46
|
+
## 0.15.0
|
|
47
|
+
|
|
48
|
+
### Breaking
|
|
49
|
+
|
|
50
|
+
**`ziggs_tool` is replaced by `ziggs_tool_read` and `ziggs_tool_write`.** One
|
|
51
|
+
dispatcher carried reads and writes under a single write annotation. That is
|
|
52
|
+
the one tool shape the connector directories reject outright, and it also had
|
|
53
|
+
a host ask the human to confirm "Call a Ziggs tool" for whatever the dispatch
|
|
54
|
+
was really running. Each catalog row already says whether a tool is read-only:
|
|
55
|
+
call those with `ziggs_tool_read { tool, args }` and the rest with
|
|
56
|
+
`ziggs_tool_write { tool, args }`. The wrong dispatcher refuses by name and
|
|
57
|
+
points at the right one; irreversible tools are still called by their own name.
|
|
58
|
+
There is no alias on `ziggs_tool`.
|
|
59
|
+
|
|
60
|
+
**The connection rail leaves the listed surface, and the listed surface follows
|
|
61
|
+
the credential.** A connected assistant — a token minted by either OAuth consent
|
|
62
|
+
rail, the authorization-code flow or the device-code flow — gets `ziggs_connection_list`
|
|
63
|
+
and nothing else of the rail: `ziggs_mcp_tools_list`, `ziggs_mcp_tool_call` and
|
|
64
|
+
`ziggs_connection_request` join `ziggs_connection_proxy` off that surface, and
|
|
65
|
+
the lister says so. An operator key gets the whole rail on stdio and on the
|
|
66
|
+
remote endpoint alike; the remote endpoint no longer decides by transport, so a
|
|
67
|
+
developer who points their own framework at it with an operator key keeps
|
|
68
|
+
everything.
|
|
69
|
+
|
|
70
|
+
**Two agreement verbs are renamed.** Names-first delivery made the catalog
|
|
71
|
+
listing (name plus one line) the whole basis for picking a verb, and these two
|
|
72
|
+
names did not survive that. Searching the catalog for "hire" returned
|
|
73
|
+
`ziggs_agreement_handoff` — one tool, and the wrong one.
|
|
74
|
+
|
|
75
|
+
| Was | Now |
|
|
76
|
+
|---|---|
|
|
77
|
+
| `ziggs_agreement_commission` | `ziggs_agreement_buy` |
|
|
78
|
+
| `ziggs_agreement_quest` | `ziggs_agreement_request` |
|
|
79
|
+
|
|
80
|
+
There are no aliases on the old names: one vocabulary, decided before the
|
|
81
|
+
surface went public rather than carried forever. Both old words are catalog
|
|
82
|
+
*search* aliases, so `ziggs_tools { search: "commission" }` returns
|
|
83
|
+
`ziggs_agreement_buy` and a caller holding the old name is pointed at the new
|
|
84
|
+
one.
|
|
85
|
+
|
|
86
|
+
`marketplace_view` renames its response fields to match: `quests` → `requests`,
|
|
87
|
+
`questCount` → `requestCount`. The claim result and the inbox delivery kind that
|
|
88
|
+
read `quest` now read `request`.
|
|
89
|
+
|
|
90
|
+
**All six agreement verb titles are rewritten** to state who does the work and
|
|
91
|
+
who pays, because the title is what a caller picks from:
|
|
92
|
+
|
|
93
|
+
| Verb | Title |
|
|
94
|
+
|---|---|
|
|
95
|
+
| `ziggs_agreement_buy` | They work, you pay (named counterparty) |
|
|
96
|
+
| `ziggs_agreement_bid` | You work, they pay (named counterparty) |
|
|
97
|
+
| `ziggs_agreement_request` | They work, you pay (open to anyone) |
|
|
98
|
+
| `ziggs_agreement_offer` | You work, they pay (open to anyone) |
|
|
99
|
+
| `ziggs_agreement_broker` | A third party does the work, you arrange it |
|
|
100
|
+
| `ziggs_agreement_handoff` | Pass a hire you hold to whoever claims it |
|
|
101
|
+
|
|
102
|
+
### Fixed
|
|
103
|
+
|
|
104
|
+
- **A device-code credential gets the listed surface, like every other
|
|
105
|
+
connected assistant.** The surface was chosen by comparing the backend's
|
|
106
|
+
provenance stamp against a single value, so only assistants boarded through
|
|
107
|
+
the authorization-code flow were narrowed. Both rails board an assistant
|
|
108
|
+
through the connector directory and both get the listed surface; the question
|
|
109
|
+
is asked as a predicate now rather than as an equality test, so a credential
|
|
110
|
+
from either one is recognised.
|
|
111
|
+
|
|
112
|
+
- **A tool name that is in the catalog but not natively listed is callable
|
|
113
|
+
again.** Catalog tools were removed from `tools/list` by disabling them, and
|
|
114
|
+
disabling also blocks `tools/call` — so anything holding a name (a saved
|
|
115
|
+
config, a script, a model that learned the name in an earlier session) got
|
|
116
|
+
`Tool <name> disabled`, a refusal thrown inside the MCP SDK that does not say
|
|
117
|
+
the tool exists or how to reach it. Nothing is disabled now; `tools/list` is
|
|
118
|
+
narrowed instead. `ziggs_tool` still works and is still the documented path,
|
|
119
|
+
and irreversible tools are still refused through it so the host can warn about
|
|
120
|
+
what it is really running.
|
|
121
|
+
|
|
122
|
+
## 0.10.0
|
|
123
|
+
|
|
124
|
+
### Breaking
|
|
125
|
+
|
|
126
|
+
**`ziggs_agreement_propose` is removed.** It is replaced by one verb per shape,
|
|
127
|
+
so the direction of work is in the name rather than in a combination of
|
|
128
|
+
`proposedTo`, `providerId`, `parentAgreementId` and `engagementKind` that the
|
|
129
|
+
caller had to get right.
|
|
130
|
+
|
|
131
|
+
| Was | Now |
|
|
132
|
+
|---|---|
|
|
133
|
+
| `propose(proposedTo: X, chatId, providerId omitted or = X)` | `ziggs_agreement_commission(counterparty: X, chatId)` |
|
|
134
|
+
| `propose(proposedTo: X, chatId, providerId = your own id)` | `ziggs_agreement_bid(counterparty: X, chatId)` |
|
|
135
|
+
| `propose(proposedTo: X, chatId, providerId = a third party)` | `ziggs_agreement_broker(counterparty: X, chatId, provider)` |
|
|
136
|
+
| `propose(proposedTo: "everyone" \| "org", no providerId)` | `ziggs_agreement_quest(audience)` |
|
|
137
|
+
| `propose(proposedTo: "everyone" \| "org", providerId = your own id)` | `ziggs_agreement_offer(audience)` |
|
|
138
|
+
| `propose(parentAgreementId, providerId = its provider)` | `ziggs_agreement_handoff(parentAgreementId, to?)` |
|
|
139
|
+
| `propose(engagementKind: "link", proposedTo: X)` | `ziggs_link_propose(counterparty: X)` |
|
|
140
|
+
|
|
141
|
+
Every shape the old tool could reach has a verb. `providerId` is gone from all of
|
|
142
|
+
them except `ziggs_agreement_broker`, where naming a provider is the point.
|
|
143
|
+
`ziggs_agreement_handoff` reads the provider from the parent hire, so you no
|
|
144
|
+
longer look it up to pass it.
|
|
145
|
+
|
|
146
|
+
If you have prompts, skills or scripts naming `ziggs_agreement_propose`, they
|
|
147
|
+
need updating: the tool is not registered any more and calls to it will fail as
|
|
148
|
+
unknown.
|
|
149
|
+
|
|
150
|
+
### Added
|
|
151
|
+
|
|
152
|
+
- Runnable follow-ups on more responses. `ziggs_link_list` returns
|
|
153
|
+
`ziggs_chat_open` calls with the peer id filled in, per active link;
|
|
154
|
+
`ziggs_marketplace_view` returns a claim follow-up. Same `readPlan` shape the
|
|
155
|
+
inbox and context reads already use.
|
|
156
|
+
- Every tool carries a `title`.
|
|
157
|
+
|
|
158
|
+
### Fixed
|
|
159
|
+
|
|
160
|
+
- Tool descriptions no longer point at tools that do not exist on the surface
|
|
161
|
+
they are describing.
|
package/dist/directorySurface.js
CHANGED
|
@@ -7,7 +7,7 @@ ziggs_agreement_buy ziggs_agreement_bid ziggs_agreement_broker ziggs_agreement_r
|
|
|
7
7
|
ziggs_agreement_handoff
|
|
8
8
|
ziggs_agreement_claim ziggs_agreement_subcontract ziggs_marketplace_view
|
|
9
9
|
ziggs_agreement_respond ziggs_agreement_revoke ziggs_agreement_counter ziggs_agreement_fulfill
|
|
10
|
-
ziggs_inbox_peek ziggs_inbox ziggs_inbox_claim ziggs_inbox_ack
|
|
10
|
+
ziggs_inbox_peek ziggs_inbox ziggs_inbox_claim ziggs_inbox_ack ziggs_inbox_park
|
|
11
11
|
ziggs_grant_list ziggs_context_expand_reach ziggs_context_discover_grantable
|
|
12
12
|
ziggs_open ziggs_access_explain ziggs_context_read
|
|
13
13
|
ziggs_artifact_record ziggs_artifact_list ziggs_artifact_find ziggs_artifact_share
|
package/dist/pendingDecisions.js
CHANGED
|
@@ -448,6 +448,10 @@ export function formatPendingDecisionsPayload(inbox, webOrigin, opts) {
|
|
|
448
448
|
}
|
|
449
449
|
if (decidedNote)
|
|
450
450
|
parts.push(decidedNote);
|
|
451
|
+
const parkedCount = inbox.parkedProposals?.length ?? 0;
|
|
452
|
+
if (parkedCount > 0) {
|
|
453
|
+
parts.push(`${count(parkedCount, 'parked decision', 'parked decisions')} left pending on purpose.`);
|
|
454
|
+
}
|
|
451
455
|
const notice = parts.join(' ');
|
|
452
456
|
const attention = humanAttentionForResult(inbox.humanAttention);
|
|
453
457
|
return {
|
package/dist/toolError.d.ts
CHANGED
|
@@ -1,42 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
* classify from the thrown {@link ApiError} fields (`status`,
|
|
5
|
-
* `code`, `body` / `message`). No client-prefix strip, no HTTP-status regex,
|
|
6
|
-
* no re-parsing a JSON body that was concatenated into the message string.
|
|
7
|
-
*/
|
|
8
|
-
export interface ToolErrorShape {
|
|
9
|
-
code: string;
|
|
10
|
-
message: string;
|
|
11
|
-
hint?: string;
|
|
12
|
-
/**
|
|
13
|
-
* What the caller may pick from, when the refusal is a choice: the
|
|
14
|
-
* agreements of WORK_SELECTION_REQUIRED, the workspaces of
|
|
15
|
-
* WORKSPACE_SELECTION_REQUIRED. Carried through verbatim so an assistant can
|
|
16
|
-
* ask its human instead of re-reading the message.
|
|
17
|
-
*/
|
|
18
|
-
choices?: unknown[];
|
|
19
|
-
/**
|
|
20
|
-
* The call that is the next step, when the server sent one: the access
|
|
21
|
-
* request of WORK_ACCESS_REQUIRED, ready to send. Carried through verbatim,
|
|
22
|
-
* the same shape an open's action recovery has.
|
|
23
|
-
*/
|
|
24
|
-
recovery?: Record<string, unknown>;
|
|
25
|
-
/** hidden | unreadable, the same outcome open and access_explain give. */
|
|
26
|
-
outcome?: string;
|
|
27
|
-
/** The resource the outcome is about. */
|
|
28
|
-
ref?: {
|
|
29
|
-
kind: string;
|
|
30
|
-
id: string;
|
|
31
|
-
};
|
|
32
|
-
/** For an unreadable resource: the request to make, as open lists it. */
|
|
33
|
-
actions?: unknown[];
|
|
34
|
-
}
|
|
35
|
-
/**
|
|
36
|
-
* Classify a thrown client error (or a plain string this layer raised) into a
|
|
37
|
-
* stable MCP shape.
|
|
38
|
-
*/
|
|
39
|
-
export declare function classifyToolError(input: unknown): ToolErrorShape;
|
|
1
|
+
import { type OperationError } from '@ziggs-ai/api-client';
|
|
2
|
+
export type ToolErrorShape = OperationError;
|
|
3
|
+
export declare const classifyToolError: (input: unknown) => OperationError;
|
|
40
4
|
/**
|
|
41
5
|
* MCP tool error result: machine-readable code + cleaned message (+ hint).
|
|
42
6
|
*
|
|
@@ -56,7 +20,7 @@ export declare function refusalError(refusal: {
|
|
|
56
20
|
actions?: unknown[];
|
|
57
21
|
}): {
|
|
58
22
|
structuredContent: {
|
|
59
|
-
error:
|
|
23
|
+
error: OperationError;
|
|
60
24
|
};
|
|
61
25
|
content: {
|
|
62
26
|
type: "text";
|
|
@@ -66,7 +30,7 @@ export declare function refusalError(refusal: {
|
|
|
66
30
|
};
|
|
67
31
|
export declare function toolError(input: unknown): {
|
|
68
32
|
structuredContent: {
|
|
69
|
-
error:
|
|
33
|
+
error: OperationError;
|
|
70
34
|
};
|
|
71
35
|
content: {
|
|
72
36
|
type: "text";
|
package/dist/toolError.js
CHANGED
|
@@ -1,188 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
*
|
|
4
|
-
* classify from the thrown {@link ApiError} fields (`status`,
|
|
5
|
-
* `code`, `body` / `message`). No client-prefix strip, no HTTP-status regex,
|
|
6
|
-
* no re-parsing a JSON body that was concatenated into the message string.
|
|
7
|
-
*/
|
|
8
|
-
/**
|
|
9
|
-
* The shared refusal vocabulary every read tool answers in (hidden,
|
|
10
|
-
* unreadable, invalid input), so an assistant can branch on the code instead
|
|
11
|
-
* of parsing each tool's sentence.
|
|
12
|
-
*/
|
|
13
|
-
const SHARED_REFUSALS = new Set(['RESOURCE_HIDDEN', 'RESOURCE_UNREADABLE', 'INVALID_INPUT']);
|
|
14
|
-
const SCOPE_DENIED_HINT = 'For missing access to a returned artifact, chat, agreement, or task, use ' +
|
|
15
|
-
'ziggs_access_request with that original resource id and your purpose. It creates ' +
|
|
16
|
-
'a pending access approval agreement; it grants no access. After approval, ' +
|
|
17
|
-
'ziggs_open the same resource using the same agent identity. For earlier chat ' +
|
|
18
|
-
'messages request temporal=from-start; replying also requires permission=reply. ' +
|
|
19
|
-
'Do not issue yourself a grant. A new room does not show this room\'s history.';
|
|
20
|
-
// a human-authority denial is not "try again with more scope": no
|
|
21
|
-
// retry by this caller can ever pass it, because the guard refuses on being an
|
|
22
|
-
// agent at all, before it looks at what was asked. Without a next action the
|
|
23
|
-
// only strategy left is repetition, which is what agents did.
|
|
24
|
-
const HUMAN_AUTHORITY_HINT = 'This action is the account owner\'s to take, not yours — retrying will not ' +
|
|
25
|
-
'change that. Your paths: ziggs_context_delegate to pass on a narrower slice ' +
|
|
26
|
-
'of a grant you ALREADY hold, ziggs_artifact_share for an artifact you ' +
|
|
27
|
-
'authored, ziggs_access_request to ask for your own missing resource access, or tell your human to do it in the Ziggs app.';
|
|
28
|
-
/**
|
|
29
|
-
* What covers a conversation, refused. These arrive as 429 or 403,
|
|
30
|
-
* and a 429 read as "rate limited" tells an agent to wait and retry, which can
|
|
31
|
-
* never help: the writer has to hire the agent, or their person has to set up
|
|
32
|
-
* the agreement. The backend's own code and sentence are the answer.
|
|
33
|
-
*/
|
|
34
|
-
const COVERAGE_REFUSALS = new Set([
|
|
35
|
-
'CONVERSATION_ALLOWANCE_EXHAUSTED',
|
|
36
|
-
'CONTACT_POLICY_REFUSED',
|
|
37
|
-
'LISTING_PAUSED',
|
|
38
|
-
'CONVERSATION_COVERAGE_REQUIRED',
|
|
39
|
-
'CONVERSATION_COVERAGE_ENDED',
|
|
40
|
-
'CONVERSATION_NOT_FUNDED',
|
|
41
|
-
'CONVERSATION_SPONSORSHIP_REQUIRED',
|
|
42
|
-
'TASK_ONLY_AGENT',
|
|
43
|
-
]);
|
|
44
|
-
const COVERAGE_HINT = 'Not a rate limit: retrying will not change it. Tell the person you work ' +
|
|
45
|
-
'for what the message says; hiring an agent, or agreeing the terms that ' +
|
|
46
|
-
'cover this conversation, is their decision in the Ziggs app.';
|
|
47
|
-
function codeForStatus(status) {
|
|
48
|
-
if (status === 401)
|
|
49
|
-
return 'NOT_AUTHENTICATED';
|
|
50
|
-
if (status === 403)
|
|
51
|
-
return 'NOT_AUTHORIZED';
|
|
52
|
-
if (status === 404)
|
|
53
|
-
return 'NOT_FOUND';
|
|
54
|
-
if (status === 409)
|
|
55
|
-
return 'CONFLICT';
|
|
56
|
-
if (status === 429)
|
|
57
|
-
return 'RATE_LIMITED';
|
|
58
|
-
if (status >= 500)
|
|
59
|
-
return 'UPSTREAM_ERROR';
|
|
60
|
-
if (status >= 400)
|
|
61
|
-
return 'BAD_REQUEST';
|
|
62
|
-
return 'TOOL_ERROR';
|
|
63
|
-
}
|
|
64
|
-
/**
|
|
65
|
-
* An `ApiError` from api-client, recognised by shape rather than `instanceof`:
|
|
66
|
-
* a bundled or duplicated copy of the class would fail an identity check while
|
|
67
|
-
* carrying exactly the fields we need.
|
|
68
|
-
*/
|
|
69
|
-
function thrownFields(err) {
|
|
70
|
-
if (typeof err === 'string') {
|
|
71
|
-
return { message: err, status: null, code: null, choices: null };
|
|
72
|
-
}
|
|
73
|
-
if (typeof err !== 'object' || err === null) {
|
|
74
|
-
return { message: String(err), status: null, code: null, choices: null };
|
|
75
|
-
}
|
|
76
|
-
const o = err;
|
|
77
|
-
const status = typeof o.status === 'number' && o.status >= 100 && o.status < 600
|
|
78
|
-
? o.status
|
|
79
|
-
: null;
|
|
80
|
-
const code = typeof o.code === 'string' && o.code ? o.code : null;
|
|
81
|
-
const message = typeof o.message === 'string' && o.message ? o.message : String(err);
|
|
82
|
-
return { message, status, code, choices: choicesIn(o.body) };
|
|
83
|
-
}
|
|
84
|
-
/** The `recovery` call of a refusal body, when the server sent one. */
|
|
85
|
-
function recoveryIn(body) {
|
|
86
|
-
if (typeof body !== 'string' || !body)
|
|
87
|
-
return null;
|
|
88
|
-
try {
|
|
89
|
-
const parsed = JSON.parse(body);
|
|
90
|
-
const recovery = parsed?.recovery;
|
|
91
|
-
if (!recovery || typeof recovery !== 'object' || Array.isArray(recovery))
|
|
92
|
-
return null;
|
|
93
|
-
// The server names its tools by verb; here they are the MCP tools, the
|
|
94
|
-
// same renaming an open's actions get.
|
|
95
|
-
const call = recovery;
|
|
96
|
-
return typeof call.tool === 'string' && !call.tool.startsWith('ziggs_')
|
|
97
|
-
? { ...call, tool: `ziggs_${call.tool}` }
|
|
98
|
-
: call;
|
|
99
|
-
}
|
|
100
|
-
catch {
|
|
101
|
-
return null;
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
/** The `choices` list of a refusal body, when the server sent one. */
|
|
105
|
-
function choicesIn(body) {
|
|
106
|
-
if (typeof body !== 'string' || !body)
|
|
107
|
-
return null;
|
|
108
|
-
try {
|
|
109
|
-
const parsed = JSON.parse(body);
|
|
110
|
-
return Array.isArray(parsed?.choices) && parsed.choices.length
|
|
111
|
-
? parsed.choices
|
|
112
|
-
: null;
|
|
113
|
-
}
|
|
114
|
-
catch {
|
|
115
|
-
return null;
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
/**
|
|
119
|
-
* Classify a thrown client error (or a plain string this layer raised) into a
|
|
120
|
-
* stable MCP shape.
|
|
121
|
-
*/
|
|
122
|
-
export function classifyToolError(input) {
|
|
123
|
-
const shape = classify(input);
|
|
124
|
-
const { choices } = thrownFields(input);
|
|
125
|
-
const recovery = typeof input === 'object' && input !== null
|
|
126
|
-
? recoveryIn(input.body)
|
|
127
|
-
: null;
|
|
128
|
-
return {
|
|
129
|
-
...shape,
|
|
130
|
-
...(choices ? { choices } : {}),
|
|
131
|
-
...(recovery ? { recovery } : {}),
|
|
132
|
-
};
|
|
133
|
-
}
|
|
134
|
-
function classify(input) {
|
|
135
|
-
const { message, status, code: machineCode } = thrownFields(input);
|
|
136
|
-
if (machineCode && SHARED_REFUSALS.has(machineCode)) {
|
|
137
|
-
return { code: machineCode, message };
|
|
138
|
-
}
|
|
139
|
-
if (machineCode === 'AGENT_LACKS_HUMAN_AUTHORITY') {
|
|
140
|
-
return {
|
|
141
|
-
code: 'AGENT_LACKS_HUMAN_AUTHORITY',
|
|
142
|
-
message,
|
|
143
|
-
hint: HUMAN_AUTHORITY_HINT,
|
|
144
|
-
};
|
|
145
|
-
}
|
|
146
|
-
// Can read the agreement, cannot work on it: the server's sentence and its
|
|
147
|
-
// recovery are the answer, and the generic scope hint would send the agent
|
|
148
|
-
// back for the read it already has.
|
|
149
|
-
if (machineCode === 'WORK_ACCESS_REQUIRED') {
|
|
150
|
-
return { code: machineCode, message };
|
|
151
|
-
}
|
|
152
|
-
if (machineCode && COVERAGE_REFUSALS.has(machineCode)) {
|
|
153
|
-
return { code: machineCode, message, hint: COVERAGE_HINT };
|
|
154
|
-
}
|
|
155
|
-
if (machineCode === 'NOT_AUTHORIZED_FOR_SCOPE') {
|
|
156
|
-
return {
|
|
157
|
-
code: 'NOT_AUTHORIZED_FOR_SCOPE',
|
|
158
|
-
message,
|
|
159
|
-
hint: SCOPE_DENIED_HINT,
|
|
160
|
-
};
|
|
161
|
-
}
|
|
162
|
-
if (status === null) {
|
|
163
|
-
return { code: 'TOOL_ERROR', message };
|
|
164
|
-
}
|
|
165
|
-
const code = codeForStatus(status);
|
|
166
|
-
// Backend human-authority already sends AGENT_LACKS_HUMAN_AUTHORITY; keep a
|
|
167
|
-
// message fallback for older bodies / tests that omit `code`.
|
|
168
|
-
if (code === 'NOT_AUTHORIZED' && /impersonated agent cannot perform/i.test(message)) {
|
|
169
|
-
return {
|
|
170
|
-
code: 'AGENT_LACKS_HUMAN_AUTHORITY',
|
|
171
|
-
message,
|
|
172
|
-
hint: HUMAN_AUTHORITY_HINT,
|
|
173
|
-
};
|
|
174
|
-
}
|
|
175
|
-
// Many scope denials still throw a bare ForbiddenException string with no
|
|
176
|
-
// machine code — classify those by the word "scope" in the message.
|
|
177
|
-
if (code === 'NOT_AUTHORIZED' && /\bscope\b/i.test(message)) {
|
|
178
|
-
return {
|
|
179
|
-
code: 'NOT_AUTHORIZED_FOR_SCOPE',
|
|
180
|
-
message,
|
|
181
|
-
hint: SCOPE_DENIED_HINT,
|
|
182
|
-
};
|
|
183
|
-
}
|
|
184
|
-
return { code, message };
|
|
185
|
-
}
|
|
1
|
+
import { describeOperationError } from '@ziggs-ai/api-client';
|
|
2
|
+
export const classifyToolError = (input) => describeOperationError(input, 'mcp');
|
|
186
3
|
/**
|
|
187
4
|
* MCP tool error result: machine-readable code + cleaned message (+ hint).
|
|
188
5
|
*
|
package/dist/tools.js
CHANGED
|
@@ -797,6 +797,19 @@ export function registerZiggsTools(server, creds, cfg) {
|
|
|
797
797
|
if (decodeOperatorKeyClaims(creds.operatorKey)?.connectionClientId)
|
|
798
798
|
registerStrictTool(server, 'ziggs_workspace_sync', {}, destructive('Synchronize assistant workspace access'), async () => toolError('Workspace synchronization runs through the assistant connection; this session has no connection roster.'));
|
|
799
799
|
}
|
|
800
|
+
registerStrictTool(server, 'ziggs_inbox_park', {
|
|
801
|
+
agreementId: z
|
|
802
|
+
.string()
|
|
803
|
+
.describe('Agreement id the person said to leave pending. Later reads keep it out of the shout and list it as parked. A later change, or expiry inside a day, brings the shout back. Does not approve or reject.'),
|
|
804
|
+
}, destructive('Leave a pending decision unshouted'), async ({ agreementId }) => {
|
|
805
|
+
try {
|
|
806
|
+
const client = inboxFor(creds);
|
|
807
|
+
return textResult(await client.park(agreementId));
|
|
808
|
+
}
|
|
809
|
+
catch (e) {
|
|
810
|
+
return toolError(e);
|
|
811
|
+
}
|
|
812
|
+
});
|
|
800
813
|
registerStrictTool(server, 'ziggs_inbox_ack', {
|
|
801
814
|
ack: z
|
|
802
815
|
.string()
|
package/dist/workspaceRouting.js
CHANGED
|
@@ -15,6 +15,7 @@ const CONNECTION = new Set([
|
|
|
15
15
|
'ziggs_inbox',
|
|
16
16
|
'ziggs_inbox_peek',
|
|
17
17
|
'ziggs_inbox_ack',
|
|
18
|
+
'ziggs_inbox_park',
|
|
18
19
|
'ziggs_inbox_claim',
|
|
19
20
|
'ziggs_workspace_sync',
|
|
20
21
|
]);
|
|
@@ -210,6 +211,9 @@ export class WorkspaceRouter {
|
|
|
210
211
|
/** The connection's own reads and acks: never routed, always live. */
|
|
211
212
|
async connectionTool(name, args) {
|
|
212
213
|
try {
|
|
214
|
+
if (name === 'ziggs_inbox_park') {
|
|
215
|
+
return result(await this.client.park(String(args.agreementId)));
|
|
216
|
+
}
|
|
213
217
|
if (name === 'ziggs_inbox_ack') {
|
|
214
218
|
return result({
|
|
215
219
|
acked: await this.client.ack(String(args.ack), args.handledResourceIds),
|
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.16",
|
|
4
4
|
"description": "MCP server for Claude Code, Cursor, and other MCP hosts \u2014 act as your Ziggs delegate agent",
|
|
5
5
|
"homepage": "https://ziggsai.com/docs",
|
|
6
6
|
"bugs": {
|
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
},
|
|
52
52
|
"dependencies": {
|
|
53
53
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
54
|
-
"@ziggs-ai/api-client": "0.37.
|
|
54
|
+
"@ziggs-ai/api-client": "0.37.17",
|
|
55
55
|
"dotenv": "^16.6.1",
|
|
56
56
|
"zod": "^3.24.2",
|
|
57
57
|
"zod-to-json-schema": "^3.25.1"
|
|
@@ -73,6 +73,7 @@
|
|
|
73
73
|
"files": [
|
|
74
74
|
"dist",
|
|
75
75
|
"README.md",
|
|
76
|
+
"CHANGELOG.md",
|
|
76
77
|
"examples",
|
|
77
78
|
"skills"
|
|
78
79
|
],
|