zrocclaw 0.0.13 → 0.0.15

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 zroclee
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zrocclaw",
3
- "version": "0.0.13",
3
+ "version": "0.0.15",
4
4
  "description": "个人专属浏览器AI助手命令行工具",
5
5
  "keywords": [
6
6
  "zrocclaw",
@@ -20,7 +20,8 @@
20
20
  "files": [
21
21
  "bin",
22
22
  "server",
23
- "README.md"
23
+ "README.md",
24
+ "LICENSE"
24
25
  ],
25
26
  "scripts": {},
26
27
  "dependencies": {
package/server/server.js CHANGED
@@ -30,7 +30,7 @@ var import_helmet = __toESM(require("helmet"));
30
30
  // src/routes/config.ts
31
31
  var import_express = require("express");
32
32
 
33
- // ../../packages/core/dist/chunk-46XX2E7T.mjs
33
+ // ../../packages/core/dist/chunk-5YB6N3QX.mjs
34
34
  var os = __toESM(require("os"), 1);
35
35
  var path = __toESM(require("path"), 1);
36
36
  var fs = __toESM(require("fs"), 1);
@@ -79,6 +79,10 @@ function getHistoryPath(subPath) {
79
79
  const historyPath = path.join(getConfigPath(), "history");
80
80
  return resolveAndEnsurePath(historyPath, subPath);
81
81
  }
82
+ function getSkillsPath(subPath) {
83
+ const skillsPath = path.join(getWorkspacePath(), "skills");
84
+ return resolveAndEnsurePath(skillsPath, subPath);
85
+ }
82
86
  var SessionModel = class _SessionModel {
83
87
  static instance;
84
88
  filePath;
@@ -167,27 +171,37 @@ var session_default = sessionModel;
167
171
  // src/routes/config.ts
168
172
  var import_promises = __toESM(require("fs/promises"));
169
173
  var import_path2 = __toESM(require("path"));
174
+ var import_crypto = __toESM(require("crypto"));
170
175
  var router = (0, import_express.Router)();
171
176
  router.get("/", (req, res) => {
172
177
  res.json({ configPath: getConfigPath() });
173
178
  });
174
179
  var configDir = getConfigPath();
175
- var configFilePath = import_path2.default.join(configDir, "model.json");
180
+ var configModelPath = import_path2.default.join(configDir, "model.json");
181
+ async function readModelConfig() {
182
+ try {
183
+ await import_promises.default.access(configModelPath);
184
+ const data = await import_promises.default.readFile(configModelPath, "utf-8");
185
+ return JSON.parse(data);
186
+ } catch (error) {
187
+ if (error.code === "ENOENT") {
188
+ return {};
189
+ }
190
+ throw error;
191
+ }
192
+ }
193
+ async function writeModelConfig(config) {
194
+ await import_promises.default.mkdir(configDir, { recursive: true });
195
+ await import_promises.default.writeFile(configModelPath, JSON.stringify(config, null, 2), "utf-8");
196
+ }
176
197
  router.get("/model", async (req, res) => {
177
198
  try {
178
- try {
179
- await import_promises.default.access(configFilePath);
180
- const data = await import_promises.default.readFile(configFilePath, "utf-8");
181
- res.json(JSON.parse(data));
182
- } catch (error) {
183
- if (error.code === "ENOENT") {
184
- await import_promises.default.mkdir(configDir, { recursive: true });
185
- await import_promises.default.writeFile(configFilePath, "{}", "utf-8");
186
- res.json({});
187
- } else {
188
- throw error;
189
- }
199
+ const config = await readModelConfig();
200
+ if (Object.keys(config).length === 0) {
201
+ await writeModelConfig({});
202
+ return res.json({});
190
203
  }
204
+ res.json(config);
191
205
  } catch (error) {
192
206
  console.error("\u83B7\u53D6\u6A21\u578B\u5931\u8D25:", error);
193
207
  res.status(500).json({ error: "\u83B7\u53D6\u6A21\u578B\u5931\u8D25" });
@@ -195,22 +209,237 @@ router.get("/model", async (req, res) => {
195
209
  });
196
210
  router.post("/model", async (req, res) => {
197
211
  try {
198
- await import_promises.default.mkdir(configDir, { recursive: true });
199
- let currentConfig = {};
212
+ const { modelName, provider, apiKey, baseURL } = req.body;
213
+ const newModel = {
214
+ id: import_crypto.default.randomUUID(),
215
+ modelName,
216
+ provider,
217
+ apiKey,
218
+ baseURL
219
+ };
220
+ const config = await readModelConfig();
221
+ if (!Array.isArray(config.models)) {
222
+ config.models = [];
223
+ }
224
+ config.models.unshift(newModel);
225
+ if (!config.defaultModel) {
226
+ config.defaultModel = newModel;
227
+ }
228
+ await writeModelConfig(config);
229
+ res.json(newModel);
230
+ } catch (error) {
231
+ console.error("\u65B0\u589E\u6A21\u578B\u5931\u8D25:", error);
232
+ res.status(500).json({ error: "\u65B0\u589E\u6A21\u578B\u5931\u8D25" });
233
+ }
234
+ });
235
+ router.post("/model/edit", async (req, res) => {
236
+ try {
237
+ const modelData = req.body;
238
+ if (!modelData || !modelData.id) {
239
+ return res.status(400).json({ error: "\u7F3A\u5C11\u6A21\u578BID" });
240
+ }
241
+ const config = await readModelConfig();
242
+ if (!Array.isArray(config.models)) {
243
+ config.models = [];
244
+ }
245
+ const index = config.models.findIndex((m) => m.id === modelData.id);
246
+ if (index === -1) {
247
+ return res.status(404).json({ error: "\u6A21\u578B\u4E0D\u5B58\u5728" });
248
+ }
249
+ config.models[index] = { ...config.models[index], ...modelData };
250
+ if (config.defaultModel && config.defaultModel.id === modelData.id) {
251
+ config.defaultModel = config.models[index];
252
+ }
253
+ await writeModelConfig(config);
254
+ res.json(config.models[index]);
255
+ } catch (error) {
256
+ console.error("\u7F16\u8F91\u6A21\u578B\u5931\u8D25:", error);
257
+ res.status(500).json({ error: "\u7F16\u8F91\u6A21\u578B\u5931\u8D25" });
258
+ }
259
+ });
260
+ router.post("/model/delete", async (req, res) => {
261
+ try {
262
+ const { id } = req.body;
263
+ if (!id) {
264
+ return res.status(400).json({ error: "\u7F3A\u5C11\u6A21\u578BID" });
265
+ }
266
+ const config = await readModelConfig();
267
+ if (!Array.isArray(config.models)) {
268
+ config.models = [];
269
+ }
270
+ const initialLength = config.models.length;
271
+ config.models = config.models.filter((m) => m.id !== id);
272
+ if (config.models.length === initialLength) {
273
+ return res.status(404).json({ error: "\u6A21\u578B\u4E0D\u5B58\u5728" });
274
+ }
275
+ if (config.defaultModel && config.defaultModel.id === id) {
276
+ config.defaultModel = config.models.length > 0 ? config.models[0] : null;
277
+ }
278
+ await writeModelConfig(config);
279
+ res.json({ success: true, message: "\u5220\u9664\u6210\u529F" });
280
+ } catch (error) {
281
+ console.error("\u5220\u9664\u6A21\u578B\u5931\u8D25:", error);
282
+ res.status(500).json({ error: "\u5220\u9664\u6A21\u578B\u5931\u8D25" });
283
+ }
284
+ });
285
+ router.post("/model/default", async (req, res) => {
286
+ try {
287
+ const { id } = req.body;
288
+ if (!id) {
289
+ return res.status(400).json({ error: "\u7F3A\u5C11\u6A21\u578BID" });
290
+ }
291
+ const config = await readModelConfig();
292
+ if (!Array.isArray(config.models)) {
293
+ config.models = [];
294
+ }
295
+ const model = config.models.find((m) => m.id === id);
296
+ if (!model) {
297
+ return res.status(404).json({ error: "\u6A21\u578B\u4E0D\u5B58\u5728" });
298
+ }
299
+ config.defaultModel = model;
300
+ await writeModelConfig(config);
301
+ res.json({ success: true, defaultModel: config.defaultModel });
302
+ } catch (error) {
303
+ console.error("\u8BBE\u7F6E\u9ED8\u8BA4\u6A21\u578B\u5931\u8D25:", error);
304
+ res.status(500).json({ error: "\u8BBE\u7F6E\u9ED8\u8BA4\u6A21\u578B\u5931\u8D25" });
305
+ }
306
+ });
307
+ var workspaceDir = getWorkspacePath();
308
+ var workspaceSkillsPath = import_path2.default.join(workspaceDir, "SKILLS.json");
309
+ var skillsDir = getSkillsPath();
310
+ async function readSkillsConfig() {
311
+ try {
312
+ await import_promises.default.access(workspaceSkillsPath);
313
+ const data = await import_promises.default.readFile(workspaceSkillsPath, "utf-8");
314
+ return JSON.parse(data);
315
+ } catch (error) {
316
+ if (error.code === "ENOENT") {
317
+ return [];
318
+ }
319
+ throw error;
320
+ }
321
+ }
322
+ async function writeSkillsConfig(config) {
323
+ await import_promises.default.mkdir(workspaceDir, { recursive: true });
324
+ await import_promises.default.writeFile(workspaceSkillsPath, JSON.stringify(config, null, 2), "utf-8");
325
+ }
326
+ router.get("/skills", async (req, res) => {
327
+ try {
200
328
  try {
201
- const data = await import_promises.default.readFile(configFilePath, "utf-8");
202
- currentConfig = JSON.parse(data);
203
- } catch (error) {
204
- if (error.code !== "ENOENT") {
205
- throw error;
329
+ await import_promises.default.access(workspaceSkillsPath);
330
+ } catch (e) {
331
+ if (e.code === "ENOENT") {
332
+ await writeSkillsConfig([]);
333
+ return res.json([]);
206
334
  }
207
335
  }
208
- const newConfig = { ...currentConfig, ...req.body };
209
- await import_promises.default.writeFile(configFilePath, JSON.stringify(newConfig, null, 2), "utf-8");
210
- res.json(newConfig);
336
+ const config = await readSkillsConfig();
337
+ res.json(config);
211
338
  } catch (error) {
212
- console.error("\u4FEE\u6539\u6A21\u578B\u5931\u8D25:", error);
213
- res.status(500).json({ error: "\u4FEE\u6539\u6A21\u578B\u5931\u8D25" });
339
+ console.error("\u83B7\u53D6\u6280\u80FD\u5217\u8868\u5931\u8D25:", error);
340
+ res.status(500).json({ error: "\u83B7\u53D6\u6280\u80FD\u5217\u8868\u5931\u8D25" });
341
+ }
342
+ });
343
+ router.post("/skills", async (req, res) => {
344
+ try {
345
+ const { name, summary, content } = req.body;
346
+ const id = import_crypto.default.randomUUID();
347
+ const newSkill = {
348
+ id,
349
+ name,
350
+ summary,
351
+ path: `./skills/${id}.md`
352
+ };
353
+ const skills = await readSkillsConfig();
354
+ if (!Array.isArray(skills)) {
355
+ throw new Error("skills.json \u683C\u5F0F\u9519\u8BEF");
356
+ }
357
+ skills.unshift(newSkill);
358
+ await writeSkillsConfig(skills);
359
+ await import_promises.default.mkdir(skillsDir, { recursive: true });
360
+ const mdPath = import_path2.default.join(skillsDir, `${id}.md`);
361
+ await import_promises.default.writeFile(mdPath, content || "", "utf-8");
362
+ res.json(newSkill);
363
+ } catch (error) {
364
+ console.error("\u65B0\u589E\u6280\u80FD\u5931\u8D25:", error);
365
+ res.status(500).json({ error: "\u65B0\u589E\u6280\u80FD\u5931\u8D25" });
366
+ }
367
+ });
368
+ router.post("/skills/edit", async (req, res) => {
369
+ try {
370
+ const { id, name, summary, content } = req.body;
371
+ if (!id) {
372
+ return res.status(400).json({ error: "\u7F3A\u5C11\u6280\u80FDID" });
373
+ }
374
+ const skills = await readSkillsConfig();
375
+ if (!Array.isArray(skills)) {
376
+ throw new Error("skills.json \u683C\u5F0F\u9519\u8BEF");
377
+ }
378
+ const index = skills.findIndex((s) => s.id === id);
379
+ if (index === -1) {
380
+ return res.status(404).json({ error: "\u6280\u80FD\u4E0D\u5B58\u5728" });
381
+ }
382
+ skills[index] = { ...skills[index], name, summary };
383
+ await writeSkillsConfig(skills);
384
+ await import_promises.default.mkdir(skillsDir, { recursive: true });
385
+ const mdPath = import_path2.default.join(skillsDir, `${id}.md`);
386
+ await import_promises.default.writeFile(mdPath, content || "", "utf-8");
387
+ res.json(skills[index]);
388
+ } catch (error) {
389
+ console.error("\u7F16\u8F91\u6280\u80FD\u5931\u8D25:", error);
390
+ res.status(500).json({ error: "\u7F16\u8F91\u6280\u80FD\u5931\u8D25" });
391
+ }
392
+ });
393
+ router.post("/skills/delete", async (req, res) => {
394
+ try {
395
+ const { id } = req.body;
396
+ if (!id) {
397
+ return res.status(400).json({ error: "\u7F3A\u5C11\u6280\u80FDID" });
398
+ }
399
+ let skills = await readSkillsConfig();
400
+ if (!Array.isArray(skills)) {
401
+ throw new Error("skills.json \u683C\u5F0F\u9519\u8BEF");
402
+ }
403
+ const initialLength = skills.length;
404
+ skills = skills.filter((s) => s.id !== id);
405
+ if (skills.length === initialLength) {
406
+ return res.status(404).json({ error: "\u6280\u80FD\u4E0D\u5B58\u5728" });
407
+ }
408
+ await writeSkillsConfig(skills);
409
+ const mdPath = import_path2.default.join(skillsDir, `${id}.md`);
410
+ try {
411
+ await import_promises.default.unlink(mdPath);
412
+ } catch (e) {
413
+ if (e.code !== "ENOENT") {
414
+ console.error("\u5220\u9664\u6280\u80FD\u6587\u4EF6\u5931\u8D25:", e);
415
+ }
416
+ }
417
+ res.json({ success: true, message: "\u5220\u9664\u6210\u529F" });
418
+ } catch (error) {
419
+ console.error("\u5220\u9664\u6280\u80FD\u5931\u8D25:", error);
420
+ res.status(500).json({ error: "\u5220\u9664\u6280\u80FD\u5931\u8D25" });
421
+ }
422
+ });
423
+ router.post("/skills/detail", async (req, res) => {
424
+ try {
425
+ const { id } = req.body;
426
+ if (!id) {
427
+ return res.status(400).json({ error: "\u7F3A\u5C11\u6280\u80FDID" });
428
+ }
429
+ const mdPath = import_path2.default.join(skillsDir, `${id}.md`);
430
+ try {
431
+ await import_promises.default.access(mdPath);
432
+ const content = await import_promises.default.readFile(mdPath, "utf-8");
433
+ res.json({ content });
434
+ } catch (e) {
435
+ if (e.code === "ENOENT") {
436
+ return res.status(404).json({ error: "\u6280\u80FD\u6587\u4EF6\u4E0D\u5B58\u5728" });
437
+ }
438
+ throw e;
439
+ }
440
+ } catch (error) {
441
+ console.error("\u83B7\u53D6\u6280\u80FD\u8BE6\u60C5\u5931\u8D25:", error);
442
+ res.status(500).json({ error: "\u83B7\u53D6\u6280\u80FD\u8BE6\u60C5\u5931\u8D25" });
214
443
  }
215
444
  });
216
445
  var config_default = router;
@@ -218,13 +447,13 @@ var config_default = router;
218
447
  // src/routes/chat.ts
219
448
  var import_express2 = require("express");
220
449
 
221
- // ../../packages/core/dist/chunk-DVDBVYRV.mjs
450
+ // ../../packages/core/dist/chunk-CY5XCIYW.mjs
222
451
  var import_playwright = require("playwright");
223
452
  var PlaywrightManager = class _PlaywrightManager {
224
453
  static instance;
225
454
  browser = null;
226
455
  context = null;
227
- page = null;
456
+ activePage = null;
228
457
  constructor() {
229
458
  }
230
459
  static getInstance() {
@@ -234,13 +463,38 @@ var PlaywrightManager = class _PlaywrightManager {
234
463
  return _PlaywrightManager.instance;
235
464
  }
236
465
  async getPage() {
237
- if (!this.page || this.page.isClosed()) {
466
+ if (!this.activePage || this.activePage.isClosed()) {
238
467
  await this.init();
239
468
  }
240
- if (!this.page) {
469
+ if (!this.activePage) {
241
470
  throw new Error("Failed to initialize Playwright page.");
242
471
  }
243
- return this.page;
472
+ return this.activePage;
473
+ }
474
+ async getPageMetadata() {
475
+ if (!this.context) return { tabs: [], activeTabIndex: -1 };
476
+ const pages = this.context.pages();
477
+ const tabs = await Promise.all(
478
+ pages.map(async (p, i) => {
479
+ try {
480
+ return { index: i, url: p.url(), title: await p.title() };
481
+ } catch {
482
+ return { index: i, url: p.url(), title: "Unknown" };
483
+ }
484
+ })
485
+ );
486
+ const activeTabIndex = pages.findIndex((p) => p === this.activePage);
487
+ return { tabs, activeTabIndex };
488
+ }
489
+ async switchPage(index) {
490
+ if (!this.context) throw new Error("Context not initialized");
491
+ const pages = this.context.pages();
492
+ if (index >= 0 && index < pages.length) {
493
+ this.activePage = pages[index];
494
+ await this.activePage.bringToFront();
495
+ return this.activePage;
496
+ }
497
+ throw new Error(`Invalid page index: ${index}`);
244
498
  }
245
499
  async start() {
246
500
  await this.init();
@@ -254,7 +508,7 @@ var PlaywrightManager = class _PlaywrightManager {
254
508
  this.browser.on("disconnected", () => {
255
509
  this.browser = null;
256
510
  this.context = null;
257
- this.page = null;
511
+ this.activePage = null;
258
512
  });
259
513
  }
260
514
  if (!this.context) {
@@ -262,16 +516,27 @@ var PlaywrightManager = class _PlaywrightManager {
262
516
  viewport: { width: 1280, height: 800 },
263
517
  userAgent: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
264
518
  });
519
+ this.context.on("page", (page) => {
520
+ this.activePage = page;
521
+ page.on("close", () => {
522
+ if (this.activePage === page) {
523
+ const pages = this.context?.pages() || [];
524
+ this.activePage = pages.length > 0 ? pages[pages.length - 1] : null;
525
+ }
526
+ });
527
+ });
265
528
  this.context.on("close", () => {
266
529
  this.context = null;
267
- this.page = null;
530
+ this.activePage = null;
268
531
  });
269
532
  }
270
- if (!this.page || this.page.isClosed()) {
271
- this.page = await this.context.newPage();
272
- this.page.on("close", () => {
273
- this.page = null;
274
- });
533
+ if (!this.activePage || this.activePage.isClosed()) {
534
+ const pages = this.context.pages();
535
+ if (pages.length > 0) {
536
+ this.activePage = pages[pages.length - 1];
537
+ } else {
538
+ this.activePage = await this.context.newPage();
539
+ }
275
540
  }
276
541
  }
277
542
  async close() {
@@ -279,7 +544,7 @@ var PlaywrightManager = class _PlaywrightManager {
279
544
  await this.browser.close();
280
545
  this.browser = null;
281
546
  this.context = null;
282
- this.page = null;
547
+ this.activePage = null;
283
548
  }
284
549
  }
285
550
  };
@@ -371,13 +636,20 @@ var PlaywrightExecutor = class {
371
636
  case "wait":
372
637
  await this.page.waitForTimeout(payload?.delay || 1e3);
373
638
  break;
639
+ case "switchTab":
640
+ if (payload?.tabIndex !== void 0) {
641
+ this.page = await playwrightManager.switchPage(payload.tabIndex);
642
+ } else {
643
+ throw new Error("switchTab action requires payload.tabIndex");
644
+ }
645
+ break;
374
646
  default:
375
647
  throw new Error(`Unsupported action type: ${type}`);
376
648
  }
377
649
  }
378
650
  };
379
651
 
380
- // ../../packages/core/dist/chunk-OSSFB5D4.mjs
652
+ // ../../packages/core/dist/chunk-JNJYWTIE.mjs
381
653
  var import_openai = require("@langchain/openai");
382
654
  var import_langchain = require("langchain");
383
655
  var import_messages = require("@langchain/core/messages");
@@ -632,9 +904,10 @@ var listFilesTool = (0, import_tools2.tool)(
632
904
  }
633
905
  );
634
906
  var extractPageStateTool = (0, import_tools3.tool)(
635
- async () => {
907
+ async (input) => {
908
+ const includeText = input?.includeText === "true";
636
909
  const p = await playwrightManager.getPage();
637
- const elementsMetadata = await p.evaluate(() => {
910
+ const evaluateResult = await p.evaluate((includeTextArg) => {
638
911
  document.querySelectorAll(".ai-label-container").forEach((el) => el.remove());
639
912
  document.querySelectorAll("[idu-mark-id]").forEach((el) => el.removeAttribute("idu-mark-id"));
640
913
  const selectors = [
@@ -696,18 +969,51 @@ var extractPageStateTool = (0, import_tools3.tool)(
696
969
  });
697
970
  }
698
971
  });
699
- return metadata;
700
- });
972
+ let viewportText = void 0;
973
+ if (includeTextArg) {
974
+ const walker = document.createTreeWalker(
975
+ document.body,
976
+ NodeFilter.SHOW_TEXT,
977
+ null
978
+ );
979
+ let node;
980
+ const textParts = [];
981
+ while (node = walker.nextNode()) {
982
+ const parent = node.parentElement;
983
+ const text = node.nodeValue?.trim();
984
+ if (parent && text) {
985
+ const tagName = parent.tagName.toLowerCase();
986
+ if (tagName !== "script" && tagName !== "style" && tagName !== "noscript") {
987
+ if (isVisible(parent)) {
988
+ const rect = parent.getBoundingClientRect();
989
+ if (isInViewport(rect)) {
990
+ textParts.push(text);
991
+ }
992
+ }
993
+ }
994
+ }
995
+ }
996
+ viewportText = Array.from(new Set(textParts)).join("\n");
997
+ }
998
+ return { metadata, viewportText };
999
+ }, includeText);
701
1000
  const title = await p.title();
1001
+ const pageMetadata = await playwrightManager.getPageMetadata();
702
1002
  return {
703
1003
  url: p.url(),
704
1004
  title,
705
- elements: elementsMetadata
1005
+ tabs: pageMetadata.tabs,
1006
+ activeTabIndex: pageMetadata.activeTabIndex,
1007
+ elements: evaluateResult.metadata,
1008
+ ...includeText && evaluateResult.viewportText ? { text: evaluateResult.viewportText } : {}
706
1009
  };
707
1010
  },
708
1011
  {
709
1012
  name: "extract_page_state",
710
- description: "\u6293\u53D6\u5F53\u524D\u9875\u9762\u7684\u5173\u952E\u4EA4\u4E92\u5143\u7D20\uFF0C\u5E76\u8F93\u51FA\u5E26\u7F16\u53F7\u7684\u5143\u7D20\u6458\u8981\u4E0E\u4E0A\u4E0B\u6587\u6458\u8981"
1013
+ description: "\u6293\u53D6\u5F53\u524D\u9875\u9762\u7684\u5173\u952E\u4EA4\u4E92\u5143\u7D20\uFF0C\u5E76\u8F93\u51FA\u5E26\u7F16\u53F7\u7684\u5143\u7D20\u6458\u8981\u4E0E\u4E0A\u4E0B\u6587\u6458\u8981\uFF0C\u540C\u65F6\u63D0\u4F9B\u5F53\u524D\u6253\u5F00\u7684\u6240\u6709\u6807\u7B7E\u9875(tabs)\u4FE1\u606F\u548C\u5F53\u524D\u6FC0\u6D3B\u7684\u6807\u7B7E\u9875\u7D22\u5F15(activeTabIndex)\u3002\u53EF\u901A\u8FC7 includeText \u53C2\u6570\u989D\u5916\u83B7\u53D6\u89C6\u53E3\u5185\u7684\u7EAF\u6587\u672C\u4FE1\u606F\u3002",
1014
+ schema: z3.object({
1015
+ includeText: z3.string().optional().default("false").describe('\u662F\u5426\u83B7\u53D6\u5F53\u524D\u89C6\u53E3\u5185\u7684\u7EAF\u6587\u672C\u4FE1\u606F\uFF0C\u4F20\u5165\u5B57\u7B26\u4E32 "true" \u6216 "false"')
1016
+ })
711
1017
  }
712
1018
  );
713
1019
  var executePlaywrightActionsTool = (0, import_tools3.tool)(
@@ -735,7 +1041,8 @@ var executePlaywrightActionsTool = (0, import_tools3.tool)(
735
1041
  "check",
736
1042
  "selectOption",
737
1043
  "wait",
738
- "goto"
1044
+ "goto",
1045
+ "switchTab"
739
1046
  ]).describe("\u52A8\u4F5C\u7C7B\u578B"),
740
1047
  target: z3.object({
741
1048
  type: z3.enum(["markId", "css", "role", "text"]),
@@ -747,7 +1054,8 @@ var executePlaywrightActionsTool = (0, import_tools3.tool)(
747
1054
  key: z3.string().optional(),
748
1055
  delay: z3.number().optional(),
749
1056
  options: z3.array(z3.string()).optional(),
750
- url: z3.string().optional()
1057
+ url: z3.string().optional(),
1058
+ tabIndex: z3.number().optional().describe("\u7528\u4E8E switchTab \u52A8\u4F5C\uFF0C\u6307\u5B9A\u8981\u5207\u6362\u7684\u6807\u7B7E\u9875\u7D22\u5F15")
751
1059
  }).optional().describe("\u52A8\u4F5C\u53C2\u6570")
752
1060
  })
753
1061
  ).describe("\u52A8\u4F5C\u5217\u8868")
@@ -770,10 +1078,10 @@ var PLAYWRIGHT_PROMPT = `
770
1078
  * **\u4F55\u65F6\u4F7F\u7528**: \u6839\u636E\u7528\u6237\u6307\u4EE4\u751F\u6210\u64CD\u4F5C\u8BA1\u5212\u5E76\u8C03\u7528\u6B64\u5DE5\u5177\u3002
771
1079
 
772
1080
  2. **extract_page_state**:
773
- * **\u7528\u9014**: \u83B7\u53D6\u5F53\u524D\u9875\u9762\u4E0A\u4E0B\u6587\u6458\u8981\uFF0C\u5E76\u5728\u9875\u9762\u4E0A\u6807\u8BB0\u53EF\u4EA4\u4E92\u5143\u7D20\uFF08Set-of-Mark\uFF09\u3002
774
- * **\u8F93\u5165**: \u65E0\u3002
775
- * **\u8F93\u51FA**: \u5305\u542B\u9875\u9762 URL\u3001Title \u4EE5\u53CA\u5E26\u6709 ID \u6807\u8BB0\u7684\u53EF\u4EA4\u4E92\u5143\u7D20\u5217\u8868\u3002
776
- * **\u4F55\u65F6\u4F7F\u7528**: \u5728\u9700\u8981\u7406\u89E3\u9875\u9762\u7ED3\u6784\u6216\u7CBE\u786E\u5B9A\u4F4D\u5143\u7D20\u65F6\u4F7F\u7528\u3002\u8FD4\u56DE\u7684\u5143\u7D20 ID \u53EF\u7528\u4E8E\u751F\u6210\u7CBE\u786E\u7684 CSS \u9009\u62E9\u5668\uFF08\u5982 \`[idu-mark-id="123"]\`\uFF09\u3002
1081
+ * **\u7528\u9014**: \u83B7\u53D6\u5F53\u524D\u9875\u9762\u4E0A\u4E0B\u6587\u6458\u8981\uFF0C\u5E76\u5728\u9875\u9762\u4E0A\u6807\u8BB0\u53EF\u4EA4\u4E92\u5143\u7D20\uFF08Set-of-Mark\uFF09\u3002\u540C\u65F6\u63D0\u4F9B\u5F53\u524D\u6253\u5F00\u7684\u6240\u6709\u6807\u7B7E\u9875(tabs)\u4FE1\u606F\u548C\u5F53\u524D\u6FC0\u6D3B\u7684\u6807\u7B7E\u9875\u7D22\u5F15(activeTabIndex)\u3002
1082
+ * **\u8F93\u5165**: \u53EF\u9009\u53C2\u6570 \`includeText\` (\u5B57\u7B26\u4E32\uFF0C"true" \u6216 "false")\uFF0C\u7528\u4E8E\u989D\u5916\u83B7\u53D6\u5F53\u524D\u89C6\u53E3\u5185\u7684\u7EAF\u6587\u672C\u4FE1\u606F\u3002
1083
+ * **\u8F93\u51FA**: \u5305\u542B\u9875\u9762 URL\u3001Title\u3001\u6240\u6709\u6253\u5F00\u7684\u6807\u7B7E\u9875\u5217\u8868\uFF08tabs\uFF0C\u5305\u542B\u7D22\u5F15\u3001URL\u548CTitle\uFF09\u3001\u5F53\u524D\u6FC0\u6D3B\u6807\u7B7E\u9875\u7D22\u5F15\uFF08activeTabIndex\uFF09\uFF0C\u4EE5\u53CA\u5E26\u6709 ID \u6807\u8BB0\u7684\u53EF\u4EA4\u4E92\u5143\u7D20\u5217\u8868\uFF08\u5F00\u542F \`includeText\` \u65F6\u8FD8\u4F1A\u5305\u542B\u89C6\u53E3\u5185\u7684\u7EAF\u6587\u672C\u4FE1\u606F\uFF09\u3002
1084
+ * **\u4F55\u65F6\u4F7F\u7528**: \u5728\u9700\u8981\u7406\u89E3\u9875\u9762\u7ED3\u6784\u3001\u7CBE\u786E\u5B9A\u4F4D\u5143\u7D20\uFF0C\u6216\u4E86\u89E3\u5F53\u524D\u6253\u5F00\u4E86\u54EA\u4E9B\u6807\u7B7E\u9875\u65F6\u4F7F\u7528\u3002\u8FD4\u56DE\u7684\u5143\u7D20 ID \u53EF\u7528\u4E8E\u751F\u6210\u7CBE\u786E\u7684 CSS \u9009\u62E9\u5668\uFF08\u5982 \`[idu-mark-id="123"]\`\uFF09\u3002\u5982\u679C\u53D1\u73B0\u9875\u9762\u6253\u5F00\u4E86\u65B0\u7A97\u53E3\uFF0C\u53EF\u7ED3\u5408 \`switchTab\` \u52A8\u4F5C\u5207\u6362\u5230\u5BF9\u5E94\u6807\u7B7E\u9875\u3002**\u6CE8\u610F\uFF1A\u4E00\u822C\u7EAF\u64CD\u4F5C\u9875\u9762\u65F6\u4E0D\u9700\u8981\u6253\u5F00 \`includeText\` \u5F00\u5173\uFF0C\u4EE5\u514D\u6D6A\u8D39\u5B9D\u8D35\u7684 Token\u3002\u53EA\u6709\u5728\u786E\u5B9E\u9700\u8981\u83B7\u53D6\u9875\u9762\u66F4\u591A\u6587\u672C\u4FE1\u606F\u8FDB\u884C\u5185\u5BB9\u7406\u89E3\u65F6\uFF0C\u624D\u5C06 \`includeText\` \u8BBE\u4E3A true\u3002**
777
1085
 
778
1086
  ### Utility
779
1087
  3. **get_current_time**:
@@ -800,9 +1108,13 @@ var PLAYWRIGHT_PROMPT = `
800
1108
  * **\u8BFB\u53D6**: \u7CFB\u7EDF\u4F1A\u5728\u6BCF\u6B21\u5BF9\u8BDD\u5F00\u59CB\u65F6\u8BFB\u53D6\u6B64\u6587\u4EF6\uFF08\u6216\u4F60\u53EF\u4EE5\u4E3B\u52A8\u8BFB\u53D6\uFF09\uFF0C\u8BF7\u9075\u5FAA\u5176\u4E2D\u7684\u6307\u793A\u3002
801
1109
  * **\u5199\u5165**: \u5F53\u7528\u6237\u901A\u8FC7\u6307\u4EE4\u544A\u77E5\u65B0\u7684\u4E60\u60EF\u3001\u89C4\u5B9A\uFF0C\u6216\u4F60\u5B66\u4F1A\u4E86\u65B0\u6280\u80FD\u65F6\uFF0C\u8BF7\u52A1\u5FC5\u66F4\u65B0\u6B64\u6587\u4EF6\u3002
802
1110
 
803
- 2. **skills/ (\u6280\u80FD\u76EE\u5F55)**:
1111
+ 2. **SKILLS.json (\u7528\u6237\u914D\u7F6E\u6280\u80FD\u5217\u8868)**:
1112
+ * \u4F4D\u4E8E\u6839\u76EE\u5F55\u4E0B\u7684 \`SKILLS.json\` \u6587\u4EF6\u5B58\u653E\u7528\u6237\u624B\u52A8\u914D\u7F6E\u7684\u6280\u80FD\u5217\u8868\u3002
1113
+ * \u4F60\u53EF\u4EE5\u901A\u8FC7\u8BFB\u53D6\u8BE5\u6587\u4EF6\u83B7\u53D6\u6280\u80FD\u5217\u8868\uFF08\u5305\u542B ID\u3001\u540D\u79F0\u3001\u6458\u8981\u548C\u8DEF\u5F84\uFF09\u3002
1114
+
1115
+ 3. **skills/ (\u6280\u80FD\u76EE\u5F55)**:
804
1116
  * \u4F4D\u4E8E \`skills/\` \u6587\u4EF6\u5939\u4E0B\u3002
805
- * **\u7528\u9014**: \u5B58\u653E\u5177\u4F53\u7684\u6280\u80FD\u6587\u6863\u6216\u590D\u6742\u4EFB\u52A1\u7684\u64CD\u4F5C\u6307\u5357\u3002
1117
+ * **\u7528\u9014**: \u5B58\u653E\u5177\u4F53\u7684\u6280\u80FD\u6587\u6863\u6216\u590D\u6742\u4EFB\u52A1\u7684\u64CD\u4F5C\u6307\u5357\u3002\u4F60\u53EF\u4EE5\u6839\u636E \`SKILLS.json\` \u4E2D\u7684 ID\uFF0C\u8BFB\u53D6\u5BF9\u5E94 \`skills/\${id}.md\` \u62FF\u5230\u6280\u80FD\u7684\u6B63\u6587\u5185\u5BB9\u3002
806
1118
  * **\u7EF4\u62A4**: \u5F53\u4F60\u4E60\u5F97\u4E00\u4E2A\u65B0\u7684\u590D\u6742\u64CD\u4F5C\u6D41\u7A0B\u65F6\uFF0C\u5E94\u5C06\u5176\u8BE6\u7EC6\u6B65\u9AA4\u6574\u7406\u4E3A\u4E00\u4E2A Markdown \u6587\u4EF6\u4FDD\u5B58\u5728 \`skills/\` \u76EE\u5F55\u4E0B\uFF0C\u5E76\u5728 \`AGENTS.md\` \u4E2D\u6DFB\u52A0\u8BE5\u6280\u80FD\u7684\u6458\u8981\u548C\u94FE\u63A5\u3002
807
1119
 
808
1120
  ## Workflow (Standard Operating Procedure)
@@ -845,7 +1157,7 @@ interface BrowserAction {
845
1157
  payload?: ActionPayload; // \u52A8\u4F5C\u53C2\u6570
846
1158
  }
847
1159
 
848
- type ActionType = 'click' | 'fill' | 'press' | 'hover' | 'check' | 'selectOption' | 'wait' | 'goto';
1160
+ type ActionType = 'click' | 'fill' | 'press' | 'hover' | 'check' | 'selectOption' | 'wait' | 'goto' | 'switchTab';
849
1161
 
850
1162
  type LocatorStrategy =
851
1163
  | 'css' // CSS \u9009\u62E9\u5668
@@ -864,6 +1176,7 @@ interface ActionPayload {
864
1176
  delay?: number; // \u7528\u4E8E wait (\u6BEB\u79D2)
865
1177
  options?: string[]; // \u7528\u4E8E selectOption
866
1178
  url?: string; // \u7528\u4E8E goto
1179
+ tabIndex?: number; // \u7528\u4E8E switchTab\uFF0C\u6307\u5B9A\u8981\u5207\u6362\u7684\u6807\u7B7E\u9875\u7D22\u5F15
867
1180
  }
868
1181
  \`\`\`
869
1182
 
@@ -1102,9 +1415,9 @@ var streamInvoke = async function* (query, thread_id, modelConfig) {
1102
1415
  // src/routes/chat.ts
1103
1416
  var import_promises2 = __toESM(require("fs/promises"));
1104
1417
  var import_path3 = __toESM(require("path"));
1105
- var import_crypto = __toESM(require("crypto"));
1418
+ var import_crypto2 = __toESM(require("crypto"));
1106
1419
  var configDir2 = getConfigPath();
1107
- var configFilePath2 = import_path3.default.join(configDir2, "model.json");
1420
+ var configFilePath = import_path3.default.join(configDir2, "model.json");
1108
1421
  var router2 = (0, import_express2.Router)();
1109
1422
  router2.get("/", (req, res) => {
1110
1423
  res.json({ status: "OK" });
@@ -1116,7 +1429,7 @@ router2.post("/stream", async (req, res) => {
1116
1429
  if (!query || !thread_id) {
1117
1430
  return res.status(400).json({ error: "Missing query or thread_id in request body" });
1118
1431
  }
1119
- const configContent = await import_promises2.default.readFile(configFilePath2, "utf-8");
1432
+ const configContent = await import_promises2.default.readFile(configFilePath, "utf-8");
1120
1433
  const config = JSON.parse(configContent);
1121
1434
  const modelConfig = config.defaultModel;
1122
1435
  if (!modelConfig) {
@@ -1143,7 +1456,7 @@ router2.get("/session", (req, res) => {
1143
1456
  const isNew = req.query.new === "true";
1144
1457
  let sessionId = session_default.getSessionId();
1145
1458
  if (!sessionId || isNew) {
1146
- sessionId = import_crypto.default.randomUUID();
1459
+ sessionId = import_crypto2.default.randomUUID();
1147
1460
  session_default.setSessionId(sessionId);
1148
1461
  }
1149
1462
  res.json({ sessionId });