|
|
@@ -15,11 +15,10 @@ const {
|
|
|
completeTodoKnowledge,
|
|
|
} = require('./official-office-knowledge-service');
|
|
|
const { createCustomerTaskOfficialSync } = require('../core/customer-task-official-sync');
|
|
|
-const { messageTimestamp, roomIdOf, isGroupMessage, evaluatePolledMessage } = require('../core/agent-poller-policy');
|
|
|
+const { messageTimestamp, roomIdOf, isGroupMessage, messageContent, evaluatePolledMessage } = require('../core/agent-poller-policy');
|
|
|
const { setActiveQiweiContext } = require('../core/credentials');
|
|
|
const { FmodeQiweiClient } = require('../providers/fmode-agent-transport');
|
|
|
const { responseMonitor } = require('./response-monitor-service');
|
|
|
-const { GroupAgentService } = require('./group-agent-service');
|
|
|
const { normalizeAllowlistIds, normalizeAllowlistContact, writeEnvValue } = require('../core/allowlist-config');
|
|
|
const { getProductMode } = require('../core/product-mode');
|
|
|
const { getRelayDaemonStatus } = require('../core/relay-daemon');
|
|
|
@@ -141,13 +140,27 @@ function loadAgentConfig(overrides = {}) {
|
|
|
value('QIWEI_AGENT_KNOWLEDGE_DIR'),
|
|
|
fs.existsSync(path.join(PROJECT_ROOT, 'knowledge')) ? path.join(PROJECT_ROOT, 'knowledge') : path.join(PACKAGE_ROOT, 'knowledge')
|
|
|
),
|
|
|
+ contextFiles: value('QIWEI_AGENT_CONTEXT_FILES', 'personality.md,context.md,rules.md').split(',').map(item => item.trim()).filter(Boolean),
|
|
|
+ contextCharLimit: number('QIWEI_AGENT_CONTEXT_CHAR_LIMIT', 6000, 1000, 20000),
|
|
|
propertyDataFile,
|
|
|
+ memory: {
|
|
|
+ enabled: bool('QIWEI_AGENT_MEMORY_ENABLED', true),
|
|
|
+ coreCharLimit: number('QIWEI_AGENT_MEMORY_CORE_CHAR_LIMIT', 4000, 500, 8000),
|
|
|
+ recallLimit: number('QIWEI_AGENT_MEMORY_RECALL_LIMIT', 6, 0, 20),
|
|
|
+ recentMessageLimit: number('QIWEI_AGENT_MEMORY_RECENT_MESSAGES', 12, 4, 40),
|
|
|
+ historyScanLimit: number('QIWEI_AGENT_MEMORY_HISTORY_SCAN_LIMIT', 240, 20, 1000),
|
|
|
+ extractionIntervalMs: number('QIWEI_AGENT_MEMORY_EXTRACTION_INTERVAL_MS', 1000, 250, 60000),
|
|
|
+ extractionRetryBaseMs: number('QIWEI_AGENT_MEMORY_EXTRACTION_RETRY_BASE_MS', 1000, 250, 60000),
|
|
|
+ extractionMaxAttempts: number('QIWEI_AGENT_MEMORY_EXTRACTION_MAX_ATTEMPTS', 3, 1, 10),
|
|
|
+ },
|
|
|
agent: {
|
|
|
provider,
|
|
|
apiKey: value('AGENT_API_KEY') || value(anthropic ? 'ANTHROPIC_AUTH_TOKEN' : 'OPENAI_API_KEY') || value('QIWEI_AUTO_REPLY_AI_KEY'),
|
|
|
baseUrl: (value('AGENT_BASE_URL') || value(anthropic ? 'ANTHROPIC_BASE_URL' : 'OPENAI_BASE_URL') || value('QIWEI_AUTO_REPLY_AI_BASE_URL') || (anthropic ? 'https://api.anthropic.com' : 'https://api.openai.com/v1')).replace(/\/$/, ''),
|
|
|
model: value('AGENT_MODEL') || value(anthropic || claudeCode ? 'ANTHROPIC_MODEL' : 'OPENAI_MODEL') || value('QIWEI_AUTO_REPLY_AI_MODEL') || (anthropic || claudeCode ? 'sonnet' : 'gpt-4.1-mini'),
|
|
|
maxToolRounds: number('AGENT_MAX_TOOL_ROUNDS', 4, 1, 8),
|
|
|
+ promptCharLimit: number('QIWEI_AGENT_PROMPT_CHAR_LIMIT', 16000, 2000, 50000),
|
|
|
+ systemPromptCharLimit: number('QIWEI_AGENT_SYSTEM_PROMPT_CHAR_LIMIT', 12000, 2000, 50000),
|
|
|
claudeExecutable: value('CLAUDE_CODE_EXECUTABLE') || path.join(path.dirname(process.execPath), 'node_modules', '@anthropic-ai', 'claude-code', 'bin', 'claude.exe'),
|
|
|
claudeWorkdir: resolvePath(value('CLAUDE_CODE_WORKDIR'), PROJECT_ROOT),
|
|
|
claudeSessionFile: resolvePath(value('CLAUDE_CODE_SESSION_FILE'), latestPath('messages', 'claude-code-sessions.json')),
|
|
|
@@ -159,6 +172,8 @@ function loadAgentConfig(overrides = {}) {
|
|
|
claudeBare: bool('CLAUDE_CODE_BARE', true),
|
|
|
claudeEffort: value('CLAUDE_CODE_EFFORT', 'low'),
|
|
|
claudeTools: value('CLAUDE_CODE_ALLOWED_TOOLS', 'Read,Glob,Grep'),
|
|
|
+ claudeSessionMaxTurns: number('CLAUDE_CODE_SESSION_MAX_TURNS', 12, 1, 100),
|
|
|
+ claudeSessionMaxAgeMs: number('CLAUDE_CODE_SESSION_MAX_AGE_MS', 86400000, 60000, 604800000),
|
|
|
},
|
|
|
qiwei: {
|
|
|
transport: 'fmode-gateway',
|
|
|
@@ -189,6 +204,7 @@ function loadAgentConfig(overrides = {}) {
|
|
|
const config = {
|
|
|
...baseConfig,
|
|
|
...overrides,
|
|
|
+ memory: { ...baseConfig.memory, ...(overrides.memory || {}) },
|
|
|
agent: { ...baseConfig.agent, ...(overrides.agent || {}) },
|
|
|
qiwei: { ...baseConfig.qiwei, ...(overrides.qiwei || {}) },
|
|
|
voice: { ...baseConfig.voice, ...(overrides.voice || {}) },
|
|
|
@@ -257,6 +273,24 @@ function backfillCustomerIntelligence(db) {
|
|
|
return { version, removed, profileFields, taskCount, alertCount };
|
|
|
}
|
|
|
|
|
|
+function backfillCustomerMemory(service) {
|
|
|
+ const version = '2';
|
|
|
+ const db = service?.db;
|
|
|
+ if (!db || !service.memory || db.getSetting('customer_memory_backfill_version', '') === version) {
|
|
|
+ return { skipped: true, conversations: 0, captured: 0 };
|
|
|
+ }
|
|
|
+ let conversations = 0;
|
|
|
+ let captured = 0;
|
|
|
+ for (const conversation of db.listConversations()) {
|
|
|
+ const result = service.memory.backfillConversation(conversation);
|
|
|
+ conversations += 1;
|
|
|
+ captured += Number(result.captured || 0);
|
|
|
+ }
|
|
|
+ db.setSetting('customer_memory_backfill_version', version);
|
|
|
+ db.audit({ actor: 'migration', action: 'customer_memory_backfilled', detail: { version, conversations, captured } });
|
|
|
+ return { version, conversations, captured };
|
|
|
+}
|
|
|
+
|
|
|
const delay = ms => new Promise(resolve => setTimeout(resolve, ms));
|
|
|
|
|
|
class QiweiAgentPoller {
|
|
|
@@ -361,17 +395,85 @@ class QiweiAgentPoller {
|
|
|
}
|
|
|
|
|
|
async process(message) {
|
|
|
- return ingestMessageForWorkbench({ config: this.config, db: this.db, service: this.service }, message, 'polling');
|
|
|
+ return ingestMessageForWorkbench({ config: this.config, db: this.db, service: this.service, qiwei: this.qiwei }, message, 'polling');
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+const OUTBOUND_NOTIFICATION_MSG_TYPE = 2001;
|
|
|
+const OUTBOUND_CONTENT_MSG_TYPES = new Set([0, 1, 2]);
|
|
|
+
|
|
|
+function outboundContactId(message = {}, config = {}) {
|
|
|
+ const senderId = String(message.senderId || '');
|
|
|
+ const receiverId = String(message.receiverId || '');
|
|
|
+ const allowedSenders = new Set((config.allowedSenders || []).map(String));
|
|
|
+ const selfUserIds = new Set([
|
|
|
+ String(config.selfUserId || ''),
|
|
|
+ String(config.userId || ''),
|
|
|
+ String(message.userId || ''),
|
|
|
+ ].filter(Boolean));
|
|
|
+ return selfUserIds.has(senderId) && allowedSenders.has(receiverId) ? receiverId : '';
|
|
|
+}
|
|
|
+
|
|
|
+function recordOutboundMessage(target, message, source, contactId) {
|
|
|
+ const content = messageContent(message);
|
|
|
+ if (!content) throw new Error('Outbound message content is empty');
|
|
|
+ const timestamp = messageTimestamp(message.timestamp);
|
|
|
+ if (!timestamp) throw new Error('Outbound message timestamp is invalid');
|
|
|
+ const existing = target.db.getConversationByContactId(contactId);
|
|
|
+ const conversation = target.db.ensureConversation(contactId, existing?.contact_name || message.receiverName || '白名单企微联系人');
|
|
|
+ const inserted = target.db.insertMessage({
|
|
|
+ conversationId: conversation.id,
|
|
|
+ externalId: String(message.msgServerId || message.msgUniqueIdentifier || `${message.senderId}:${message.seq}`),
|
|
|
+ direction: 'outbound',
|
|
|
+ senderType: 'human',
|
|
|
+ content,
|
|
|
+ status: 'sent',
|
|
|
+ createdAt: new Date(timestamp * 1000).toISOString(),
|
|
|
+ raw: { ...message, source },
|
|
|
+ });
|
|
|
+ if (inserted.created) {
|
|
|
+ target.db.audit({
|
|
|
+ actor: 'runtime',
|
|
|
+ action: 'outbound_message_captured',
|
|
|
+ conversationId: conversation.id,
|
|
|
+ entityId: inserted.message.id,
|
|
|
+ detail: { source, seq: Number(message.seq) || 0 },
|
|
|
+ });
|
|
|
+ target.service?.emit?.('change', { type: 'message', conversationId: conversation.id });
|
|
|
+ }
|
|
|
+ return { status: inserted.created ? 'outbound_recorded' : 'outbound_duplicate', message: inserted.message };
|
|
|
+}
|
|
|
+
|
|
|
+async function syncOutboundNotification(target, notification, source, contactId) {
|
|
|
+ if (!target.qiwei?.syncMessages) throw new Error('Outbound message sync is unavailable');
|
|
|
+ const notificationSeq = Number(notification.seq) || 0;
|
|
|
+ const senderId = String(notification.senderId || '');
|
|
|
+ const delays = [0, 250, 750, 1500];
|
|
|
+ for (const delayMs of delays) {
|
|
|
+ if (delayMs) await new Promise(resolve => setTimeout(resolve, delayMs));
|
|
|
+ const result = await target.qiwei.syncMessages(Math.max(0, notificationSeq - 1), 10);
|
|
|
+ const message = (result.syncMsgList || []).find(item => (
|
|
|
+ Number(item.seq) >= notificationSeq
|
|
|
+ && String(item.senderId || '') === senderId
|
|
|
+ && String(item.receiverId || '') === contactId
|
|
|
+ && OUTBOUND_CONTENT_MSG_TYPES.has(Number(item.msgType))
|
|
|
+ && Boolean(messageContent(item))
|
|
|
+ ));
|
|
|
+ if (message) return recordOutboundMessage(target, message, `${source}:outbound-sync`, contactId);
|
|
|
}
|
|
|
+ throw new Error(`Outbound message content is not available after notification seq=${notificationSeq}`);
|
|
|
}
|
|
|
|
|
|
async function ingestMessageForWorkbench(target, message = {}, source = 'callback') {
|
|
|
if (isGroupMessage(message)) {
|
|
|
- try { return await groupAgentService.ingestPolledMessage(message, target.config); }
|
|
|
- catch (error) {
|
|
|
- target.db.audit({ actor: 'agent', action: 'group_agent_failed', detail: { roomId: roomIdOf(message), source, message: error.message } });
|
|
|
- throw error;
|
|
|
- }
|
|
|
+ return { status: 'ignored_group_reply_disabled' };
|
|
|
+ }
|
|
|
+ const contactId = outboundContactId(message, target.config);
|
|
|
+ if (contactId && Number(message.msgType) === OUTBOUND_NOTIFICATION_MSG_TYPE) {
|
|
|
+ return syncOutboundNotification(target, message, source, contactId);
|
|
|
+ }
|
|
|
+ if (contactId && OUTBOUND_CONTENT_MSG_TYPES.has(Number(message.msgType)) && messageContent(message)) {
|
|
|
+ return recordOutboundMessage(target, message, source, contactId);
|
|
|
}
|
|
|
const candidate = evaluatePolledMessage(message, target.config);
|
|
|
if (!candidate.eligible) return { status: `ignored_${candidate.reason || 'ineligible'}` };
|
|
|
@@ -408,6 +510,8 @@ function createWorkbench(overrides = {}) {
|
|
|
const knowledge = overrides.knowledge || new AgentKnowledgeStore({
|
|
|
knowledgeDir: config.knowledgeDir,
|
|
|
propertyDataFile: config.propertyDataFile,
|
|
|
+ contextFiles: config.contextFiles,
|
|
|
+ contextCharLimit: config.contextCharLimit,
|
|
|
});
|
|
|
backfillPropertyRecommendations(db, knowledge);
|
|
|
backfillSentVoiceAudioPaths(db);
|
|
|
@@ -415,6 +519,7 @@ function createWorkbench(overrides = {}) {
|
|
|
const voice = overrides.voice || new VoiceCloneService({ config: config.voice, qiwei });
|
|
|
const agent = overrides.agent || new QiweiAgentRuntime({ config: config.agent, knowledge });
|
|
|
const service = overrides.service || new AgentWorkbenchService({ db, agent, qiwei, config });
|
|
|
+ backfillCustomerMemory(service);
|
|
|
const poller = overrides.poller || new QiweiAgentPoller({ config: config.qiwei, db, qiwei, service });
|
|
|
return { config, db, knowledge, qiwei, voice, agent, service, poller };
|
|
|
}
|
|
|
@@ -484,12 +589,6 @@ function activeAccountMetadata() {
|
|
|
};
|
|
|
}
|
|
|
|
|
|
-const groupAgentService = new GroupAgentService({
|
|
|
- projectRoot: PROJECT_ROOT,
|
|
|
- getRuntime: () => workbench,
|
|
|
- getAccount: () => activeAccountMetadata(),
|
|
|
-});
|
|
|
-
|
|
|
function applyActiveAccountContext(account) {
|
|
|
setActiveQiweiContext(account);
|
|
|
Object.assign(workbench.config.qiwei, account);
|
|
|
@@ -869,12 +968,31 @@ function publicConversation(row) {
|
|
|
lastRecommendedAt: item.last_recommended_at,
|
|
|
updatedAt: item.updated_at,
|
|
|
})),
|
|
|
+ memories: (detail.memories || []).map(item => ({
|
|
|
+ id: item.id,
|
|
|
+ type: item.type,
|
|
|
+ content: item.content,
|
|
|
+ confidence: item.confidence,
|
|
|
+ importance: item.importance,
|
|
|
+ status: item.status,
|
|
|
+ sourceMessageIds: item.source_message_ids || [],
|
|
|
+ direction: item.direction,
|
|
|
+ createdBy: item.created_by,
|
|
|
+ expiresAt: item.expires_at,
|
|
|
+ updatedAt: item.updated_at,
|
|
|
+ })),
|
|
|
+ memorySnapshot: detail.memorySnapshot ? {
|
|
|
+ version: detail.memorySnapshot.version,
|
|
|
+ coreChars: String(detail.memorySnapshot.compact_text || '').length,
|
|
|
+ createdAt: detail.memorySnapshot.created_at,
|
|
|
+ } : null,
|
|
|
summary: {
|
|
|
openTasks: customerTasks.filter(item => ['open', 'in_progress'].includes(item.status)).length,
|
|
|
openAlerts: customerAlerts.filter(item => item.status === 'open').length,
|
|
|
highAlerts: customerAlerts.filter(item => item.status === 'open' && ['high', 'critical'].includes(item.severity)).length,
|
|
|
recommendationCount: customerRecommendations.length,
|
|
|
pendingFeedbackCount: customerRecommendations.filter(item => ['candidate', 'recommended'].includes(item.status)).length,
|
|
|
+ activeMemories: (detail.memories || []).filter(item => item.status === 'active').length,
|
|
|
},
|
|
|
},
|
|
|
pendingReply: pending ? {
|
|
|
@@ -954,6 +1072,7 @@ async function ingestWebhookMessage(message = {}) {
|
|
|
config: workbench.config.qiwei,
|
|
|
db: workbench.db,
|
|
|
service: workbench.service,
|
|
|
+ qiwei: workbench.qiwei,
|
|
|
}, message, 'relay_callback');
|
|
|
}
|
|
|
|
|
|
@@ -1086,73 +1205,6 @@ function getResponseMonitor() {
|
|
|
};
|
|
|
}
|
|
|
|
|
|
-function getGroupAgents() {
|
|
|
- const groups = groupAgentService.list();
|
|
|
- return {
|
|
|
- status: 'ok',
|
|
|
- assistantMessage: `已读取 ${groups.length} 个客户群的智能回复状态`,
|
|
|
- summary: {
|
|
|
- groupCount: groups.length,
|
|
|
- pendingCount: groups.filter(item => item.pendingReply).length,
|
|
|
- errorCount: groups.filter(item => item.agentError || item.sendError).length,
|
|
|
- autoCount: groups.filter(item => item.mode === 'auto').length,
|
|
|
- },
|
|
|
- data: { groups },
|
|
|
- warnings: [],
|
|
|
- errors: [],
|
|
|
- };
|
|
|
-}
|
|
|
-
|
|
|
-async function generateGroupReply(roomId, options = {}) {
|
|
|
- try {
|
|
|
- const result = await groupAgentService.generate(roomId, options);
|
|
|
- return {
|
|
|
- status: 'ok',
|
|
|
- assistantMessage: result.status === 'no_reply_needed'
|
|
|
- ? 'Agent 判断这条群消息无需回复'
|
|
|
- : result.status === 'auto_sent' ? '群聊 Agent 已忽略风险并自动发送回复' : '群聊 Agent 已生成待审核草稿',
|
|
|
- data: result,
|
|
|
- };
|
|
|
- } catch {
|
|
|
- throw new Error('群聊 Agent 暂时不可用,消息已保留,可稍后重试或人工回复');
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-function changeGroupMode(roomId, mode, confirmation) {
|
|
|
- const group = groupAgentService.setMode(roomId, mode, confirmation);
|
|
|
- return {
|
|
|
- status: 'ok',
|
|
|
- assistantMessage: group.mode === 'auto'
|
|
|
- ? '群聊全自动模式已开启:后续 Agent 非空回复将忽略风险并直接发送'
|
|
|
- : '群聊已切回人工审核模式',
|
|
|
- data: { group },
|
|
|
- };
|
|
|
-}
|
|
|
-
|
|
|
-async function approveGroupDraft(roomId, draftId, content) {
|
|
|
- try {
|
|
|
- const result = await groupAgentService.approve(roomId, draftId, content);
|
|
|
- return { status: 'ok', assistantMessage: '群聊回复已真实发送', data: result };
|
|
|
- } catch (error) {
|
|
|
- if (/不存在|已经是|不能为空|过长|尚未确认为/.test(error.message)) throw error;
|
|
|
- throw new Error('群聊发送失败,请确认企微账号在线后重试');
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-function rejectGroupDraft(roomId, draftId, reason) {
|
|
|
- const result = groupAgentService.reject(roomId, draftId, reason);
|
|
|
- return { status: 'ok', assistantMessage: '群聊草稿已驳回', data: result };
|
|
|
-}
|
|
|
-
|
|
|
-async function regenerateGroupDraft(roomId, draftId) {
|
|
|
- try {
|
|
|
- const result = await groupAgentService.regenerate(roomId, draftId);
|
|
|
- return { status: 'ok', assistantMessage: '群聊 Agent 已重新生成待审核草稿', data: result };
|
|
|
- } catch {
|
|
|
- throw new Error('群聊 Agent 暂时不可用,原草稿已保留');
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
function updateCustomerProfile(conversationId, input = {}) {
|
|
|
const conversation = workbench.db.getConversation(conversationId);
|
|
|
if (!conversation) throw new Error('客户会话不存在');
|
|
|
@@ -1179,6 +1231,7 @@ function updateCustomerProfile(conversationId, input = {}) {
|
|
|
? current.tags
|
|
|
: [...new Set((Array.isArray(input.tags) ? input.tags : String(input.tags || '').split(/[,,]/)).map(item => String(item).trim()).filter(Boolean))];
|
|
|
const updated = workbench.db.updateProfile(conversationId, nextProfile, tags);
|
|
|
+ const memory = workbench.service.memory.syncHumanProfile(conversationId, patch);
|
|
|
workbench.db.audit({
|
|
|
actor: 'human',
|
|
|
action: 'customer_profile_updated',
|
|
|
@@ -1191,7 +1244,7 @@ function updateCustomerProfile(conversationId, input = {}) {
|
|
|
status: 'ok',
|
|
|
assistantMessage: `客户“${conversation.contact_name || '未命名客户'}”的主档已更新。`,
|
|
|
summary: { changedFields, tagCount: tags.length },
|
|
|
- data: { profile: visibleProfile, profileEvidence: __evidence || {}, tags, updatedAt: updated.updatedAt },
|
|
|
+ data: { profile: visibleProfile, profileEvidence: __evidence || {}, tags, updatedAt: updated.updatedAt, memory },
|
|
|
warnings: [],
|
|
|
errors: [],
|
|
|
};
|
|
|
@@ -1616,6 +1669,34 @@ function updateCustomerAlert(alertId, input = {}) {
|
|
|
return { status: 'ok', assistantMessage: `客户预警已更新为 ${alert.status}`, data: { alert } };
|
|
|
}
|
|
|
|
|
|
+function addCustomerMemory(conversationId, input = {}) {
|
|
|
+ const result = workbench.service.addCustomerMemory(conversationId, input, 'human');
|
|
|
+ return { status: 'ok', assistantMessage: '客户长期记忆已添加,并将在后续回复中生效', data: result };
|
|
|
+}
|
|
|
+
|
|
|
+function updateCustomerMemory(memoryId, input = {}) {
|
|
|
+ const action = String(input.action || 'update');
|
|
|
+ if (action === 'forget') {
|
|
|
+ const result = workbench.service.forgetCustomerMemory(memoryId, 'human');
|
|
|
+ return { status: 'ok', assistantMessage: '客户记忆已彻底遗忘', data: result };
|
|
|
+ }
|
|
|
+ const patch = action === 'confirm'
|
|
|
+ ? { type: 'fact', status: 'active', confidence: 1 }
|
|
|
+ : action === 'reject'
|
|
|
+ ? { status: 'rejected' }
|
|
|
+ : {
|
|
|
+ content: input.content,
|
|
|
+ type: input.type,
|
|
|
+ status: input.status,
|
|
|
+ importance: input.importance,
|
|
|
+ expiresAt: input.expiresAt,
|
|
|
+ };
|
|
|
+ const sanitized = Object.fromEntries(Object.entries(patch).filter(([, value]) => value !== undefined));
|
|
|
+ const result = workbench.service.updateCustomerMemory(memoryId, sanitized, 'human');
|
|
|
+ const message = action === 'confirm' ? '推断已人工确认为客户事实' : action === 'reject' ? '客户记忆已拒绝,不再进入上下文' : '客户记忆已更新';
|
|
|
+ return { status: 'ok', assistantMessage: message, data: result };
|
|
|
+}
|
|
|
+
|
|
|
function getAudit(limit = 200) {
|
|
|
return { status: 'ok', data: { audit: workbench.db.listAudit(Math.max(1, Math.min(500, Number(limit) || 200))) } };
|
|
|
}
|
|
|
@@ -1635,6 +1716,7 @@ async function ingestRuntimeMessage(message = {}, options = {}) {
|
|
|
config: workbench.config.qiwei,
|
|
|
db: workbench.db,
|
|
|
service: workbench.service,
|
|
|
+ qiwei: workbench.qiwei,
|
|
|
}, message, String(options.source || 'runtime'));
|
|
|
}
|
|
|
|
|
|
@@ -1686,12 +1768,6 @@ module.exports = {
|
|
|
updateAllowlist,
|
|
|
getConversations,
|
|
|
getResponseMonitor,
|
|
|
- getGroupAgents,
|
|
|
- changeGroupMode,
|
|
|
- generateGroupReply,
|
|
|
- approveGroupDraft,
|
|
|
- rejectGroupDraft,
|
|
|
- regenerateGroupDraft,
|
|
|
updateCustomerProfile,
|
|
|
updateCustomerRecommendation,
|
|
|
syncConversations,
|
|
|
@@ -1711,6 +1787,8 @@ module.exports = {
|
|
|
updateCustomerTask,
|
|
|
syncCustomerTaskToOfficialTodo,
|
|
|
updateCustomerAlert,
|
|
|
+ addCustomerMemory,
|
|
|
+ updateCustomerMemory,
|
|
|
getAudit,
|
|
|
ingestRuntimeMessage,
|
|
|
ingestWebhookMessage,
|
|
|
@@ -1718,5 +1796,5 @@ module.exports = {
|
|
|
stopListener,
|
|
|
getAgentRuntimeConfig,
|
|
|
createWorkbench,
|
|
|
- __testing: { loadAgentConfig, normalizeAllowlistIds, normalizeAllowlistContact, accountRuntimeKey, accountWorkbenchOverrides, activeAccountMetadata, FmodeQiweiClient, QiweiAgentPoller, ingestMessageForWorkbench, conversationChannelInfo, publicConversation, backfillCustomerIntelligence, backfillPropertyRecommendations, backfillSentVoiceAudioPaths, sentVoiceRuns, pendingVoiceDraft, markVoiceDraftSent, detectedPropertiesInMessage, groupAgentService, startListenerForWorkbench },
|
|
|
+ __testing: { loadAgentConfig, normalizeAllowlistIds, normalizeAllowlistContact, accountRuntimeKey, accountWorkbenchOverrides, activeAccountMetadata, FmodeQiweiClient, QiweiAgentPoller, outboundContactId, recordOutboundMessage, syncOutboundNotification, ingestMessageForWorkbench, conversationChannelInfo, publicConversation, backfillCustomerIntelligence, backfillCustomerMemory, backfillPropertyRecommendations, backfillSentVoiceAudioPaths, sentVoiceRuns, pendingVoiceDraft, markVoiceDraftSent, detectedPropertiesInMessage, startListenerForWorkbench },
|
|
|
};
|