@xcanwin/manyoyo 6.2.7 → 6.2.9
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/web/frontend/app.js +19 -37
- package/lib/web/frontend/chat-behavior.js +59 -1
- package/lib/web/server.js +121 -9
- package/package.json +1 -1
package/lib/web/frontend/app.js
CHANGED
|
@@ -387,35 +387,6 @@
|
|
|
387
387
|
return status;
|
|
388
388
|
}
|
|
389
389
|
|
|
390
|
-
function buildStructuredTraceResidualLines(message) {
|
|
391
|
-
const lines = String(message && message.content ? message.content : '')
|
|
392
|
-
.split('\n')
|
|
393
|
-
.map(function (line) {
|
|
394
|
-
return String(line || '').trim();
|
|
395
|
-
})
|
|
396
|
-
.filter(Boolean);
|
|
397
|
-
const traceEvents = Array.isArray(message && message.traceEvents) ? message.traceEvents : [];
|
|
398
|
-
const consumed = new Map();
|
|
399
|
-
traceEvents.forEach(function (traceEvent) {
|
|
400
|
-
const key = traceEvent && traceEvent.text ? String(traceEvent.text).trim() : '';
|
|
401
|
-
if (!key) {
|
|
402
|
-
return;
|
|
403
|
-
}
|
|
404
|
-
consumed.set(key, (consumed.get(key) || 0) + 1);
|
|
405
|
-
});
|
|
406
|
-
return lines.filter(function (line) {
|
|
407
|
-
if (!line || line === '[执行过程]') {
|
|
408
|
-
return false;
|
|
409
|
-
}
|
|
410
|
-
const remaining = consumed.get(line) || 0;
|
|
411
|
-
if (remaining > 0) {
|
|
412
|
-
consumed.set(line, remaining - 1);
|
|
413
|
-
return false;
|
|
414
|
-
}
|
|
415
|
-
return true;
|
|
416
|
-
});
|
|
417
|
-
}
|
|
418
|
-
|
|
419
390
|
function resolveTraceTone(traceEvent) {
|
|
420
391
|
const kind = traceEvent && traceEvent.kind ? String(traceEvent.kind) : '';
|
|
421
392
|
if (kind === 'command') return 'command';
|
|
@@ -586,20 +557,24 @@
|
|
|
586
557
|
const container = document.createElement('div');
|
|
587
558
|
container.className = 'trace-structured';
|
|
588
559
|
|
|
560
|
+
const messageId = message && message.id ? message.id : '';
|
|
561
|
+
const pending = Boolean(state.agentRun.active && state.agentRun.traceMessageId === messageId);
|
|
562
|
+
|
|
589
563
|
const flow = document.createElement('div');
|
|
590
564
|
flow.className = 'trace-flow';
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
565
|
+
if (pending) {
|
|
566
|
+
window.ManyoyoChatBehavior.buildStructuredTraceResidualLines(message).forEach(function (line) {
|
|
567
|
+
flow.appendChild(createResidualTraceCard(line));
|
|
568
|
+
});
|
|
569
|
+
}
|
|
594
570
|
const traceEvents = Array.isArray(message && message.traceEvents) ? message.traceEvents : [];
|
|
595
|
-
|
|
571
|
+
const mergedTraceEvents = window.ManyoyoChatBehavior.mergeToolTraceEvents(traceEvents);
|
|
572
|
+
mergedTraceEvents.forEach(function (traceEvent) {
|
|
596
573
|
flow.appendChild(createTraceEventCard(traceEvent));
|
|
597
574
|
});
|
|
598
575
|
|
|
599
|
-
const
|
|
600
|
-
const
|
|
601
|
-
const summaryInfo = window.ManyoyoChatBehavior.summarizeTraceFlow(traceEvents, { pending });
|
|
602
|
-
const hasError = traceEvents.some(function (event) {
|
|
576
|
+
const summaryInfo = window.ManyoyoChatBehavior.summarizeTraceFlow(mergedTraceEvents, { pending });
|
|
577
|
+
const hasError = mergedTraceEvents.some(function (event) {
|
|
603
578
|
return event && event.kind === 'error';
|
|
604
579
|
});
|
|
605
580
|
const defaultOpen = hasError;
|
|
@@ -2108,6 +2083,13 @@
|
|
|
2108
2083
|
{ label: '最近 resume', value: lastResumeText },
|
|
2109
2084
|
{ label: '最近结果', value: detail.lastResumeOk == null ? '暂无' : (detail.lastResumeOk ? '成功' : '失败'), tone: detail.lastResumeOk == null ? 'info' : (detail.lastResumeOk ? 'ok' : 'danger') }
|
|
2110
2085
|
]);
|
|
2086
|
+
renderKeyValueCard(detailSummary, '用量统计', detail.usageTotal ? [
|
|
2087
|
+
{ label: '累计输入 tokens', value: String(detail.usageTotal.inputTokens) },
|
|
2088
|
+
{ label: '累计输出 tokens', value: String(detail.usageTotal.outputTokens) },
|
|
2089
|
+
{ label: '累计花费', value: typeof detail.usageTotal.costUsd === 'number' ? `$${detail.usageTotal.costUsd.toFixed(4)}` : '暂不支持' }
|
|
2090
|
+
] : [
|
|
2091
|
+
{ label: '状态', value: '暂无数据(当前 Agent 程序不支持用量统计,或还未执行过对话)', tone: 'info' }
|
|
2092
|
+
]);
|
|
2111
2093
|
renderKeyValueCard(detailSummary, '最近活动', [
|
|
2112
2094
|
{ label: '最近角色', value: latestRoleLabel },
|
|
2113
2095
|
{ label: '最近时间', value: latestTimestampText },
|
|
@@ -22,6 +22,62 @@
|
|
|
22
22
|
return trimmed ? `${trimmed} · MANYOYO Web` : 'MANYOYO Web';
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
+
function buildStructuredTraceResidualLines(message) {
|
|
26
|
+
const lines = String(message && message.content ? message.content : '')
|
|
27
|
+
.split('\n')
|
|
28
|
+
.map(line => String(line || '').trim())
|
|
29
|
+
.filter(Boolean);
|
|
30
|
+
const traceEvents = Array.isArray(message && message.traceEvents) ? message.traceEvents : [];
|
|
31
|
+
const consumed = new Map();
|
|
32
|
+
traceEvents.forEach(traceEvent => {
|
|
33
|
+
const text = traceEvent && traceEvent.text ? String(traceEvent.text) : '';
|
|
34
|
+
if (!text) {
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
text.split('\n').forEach(subLine => {
|
|
38
|
+
const key = String(subLine || '').trim();
|
|
39
|
+
if (!key) {
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
consumed.set(key, (consumed.get(key) || 0) + 1);
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
return lines.filter(line => {
|
|
46
|
+
if (!line || line === '[执行过程]') {
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
const remaining = consumed.get(line) || 0;
|
|
50
|
+
if (remaining > 0) {
|
|
51
|
+
consumed.set(line, remaining - 1);
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
return true;
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const MERGEABLE_TRACE_KINDS = new Set(['tool', 'command', 'mcp']);
|
|
59
|
+
|
|
60
|
+
function mergeToolTraceEvents(traceEvents) {
|
|
61
|
+
const events = Array.isArray(traceEvents) ? traceEvents : [];
|
|
62
|
+
const result = [];
|
|
63
|
+
const indexByKey = new Map();
|
|
64
|
+
events.forEach(event => {
|
|
65
|
+
const kind = event && event.kind ? String(event.kind) : '';
|
|
66
|
+
const toolId = event && event.toolId ? String(event.toolId) : '';
|
|
67
|
+
if (MERGEABLE_TRACE_KINDS.has(kind) && toolId) {
|
|
68
|
+
const key = `${kind}:${toolId}`;
|
|
69
|
+
if (indexByKey.has(key)) {
|
|
70
|
+
const index = indexByKey.get(key);
|
|
71
|
+
result[index] = Object.assign({}, result[index], event);
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
indexByKey.set(key, result.length);
|
|
75
|
+
}
|
|
76
|
+
result.push(event);
|
|
77
|
+
});
|
|
78
|
+
return result;
|
|
79
|
+
}
|
|
80
|
+
|
|
25
81
|
function mergeTraceIntoReply(messages) {
|
|
26
82
|
const list = Array.isArray(messages) ? messages : [];
|
|
27
83
|
const result = [];
|
|
@@ -44,6 +100,8 @@
|
|
|
44
100
|
isNearBottom,
|
|
45
101
|
summarizeTraceFlow,
|
|
46
102
|
buildDocumentTitle,
|
|
47
|
-
mergeTraceIntoReply
|
|
103
|
+
mergeTraceIntoReply,
|
|
104
|
+
mergeToolTraceEvents,
|
|
105
|
+
buildStructuredTraceResidualLines
|
|
48
106
|
};
|
|
49
107
|
}());
|
package/lib/web/server.js
CHANGED
|
@@ -189,7 +189,25 @@ function createEmptyWebAgentSession(agentId, agentName) {
|
|
|
189
189
|
lastResumeAt: null,
|
|
190
190
|
lastResumeOk: null,
|
|
191
191
|
lastResumeError: '',
|
|
192
|
-
engineSessionId: ''
|
|
192
|
+
engineSessionId: '',
|
|
193
|
+
usageTotal: null
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function normalizeWebAgentUsageTotal(value) {
|
|
198
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
199
|
+
return null;
|
|
200
|
+
}
|
|
201
|
+
if (typeof value.inputTokens !== 'number' || typeof value.outputTokens !== 'number') {
|
|
202
|
+
return null;
|
|
203
|
+
}
|
|
204
|
+
if (typeof value.costUsd !== 'number' && value.costUsd !== null) {
|
|
205
|
+
return null;
|
|
206
|
+
}
|
|
207
|
+
return {
|
|
208
|
+
inputTokens: value.inputTokens,
|
|
209
|
+
outputTokens: value.outputTokens,
|
|
210
|
+
costUsd: typeof value.costUsd === 'number' ? value.costUsd : null
|
|
193
211
|
};
|
|
194
212
|
}
|
|
195
213
|
|
|
@@ -218,7 +236,8 @@ function normalizeWebAgentSessionRecord(agentId, rawAgent) {
|
|
|
218
236
|
lastResumeAt: typeof source.lastResumeAt === 'string' ? source.lastResumeAt : null,
|
|
219
237
|
lastResumeOk: typeof source.lastResumeOk === 'boolean' ? source.lastResumeOk : null,
|
|
220
238
|
lastResumeError: typeof source.lastResumeError === 'string' ? source.lastResumeError : '',
|
|
221
|
-
engineSessionId: typeof source.engineSessionId === 'string' ? source.engineSessionId : ''
|
|
239
|
+
engineSessionId: typeof source.engineSessionId === 'string' ? source.engineSessionId : '',
|
|
240
|
+
usageTotal: normalizeWebAgentUsageTotal(source.usageTotal)
|
|
222
241
|
};
|
|
223
242
|
}
|
|
224
243
|
|
|
@@ -1039,6 +1058,86 @@ function extractEngineSessionId(agentProgram, text) {
|
|
|
1039
1058
|
return '';
|
|
1040
1059
|
}
|
|
1041
1060
|
|
|
1061
|
+
function extractClaudeTurnUsage(text) {
|
|
1062
|
+
let result = null;
|
|
1063
|
+
for (const rawLine of String(text || '').split('\n')) {
|
|
1064
|
+
const payload = parseJsonObjectLine(rawLine);
|
|
1065
|
+
if (!payload || payload.type !== 'result') {
|
|
1066
|
+
continue;
|
|
1067
|
+
}
|
|
1068
|
+
const usage = toPlainObject(payload.usage);
|
|
1069
|
+
result = {
|
|
1070
|
+
inputTokens: typeof usage.input_tokens === 'number' ? usage.input_tokens : null,
|
|
1071
|
+
outputTokens: typeof usage.output_tokens === 'number' ? usage.output_tokens : null,
|
|
1072
|
+
costUsd: typeof payload.total_cost_usd === 'number' ? payload.total_cost_usd : null
|
|
1073
|
+
};
|
|
1074
|
+
}
|
|
1075
|
+
return result;
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
function extractCodexTurnUsage(text) {
|
|
1079
|
+
let result = null;
|
|
1080
|
+
for (const rawLine of String(text || '').split('\n')) {
|
|
1081
|
+
const payload = parseJsonObjectLine(rawLine);
|
|
1082
|
+
if (!payload || payload.type !== 'turn.completed') {
|
|
1083
|
+
continue;
|
|
1084
|
+
}
|
|
1085
|
+
const usage = toPlainObject(payload.usage);
|
|
1086
|
+
result = {
|
|
1087
|
+
inputTokens: typeof usage.input_tokens === 'number' ? usage.input_tokens : null,
|
|
1088
|
+
outputTokens: typeof usage.output_tokens === 'number' ? usage.output_tokens : null,
|
|
1089
|
+
costUsd: null
|
|
1090
|
+
};
|
|
1091
|
+
}
|
|
1092
|
+
return result;
|
|
1093
|
+
}
|
|
1094
|
+
|
|
1095
|
+
function extractOpenCodeTurnUsage(text) {
|
|
1096
|
+
let result = null;
|
|
1097
|
+
for (const rawLine of String(text || '').split('\n')) {
|
|
1098
|
+
const payload = parseJsonObjectLine(rawLine);
|
|
1099
|
+
if (!payload || payload.type !== 'step_finish') {
|
|
1100
|
+
continue;
|
|
1101
|
+
}
|
|
1102
|
+
const part = toPlainObject(payload.part);
|
|
1103
|
+
const tokens = toPlainObject(part.tokens);
|
|
1104
|
+
result = {
|
|
1105
|
+
inputTokens: typeof tokens.input === 'number' ? tokens.input : null,
|
|
1106
|
+
outputTokens: typeof tokens.output === 'number' ? tokens.output : null,
|
|
1107
|
+
costUsd: typeof part.cost === 'number' ? part.cost : null
|
|
1108
|
+
};
|
|
1109
|
+
}
|
|
1110
|
+
return result;
|
|
1111
|
+
}
|
|
1112
|
+
|
|
1113
|
+
// Gemini 的非交互 stream-json 输出目前没有已确认的 usage/token 字段可解析,故不返回数据(而非猜测字段名)。
|
|
1114
|
+
function extractTurnUsage(agentProgram, text) {
|
|
1115
|
+
if (agentProgram === 'claude') {
|
|
1116
|
+
return extractClaudeTurnUsage(text);
|
|
1117
|
+
}
|
|
1118
|
+
if (agentProgram === 'codex') {
|
|
1119
|
+
return extractCodexTurnUsage(text);
|
|
1120
|
+
}
|
|
1121
|
+
if (agentProgram === 'opencode') {
|
|
1122
|
+
return extractOpenCodeTurnUsage(text);
|
|
1123
|
+
}
|
|
1124
|
+
return null;
|
|
1125
|
+
}
|
|
1126
|
+
|
|
1127
|
+
function accumulateUsageTotal(baseline, turnUsage) {
|
|
1128
|
+
const base = baseline && typeof baseline === 'object'
|
|
1129
|
+
? baseline
|
|
1130
|
+
: { inputTokens: 0, outputTokens: 0, costUsd: null };
|
|
1131
|
+
const baseCost = typeof base.costUsd === 'number' ? base.costUsd : null;
|
|
1132
|
+
const turnCost = typeof turnUsage.costUsd === 'number' ? turnUsage.costUsd : null;
|
|
1133
|
+
return {
|
|
1134
|
+
inputTokens: (base.inputTokens || 0) + (turnUsage.inputTokens || 0),
|
|
1135
|
+
outputTokens: (base.outputTokens || 0) + (turnUsage.outputTokens || 0),
|
|
1136
|
+
// 引擎从未上报过费用(如 Codex)时保持 null,不要伪装成"花费为 0"
|
|
1137
|
+
costUsd: baseCost === null && turnCost === null ? null : (baseCost || 0) + (turnCost || 0)
|
|
1138
|
+
};
|
|
1139
|
+
}
|
|
1140
|
+
|
|
1042
1141
|
function getAgentRuntimeMeta(template) {
|
|
1043
1142
|
const normalizedTemplate = normalizeAgentPromptCommandTemplate(template, 'agentPromptCommand');
|
|
1044
1143
|
const agentProgram = resolveAgentProgram(normalizedTemplate);
|
|
@@ -1483,6 +1582,7 @@ function prepareCodexTraceEvent(payload) {
|
|
|
1483
1582
|
const mcpServer = pickFirstString(item.server);
|
|
1484
1583
|
const mcpTool = pickFirstString(item.tool);
|
|
1485
1584
|
const itemStatus = pickFirstString(item.status);
|
|
1585
|
+
const toolId = pickFirstString(item.id);
|
|
1486
1586
|
|
|
1487
1587
|
function shortenText(value, maxChars = 140) {
|
|
1488
1588
|
const raw = clipText(stripAnsi(String(value || '')).replace(/\s+/g, ' ').trim(), maxChars);
|
|
@@ -1558,14 +1658,16 @@ function prepareCodexTraceEvent(payload) {
|
|
|
1558
1658
|
return createTraceEvent('tool', `[工具开始] ${toolName || 'tool_call'}`, {
|
|
1559
1659
|
phase: 'started',
|
|
1560
1660
|
status: pickDisplayStatus('in_progress'),
|
|
1561
|
-
toolName: toolName || 'tool_call'
|
|
1661
|
+
toolName: toolName || 'tool_call',
|
|
1662
|
+
toolId
|
|
1562
1663
|
});
|
|
1563
1664
|
}
|
|
1564
1665
|
if (itemType === 'command_execution') {
|
|
1565
1666
|
return createTraceEvent('command', `[命令开始] ${commandText || 'command_execution'}`, {
|
|
1566
1667
|
phase: 'started',
|
|
1567
1668
|
status: pickDisplayStatus('in_progress'),
|
|
1568
|
-
command: commandText || 'command_execution'
|
|
1669
|
+
command: commandText || 'command_execution',
|
|
1670
|
+
toolId
|
|
1569
1671
|
});
|
|
1570
1672
|
}
|
|
1571
1673
|
if (itemType === 'mcp_tool_call') {
|
|
@@ -1583,7 +1685,8 @@ function prepareCodexTraceEvent(payload) {
|
|
|
1583
1685
|
arguments: item.arguments && typeof item.arguments === 'object' && !Array.isArray(item.arguments)
|
|
1584
1686
|
? item.arguments
|
|
1585
1687
|
: null,
|
|
1586
|
-
argumentSummary: summary
|
|
1688
|
+
argumentSummary: summary,
|
|
1689
|
+
toolId
|
|
1587
1690
|
}
|
|
1588
1691
|
);
|
|
1589
1692
|
}
|
|
@@ -1612,7 +1715,8 @@ function prepareCodexTraceEvent(payload) {
|
|
|
1612
1715
|
return createTraceEvent('tool', `[工具完成] ${toolName || 'tool_call'}`, {
|
|
1613
1716
|
phase: 'completed',
|
|
1614
1717
|
status: pickDisplayStatus('completed'),
|
|
1615
|
-
toolName: toolName || 'tool_call'
|
|
1718
|
+
toolName: toolName || 'tool_call',
|
|
1719
|
+
toolId
|
|
1616
1720
|
});
|
|
1617
1721
|
}
|
|
1618
1722
|
if (itemType === 'command_execution') {
|
|
@@ -1622,7 +1726,8 @@ function prepareCodexTraceEvent(payload) {
|
|
|
1622
1726
|
status: pickDisplayStatus(suffix),
|
|
1623
1727
|
command: commandText || 'command_execution',
|
|
1624
1728
|
exitCode: typeof item.exit_code === 'number' ? item.exit_code : null,
|
|
1625
|
-
result: item.aggregated_output !== undefined ? item.aggregated_output : null
|
|
1729
|
+
result: item.aggregated_output !== undefined ? item.aggregated_output : null,
|
|
1730
|
+
toolId
|
|
1626
1731
|
});
|
|
1627
1732
|
}
|
|
1628
1733
|
if (itemType === 'mcp_tool_call') {
|
|
@@ -1642,7 +1747,8 @@ function prepareCodexTraceEvent(payload) {
|
|
|
1642
1747
|
: null,
|
|
1643
1748
|
argumentSummary: summary,
|
|
1644
1749
|
result: item.result !== undefined ? item.result : null,
|
|
1645
|
-
error: item.error !== undefined ? item.error : null
|
|
1750
|
+
error: item.error !== undefined ? item.error : null,
|
|
1751
|
+
toolId
|
|
1646
1752
|
}
|
|
1647
1753
|
);
|
|
1648
1754
|
}
|
|
@@ -1755,13 +1861,15 @@ async function prepareWebAgentExecution(ctx, state, sessionRef, prompt) {
|
|
|
1755
1861
|
}
|
|
1756
1862
|
|
|
1757
1863
|
function finalizeWebAgentExecution(state, sessionRef, agentSession, agentMeta, meta, result) {
|
|
1864
|
+
const turnUsage = extractTurnUsage(agentMeta.agentProgram, result.stdout);
|
|
1758
1865
|
appendWebSessionMessage(state.webHistoryDir, sessionRef, 'assistant', result.output, {
|
|
1759
1866
|
exitCode: result.exitCode,
|
|
1760
1867
|
mode: 'agent',
|
|
1761
1868
|
contextMode: meta.contextMode,
|
|
1762
1869
|
resumeAttempted: meta.resumeAttempted,
|
|
1763
1870
|
resumeSucceeded: meta.resumeSucceeded,
|
|
1764
|
-
interrupted: result.interrupted === true
|
|
1871
|
+
interrupted: result.interrupted === true,
|
|
1872
|
+
...(turnUsage ? { usage: turnUsage } : {})
|
|
1765
1873
|
});
|
|
1766
1874
|
const patch = {
|
|
1767
1875
|
lastResumeAt: meta.resumeAttempted ? new Date().toISOString() : (agentSession.lastResumeAt || null),
|
|
@@ -1772,6 +1880,9 @@ function finalizeWebAgentExecution(state, sessionRef, agentSession, agentMeta, m
|
|
|
1772
1880
|
if (engineSessionId) {
|
|
1773
1881
|
patch.engineSessionId = engineSessionId;
|
|
1774
1882
|
}
|
|
1883
|
+
if (turnUsage) {
|
|
1884
|
+
patch.usageTotal = accumulateUsageTotal(agentSession.usageTotal, turnUsage);
|
|
1885
|
+
}
|
|
1775
1886
|
patchWebAgentSessionState(state.webHistoryDir, sessionRef, patch);
|
|
1776
1887
|
}
|
|
1777
1888
|
|
|
@@ -3619,6 +3730,7 @@ function buildSessionDetail(ctx, state, containerMap, name) {
|
|
|
3619
3730
|
lastResumeAt: agentSession.lastResumeAt || null,
|
|
3620
3731
|
lastResumeOk: typeof agentSession.lastResumeOk === 'boolean' ? agentSession.lastResumeOk : null,
|
|
3621
3732
|
lastResumeError: agentSession.lastResumeError || '',
|
|
3733
|
+
usageTotal: agentSession.usageTotal || null,
|
|
3622
3734
|
applied
|
|
3623
3735
|
};
|
|
3624
3736
|
}
|