oh-my-im 0.1.13 → 0.1.16
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/README.md +1 -1
- package/dist/agents/pi-agent.js +1 -1
- package/dist/bot-app.js +21 -16
- package/dist/dws-dashboard.js +16 -4
- package/dist/group-worker.js +34 -13
- package/dist/omi.js +21 -17
- package/package.json +1 -1
package/README.md
CHANGED
package/dist/agents/pi-agent.js
CHANGED
|
@@ -79,7 +79,7 @@ function extractAssistantText(message) {
|
|
|
79
79
|
const text = value.content.flatMap((part) => {
|
|
80
80
|
const item = asObject(part);
|
|
81
81
|
return item?.type === "text" && typeof item.text === "string" ? [item.text] : [];
|
|
82
|
-
}).join("
|
|
82
|
+
}).join("");
|
|
83
83
|
return text || undefined;
|
|
84
84
|
}
|
|
85
85
|
function runPiOnce(prompt, sessionId, config, callbacks = {}) {
|
package/dist/bot-app.js
CHANGED
|
@@ -139,7 +139,7 @@ function shortModelName(model) {
|
|
|
139
139
|
return value.includes("/") ? value.slice(value.lastIndexOf("/") + 1) : value;
|
|
140
140
|
}
|
|
141
141
|
function buildCardContent(content, note) {
|
|
142
|
-
const safeContent = content.trim() || "
|
|
142
|
+
const safeContent = content.trim() || "[OMG] 正在分析...";
|
|
143
143
|
const safeNote = note?.trim();
|
|
144
144
|
return safeNote ? `${safeContent}\n\n\n${safeNote}` : safeContent;
|
|
145
145
|
}
|
|
@@ -559,7 +559,7 @@ export async function runApp(configOverride, options = {}) {
|
|
|
559
559
|
if (steer && text) {
|
|
560
560
|
const steered = steer(text);
|
|
561
561
|
await bot.sendText(message.conversationId, steered
|
|
562
|
-
? "已将这条消息作为引导发送给当前 Pi 任务。"
|
|
562
|
+
? "[灵感]已将这条消息作为引导发送给当前 Pi 任务。"
|
|
563
563
|
: "当前 Pi 任务暂时无法接收引导,消息已排队等待处理。");
|
|
564
564
|
}
|
|
565
565
|
else if (state.activeAgent === "codex" && text) {
|
|
@@ -609,23 +609,24 @@ export async function runApp(configOverride, options = {}) {
|
|
|
609
609
|
const finishedTitle = (icon, state) => `${icon} ${title}${state} 总耗时 ${formatElapsed()}`;
|
|
610
610
|
const responseMode = options.getResponseMode?.() ?? "card";
|
|
611
611
|
const reply = responseMode === "card"
|
|
612
|
-
? await bot.sendThinkingCard(message,
|
|
612
|
+
? await bot.sendThinkingCard(message, "[OMG] 正在分析...", processingTitle())
|
|
613
613
|
: { conversationId: message.conversationId, mode: "text" };
|
|
614
614
|
if (responseMode === "text")
|
|
615
|
-
await bot.sendText(message.conversationId,
|
|
615
|
+
await bot.sendText(message.conversationId, "[OMG] 正在分析...");
|
|
616
616
|
let elapsedTimer;
|
|
617
|
-
let latestCardContent =
|
|
617
|
+
let latestCardContent = "[OMG] 正在分析...";
|
|
618
618
|
const agent = selectedAgent;
|
|
619
619
|
let prompt = "";
|
|
620
620
|
try {
|
|
621
621
|
let latestStats = {};
|
|
622
622
|
let streamedText = "";
|
|
623
|
-
let lastStreamText = "";
|
|
624
623
|
let toolStatus = "";
|
|
625
624
|
let lastUpdateAt = 0;
|
|
626
625
|
let pendingUpdate;
|
|
627
626
|
let cardUpdatesStopped = false;
|
|
628
|
-
|
|
627
|
+
let cardUpdateChain = Promise.resolve();
|
|
628
|
+
const getCardUpdateInterval = () => Math.max(0, options.getCardUpdateIntervalMs?.() ?? 3_000);
|
|
629
|
+
const liveCardUpdates = responseMode === "card" && getCardUpdateInterval() > 0;
|
|
629
630
|
const stopCardUpdates = () => {
|
|
630
631
|
cardUpdatesStopped = true;
|
|
631
632
|
if (pendingUpdate)
|
|
@@ -651,7 +652,11 @@ export async function runApp(configOverride, options = {}) {
|
|
|
651
652
|
return;
|
|
652
653
|
lastUpdateAt = Date.now();
|
|
653
654
|
pendingUpdate = undefined;
|
|
654
|
-
|
|
655
|
+
cardUpdateChain = cardUpdateChain.then(async () => {
|
|
656
|
+
if (cardUpdatesStopped)
|
|
657
|
+
return;
|
|
658
|
+
await bot.updateReply(reply, title, content, { fallbackToText: false });
|
|
659
|
+
}).catch((err) => {
|
|
655
660
|
log.warn(`card update skipped: ${err instanceof Error ? err.message : String(err)}`);
|
|
656
661
|
});
|
|
657
662
|
};
|
|
@@ -668,7 +673,7 @@ export async function runApp(configOverride, options = {}) {
|
|
|
668
673
|
pendingUpdate.unref();
|
|
669
674
|
}
|
|
670
675
|
};
|
|
671
|
-
if (
|
|
676
|
+
if (liveCardUpdates) {
|
|
672
677
|
elapsedTimer = setInterval(() => {
|
|
673
678
|
if (!cardUpdatesStopped)
|
|
674
679
|
updateCard(processingTitle(), latestCardContent);
|
|
@@ -686,18 +691,17 @@ export async function runApp(configOverride, options = {}) {
|
|
|
686
691
|
},
|
|
687
692
|
onText: (content) => {
|
|
688
693
|
const stats = formatStats(latestStats);
|
|
689
|
-
|
|
690
|
-
lastStreamText = content;
|
|
691
|
-
if (delta.trim())
|
|
692
|
-
streamedText = streamedText ? `${streamedText}\n\n${delta.trim()}` : delta.trim();
|
|
694
|
+
streamedText = content;
|
|
693
695
|
toolStatus = "";
|
|
694
|
-
|
|
696
|
+
if (liveCardUpdates)
|
|
697
|
+
updateCard(processingTitle(), buildCardContent(toolStatus ? `${streamedText}\n\n${toolStatus}` : (streamedText || content), stats ? `工具:${stats}` : undefined));
|
|
695
698
|
},
|
|
696
699
|
onToolUse: (toolName, stats) => {
|
|
697
700
|
latestStats = stats;
|
|
698
701
|
const statsText = formatStats(stats);
|
|
699
|
-
toolStatus =
|
|
700
|
-
|
|
702
|
+
toolStatus = `[OMG] 调用工具:${toolName} x${stats[toolName] ?? 1}`;
|
|
703
|
+
if (liveCardUpdates)
|
|
704
|
+
updateCard(processingTitle(), buildCardContent(streamedText ? `${streamedText}\n\n${toolStatus}` : toolStatus, statsText ? `工具:${statsText}` : undefined));
|
|
701
705
|
},
|
|
702
706
|
});
|
|
703
707
|
if (elapsedTimer) {
|
|
@@ -710,6 +714,7 @@ export async function runApp(configOverride, options = {}) {
|
|
|
710
714
|
clearTimeout(pendingUpdate);
|
|
711
715
|
pendingUpdate = undefined;
|
|
712
716
|
}
|
|
717
|
+
await cardUpdateChain;
|
|
713
718
|
state.stopCardUpdates = undefined;
|
|
714
719
|
stopCardUpdatesForCurrentTask = undefined;
|
|
715
720
|
state.sessions[agent] = result.sessionId ?? state.sessions[agent];
|
package/dist/dws-dashboard.js
CHANGED
|
@@ -32,8 +32,8 @@ function normalizeConfig(value) {
|
|
|
32
32
|
: Number(source.cardUpdateIntervalMs) <= 60
|
|
33
33
|
? Number(source.cardUpdateIntervalMs) * 1_000
|
|
34
34
|
: Number(source.cardUpdateIntervalMs);
|
|
35
|
-
if (cardUpdateIntervalMs !== 0 && (cardUpdateIntervalMs <
|
|
36
|
-
throw new Error("卡片更新间隔需为 0(完成后一次性发送)或
|
|
35
|
+
if (cardUpdateIntervalMs !== 0 && (cardUpdateIntervalMs < 1_000 || cardUpdateIntervalMs > 60_000))
|
|
36
|
+
throw new Error("卡片更新间隔需为 0(完成后一次性发送)或 1-60 秒");
|
|
37
37
|
const responseMode = source.responseMode === "text" ? "text" : "card";
|
|
38
38
|
if (source.showElapsed !== undefined && typeof source.showElapsed !== "boolean")
|
|
39
39
|
throw new Error("总耗时显示开关格式无效");
|
|
@@ -140,7 +140,15 @@ export function startDashboard(port, hooks, options = {}) {
|
|
|
140
140
|
responsePanel.innerHTML = '<div class="field"><label>Agent 回复方式</label><div class="agent-options"><label><input type="radio" name="responseMode" value="card" checked> 互动卡片</label><label><input type="radio" name="responseMode" value="text"> 普通文本</label></div><div class="field-help">群聊和私聊统一使用此方式。普通文本只在 Agent 完成后发送最终结果。</div></div>';
|
|
141
141
|
agentSettings.querySelector('.agent-row')?.append(responsePanel);
|
|
142
142
|
const cardIntervalInput = document.querySelector('#cardUpdateIntervalMs');
|
|
143
|
-
if (cardIntervalInput)
|
|
143
|
+
if (cardIntervalInput) {
|
|
144
|
+
cardIntervalInput.min = '0';
|
|
145
|
+
cardIntervalInput.max = '60';
|
|
146
|
+
cardIntervalInput.step = '1';
|
|
147
|
+
const field = cardIntervalInput.closest('.compact-number-field');
|
|
148
|
+
if (field?.lastChild?.nodeType === Node.TEXT_NODE) {
|
|
149
|
+
field.lastChild.textContent = ' 秒(0:完成后一次性发送,1-60:按间隔更新)';
|
|
150
|
+
}
|
|
151
|
+
}
|
|
144
152
|
panel.innerHTML = '<div class="field"><label>Pi 默认模型</label><select id="piModel"><option value="">使用 Pi CLI 默认模型</option></select><div class="field-help">群聊和私聊使用 Pi 时都采用此模型。Codex 始终使用系统 Codex CLI 的默认模型。</div></div>';
|
|
145
153
|
agentSettings.querySelector('.agent-row')?.append(panel);
|
|
146
154
|
const loadModels = async (agent) => {
|
|
@@ -166,6 +174,10 @@ export function startDashboard(port, hooks, options = {}) {
|
|
|
166
174
|
const response = await fetch('/api/state', { cache: 'no-store' });
|
|
167
175
|
const body = await response.json();
|
|
168
176
|
const models = body.config?.agentModels || {};
|
|
177
|
+
const intervalInput = document.querySelector('#cardUpdateIntervalMs');
|
|
178
|
+
if (intervalInput && body.config?.cardUpdateIntervalMs !== undefined) {
|
|
179
|
+
intervalInput.value = String(body.config.cardUpdateIntervalMs / 1000);
|
|
180
|
+
}
|
|
169
181
|
const responseMode = body.config?.responseMode === 'text' ? 'text' : 'card';
|
|
170
182
|
document.querySelectorAll('input[name=responseMode]').forEach((input) => { input.checked = input.value === responseMode; });
|
|
171
183
|
document.querySelector('#piModel').value = models.pi || '';
|
|
@@ -404,7 +416,7 @@ async function loadConfiguredMembers(rule,group){const people=rule.querySelector
|
|
|
404
416
|
function addConfiguredRule(group){const rule=document.createElement('article');rule.className='rule';rule.innerHTML='<div class="rule-grid configured-grid"><div class="field"><label>钉钉群</label><div class="rule-value group-value"></div></div><div class="field"><label>钉钉人员</label><div class="member-list people"></div></div><button class="delete" title="删除规则">×</button></div>';rule._targets=group.targets;rule._group=group;rule.querySelector('.group-value').textContent=group.groupName;rule.querySelector('.delete').onclick=()=>rule.remove();rules.append(rule);void loadConfiguredMembers(rule,group)}
|
|
405
417
|
const keywordIds={pause:'keywordsPause',monitorOpen:'keywordsMonitorOpen',monitorStop:'keywordsMonitorStop',switchPi:'keywordsSwitchPi',switchCodex:'keywordsSwitchCodex'};
|
|
406
418
|
const parseKeywords=id=>document.querySelector('#'+id).value.split(/[||]+/).map(value=>value.trim()).filter(Boolean);
|
|
407
|
-
function renderStatic(data,replaceConfig=false){rulesPanel.querySelector('.rules-toggle').textContent='钉钉群监控绑定 ('+data.config.targets.length+')';if(replaceConfig){if(document.querySelector('#privateChatEnabled'))document.querySelector('#privateChatEnabled').checked=data.config.privateChatEnabled!==false;document.querySelectorAll('input[name=agent]').forEach(input=>{input.checked=input.value===(data.config.agent||'codex')});document.querySelector('#robotName').value=data.config.robotName||'';document.querySelector('#clientId').value=data.config.clientId||'';document.querySelector('#showElapsed').checked=data.config.showElapsed!==false;document.querySelector('#showProcessingDetails').checked=data.config.showProcessingDetails===true;document.querySelector('#cardUpdateIntervalMs').value=(data.config.cardUpdateIntervalMs
|
|
419
|
+
function renderStatic(data,replaceConfig=false){rulesPanel.querySelector('.rules-toggle').textContent='钉钉群监控绑定 ('+data.config.targets.length+')';if(replaceConfig){if(document.querySelector('#privateChatEnabled'))document.querySelector('#privateChatEnabled').checked=data.config.privateChatEnabled!==false;document.querySelectorAll('input[name=agent]').forEach(input=>{input.checked=input.value===(data.config.agent||'codex')});document.querySelector('#robotName').value=data.config.robotName||'';document.querySelector('#clientId').value=data.config.clientId||'';document.querySelector('#showElapsed').checked=data.config.showElapsed!==false;document.querySelector('#showProcessingDetails').checked=data.config.showProcessingDetails===true;document.querySelector('#cardUpdateIntervalMs').value=(data.config.cardUpdateIntervalMs??3000)/1000;document.querySelector('#clientSecret').value='';document.querySelector('#webhookUrl').value='';document.querySelector('#personalHistoryMessageLimit').value=data.config.personalHistoryMessageLimit||10;document.querySelector('#personalHistoryPollIntervalSeconds').value=data.config.personalHistoryPollIntervalSeconds??15;document.querySelector('#personalHistoryLookbackMinutes').value=data.config.personalHistoryLookbackMinutes??10;document.querySelector('#robotSenderOpenDingTalkId').value=data.config.robotSenderOpenDingTalkId||'';document.querySelector('#groupPromptPrefix').value=data.config.groupPromptSuffix||'';setBotUsers(data.config.botAllowedUserIds||[],data.config.botAllowedUserNames||{},data.config.botSuperAdminUserIds||[]);Object.entries(keywordIds).forEach(([key,id])=>{document.querySelector('#'+id).value=(data.config.commandKeywords?.[key]||[]).join('|')})}const c=document.querySelector('#connection');if(c){c.textContent=data.status.eventConnected?'事件连接正常':'事件未连接';c.className='status '+(data.status.eventConnected?'connected':'stopped');}const list=document.querySelector('#replies');if(!list)return;if(!data.replies.length){list.className='empty';list.textContent='暂无回复';return}list.className='';const recentReplies=[...data.replies].sort((a,b)=>new Date(a.createdAt||0)-new Date(b.createdAt||0)).slice(-10);const replySignature=recentReplies.map(r=>r.id+':'+r.status+':'+r.content).join('|');if(list.dataset.signature===replySignature)return;const wasAtBottom=list.scrollHeight-list.scrollTop-list.clientHeight<24;list.dataset.signature=replySignature;list.innerHTML=recentReplies.map(r=>{const state=r.status==='processing'?'处理中':r.status==='completed'?'完成':'失败',replyStyle=r.status==='processing'?'#fffbeb;color:#92400e':r.status==='completed'?'#ecfdf3;color:#166534':'#fff1f2;color:#b42318',senderNames=[...new Set((r.senderNames||[]).filter(Boolean))].join('、');const conversationLabel=r.conversationType==='personal'?'个人':'群聊 · 群名:'+esc(r.conversationName||r.groupName||'-');return '<article class="reply"><div class="meta">'+conversationLabel+' · 发送人:'+esc(senderNames||'-')+' · '+fmt(r.createdAt)+' · '+esc(r.agent==='pi'?'Pi':'Codex')+' · '+state+' · '+r.messageCount+' 条消息</div><div style="margin-top:6px;padding:8px;border-radius:4px;background:#eff6ff;color:#174ea6"><pre style="margin:0;white-space:pre-wrap;word-break:break-word;font:12px/1.5 ui-monospace,SFMono-Regular,Menlo,monospace">'+esc(r.question||'')+'</pre></div><div style="margin-top:6px;padding:8px;border-radius:4px;background:'+replyStyle+'"><pre style="margin:0;white-space:pre-wrap;word-break:break-word;font:12px/1.5 ui-monospace,SFMono-Regular,Menlo,monospace">'+esc(r.content)+'</pre></div></article>'}).join('');if(wasAtBottom||recentReplies.length>0)list.scrollTop=list.scrollHeight}
|
|
408
420
|
async function refresh(full=false){try{const r=await fetch('/api/state',{cache:'no-store'});if(!r.ok)throw new Error('读取状态失败');const data=await r.json();renderStatic(data,full);if(full){rules.replaceChildren();const grouped=new Map();data.config.targets.forEach(t=>{const key=t.groupId;if(!grouped.has(key))grouped.set(key,{groupId:t.groupId,groupName:t.groupName,targets:[]});grouped.get(key).targets.push(t)});grouped.forEach(addConfiguredRule);const formatInput=document.querySelector('input[name=format][value='+data.config.replyFormat+']');if(formatInput)formatInput.checked=true;loaded=true}}catch(e){notice.textContent=e.message;notice.className='notice error'}}
|
|
409
421
|
let latestSystemLog='',systemLogOffset;const filterSystemLog=(content,level)=>{if(level==='all')return content;const lines=content.split('\n'),entries=[];let current=[];const isEntryStart=line=>/^\[\d{4}-\d{2}-\d{2}T[^\]]+\]\s+\[[^\]]+\]\s+\[(DEBUG|INFO|WARN|ERROR)\]/.test(line);lines.forEach(line=>{if(isEntryStart(line)){if(current.length)entries.push(current);current=[line]}else if(current.length)current.push(line)});if(current.length)entries.push(current);return entries.filter(entry=>entry[0].includes('['+level+']')).map(entry=>entry.join('\n')).join('\n')};const renderSystemLog=()=>{const log=document.querySelector('#systemLog'),level=document.querySelector('#monitorLogLevel').value,filtered=filterSystemLog(latestSystemLog,level),atBottom=log.scrollHeight-log.scrollTop-log.clientHeight<30;if(log.textContent!==filtered){log.textContent=filtered||'暂无匹配日志';if(document.querySelector('#monitorAutoScroll').checked&&(atBottom||!log.dataset.loaded))log.scrollTop=log.scrollHeight;log.dataset.loaded='1'}};document.querySelector('#monitorLogLevel').onchange=renderSystemLog;document.querySelector('#monitorAutoScroll').onchange=event=>{document.querySelector('#systemLogMeta').textContent=event.target.checked?'正在恢复实时拉取...':'实时拉取已暂停';if(event.target.checked)void loadSystemMonitor()};const loadSystemMonitor=async()=>{try{const statusResponse=await fetch('/api/system-status',{cache:'no-store'}),statusBody=await statusResponse.json();if(!statusResponse.ok)throw new Error(statusBody.error||'系统状态读取失败');const status=statusBody.status||{},processes=document.querySelector('#monitorProcesses'),meta=document.querySelector('#monitorMeta');meta.textContent='模式:'+(status.mode||'-')+' · 启动:'+fmt(status.startedAt)+' · 检查:'+fmt(status.checkedAt);processes.innerHTML=(status.processes||[]).map(item=>'<article class="process-card"><strong>'+esc(item.role)+'</strong><div class="process-state '+(item.running?'running':'')+'">'+(item.running?'运行中':'已停止')+'</div><code>PID '+esc(String(item.pid||'-'))+'</code>'+(['group-worker','bot'].includes(item.role)?'<div class="process-actions"><button class="button process-action" data-role="'+esc(item.role)+'" data-action="'+(item.running?'stop':'start')+'">'+(item.running?'停止':'启动')+'</button></div>':'')+'</article>').join('')||'<div class="empty">未发现系统进程</div>';processes.querySelectorAll('.process-action').forEach(button=>button.onclick=async()=>{const role=button.dataset.role,action=button.dataset.action;button.disabled=true;try{const r=await fetch('/api/system-process/'+encodeURIComponent(role)+'/'+action,{method:'POST'});if(!r.ok){const body=await r.json().catch(()=>({}));throw new Error(body.error||'进程操作失败')}setTimeout(loadSystemMonitor,800)}catch(e){alert(e instanceof Error?e.message:String(e))}finally{button.disabled=false}});if(!document.querySelector('#monitorAutoScroll').checked){document.querySelector('#systemLogMeta').textContent='实时拉取已暂停';return}const logUrl='/api/system-logs'+(systemLogOffset===undefined?'':'?offset='+encodeURIComponent(systemLogOffset)),logResponse=await fetch(logUrl,{cache:'no-store'}),logBody=await logResponse.json();if(!logResponse.ok)throw new Error(logBody.error||'系统日志读取失败');if(logBody.reset||systemLogOffset===undefined)latestSystemLog=logBody.content||'';else latestSystemLog+=logBody.content||'';systemLogOffset=Number(logBody.nextOffset||0);if(latestSystemLog.length>1000000){latestSystemLog=latestSystemLog.slice(-1000000).replace(/^[^\n]*\n/,'')}renderSystemLog();document.querySelector('#systemLogMeta').textContent=(logBody.path||'')+' · '+Number(logBody.size||0).toLocaleString()+' bytes · tail 实时增量'}catch(e){document.querySelector('#monitorMeta').textContent=e instanceof Error?e.message:String(e)}};document.querySelector('#restartSystem').onclick=async()=>{if(!confirm('确认重启整个 oh-my-im 系统吗?当前正在执行的任务会被中断。'))return;const button=document.querySelector('#restartSystem');button.disabled=true;button.textContent='正在重启...';try{const r=await fetch('/api/system-restart',{method:'POST'}),body=await r.json().catch(()=>({}));if(!r.ok)throw new Error(body.error||'重启失败');document.querySelector('#monitorMeta').textContent='重启指令已发送,等待服务恢复...';setTimeout(()=>location.reload(),4000)}catch(e){alert(e instanceof Error?e.message:String(e));button.disabled=false;button.textContent='重启系统'}};setInterval(()=>{if(document.querySelector('.page-monitor')?.classList.contains('active'))void loadSystemMonitor()},1000);void loadSystemMonitor();setInterval(()=>{void refresh(false)},1000);document.querySelector('#add').onclick=()=>addDraftRule();document.querySelector('#save').onclick=async()=>{const saveButton=document.querySelector('#save');try{const stateResponse=await fetch('/api/state',{cache:'no-store'}),state=await stateResponse.json();if(!stateResponse.ok||!state.config)throw new Error('读取当前配置失败');const targets=[...rules.children].flatMap(rule=>{if(rule._targets&&!rule._members)return rule._targets;const groupId=rule._group?.groupId||rule.querySelector('[data-key=groupId]')?.value,groupName=rule._group?.groupName||rule.querySelector('[data-key=groupName]')?.value,members=rule._members||[],selected=[...rule.querySelectorAll('[data-sender-id]:checked')];return selected.map(box=>{const m=members.find(x=>x.senderId===box.dataset.senderId);return {groupId,groupName,senderId:box.dataset.senderId,senderName:m?m.senderName:''}})}),robotName=document.querySelector('#robotName').value.trim(),clientId=document.querySelector('#clientId').value.trim(),showElapsed=document.querySelector('#showElapsed').checked,cardUpdateIntervalMs=Number(document.querySelector('#cardUpdateIntervalMs').value||'3'),clientSecret=document.querySelector('#clientSecret').value.trim(),personalHistoryMessageLimit=Number(document.querySelector('#personalHistoryMessageLimit').value||10),personalHistoryPollIntervalSeconds=Number(document.querySelector('#personalHistoryPollIntervalSeconds').value||0),personalHistoryLookbackMinutes=Number(document.querySelector('#personalHistoryLookbackMinutes').value||10),webhookUrl=document.querySelector('#webhookUrl').value.trim(),robotSenderOpenDingTalkId=document.querySelector('#robotSenderOpenDingTalkId').value.trim(),groupPromptSuffix=document.querySelector('#groupPromptPrefix').value.trim(),botAllowedUserIds=selectedBotUsers.map(user=>user.id),botAllowedUserNames=Object.fromEntries(selectedBotUsers.map(user=>[user.id,user.name])),botSuperAdminUserIds=[...selectedSuperAdminIds],botSuperAdminUserNames=Object.fromEntries(selectedBotUsers.filter(user=>selectedSuperAdminIds.includes(user.id)).map(user=>[user.id,user.name])),commandKeywords={pause:parseKeywords(keywordIds.pause),monitorOpen:parseKeywords(keywordIds.monitorOpen),monitorStop:parseKeywords(keywordIds.monitorStop),switchPi:parseKeywords(keywordIds.switchPi),switchCodex:parseKeywords(keywordIds.switchCodex)},agent=agentSelect().value,replyFormat=document.querySelector('input[name=format]:checked')?.value; if(!replyFormat)throw new Error('请选择回复格式');notice.textContent='保存中...';notice.className='notice';saveButton.disabled=true;saveButton.textContent='保存中...';const r=await fetch('/api/config',{method:'PUT',headers:{'content-type':'application/json'},body:JSON.stringify({...state.config,targets,botAllowedUserIds,botAllowedUserNames,robotSenderOpenDingTalkId,groupPromptSuffix,cardUpdateIntervalMs:cardUpdateIntervalMs*1000,showElapsed,personalHistoryMessageLimit,personalHistoryPollIntervalSeconds,personalHistoryLookbackMinutes,webhookUrl,botSuperAdminUserIds,botSuperAdminUserNames,commandKeywords,replyFormat,robotName,clientId,clientSecret,agent})});const b=await r.json().catch(()=>({}));if(!r.ok||b.saved!==true)throw new Error(b.error||'保存失败:服务端未确认保存');notice.textContent='已保存,钉钉规则与机器人设置已立即生效。';notice.className='notice';await refresh(true)}catch(e){console.error('[Dashboard] save failed',e);notice.textContent=e instanceof Error?e.message:String(e);notice.className='notice error'}finally{saveButton.disabled=false;saveButton.textContent='保存并生效';setTimeout(()=>{if(!notice.classList.contains('error'))notice.textContent=''},3000)}};refresh(true);
|
|
410
422
|
</script></body></html>`;
|
package/dist/group-worker.js
CHANGED
|
@@ -819,7 +819,7 @@ async function handleBatch(events, groupId, cardState, sessions, cardClient, get
|
|
|
819
819
|
if (responseMode === "card" && storedCard?.status === "processing") {
|
|
820
820
|
card = { groupId, cardBizId: storedCard.cardBizId };
|
|
821
821
|
try {
|
|
822
|
-
await cardClient.update(card, processingTitle(), `[
|
|
822
|
+
await cardClient.update(card, processingTitle(), `[OMG] 正在分析...`);
|
|
823
823
|
}
|
|
824
824
|
catch (err) {
|
|
825
825
|
if (!isMissingCardError(err))
|
|
@@ -831,7 +831,7 @@ async function handleBatch(events, groupId, cardState, sessions, cardClient, get
|
|
|
831
831
|
}
|
|
832
832
|
if (responseMode === "card" && !card) {
|
|
833
833
|
const cardBizId = randomUUID();
|
|
834
|
-
card = await cardClient.create(groupId, cardBizId, processingTitle(), `[
|
|
834
|
+
card = await cardClient.create(groupId, cardBizId, processingTitle(), `[OMG] 正在分析...`);
|
|
835
835
|
cardState.cards[groupId] = { cardBizId, status: "processing" };
|
|
836
836
|
await saveCardState(cardState);
|
|
837
837
|
}
|
|
@@ -849,7 +849,7 @@ async function handleBatch(events, groupId, cardState, sessions, cardClient, get
|
|
|
849
849
|
question: batchQuestion(events),
|
|
850
850
|
senderNames: batchSenderNames(events, getDashboardConfig()),
|
|
851
851
|
senderDetails: batchSenderDetails(events, getDashboardConfig()),
|
|
852
|
-
content: `[
|
|
852
|
+
content: `[OMG] 正在分析...`,
|
|
853
853
|
agent,
|
|
854
854
|
messageCount: events.length,
|
|
855
855
|
};
|
|
@@ -934,7 +934,6 @@ async function handleBatch(events, groupId, cardState, sessions, cardClient, get
|
|
|
934
934
|
}, 1_000);
|
|
935
935
|
elapsedTimer.unref();
|
|
936
936
|
}
|
|
937
|
-
let lastText = "";
|
|
938
937
|
let streamedText = "";
|
|
939
938
|
let toolStatus = "";
|
|
940
939
|
const result = await runAgent(agent, buildPrompt(events, dashboardConfig.groupPromptSuffix), sessionId, agentConfig, {
|
|
@@ -944,20 +943,18 @@ async function handleBatch(events, groupId, cardState, sessions, cardClient, get
|
|
|
944
943
|
queue.steer = steer; },
|
|
945
944
|
onToolUse: (toolName, stats) => {
|
|
946
945
|
log.info(`codex tool=${toolName} count=${stats[toolName] ?? 1}`);
|
|
947
|
-
toolStatus =
|
|
948
|
-
liveReply.content = streamedText ? `${streamedText}\n\n${toolStatus}` : toolStatus;
|
|
946
|
+
toolStatus = `[OMG] 调用工具:${toolName} x${stats[toolName] ?? 1}`;
|
|
947
|
+
liveReply.content = streamedText ? `${streamedText}\n\n\n${toolStatus}` : toolStatus;
|
|
949
948
|
latestVisibleContent = liveReply.content;
|
|
950
949
|
updateCard(processingTitle(), liveReply.content);
|
|
951
950
|
},
|
|
952
951
|
onText: (text) => {
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
const visible = delta.trim();
|
|
952
|
+
streamedText = text;
|
|
953
|
+
const visible = streamedText.trim();
|
|
956
954
|
if (visible) {
|
|
957
|
-
streamedText = streamedText ? `${streamedText}\n\n${visible}` : visible;
|
|
958
955
|
toolStatus = "";
|
|
959
956
|
liveReply.content = streamedText;
|
|
960
|
-
latestVisibleContent =
|
|
957
|
+
latestVisibleContent = streamedText;
|
|
961
958
|
log.debug(`codex output: ${visible.slice(0, 2_000)}`);
|
|
962
959
|
updateCard(processingTitle(), streamedText.slice(-8_000));
|
|
963
960
|
}
|
|
@@ -1067,7 +1064,7 @@ async function enqueueGroupEvent(event, groupId, queues, cardState, sessions, ca
|
|
|
1067
1064
|
if (queue.activeAgent === "pi" && queue.steer && content) {
|
|
1068
1065
|
const steered = queue.steer(content);
|
|
1069
1066
|
if (steered) {
|
|
1070
|
-
void sendRobotText(groupId, getDashboardConfig(), "已将这条消息作为引导发送给当前 Pi 任务。")
|
|
1067
|
+
void sendRobotText(groupId, getDashboardConfig(), "[灵感]已将这条消息作为引导发送给当前 Pi 任务。")
|
|
1071
1068
|
.catch((err) => log.warn(`steer acknowledgement failed: ${String(err)}`));
|
|
1072
1069
|
log.info(`steered message=${event.message_id || "unknown"} group=${groupId}`);
|
|
1073
1070
|
return;
|
|
@@ -1156,7 +1153,7 @@ function startGroupListener(seen, queues, cardState, sessions, cardClient, getDa
|
|
|
1156
1153
|
// metadata is complete. Never feed our own steer acknowledgement back into
|
|
1157
1154
|
// the active Pi task, even if robot detection missed that event.
|
|
1158
1155
|
const eventContent = (event.content || event.text || "").trim();
|
|
1159
|
-
if (queues.get(groupId)?.running &&
|
|
1156
|
+
if (queues.get(groupId)?.running && /^\[灵感\]已将这条消息作为引导发送给当前 Pi 任务[。.!!]?$/u.test(eventContent)) {
|
|
1160
1157
|
log.debug(`ignored self steer acknowledgement group=${groupId}`);
|
|
1161
1158
|
return;
|
|
1162
1159
|
}
|
|
@@ -1238,6 +1235,30 @@ function startGroupListener(seen, queues, cardState, sessions, cardClient, getDa
|
|
|
1238
1235
|
log.info(`ignored mentioned group message group=${groupId} sender=${getSenderId(event)}`);
|
|
1239
1236
|
return;
|
|
1240
1237
|
}
|
|
1238
|
+
if (rawContent === "/new") {
|
|
1239
|
+
if (!acceptsTarget(event, config)) {
|
|
1240
|
+
log.debug(`ignored new session command from unmonitored sender=${getSenderId(event)}`);
|
|
1241
|
+
return;
|
|
1242
|
+
}
|
|
1243
|
+
const commandKey = `${groupId}:${getSenderId(event)}:new-session`;
|
|
1244
|
+
const now = Date.now();
|
|
1245
|
+
const previous = recentCommands.get(commandKey);
|
|
1246
|
+
if (previous && now - previous < commandDeduplicationMs)
|
|
1247
|
+
return;
|
|
1248
|
+
recentCommands.set(commandKey, now);
|
|
1249
|
+
void monitorCommandChain.then(async () => {
|
|
1250
|
+
if (queues.get(groupId)?.running) {
|
|
1251
|
+
await sendRobotText(groupId, getDashboardConfig(), "当前 Agent 任务正在运行,请先暂停后再新建会话。");
|
|
1252
|
+
return;
|
|
1253
|
+
}
|
|
1254
|
+
sessions.delete(`pi:${groupId}`);
|
|
1255
|
+
sessions.delete(`codex:${groupId}`);
|
|
1256
|
+
await saveGroupSessions(sessions);
|
|
1257
|
+
await sendRobotText(groupId, getDashboardConfig(), "已清空当前群会话的 Agent session,下一条消息将使用新会话处理。");
|
|
1258
|
+
log.info(`group sessions cleared group=${groupId}`);
|
|
1259
|
+
}).catch((err) => log.error(`new session command failed group=${groupId}: ${String(err)}`));
|
|
1260
|
+
return;
|
|
1261
|
+
}
|
|
1241
1262
|
runtime.lastEventAt = new Date().toISOString();
|
|
1242
1263
|
const command = parseMonitorCommand({ senderId: getSenderId(event), content: rawContent }, config.commandKeywords);
|
|
1243
1264
|
log.info(`group event classified group=${groupId} sender=${getSenderId(event)} command=${typeof command === "object" ? `${command.type}:${command.agent}` : command || "message"} configuredTargets=${config.targets.filter((target) => target.groupId === groupId).length}`);
|
package/dist/omi.js
CHANGED
|
@@ -42,21 +42,21 @@ async function checkForUpdate() {
|
|
|
42
42
|
try {
|
|
43
43
|
const response = await fetch(`https://registry.npmjs.org/${packageName}/latest`, { signal: AbortSignal.timeout(3_000) });
|
|
44
44
|
if (!response.ok)
|
|
45
|
-
return;
|
|
45
|
+
return true;
|
|
46
46
|
const latest = (await response.json()).version?.trim();
|
|
47
47
|
if (!latest || compareVersions(latest, current) <= 0)
|
|
48
|
-
return;
|
|
48
|
+
return true;
|
|
49
49
|
console.log(`发现 ${packageName} 新版本:v${latest}(当前 v${current})`);
|
|
50
50
|
console.log(`GitHub:${repositoryUrl}`);
|
|
51
51
|
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
52
52
|
console.log(`如需升级,请执行:npm install -g ${packageName}@latest`);
|
|
53
|
-
return;
|
|
53
|
+
return true;
|
|
54
54
|
}
|
|
55
55
|
const readline = createInterface({ input: process.stdin, output: process.stdout });
|
|
56
56
|
try {
|
|
57
57
|
const answer = (await readline.question("是否立即升级?[y/N] ")).trim().toLowerCase();
|
|
58
58
|
if (answer !== "y" && answer !== "yes")
|
|
59
|
-
return;
|
|
59
|
+
return true;
|
|
60
60
|
}
|
|
61
61
|
finally {
|
|
62
62
|
readline.close();
|
|
@@ -65,12 +65,14 @@ async function checkForUpdate() {
|
|
|
65
65
|
const result = spawnSync("npm", ["install", "-g", `${packageName}@latest`], { stdio: "inherit" });
|
|
66
66
|
if (result.status !== 0) {
|
|
67
67
|
console.error("升级失败,请稍后手动执行:", `npm install -g ${packageName}@latest`);
|
|
68
|
-
return;
|
|
68
|
+
return true;
|
|
69
69
|
}
|
|
70
70
|
console.log(`升级完成,请重新执行:${process.argv.slice(2).join(" ") || "omi"}`);
|
|
71
|
+
return false;
|
|
71
72
|
}
|
|
72
73
|
catch {
|
|
73
74
|
// Version checking must never prevent the already installed version from starting.
|
|
75
|
+
return true;
|
|
74
76
|
}
|
|
75
77
|
}
|
|
76
78
|
function printHelp() {
|
|
@@ -282,8 +284,8 @@ if (args.includes("-h") || args.includes("--help") || command === "help") {
|
|
|
282
284
|
printHelp();
|
|
283
285
|
}
|
|
284
286
|
else if (command === "start" || command === "listen") {
|
|
285
|
-
await checkForUpdate()
|
|
286
|
-
|
|
287
|
+
if (await checkForUpdate())
|
|
288
|
+
start(noListen ? "bot" : "listen");
|
|
287
289
|
}
|
|
288
290
|
else if (command === "stop") {
|
|
289
291
|
await stop();
|
|
@@ -292,18 +294,20 @@ else if (command === "status") {
|
|
|
292
294
|
status();
|
|
293
295
|
}
|
|
294
296
|
else if (command === "restart") {
|
|
295
|
-
await checkForUpdate()
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
297
|
+
if (await checkForUpdate()) {
|
|
298
|
+
const current = readState();
|
|
299
|
+
const mode = current?.mode ?? "listen";
|
|
300
|
+
await stop();
|
|
301
|
+
start(mode, current?.workspace || launchWorkspace);
|
|
302
|
+
}
|
|
300
303
|
}
|
|
301
304
|
else if (command === "update") {
|
|
302
|
-
await checkForUpdate()
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
305
|
+
if (await checkForUpdate()) {
|
|
306
|
+
const current = readState();
|
|
307
|
+
const mode = current?.mode ?? "listen";
|
|
308
|
+
await stop();
|
|
309
|
+
start(mode, current?.workspace || launchWorkspace);
|
|
310
|
+
}
|
|
307
311
|
}
|
|
308
312
|
else {
|
|
309
313
|
console.error(`Unknown command: ${command}`);
|