@yeaft/webchat-agent 0.1.90 → 0.1.92
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/claude.js +48 -2
- package/connection/message-router.js +5 -1
- package/conversation.js +43 -2
- package/package.json +1 -1
package/claude.js
CHANGED
|
@@ -147,6 +147,16 @@ export async function startClaudeQuery(conversationId, workDir, resumeSessionId)
|
|
|
147
147
|
return state;
|
|
148
148
|
}
|
|
149
149
|
|
|
150
|
+
/**
|
|
151
|
+
* Detect if an error message indicates prompt token count exceeded the model limit.
|
|
152
|
+
* Matches API errors like "prompt token count of 138392 exceeds the limit of 128000".
|
|
153
|
+
*/
|
|
154
|
+
export function isPromptTokenOverflow(errorMessage) {
|
|
155
|
+
if (!errorMessage) return false;
|
|
156
|
+
const msg = errorMessage.toLowerCase();
|
|
157
|
+
return msg.includes('prompt') && msg.includes('token') && (msg.includes('exceed') || msg.includes('limit'));
|
|
158
|
+
}
|
|
159
|
+
|
|
150
160
|
/**
|
|
151
161
|
* 检测并追踪后台任务(仅 Bash 和 Agent 任务)
|
|
152
162
|
* 普通工具调用(Read、Edit、Grep、Glob 等)不跟踪
|
|
@@ -508,10 +518,12 @@ async function processClaudeOutput(conversationId, claudeQuery, state) {
|
|
|
508
518
|
// ★ Pre-send compact check for RolePlay auto-continue
|
|
509
519
|
const rpAutoCompactThreshold = ctx.CONFIG?.autoCompactThreshold || 110000;
|
|
510
520
|
const rpEstimatedNewTokens = Math.ceil(prompt.length / 3);
|
|
511
|
-
|
|
521
|
+
// Include output_tokens: the assistant's output becomes part of context for the next turn
|
|
522
|
+
const rpOutputTokens = message.usage?.output_tokens || 0;
|
|
523
|
+
const rpEstimatedTotal = inputTokens + rpOutputTokens + rpEstimatedNewTokens;
|
|
512
524
|
|
|
513
525
|
if (rpEstimatedTotal > rpAutoCompactThreshold) {
|
|
514
|
-
console.log(`[RolePlay] Pre-send compact: estimated ${rpEstimatedTotal} tokens (
|
|
526
|
+
console.log(`[RolePlay] Pre-send compact: estimated ${rpEstimatedTotal} tokens (input: ${inputTokens} + output: ${rpOutputTokens} + new: ~${rpEstimatedNewTokens}) exceeds threshold ${rpAutoCompactThreshold}`);
|
|
515
527
|
ctx.sendToServer({
|
|
516
528
|
type: 'compact_status',
|
|
517
529
|
conversationId,
|
|
@@ -543,6 +555,7 @@ async function processClaudeOutput(conversationId, claudeQuery, state) {
|
|
|
543
555
|
type: 'user',
|
|
544
556
|
message: { role: 'user', content: prompt }
|
|
545
557
|
};
|
|
558
|
+
state._lastUserMessage = userMessage; // Save for prompt-overflow retry
|
|
546
559
|
sendOutput(conversationId, userMessage);
|
|
547
560
|
state.inputStream.enqueue(userMessage);
|
|
548
561
|
|
|
@@ -648,6 +661,39 @@ async function processClaudeOutput(conversationId, claudeQuery, state) {
|
|
|
648
661
|
} else if (resultHandled) {
|
|
649
662
|
// Turn 已正常完成,进程退出产生的 error 不发送给用户
|
|
650
663
|
console.warn(`[SDK] Ignoring post-result error for ${conversationId}: ${error.message}`);
|
|
664
|
+
} else if (isPromptTokenOverflow(error.message) && state.claudeSessionId && !state._compactRetried) {
|
|
665
|
+
// ★ 兜底:prompt token 溢出 → 自动 compact + 重试(而非暴露 raw API error 给用户)
|
|
666
|
+
console.warn(`[SDK] Prompt token overflow for ${conversationId}, auto-compact + retry`);
|
|
667
|
+
const savedSessionId = state.claudeSessionId;
|
|
668
|
+
const savedLastMsg = state._lastUserMessage;
|
|
669
|
+
|
|
670
|
+
ctx.sendToServer({
|
|
671
|
+
type: 'compact_status',
|
|
672
|
+
conversationId,
|
|
673
|
+
status: 'compacting',
|
|
674
|
+
message: 'Context too long, auto-compacting and retrying...'
|
|
675
|
+
});
|
|
676
|
+
|
|
677
|
+
// 重启 SDK(startClaudeQuery 会先 abort 当前 state,使 finally 中 isStale=true)
|
|
678
|
+
try {
|
|
679
|
+
const newState = await startClaudeQuery(conversationId, state.workDir, savedSessionId);
|
|
680
|
+
newState._compactRetried = true; // 防止无限重试
|
|
681
|
+
newState.turnActive = true;
|
|
682
|
+
newState.turnResultReceived = false;
|
|
683
|
+
|
|
684
|
+
// 先 compact,再重试原始消息(如果有的话)
|
|
685
|
+
if (savedLastMsg) {
|
|
686
|
+
newState._pendingUserMessage = savedLastMsg;
|
|
687
|
+
}
|
|
688
|
+
newState.inputStream.enqueue({
|
|
689
|
+
type: 'user',
|
|
690
|
+
message: { role: 'user', content: '/compact' }
|
|
691
|
+
});
|
|
692
|
+
sendConversationList();
|
|
693
|
+
} catch (retryError) {
|
|
694
|
+
console.error(`[SDK] Compact-retry failed for ${conversationId}:`, retryError.message);
|
|
695
|
+
sendError(conversationId, `Context too long. Auto-compact failed: ${retryError.message}`);
|
|
696
|
+
}
|
|
651
697
|
} else {
|
|
652
698
|
console.error(`[SDK] Error for ${conversationId}:`, error.message);
|
|
653
699
|
sendError(conversationId, error.message);
|
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
createConversation, resumeConversation, deleteConversation,
|
|
13
13
|
handleRefreshConversation, handleCancelExecution,
|
|
14
14
|
handleUserInput, handleUpdateConversationSettings, handleAskUserAnswer,
|
|
15
|
-
sendConversationList, handleCheckCrewContext
|
|
15
|
+
sendConversationList, handleCheckCrewContext, handleCheckRolePlaySessions
|
|
16
16
|
} from '../conversation.js';
|
|
17
17
|
import {
|
|
18
18
|
createCrewSession, handleCrewHumanInput, handleCrewControl,
|
|
@@ -70,6 +70,10 @@ export async function handleMessage(msg) {
|
|
|
70
70
|
handleCheckCrewContext(msg);
|
|
71
71
|
break;
|
|
72
72
|
|
|
73
|
+
case 'check_roleplay_sessions':
|
|
74
|
+
handleCheckRolePlaySessions(msg);
|
|
75
|
+
break;
|
|
76
|
+
|
|
73
77
|
case 'resume_conversation':
|
|
74
78
|
await resumeConversation(msg);
|
|
75
79
|
break;
|
package/conversation.js
CHANGED
|
@@ -3,6 +3,7 @@ import { loadSessionHistory } from './history.js';
|
|
|
3
3
|
import { startClaudeQuery } from './claude.js';
|
|
4
4
|
import { crewSessions, loadCrewIndex } from './crew.js';
|
|
5
5
|
import { rolePlaySessions, saveRolePlayIndex, removeRolePlaySession, loadRolePlayIndex, validateRolePlayConfig, initRolePlayRouteState, loadCrewContext, refreshCrewContext, initCrewContextMtimes } from './roleplay.js';
|
|
6
|
+
import { loadRolePlaySessionIndex } from './roleplay-session.js';
|
|
6
7
|
import { initRolePlayDir, writeSessionClaudeMd, generateSessionName, getDefaultRoles, getSessionDir } from './roleplay-dir.js';
|
|
7
8
|
import { addRolePlaySession, findRolePlaySessionByConversationId, setActiveRolePlaySession } from './roleplay-session.js';
|
|
8
9
|
|
|
@@ -674,11 +675,13 @@ export async function handleUserInput(msg) {
|
|
|
674
675
|
// ★ Pre-send compact check: estimate total tokens and compact before sending if needed
|
|
675
676
|
const autoCompactThreshold = ctx.CONFIG?.autoCompactThreshold || 110000;
|
|
676
677
|
const lastInputTokens = state.lastResultInputTokens || 0;
|
|
678
|
+
const lastOutputTokens = state.lastResultOutputTokens || 0;
|
|
677
679
|
const estimatedNewTokens = Math.ceil(effectivePrompt.length / 3); // conservative: ~3 chars per token
|
|
678
|
-
|
|
680
|
+
// Include output_tokens: the assistant's last output becomes part of context for the next turn
|
|
681
|
+
const estimatedTotal = lastInputTokens + lastOutputTokens + estimatedNewTokens;
|
|
679
682
|
|
|
680
683
|
if (estimatedTotal > autoCompactThreshold && state.inputStream) {
|
|
681
|
-
console.log(`[${conversationId}] Pre-send compact: estimated ${estimatedTotal} tokens (
|
|
684
|
+
console.log(`[${conversationId}] Pre-send compact: estimated ${estimatedTotal} tokens (input: ${lastInputTokens} + output: ${lastOutputTokens} + new: ~${estimatedNewTokens}) exceeds threshold ${autoCompactThreshold}`);
|
|
682
685
|
ctx.sendToServer({
|
|
683
686
|
type: 'compact_status',
|
|
684
687
|
conversationId,
|
|
@@ -701,6 +704,7 @@ export async function handleUserInput(msg) {
|
|
|
701
704
|
|
|
702
705
|
state.turnActive = true;
|
|
703
706
|
state.turnResultReceived = false; // 重置 per-turn 去重标志
|
|
707
|
+
state._lastUserMessage = userMessage; // Save for prompt-overflow retry
|
|
704
708
|
sendConversationList(); // 在 turnActive=true 后通知 server,确保 processing 状态正确
|
|
705
709
|
sendOutput(conversationId, displayMessage);
|
|
706
710
|
state.inputStream.enqueue(userMessage);
|
|
@@ -817,3 +821,40 @@ export function handleCheckCrewContext(msg) {
|
|
|
817
821
|
featureCount: crewContext.features.length,
|
|
818
822
|
});
|
|
819
823
|
}
|
|
824
|
+
|
|
825
|
+
/**
|
|
826
|
+
* Handle check_roleplay_sessions request — check if a directory has .roleplay/session.json
|
|
827
|
+
* and return the list of existing sessions for restore.
|
|
828
|
+
*/
|
|
829
|
+
export function handleCheckRolePlaySessions(msg) {
|
|
830
|
+
const { projectDir, requestId } = msg;
|
|
831
|
+
if (!projectDir) {
|
|
832
|
+
ctx.sendToServer({ type: 'roleplay_sessions_result', requestId, found: false, sessions: [] });
|
|
833
|
+
return;
|
|
834
|
+
}
|
|
835
|
+
const index = loadRolePlaySessionIndex(projectDir);
|
|
836
|
+
const activeSessions = index.sessions.filter(s => s.status === 'active');
|
|
837
|
+
if (activeSessions.length === 0) {
|
|
838
|
+
ctx.sendToServer({ type: 'roleplay_sessions_result', requestId, found: false, sessions: [] });
|
|
839
|
+
return;
|
|
840
|
+
}
|
|
841
|
+
ctx.sendToServer({
|
|
842
|
+
type: 'roleplay_sessions_result',
|
|
843
|
+
requestId,
|
|
844
|
+
found: true,
|
|
845
|
+
sessions: activeSessions.map(s => ({
|
|
846
|
+
name: s.name,
|
|
847
|
+
teamType: s.teamType,
|
|
848
|
+
language: s.language,
|
|
849
|
+
conversationId: s.conversationId,
|
|
850
|
+
roles: (s.roles || []).map(r => ({
|
|
851
|
+
name: r.name,
|
|
852
|
+
displayName: r.displayName,
|
|
853
|
+
icon: r.icon || '',
|
|
854
|
+
description: r.description || '',
|
|
855
|
+
})),
|
|
856
|
+
createdAt: s.createdAt,
|
|
857
|
+
updatedAt: s.updatedAt,
|
|
858
|
+
})),
|
|
859
|
+
});
|
|
860
|
+
}
|