@yeaft/webchat-agent 0.1.73 → 0.1.75
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/crew/control.js +5 -0
- package/crew/role-output.js +36 -14
- package/crew/role-query.js +9 -3
- package/package.json +1 -1
package/crew/control.js
CHANGED
|
@@ -362,6 +362,11 @@ async function clearSession(session) {
|
|
|
362
362
|
|
|
363
363
|
session.round = 0;
|
|
364
364
|
|
|
365
|
+
// 重置计费统计(clearSession 清除了所有 claudeSessionId,后续 query 全新,费用从零开始)
|
|
366
|
+
session.costUsd = 0;
|
|
367
|
+
session.totalInputTokens = 0;
|
|
368
|
+
session.totalOutputTokens = 0;
|
|
369
|
+
|
|
365
370
|
const messagesPath = join(session.sharedDir, 'messages.json');
|
|
366
371
|
await fs.writeFile(messagesPath, '[]').catch(() => {});
|
|
367
372
|
await cleanupMessageShards(session.sharedDir);
|
package/crew/role-output.js
CHANGED
|
@@ -15,11 +15,40 @@ const getMaxContext = () => ctx.CONFIG?.maxContextTokens || 128000;
|
|
|
15
15
|
* 处理角色的流式输出
|
|
16
16
|
*/
|
|
17
17
|
export async function processRoleOutput(session, roleName, roleQuery, roleState) {
|
|
18
|
+
// 辅助函数:将 lastSeenUsage 结算到 session(用于 abort/error 场景,避免丢失 token)
|
|
19
|
+
function settleLastSeenUsage() {
|
|
20
|
+
if (!roleState.lastSeenUsage) return;
|
|
21
|
+
const { totalCostUsd, inputTokens, outputTokens } = roleState.lastSeenUsage;
|
|
22
|
+
if (totalCostUsd != null) {
|
|
23
|
+
const costDelta = totalCostUsd - roleState.lastCostUsd;
|
|
24
|
+
if (costDelta > 0) session.costUsd += costDelta;
|
|
25
|
+
roleState.lastCostUsd = totalCostUsd;
|
|
26
|
+
}
|
|
27
|
+
if (inputTokens != null || outputTokens != null) {
|
|
28
|
+
const inputDelta = (inputTokens || 0) - (roleState.lastInputTokens || 0);
|
|
29
|
+
const outputDelta = (outputTokens || 0) - (roleState.lastOutputTokens || 0);
|
|
30
|
+
if (inputDelta > 0) session.totalInputTokens += inputDelta;
|
|
31
|
+
if (outputDelta > 0) session.totalOutputTokens += outputDelta;
|
|
32
|
+
roleState.lastInputTokens = inputTokens || 0;
|
|
33
|
+
roleState.lastOutputTokens = outputTokens || 0;
|
|
34
|
+
}
|
|
35
|
+
roleState.lastSeenUsage = null;
|
|
36
|
+
}
|
|
37
|
+
|
|
18
38
|
try {
|
|
19
39
|
for await (const message of roleQuery) {
|
|
20
40
|
// 检查 session 是否已停止或暂停
|
|
21
41
|
if (session.status === 'stopped' || session.status === 'paused') break;
|
|
22
42
|
|
|
43
|
+
// 每次收到带 usage/cost 的消息,暂存到 lastSeenUsage(供 abort/error 结算)
|
|
44
|
+
if (message.total_cost_usd != null || message.usage) {
|
|
45
|
+
roleState.lastSeenUsage = {
|
|
46
|
+
totalCostUsd: message.total_cost_usd,
|
|
47
|
+
inputTokens: message.usage?.input_tokens,
|
|
48
|
+
outputTokens: message.usage?.output_tokens
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
23
52
|
if (message.type === 'system' && message.subtype === 'init') {
|
|
24
53
|
roleState.claudeSessionId = message.session_id;
|
|
25
54
|
console.log(`[Crew] ${roleName} session: ${message.session_id}`);
|
|
@@ -70,20 +99,8 @@ export async function processRoleOutput(session, roleName, roleQuery, roleState)
|
|
|
70
99
|
|
|
71
100
|
endRoleStreaming(session, roleName);
|
|
72
101
|
|
|
73
|
-
//
|
|
74
|
-
|
|
75
|
-
const costDelta = message.total_cost_usd - roleState.lastCostUsd;
|
|
76
|
-
if (costDelta > 0) session.costUsd += costDelta;
|
|
77
|
-
roleState.lastCostUsd = message.total_cost_usd;
|
|
78
|
-
}
|
|
79
|
-
if (message.usage) {
|
|
80
|
-
const inputDelta = (message.usage.input_tokens || 0) - (roleState.lastInputTokens || 0);
|
|
81
|
-
const outputDelta = (message.usage.output_tokens || 0) - (roleState.lastOutputTokens || 0);
|
|
82
|
-
if (inputDelta > 0) session.totalInputTokens += inputDelta;
|
|
83
|
-
if (outputDelta > 0) session.totalOutputTokens += outputDelta;
|
|
84
|
-
roleState.lastInputTokens = message.usage.input_tokens || 0;
|
|
85
|
-
roleState.lastOutputTokens = message.usage.output_tokens || 0;
|
|
86
|
-
}
|
|
102
|
+
// 更新费用(通过 settleLastSeenUsage 统一处理,避免重复逻辑)
|
|
103
|
+
settleLastSeenUsage();
|
|
87
104
|
|
|
88
105
|
// 持久化 sessionId
|
|
89
106
|
if (roleState.claudeSessionId) {
|
|
@@ -175,6 +192,8 @@ export async function processRoleOutput(session, roleName, roleQuery, roleState)
|
|
|
175
192
|
} catch (error) {
|
|
176
193
|
if (error.name === 'AbortError') {
|
|
177
194
|
console.log(`[Crew] ${roleName} aborted`);
|
|
195
|
+
// 结算 abort 前累积的 usage,避免丢失 token
|
|
196
|
+
settleLastSeenUsage();
|
|
178
197
|
if (session.status === 'paused' && roleState.accumulatedText) {
|
|
179
198
|
const routes = parseRoutes(roleState.accumulatedText);
|
|
180
199
|
if (routes.length > 0 && session.pendingRoutes.length === 0) {
|
|
@@ -186,6 +205,9 @@ export async function processRoleOutput(session, roleName, roleQuery, roleState)
|
|
|
186
205
|
} else {
|
|
187
206
|
console.error(`[Crew] ${roleName} error:`, error.message);
|
|
188
207
|
|
|
208
|
+
// 结算 error 前累积的 usage,避免丢失 token
|
|
209
|
+
settleLastSeenUsage();
|
|
210
|
+
|
|
189
211
|
// Step 1: 清理 roleState
|
|
190
212
|
endRoleStreaming(session, roleName);
|
|
191
213
|
const errorTurnText = roleState.accumulatedText;
|
package/crew/role-query.js
CHANGED
|
@@ -127,6 +127,11 @@ export async function createRoleQuery(session, roleName) {
|
|
|
127
127
|
options: queryOptions
|
|
128
128
|
});
|
|
129
129
|
|
|
130
|
+
// resume 场景:保留已有 roleState 的 baseline,避免双重计算
|
|
131
|
+
// 只有 fresh query(无 savedSessionId)才用 0 初始化
|
|
132
|
+
const existingState = session.roleStates.get(roleName);
|
|
133
|
+
const isResume = !!savedSessionId;
|
|
134
|
+
|
|
130
135
|
const roleState = {
|
|
131
136
|
query: roleQuery,
|
|
132
137
|
inputStream,
|
|
@@ -134,9 +139,10 @@ export async function createRoleQuery(session, roleName) {
|
|
|
134
139
|
accumulatedText: '',
|
|
135
140
|
turnActive: false,
|
|
136
141
|
claudeSessionId: savedSessionId,
|
|
137
|
-
lastCostUsd: 0,
|
|
138
|
-
lastInputTokens: 0,
|
|
139
|
-
lastOutputTokens: 0,
|
|
142
|
+
lastCostUsd: (isResume && existingState?.lastCostUsd) || 0,
|
|
143
|
+
lastInputTokens: (isResume && existingState?.lastInputTokens) || 0,
|
|
144
|
+
lastOutputTokens: (isResume && existingState?.lastOutputTokens) || 0,
|
|
145
|
+
lastSeenUsage: null,
|
|
140
146
|
consecutiveErrors: 0,
|
|
141
147
|
lastDispatchContent: null,
|
|
142
148
|
lastDispatchFrom: null,
|