@guuey/mcp-apps-host 0.21.0 → 0.22.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/action.d.ts +36 -0
- package/dist/action.d.ts.map +1 -1
- package/dist/action.js +69 -4
- package/package.json +2 -2
- package/src/action.ts +82 -3
package/dist/action.d.ts
CHANGED
|
@@ -101,6 +101,15 @@ export interface CreateMcpUiActionRelayDeps {
|
|
|
101
101
|
* treated as unavailable.
|
|
102
102
|
*/
|
|
103
103
|
callTool: (uri: string, name: string, args: McpToolStructuredContent | undefined) => Promise<unknown>;
|
|
104
|
+
/**
|
|
105
|
+
* Fired ONCE when a card locator's `ggui_runtime_pull` circuit OPENS
|
|
106
|
+
* (guuey#1249 item 4) — the live session is unrestorable (N consecutive
|
|
107
|
+
* both-doors-gone pulls). The complement to leg 1's storm-break: the host
|
|
108
|
+
* surfaces a visible "this session ended — start a new chat" state and drops
|
|
109
|
+
* the stale thread, so a tripped circuit is never a silent frozen card.
|
|
110
|
+
* Optional: a host that only wants the storm bounded omits it.
|
|
111
|
+
*/
|
|
112
|
+
onSessionUnrestorable?: (resourceUri: string) => void;
|
|
104
113
|
}
|
|
105
114
|
/** The request shape a mounted card's `onCallTool` bridge produces. */
|
|
106
115
|
export interface UiActionRequest {
|
|
@@ -109,10 +118,37 @@ export interface UiActionRequest {
|
|
|
109
118
|
name: string;
|
|
110
119
|
arguments?: McpToolStructuredContent;
|
|
111
120
|
}
|
|
121
|
+
/**
|
|
122
|
+
* Consecutive `unavailable` pull results (per card locator) that OPEN the
|
|
123
|
+
* circuit (guuey#1235 leg 1). At the #1233 storm's ~12-18 pulls/min a 3-strike
|
|
124
|
+
* trip bounds the hammer in ~10-15s; a single good pull resets it, so a
|
|
125
|
+
* transient blip never trips.
|
|
126
|
+
*/
|
|
127
|
+
export declare const PULL_CIRCUIT_THRESHOLD = 3;
|
|
128
|
+
/**
|
|
129
|
+
* Logged ONCE when a locator's pull circuit opens — the bounded-storm marker
|
|
130
|
+
* (sentry's readability ask; the #1233 next-incident is scopeable from it).
|
|
131
|
+
*/
|
|
132
|
+
export declare const UI_ACTION_PULL_CIRCUIT_OPEN = "UI_ACTION_PULL_CIRCUIT_OPEN";
|
|
112
133
|
/**
|
|
113
134
|
* Assemble the sandbox-facing action relay from a host transport. The
|
|
114
135
|
* returned function is shaped for an `onCallTool` bridge: it always
|
|
115
136
|
* resolves (never rejects), answering in-band.
|
|
137
|
+
*
|
|
138
|
+
* guuey#1235 leg 1 — the `ggui_runtime_pull` CIRCUIT BREAK. A card whose live
|
|
139
|
+
* session died keeps auto-polling on its own interval; each pull `tools/call`
|
|
140
|
+
* 404s at the pod door → `unavailable` → the sandbox polls again, hammering the
|
|
141
|
+
* door indefinitely (the #1233 prod storm: 57+ over 15 min). After
|
|
142
|
+
* {@link PULL_CIRCUIT_THRESHOLD} CONSECUTIVE `unavailable` pull results for a
|
|
143
|
+
* locator, the circuit OPENS: the relay stops calling the transport for that
|
|
144
|
+
* locator's pull (fail-fast, no network) — the server storm is bounded. A
|
|
145
|
+
* single non-`unavailable` pull result CLOSES it (the session recovered). Only
|
|
146
|
+
* the pull rung is counted — a failed user gesture (`submit_action`) or token
|
|
147
|
+
* refresh must never trip the auto-poll break, and never opens another rung.
|
|
148
|
+
*
|
|
149
|
+
* The complement — telling the USER the session is unrestorable so a tripped
|
|
150
|
+
* circuit is not a silent freeze — is the `onSessionUnrestorable` signal
|
|
151
|
+
* (guuey#1249 item 4); this leg only bounds the hammer.
|
|
116
152
|
*/
|
|
117
153
|
export declare function createMcpUiActionRelay(deps: CreateMcpUiActionRelayDeps): (request: UiActionRequest) => Promise<McpToolCallResult>;
|
|
118
154
|
//# sourceMappingURL=action.d.ts.map
|
package/dist/action.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"action.d.ts","sourceRoot":"","sources":["../src/action.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,oFAAoF;AACpF,MAAM,MAAM,kBAAkB,GAC1B;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAC9B;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GACjD;IACE,IAAI,EAAE,UAAU,CAAC;IACjB,QAAQ,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,CAC3C;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,GAChB;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,CACnB,CAAC;CACH,CAAC;AAEN;;;;GAIG;AACH,MAAM,MAAM,wBAAwB,GAAG;IACrC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB,CAAC;AAEF;;;;;GAKG;AACH,MAAM,MAAM,iBAAiB,GAAG;IAC9B,OAAO,EAAE,kBAAkB,EAAE,CAAC;IAC9B,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,iBAAiB,CAAC,EAAE,wBAAwB,CAAC;CAC9C,CAAC;AAEF;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,eAAe,EAAE,WAAW,CAAC,MAAM,CAI9C,CAAC;AAEH;;;;;;;;GAQG;AACH,eAAO,MAAM,wBAAwB,EAAE,WAAW,CAAC,MAAM,CAEvD,CAAC;AAEH,yEAAyE;AACzE,eAAO,MAAM,0BAA0B,2CACG,CAAC;AAE3C;;;;GAIG;AACH,wBAAgB,yBAAyB,IAAI,iBAAiB,CAK7D;AAkCD;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,GAAG,iBAAiB,GAAG,SAAS,CAgB9E;AAED,iFAAiF;AACjF,MAAM,WAAW,0BAA0B;IACzC;;;;;OAKG;IACH,QAAQ,EAAE,CACR,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,wBAAwB,GAAG,SAAS,KACvC,OAAO,CAAC,OAAO,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"action.d.ts","sourceRoot":"","sources":["../src/action.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,oFAAoF;AACpF,MAAM,MAAM,kBAAkB,GAC1B;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAC9B;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GACjD;IACE,IAAI,EAAE,UAAU,CAAC;IACjB,QAAQ,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,CAC3C;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,GAChB;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,CACnB,CAAC;CACH,CAAC;AAEN;;;;GAIG;AACH,MAAM,MAAM,wBAAwB,GAAG;IACrC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB,CAAC;AAEF;;;;;GAKG;AACH,MAAM,MAAM,iBAAiB,GAAG;IAC9B,OAAO,EAAE,kBAAkB,EAAE,CAAC;IAC9B,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,iBAAiB,CAAC,EAAE,wBAAwB,CAAC;CAC9C,CAAC;AAEF;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,eAAe,EAAE,WAAW,CAAC,MAAM,CAI9C,CAAC;AAEH;;;;;;;;GAQG;AACH,eAAO,MAAM,wBAAwB,EAAE,WAAW,CAAC,MAAM,CAEvD,CAAC;AAEH,yEAAyE;AACzE,eAAO,MAAM,0BAA0B,2CACG,CAAC;AAE3C;;;;GAIG;AACH,wBAAgB,yBAAyB,IAAI,iBAAiB,CAK7D;AAkCD;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,GAAG,iBAAiB,GAAG,SAAS,CAgB9E;AAED,iFAAiF;AACjF,MAAM,WAAW,0BAA0B;IACzC;;;;;OAKG;IACH,QAAQ,EAAE,CACR,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,wBAAwB,GAAG,SAAS,KACvC,OAAO,CAAC,OAAO,CAAC,CAAC;IACtB;;;;;;;OAOG;IACH,qBAAqB,CAAC,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,IAAI,CAAC;CACvD;AAED,uEAAuE;AACvE,MAAM,WAAW,eAAe;IAC9B,yEAAyE;IACzE,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE,wBAAwB,CAAC;CACtC;AAKD;;;;;GAKG;AACH,eAAO,MAAM,sBAAsB,IAAI,CAAC;AAExC;;;GAGG;AACH,eAAO,MAAM,2BAA2B,gCAAgC,CAAC;AAEzE;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,sBAAsB,CACpC,IAAI,EAAE,0BAA0B,GAC/B,CAAC,OAAO,EAAE,eAAe,KAAK,OAAO,CAAC,iBAAiB,CAAC,CAmD1D"}
|
package/dist/action.js
CHANGED
|
@@ -113,26 +113,91 @@ export function asToolCallResult(value) {
|
|
|
113
113
|
: {}),
|
|
114
114
|
};
|
|
115
115
|
}
|
|
116
|
+
/** The host-relayed auto-poll rung (guuey#1235). A dead session's pull is the storm. */
|
|
117
|
+
const PULL_TOOL = "ggui_runtime_pull";
|
|
118
|
+
/**
|
|
119
|
+
* Consecutive `unavailable` pull results (per card locator) that OPEN the
|
|
120
|
+
* circuit (guuey#1235 leg 1). At the #1233 storm's ~12-18 pulls/min a 3-strike
|
|
121
|
+
* trip bounds the hammer in ~10-15s; a single good pull resets it, so a
|
|
122
|
+
* transient blip never trips.
|
|
123
|
+
*/
|
|
124
|
+
export const PULL_CIRCUIT_THRESHOLD = 3;
|
|
125
|
+
/**
|
|
126
|
+
* Logged ONCE when a locator's pull circuit opens — the bounded-storm marker
|
|
127
|
+
* (sentry's readability ask; the #1233 next-incident is scopeable from it).
|
|
128
|
+
*/
|
|
129
|
+
export const UI_ACTION_PULL_CIRCUIT_OPEN = "UI_ACTION_PULL_CIRCUIT_OPEN";
|
|
116
130
|
/**
|
|
117
131
|
* Assemble the sandbox-facing action relay from a host transport. The
|
|
118
132
|
* returned function is shaped for an `onCallTool` bridge: it always
|
|
119
133
|
* resolves (never rejects), answering in-band.
|
|
134
|
+
*
|
|
135
|
+
* guuey#1235 leg 1 — the `ggui_runtime_pull` CIRCUIT BREAK. A card whose live
|
|
136
|
+
* session died keeps auto-polling on its own interval; each pull `tools/call`
|
|
137
|
+
* 404s at the pod door → `unavailable` → the sandbox polls again, hammering the
|
|
138
|
+
* door indefinitely (the #1233 prod storm: 57+ over 15 min). After
|
|
139
|
+
* {@link PULL_CIRCUIT_THRESHOLD} CONSECUTIVE `unavailable` pull results for a
|
|
140
|
+
* locator, the circuit OPENS: the relay stops calling the transport for that
|
|
141
|
+
* locator's pull (fail-fast, no network) — the server storm is bounded. A
|
|
142
|
+
* single non-`unavailable` pull result CLOSES it (the session recovered). Only
|
|
143
|
+
* the pull rung is counted — a failed user gesture (`submit_action`) or token
|
|
144
|
+
* refresh must never trip the auto-poll break, and never opens another rung.
|
|
145
|
+
*
|
|
146
|
+
* The complement — telling the USER the session is unrestorable so a tripped
|
|
147
|
+
* circuit is not a silent freeze — is the `onSessionUnrestorable` signal
|
|
148
|
+
* (guuey#1249 item 4); this leg only bounds the hammer.
|
|
120
149
|
*/
|
|
121
150
|
export function createMcpUiActionRelay(deps) {
|
|
151
|
+
// Per-relay-instance (one card mount). A recovered/absent locator is deleted,
|
|
152
|
+
// so this stays as small as the mounted cards; the mount tears it down.
|
|
153
|
+
const pullFailures = new Map();
|
|
154
|
+
const recordPull = (uri, unavailable) => {
|
|
155
|
+
if (!unavailable) {
|
|
156
|
+
pullFailures.delete(uri); // the session answered → close the circuit
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
const next = (pullFailures.get(uri) ?? 0) + 1;
|
|
160
|
+
pullFailures.set(uri, next);
|
|
161
|
+
if (next === PULL_CIRCUIT_THRESHOLD) {
|
|
162
|
+
// Once, at the trip — not per subsequent short-circuited poll.
|
|
163
|
+
console.warn(UI_ACTION_PULL_CIRCUIT_OPEN, {
|
|
164
|
+
resourceUri: uri,
|
|
165
|
+
consecutiveUnavailable: next,
|
|
166
|
+
});
|
|
167
|
+
// guuey#1249 item 4: tell the host the session is unrestorable so the
|
|
168
|
+
// bounded circuit isn't a silent freeze. A throw from the host callback
|
|
169
|
+
// must never break the relay's never-reject contract.
|
|
170
|
+
try {
|
|
171
|
+
deps.onSessionUnrestorable?.(uri);
|
|
172
|
+
}
|
|
173
|
+
catch {
|
|
174
|
+
// A host-supplied callback that throws is the host's bug, not the
|
|
175
|
+
// relay's — the storm is already bounded either way.
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
};
|
|
122
179
|
return async (request) => {
|
|
123
180
|
if (!UI_ACTION_TOOLS.has(request.name))
|
|
124
181
|
return unavailableToolCallResult();
|
|
125
182
|
if (!request.resourceUri.startsWith("ui://"))
|
|
126
183
|
return unavailableToolCallResult();
|
|
184
|
+
const isPull = request.name === PULL_TOOL;
|
|
185
|
+
// Circuit OPEN for this locator's pull → fail-fast, never touch the door.
|
|
186
|
+
if (isPull && (pullFailures.get(request.resourceUri) ?? 0) >= PULL_CIRCUIT_THRESHOLD) {
|
|
187
|
+
return unavailableToolCallResult();
|
|
188
|
+
}
|
|
127
189
|
let raw;
|
|
128
190
|
try {
|
|
129
191
|
raw = await deps.callTool(request.resourceUri, request.name, request.arguments);
|
|
130
192
|
}
|
|
131
193
|
catch {
|
|
132
|
-
|
|
194
|
+
if (isPull)
|
|
195
|
+
recordPull(request.resourceUri, true); // transport failure == unavailable
|
|
196
|
+
return unavailableToolCallResult(); // in-band
|
|
133
197
|
}
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
198
|
+
const result = raw === undefined ? undefined : asToolCallResult(raw);
|
|
199
|
+
if (isPull)
|
|
200
|
+
recordPull(request.resourceUri, result === undefined);
|
|
201
|
+
return result ?? unavailableToolCallResult();
|
|
137
202
|
};
|
|
138
203
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@guuey/mcp-apps-host",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.22.1",
|
|
4
4
|
"description": "The MCP Apps (SEP-1865) Host role for guuey's chat surfaces — view-mount narrowing across UI channels, ui:// locator rehydration by resources/read, and the sandbox-trust channel contract. Vendor-neutral: any spec-following MCP App mounts through it.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
}
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
|
-
"@ggui-ai/protocol": "0.
|
|
36
|
+
"@ggui-ai/protocol": "0.16.0",
|
|
37
37
|
"@modelcontextprotocol/ext-apps": "^1.7.5",
|
|
38
38
|
"@silverprotocol/core": "0.6.1"
|
|
39
39
|
},
|
package/src/action.ts
CHANGED
|
@@ -166,6 +166,15 @@ export interface CreateMcpUiActionRelayDeps {
|
|
|
166
166
|
name: string,
|
|
167
167
|
args: McpToolStructuredContent | undefined,
|
|
168
168
|
) => Promise<unknown>;
|
|
169
|
+
/**
|
|
170
|
+
* Fired ONCE when a card locator's `ggui_runtime_pull` circuit OPENS
|
|
171
|
+
* (guuey#1249 item 4) — the live session is unrestorable (N consecutive
|
|
172
|
+
* both-doors-gone pulls). The complement to leg 1's storm-break: the host
|
|
173
|
+
* surfaces a visible "this session ended — start a new chat" state and drops
|
|
174
|
+
* the stale thread, so a tripped circuit is never a silent frozen card.
|
|
175
|
+
* Optional: a host that only wants the storm bounded omits it.
|
|
176
|
+
*/
|
|
177
|
+
onSessionUnrestorable?: (resourceUri: string) => void;
|
|
169
178
|
}
|
|
170
179
|
|
|
171
180
|
/** The request shape a mounted card's `onCallTool` bridge produces. */
|
|
@@ -176,24 +185,94 @@ export interface UiActionRequest {
|
|
|
176
185
|
arguments?: McpToolStructuredContent;
|
|
177
186
|
}
|
|
178
187
|
|
|
188
|
+
/** The host-relayed auto-poll rung (guuey#1235). A dead session's pull is the storm. */
|
|
189
|
+
const PULL_TOOL = "ggui_runtime_pull";
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Consecutive `unavailable` pull results (per card locator) that OPEN the
|
|
193
|
+
* circuit (guuey#1235 leg 1). At the #1233 storm's ~12-18 pulls/min a 3-strike
|
|
194
|
+
* trip bounds the hammer in ~10-15s; a single good pull resets it, so a
|
|
195
|
+
* transient blip never trips.
|
|
196
|
+
*/
|
|
197
|
+
export const PULL_CIRCUIT_THRESHOLD = 3;
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Logged ONCE when a locator's pull circuit opens — the bounded-storm marker
|
|
201
|
+
* (sentry's readability ask; the #1233 next-incident is scopeable from it).
|
|
202
|
+
*/
|
|
203
|
+
export const UI_ACTION_PULL_CIRCUIT_OPEN = "UI_ACTION_PULL_CIRCUIT_OPEN";
|
|
204
|
+
|
|
179
205
|
/**
|
|
180
206
|
* Assemble the sandbox-facing action relay from a host transport. The
|
|
181
207
|
* returned function is shaped for an `onCallTool` bridge: it always
|
|
182
208
|
* resolves (never rejects), answering in-band.
|
|
209
|
+
*
|
|
210
|
+
* guuey#1235 leg 1 — the `ggui_runtime_pull` CIRCUIT BREAK. A card whose live
|
|
211
|
+
* session died keeps auto-polling on its own interval; each pull `tools/call`
|
|
212
|
+
* 404s at the pod door → `unavailable` → the sandbox polls again, hammering the
|
|
213
|
+
* door indefinitely (the #1233 prod storm: 57+ over 15 min). After
|
|
214
|
+
* {@link PULL_CIRCUIT_THRESHOLD} CONSECUTIVE `unavailable` pull results for a
|
|
215
|
+
* locator, the circuit OPENS: the relay stops calling the transport for that
|
|
216
|
+
* locator's pull (fail-fast, no network) — the server storm is bounded. A
|
|
217
|
+
* single non-`unavailable` pull result CLOSES it (the session recovered). Only
|
|
218
|
+
* the pull rung is counted — a failed user gesture (`submit_action`) or token
|
|
219
|
+
* refresh must never trip the auto-poll break, and never opens another rung.
|
|
220
|
+
*
|
|
221
|
+
* The complement — telling the USER the session is unrestorable so a tripped
|
|
222
|
+
* circuit is not a silent freeze — is the `onSessionUnrestorable` signal
|
|
223
|
+
* (guuey#1249 item 4); this leg only bounds the hammer.
|
|
183
224
|
*/
|
|
184
225
|
export function createMcpUiActionRelay(
|
|
185
226
|
deps: CreateMcpUiActionRelayDeps,
|
|
186
227
|
): (request: UiActionRequest) => Promise<McpToolCallResult> {
|
|
228
|
+
// Per-relay-instance (one card mount). A recovered/absent locator is deleted,
|
|
229
|
+
// so this stays as small as the mounted cards; the mount tears it down.
|
|
230
|
+
const pullFailures = new Map<string, number>();
|
|
231
|
+
|
|
232
|
+
const recordPull = (uri: string, unavailable: boolean): void => {
|
|
233
|
+
if (!unavailable) {
|
|
234
|
+
pullFailures.delete(uri); // the session answered → close the circuit
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
const next = (pullFailures.get(uri) ?? 0) + 1;
|
|
238
|
+
pullFailures.set(uri, next);
|
|
239
|
+
if (next === PULL_CIRCUIT_THRESHOLD) {
|
|
240
|
+
// Once, at the trip — not per subsequent short-circuited poll.
|
|
241
|
+
console.warn(UI_ACTION_PULL_CIRCUIT_OPEN, {
|
|
242
|
+
resourceUri: uri,
|
|
243
|
+
consecutiveUnavailable: next,
|
|
244
|
+
});
|
|
245
|
+
// guuey#1249 item 4: tell the host the session is unrestorable so the
|
|
246
|
+
// bounded circuit isn't a silent freeze. A throw from the host callback
|
|
247
|
+
// must never break the relay's never-reject contract.
|
|
248
|
+
try {
|
|
249
|
+
deps.onSessionUnrestorable?.(uri);
|
|
250
|
+
} catch {
|
|
251
|
+
// A host-supplied callback that throws is the host's bug, not the
|
|
252
|
+
// relay's — the storm is already bounded either way.
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
};
|
|
256
|
+
|
|
187
257
|
return async (request) => {
|
|
188
258
|
if (!UI_ACTION_TOOLS.has(request.name)) return unavailableToolCallResult();
|
|
189
259
|
if (!request.resourceUri.startsWith("ui://")) return unavailableToolCallResult();
|
|
260
|
+
|
|
261
|
+
const isPull = request.name === PULL_TOOL;
|
|
262
|
+
// Circuit OPEN for this locator's pull → fail-fast, never touch the door.
|
|
263
|
+
if (isPull && (pullFailures.get(request.resourceUri) ?? 0) >= PULL_CIRCUIT_THRESHOLD) {
|
|
264
|
+
return unavailableToolCallResult();
|
|
265
|
+
}
|
|
266
|
+
|
|
190
267
|
let raw: unknown;
|
|
191
268
|
try {
|
|
192
269
|
raw = await deps.callTool(request.resourceUri, request.name, request.arguments);
|
|
193
270
|
} catch {
|
|
194
|
-
|
|
271
|
+
if (isPull) recordPull(request.resourceUri, true); // transport failure == unavailable
|
|
272
|
+
return unavailableToolCallResult(); // in-band
|
|
195
273
|
}
|
|
196
|
-
|
|
197
|
-
|
|
274
|
+
const result = raw === undefined ? undefined : asToolCallResult(raw);
|
|
275
|
+
if (isPull) recordPull(request.resourceUri, result === undefined);
|
|
276
|
+
return result ?? unavailableToolCallResult();
|
|
198
277
|
};
|
|
199
278
|
}
|