@yeaft/webchat-agent 0.1.124 → 0.1.125
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 +0 -224
- package/connection/message-router.js +2 -6
- package/conversation.js +8 -317
- package/crew/context-loader.js +171 -0
- package/crew.js +3 -0
- package/package.json +1 -1
- package/roleplay-dir.js +0 -305
- package/roleplay-i18n.js +0 -574
- package/roleplay-session.js +0 -184
- package/roleplay.js +0 -1061
package/claude.js
CHANGED
|
@@ -1,17 +1,6 @@
|
|
|
1
1
|
import { query, Stream } from './sdk/index.js';
|
|
2
2
|
import ctx from './context.js';
|
|
3
3
|
import { sendConversationList, sendOutput, sendError, handleAskUserQuestion } from './conversation.js';
|
|
4
|
-
import {
|
|
5
|
-
buildRolePlaySystemPrompt,
|
|
6
|
-
rolePlaySessions,
|
|
7
|
-
initRolePlayRouteState,
|
|
8
|
-
detectRoleSignal,
|
|
9
|
-
processRolePlayRoutes,
|
|
10
|
-
buildRouteEventMessage,
|
|
11
|
-
getRolePlayRouteState,
|
|
12
|
-
refreshCrewContext,
|
|
13
|
-
writeBackRouteContext
|
|
14
|
-
} from './roleplay.js';
|
|
15
4
|
|
|
16
5
|
/**
|
|
17
6
|
* Determine maxContextTokens and autoCompactThreshold from model name.
|
|
@@ -36,13 +25,11 @@ export function getModelContextConfig(modelName) {
|
|
|
36
25
|
export async function startClaudeQuery(conversationId, workDir, resumeSessionId) {
|
|
37
26
|
// 如果已存在,先保存 per-session 设置,再关闭
|
|
38
27
|
let savedDisallowedTools = null;
|
|
39
|
-
let savedRolePlayConfig = null;
|
|
40
28
|
let savedUserId = undefined;
|
|
41
29
|
let savedUsername = undefined;
|
|
42
30
|
if (ctx.conversations.has(conversationId)) {
|
|
43
31
|
const existing = ctx.conversations.get(conversationId);
|
|
44
32
|
savedDisallowedTools = existing.disallowedTools ?? null;
|
|
45
|
-
savedRolePlayConfig = existing.rolePlayConfig ?? null;
|
|
46
33
|
savedUserId = existing.userId;
|
|
47
34
|
savedUsername = existing.username;
|
|
48
35
|
if (existing.abortController) {
|
|
@@ -80,8 +67,6 @@ export async function startClaudeQuery(conversationId, workDir, resumeSessionId)
|
|
|
80
67
|
backgroundTasks: new Map(),
|
|
81
68
|
// Per-session 工具禁用设置
|
|
82
69
|
disallowedTools: savedDisallowedTools,
|
|
83
|
-
// Role Play config (for appendSystemPrompt injection)
|
|
84
|
-
rolePlayConfig: savedRolePlayConfig,
|
|
85
70
|
// 保留用户信息(从旧 state 恢复)
|
|
86
71
|
userId: savedUserId,
|
|
87
72
|
username: savedUsername,
|
|
@@ -111,18 +96,6 @@ export async function startClaudeQuery(conversationId, workDir, resumeSessionId)
|
|
|
111
96
|
console.log(`[SDK] Disallowed tools: ${effectiveDisallowedTools.join(', ')}`);
|
|
112
97
|
}
|
|
113
98
|
|
|
114
|
-
// Role Play: inject appendSystemPrompt with role descriptions and workflow
|
|
115
|
-
if (savedRolePlayConfig) {
|
|
116
|
-
options.appendSystemPrompt = buildRolePlaySystemPrompt(savedRolePlayConfig);
|
|
117
|
-
console.log(`[SDK] RolePlay appendSystemPrompt injected (teamType: ${savedRolePlayConfig.teamType})`);
|
|
118
|
-
|
|
119
|
-
// Initialize RolePlay route state if session exists
|
|
120
|
-
const rpSession = rolePlaySessions.get(conversationId);
|
|
121
|
-
if (rpSession) {
|
|
122
|
-
initRolePlayRouteState(rpSession, state);
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
|
|
126
99
|
// Validate session ID is a valid UUID before using it
|
|
127
100
|
const isValidUUID = (id) => {
|
|
128
101
|
if (!id) return false;
|
|
@@ -461,57 +434,6 @@ async function processClaudeOutput(conversationId, claudeQuery, state) {
|
|
|
461
434
|
continue;
|
|
462
435
|
}
|
|
463
436
|
|
|
464
|
-
// ★ RolePlay ROUTE detection: check accumulated text for ROUTE blocks
|
|
465
|
-
const rpSession = state.rolePlayConfig ? rolePlaySessions.get(conversationId) : null;
|
|
466
|
-
let roleplayAutoContinue = false;
|
|
467
|
-
let roleplayContinueRoles = [];
|
|
468
|
-
|
|
469
|
-
if (rpSession && rpSession._routeInitialized && state._roleplayAccumulated) {
|
|
470
|
-
const { routes, hasHumanRoute, continueRoles } = processRolePlayRoutes(
|
|
471
|
-
state._roleplayAccumulated, rpSession
|
|
472
|
-
);
|
|
473
|
-
|
|
474
|
-
if (routes.length > 0) {
|
|
475
|
-
// Send route events to frontend
|
|
476
|
-
for (const route of routes) {
|
|
477
|
-
const routeEvent = buildRouteEventMessage(
|
|
478
|
-
conversationId, rpSession.currentRole || 'unknown', route
|
|
479
|
-
);
|
|
480
|
-
ctx.sendToServer(routeEvent);
|
|
481
|
-
}
|
|
482
|
-
|
|
483
|
-
// ★ Write-back: persist route task info to .crew/context/features
|
|
484
|
-
const taskRoutes = routes.filter(r => r.taskId && r.summary);
|
|
485
|
-
if (taskRoutes.length > 0 && state.workDir) {
|
|
486
|
-
writeBackRouteContext(state.workDir, taskRoutes, rpSession.currentRole || 'unknown', rpSession);
|
|
487
|
-
}
|
|
488
|
-
|
|
489
|
-
// Send route state update
|
|
490
|
-
const routeState = getRolePlayRouteState(conversationId);
|
|
491
|
-
if (routeState) {
|
|
492
|
-
ctx.sendToServer({
|
|
493
|
-
type: 'roleplay_status',
|
|
494
|
-
conversationId,
|
|
495
|
-
...routeState
|
|
496
|
-
});
|
|
497
|
-
}
|
|
498
|
-
|
|
499
|
-
if (hasHumanRoute) {
|
|
500
|
-
// Stop auto-continue, wait for user input
|
|
501
|
-
ctx.sendToServer({
|
|
502
|
-
type: 'roleplay_waiting_human',
|
|
503
|
-
conversationId,
|
|
504
|
-
fromRole: rpSession.currentRole,
|
|
505
|
-
message: rpSession.waitingHumanContext?.message || ''
|
|
506
|
-
});
|
|
507
|
-
} else if (continueRoles.length > 0) {
|
|
508
|
-
// Auto-continue: pick the first route target and send the prompt
|
|
509
|
-
roleplayAutoContinue = true;
|
|
510
|
-
roleplayContinueRoles = continueRoles;
|
|
511
|
-
}
|
|
512
|
-
}
|
|
513
|
-
}
|
|
514
|
-
|
|
515
437
|
// ★ Turn 完成:发送 turn_completed,进程继续运行等待下一条消息
|
|
516
438
|
// stream-json 模式下 Claude 进程是持久运行的,for-await 在 result 后继续等待
|
|
517
439
|
// 不清空 state.query 和 state.inputStream,下次用户消息直接通过同一个 inputStream 发送
|
|
@@ -523,89 +445,6 @@ async function processClaudeOutput(conversationId, claudeQuery, state) {
|
|
|
523
445
|
// 不 await 会导致 encrypt 失败时消息静默丢失,前端卡在"思考中"
|
|
524
446
|
await sendOutput(conversationId, message);
|
|
525
447
|
|
|
526
|
-
// ★ RolePlay auto-continue: inject next role's prompt into the same conversation
|
|
527
|
-
if (roleplayAutoContinue && rpSession && state.inputStream) {
|
|
528
|
-
// Reset accumulated text for next turn
|
|
529
|
-
state._roleplayAccumulated = '';
|
|
530
|
-
|
|
531
|
-
for (const { to, prompt, taskId, taskTitle } of roleplayContinueRoles) {
|
|
532
|
-
rpSession.currentRole = to;
|
|
533
|
-
if (rpSession.roleStates[to]) {
|
|
534
|
-
rpSession.roleStates[to].status = 'active';
|
|
535
|
-
}
|
|
536
|
-
|
|
537
|
-
console.log(`[RolePlay] Auto-continuing to role: ${to}`);
|
|
538
|
-
|
|
539
|
-
// ★ Send roleplay_status with updated currentRole so frontend knows which role is active
|
|
540
|
-
ctx.sendToServer({
|
|
541
|
-
type: 'roleplay_status',
|
|
542
|
-
conversationId,
|
|
543
|
-
currentRole: rpSession.currentRole,
|
|
544
|
-
round: rpSession.round,
|
|
545
|
-
features: rpSession.features ? Array.from(rpSession.features.values()) : [],
|
|
546
|
-
roleStates: rpSession.roleStates || {},
|
|
547
|
-
waitingHuman: false
|
|
548
|
-
});
|
|
549
|
-
|
|
550
|
-
// ★ Pre-send compact check for RolePlay auto-continue
|
|
551
|
-
const rpAutoCompactThreshold = state.autoCompactThreshold || ctx.CONFIG?.autoCompactThreshold || 110000;
|
|
552
|
-
const rpEstimatedNewTokens = Math.ceil(prompt.length / 3);
|
|
553
|
-
// Include output_tokens: the assistant's output becomes part of context for the next turn
|
|
554
|
-
const rpOutputTokens = message.usage?.output_tokens || 0;
|
|
555
|
-
const rpEstimatedTotal = inputTokens + rpOutputTokens + rpEstimatedNewTokens;
|
|
556
|
-
|
|
557
|
-
if (rpEstimatedTotal > rpAutoCompactThreshold) {
|
|
558
|
-
console.log(`[RolePlay] Pre-send compact: estimated ${rpEstimatedTotal} tokens (input: ${inputTokens} + output: ${rpOutputTokens} + new: ~${rpEstimatedNewTokens}) exceeds threshold ${rpAutoCompactThreshold}`);
|
|
559
|
-
ctx.sendToServer({
|
|
560
|
-
type: 'compact_status',
|
|
561
|
-
conversationId,
|
|
562
|
-
status: 'compacting',
|
|
563
|
-
message: `Auto-compacting before RolePlay continue: estimated ${rpEstimatedTotal} tokens (threshold: ${rpAutoCompactThreshold})`
|
|
564
|
-
});
|
|
565
|
-
// Store pending message and compact first
|
|
566
|
-
const userMessage = {
|
|
567
|
-
type: 'user',
|
|
568
|
-
message: { role: 'user', content: prompt }
|
|
569
|
-
};
|
|
570
|
-
state._pendingUserMessage = userMessage;
|
|
571
|
-
state.turnActive = true;
|
|
572
|
-
state.turnResultReceived = false;
|
|
573
|
-
state.inputStream.enqueue({
|
|
574
|
-
type: 'user',
|
|
575
|
-
message: { role: 'user', content: '/compact' }
|
|
576
|
-
});
|
|
577
|
-
sendConversationList();
|
|
578
|
-
break;
|
|
579
|
-
}
|
|
580
|
-
|
|
581
|
-
// Re-activate the turn
|
|
582
|
-
state.turnActive = true;
|
|
583
|
-
state.turnResultReceived = false;
|
|
584
|
-
|
|
585
|
-
// Send the continuation prompt through the same input stream
|
|
586
|
-
const userMessage = {
|
|
587
|
-
type: 'user',
|
|
588
|
-
message: { role: 'user', content: prompt }
|
|
589
|
-
};
|
|
590
|
-
state._lastUserMessage = userMessage; // Save for prompt-overflow retry
|
|
591
|
-
sendOutput(conversationId, userMessage);
|
|
592
|
-
state.inputStream.enqueue(userMessage);
|
|
593
|
-
|
|
594
|
-
// RolePlay uses a single conversation — only one target can be active
|
|
595
|
-
// at a time. Additional route targets are ignored.
|
|
596
|
-
break;
|
|
597
|
-
}
|
|
598
|
-
|
|
599
|
-
// Send status update (don't send turn_completed yet since we're continuing)
|
|
600
|
-
sendConversationList();
|
|
601
|
-
continue;
|
|
602
|
-
}
|
|
603
|
-
|
|
604
|
-
// Reset accumulated text
|
|
605
|
-
if (state._roleplayAccumulated !== undefined) {
|
|
606
|
-
state._roleplayAccumulated = '';
|
|
607
|
-
}
|
|
608
|
-
|
|
609
448
|
await ctx.sendToServer({
|
|
610
449
|
type: 'turn_completed',
|
|
611
450
|
conversationId,
|
|
@@ -630,72 +469,9 @@ async function processClaudeOutput(conversationId, claudeQuery, state) {
|
|
|
630
469
|
continue;
|
|
631
470
|
}
|
|
632
471
|
|
|
633
|
-
// ★ RolePlay: accumulate assistant text and detect ROLE signals
|
|
634
|
-
if (state.rolePlayConfig && message.type === 'assistant' && message.message?.content) {
|
|
635
|
-
const content = message.message.content;
|
|
636
|
-
let textChunk = '';
|
|
637
|
-
if (typeof content === 'string') {
|
|
638
|
-
textChunk = content;
|
|
639
|
-
} else if (Array.isArray(content)) {
|
|
640
|
-
textChunk = content.filter(b => b.type === 'text').map(b => b.text).join('');
|
|
641
|
-
}
|
|
642
|
-
if (textChunk) {
|
|
643
|
-
if (state._roleplayAccumulated === undefined) {
|
|
644
|
-
state._roleplayAccumulated = '';
|
|
645
|
-
}
|
|
646
|
-
state._roleplayAccumulated += textChunk;
|
|
647
|
-
|
|
648
|
-
// Detect ROLE signal for current role tracking
|
|
649
|
-
const rpSession = rolePlaySessions.get(conversationId);
|
|
650
|
-
if (rpSession && rpSession._routeInitialized) {
|
|
651
|
-
const detectedRole = detectRoleSignal(textChunk);
|
|
652
|
-
if (detectedRole) {
|
|
653
|
-
const prevRole = rpSession.currentRole;
|
|
654
|
-
rpSession.currentRole = detectedRole;
|
|
655
|
-
if (rpSession.roleStates[detectedRole]) {
|
|
656
|
-
rpSession.roleStates[detectedRole].status = 'active';
|
|
657
|
-
}
|
|
658
|
-
if (prevRole && prevRole !== detectedRole && rpSession.roleStates[prevRole]) {
|
|
659
|
-
rpSession.roleStates[prevRole].status = 'idle';
|
|
660
|
-
}
|
|
661
|
-
console.log(`[RolePlay] Role switched: ${prevRole || 'none'} -> ${detectedRole}`);
|
|
662
|
-
|
|
663
|
-
// ★ Send roleplay_status so frontend fallbackRole stays in sync
|
|
664
|
-
ctx.sendToServer({
|
|
665
|
-
type: 'roleplay_status',
|
|
666
|
-
conversationId,
|
|
667
|
-
currentRole: rpSession.currentRole,
|
|
668
|
-
round: rpSession.round,
|
|
669
|
-
features: rpSession.features ? Array.from(rpSession.features.values()) : [],
|
|
670
|
-
roleStates: rpSession.roleStates || {},
|
|
671
|
-
waitingHuman: rpSession.waitingHuman || false
|
|
672
|
-
});
|
|
673
|
-
}
|
|
674
|
-
}
|
|
675
|
-
}
|
|
676
|
-
}
|
|
677
|
-
|
|
678
472
|
// 检测后台任务
|
|
679
473
|
detectAndTrackBackgroundTask(conversationId, state, message);
|
|
680
474
|
|
|
681
|
-
// ★ RolePlay: attach role metadata to messages sent to frontend
|
|
682
|
-
if (state.rolePlayConfig) {
|
|
683
|
-
const rpSession = rolePlaySessions.get(conversationId);
|
|
684
|
-
if (rpSession && rpSession._routeInitialized) {
|
|
685
|
-
// Attach current role info to the message as metadata
|
|
686
|
-
const enrichedMessage = {
|
|
687
|
-
...message,
|
|
688
|
-
_roleplay: {
|
|
689
|
-
role: rpSession.currentRole,
|
|
690
|
-
features: rpSession.features ? Array.from(rpSession.features.values()) : [],
|
|
691
|
-
round: rpSession.round
|
|
692
|
-
}
|
|
693
|
-
};
|
|
694
|
-
sendOutput(conversationId, enrichedMessage);
|
|
695
|
-
continue;
|
|
696
|
-
}
|
|
697
|
-
}
|
|
698
|
-
|
|
699
475
|
sendOutput(conversationId, message);
|
|
700
476
|
}
|
|
701
477
|
} catch (error) {
|
|
@@ -12,13 +12,13 @@ import {
|
|
|
12
12
|
createConversation, resumeConversation, deleteConversation,
|
|
13
13
|
handleRefreshConversation, handleCancelExecution,
|
|
14
14
|
handleUserInput, handleUpdateConversationSettings, handleAskUserAnswer,
|
|
15
|
-
sendConversationList
|
|
15
|
+
sendConversationList
|
|
16
16
|
} from '../conversation.js';
|
|
17
17
|
import {
|
|
18
18
|
createCrewSession, handleCrewHumanInput, handleCrewControl,
|
|
19
19
|
addRoleToSession, removeRoleFromSession,
|
|
20
20
|
handleListCrewSessions, handleCheckCrewExists, handleDeleteCrewDir, resumeCrewSession, removeFromCrewIndex, hideCrewSession,
|
|
21
|
-
handleLoadCrewHistory
|
|
21
|
+
handleLoadCrewHistory, handleCheckCrewContext
|
|
22
22
|
} from '../crew.js';
|
|
23
23
|
import { sendToServer, flushMessageBuffer } from './buffer.js';
|
|
24
24
|
import { handleRestartAgent, handleUpgradeAgent } from './upgrade.js';
|
|
@@ -70,10 +70,6 @@ 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
|
-
|
|
77
73
|
case 'resume_conversation':
|
|
78
74
|
await resumeConversation(msg);
|
|
79
75
|
break;
|