@nexface/tools 0.1.13-alpha.3 → 0.1.13-alpha.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/browser/actions.js +34 -9
- package/dist/browser/controller.js +53 -73
- package/dist/browser/diagnostics.js +8 -2
- package/dist/browser/dom.js +12 -27
- package/dist/browser/feedback.js +6 -4
- package/dist/browser/locator.js +3 -8
- package/dist/browser/snapshot.js +10 -21
- package/dist/browser/toolset.js +42 -35
- package/dist/browser/types.d.ts +1 -1
- package/dist/definition.d.ts +1 -3
- package/dist/definition.js +2 -1
- package/dist/internal-react.d.ts +1 -1
- package/dist/internal-react.js +3 -3
- package/dist/operation-lock.js +36 -1
- package/dist/operation-runtime.js +41 -12
- package/dist/operation-surface.js +118 -40
- package/dist/operation-view.d.ts +1 -1
- package/dist/operation-view.js +120 -59
- package/dist/react.js +6 -8
- package/dist/registration.js +3 -1
- package/dist/runtime.d.ts +1 -1
- package/dist/runtime.js +66 -44
- package/dist/shared/errors.d.ts +1 -1
- package/dist/shared/schema.js +45 -18
- package/dist/styles.css +879 -109
- package/dist/tool-search.js +37 -11
- package/dist/webmcp.js +38 -11
- package/package.json +1 -1
package/dist/operation-view.js
CHANGED
|
@@ -1,13 +1,19 @@
|
|
|
1
1
|
import { CursorIcon as Cursor } from "@phosphor-icons/react/Cursor";
|
|
2
2
|
import { SparkleIcon as Sparkle } from "@phosphor-icons/react/Sparkle";
|
|
3
3
|
import * as React from "react";
|
|
4
|
-
import { useCallback, useLayoutEffect, useEffect, useRef, useState } from "react";
|
|
4
|
+
import { useCallback, useLayoutEffect, useEffect, useRef, useState, } from "react";
|
|
5
5
|
import { createPortal } from "react-dom";
|
|
6
6
|
import { PageCommandError } from "./browser/locator.js";
|
|
7
7
|
import { lockOperationSurface } from "./operation-lock.js";
|
|
8
|
-
const center = (rect) => ({
|
|
8
|
+
const center = (rect) => ({
|
|
9
|
+
left: rect.left + rect.width / 2,
|
|
10
|
+
top: rect.top + rect.height / 2,
|
|
11
|
+
});
|
|
9
12
|
const distance = (a, b) => Math.hypot(a.left - b.left, a.top - b.top);
|
|
10
|
-
const interrupted = () => new PageCommandError({
|
|
13
|
+
const interrupted = () => new PageCommandError({
|
|
14
|
+
code: "EXECUTION_CANCELLED",
|
|
15
|
+
message: "Page action presentation was interrupted.",
|
|
16
|
+
});
|
|
11
17
|
function presentPointer(pointer, target, presentation) {
|
|
12
18
|
let finished = false;
|
|
13
19
|
const finish = (error) => {
|
|
@@ -19,45 +25,57 @@ function presentPointer(pointer, target, presentation) {
|
|
|
19
25
|
presentation.signal?.removeEventListener("abort", onAbort);
|
|
20
26
|
presentation.finish(error);
|
|
21
27
|
};
|
|
22
|
-
const reached = () => pointer.isConnected &&
|
|
28
|
+
const reached = () => pointer.isConnected &&
|
|
29
|
+
target.isConnected &&
|
|
30
|
+
distance(pointer.getBoundingClientRect(), center(target.getBoundingClientRect())) < 2;
|
|
23
31
|
const onTransition = (event) => {
|
|
24
32
|
if (event.target === pointer && event.propertyName === "transform" && reached())
|
|
25
33
|
finish();
|
|
26
34
|
};
|
|
27
35
|
const onAbort = () => finish(interrupted());
|
|
28
|
-
const timer = setTimeout(() => reached()
|
|
29
|
-
|
|
30
|
-
|
|
36
|
+
const timer = setTimeout(() => reached()
|
|
37
|
+
? finish()
|
|
38
|
+
: finish(new PageCommandError({
|
|
39
|
+
code: "ACTION_TIMEOUT",
|
|
40
|
+
message: "Page action pointer did not reach its target in time.",
|
|
41
|
+
})), 750);
|
|
31
42
|
pointer.addEventListener("transitionend", onTransition);
|
|
32
43
|
presentation.signal?.addEventListener("abort", onAbort, { once: true });
|
|
33
|
-
queueMicrotask(() => {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
44
|
+
queueMicrotask(() => {
|
|
45
|
+
if (presentation.signal?.aborted)
|
|
46
|
+
onAbort();
|
|
47
|
+
else if (reached())
|
|
48
|
+
finish();
|
|
49
|
+
});
|
|
37
50
|
return () => finish(interrupted());
|
|
38
51
|
}
|
|
39
52
|
function FeedbackLabel({ feedback }) {
|
|
40
|
-
return React.createElement("span", { className: "agent-operation__pointer-label", "data-browser-tool": feedback.centered ? "true" : undefined, "data-operation-effect": feedback.effect, title: feedback.label },
|
|
41
|
-
feedback.effect === "processing" ? React.createElement("span", { className: "agent-operation__pointer-processing" }) : null,
|
|
42
|
-
feedback.effect === "read" ? React.createElement("span", { className: "agent-operation__pointer-read-scan" },
|
|
53
|
+
return (React.createElement("span", { className: "agent-operation__pointer-label", "data-browser-tool": feedback.centered ? "true" : undefined, "data-operation-effect": feedback.effect, title: feedback.label },
|
|
54
|
+
feedback.effect === "processing" ? (React.createElement("span", { className: "agent-operation__pointer-processing" })) : null,
|
|
55
|
+
feedback.effect === "read" ? (React.createElement("span", { className: "agent-operation__pointer-read-scan" },
|
|
43
56
|
React.createElement("i", null),
|
|
44
57
|
React.createElement("i", null),
|
|
45
58
|
React.createElement("i", null),
|
|
46
59
|
React.createElement("i", null),
|
|
47
|
-
React.createElement("i", null)) : null,
|
|
48
|
-
feedback.effect === "navigate" ? React.createElement("span", { className: "agent-operation__pointer-navigation-trail" }) : null,
|
|
49
|
-
React.createElement("span", { className: "agent-operation__pointer-label-text" }, feedback.label));
|
|
60
|
+
React.createElement("i", null))) : null,
|
|
61
|
+
feedback.effect === "navigate" ? (React.createElement("span", { className: "agent-operation__pointer-navigation-trail" })) : null,
|
|
62
|
+
React.createElement("span", { className: "agent-operation__pointer-label-text" }, feedback.label)));
|
|
50
63
|
}
|
|
51
|
-
function FeedbackIndicator({ feedback, position }) {
|
|
64
|
+
function FeedbackIndicator({ feedback, position, }) {
|
|
52
65
|
const [lastPosition, setLastPosition] = useState(position ?? feedback.position);
|
|
53
|
-
useLayoutEffect(() => {
|
|
54
|
-
|
|
66
|
+
useLayoutEffect(() => {
|
|
67
|
+
if (position)
|
|
68
|
+
setLastPosition(position);
|
|
69
|
+
}, [position?.left, position?.top]);
|
|
55
70
|
const current = position ?? lastPosition;
|
|
56
|
-
return current ? React.createElement("div", { "aria-hidden": true, className: "agent-operation__pointer-effect", "data-active": "true", "data-operation-effect": feedback.effect, style: {
|
|
57
|
-
"--agent-operation-pointer-
|
|
58
|
-
|
|
71
|
+
return current ? (React.createElement("div", { "aria-hidden": true, className: "agent-operation__pointer-effect", "data-active": "true", "data-operation-effect": feedback.effect, style: {
|
|
72
|
+
"--agent-operation-pointer-duration": "0ms",
|
|
73
|
+
"--agent-operation-pointer-x": `${current.left}px`,
|
|
74
|
+
"--agent-operation-pointer-y": `${current.top}px`,
|
|
75
|
+
} },
|
|
76
|
+
React.createElement(FeedbackLabel, { feedback: feedback }))) : null;
|
|
59
77
|
}
|
|
60
|
-
function AgentPointer({ active, feedback, targetPosition, surfaceCenter }) {
|
|
78
|
+
function AgentPointer({ active, feedback, targetPosition, surfaceCenter, }) {
|
|
61
79
|
const pointer = useRef(null);
|
|
62
80
|
const [motion, setMotion] = useState();
|
|
63
81
|
const action = feedback?.effect === "action" && (!feedback.requiresTarget || Boolean(targetPosition));
|
|
@@ -75,8 +93,11 @@ function AgentPointer({ active, feedback, targetPosition, surfaceCenter }) {
|
|
|
75
93
|
return;
|
|
76
94
|
}
|
|
77
95
|
if (!motion) {
|
|
78
|
-
setMotion({
|
|
79
|
-
|
|
96
|
+
setMotion({
|
|
97
|
+
position: feedback?.persistentCursor && surfaceCenter ? surfaceCenter : targetPosition,
|
|
98
|
+
durationMs: 0,
|
|
99
|
+
persistent: Boolean(feedback?.persistentCursor),
|
|
100
|
+
});
|
|
80
101
|
return;
|
|
81
102
|
}
|
|
82
103
|
if (distance(motion.position, targetPosition) < 1) {
|
|
@@ -85,9 +106,14 @@ function AgentPointer({ active, feedback, targetPosition, surfaceCenter }) {
|
|
|
85
106
|
return;
|
|
86
107
|
}
|
|
87
108
|
const from = pointer.current?.getBoundingClientRect() ?? motion.position;
|
|
88
|
-
const durationMs = window.matchMedia?.("(prefers-reduced-motion: reduce)").matches
|
|
109
|
+
const durationMs = window.matchMedia?.("(prefers-reduced-motion: reduce)").matches
|
|
110
|
+
? 0
|
|
89
111
|
: Math.round(Math.min(360, Math.max(180, 180 + distance(from, targetPosition) * 0.18)));
|
|
90
|
-
setMotion({
|
|
112
|
+
setMotion({
|
|
113
|
+
position: targetPosition,
|
|
114
|
+
durationMs,
|
|
115
|
+
persistent: motion.persistent || Boolean(feedback?.persistentCursor),
|
|
116
|
+
});
|
|
91
117
|
}, [active, action, feedback?.id, targetPosition?.left, targetPosition?.top, motion]);
|
|
92
118
|
const show = Boolean(active && motion && (motion.persistent || action));
|
|
93
119
|
useLayoutEffect(() => {
|
|
@@ -100,18 +126,22 @@ function AgentPointer({ active, feedback, targetPosition, surfaceCenter }) {
|
|
|
100
126
|
if (show && pointer.current)
|
|
101
127
|
return presentPointer(pointer.current, feedback.target, feedback.presentation);
|
|
102
128
|
}, [active, show, feedback?.presentation, feedback?.target, Boolean(targetPosition)]);
|
|
103
|
-
return show && motion ? React.createElement("div", { "aria-hidden": true, ref: pointer, className: "agent-operation__pointer-effect", "data-active": action, "data-operation-effect": action ? "action" : undefined, style: {
|
|
104
|
-
"--agent-operation-pointer-
|
|
129
|
+
return show && motion ? (React.createElement("div", { "aria-hidden": true, ref: pointer, className: "agent-operation__pointer-effect", "data-active": action, "data-operation-effect": action ? "action" : undefined, style: {
|
|
130
|
+
"--agent-operation-pointer-duration": `${motion.durationMs}ms`,
|
|
131
|
+
"--agent-operation-pointer-x": `${motion.position.left}px`,
|
|
132
|
+
"--agent-operation-pointer-y": `${motion.position.top}px`,
|
|
133
|
+
} },
|
|
105
134
|
React.createElement("span", { className: "agent-operation__pointer-cursor" },
|
|
106
135
|
React.createElement(Cursor, { className: "agent-operation__pointer", size: 24, weight: "fill" })),
|
|
107
|
-
action && feedback ? React.createElement(FeedbackLabel, { feedback: feedback }) : null) : null;
|
|
136
|
+
action && feedback ? React.createElement(FeedbackLabel, { feedback: feedback }) : null)) : null;
|
|
108
137
|
}
|
|
109
|
-
export function OperationOverlayView({ operation, feedback, progressHistory = [], running, humanHasControl = false, cancelling = false, onCancel: cancel, resolveSurface }) {
|
|
138
|
+
export function OperationOverlayView({ operation, feedback, progressHistory = [], running, humanHasControl = false, cancelling = false, onCancel: cancel, resolveSurface, }) {
|
|
110
139
|
const [, setGeometry] = useState("");
|
|
111
140
|
const [historyOpen, setHistoryOpen] = useState(false);
|
|
112
141
|
const [historyId] = useState(() => `nexface-operation-history-${globalThis.crypto?.randomUUID?.() ?? `${Date.now()}-${Math.random()}`}`);
|
|
113
142
|
const cancelButtonRef = useRef(null);
|
|
114
|
-
const controllerShouldBeVisible = operation.status !== "cancelled" &&
|
|
143
|
+
const controllerShouldBeVisible = operation.status !== "cancelled" &&
|
|
144
|
+
(running || humanHasControl || operation.status === "failed");
|
|
115
145
|
const [controllerVisibility, setControllerVisibility] = useState(controllerShouldBeVisible ? "visible" : undefined);
|
|
116
146
|
useEffect(() => {
|
|
117
147
|
if (controllerShouldBeVisible) {
|
|
@@ -119,12 +149,14 @@ export function OperationOverlayView({ operation, feedback, progressHistory = []
|
|
|
119
149
|
return;
|
|
120
150
|
}
|
|
121
151
|
setHistoryOpen(false);
|
|
122
|
-
setControllerVisibility((current) => current ? "exiting" : undefined);
|
|
152
|
+
setControllerVisibility((current) => (current ? "exiting" : undefined));
|
|
123
153
|
const timer = setTimeout(() => setControllerVisibility(undefined), 300);
|
|
124
154
|
return () => clearTimeout(timer);
|
|
125
155
|
}, [controllerShouldBeVisible]);
|
|
126
|
-
useEffect(() => {
|
|
127
|
-
|
|
156
|
+
useEffect(() => {
|
|
157
|
+
if (!progressHistory.length)
|
|
158
|
+
setHistoryOpen(false);
|
|
159
|
+
}, [progressHistory.length]);
|
|
128
160
|
useLayoutEffect(() => {
|
|
129
161
|
const surface = resolveSurface?.();
|
|
130
162
|
if (!surface || !running || humanHasControl)
|
|
@@ -144,17 +176,22 @@ export function OperationOverlayView({ operation, feedback, progressHistory = []
|
|
|
144
176
|
useLayoutEffect(() => {
|
|
145
177
|
const update = () => {
|
|
146
178
|
const { surfaceRect, targetRect } = readGeometry();
|
|
147
|
-
const geometry = [surfaceRect, targetRect]
|
|
179
|
+
const geometry = [surfaceRect, targetRect]
|
|
180
|
+
.map((rect) => {
|
|
148
181
|
if (!rect)
|
|
149
182
|
return "";
|
|
150
183
|
return [rect.left, rect.top, rect.width, rect.height].join(",");
|
|
151
|
-
})
|
|
184
|
+
})
|
|
185
|
+
.join(";");
|
|
152
186
|
setGeometry(geometry);
|
|
153
187
|
};
|
|
154
188
|
update();
|
|
155
189
|
if (!running && !humanHasControl)
|
|
156
190
|
return;
|
|
157
|
-
let frame = requestAnimationFrame(function track() {
|
|
191
|
+
let frame = requestAnimationFrame(function track() {
|
|
192
|
+
update();
|
|
193
|
+
frame = requestAnimationFrame(track);
|
|
194
|
+
});
|
|
158
195
|
return () => cancelAnimationFrame(frame);
|
|
159
196
|
}, [readGeometry, operation, running, humanHasControl]);
|
|
160
197
|
if (typeof document === "undefined")
|
|
@@ -170,29 +207,53 @@ export function OperationOverlayView({ operation, feedback, progressHistory = []
|
|
|
170
207
|
const controllerExiting = controllerVisibility === "exiting";
|
|
171
208
|
const controllerLabel = humanHasControl ? undefined : operation.progressLabel;
|
|
172
209
|
const compactStatusLabel = humanHasControl ? "等待你完成操作" : operation.progressLabel;
|
|
173
|
-
const statusLabel = humanHasControl
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
210
|
+
const statusLabel = humanHasControl
|
|
211
|
+
? "等待你完成操作"
|
|
212
|
+
: {
|
|
213
|
+
running: "Agent 正在控制",
|
|
214
|
+
completed: running ? "工具已完成,任务继续中" : "操作已完成",
|
|
215
|
+
failed: running ? "Agent 正在处理错误" : "操作失败",
|
|
216
|
+
cancelled: "任务已终止",
|
|
217
|
+
}[operation.status];
|
|
177
218
|
const targetPosition = rect ? center(rect) : undefined;
|
|
178
219
|
return createPortal(React.createElement("div", { className: "agent-operation", "data-nexface-ignore": true, "data-feedback-active": operationFeedbackActive, "data-structured-target-lost": structuredTargetLost, "data-human-control": humanHasControl, "data-operation-effect": effect ?? "action", "data-run-active": running, "data-status": operation.status },
|
|
179
|
-
agentControlsSurface && surfaceRect ? React.createElement("div", { "aria-hidden": true, className: "agent-operation__surface", style: {
|
|
180
|
-
...(highlightedRect
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
220
|
+
agentControlsSurface && surfaceRect ? (React.createElement("div", { "aria-hidden": true, className: "agent-operation__surface", style: {
|
|
221
|
+
...(highlightedRect
|
|
222
|
+
? {
|
|
223
|
+
"--agent-operation-cutout-height": `${highlightedRect.height}px`,
|
|
224
|
+
"--agent-operation-cutout-left": `${highlightedRect.left - surfaceRect.left}px`,
|
|
225
|
+
"--agent-operation-cutout-top": `${highlightedRect.top - surfaceRect.top}px`,
|
|
226
|
+
"--agent-operation-cutout-width": `${highlightedRect.width}px`,
|
|
227
|
+
}
|
|
228
|
+
: {}),
|
|
229
|
+
height: surfaceRect.height,
|
|
230
|
+
left: surfaceRect.left,
|
|
231
|
+
top: surfaceRect.top,
|
|
232
|
+
width: surfaceRect.width,
|
|
233
|
+
} })) : null,
|
|
234
|
+
activeFeedback && highlightedRect ? (React.createElement("div", { "aria-hidden": true, className: "agent-operation__target", "data-operation-effect": effect, style: {
|
|
235
|
+
height: highlightedRect.height,
|
|
236
|
+
left: highlightedRect.left,
|
|
237
|
+
top: highlightedRect.top,
|
|
238
|
+
width: highlightedRect.width,
|
|
239
|
+
} })) : null,
|
|
186
240
|
React.createElement(AgentPointer, { active: agentControlsSurface && !cancelling, feedback: feedback?.presentation ? feedback : activeFeedback, targetPosition: targetPosition, surfaceCenter: surfaceRect ? center(surfaceRect) : undefined }),
|
|
187
|
-
activeFeedback && effect !== "action" && surfaceRect ? React.createElement(FeedbackIndicator, { key: activeFeedback.id, feedback: activeFeedback, position: activeFeedback.centered ? center(surfaceRect) : targetPosition }) : null,
|
|
188
|
-
controllerMounted ? React.createElement("section", { "aria-live": "polite", className: "agent-operation__controller", "data-visibility": controllerExiting ? "exiting" : "visible", style: surfaceRect
|
|
189
|
-
|
|
241
|
+
activeFeedback && effect !== "action" && surfaceRect ? (React.createElement(FeedbackIndicator, { key: activeFeedback.id, feedback: activeFeedback, position: activeFeedback.centered ? center(surfaceRect) : targetPosition })) : null,
|
|
242
|
+
controllerMounted ? (React.createElement("section", { "aria-live": "polite", className: "agent-operation__controller", "data-visibility": controllerExiting ? "exiting" : "visible", style: surfaceRect
|
|
243
|
+
? {
|
|
244
|
+
left: surfaceRect.left + surfaceRect.width / 2,
|
|
245
|
+
right: "auto",
|
|
246
|
+
"--agent-operation-controller-x": "-50%",
|
|
247
|
+
maxWidth: Math.max(0, surfaceRect.width - 32),
|
|
248
|
+
}
|
|
249
|
+
: undefined },
|
|
250
|
+
historyOpen ? (React.createElement("aside", { "aria-label": "\u672C\u6B21\u8FD0\u884C\u7684\u64CD\u4F5C\u8BB0\u5F55", className: "agent-operation__history", id: historyId },
|
|
190
251
|
React.createElement("header", null,
|
|
191
252
|
React.createElement("strong", null, "\u64CD\u4F5C\u8BB0\u5F55"),
|
|
192
253
|
React.createElement("span", null, progressHistory.length)),
|
|
193
|
-
React.createElement("ol", null, progressHistory.map(({ progressLabel: label, toolCallId }, index) => React.createElement("li", { key: toolCallId },
|
|
254
|
+
React.createElement("ol", null, progressHistory.map(({ progressLabel: label, toolCallId }, index) => (React.createElement("li", { key: toolCallId },
|
|
194
255
|
React.createElement("span", { "aria-hidden": true }, index + 1),
|
|
195
|
-
React.createElement("p", null, label))))) : null,
|
|
256
|
+
React.createElement("p", null, label))))))) : null,
|
|
196
257
|
React.createElement("span", { "aria-hidden": true, className: "agent-operation__edge-flow" }),
|
|
197
258
|
React.createElement("span", { "aria-hidden": true, className: "agent-operation__pause-mark" },
|
|
198
259
|
React.createElement("i", null),
|
|
@@ -204,8 +265,8 @@ export function OperationOverlayView({ operation, feedback, progressHistory = []
|
|
|
204
265
|
React.createElement("div", null,
|
|
205
266
|
React.createElement("span", { className: "agent-operation__heading" },
|
|
206
267
|
React.createElement("strong", null, controllerLabel)),
|
|
207
|
-
statusLabel ? React.createElement("small", { className: "agent-operation__description" },
|
|
208
|
-
React.createElement("span", { className: "agent-operation__status" }, statusLabel)) : null),
|
|
209
|
-
(running || humanHasControl) && operation.status !== "cancelled" ? React.createElement("span", { className: "agent-operation__compact-status", title: compactStatusLabel }, compactStatusLabel) : null,
|
|
210
|
-
cancel && (running || humanHasControl) && operation.status !== "cancelled" ? React.createElement("button", { ref: cancelButtonRef, className: "agent-operation__stop", type: "button", disabled: cancelling, onClick: cancel }, cancelling ? "正在终止" : "终止任务") : null) : null), document.body);
|
|
268
|
+
statusLabel ? (React.createElement("small", { className: "agent-operation__description" },
|
|
269
|
+
React.createElement("span", { className: "agent-operation__status" }, statusLabel))) : null),
|
|
270
|
+
(running || humanHasControl) && operation.status !== "cancelled" ? (React.createElement("span", { className: "agent-operation__compact-status", title: compactStatusLabel }, compactStatusLabel)) : null,
|
|
271
|
+
cancel && (running || humanHasControl) && operation.status !== "cancelled" ? (React.createElement("button", { ref: cancelButtonRef, className: "agent-operation__stop", type: "button", disabled: cancelling, onClick: cancel }, cancelling ? "正在终止" : "终止任务")) : null)) : null), document.body);
|
|
211
272
|
}
|
package/dist/react.js
CHANGED
|
@@ -3,7 +3,7 @@ import { Children, Fragment, cloneElement, useCallback, useEffect, useLayoutEffe
|
|
|
3
3
|
import { defineAgentTool } from "./definition.js";
|
|
4
4
|
import { getAgentNavigationEntries, } from "./navigation.js";
|
|
5
5
|
import { AgentToolError, getInternalToolRuntime } from "./runtime.js";
|
|
6
|
-
import { InternalToolRuntimeScope, useInternalToolRuntime
|
|
6
|
+
import { InternalToolRuntimeScope, useInternalToolRuntime } from "./internal-react.js";
|
|
7
7
|
import { connectToolRuntimeToWebMcp } from "./webmcp.js";
|
|
8
8
|
const bindingInternals = new WeakMap();
|
|
9
9
|
export function getAgentToolBindingInternals(binding) {
|
|
@@ -55,10 +55,10 @@ function createAgentToolBinding(toolName) {
|
|
|
55
55
|
function useAgentBinding(definition, execute, options) {
|
|
56
56
|
const runtime = useInternalToolRuntime();
|
|
57
57
|
const confirmation = options.requiresConfirmation;
|
|
58
|
-
if (confirmation !== undefined
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
58
|
+
if (confirmation !== undefined &&
|
|
59
|
+
(!confirmation ||
|
|
60
|
+
typeof confirmation !== "object" ||
|
|
61
|
+
(confirmation.mode !== "chat" && confirmation.mode !== "page"))) {
|
|
62
62
|
throw new Error("requiresConfirmation must explicitly use chat or page mode.");
|
|
63
63
|
}
|
|
64
64
|
const executeRef = useRef(execute);
|
|
@@ -82,9 +82,7 @@ export function useAgentTool(definition, execute, options = {}) {
|
|
|
82
82
|
return useAgentBinding(definition, execute, options);
|
|
83
83
|
}
|
|
84
84
|
function getAgentNavigationExecutor(executors, key) {
|
|
85
|
-
if (!executors
|
|
86
|
-
|| typeof executors !== "object"
|
|
87
|
-
|| !Object.hasOwn(executors, key)) {
|
|
85
|
+
if (!executors || typeof executors !== "object" || !Object.hasOwn(executors, key)) {
|
|
88
86
|
throw new Error(`Missing navigation executor: ${key}.`);
|
|
89
87
|
}
|
|
90
88
|
const executor = executors[key];
|
package/dist/registration.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { assertAgentToolInputSchema } from "./shared/schema.js";
|
|
2
2
|
export function registerAgentTool(definition, options) {
|
|
3
|
-
if (!definition ||
|
|
3
|
+
if (!definition ||
|
|
4
|
+
typeof definition.name !== "string" ||
|
|
5
|
+
typeof options?.execute !== "function") {
|
|
4
6
|
throw new TypeError("registerAgentTool requires a definition and execute function.");
|
|
5
7
|
}
|
|
6
8
|
if ("requiresConfirmation" in options) {
|
package/dist/runtime.d.ts
CHANGED
|
@@ -14,7 +14,7 @@ export interface ToolRuntimeToolChangeEvent {
|
|
|
14
14
|
readonly change: "registered" | "unregistered" | "visibility";
|
|
15
15
|
}
|
|
16
16
|
declare const CONTEXT_ERROR_CODES: readonly ["CONTEXT_DISPOSED", "CONTEXT_NAME_MISMATCH", "DUPLICATE_CONTEXT", "INVALID_CONTEXT"];
|
|
17
|
-
type AgentContextErrorCode = typeof CONTEXT_ERROR_CODES[number];
|
|
17
|
+
type AgentContextErrorCode = (typeof CONTEXT_ERROR_CODES)[number];
|
|
18
18
|
export declare function isToolRuntimeError(error: unknown): error is {
|
|
19
19
|
readonly type: "tool_runtime_error";
|
|
20
20
|
readonly domain: "tool" | "context";
|
package/dist/runtime.js
CHANGED
|
@@ -2,13 +2,13 @@ import { searchNavigationTools, searchToolDefinition } from "./tool-search.js";
|
|
|
2
2
|
import { pageDecisionSymbol } from "./shared/runtime-execution.js";
|
|
3
3
|
import { PageCommandError } from "./browser/locator.js";
|
|
4
4
|
import { measurePagePhase } from "./browser/diagnostics.js";
|
|
5
|
-
import { runtimeControllerSymbol } from "./shared/runtime-controller.js";
|
|
5
|
+
import { runtimeControllerSymbol, } from "./shared/runtime-controller.js";
|
|
6
6
|
import { AgentToolError, TOOL_ERROR_CODES } from "./shared/errors.js";
|
|
7
7
|
export { AgentToolError } from "./shared/errors.js";
|
|
8
8
|
import { OperationTracker } from "./operation-runtime.js";
|
|
9
9
|
import { isValidAgentContextName } from "./definition.js";
|
|
10
10
|
import { assertAgentToolInputSchema, validateAgentToolInput } from "./shared/schema.js";
|
|
11
|
-
import { createBrowserToolset
|
|
11
|
+
import { createBrowserToolset } from "./browser/toolset.js";
|
|
12
12
|
const CONTEXT_ERROR_CODES = [
|
|
13
13
|
"CONTEXT_DISPOSED",
|
|
14
14
|
"CONTEXT_NAME_MISMATCH",
|
|
@@ -21,9 +21,9 @@ export function isToolRuntimeError(error) {
|
|
|
21
21
|
if (!error || typeof error !== "object")
|
|
22
22
|
return false;
|
|
23
23
|
const candidate = error;
|
|
24
|
-
if (candidate.type !== "tool_runtime_error"
|
|
25
|
-
|
|
26
|
-
|
|
24
|
+
if (candidate.type !== "tool_runtime_error" ||
|
|
25
|
+
typeof candidate.message !== "string" ||
|
|
26
|
+
typeof candidate.code !== "string") {
|
|
27
27
|
return false;
|
|
28
28
|
}
|
|
29
29
|
if (candidate.domain === "tool") {
|
|
@@ -44,9 +44,7 @@ function assertContextName(name) {
|
|
|
44
44
|
}
|
|
45
45
|
}
|
|
46
46
|
function normalizeContextValue(value, path = "value", seen = new WeakSet()) {
|
|
47
|
-
if (value === null
|
|
48
|
-
|| typeof value === "string"
|
|
49
|
-
|| typeof value === "boolean") {
|
|
47
|
+
if (value === null || typeof value === "string" || typeof value === "boolean") {
|
|
50
48
|
return value;
|
|
51
49
|
}
|
|
52
50
|
if (typeof value === "number") {
|
|
@@ -83,8 +81,8 @@ function normalizeContextValue(value, path = "value", seen = new WeakSet()) {
|
|
|
83
81
|
}
|
|
84
82
|
function normalizeContext(context) {
|
|
85
83
|
assertContextName(context.name);
|
|
86
|
-
if (context.description !== undefined
|
|
87
|
-
|
|
84
|
+
if (context.description !== undefined &&
|
|
85
|
+
(typeof context.description !== "string" || !context.description.trim())) {
|
|
88
86
|
throw new AgentContextError("INVALID_CONTEXT", "Context description must be a non-empty string when provided.");
|
|
89
87
|
}
|
|
90
88
|
return {
|
|
@@ -139,16 +137,22 @@ export class ToolRuntimeImplementation extends EventTarget {
|
|
|
139
137
|
beginInternalOperation(options = {}) {
|
|
140
138
|
return this.#operations.begin(options);
|
|
141
139
|
}
|
|
142
|
-
endBridgeOperation(handle) {
|
|
140
|
+
endBridgeOperation(handle) {
|
|
141
|
+
return this.#operations.disposeBridge(handle);
|
|
142
|
+
}
|
|
143
143
|
setTaskProgress = (handle, progress) => this.#operations.setTaskProgress(handle, progress);
|
|
144
144
|
setOperationWaiting = (handle, waiting) => this.#operations.setWaitingForUser(handle, waiting);
|
|
145
145
|
getActivitySnapshot = () => this.#operations.getSnapshot();
|
|
146
146
|
subscribeActivity = (listener) => this.#operations.subscribe(listener);
|
|
147
|
-
requestOperationCancel(id) {
|
|
147
|
+
requestOperationCancel(id) {
|
|
148
|
+
this.#operations.requestCancel(id);
|
|
149
|
+
}
|
|
148
150
|
setPageActionFeedback(listener) {
|
|
149
151
|
this.#pageActionFeedback = listener;
|
|
150
|
-
return () => {
|
|
151
|
-
this.#pageActionFeedback
|
|
152
|
+
return () => {
|
|
153
|
+
if (this.#pageActionFeedback === listener)
|
|
154
|
+
this.#pageActionFeedback = undefined;
|
|
155
|
+
};
|
|
152
156
|
}
|
|
153
157
|
#emitExecution(event) {
|
|
154
158
|
this.#operations.observe(event);
|
|
@@ -156,8 +160,7 @@ export class ToolRuntimeImplementation extends EventTarget {
|
|
|
156
160
|
}
|
|
157
161
|
constructor(options = {}) {
|
|
158
162
|
super();
|
|
159
|
-
if (options.pageAutomation !== undefined &&
|
|
160
|
-
typeof options.pageAutomation !== "boolean") {
|
|
163
|
+
if (options.pageAutomation !== undefined && typeof options.pageAutomation !== "boolean") {
|
|
161
164
|
throw new AgentToolError("INVALID_INPUT", "pageAutomation must be a boolean.");
|
|
162
165
|
}
|
|
163
166
|
if (options.routeSearch !== undefined && typeof options.routeSearch !== "boolean") {
|
|
@@ -172,7 +175,9 @@ export class ToolRuntimeImplementation extends EventTarget {
|
|
|
172
175
|
if (searchQueries?.some((phrase) => typeof phrase !== "string" || !phrase.trim()))
|
|
173
176
|
throw new AgentToolError("INVALID_INPUT", "Search queries must contain nonblank strings.");
|
|
174
177
|
const navigation = this.getRegisteredTools().filter(({ definition }) => definition.effect === "navigate");
|
|
175
|
-
const candidates = searchQueries === undefined
|
|
178
|
+
const candidates = searchQueries === undefined
|
|
179
|
+
? navigation
|
|
180
|
+
: searchNavigationTools(navigation, searchQueries.map((phrase) => phrase.trim()));
|
|
176
181
|
if (searchQueries !== undefined) {
|
|
177
182
|
const previous = this.#visibleNavigation;
|
|
178
183
|
this.#visibleNavigation = new Set(candidates);
|
|
@@ -185,7 +190,9 @@ export class ToolRuntimeImplementation extends EventTarget {
|
|
|
185
190
|
})));
|
|
186
191
|
}
|
|
187
192
|
}
|
|
188
|
-
return {
|
|
193
|
+
return {
|
|
194
|
+
tools: candidates.map(({ definition: { name, description } }) => ({ name, description })),
|
|
195
|
+
};
|
|
189
196
|
});
|
|
190
197
|
this.#browserToolset = options.pageAutomation ? createBrowserToolset() : undefined;
|
|
191
198
|
runtimeInternals.set(this, this);
|
|
@@ -227,9 +234,7 @@ export class ToolRuntimeImplementation extends EventTarget {
|
|
|
227
234
|
definition,
|
|
228
235
|
get confirmation() {
|
|
229
236
|
const confirmation = options.getConfirmation?.();
|
|
230
|
-
return confirmation === undefined
|
|
231
|
-
? undefined
|
|
232
|
-
: Object.freeze({ ...confirmation });
|
|
237
|
+
return confirmation === undefined ? undefined : Object.freeze({ ...confirmation });
|
|
233
238
|
},
|
|
234
239
|
lifecycleSignal: lifecycleController.signal,
|
|
235
240
|
});
|
|
@@ -238,9 +243,7 @@ export class ToolRuntimeImplementation extends EventTarget {
|
|
|
238
243
|
definition,
|
|
239
244
|
runtimeTool,
|
|
240
245
|
execute,
|
|
241
|
-
...(options.getConfirmation
|
|
242
|
-
? { getConfirmation: options.getConfirmation }
|
|
243
|
-
: {}),
|
|
246
|
+
...(options.getConfirmation ? { getConfirmation: options.getConfirmation } : {}),
|
|
244
247
|
lifecycleController,
|
|
245
248
|
};
|
|
246
249
|
this.#bindings.set(definition.name, binding);
|
|
@@ -378,7 +381,9 @@ export class ToolRuntimeImplementation extends EventTarget {
|
|
|
378
381
|
getWebMcpTools() {
|
|
379
382
|
return [...this.#bindings.values()]
|
|
380
383
|
.map(({ runtimeTool }) => runtimeTool)
|
|
381
|
-
.filter((tool) => !this.#routeSearch ||
|
|
384
|
+
.filter((tool) => !this.#routeSearch ||
|
|
385
|
+
tool.definition.effect !== "navigate" ||
|
|
386
|
+
this.#visibleNavigation.has(tool))
|
|
382
387
|
.sort((left, right) => compareStrings(left.definition.name, right.definition.name));
|
|
383
388
|
}
|
|
384
389
|
isRegistered(name) {
|
|
@@ -386,10 +391,8 @@ export class ToolRuntimeImplementation extends EventTarget {
|
|
|
386
391
|
}
|
|
387
392
|
getTools() {
|
|
388
393
|
const applicationTools = this.getWebMcpTools();
|
|
389
|
-
const browserTools = (this.#browserToolset?.definitions ?? [])
|
|
390
|
-
|
|
391
|
-
return [...applicationTools, ...browserTools]
|
|
392
|
-
.sort((left, right) => compareStrings(left.definition.name, right.definition.name));
|
|
394
|
+
const browserTools = (this.#browserToolset?.definitions ?? []).map((definition) => this.#getBrowserRuntimeTool(definition));
|
|
395
|
+
return [...applicationTools, ...browserTools].sort((left, right) => compareStrings(left.definition.name, right.definition.name));
|
|
393
396
|
}
|
|
394
397
|
waitForTool(definition, options = {}) {
|
|
395
398
|
const isTargetRegistered = () => this.#bindings.has(definition.name);
|
|
@@ -412,8 +415,7 @@ export class ToolRuntimeImplementation extends EventTarget {
|
|
|
412
415
|
};
|
|
413
416
|
const onToolChange = (event) => {
|
|
414
417
|
const detail = event.detail;
|
|
415
|
-
if (detail?.toolName !== definition.name
|
|
416
|
-
|| detail.change !== "registered")
|
|
418
|
+
if (detail?.toolName !== definition.name || detail.change !== "registered")
|
|
417
419
|
return;
|
|
418
420
|
if (isTargetRegistered())
|
|
419
421
|
settle(resolve);
|
|
@@ -439,7 +441,11 @@ export class ToolRuntimeImplementation extends EventTarget {
|
|
|
439
441
|
}
|
|
440
442
|
execute(tool, input, options = {}) {
|
|
441
443
|
const label = options.progressLabel?.trim().replace(/\s+/g, " ").slice(0, 48);
|
|
442
|
-
options = {
|
|
444
|
+
options = {
|
|
445
|
+
...(options.signal ? { signal: options.signal } : {}),
|
|
446
|
+
...(label ? { progressLabel: label } : {}),
|
|
447
|
+
...(options[pageDecisionSymbol] ? { [pageDecisionSymbol]: true } : {}),
|
|
448
|
+
};
|
|
443
449
|
const definition = tool.definition;
|
|
444
450
|
const name = definition.name;
|
|
445
451
|
const binding = this.#bindings.get(name);
|
|
@@ -455,8 +461,8 @@ export class ToolRuntimeImplementation extends EventTarget {
|
|
|
455
461
|
if (!browserHandler) {
|
|
456
462
|
return Promise.reject(new AgentToolError("UNKNOWN_TOOL", `Unknown tool: ${name}.`));
|
|
457
463
|
}
|
|
458
|
-
if (!browserToolset?.definitions.some((candidate) => candidate === definition)
|
|
459
|
-
|
|
464
|
+
if (!browserToolset?.definitions.some((candidate) => candidate === definition) ||
|
|
465
|
+
this.#getBrowserRuntimeTool(definition) !== tool) {
|
|
460
466
|
return Promise.reject(new AgentToolError("CONTRACT_MISMATCH", `Tool "${name}" is registered with a different Definition.`));
|
|
461
467
|
}
|
|
462
468
|
return this.#enqueue((operationId) => this.#observeExecution(tool, input, options.signal, async (onFeedback, assertActive) => {
|
|
@@ -498,7 +504,9 @@ export class ToolRuntimeImplementation extends EventTarget {
|
|
|
498
504
|
};
|
|
499
505
|
// Reserve the tail before invoking executors or notifying synchronous listeners.
|
|
500
506
|
let settleTail;
|
|
501
|
-
const tail = new Promise((resolve) => {
|
|
507
|
+
const tail = new Promise((resolve) => {
|
|
508
|
+
settleTail = resolve;
|
|
509
|
+
});
|
|
502
510
|
this.#executionTail = tail;
|
|
503
511
|
const queuedExecution = previous ? previous.then(start) : start();
|
|
504
512
|
let execution = queuedExecution;
|
|
@@ -530,9 +538,13 @@ export class ToolRuntimeImplementation extends EventTarget {
|
|
|
530
538
|
const target = binding.resolveTarget?.(input);
|
|
531
539
|
return target?.isConnected ? target : undefined;
|
|
532
540
|
}
|
|
533
|
-
if (this.#browserRuntimeTools.get(tool.definition) !== tool ||
|
|
541
|
+
if (this.#browserRuntimeTools.get(tool.definition) !== tool ||
|
|
542
|
+
!input ||
|
|
543
|
+
typeof input !== "object")
|
|
534
544
|
return undefined;
|
|
535
|
-
return this.#browserToolset?.handlers
|
|
545
|
+
return this.#browserToolset?.handlers
|
|
546
|
+
.get(tool.definition.name)
|
|
547
|
+
?.resolveTarget?.(input);
|
|
536
548
|
}
|
|
537
549
|
async #observeExecution(tool, input, signal, execute, progressLabel, operationId) {
|
|
538
550
|
const running = {
|
|
@@ -549,7 +561,10 @@ export class ToolRuntimeImplementation extends EventTarget {
|
|
|
549
561
|
const assertActive = () => {
|
|
550
562
|
signal?.throwIfAborted();
|
|
551
563
|
if (presentation && this.#pageActionFeedback !== presentation) {
|
|
552
|
-
throw new PageCommandError({
|
|
564
|
+
throw new PageCommandError({
|
|
565
|
+
code: "EXECUTION_CANCELLED",
|
|
566
|
+
message: "Page action presentation was detached.",
|
|
567
|
+
});
|
|
553
568
|
}
|
|
554
569
|
};
|
|
555
570
|
this.#emitExecution(running);
|
|
@@ -558,26 +573,33 @@ export class ToolRuntimeImplementation extends EventTarget {
|
|
|
558
573
|
const result = await execute(async (feedback) => {
|
|
559
574
|
assertActive();
|
|
560
575
|
if (presentation)
|
|
561
|
-
await measurePagePhase("presentation", async () => {
|
|
576
|
+
await measurePagePhase("presentation", async () => {
|
|
577
|
+
await presentation(running, feedback, signal);
|
|
578
|
+
});
|
|
562
579
|
assertActive();
|
|
563
580
|
}, assertActive);
|
|
564
581
|
signal?.throwIfAborted();
|
|
565
582
|
this.#emitExecution({
|
|
566
|
-
...running,
|
|
583
|
+
...running,
|
|
584
|
+
status: "success",
|
|
585
|
+
finishedAt: Date.now(),
|
|
586
|
+
result,
|
|
567
587
|
});
|
|
568
588
|
return result;
|
|
569
589
|
}
|
|
570
590
|
catch (error) {
|
|
571
591
|
this.#emitExecution({
|
|
572
|
-
...running,
|
|
573
|
-
|
|
592
|
+
...running,
|
|
593
|
+
status: "error",
|
|
594
|
+
cancelled: signal?.aborted === true,
|
|
595
|
+
finishedAt: Date.now(),
|
|
596
|
+
error,
|
|
574
597
|
});
|
|
575
598
|
throw error;
|
|
576
599
|
}
|
|
577
600
|
}
|
|
578
601
|
async #executeApplication(name, input, options, binding, operationId) {
|
|
579
|
-
const id = globalThis.crypto?.randomUUID?.()
|
|
580
|
-
?? `${Date.now()}-${Math.random()}`;
|
|
602
|
+
const id = globalThis.crypto?.randomUUID?.() ?? `${Date.now()}-${Math.random()}`;
|
|
581
603
|
const startedAt = Date.now();
|
|
582
604
|
const running = {
|
|
583
605
|
...(operationId ? { operationId } : {}),
|
package/dist/shared/errors.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export declare const TOOL_ERROR_CODES: readonly ["EXECUTION_OUTCOME_UNKNOWN", "ACTION_IN_FLIGHT", "ACTION_NOT_HANDLED", "ACTION_TARGET_UNAVAILABLE", "BROWSER_EXECUTION_FAILED", "BROWSER_SNAPSHOT_REQUIRED", "BROWSER_TOOLS_UNAVAILABLE", "CONTRACT_MISMATCH", "DUPLICATE_BINDING", "INVALID_INPUT", "INVALID_TOOL_RUNTIME", "OPERATION_BUSY", "TOOL_CANCELLED", "TOOL_CONFIRMATION_INVALIDATED", "TOOL_CONFIRMATION_REJECTED", "TOOL_ERROR", "TOOL_REGISTRATION_TIMEOUT", "TOOL_SKIPPED", "TOOL_UNAVAILABLE", "UNKNOWN_TOOL"];
|
|
2
|
-
type AgentToolErrorCode = typeof TOOL_ERROR_CODES[number];
|
|
2
|
+
type AgentToolErrorCode = (typeof TOOL_ERROR_CODES)[number];
|
|
3
3
|
export declare class AgentToolError extends Error {
|
|
4
4
|
readonly code: AgentToolErrorCode;
|
|
5
5
|
readonly details?: unknown | undefined;
|