@xcanwin/manyoyo 5.6.10 → 5.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/agent-resume.js +12 -0
- package/lib/web/frontend/app.js +15 -1
- package/lib/web/server.js +547 -18
- package/package.json +1 -1
package/lib/agent-resume.js
CHANGED
|
@@ -16,7 +16,10 @@ const AGENT_PROMPT_TEMPLATE_MAP = {
|
|
|
16
16
|
opencode: 'opencode run {prompt}'
|
|
17
17
|
};
|
|
18
18
|
|
|
19
|
+
const CLAUDE_DANGEROUS_FLAG = '--dangerously-skip-permissions';
|
|
20
|
+
const GEMINI_YOLO_FLAG = '--yolo';
|
|
19
21
|
const CODEX_DANGEROUS_FLAG = '--dangerously-bypass-approvals-and-sandbox';
|
|
22
|
+
const OPENCODE_PERMISSION_KEY = 'OPENCODE_PERMISSION=';
|
|
20
23
|
|
|
21
24
|
function stripLeadingAssignments(commandText) {
|
|
22
25
|
let rest = String(commandText || '').trim();
|
|
@@ -80,11 +83,20 @@ function resolveAgentPromptCommandTemplate(commandText) {
|
|
|
80
83
|
const normalizedCommand = String(commandText || '').trim();
|
|
81
84
|
const program = resolveAgentProgram(commandText);
|
|
82
85
|
const template = AGENT_PROMPT_TEMPLATE_MAP[program] || '';
|
|
86
|
+
if (program === 'claude' && normalizedCommand.includes(CLAUDE_DANGEROUS_FLAG)) {
|
|
87
|
+
return `${normalizedCommand} -p {prompt}`;
|
|
88
|
+
}
|
|
89
|
+
if (program === 'gemini' && normalizedCommand.includes(GEMINI_YOLO_FLAG)) {
|
|
90
|
+
return `${normalizedCommand} -p {prompt}`;
|
|
91
|
+
}
|
|
83
92
|
if (program === 'codex' && template) {
|
|
84
93
|
if (normalizedCommand.includes(CODEX_DANGEROUS_FLAG)) {
|
|
85
94
|
return `codex exec ${CODEX_DANGEROUS_FLAG} --skip-git-repo-check {prompt}`;
|
|
86
95
|
}
|
|
87
96
|
}
|
|
97
|
+
if (program === 'opencode' && normalizedCommand.includes(OPENCODE_PERMISSION_KEY)) {
|
|
98
|
+
return `${normalizedCommand} run {prompt}`;
|
|
99
|
+
}
|
|
88
100
|
return template;
|
|
89
101
|
}
|
|
90
102
|
|
package/lib/web/frontend/app.js
CHANGED
|
@@ -178,6 +178,10 @@
|
|
|
178
178
|
codex: 'codex exec --skip-git-repo-check {prompt}',
|
|
179
179
|
opencode: 'opencode run {prompt}'
|
|
180
180
|
};
|
|
181
|
+
const CLAUDE_DANGEROUS_FLAG = '--dangerously-skip-permissions';
|
|
182
|
+
const GEMINI_YOLO_FLAG = '--yolo';
|
|
183
|
+
const CODEX_DANGEROUS_FLAG = '--dangerously-bypass-approvals-and-sandbox';
|
|
184
|
+
const OPENCODE_PERMISSION_KEY = 'OPENCODE_PERMISSION=';
|
|
181
185
|
const markdownRenderer = window.ManyoyoMarkdown
|
|
182
186
|
&& typeof window.ManyoyoMarkdown.shouldRenderMessage === 'function'
|
|
183
187
|
&& typeof window.ManyoyoMarkdown.render === 'function'
|
|
@@ -559,11 +563,21 @@
|
|
|
559
563
|
}
|
|
560
564
|
|
|
561
565
|
function resolveAgentPromptTemplate(commandText) {
|
|
566
|
+
const normalizedCommand = String(commandText || '').trim();
|
|
562
567
|
const program = resolveAgentProgram(commandText);
|
|
563
568
|
const template = AGENT_PROMPT_TEMPLATE_MAP[program] || '';
|
|
564
|
-
if (program === '
|
|
569
|
+
if (program === 'claude' && normalizedCommand.includes(CLAUDE_DANGEROUS_FLAG)) {
|
|
570
|
+
return `${normalizedCommand} -p {prompt}`;
|
|
571
|
+
}
|
|
572
|
+
if (program === 'gemini' && normalizedCommand.includes(GEMINI_YOLO_FLAG)) {
|
|
573
|
+
return `${normalizedCommand} -p {prompt}`;
|
|
574
|
+
}
|
|
575
|
+
if (program === 'codex' && normalizedCommand.includes(CODEX_DANGEROUS_FLAG)) {
|
|
565
576
|
return 'codex exec --dangerously-bypass-approvals-and-sandbox --skip-git-repo-check {prompt}';
|
|
566
577
|
}
|
|
578
|
+
if (program === 'opencode' && normalizedCommand.includes(OPENCODE_PERMISSION_KEY)) {
|
|
579
|
+
return `${normalizedCommand} run {prompt}`;
|
|
580
|
+
}
|
|
567
581
|
return template;
|
|
568
582
|
}
|
|
569
583
|
|
package/lib/web/server.js
CHANGED
|
@@ -297,13 +297,210 @@ function buildCodexAgentExecCommand(template, prompt) {
|
|
|
297
297
|
: renderAgentPromptCommand(codexTemplate, prompt);
|
|
298
298
|
}
|
|
299
299
|
|
|
300
|
+
function prependAgentFlags(commandText, matchPattern, flagSpecs) {
|
|
301
|
+
const matched = String(commandText || '').match(matchPattern);
|
|
302
|
+
if (!matched) {
|
|
303
|
+
return String(commandText || '');
|
|
304
|
+
}
|
|
305
|
+
const prefix = matched[1] || '';
|
|
306
|
+
let suffix = matched[matched.length - 1] || '';
|
|
307
|
+
for (let i = flagSpecs.length - 1; i >= 0; i -= 1) {
|
|
308
|
+
const spec = flagSpecs[i];
|
|
309
|
+
if (!spec || !spec.flag || !(spec.pattern instanceof RegExp) || spec.pattern.test(suffix)) {
|
|
310
|
+
continue;
|
|
311
|
+
}
|
|
312
|
+
suffix = ` ${spec.flag}${suffix}`;
|
|
313
|
+
}
|
|
314
|
+
return `${prefix}${suffix}`;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
function buildClaudeAgentExecCommand(template, prompt) {
|
|
318
|
+
const templateText = normalizeAgentPromptCommandTemplate(template, 'agentPromptCommand');
|
|
319
|
+
const claudeTemplate = prependAgentFlags(
|
|
320
|
+
templateText,
|
|
321
|
+
/^(((?:(?:[A-Za-z_][A-Za-z0-9_]*=)(?:"(?:\\.|[^"])*"|'(?:\\.|[^'])*'|[^\s]+)\s+)*)claude\b)(.*)$/,
|
|
322
|
+
[
|
|
323
|
+
{ flag: '--verbose', pattern: /(?:^|\s)--verbose(?:\s|$)/ },
|
|
324
|
+
{ flag: '--output-format stream-json', pattern: /(?:^|\s)--output-format(?:\s|$)/ }
|
|
325
|
+
]
|
|
326
|
+
);
|
|
327
|
+
return claudeTemplate === templateText
|
|
328
|
+
? renderAgentPromptCommand(templateText, prompt)
|
|
329
|
+
: renderAgentPromptCommand(claudeTemplate, prompt);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
function buildGeminiAgentExecCommand(template, prompt) {
|
|
333
|
+
const templateText = normalizeAgentPromptCommandTemplate(template, 'agentPromptCommand');
|
|
334
|
+
const geminiTemplate = prependAgentFlags(
|
|
335
|
+
templateText,
|
|
336
|
+
/^(((?:(?:[A-Za-z_][A-Za-z0-9_]*=)(?:"(?:\\.|[^"])*"|'(?:\\.|[^'])*'|[^\s]+)\s+)*)gemini\b)(.*)$/,
|
|
337
|
+
[
|
|
338
|
+
{ flag: '--output-format stream-json', pattern: /(?:^|\s)--output-format(?:\s|$)/ }
|
|
339
|
+
]
|
|
340
|
+
);
|
|
341
|
+
return geminiTemplate === templateText
|
|
342
|
+
? renderAgentPromptCommand(templateText, prompt)
|
|
343
|
+
: renderAgentPromptCommand(geminiTemplate, prompt);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
function buildOpenCodeAgentExecCommand(template, prompt) {
|
|
347
|
+
const templateText = normalizeAgentPromptCommandTemplate(template, 'agentPromptCommand');
|
|
348
|
+
const opencodeTemplate = prependAgentFlags(
|
|
349
|
+
templateText,
|
|
350
|
+
/^(((?:(?:[A-Za-z_][A-Za-z0-9_]*=)(?:"(?:\\.|[^"])*"|'(?:\\.|[^'])*'|[^\s]+)\s+)*)opencode\s+run\b)(.*)$/,
|
|
351
|
+
[
|
|
352
|
+
{ flag: '--format json', pattern: /(?:^|\s)--format(?:\s|$)/ }
|
|
353
|
+
]
|
|
354
|
+
);
|
|
355
|
+
return opencodeTemplate === templateText
|
|
356
|
+
? renderAgentPromptCommand(templateText, prompt)
|
|
357
|
+
: renderAgentPromptCommand(opencodeTemplate, prompt);
|
|
358
|
+
}
|
|
359
|
+
|
|
300
360
|
function buildWebAgentExecCommand(template, prompt, agentProgram) {
|
|
301
|
-
|
|
361
|
+
switch (agentProgram) {
|
|
362
|
+
case 'claude':
|
|
363
|
+
return buildClaudeAgentExecCommand(template, prompt);
|
|
364
|
+
case 'gemini':
|
|
365
|
+
return buildGeminiAgentExecCommand(template, prompt);
|
|
366
|
+
case 'codex':
|
|
302
367
|
return buildCodexAgentExecCommand(template, prompt);
|
|
368
|
+
case 'opencode':
|
|
369
|
+
return buildOpenCodeAgentExecCommand(template, prompt);
|
|
370
|
+
default:
|
|
371
|
+
break;
|
|
303
372
|
}
|
|
304
373
|
return renderAgentPromptCommand(template, prompt);
|
|
305
374
|
}
|
|
306
375
|
|
|
376
|
+
function parseJsonObjectLine(line) {
|
|
377
|
+
const text = String(line || '').trim();
|
|
378
|
+
if (!text) {
|
|
379
|
+
return null;
|
|
380
|
+
}
|
|
381
|
+
try {
|
|
382
|
+
const payload = JSON.parse(text);
|
|
383
|
+
return payload && typeof payload === 'object' ? payload : null;
|
|
384
|
+
} catch (e) {
|
|
385
|
+
return null;
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
function collectStructuredText(value) {
|
|
390
|
+
if (typeof value === 'string') {
|
|
391
|
+
return value.trim();
|
|
392
|
+
}
|
|
393
|
+
if (Array.isArray(value)) {
|
|
394
|
+
return value.map(item => collectStructuredText(item)).filter(Boolean).join('\n').trim();
|
|
395
|
+
}
|
|
396
|
+
if (!value || typeof value !== 'object') {
|
|
397
|
+
return '';
|
|
398
|
+
}
|
|
399
|
+
if (typeof value.text === 'string' && value.text.trim()) {
|
|
400
|
+
return value.text.trim();
|
|
401
|
+
}
|
|
402
|
+
if (typeof value.content === 'string' && value.content.trim()) {
|
|
403
|
+
return value.content.trim();
|
|
404
|
+
}
|
|
405
|
+
if (Array.isArray(value.content)) {
|
|
406
|
+
return value.content.map(item => collectStructuredText(item)).filter(Boolean).join('\n').trim();
|
|
407
|
+
}
|
|
408
|
+
return '';
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
function extractClaudeAgentMessage(text) {
|
|
412
|
+
let lastMessage = '';
|
|
413
|
+
for (const rawLine of String(text || '').split('\n')) {
|
|
414
|
+
const payload = parseJsonObjectLine(rawLine);
|
|
415
|
+
if (!payload || payload.type !== 'assistant') {
|
|
416
|
+
continue;
|
|
417
|
+
}
|
|
418
|
+
const message = toPlainObject(payload.message);
|
|
419
|
+
const content = Array.isArray(message.content) ? message.content : [];
|
|
420
|
+
const nextMessage = content
|
|
421
|
+
.filter(item => item && typeof item === 'object' && item.type === 'text')
|
|
422
|
+
.map(item => collectStructuredText(item))
|
|
423
|
+
.filter(Boolean)
|
|
424
|
+
.join('\n')
|
|
425
|
+
.trim();
|
|
426
|
+
if (nextMessage) {
|
|
427
|
+
lastMessage = nextMessage;
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
return lastMessage.trim();
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
function extractGeminiAgentMessage(text) {
|
|
434
|
+
let lastMessage = '';
|
|
435
|
+
let deltaMessage = '';
|
|
436
|
+
for (const rawLine of String(text || '').split('\n')) {
|
|
437
|
+
const payload = parseJsonObjectLine(rawLine);
|
|
438
|
+
if (!payload || payload.type !== 'message' || payload.role !== 'assistant') {
|
|
439
|
+
continue;
|
|
440
|
+
}
|
|
441
|
+
const content = collectStructuredText(payload.content);
|
|
442
|
+
if (!content) {
|
|
443
|
+
continue;
|
|
444
|
+
}
|
|
445
|
+
if (payload.delta === true) {
|
|
446
|
+
deltaMessage += content;
|
|
447
|
+
lastMessage = deltaMessage.trim();
|
|
448
|
+
continue;
|
|
449
|
+
}
|
|
450
|
+
deltaMessage = '';
|
|
451
|
+
lastMessage = content;
|
|
452
|
+
}
|
|
453
|
+
return lastMessage.trim();
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
function extractOpenCodeAgentMessage(text) {
|
|
457
|
+
let lastMessage = '';
|
|
458
|
+
let deltaMessage = '';
|
|
459
|
+
for (const rawLine of String(text || '').split('\n')) {
|
|
460
|
+
const payload = parseJsonObjectLine(rawLine);
|
|
461
|
+
if (!payload) {
|
|
462
|
+
continue;
|
|
463
|
+
}
|
|
464
|
+
const eventType = pickFirstString(payload.type);
|
|
465
|
+
const message = toPlainObject(payload.message);
|
|
466
|
+
const role = pickFirstString(payload.role, message.role);
|
|
467
|
+
if (eventType !== 'message' && eventType !== 'assistant' && eventType !== 'assistant_message' && eventType !== 'text') {
|
|
468
|
+
continue;
|
|
469
|
+
}
|
|
470
|
+
if (role && role !== 'assistant') {
|
|
471
|
+
continue;
|
|
472
|
+
}
|
|
473
|
+
const content = collectStructuredText(message.content || payload.content || payload.text || payload);
|
|
474
|
+
if (!content) {
|
|
475
|
+
continue;
|
|
476
|
+
}
|
|
477
|
+
if (payload.delta === true) {
|
|
478
|
+
deltaMessage += content;
|
|
479
|
+
lastMessage = deltaMessage.trim();
|
|
480
|
+
continue;
|
|
481
|
+
}
|
|
482
|
+
deltaMessage = '';
|
|
483
|
+
lastMessage = content;
|
|
484
|
+
}
|
|
485
|
+
return lastMessage.trim();
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
function extractAgentMessageFromStructuredOutput(agentProgram, text) {
|
|
489
|
+
if (agentProgram === 'codex') {
|
|
490
|
+
return extractAgentMessageFromCodexJsonl(text);
|
|
491
|
+
}
|
|
492
|
+
if (agentProgram === 'claude') {
|
|
493
|
+
return extractClaudeAgentMessage(text);
|
|
494
|
+
}
|
|
495
|
+
if (agentProgram === 'gemini') {
|
|
496
|
+
return extractGeminiAgentMessage(text);
|
|
497
|
+
}
|
|
498
|
+
if (agentProgram === 'opencode') {
|
|
499
|
+
return extractOpenCodeAgentMessage(text);
|
|
500
|
+
}
|
|
501
|
+
return '';
|
|
502
|
+
}
|
|
503
|
+
|
|
307
504
|
function getAgentRuntimeMeta(history) {
|
|
308
505
|
const sessionHistory = history && typeof history === 'object' ? history : {};
|
|
309
506
|
const template = normalizeAgentPromptCommandTemplate(sessionHistory.agentPromptCommand, 'agentPromptCommand');
|
|
@@ -376,6 +573,333 @@ function buildAgentPromptWithHistory(history, prompt) {
|
|
|
376
573
|
].join('\n');
|
|
377
574
|
}
|
|
378
575
|
|
|
576
|
+
function shortenTraceText(value, maxChars = 140) {
|
|
577
|
+
const raw = clipText(stripAnsi(String(value || '')).replace(/\s+/g, ' ').trim(), maxChars);
|
|
578
|
+
return raw.trim();
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
function summarizeTraceArguments(args) {
|
|
582
|
+
if (!args || typeof args !== 'object' || Array.isArray(args)) {
|
|
583
|
+
return '';
|
|
584
|
+
}
|
|
585
|
+
const parts = [];
|
|
586
|
+
for (const [key, value] of Object.entries(args)) {
|
|
587
|
+
if (value === undefined || value === null) continue;
|
|
588
|
+
if (typeof value === 'string') {
|
|
589
|
+
const textValue = value.trim();
|
|
590
|
+
if (!textValue) continue;
|
|
591
|
+
parts.push(`${key}=${shortenTraceText(textValue, 80)}`);
|
|
592
|
+
continue;
|
|
593
|
+
}
|
|
594
|
+
if (typeof value === 'number' || typeof value === 'boolean') {
|
|
595
|
+
parts.push(`${key}=${String(value)}`);
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
return parts.slice(0, 3).join(', ');
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
function createStructuredTraceEvent(provider, kind, eventType, textValue, extra = {}) {
|
|
602
|
+
const normalizedText = String(textValue || '').trim();
|
|
603
|
+
if (!normalizedText) {
|
|
604
|
+
return null;
|
|
605
|
+
}
|
|
606
|
+
return {
|
|
607
|
+
provider,
|
|
608
|
+
kind,
|
|
609
|
+
eventType,
|
|
610
|
+
text: normalizedText,
|
|
611
|
+
...extra
|
|
612
|
+
};
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
function prepareClaudeTraceEvents(payload, state) {
|
|
616
|
+
const eventType = pickFirstString(payload.type);
|
|
617
|
+
const subtype = pickFirstString(payload.subtype);
|
|
618
|
+
const message = toPlainObject(payload.message);
|
|
619
|
+
const content = Array.isArray(message.content) ? message.content : [];
|
|
620
|
+
const toolNamesById = state.toolNamesById;
|
|
621
|
+
const events = [];
|
|
622
|
+
|
|
623
|
+
if (eventType === 'system' && subtype === 'init') {
|
|
624
|
+
events.push(createStructuredTraceEvent('claude', 'thread', eventType, '[会话] Claude 已开始处理', {
|
|
625
|
+
phase: 'started',
|
|
626
|
+
status: 'started',
|
|
627
|
+
subtype
|
|
628
|
+
}));
|
|
629
|
+
return events.filter(Boolean);
|
|
630
|
+
}
|
|
631
|
+
if (eventType === 'assistant') {
|
|
632
|
+
content.forEach(item => {
|
|
633
|
+
if (!item || typeof item !== 'object') {
|
|
634
|
+
return;
|
|
635
|
+
}
|
|
636
|
+
if (item.type === 'text') {
|
|
637
|
+
const detail = collectStructuredText(item);
|
|
638
|
+
if (detail) {
|
|
639
|
+
events.push(createStructuredTraceEvent('claude', 'agent_message', eventType, `[说明] ${detail}`, {
|
|
640
|
+
phase: 'completed',
|
|
641
|
+
status: 'completed',
|
|
642
|
+
detail
|
|
643
|
+
}));
|
|
644
|
+
}
|
|
645
|
+
return;
|
|
646
|
+
}
|
|
647
|
+
if (item.type === 'tool_use') {
|
|
648
|
+
const toolName = pickFirstString(item.name, item.id, 'tool');
|
|
649
|
+
const toolId = pickFirstString(item.id);
|
|
650
|
+
if (toolId) {
|
|
651
|
+
toolNamesById.set(toolId, toolName);
|
|
652
|
+
}
|
|
653
|
+
const summary = summarizeTraceArguments(toPlainObject(item.input));
|
|
654
|
+
events.push(createStructuredTraceEvent(
|
|
655
|
+
'claude',
|
|
656
|
+
'tool',
|
|
657
|
+
eventType,
|
|
658
|
+
summary ? `[工具开始] ${toolName} (${summary})` : `[工具开始] ${toolName}`,
|
|
659
|
+
{
|
|
660
|
+
phase: 'started',
|
|
661
|
+
status: 'in_progress',
|
|
662
|
+
toolName,
|
|
663
|
+
toolId,
|
|
664
|
+
arguments: toPlainObject(item.input),
|
|
665
|
+
argumentSummary: summary
|
|
666
|
+
}
|
|
667
|
+
));
|
|
668
|
+
}
|
|
669
|
+
});
|
|
670
|
+
return events.filter(Boolean);
|
|
671
|
+
}
|
|
672
|
+
if (eventType === 'user') {
|
|
673
|
+
content.forEach(item => {
|
|
674
|
+
if (!item || typeof item !== 'object' || item.type !== 'tool_result') {
|
|
675
|
+
return;
|
|
676
|
+
}
|
|
677
|
+
const toolId = pickFirstString(item.tool_use_id);
|
|
678
|
+
const toolName = pickFirstString(toolNamesById.get(toolId), toolId, 'tool');
|
|
679
|
+
const status = item.is_error === true ? 'error' : 'success';
|
|
680
|
+
events.push(createStructuredTraceEvent('claude', 'tool', eventType, `[工具完成] ${toolName} (${status})`, {
|
|
681
|
+
phase: 'completed',
|
|
682
|
+
status,
|
|
683
|
+
toolName,
|
|
684
|
+
toolId,
|
|
685
|
+
result: collectStructuredText(item.content),
|
|
686
|
+
error: item.is_error === true ? collectStructuredText(item.content) : ''
|
|
687
|
+
}));
|
|
688
|
+
});
|
|
689
|
+
return events.filter(Boolean);
|
|
690
|
+
}
|
|
691
|
+
if (eventType === 'result') {
|
|
692
|
+
events.push(createStructuredTraceEvent('claude', 'turn', eventType, '[回合] 响应完成', {
|
|
693
|
+
phase: 'completed',
|
|
694
|
+
status: pickFirstString(subtype, 'completed'),
|
|
695
|
+
subtype
|
|
696
|
+
}));
|
|
697
|
+
return events.filter(Boolean);
|
|
698
|
+
}
|
|
699
|
+
if (eventType === 'error') {
|
|
700
|
+
const detail = pickFirstString(payload.message, payload.error);
|
|
701
|
+
events.push(createStructuredTraceEvent('claude', 'error', eventType, detail ? `[错误] ${detail}` : '[错误] Claude 返回了错误事件', {
|
|
702
|
+
status: 'error',
|
|
703
|
+
detail
|
|
704
|
+
}));
|
|
705
|
+
return events.filter(Boolean);
|
|
706
|
+
}
|
|
707
|
+
return [];
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
function prepareGeminiTraceEvents(payload, state) {
|
|
711
|
+
const eventType = pickFirstString(payload.type);
|
|
712
|
+
const toolNamesById = state.toolNamesById;
|
|
713
|
+
const events = [];
|
|
714
|
+
|
|
715
|
+
if (eventType === 'init') {
|
|
716
|
+
events.push(createStructuredTraceEvent('gemini', 'thread', eventType, '[会话] Gemini 已开始处理', {
|
|
717
|
+
phase: 'started',
|
|
718
|
+
status: 'started',
|
|
719
|
+
sessionId: pickFirstString(payload.session_id),
|
|
720
|
+
model: pickFirstString(payload.model)
|
|
721
|
+
}));
|
|
722
|
+
return events.filter(Boolean);
|
|
723
|
+
}
|
|
724
|
+
if (eventType === 'message' && payload.role === 'assistant') {
|
|
725
|
+
if (payload.delta === true) {
|
|
726
|
+
return [];
|
|
727
|
+
}
|
|
728
|
+
const detail = collectStructuredText(payload.content);
|
|
729
|
+
if (!detail) {
|
|
730
|
+
return [];
|
|
731
|
+
}
|
|
732
|
+
events.push(createStructuredTraceEvent('gemini', 'agent_message', eventType, `[说明] ${detail}`, {
|
|
733
|
+
phase: 'completed',
|
|
734
|
+
status: 'completed',
|
|
735
|
+
detail
|
|
736
|
+
}));
|
|
737
|
+
return events.filter(Boolean);
|
|
738
|
+
}
|
|
739
|
+
if (eventType === 'tool_use') {
|
|
740
|
+
const toolName = pickFirstString(payload.tool_name, payload.tool_id, 'tool');
|
|
741
|
+
const toolId = pickFirstString(payload.tool_id);
|
|
742
|
+
if (toolId) {
|
|
743
|
+
toolNamesById.set(toolId, toolName);
|
|
744
|
+
}
|
|
745
|
+
const summary = summarizeTraceArguments(toPlainObject(payload.parameters));
|
|
746
|
+
events.push(createStructuredTraceEvent(
|
|
747
|
+
'gemini',
|
|
748
|
+
'tool',
|
|
749
|
+
eventType,
|
|
750
|
+
summary ? `[工具开始] ${toolName} (${summary})` : `[工具开始] ${toolName}`,
|
|
751
|
+
{
|
|
752
|
+
phase: 'started',
|
|
753
|
+
status: 'in_progress',
|
|
754
|
+
toolName,
|
|
755
|
+
toolId,
|
|
756
|
+
arguments: toPlainObject(payload.parameters),
|
|
757
|
+
argumentSummary: summary
|
|
758
|
+
}
|
|
759
|
+
));
|
|
760
|
+
return events.filter(Boolean);
|
|
761
|
+
}
|
|
762
|
+
if (eventType === 'tool_result') {
|
|
763
|
+
const toolId = pickFirstString(payload.tool_id);
|
|
764
|
+
const toolName = pickFirstString(toolNamesById.get(toolId), toolId, 'tool');
|
|
765
|
+
const status = pickFirstString(payload.status, 'completed');
|
|
766
|
+
events.push(createStructuredTraceEvent('gemini', 'tool', eventType, `[工具完成] ${toolName} (${status})`, {
|
|
767
|
+
phase: 'completed',
|
|
768
|
+
status,
|
|
769
|
+
toolName,
|
|
770
|
+
toolId,
|
|
771
|
+
result: collectStructuredText(payload.output),
|
|
772
|
+
error: toPlainObject(payload.error)
|
|
773
|
+
}));
|
|
774
|
+
return events.filter(Boolean);
|
|
775
|
+
}
|
|
776
|
+
if (eventType === 'result') {
|
|
777
|
+
events.push(createStructuredTraceEvent('gemini', 'turn', eventType, '[回合] 响应完成', {
|
|
778
|
+
phase: 'completed',
|
|
779
|
+
status: pickFirstString(payload.status, 'completed')
|
|
780
|
+
}));
|
|
781
|
+
return events.filter(Boolean);
|
|
782
|
+
}
|
|
783
|
+
if (eventType === 'error') {
|
|
784
|
+
const detail = pickFirstString(payload.message);
|
|
785
|
+
events.push(createStructuredTraceEvent('gemini', 'error', eventType, detail ? `[错误] ${detail}` : '[错误] Gemini 返回了错误事件', {
|
|
786
|
+
status: pickFirstString(payload.severity, 'error'),
|
|
787
|
+
detail
|
|
788
|
+
}));
|
|
789
|
+
return events.filter(Boolean);
|
|
790
|
+
}
|
|
791
|
+
return [];
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
function prepareOpenCodeTraceEvents(payload, state) {
|
|
795
|
+
const eventType = pickFirstString(payload.type);
|
|
796
|
+
const message = toPlainObject(payload.message);
|
|
797
|
+
const role = pickFirstString(payload.role, message.role);
|
|
798
|
+
const toolNamesById = state.toolNamesById;
|
|
799
|
+
const events = [];
|
|
800
|
+
|
|
801
|
+
if (eventType === 'session.start' || eventType === 'init') {
|
|
802
|
+
events.push(createStructuredTraceEvent('opencode', 'thread', eventType, '[会话] OpenCode 已开始处理', {
|
|
803
|
+
phase: 'started',
|
|
804
|
+
status: 'started',
|
|
805
|
+
sessionId: pickFirstString(payload.session_id, payload.sessionID)
|
|
806
|
+
}));
|
|
807
|
+
return events.filter(Boolean);
|
|
808
|
+
}
|
|
809
|
+
if (eventType === 'message' || eventType === 'assistant' || eventType === 'assistant_message' || eventType === 'text') {
|
|
810
|
+
if (role && role !== 'assistant') {
|
|
811
|
+
return [];
|
|
812
|
+
}
|
|
813
|
+
if (payload.delta === true) {
|
|
814
|
+
return [];
|
|
815
|
+
}
|
|
816
|
+
const detail = collectStructuredText(message.content || payload.content || payload.text || payload);
|
|
817
|
+
if (!detail) {
|
|
818
|
+
return [];
|
|
819
|
+
}
|
|
820
|
+
events.push(createStructuredTraceEvent('opencode', 'agent_message', eventType, `[说明] ${detail}`, {
|
|
821
|
+
phase: 'completed',
|
|
822
|
+
status: 'completed',
|
|
823
|
+
detail
|
|
824
|
+
}));
|
|
825
|
+
return events.filter(Boolean);
|
|
826
|
+
}
|
|
827
|
+
if (eventType === 'tool_use' || eventType === 'step_start') {
|
|
828
|
+
const toolName = pickFirstString(payload.tool_name, payload.name, payload.tool, payload.step, payload.tool_id, 'tool');
|
|
829
|
+
const toolId = pickFirstString(payload.tool_id, payload.id);
|
|
830
|
+
if (toolId) {
|
|
831
|
+
toolNamesById.set(toolId, toolName);
|
|
832
|
+
}
|
|
833
|
+
const argumentsValue = toPlainObject(payload.parameters || payload.input || payload.arguments);
|
|
834
|
+
const summary = summarizeTraceArguments(argumentsValue);
|
|
835
|
+
events.push(createStructuredTraceEvent(
|
|
836
|
+
'opencode',
|
|
837
|
+
'tool',
|
|
838
|
+
eventType,
|
|
839
|
+
summary ? `[工具开始] ${toolName} (${summary})` : `[工具开始] ${toolName}`,
|
|
840
|
+
{
|
|
841
|
+
phase: 'started',
|
|
842
|
+
status: pickFirstString(payload.status, 'in_progress'),
|
|
843
|
+
toolName,
|
|
844
|
+
toolId,
|
|
845
|
+
arguments: argumentsValue,
|
|
846
|
+
argumentSummary: summary
|
|
847
|
+
}
|
|
848
|
+
));
|
|
849
|
+
return events.filter(Boolean);
|
|
850
|
+
}
|
|
851
|
+
if (eventType === 'tool_result' || eventType === 'step_finish') {
|
|
852
|
+
const toolId = pickFirstString(payload.tool_id, payload.id);
|
|
853
|
+
const toolName = pickFirstString(toolNamesById.get(toolId), payload.tool_name, payload.name, payload.tool, toolId, 'tool');
|
|
854
|
+
const status = pickFirstString(payload.status, payload.state, 'completed');
|
|
855
|
+
events.push(createStructuredTraceEvent('opencode', 'tool', eventType, `[工具完成] ${toolName} (${status})`, {
|
|
856
|
+
phase: 'completed',
|
|
857
|
+
status,
|
|
858
|
+
toolName,
|
|
859
|
+
toolId,
|
|
860
|
+
result: collectStructuredText(payload.output || payload.result),
|
|
861
|
+
error: toPlainObject(payload.error)
|
|
862
|
+
}));
|
|
863
|
+
return events.filter(Boolean);
|
|
864
|
+
}
|
|
865
|
+
if (eventType === 'result') {
|
|
866
|
+
events.push(createStructuredTraceEvent('opencode', 'turn', eventType, '[回合] 响应完成', {
|
|
867
|
+
phase: 'completed',
|
|
868
|
+
status: pickFirstString(payload.status, 'completed')
|
|
869
|
+
}));
|
|
870
|
+
return events.filter(Boolean);
|
|
871
|
+
}
|
|
872
|
+
if (eventType === 'error') {
|
|
873
|
+
const detail = pickFirstString(payload.message, payload.error && payload.error.message);
|
|
874
|
+
events.push(createStructuredTraceEvent('opencode', 'error', eventType, detail ? `[错误] ${detail}` : '[错误] OpenCode 返回了错误事件', {
|
|
875
|
+
status: 'error',
|
|
876
|
+
detail
|
|
877
|
+
}));
|
|
878
|
+
return events.filter(Boolean);
|
|
879
|
+
}
|
|
880
|
+
return [];
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
function prepareStructuredTraceEvents(agentProgram, payload, state) {
|
|
884
|
+
if (!payload || typeof payload !== 'object') {
|
|
885
|
+
return [];
|
|
886
|
+
}
|
|
887
|
+
if (agentProgram === 'codex') {
|
|
888
|
+
const traceEvent = prepareCodexTraceEvent(payload);
|
|
889
|
+
return traceEvent ? [traceEvent] : [];
|
|
890
|
+
}
|
|
891
|
+
if (agentProgram === 'claude') {
|
|
892
|
+
return prepareClaudeTraceEvents(payload, state);
|
|
893
|
+
}
|
|
894
|
+
if (agentProgram === 'gemini') {
|
|
895
|
+
return prepareGeminiTraceEvents(payload, state);
|
|
896
|
+
}
|
|
897
|
+
if (agentProgram === 'opencode') {
|
|
898
|
+
return prepareOpenCodeTraceEvents(payload, state);
|
|
899
|
+
}
|
|
900
|
+
return [];
|
|
901
|
+
}
|
|
902
|
+
|
|
379
903
|
function prepareCodexTraceEvent(payload) {
|
|
380
904
|
if (!payload || typeof payload !== 'object') {
|
|
381
905
|
return null;
|
|
@@ -1424,7 +1948,9 @@ async function ensureWebContainer(ctx, state, containerInput) {
|
|
|
1424
1948
|
}
|
|
1425
1949
|
}
|
|
1426
1950
|
|
|
1427
|
-
async function execCommandInWebContainer(ctx, containerName, command) {
|
|
1951
|
+
async function execCommandInWebContainer(ctx, containerName, command, options = {}) {
|
|
1952
|
+
const opts = options && typeof options === 'object' ? options : {};
|
|
1953
|
+
const agentProgram = typeof opts.agentProgram === 'string' ? opts.agentProgram : '';
|
|
1428
1954
|
return await new Promise((resolve, reject) => {
|
|
1429
1955
|
const process = spawn(
|
|
1430
1956
|
ctx.dockerCmd,
|
|
@@ -1474,8 +2000,8 @@ async function execCommandInWebContainer(ctx, containerName, command) {
|
|
|
1474
2000
|
const clippedStdout = stdoutTruncated ? `${stdoutOutput}\n...[stdout-truncated]` : stdoutOutput;
|
|
1475
2001
|
const clippedStderr = stderrTruncated ? `${stderrOutput}\n...[stderr-truncated]` : stderrOutput;
|
|
1476
2002
|
const clippedRaw = `${clippedStdout}${clippedStdout && clippedStderr ? '\n' : ''}${clippedStderr}`;
|
|
1477
|
-
const
|
|
1478
|
-
const cleanOutputSource =
|
|
2003
|
+
const extractedAgentMessage = extractAgentMessageFromStructuredOutput(agentProgram, clippedStdout);
|
|
2004
|
+
const cleanOutputSource = extractedAgentMessage || clippedRaw;
|
|
1479
2005
|
const output = clipText(stripAnsi(cleanOutputSource).trim() || '(无输出)');
|
|
1480
2006
|
resolve({ exitCode, output });
|
|
1481
2007
|
});
|
|
@@ -1509,6 +2035,9 @@ async function execAgentInWebContainerStream(ctx, state, containerName, command,
|
|
|
1509
2035
|
let stderrTruncated = false;
|
|
1510
2036
|
let stdoutPending = '';
|
|
1511
2037
|
let stderrPending = '';
|
|
2038
|
+
const structuredTraceState = {
|
|
2039
|
+
toolNamesById: new Map()
|
|
2040
|
+
};
|
|
1512
2041
|
function appendChunk(chunk, target) {
|
|
1513
2042
|
if (!chunk) return;
|
|
1514
2043
|
const text = chunk.toString('utf-8');
|
|
@@ -1531,26 +2060,24 @@ async function execAgentInWebContainerStream(ctx, state, containerName, command,
|
|
|
1531
2060
|
if (!rawLine) {
|
|
1532
2061
|
return;
|
|
1533
2062
|
}
|
|
1534
|
-
if (agentProgram === 'codex') {
|
|
1535
|
-
|
|
1536
|
-
try {
|
|
1537
|
-
payload = JSON.parse(rawLine);
|
|
1538
|
-
} catch (e) {
|
|
1539
|
-
payload = null;
|
|
1540
|
-
}
|
|
2063
|
+
if (agentProgram === 'claude' || agentProgram === 'gemini' || agentProgram === 'codex' || agentProgram === 'opencode') {
|
|
2064
|
+
const payload = parseJsonObjectLine(rawLine);
|
|
1541
2065
|
if (payload) {
|
|
1542
|
-
const
|
|
1543
|
-
|
|
2066
|
+
const traceEvents = prepareStructuredTraceEvents(agentProgram, payload, structuredTraceState);
|
|
2067
|
+
traceEvents.forEach(traceEvent => {
|
|
2068
|
+
if (!traceEvent || !traceEvent.text) {
|
|
2069
|
+
return;
|
|
2070
|
+
}
|
|
1544
2071
|
onEvent({
|
|
1545
2072
|
type: 'trace',
|
|
1546
2073
|
stream: 'stdout',
|
|
1547
2074
|
text: traceEvent.text,
|
|
1548
2075
|
traceEvent
|
|
1549
2076
|
});
|
|
1550
|
-
}
|
|
2077
|
+
});
|
|
1551
2078
|
return;
|
|
1552
2079
|
}
|
|
1553
|
-
if (/^OpenAI Codex\b/.test(rawLine) || /^tokens used\b/i.test(rawLine)) {
|
|
2080
|
+
if (agentProgram === 'codex' && (/^OpenAI Codex\b/.test(rawLine) || /^tokens used\b/i.test(rawLine))) {
|
|
1554
2081
|
return;
|
|
1555
2082
|
}
|
|
1556
2083
|
}
|
|
@@ -1614,8 +2141,8 @@ async function execAgentInWebContainerStream(ctx, state, containerName, command,
|
|
|
1614
2141
|
const clippedStdout = stdoutTruncated ? `${stdoutOutput}\n...[stdout-truncated]` : stdoutOutput;
|
|
1615
2142
|
const clippedStderr = stderrTruncated ? `${stderrOutput}\n...[stderr-truncated]` : stderrOutput;
|
|
1616
2143
|
const clippedRaw = `${clippedStdout}${clippedStdout && clippedStderr ? '\n' : ''}${clippedStderr}`;
|
|
1617
|
-
const
|
|
1618
|
-
const cleanOutputSource =
|
|
2144
|
+
const extractedAgentMessage = extractAgentMessageFromStructuredOutput(agentProgram, clippedStdout);
|
|
2145
|
+
const cleanOutputSource = extractedAgentMessage || clippedRaw;
|
|
1619
2146
|
const output = clipText(stripAnsi(cleanOutputSource).trim() || '(无输出)');
|
|
1620
2147
|
resolve({
|
|
1621
2148
|
exitCode,
|
|
@@ -2287,7 +2814,9 @@ async function handleWebApi(req, res, pathname, ctx, state) {
|
|
|
2287
2814
|
mode: 'agent',
|
|
2288
2815
|
contextMode
|
|
2289
2816
|
});
|
|
2290
|
-
const result = await execCommandInWebContainer(ctx, containerName, command
|
|
2817
|
+
const result = await execCommandInWebContainer(ctx, containerName, command, {
|
|
2818
|
+
agentProgram: agentMeta.agentProgram
|
|
2819
|
+
});
|
|
2291
2820
|
finalizeWebAgentExecution(state, containerName, history, agentMeta, {
|
|
2292
2821
|
contextMode,
|
|
2293
2822
|
resumeAttempted,
|