xshat-lite 1.0.0
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/README.md +211 -0
- package/bin/xshat.mjs +36 -0
- package/package.json +60 -0
- package/src/config/default.mjs +620 -0
- package/src/index.mjs +2865 -0
- package/src/modules/agent-executor.mjs +1184 -0
- package/src/modules/agent-helpers.mjs +132 -0
- package/src/modules/agent-loop.mjs +181 -0
- package/src/modules/agent.mjs +152 -0
- package/src/modules/api-chat.mjs +212 -0
- package/src/modules/browser.mjs +1384 -0
- package/src/modules/chat-helpers.mjs +275 -0
- package/src/modules/chat.mjs +589 -0
- package/src/modules/docs.mjs +88 -0
- package/src/modules/local-intent-router.mjs +280 -0
- package/src/modules/logger.mjs +111 -0
- package/src/modules/multi-agent.mjs +183 -0
- package/src/modules/navigation-recovery.mjs +35 -0
- package/src/modules/provider-debug.mjs +98 -0
- package/src/modules/provider-pool.mjs +66 -0
- package/src/modules/provider-runtime.mjs +26 -0
- package/src/modules/provider-strategies.mjs +102 -0
- package/src/modules/response-parser.mjs +511 -0
- package/src/modules/response-pipeline.mjs +211 -0
- package/src/modules/runtime-maintenance.mjs +195 -0
- package/src/modules/session-browser.mjs +180 -0
- package/src/modules/session.mjs +370 -0
- package/src/modules/settings-menu.mjs +367 -0
- package/src/modules/task-runner.mjs +511 -0
- package/src/modules/task-store.mjs +262 -0
- package/src/modules/ui.mjs +2204 -0
- package/src/utils/config.mjs +257 -0
- package/src/utils/key-input.mjs +86 -0
- package/src/utils/storage.mjs +55 -0
|
@@ -0,0 +1,511 @@
|
|
|
1
|
+
import {
|
|
2
|
+
buildMultiAgentPromptSection,
|
|
3
|
+
resolveEnvelopeAgentRole
|
|
4
|
+
} from './multi-agent.mjs';
|
|
5
|
+
|
|
6
|
+
function normalizeWhitespace(text) {
|
|
7
|
+
return String(text ?? '')
|
|
8
|
+
.replace(/\u200B/g, '')
|
|
9
|
+
.replace(/\r/g, '')
|
|
10
|
+
.replace(/[ \t]+\n/g, '\n')
|
|
11
|
+
.replace(/\n{3,}/g, '\n\n')
|
|
12
|
+
.trim();
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function stripLeadingProviderWrapper(text) {
|
|
16
|
+
return String(text ?? '').replace(
|
|
17
|
+
/^\s*(?:Gemini|AI|Assistant)\s*(?:说|:|:)\s*/i,
|
|
18
|
+
''
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function extractJsonFence(text) {
|
|
23
|
+
const match = text.match(/```json\s*([\s\S]*?)```/i);
|
|
24
|
+
return match ? match[1].trim() : null;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function extractFirstJsonObject(text) {
|
|
28
|
+
const source = String(text ?? '');
|
|
29
|
+
const start = source.indexOf('{');
|
|
30
|
+
|
|
31
|
+
if (start < 0) {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
let depth = 0;
|
|
36
|
+
let inString = false;
|
|
37
|
+
let escaped = false;
|
|
38
|
+
|
|
39
|
+
for (let index = start; index < source.length; index++) {
|
|
40
|
+
const char = source[index];
|
|
41
|
+
|
|
42
|
+
if (inString) {
|
|
43
|
+
if (escaped) {
|
|
44
|
+
escaped = false;
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (char === '\\') {
|
|
49
|
+
escaped = true;
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (char === '"') {
|
|
54
|
+
inString = false;
|
|
55
|
+
}
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (char === '"') {
|
|
60
|
+
inString = true;
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (char === '{') {
|
|
65
|
+
depth++;
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (char === '}') {
|
|
70
|
+
depth--;
|
|
71
|
+
if (depth === 0) {
|
|
72
|
+
return source.slice(start, index + 1).trim();
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function safeParseJson(text) {
|
|
81
|
+
try {
|
|
82
|
+
return JSON.parse(text);
|
|
83
|
+
} catch {
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function normalizeLooseJson(text) {
|
|
89
|
+
const source = String(text ?? '');
|
|
90
|
+
let output = '';
|
|
91
|
+
let inString = false;
|
|
92
|
+
let escaped = false;
|
|
93
|
+
const VALID_ESCAPES = new Set(['"', '\\', '/', 'b', 'f', 'n', 'r', 't', 'u']);
|
|
94
|
+
|
|
95
|
+
for (let index = 0; index < source.length; index++) {
|
|
96
|
+
const char = source[index];
|
|
97
|
+
|
|
98
|
+
if (inString) {
|
|
99
|
+
if (escaped) {
|
|
100
|
+
output += char;
|
|
101
|
+
escaped = false;
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (char === '\\') {
|
|
106
|
+
const next = source[index + 1];
|
|
107
|
+
if (next && VALID_ESCAPES.has(next)) {
|
|
108
|
+
output += char;
|
|
109
|
+
escaped = true;
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
output += '\\\\';
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (char === '"') {
|
|
118
|
+
output += char;
|
|
119
|
+
inString = false;
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (char === '\n') {
|
|
124
|
+
output += '\\n';
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (char === '\r') {
|
|
129
|
+
output += '\\r';
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
} else if (char === '"') {
|
|
133
|
+
inString = true;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
output += char;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return output;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function safeParseLooseJson(text) {
|
|
143
|
+
const normalized = normalizeLooseJson(text);
|
|
144
|
+
if (normalized === text) {
|
|
145
|
+
return null;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return safeParseJson(normalized);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function normalizeCommandName(name) {
|
|
152
|
+
const value = String(name ?? '')
|
|
153
|
+
.trim()
|
|
154
|
+
.toLowerCase();
|
|
155
|
+
|
|
156
|
+
const aliases = new Map([
|
|
157
|
+
['open_browser', 'open_url'],
|
|
158
|
+
['open_page', 'open_url'],
|
|
159
|
+
['browse_url', 'open_url'],
|
|
160
|
+
['navigate', 'open_url'],
|
|
161
|
+
['navigate_url', 'open_url'],
|
|
162
|
+
['send', 'send_message'],
|
|
163
|
+
['reply_with', 'send_message'],
|
|
164
|
+
['exec', 'run_shell'],
|
|
165
|
+
['exec_cmd', 'run_shell'],
|
|
166
|
+
['run_command', 'run_shell'],
|
|
167
|
+
['shell', 'run_shell'],
|
|
168
|
+
['cat_file', 'read_file'],
|
|
169
|
+
['read_path', 'read_file'],
|
|
170
|
+
['save_file', 'write_file'],
|
|
171
|
+
['create_file', 'write_file'],
|
|
172
|
+
['update_file', 'write_file'],
|
|
173
|
+
['remove_file', 'delete_file'],
|
|
174
|
+
['unlink_file', 'delete_file'],
|
|
175
|
+
['get_agent_page', 'read_agent_page'],
|
|
176
|
+
['extract_agent_page', 'read_agent_page'],
|
|
177
|
+
['send_page', 'send_agent_page'],
|
|
178
|
+
['send_current_page', 'send_agent_page'],
|
|
179
|
+
['app_state', 'get_app_state'],
|
|
180
|
+
['status', 'get_app_state'],
|
|
181
|
+
['state', 'get_app_state'],
|
|
182
|
+
['focus_input', 'focus_chat_input'],
|
|
183
|
+
['focus_chatbox', 'focus_chat_input'],
|
|
184
|
+
['type_chat_input', 'send_chat_input'],
|
|
185
|
+
['fill_chat_input', 'send_chat_input'],
|
|
186
|
+
['send_chat_message', 'send_chat_input'],
|
|
187
|
+
['submit_input', 'submit_current_input'],
|
|
188
|
+
['submit_chat_input', 'submit_current_input'],
|
|
189
|
+
['new_chat', 'start_new_chat'],
|
|
190
|
+
['create_new_chat', 'start_new_chat'],
|
|
191
|
+
['input_text', 'type_selector'],
|
|
192
|
+
['type_text', 'type_selector'],
|
|
193
|
+
['fill_text', 'type_selector'],
|
|
194
|
+
['fill_input', 'type_selector'],
|
|
195
|
+
['fill_selector', 'type_selector'],
|
|
196
|
+
['enter_text', 'type_selector'],
|
|
197
|
+
['click_element', 'click_selector'],
|
|
198
|
+
['click_target', 'click_selector'],
|
|
199
|
+
['wait_text', 'wait_for_text'],
|
|
200
|
+
['wait_element', 'wait_for_selector'],
|
|
201
|
+
['wait_dom', 'wait_for_selector'],
|
|
202
|
+
['extract_text', 'extract_selector_text'],
|
|
203
|
+
['read_page', 'read_agent_page'],
|
|
204
|
+
['inspect_page', 'inspect_dom'],
|
|
205
|
+
['screenshot', 'screenshot_agent_page'],
|
|
206
|
+
['capture_page', 'screenshot_agent_page'],
|
|
207
|
+
['keypress', 'press_key'],
|
|
208
|
+
['keyboard_press', 'press_key'],
|
|
209
|
+
['scroll', 'scroll_page']
|
|
210
|
+
]);
|
|
211
|
+
|
|
212
|
+
return aliases.get(value) || value;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function pickFirstValue(...values) {
|
|
216
|
+
for (const value of values) {
|
|
217
|
+
if (value !== undefined && value !== null && value !== '') {
|
|
218
|
+
return value;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
return undefined;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function normalizeCommandArgs(name, rawArgs = {}) {
|
|
226
|
+
const args = { ...rawArgs };
|
|
227
|
+
|
|
228
|
+
switch (name) {
|
|
229
|
+
case 'open_url':
|
|
230
|
+
return {
|
|
231
|
+
...args,
|
|
232
|
+
url: String(
|
|
233
|
+
pickFirstValue(args.url, args.href, args.link, args.target, args.uri) || ''
|
|
234
|
+
).trim()
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
case 'send_message':
|
|
238
|
+
return {
|
|
239
|
+
...args,
|
|
240
|
+
content: String(
|
|
241
|
+
pickFirstValue(args.content, args.text, args.message, args.prompt) || ''
|
|
242
|
+
)
|
|
243
|
+
};
|
|
244
|
+
|
|
245
|
+
case 'focus_chat_input':
|
|
246
|
+
case 'submit_current_input':
|
|
247
|
+
case 'start_new_chat':
|
|
248
|
+
return {
|
|
249
|
+
...args
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
case 'send_chat_input':
|
|
253
|
+
return {
|
|
254
|
+
...args,
|
|
255
|
+
text: String(
|
|
256
|
+
pickFirstValue(args.text, args.content, args.message, args.prompt, args.value) || ''
|
|
257
|
+
)
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
case 'run_shell':
|
|
261
|
+
return {
|
|
262
|
+
...args,
|
|
263
|
+
command: String(
|
|
264
|
+
pickFirstValue(args.command, args.cmd, args.script, args.shell) || ''
|
|
265
|
+
).trim()
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
case 'read_file':
|
|
269
|
+
case 'file_exists':
|
|
270
|
+
case 'list_files':
|
|
271
|
+
case 'write_file':
|
|
272
|
+
case 'delete_file':
|
|
273
|
+
case 'screenshot_agent_page':
|
|
274
|
+
return {
|
|
275
|
+
...args,
|
|
276
|
+
path: String(
|
|
277
|
+
pickFirstValue(args.path, args.file, args.filePath, args.target) || ''
|
|
278
|
+
).trim()
|
|
279
|
+
};
|
|
280
|
+
|
|
281
|
+
case 'click_selector':
|
|
282
|
+
case 'hover_selector':
|
|
283
|
+
case 'wait_for_selector':
|
|
284
|
+
case 'extract_selector_text':
|
|
285
|
+
case 'check_checkbox':
|
|
286
|
+
case 'uncheck_checkbox':
|
|
287
|
+
return {
|
|
288
|
+
...args,
|
|
289
|
+
selector: String(
|
|
290
|
+
pickFirstValue(args.selector, args.css, args.locator, args.target, args.query) || ''
|
|
291
|
+
).trim()
|
|
292
|
+
};
|
|
293
|
+
|
|
294
|
+
case 'type_selector':
|
|
295
|
+
return {
|
|
296
|
+
...args,
|
|
297
|
+
selector: String(
|
|
298
|
+
pickFirstValue(args.selector, args.css, args.locator, args.target, args.query) || ''
|
|
299
|
+
).trim(),
|
|
300
|
+
text: String(
|
|
301
|
+
pickFirstValue(args.text, args.value, args.content, args.message) || ''
|
|
302
|
+
)
|
|
303
|
+
};
|
|
304
|
+
|
|
305
|
+
case 'wait_for_text':
|
|
306
|
+
return {
|
|
307
|
+
...args,
|
|
308
|
+
text: String(
|
|
309
|
+
pickFirstValue(args.text, args.value, args.content, args.message) || ''
|
|
310
|
+
)
|
|
311
|
+
};
|
|
312
|
+
|
|
313
|
+
case 'press_key':
|
|
314
|
+
return {
|
|
315
|
+
...args,
|
|
316
|
+
key: String(pickFirstValue(args.key, args.code, args.press) || '').trim()
|
|
317
|
+
};
|
|
318
|
+
|
|
319
|
+
case 'select_option':
|
|
320
|
+
return {
|
|
321
|
+
...args,
|
|
322
|
+
selector: String(
|
|
323
|
+
pickFirstValue(args.selector, args.css, args.locator, args.target) || ''
|
|
324
|
+
).trim()
|
|
325
|
+
};
|
|
326
|
+
|
|
327
|
+
case 'scroll_page':
|
|
328
|
+
return {
|
|
329
|
+
...args,
|
|
330
|
+
selector: String(
|
|
331
|
+
pickFirstValue(args.selector, args.css, args.locator, args.target) || ''
|
|
332
|
+
).trim(),
|
|
333
|
+
y: pickFirstValue(args.y, args.top, args.deltaY, args.scrollY),
|
|
334
|
+
x: pickFirstValue(args.x, args.left, args.deltaX, args.scrollX)
|
|
335
|
+
};
|
|
336
|
+
|
|
337
|
+
case 'inspect_dom':
|
|
338
|
+
return {
|
|
339
|
+
...args,
|
|
340
|
+
selector: String(
|
|
341
|
+
pickFirstValue(args.selector, args.css, args.locator, args.target) || ''
|
|
342
|
+
).trim()
|
|
343
|
+
};
|
|
344
|
+
|
|
345
|
+
default:
|
|
346
|
+
return args;
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
function normalizeCommand(command) {
|
|
351
|
+
if (!command || typeof command !== 'object') {
|
|
352
|
+
return null;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
const name = normalizeCommandName(command.name);
|
|
356
|
+
const args =
|
|
357
|
+
command.args && typeof command.args === 'object' && !Array.isArray(command.args)
|
|
358
|
+
? command.args
|
|
359
|
+
: {};
|
|
360
|
+
|
|
361
|
+
if (!name) {
|
|
362
|
+
return null;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
return {
|
|
366
|
+
name,
|
|
367
|
+
args: normalizeCommandArgs(name, args)
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
function normalizeEnvelope(payload, rawText) {
|
|
372
|
+
if (!payload || typeof payload !== 'object' || Array.isArray(payload)) {
|
|
373
|
+
return null;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
const type = typeof payload.type === 'string' ? payload.type.trim() : '';
|
|
377
|
+
const text = normalizeWhitespace(payload.text ?? '');
|
|
378
|
+
const command = normalizeCommand(payload.command);
|
|
379
|
+
const agent = resolveEnvelopeAgentRole(payload);
|
|
380
|
+
|
|
381
|
+
if (!type || !['reply', 'action', 'handoff', 'error'].includes(type)) {
|
|
382
|
+
return null;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
return {
|
|
386
|
+
ok: true,
|
|
387
|
+
mode: 'structured',
|
|
388
|
+
type,
|
|
389
|
+
text,
|
|
390
|
+
agent,
|
|
391
|
+
command,
|
|
392
|
+
raw: rawText
|
|
393
|
+
};
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
export function looksLikeStructuredEnvelopeText(rawText) {
|
|
397
|
+
const normalized = normalizeWhitespace(stripLeadingProviderWrapper(rawText));
|
|
398
|
+
if (!normalized.startsWith('{')) {
|
|
399
|
+
return false;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
return /"type"\s*:/.test(normalized) && /"text"\s*:/.test(normalized);
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
export function isStructuredResponseIncomplete(rawText) {
|
|
406
|
+
if (!looksLikeStructuredEnvelopeText(rawText)) {
|
|
407
|
+
return false;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
const normalized = normalizeWhitespace(stripLeadingProviderWrapper(rawText));
|
|
411
|
+
const directPayload = safeParseJson(normalized) || safeParseLooseJson(normalized);
|
|
412
|
+
|
|
413
|
+
if (normalizeEnvelope(directPayload, normalized)) {
|
|
414
|
+
return false;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
const embedded = extractFirstJsonObject(normalized);
|
|
418
|
+
if (!embedded) {
|
|
419
|
+
return true;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
const embeddedPayload = safeParseJson(embedded) || safeParseLooseJson(embedded);
|
|
423
|
+
return !normalizeEnvelope(embeddedPayload, normalized);
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
export function getAgentSystemPrompt(options = {}) {
|
|
427
|
+
const providerId = String(options.providerId || '').trim().toLowerCase();
|
|
428
|
+
const providerPatches = [];
|
|
429
|
+
|
|
430
|
+
if (providerId === 'deepseek' || providerId === 'api-deepseek') {
|
|
431
|
+
providerPatches.push(
|
|
432
|
+
'当前提供方是 DeepSeek。DeepSeek 有时会习惯性输出解释、能力边界或多余前缀,你必须压制这种倾向。',
|
|
433
|
+
'对于本宿主环境,你默认已经具备浏览器自动化、页面读取、文件读写和系统命令这些能力;不要再说“我无法联网”“无法访问互联网”“没有工具”“只是静态模型”。',
|
|
434
|
+
'DeepSeek 尤其容易把本地文件任务误说成网页任务。只要用户提到当前目录、项目目录、工作区、app.txt、创建文件、写入文件、检查文件是否存在,就优先考虑 file_exists、read_file、write_file、list_files 或 run_shell。',
|
|
435
|
+
'DeepSeek 尤其容易在可执行任务上只给建议不行动。这里禁止只提建议;若能推进,就返回 action 并给出最小必要命令。',
|
|
436
|
+
'如果需要向网页输入内容,标准命令名只能是 type_selector,不要使用 input_text、fill_text、fill_input 之类别名。',
|
|
437
|
+
'如果用户是在追问上一轮动作结果,优先基于已给出的回执继续规划,不要假装缺少搜索工具。'
|
|
438
|
+
);
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
return [
|
|
442
|
+
'你是 XShat 的浏览器自动化 Agent。',
|
|
443
|
+
'你运行在一个具备真实浏览器、文件系统和系统命令能力的宿主程序中。',
|
|
444
|
+
'你不是只能静态回答的普通聊天助手;应优先调用宿主能力解决问题,而不是先解释做不到。',
|
|
445
|
+
'请始终只输出一个 JSON 对象。',
|
|
446
|
+
'外层禁止输出 JSON 之外的解释文字、寒暄、代码块围栏或任何额外前后缀。',
|
|
447
|
+
'不要输出 Gemini 说、AI:、Assistant: 之类的角色前缀,JSON 必须直接以 { 开头。',
|
|
448
|
+
'JSON 格式固定为: {"type":"reply|action|handoff|error","text":"...","command":null|{"name":"...","args":{}}}。',
|
|
449
|
+
'你可以额外附带可选字段 agent,用于标记当前步骤应由哪个角色负责。',
|
|
450
|
+
'text 字段允许使用简洁、干净的 Markdown,例如短段落、项目符号、编号列表和简短代码片段;不要滥用标题,不要写成大段模板化套话。',
|
|
451
|
+
'reply 表示直接回答用户,command 必须为 null。',
|
|
452
|
+
'action 表示需要宿主程序执行一步命令,command 必须存在且只能包含一个命令。',
|
|
453
|
+
'handoff 表示需要人工接管,例如登录、验证码、二次确认、复杂拖拽、必须肉眼判断的页面状态。',
|
|
454
|
+
'error 只是最后手段,只有在尝试合理动作后仍然无法推进时才能使用,并说明阻塞原因。',
|
|
455
|
+
'仅允许使用以下命令名: open_url, send_message, run_shell, list_files, file_exists, read_file, write_file, delete_file, read_agent_page, send_agent_page, get_app_state, focus_chat_input, send_chat_input, submit_current_input, start_new_chat, click_selector, type_selector, wait_for_text, wait_for_selector, wait_for_navigation, press_key, select_option, scroll_page, screenshot_agent_page, extract_selector_text, inspect_dom, handoff。',
|
|
456
|
+
'注意:输入网页内容时只能使用 type_selector;点击网页元素时只能使用 click_selector。',
|
|
457
|
+
'不要发明新命令,不要输出 command list,不要输出多个 JSON 对象。',
|
|
458
|
+
'常见命令参数示例: open_url 使用 {"url":"https://example.com"};send_message 使用 {"content":"..."};run_shell 使用 {"command":"dir"};list_files 使用 {"path":"."};file_exists 使用 {"path":"app.txt"};read_file/write_file/delete_file 使用 {"path":"src/index.mjs"},其中 write_file 额外使用 {"content":"..."};focus_chat_input / submit_current_input / start_new_chat 一般不需要参数;send_chat_input 使用 {"text":"你好"};click_selector/extract_selector_text/wait_for_selector 使用 {"selector":"button"};type_selector 使用 {"selector":"textarea","text":"hello"};wait_for_text 使用 {"text":"登录成功"};wait_for_navigation 使用 {"state":"domcontentloaded"};press_key 使用 {"key":"Enter"};select_option 使用 {"selector":"select","value":"cn"} 或 {"selector":"select","label":"China"};scroll_page 使用 {"y":900} 或 {"selector":"#result"};screenshot_agent_page 使用 {"path":"runtime/agent/page.png"};inspect_dom 使用 {"selector":"main","maxNodes":20}。',
|
|
459
|
+
'对于 Gemini、DeepSeek、Duck.ai 这类聊天网页,优先使用 focus_chat_input、send_chat_input、submit_current_input、start_new_chat 这些高层聊天命令,而不是自己猜输入框 selector。',
|
|
460
|
+
'你可以通过 open_url 打开搜索引擎、新闻页、比分页、文档页或目标网站,再结合 read_agent_page、extract_selector_text、inspect_dom 获取内容。',
|
|
461
|
+
'当用户询问“最新”“当前”“实时”“比分”“新闻”“网页内容”时,不要声称无法联网;应优先尝试 open_url -> wait_for_text / extract_selector_text / read_agent_page。',
|
|
462
|
+
'当用户询问当前目录、工作区、项目目录、某个文件是否存在、某个文件内容、把内容写入文件、创建文件时,不要把它理解成网页任务;应优先使用 list_files、file_exists、read_file、write_file、delete_file 或 run_shell。',
|
|
463
|
+
'当用户要求把结果写入文件时,不要只口头回答;在拿到内容后应优先使用 write_file,必要时先 read_file 再追加或改写。',
|
|
464
|
+
'当任务需要批量处理、持续刷新、后台循环、定时抓取或长时间写入时,如果允许 Shell 和文件写入,可以先 write_file 生成脚本,再 run_shell 启动它;只有遇到真实外部阻塞时才 handoff。',
|
|
465
|
+
'不要把“没有专属搜索工具”“只是单步 Agent”“不能持续执行”当作默认借口;你可以通过浏览器自动化、脚本文件和系统命令组合完成很多工作。',
|
|
466
|
+
'决策顺序: 先观察,再动作;信息不足时优先 get_app_state、read_agent_page、extract_selector_text,确认页面状态后再点击或输入。',
|
|
467
|
+
'网页自动化建议优先序列: open_url -> wait_for_text -> click_selector/type_selector -> extract_selector_text/read_agent_page -> send_agent_page。',
|
|
468
|
+
'当你不知道当前应用环境、浏览器状态、当前 provider、工作区能力或自动执行开关时,请先调用 get_app_state。',
|
|
469
|
+
'当用户明显高估或低估能力时,用 action 先尝试最小可行步骤,而不是直接争论能力边界。',
|
|
470
|
+
'当需求超出支持命令范围时,不要硬编命令;应改用 handoff 或 error。',
|
|
471
|
+
'每次只推进一个最小必要动作,避免一次返回多个动作或跳步。',
|
|
472
|
+
'如果是 reply,优先直接给结果;需要结构时,先给结论,再给 2-6 条高密度要点。',
|
|
473
|
+
'避免重复用户原话、避免空泛过渡句、避免“当然可以”“以下是”这类占位开场,除非确实能提升可读性。',
|
|
474
|
+
'如果涉及对比、排行、赛程、统计、时间线或结构化清单,优先输出紧凑 Markdown 列表或短表格;只有在列数很少且内容短时才用表格。',
|
|
475
|
+
'终端宽度有限,避免输出列太多、单元格太长的大表格;必要时改成分组列表、卡片式条目或“项目: 值”的结构。',
|
|
476
|
+
'CLI 展示优先顺序: 先给一句结论,再给 2-6 条项目列表;只有当表格比列表更清晰时才输出 2-4 列的小表格。',
|
|
477
|
+
'每条列表项尽量控制在一两行内;如果信息很多,分成小节列表,不要堆成一整墙文字。',
|
|
478
|
+
'预测、推荐、对比、汇总、赛程和配置说明这类内容,优先输出小表格、项目列表或“项目: 值”块,让终端里可以快速扫读。',
|
|
479
|
+
...providerPatches,
|
|
480
|
+
buildMultiAgentPromptSection(options)
|
|
481
|
+
].join(' ');
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
export function parseAgentResponse(rawText) {
|
|
485
|
+
const normalized = normalizeWhitespace(stripLeadingProviderWrapper(rawText));
|
|
486
|
+
const fenced = extractJsonFence(normalized);
|
|
487
|
+
const embedded = extractFirstJsonObject(normalized);
|
|
488
|
+
const directPayload = safeParseJson(normalized);
|
|
489
|
+
const fencedPayload = fenced ? safeParseJson(fenced) : null;
|
|
490
|
+
const embeddedPayload = embedded ? safeParseJson(embedded) : null;
|
|
491
|
+
const loosePayload =
|
|
492
|
+
embedded && !embeddedPayload ? safeParseLooseJson(embedded) : null;
|
|
493
|
+
const structured =
|
|
494
|
+
normalizeEnvelope(directPayload, normalized) ||
|
|
495
|
+
normalizeEnvelope(fencedPayload, normalized) ||
|
|
496
|
+
normalizeEnvelope(embeddedPayload, normalized) ||
|
|
497
|
+
normalizeEnvelope(loosePayload, normalized);
|
|
498
|
+
|
|
499
|
+
if (structured) {
|
|
500
|
+
return structured;
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
return {
|
|
504
|
+
ok: true,
|
|
505
|
+
mode: 'plain',
|
|
506
|
+
type: 'reply',
|
|
507
|
+
text: normalized,
|
|
508
|
+
command: null,
|
|
509
|
+
raw: rawText
|
|
510
|
+
};
|
|
511
|
+
}
|