camofox-browser 2.1.1 → 2.4.3
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 +150 -0
- package/README.md +310 -34
- package/dist/src/cli/commands/content.d.ts.map +1 -1
- package/dist/src/cli/commands/content.js +37 -0
- package/dist/src/cli/commands/content.js.map +1 -1
- package/dist/src/cli/commands/core.d.ts.map +1 -1
- package/dist/src/cli/commands/core.js +21 -4
- package/dist/src/cli/commands/core.js.map +1 -1
- package/dist/src/cli/commands/interaction.d.ts.map +1 -1
- package/dist/src/cli/commands/interaction.js +5 -14
- package/dist/src/cli/commands/interaction.js.map +1 -1
- package/dist/src/cli/commands/navigation.d.ts.map +1 -1
- package/dist/src/cli/commands/navigation.js +12 -6
- package/dist/src/cli/commands/navigation.js.map +1 -1
- package/dist/src/cli/commands/server.d.ts.map +1 -1
- package/dist/src/cli/commands/server.js +9 -3
- package/dist/src/cli/commands/server.js.map +1 -1
- package/dist/src/cli/commands/session.d.ts.map +1 -1
- package/dist/src/cli/commands/session.js +23 -5
- package/dist/src/cli/commands/session.js.map +1 -1
- package/dist/src/cli/server/manager.d.ts +1 -0
- package/dist/src/cli/server/manager.d.ts.map +1 -1
- package/dist/src/cli/server/manager.js +7 -12
- package/dist/src/cli/server/manager.js.map +1 -1
- package/dist/src/middleware/lifecycle-activity.d.ts +9 -0
- package/dist/src/middleware/lifecycle-activity.d.ts.map +1 -0
- package/dist/src/middleware/lifecycle-activity.js +21 -0
- package/dist/src/middleware/lifecycle-activity.js.map +1 -0
- package/dist/src/openapi/spec.d.ts +4 -0
- package/dist/src/openapi/spec.d.ts.map +1 -0
- package/dist/src/openapi/spec.js +730 -0
- package/dist/src/openapi/spec.js.map +1 -0
- package/dist/src/routes/core.d.ts.map +1 -1
- package/dist/src/routes/core.js +545 -58
- package/dist/src/routes/core.js.map +1 -1
- package/dist/src/routes/docs.d.ts +3 -0
- package/dist/src/routes/docs.d.ts.map +1 -0
- package/dist/src/routes/docs.js +23 -0
- package/dist/src/routes/docs.js.map +1 -0
- package/dist/src/routes/openclaw.d.ts.map +1 -1
- package/dist/src/routes/openclaw.js +317 -90
- package/dist/src/routes/openclaw.js.map +1 -1
- package/dist/src/server.js +55 -4
- package/dist/src/server.js.map +1 -1
- package/dist/src/services/context-pool.d.ts +21 -4
- package/dist/src/services/context-pool.d.ts.map +1 -1
- package/dist/src/services/context-pool.js +290 -71
- package/dist/src/services/context-pool.js.map +1 -1
- package/dist/src/services/download.d.ts +2 -0
- package/dist/src/services/download.d.ts.map +1 -1
- package/dist/src/services/download.js +110 -80
- package/dist/src/services/download.js.map +1 -1
- package/dist/src/services/lifecycle-controller.d.ts +40 -0
- package/dist/src/services/lifecycle-controller.d.ts.map +1 -0
- package/dist/src/services/lifecycle-controller.js +106 -0
- package/dist/src/services/lifecycle-controller.js.map +1 -0
- package/dist/src/services/resource-extractor.d.ts +1 -0
- package/dist/src/services/resource-extractor.d.ts.map +1 -1
- package/dist/src/services/resource-extractor.js +7 -0
- package/dist/src/services/resource-extractor.js.map +1 -1
- package/dist/src/services/session.d.ts +109 -4
- package/dist/src/services/session.d.ts.map +1 -1
- package/dist/src/services/session.js +622 -64
- package/dist/src/services/session.js.map +1 -1
- package/dist/src/services/structured-extractor.d.ts +39 -0
- package/dist/src/services/structured-extractor.d.ts.map +1 -0
- package/dist/src/services/structured-extractor.js +487 -0
- package/dist/src/services/structured-extractor.js.map +1 -0
- package/dist/src/services/tab.d.ts +30 -3
- package/dist/src/services/tab.d.ts.map +1 -1
- package/dist/src/services/tab.js +872 -124
- package/dist/src/services/tab.js.map +1 -1
- package/dist/src/services/tracing.d.ts +7 -0
- package/dist/src/services/tracing.d.ts.map +1 -1
- package/dist/src/services/tracing.js +200 -19
- package/dist/src/services/tracing.js.map +1 -1
- package/dist/src/services/vnc.d.ts.map +1 -1
- package/dist/src/services/vnc.js +5 -3
- package/dist/src/services/vnc.js.map +1 -1
- package/dist/src/services/youtube.js +1 -1
- package/dist/src/services/youtube.js.map +1 -1
- package/dist/src/types.d.ts +71 -1
- package/dist/src/types.d.ts.map +1 -1
- package/dist/src/utils/config.d.ts +79 -3
- package/dist/src/utils/config.d.ts.map +1 -1
- package/dist/src/utils/config.js +145 -3
- package/dist/src/utils/config.js.map +1 -1
- package/dist/src/utils/presets.d.ts.map +1 -1
- package/dist/src/utils/presets.js +3 -1
- package/dist/src/utils/presets.js.map +1 -1
- package/dist/src/utils/proxy-profiles.d.ts +18 -0
- package/dist/src/utils/proxy-profiles.d.ts.map +1 -0
- package/dist/src/utils/proxy-profiles.js +197 -0
- package/dist/src/utils/proxy-profiles.js.map +1 -0
- package/dist/src/utils/sidecar-version.d.ts +12 -0
- package/dist/src/utils/sidecar-version.d.ts.map +1 -0
- package/dist/src/utils/sidecar-version.js +63 -0
- package/dist/src/utils/sidecar-version.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/openclaw.plugin.json +39 -0
- package/package.json +16 -4
- package/plugin.ts +949 -0
|
@@ -1,4 +1,37 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
2
35
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
36
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
37
|
};
|
|
@@ -10,9 +43,12 @@ const express_1 = require("express");
|
|
|
10
43
|
const errors_1 = require("../middleware/errors");
|
|
11
44
|
const logging_1 = require("../middleware/logging");
|
|
12
45
|
const auth_1 = require("../middleware/auth");
|
|
46
|
+
const structured_extractor_1 = require("../services/structured-extractor");
|
|
13
47
|
const config_1 = require("../utils/config");
|
|
14
48
|
const browser_1 = require("../services/browser");
|
|
15
49
|
const context_pool_1 = require("../services/context-pool");
|
|
50
|
+
const lifecycle_controller_1 = require("../services/lifecycle-controller");
|
|
51
|
+
const download_1 = require("../services/download");
|
|
16
52
|
const session_1 = require("../services/session");
|
|
17
53
|
const tab_1 = require("../services/tab");
|
|
18
54
|
const CONFIG = (0, config_1.loadConfig)();
|
|
@@ -26,6 +62,15 @@ const PKG_VERSION = (() => {
|
|
|
26
62
|
return pkg.version;
|
|
27
63
|
})();
|
|
28
64
|
const router = (0, express_1.Router)();
|
|
65
|
+
function getRouteErrorStatus(err) {
|
|
66
|
+
if (typeof err === 'object' && err !== null && 'statusCode' in err && typeof err.statusCode === 'number') {
|
|
67
|
+
return err.statusCode;
|
|
68
|
+
}
|
|
69
|
+
if (typeof err === 'object' && err !== null && 'status' in err && typeof err.status === 'number') {
|
|
70
|
+
return err.status;
|
|
71
|
+
}
|
|
72
|
+
return 500;
|
|
73
|
+
}
|
|
29
74
|
function isLoadState(value) {
|
|
30
75
|
return value === 'load' || value === 'domcontentloaded' || value === 'networkidle';
|
|
31
76
|
}
|
|
@@ -61,32 +106,137 @@ router.get('/', async (_req, res) => {
|
|
|
61
106
|
});
|
|
62
107
|
// POST /tabs/open - Open tab (alias for POST /tabs, OpenClaw format)
|
|
63
108
|
router.post('/tabs/open', async (req, res) => {
|
|
109
|
+
let profileUserId;
|
|
110
|
+
let profileSessionKey;
|
|
111
|
+
let createdSessionProfile = false;
|
|
112
|
+
let createdSessionProfileSignature;
|
|
113
|
+
let createdDefaultSessionProfileClaim = false;
|
|
114
|
+
let releaseSessionProfileCreate;
|
|
64
115
|
try {
|
|
65
|
-
|
|
116
|
+
if (CONFIG.apiKey && !(0, auth_1.isAuthorizedWithApiKey)(req, CONFIG.apiKey)) {
|
|
117
|
+
return res.status(403).json({ error: 'Forbidden' });
|
|
118
|
+
}
|
|
119
|
+
const { url, userId, listItemId = 'default', proxyProfile, proxy, geoMode } = req.body;
|
|
66
120
|
if (!userId) {
|
|
67
121
|
return res.status(400).json({ error: 'userId is required' });
|
|
68
122
|
}
|
|
69
123
|
if (!url) {
|
|
70
124
|
return res.status(400).json({ error: 'url is required' });
|
|
71
125
|
}
|
|
72
|
-
|
|
126
|
+
profileUserId = userId;
|
|
127
|
+
profileSessionKey = listItemId;
|
|
128
|
+
// Establish/check session profile if proxy/geo fields provided
|
|
129
|
+
let resolvedSessionProfile;
|
|
130
|
+
if (proxyProfile || proxy || geoMode) {
|
|
131
|
+
const { resolveSessionProfileInput, getConfiguredServerProxy, loadProxyProfiles } = await Promise.resolve().then(() => __importStar(require('../utils/proxy-profiles')));
|
|
132
|
+
const profileInput = {
|
|
133
|
+
proxy: proxy,
|
|
134
|
+
proxyProfile,
|
|
135
|
+
geoMode: geoMode,
|
|
136
|
+
};
|
|
137
|
+
const deps = {
|
|
138
|
+
serverProxy: getConfiguredServerProxy(CONFIG.proxy),
|
|
139
|
+
proxyProfiles: loadProxyProfiles(CONFIG.proxyProfilesFile),
|
|
140
|
+
};
|
|
141
|
+
try {
|
|
142
|
+
const resolvedProfileBase = resolveSessionProfileInput(profileInput, deps);
|
|
143
|
+
resolvedSessionProfile = { ...resolvedProfileBase, sessionKey: listItemId };
|
|
144
|
+
}
|
|
145
|
+
catch (err) {
|
|
146
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
147
|
+
return res.status(400).json({ error: message });
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
const urlErr = await (0, tab_1.validateNavigationUrl)(url, {
|
|
151
|
+
allowPrivateNetworkTargets: CONFIG.allowPrivateNetworkTargets,
|
|
152
|
+
});
|
|
73
153
|
if (urlErr)
|
|
74
154
|
return res.status(400).json({ error: urlErr });
|
|
75
|
-
const
|
|
76
|
-
|
|
155
|
+
const canonical = (0, session_1.getCanonicalProfile)(userId);
|
|
156
|
+
if (!canonical) {
|
|
157
|
+
(0, logging_1.log)('warn', 'openclaw tab open rejected: no canonical profile', { userId: String(userId) });
|
|
158
|
+
return res.status(409).json({
|
|
159
|
+
error: 'No canonical profile',
|
|
160
|
+
message: 'Cannot open tabs via this endpoint without an established canonical profile. Use core POST /tabs first.',
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
if (resolvedSessionProfile) {
|
|
164
|
+
while (true) {
|
|
165
|
+
await (0, session_1.waitForSessionProfileCreate)(userId, listItemId);
|
|
166
|
+
const existingProfile = (0, session_1.getEstablishedSessionProfile)(userId, listItemId);
|
|
167
|
+
if (existingProfile) {
|
|
168
|
+
if (existingProfile.signature !== resolvedSessionProfile.signature) {
|
|
169
|
+
return res.status(409).json({ error: 'Session profile conflict' });
|
|
170
|
+
}
|
|
171
|
+
break;
|
|
172
|
+
}
|
|
173
|
+
if ((0, session_1.hasDefaultSessionProfileRuntime)(userId, listItemId)) {
|
|
174
|
+
return res.status(409).json({ error: 'Session profile conflict' });
|
|
175
|
+
}
|
|
176
|
+
const mutex = (0, session_1.acquireSessionProfileCreateMutex)(userId, listItemId, resolvedSessionProfile.signature);
|
|
177
|
+
if (mutex.acquired) {
|
|
178
|
+
releaseSessionProfileCreate = mutex.release;
|
|
179
|
+
try {
|
|
180
|
+
(0, session_1.establishSessionProfile)(userId, listItemId, resolvedSessionProfile);
|
|
181
|
+
createdSessionProfile = true;
|
|
182
|
+
createdSessionProfileSignature = resolvedSessionProfile.signature;
|
|
183
|
+
}
|
|
184
|
+
catch (err) {
|
|
185
|
+
releaseSessionProfileCreate(false);
|
|
186
|
+
releaseSessionProfileCreate = undefined;
|
|
187
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
188
|
+
if (message === 'Session profile conflict') {
|
|
189
|
+
return res.status(409).json({ error: 'Session profile conflict' });
|
|
190
|
+
}
|
|
191
|
+
return res.status(400).json({ error: message });
|
|
192
|
+
}
|
|
193
|
+
break;
|
|
194
|
+
}
|
|
195
|
+
await mutex.wait;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
else {
|
|
199
|
+
await (0, session_1.waitForSessionProfileCreate)(userId, listItemId);
|
|
200
|
+
if (!(0, session_1.getEstablishedSessionProfile)(userId, listItemId)) {
|
|
201
|
+
createdDefaultSessionProfileClaim = (0, session_1.claimDefaultSessionProfileRuntime)(userId, listItemId);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
const establishedProfile = (0, session_1.getEstablishedSessionProfile)(userId, listItemId);
|
|
205
|
+
const sessionMapKey = establishedProfile
|
|
206
|
+
? (0, session_1.getSessionMapKey)(userId, listItemId, establishedProfile.signature)
|
|
207
|
+
: (0, session_1.getSessionMapKey)(userId, canonical.resolvedOverrides);
|
|
208
|
+
const session = await (0, session_1.getSession)(userId, canonical.resolvedOverrides, listItemId);
|
|
77
209
|
const totalTabs = (0, session_1.countTotalTabsForSessions)([[sessionMapKey, session]]);
|
|
78
210
|
if (totalTabs >= session_1.MAX_TABS_PER_SESSION) {
|
|
211
|
+
if (createdSessionProfile && createdSessionProfileSignature) {
|
|
212
|
+
await (0, session_1.rollbackSessionProfileRuntime)(userId, listItemId, createdSessionProfileSignature);
|
|
213
|
+
}
|
|
214
|
+
if (createdDefaultSessionProfileClaim) {
|
|
215
|
+
(0, session_1.clearDefaultSessionProfileClaim)(userId, listItemId);
|
|
216
|
+
createdDefaultSessionProfileClaim = false;
|
|
217
|
+
}
|
|
218
|
+
releaseSessionProfileCreate?.(false);
|
|
219
|
+
releaseSessionProfileCreate = undefined;
|
|
79
220
|
return res.status(429).json({ error: 'Maximum tabs per session reached' });
|
|
80
221
|
}
|
|
81
222
|
const group = (0, session_1.getTabGroup)(session, listItemId);
|
|
82
223
|
const page = await session.context.newPage();
|
|
83
224
|
const tabId = node_crypto_1.default.randomUUID();
|
|
84
|
-
const tabState = (0, tab_1.createTabState)(page);
|
|
225
|
+
const tabState = await (0, tab_1.createTabState)(page);
|
|
85
226
|
group.set(tabId, tabState);
|
|
86
227
|
(0, session_1.indexTab)(tabId, sessionMapKey);
|
|
87
|
-
|
|
228
|
+
(0, download_1.registerDownloadListener)(tabId, String(userId), page);
|
|
229
|
+
await (0, tab_1.navigateWithSafetyGuard)(page, url, {
|
|
230
|
+
allowPrivateNetworkTargets: CONFIG.allowPrivateNetworkTargets,
|
|
231
|
+
waitUntil: 'domcontentloaded',
|
|
232
|
+
timeout: 30000,
|
|
233
|
+
});
|
|
88
234
|
tabState.visitedUrls.add(url);
|
|
89
235
|
(0, logging_1.log)('info', 'openclaw tab opened', { reqId: req.reqId, tabId, url: page.url() });
|
|
236
|
+
lifecycle_controller_1.lifecycleController.recordInteractiveActivity();
|
|
237
|
+
releaseSessionProfileCreate?.(true);
|
|
238
|
+
releaseSessionProfileCreate = undefined;
|
|
239
|
+
createdSessionProfile = false;
|
|
90
240
|
return res.json({
|
|
91
241
|
ok: true,
|
|
92
242
|
targetId: tabId,
|
|
@@ -96,9 +246,22 @@ router.post('/tabs/open', async (req, res) => {
|
|
|
96
246
|
});
|
|
97
247
|
}
|
|
98
248
|
catch (err) {
|
|
249
|
+
if (createdSessionProfile && profileUserId && profileSessionKey) {
|
|
250
|
+
if (createdSessionProfileSignature) {
|
|
251
|
+
await (0, session_1.rollbackSessionProfileRuntime)(profileUserId, profileSessionKey, createdSessionProfileSignature);
|
|
252
|
+
}
|
|
253
|
+
else {
|
|
254
|
+
(0, session_1.clearSessionProfile)(profileUserId, profileSessionKey);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
if (createdDefaultSessionProfileClaim && profileUserId && profileSessionKey) {
|
|
258
|
+
(0, session_1.clearDefaultSessionProfileClaim)(profileUserId, profileSessionKey);
|
|
259
|
+
}
|
|
260
|
+
releaseSessionProfileCreate?.(false);
|
|
261
|
+
releaseSessionProfileCreate = undefined;
|
|
99
262
|
const message = err instanceof Error ? err.message : String(err);
|
|
100
263
|
(0, logging_1.log)('error', 'openclaw tab open failed', { reqId: req.reqId, error: message });
|
|
101
|
-
return res.status(
|
|
264
|
+
return res.status(getRouteErrorStatus(err)).json({ error: (0, errors_1.safeError)(err) });
|
|
102
265
|
}
|
|
103
266
|
});
|
|
104
267
|
// POST /start - Start browser (OpenClaw expects this)
|
|
@@ -128,16 +291,16 @@ router.post('/stop', async (req, res) => {
|
|
|
128
291
|
// POST /navigate - Navigate (OpenClaw format with targetId in body)
|
|
129
292
|
router.post('/navigate', async (req, res) => {
|
|
130
293
|
try {
|
|
131
|
-
|
|
294
|
+
if (CONFIG.apiKey && !(0, auth_1.isAuthorizedWithApiKey)(req, CONFIG.apiKey)) {
|
|
295
|
+
return res.status(403).json({ error: 'Forbidden' });
|
|
296
|
+
}
|
|
297
|
+
const { targetId, url, macro, query, userId } = req.body;
|
|
132
298
|
if (!userId) {
|
|
133
299
|
return res.status(400).json({ error: 'userId is required' });
|
|
134
300
|
}
|
|
135
|
-
if (!url) {
|
|
136
|
-
return res.status(400).json({ error: 'url
|
|
301
|
+
if (!url && !macro) {
|
|
302
|
+
return res.status(400).json({ error: 'url or macro required' });
|
|
137
303
|
}
|
|
138
|
-
const urlErr = (0, tab_1.validateUrl)(url);
|
|
139
|
-
if (urlErr)
|
|
140
|
-
return res.status(400).json({ error: urlErr });
|
|
141
304
|
const found = (0, session_1.findTabById)(String(targetId), userId);
|
|
142
305
|
if (!found) {
|
|
143
306
|
return res.status(404).json({ error: 'Tab not found' });
|
|
@@ -145,17 +308,36 @@ router.post('/navigate', async (req, res) => {
|
|
|
145
308
|
const { tabState } = found;
|
|
146
309
|
tabState.toolCalls++;
|
|
147
310
|
const result = await (0, session_1.withUserLimit)(String(userId), CONFIG.maxConcurrentPerUser, () => (0, tab_1.withTimeout)((0, tab_1.withTabLock)(String(targetId), async () => {
|
|
148
|
-
|
|
149
|
-
|
|
311
|
+
let targetUrl = url;
|
|
312
|
+
if (macro) {
|
|
313
|
+
const { expandMacro } = await Promise.resolve().then(() => __importStar(require('../utils/macros')));
|
|
314
|
+
targetUrl = expandMacro(macro, query) || url;
|
|
315
|
+
}
|
|
316
|
+
if (!targetUrl)
|
|
317
|
+
return { status: 400, body: { error: 'url or macro required' } };
|
|
318
|
+
const urlErr = await (0, tab_1.validateNavigationUrl)(targetUrl, {
|
|
319
|
+
allowPrivateNetworkTargets: CONFIG.allowPrivateNetworkTargets,
|
|
320
|
+
});
|
|
321
|
+
if (urlErr)
|
|
322
|
+
return { status: 400, body: { error: urlErr } };
|
|
323
|
+
await (0, tab_1.navigateWithSafetyGuard)(tabState.page, targetUrl, {
|
|
324
|
+
allowPrivateNetworkTargets: CONFIG.allowPrivateNetworkTargets,
|
|
325
|
+
waitUntil: 'domcontentloaded',
|
|
326
|
+
timeout: 30000,
|
|
327
|
+
});
|
|
328
|
+
tabState.visitedUrls.add(targetUrl);
|
|
150
329
|
tabState.refs = await (0, tab_1.buildRefs)(tabState.page);
|
|
151
|
-
return { ok: true, targetId, url: tabState.page.url() };
|
|
330
|
+
return { status: 200, body: { ok: true, targetId, url: tabState.page.url() } };
|
|
152
331
|
}), CONFIG.handlerTimeoutMs, 'openclaw-navigate'));
|
|
153
|
-
|
|
332
|
+
if (result.status !== 200)
|
|
333
|
+
return res.status(result.status).json(result.body);
|
|
334
|
+
lifecycle_controller_1.lifecycleController.recordInteractiveActivity();
|
|
335
|
+
return res.json(result.body);
|
|
154
336
|
}
|
|
155
337
|
catch (err) {
|
|
156
338
|
const message = err instanceof Error ? err.message : String(err);
|
|
157
339
|
(0, logging_1.log)('error', 'openclaw navigate failed', { reqId: req.reqId, error: message });
|
|
158
|
-
return res.status(
|
|
340
|
+
return res.status(getRouteErrorStatus(err)).json({ error: (0, errors_1.safeError)(err) });
|
|
159
341
|
}
|
|
160
342
|
});
|
|
161
343
|
// GET /snapshot - Snapshot (OpenClaw format with query params)
|
|
@@ -171,25 +353,29 @@ router.get('/snapshot', async (req, res) => {
|
|
|
171
353
|
}
|
|
172
354
|
const { tabState } = found;
|
|
173
355
|
tabState.toolCalls++;
|
|
174
|
-
const
|
|
356
|
+
const rawOffset = Number(req.query.offset);
|
|
357
|
+
const offset = Number.isFinite(rawOffset) && rawOffset > 0 ? Math.floor(rawOffset) : 0;
|
|
358
|
+
const raw = await (0, session_1.withUserLimit)(String(userId), CONFIG.maxConcurrentPerUser, () => (0, tab_1.withTimeout)((0, tab_1.snapshotTab)(tabState), CONFIG.handlerTimeoutMs, 'openclaw-snapshot'));
|
|
359
|
+
const payload = (0, tab_1.buildSnapshotPayload)(raw, offset);
|
|
175
360
|
return res.json({
|
|
176
361
|
ok: true,
|
|
177
362
|
format: 'aria',
|
|
178
363
|
targetId,
|
|
179
|
-
|
|
180
|
-
snapshot: result.snapshot,
|
|
181
|
-
refsCount: result.refsCount,
|
|
364
|
+
...payload,
|
|
182
365
|
});
|
|
183
366
|
}
|
|
184
367
|
catch (err) {
|
|
185
368
|
const message = err instanceof Error ? err.message : String(err);
|
|
186
369
|
(0, logging_1.log)('error', 'openclaw snapshot failed', { reqId: req.reqId, error: message });
|
|
187
|
-
return res.status(
|
|
370
|
+
return res.status(getRouteErrorStatus(err)).json({ error: (0, errors_1.safeError)(err) });
|
|
188
371
|
}
|
|
189
372
|
});
|
|
190
373
|
// POST /act - Combined action endpoint (OpenClaw format)
|
|
191
374
|
router.post('/act', async (req, res) => {
|
|
192
375
|
try {
|
|
376
|
+
if (CONFIG.apiKey && !(0, auth_1.isAuthorizedWithApiKey)(req, CONFIG.apiKey)) {
|
|
377
|
+
return res.status(403).json({ error: 'Forbidden' });
|
|
378
|
+
}
|
|
193
379
|
const body = req.body;
|
|
194
380
|
const kind = typeof body.kind === 'string' ? body.kind : undefined;
|
|
195
381
|
const targetId = typeof body.targetId === 'string' ? body.targetId : undefined;
|
|
@@ -235,16 +421,17 @@ router.post('/act', async (req, res) => {
|
|
|
235
421
|
}
|
|
236
422
|
}
|
|
237
423
|
};
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
424
|
+
await (0, tab_1.withBlockedNavigationTracking)(tabState.page, async () => {
|
|
425
|
+
if (ref) {
|
|
426
|
+
const locator = await (0, tab_1.refToLocator)(tabState.page, ref, tabState.refs);
|
|
427
|
+
if (!locator)
|
|
428
|
+
throw new Error(`Unknown ref: ${ref}`);
|
|
429
|
+
await doClick(locator);
|
|
430
|
+
}
|
|
431
|
+
else {
|
|
432
|
+
await doClick(String(selector));
|
|
433
|
+
}
|
|
434
|
+
});
|
|
248
435
|
tabState.refs = await (0, tab_1.buildRefs)(tabState.page);
|
|
249
436
|
return { ok: true, targetId, url: tabState.page.url() };
|
|
250
437
|
}
|
|
@@ -257,20 +444,22 @@ router.post('/act', async (req, res) => {
|
|
|
257
444
|
if (typeof text !== 'string' || text.length === 0) {
|
|
258
445
|
throw new Error('text is required');
|
|
259
446
|
}
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
447
|
+
await (0, tab_1.withBlockedNavigationTracking)(tabState.page, async () => {
|
|
448
|
+
if (ref) {
|
|
449
|
+
const locator = await (0, tab_1.refToLocator)(tabState.page, ref, tabState.refs);
|
|
450
|
+
if (!locator)
|
|
451
|
+
throw new Error(`Unknown ref: ${ref}`);
|
|
452
|
+
await (0, tab_1.smartFill)(locator, tabState.page, text);
|
|
453
|
+
if (submit)
|
|
454
|
+
await tabState.page.keyboard.press('Enter');
|
|
455
|
+
}
|
|
456
|
+
else {
|
|
457
|
+
const locator = tabState.page.locator(String(selector));
|
|
458
|
+
await (0, tab_1.smartFill)(locator, tabState.page, text);
|
|
459
|
+
if (submit)
|
|
460
|
+
await tabState.page.keyboard.press('Enter');
|
|
461
|
+
}
|
|
462
|
+
});
|
|
274
463
|
return { ok: true, targetId };
|
|
275
464
|
}
|
|
276
465
|
case 'select': {
|
|
@@ -282,15 +471,17 @@ router.post('/act', async (req, res) => {
|
|
|
282
471
|
if (typeof value !== 'string') {
|
|
283
472
|
throw new Error('value is required');
|
|
284
473
|
}
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
474
|
+
await (0, tab_1.withBlockedNavigationTracking)(tabState.page, async () => {
|
|
475
|
+
if (ref) {
|
|
476
|
+
const locator = await (0, tab_1.refToLocator)(tabState.page, ref, tabState.refs);
|
|
477
|
+
if (!locator)
|
|
478
|
+
throw new Error(`Unknown ref: ${ref}`);
|
|
479
|
+
await locator.selectOption(value);
|
|
480
|
+
}
|
|
481
|
+
else {
|
|
482
|
+
await tabState.page.locator(String(selector)).selectOption(value);
|
|
483
|
+
}
|
|
484
|
+
});
|
|
294
485
|
return { ok: true, targetId };
|
|
295
486
|
}
|
|
296
487
|
case 'press': {
|
|
@@ -298,7 +489,9 @@ router.post('/act', async (req, res) => {
|
|
|
298
489
|
const { key } = params;
|
|
299
490
|
if (!key)
|
|
300
491
|
throw new Error('key is required');
|
|
301
|
-
await tabState.page
|
|
492
|
+
await (0, tab_1.withBlockedNavigationTracking)(tabState.page, async () => {
|
|
493
|
+
await tabState.page.keyboard.press(key);
|
|
494
|
+
});
|
|
302
495
|
return { ok: true, targetId };
|
|
303
496
|
}
|
|
304
497
|
case 'scroll':
|
|
@@ -307,17 +500,20 @@ router.post('/act', async (req, res) => {
|
|
|
307
500
|
const ref = params.ref;
|
|
308
501
|
const direction = params.direction ?? 'down';
|
|
309
502
|
const amount = params.amount ?? 500;
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
503
|
+
await (0, tab_1.withBlockedNavigationTracking)(tabState.page, async () => {
|
|
504
|
+
if (ref) {
|
|
505
|
+
const locator = await (0, tab_1.refToLocator)(tabState.page, ref, tabState.refs);
|
|
506
|
+
if (!locator)
|
|
507
|
+
throw new Error(`Unknown ref: ${ref}`);
|
|
508
|
+
await locator.scrollIntoViewIfNeeded({ timeout: 5000 });
|
|
509
|
+
}
|
|
510
|
+
else {
|
|
511
|
+
const isHorizontal = direction === 'left' || direction === 'right';
|
|
512
|
+
const delta = direction === 'up' || direction === 'left' ? -amount : amount;
|
|
513
|
+
await tabState.page.mouse.wheel(isHorizontal ? delta : 0, isHorizontal ? 0 : delta);
|
|
514
|
+
}
|
|
515
|
+
await tabState.page.waitForTimeout(300);
|
|
516
|
+
});
|
|
321
517
|
return { ok: true, targetId };
|
|
322
518
|
}
|
|
323
519
|
case 'hover': {
|
|
@@ -325,15 +521,17 @@ router.post('/act', async (req, res) => {
|
|
|
325
521
|
const { ref, selector } = params;
|
|
326
522
|
if (!ref && !selector)
|
|
327
523
|
throw new Error('ref or selector required');
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
524
|
+
await (0, tab_1.withBlockedNavigationTracking)(tabState.page, async () => {
|
|
525
|
+
if (ref) {
|
|
526
|
+
const locator = await (0, tab_1.refToLocator)(tabState.page, ref, tabState.refs);
|
|
527
|
+
if (!locator)
|
|
528
|
+
throw new Error(`Unknown ref: ${ref}`);
|
|
529
|
+
await locator.hover({ timeout: 5000 });
|
|
530
|
+
}
|
|
531
|
+
else {
|
|
532
|
+
await tabState.page.locator(String(selector)).hover({ timeout: 5000 });
|
|
533
|
+
}
|
|
534
|
+
});
|
|
337
535
|
return { ok: true, targetId };
|
|
338
536
|
}
|
|
339
537
|
case 'wait': {
|
|
@@ -341,17 +539,38 @@ router.post('/act', async (req, res) => {
|
|
|
341
539
|
const { timeMs, text } = params;
|
|
342
540
|
const loadStateUnknown = params.loadState;
|
|
343
541
|
const loadState = typeof loadStateUnknown === 'string' && isLoadState(loadStateUnknown) ? loadStateUnknown : undefined;
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
542
|
+
await (0, tab_1.withBlockedNavigationTracking)(tabState.page, async () => {
|
|
543
|
+
try {
|
|
544
|
+
if (timeMs) {
|
|
545
|
+
await tabState.page.waitForTimeout(timeMs);
|
|
546
|
+
}
|
|
547
|
+
else if (text) {
|
|
548
|
+
await tabState.page.waitForSelector(`text=${text}`, { timeout: 30000 });
|
|
549
|
+
}
|
|
550
|
+
else if (loadState) {
|
|
551
|
+
await tabState.page.waitForLoadState(loadState, { timeout: 30000 });
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
catch (err) {
|
|
555
|
+
await (0, tab_1.flushBlockedNavigationError)(tabState.page);
|
|
556
|
+
throw err;
|
|
557
|
+
}
|
|
558
|
+
});
|
|
353
559
|
return { ok: true, targetId, url: tabState.page.url() };
|
|
354
560
|
}
|
|
561
|
+
case 'extractStructured': {
|
|
562
|
+
const params = body;
|
|
563
|
+
if (!params.schema || typeof params.schema !== 'object' || Array.isArray(params.schema)) {
|
|
564
|
+
throw new structured_extractor_1.StructuredExtractSchemaError('schema must be an object');
|
|
565
|
+
}
|
|
566
|
+
const payload = await (0, structured_extractor_1.extractStructuredData)(tabState.page, params.schema);
|
|
567
|
+
return {
|
|
568
|
+
ok: true,
|
|
569
|
+
targetId,
|
|
570
|
+
data: payload.data,
|
|
571
|
+
metadata: payload.metadata,
|
|
572
|
+
};
|
|
573
|
+
}
|
|
355
574
|
case 'close': {
|
|
356
575
|
await (0, tab_1.safePageClose)(tabState.page);
|
|
357
576
|
found.group.delete(String(targetId));
|
|
@@ -365,16 +584,24 @@ router.post('/act', async (req, res) => {
|
|
|
365
584
|
throw new Error(`Unsupported action kind: ${kind}`);
|
|
366
585
|
}
|
|
367
586
|
}), CONFIG.handlerTimeoutMs, 'act'));
|
|
587
|
+
lifecycle_controller_1.lifecycleController.recordInteractiveActivity();
|
|
368
588
|
return res.json(result);
|
|
369
589
|
}
|
|
370
590
|
catch (err) {
|
|
591
|
+
if (err instanceof structured_extractor_1.StructuredExtractSchemaError) {
|
|
592
|
+
return res.status(err.statusCode).json({ error: err.message });
|
|
593
|
+
}
|
|
594
|
+
if (err instanceof structured_extractor_1.StructuredExtractRuntimeError) {
|
|
595
|
+
return res.status(err.statusCode).json({
|
|
596
|
+
error: 'Structured extraction failed',
|
|
597
|
+
fieldPath: err.fieldPath,
|
|
598
|
+
reason: err.reason,
|
|
599
|
+
});
|
|
600
|
+
}
|
|
371
601
|
const message = err instanceof Error ? err.message : String(err);
|
|
372
|
-
const statusCode = err?.statusCode;
|
|
373
602
|
const kindFromBody = typeof req.body.kind === 'string' ? req.body.kind : undefined;
|
|
374
603
|
(0, logging_1.log)('error', 'act failed', { reqId: req.reqId, kind: kindFromBody, error: message });
|
|
375
|
-
|
|
376
|
-
return res.status(400).json({ error: message });
|
|
377
|
-
return res.status(500).json({ error: (0, errors_1.safeError)(err) });
|
|
604
|
+
return res.status(getRouteErrorStatus(err)).json({ error: (0, errors_1.safeError)(err) });
|
|
378
605
|
}
|
|
379
606
|
});
|
|
380
607
|
exports.default = router;
|