evolclaw 3.1.0 → 3.1.1

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.
@@ -8,7 +8,7 @@ import crypto from 'crypto';
8
8
  import path from 'path';
9
9
  import fs from 'fs';
10
10
  import os from 'os';
11
- import { parseTriggerSet } from './trigger/parser.js';
11
+ import { parseTriggerSet, parseTriggerUpdate } from './trigger/parser.js';
12
12
  import { calcNextFireAt } from './trigger/scheduler.js';
13
13
  import { checkLatestVersion, getLocalVersion, isLinkedInstall, compareVersions } from '../utils/npm-ops.js';
14
14
  const allEfforts = ['low', 'medium', 'high', 'max'];
@@ -24,17 +24,8 @@ function getAvailableEfforts(agent, model) {
24
24
  }
25
25
  return [];
26
26
  }
27
- function formatModelUsage(agent, model) {
28
- const efforts = getAvailableEfforts(agent, model);
29
- const lines = [
30
- '用法:',
31
- ' /model <模型> 切换模型',
32
- ];
33
- if (efforts.length > 0) {
34
- lines.push(' /model <模型> <强度> 切换模型+推理强度');
35
- lines.push(' /effort [level] 查看或切换推理强度');
36
- }
37
- return lines.join('\n');
27
+ function formatModelUsage(_agent, _model) {
28
+ return '用法: /model <模型>';
38
29
  }
39
30
  /**
40
31
  * 写入用户级 ~/.claude/settings.json(与 Claude CLI 行为一致)
@@ -341,8 +332,6 @@ export class CommandHandler {
341
332
  return renderCommandCardAsText(card);
342
333
  if (!adapter?.send)
343
334
  return renderCommandCardAsText(card);
344
- if (this.isSessionBusy(opts.interaction.sessionId))
345
- return renderCommandCardAsText(card);
346
335
  try {
347
336
  const envelope = buildEnvelope({
348
337
  channel: opts.channel,
@@ -382,14 +371,6 @@ export class CommandHandler {
382
371
  });
383
372
  return { matched: true, result: '✓ 已回答' };
384
373
  }
385
- /** 判断指定 session 是否有活跃流(用于 idle 守卫和卡片降级) */
386
- isSessionBusy(sessionId) {
387
- for (const agent of this.agentMap.values()) {
388
- if (agent.hasActiveStream(sessionId))
389
- return true;
390
- }
391
- return false;
392
- }
393
374
  /** 获取活跃会话,无会话时自动创建(话题除外) */
394
375
  async ensureSession(channel, channelId, threadId, chatType) {
395
376
  if (threadId) {
@@ -1029,9 +1010,9 @@ export class CommandHandler {
1029
1010
  return ` ${prefix} ${m.key} (${m.nameZh}) - ${m.description}${suffix}`;
1030
1011
  }).join('\n');
1031
1012
  if (isOwner) {
1032
- return { kind: 'command.result', text: `🔐 当前权限模式: ${currentMode}\n\n${modeList}\n\n用法:\n /perm <模式> 切换权限模式\n /perm allow|always|deny 审批权限请求` };
1013
+ return { kind: 'command.result', text: `权限模式: ${currentMode}\n\n${modeList}\n\n用法: /perm <模式> allow|always|deny` };
1033
1014
  }
1034
- return { kind: 'command.result', text: `🔐 当前权限模式: ${currentMode}` };
1015
+ return { kind: 'command.result', text: `当前权限模式: ${currentMode}` };
1035
1016
  }
1036
1017
  const parts = args.split(/\s+/);
1037
1018
  // /perm <mode> 或 /perm allow|always|deny:切换模式 / 快捷审批
@@ -1235,7 +1216,7 @@ export class CommandHandler {
1235
1216
  const list = available.map(a => `${a === currentAgent ? ' ✓' : ' '} ${a}`).join('\n');
1236
1217
  const canSwitchAgent = activeChatType === 'group' ? isOwner : isAdmin;
1237
1218
  if (canSwitchAgent) {
1238
- return { kind: 'command.result', text: `当前 Agent: ${currentAgent}\n\n可用:\n${list}\n\n用法: /agent <name>` };
1219
+ return { kind: 'command.result', text: `当前 Agent: ${currentAgent}\n\n可用:\n${list}\n用法: /agent <name>` };
1239
1220
  }
1240
1221
  return { kind: 'command.result', text: `当前 Agent: ${currentAgent}` };
1241
1222
  }
@@ -1373,7 +1354,7 @@ export class CommandHandler {
1373
1354
  ? `\n推理强度: ${currentEffort === 'auto' ? 'auto (SDK默认)' : currentEffort} (使用 /effort 调整)`
1374
1355
  : '';
1375
1356
  if (isAdmin) {
1376
- return { kind: 'command.result', text: `当前模型: ${currentModel}${effortHint}\n\n可用模型:\n${modelList}\n\n${formatModelUsage(modelAgent, currentModel)}` };
1357
+ return { kind: 'command.result', text: `当前模型: ${currentModel}${effortHint}\n\n可用模型:\n${modelList}\n\n用法: /model <模型>` };
1377
1358
  }
1378
1359
  return { kind: 'command.result', text: `当前模型: ${currentModel}${effortHint}` };
1379
1360
  }
@@ -1494,12 +1475,11 @@ export class CommandHandler {
1494
1475
  }
1495
1476
  // 降级:文本
1496
1477
  const effortDisplay = currentEffort === 'auto' ? 'auto (SDK默认)' : currentEffort;
1497
- const allItems = [...efforts, 'auto'];
1498
- const effortList = allItems.map(e => ` ${e === currentEffort ? '✓' : ' '} ${e}${e === 'auto' ? ' (SDK默认)' : ''}`).join('\n');
1478
+ const effortOptions = [...efforts, 'auto'].join(' / ');
1499
1479
  if (isAdmin) {
1500
- return { kind: 'command.result', text: `⚡ 推理强度: ${effortDisplay}\n\n可选:\n${effortList}\n\n用法: /effort <level>` };
1480
+ return { kind: 'command.result', text: `推理强度: ${effortDisplay} 可选: ${effortOptions} 用法: /effort <level>` };
1501
1481
  }
1502
- return { kind: 'command.result', text: `⚡ 推理强度: ${effortDisplay}` };
1482
+ return { kind: 'command.result', text: `推理强度: ${effortDisplay}` };
1503
1483
  }
1504
1484
  // 带参(切换)需 admin+;无参查询已在上方返回
1505
1485
  if (!isAdmin)
@@ -1534,9 +1514,7 @@ export class CommandHandler {
1534
1514
  return { kind: 'command.error', text: '❌ 无权限:此命令仅限 owner 使用' };
1535
1515
  // 无参数时返回用法说明
1536
1516
  if (normalizedContent === '/aid') {
1537
- return { kind: 'command.result', text: `🆔 AID 身份管理
1538
-
1539
- 用法:
1517
+ return { kind: 'command.result', text: `用法:
1540
1518
  /aid list 列出本地所有 AID
1541
1519
  /aid show <aid> 查看 AID 详情
1542
1520
  /aid new <aid> 创建新 AID
@@ -1546,27 +1524,16 @@ export class CommandHandler {
1546
1524
  /aid agentmd get <aid> 下载并验签 agent.md` };
1547
1525
  }
1548
1526
  if (normalizedContent === '/rpc') {
1549
- return { kind: 'command.result', text: `📡 AUN RPC 调用
1550
-
1551
- 用法:
1552
- /rpc --as <aid> --params <json>
1553
-
1554
- 参数格式:
1555
- 单行 JSON 单次调用
1556
- 多行 JSONL 逐行执行,失败即停
1557
-
1558
- 示例:
1559
- /rpc --as myaid.agentid.pub --params {"method":"meta.ping","params":{}}` };
1527
+ return { kind: 'command.result', text: `用法: /rpc --as <aid> --params <json>
1528
+ 示例: /rpc --as myaid.agentid.pub --params {"method":"meta.ping","params":{}}` };
1560
1529
  }
1561
1530
  if (normalizedContent === '/storage') {
1562
- return { kind: 'command.result', text: `📦 文件存储
1563
-
1564
- 用法:
1565
- /storage upload <aid> <file> <path> [--public] 上传文件
1566
- /storage download <aid> <url> [local-path] 下载文件
1567
- /storage ls <aid> [prefix] 列文件
1568
- /storage rm <aid> <path> 删文件
1569
- /storage quota <aid> 查配额` };
1531
+ return { kind: 'command.result', text: `用法:
1532
+ /storage upload <aid> <file> <path> [--public]
1533
+ /storage download <aid> <url> [local-path]
1534
+ /storage ls <aid> [prefix]
1535
+ /storage rm <aid> <path>
1536
+ /storage quota <aid>` };
1570
1537
  }
1571
1538
  const cliArgs = normalizedContent.slice(1); // strip leading /
1572
1539
  try {
@@ -1644,9 +1611,9 @@ export class CommandHandler {
1644
1611
  return ` ${prefix} ${m.key} — ${m.label}`;
1645
1612
  }).join('\n');
1646
1613
  if (isOwner) {
1647
- return { kind: 'command.result', text: [`📋 中间输出模式: ${currentMode}`, '', modeList, '', '用法: /activity <all|dm|owner|none>'].join('\n') };
1614
+ return { kind: 'command.result', text: `中间输出: ${currentMode} 用法: /activity <all|dm|owner|none>` };
1648
1615
  }
1649
- return { kind: 'command.result', text: `📋 中间输出模式: ${currentMode}` };
1616
+ return { kind: 'command.result', text: `中间输出: ${currentMode}` };
1650
1617
  }
1651
1618
  const newMode = modeMap[activityArg];
1652
1619
  if (!newMode) {
@@ -1678,8 +1645,12 @@ export class CommandHandler {
1678
1645
  const arg = normalizedContent.slice(9).trim();
1679
1646
  const currentMode = chatmodeSession.sessionMode || 'interactive';
1680
1647
  const chatmodeChatType = chatmodeSession.chatType || activeChatType;
1681
- const canSwitch = chatmodeChatType !== 'group' || isAdmin;
1648
+ const isGroup = chatmodeChatType === 'group';
1649
+ const canSwitch = !isGroup;
1682
1650
  if (!arg) {
1651
+ if (isGroup) {
1652
+ return { kind: 'command.result', text: `📋 会话模式: proactive(群聊强制)` };
1653
+ }
1683
1654
  // 尝试发送 CommandCard 卡片
1684
1655
  if (canSwitch) {
1685
1656
  const modes = [
@@ -1712,23 +1683,16 @@ export class CommandHandler {
1712
1683
  }
1713
1684
  // 降级:文本
1714
1685
  if (canSwitch) {
1715
- return { kind: 'command.result', text: [
1716
- `📋 会话模式: ${currentMode}`,
1717
- '',
1718
- '模式说明:',
1719
- ' • interactive — 交互模式:收到消息时才回复,回复直接显示',
1720
- ' • proactive — 主动模式:流式输出静默,由 Agent 自调 ctl send 发声',
1721
- '',
1722
- '用法: /chatmode <interactive|proactive>',
1723
- ].join('\n') };
1724
- }
1725
- return { kind: 'command.result', text: `📋 会话模式: ${currentMode}` };
1686
+ return { kind: 'command.result', text: `会话模式: ${currentMode} 用法: /chatmode <interactive|proactive>` };
1687
+ }
1688
+ return { kind: 'command.result', text: `会话模式: ${currentMode}` };
1726
1689
  }
1727
1690
  if (arg !== 'interactive' && arg !== 'proactive') {
1728
1691
  return { kind: 'command.error', text: `❌ 无效模式: ${arg}\n可选: interactive / proactive` };
1729
1692
  }
1730
- if ((chatmodeSession.chatType || activeChatType) === 'group' && !isAdmin) {
1731
- return { kind: 'command.error', text: '❌ 无权限:群聊中切换会话模式仅限管理员使用' };
1693
+ // 群聊强制 proactive,不可切换
1694
+ if ((chatmodeSession.chatType || activeChatType) === 'group') {
1695
+ return { kind: 'command.error', text: '❌ 群聊强制 proactive 模式,不可切换' };
1732
1696
  }
1733
1697
  if (arg === currentMode) {
1734
1698
  return { kind: 'command.result', text: `📋 当前会话模式已是 ${arg}` };
@@ -1796,17 +1760,10 @@ export class CommandHandler {
1796
1760
  // 卡片降级:fall through 到下方文本输出
1797
1761
  }
1798
1762
  // 降级:文本
1799
- const lines = [];
1800
- lines.push(`📋 分发模式: ${displayMode}`);
1801
- lines.push('');
1802
- lines.push('模式说明:');
1803
- lines.push(' • mention — 提及模式:仅当被@提及时响应群消息(含@all)');
1804
- lines.push(' • broadcast — 广播模式:群内所有消息都触发响应');
1805
1763
  if (isAdmin) {
1806
- lines.push('');
1807
- lines.push('用法: /dispatch <mention|broadcast>');
1764
+ return { kind: 'command.result', text: `分发模式: ${displayMode} 用法: /dispatch <mention|broadcast>` };
1808
1765
  }
1809
- return { kind: 'command.result', text: lines.join('\n') };
1766
+ return { kind: 'command.result', text: `分发模式: ${displayMode}` };
1810
1767
  }
1811
1768
  if (arg !== 'mention' && arg !== 'broadcast') {
1812
1769
  return { kind: 'command.error', text: `❌ 无效模式: ${arg}\n可选: mention / broadcast\n用法: /dispatch <模式>` };
@@ -1815,7 +1772,7 @@ export class CommandHandler {
1815
1772
  return { kind: 'command.error', text: '❌ 无权限:群聊中切换分发模式仅限管理员使用' };
1816
1773
  }
1817
1774
  if (arg === currentMode) {
1818
- return { kind: 'command.result', text: `📋 当前已是 ${arg}` };
1775
+ return { kind: 'command.result', text: `当前已是 ${arg}` };
1819
1776
  }
1820
1777
  const metadata = { ...(dispatchSession.metadata || {}), dispatchMode: arg };
1821
1778
  await this.sessionManager.updateSession(dispatchSession.id, { metadata });
@@ -3111,6 +3068,45 @@ export class CommandHandler {
3111
3068
  this.eventBus.publish({ type: 'trigger:cancelled', triggerId: trigger.id, by: peerId });
3112
3069
  return `✅ 触发器已取消:**${trigger.name}**`;
3113
3070
  }
3071
+ // /trigger update <name|id> [--参数...]
3072
+ if (sub.startsWith('update ')) {
3073
+ if (!manager || !scheduler)
3074
+ return '⚠️ 触发器功能未启用';
3075
+ const args = sub.slice('update '.length);
3076
+ const result = parseTriggerUpdate(args);
3077
+ if (!result.ok)
3078
+ return `❌ ${result.error}`;
3079
+ const { nameOrId, value: patch } = result;
3080
+ // Find trigger: non-admin lookup is scoped
3081
+ let trigger;
3082
+ if (isAdmin) {
3083
+ trigger = manager.getByName(nameOrId) ?? manager.getById(nameOrId);
3084
+ }
3085
+ else {
3086
+ trigger = manager.getByNameScoped(nameOrId, peerId, channel)
3087
+ ?? manager.getByIdScoped(nameOrId, peerId, channel);
3088
+ }
3089
+ if (!trigger) {
3090
+ return isAdmin
3091
+ ? `❌ 未找到触发器:${nameOrId}`
3092
+ : `❌ 未找到触发器 "${nameOrId}",或无权限修改`;
3093
+ }
3094
+ // If schedule changed, recalculate nextFireAt
3095
+ if (patch.scheduleType && patch.scheduleValue) {
3096
+ const now = Date.now();
3097
+ patch.nextFireAt = calcNextFireAt(patch.scheduleType, patch.scheduleValue, now);
3098
+ }
3099
+ let updated;
3100
+ try {
3101
+ updated = manager.update(trigger.id, patch);
3102
+ scheduler.update(updated);
3103
+ }
3104
+ catch (err) {
3105
+ return `❌ 更新失败:${err.message}`;
3106
+ }
3107
+ const nextStr = new Date(updated.nextFireAt).toLocaleString();
3108
+ return `✅ 触发器已更新:**${updated.name}**\n下次触发:${nextStr}`;
3109
+ }
3114
3110
  // /trigger set ...
3115
3111
  if (sub.startsWith('set ')) {
3116
3112
  if (!manager || !scheduler)
@@ -3155,7 +3151,7 @@ export class CommandHandler {
3155
3151
  const nextStr = new Date(nextFireAt).toLocaleString();
3156
3152
  return `✅ 触发器已注册:**${name}**\n下次触发:${nextStr}`;
3157
3153
  }
3158
- return `❌ 未知子命令。用法:\n/trigger — 查看活跃触发器\n/trigger list — 查看所有触发器\n/trigger set <参数> — 注册触发器\n/trigger cancel <名称> — 取消触发器`;
3154
+ return `❌ 未知子命令。用法:\n/trigger — 查看活跃触发器\n/trigger list — 查看所有触发器\n/trigger set <参数> — 注册触发器\n/trigger update <名称|ID> <参数> — 修改触发器\n/trigger cancel <名称> — 取消触发器`;
3159
3155
  }
3160
3156
  // ── /rewind helpers ──
3161
3157
  async handleRewindList(session, agent) {
@@ -3376,7 +3372,10 @@ export class CommandHandler {
3376
3372
  }
3377
3373
  // 4. /send 文本消息:直接通过 adapter 主动发送,不走 handle()
3378
3374
  if (cmd.startsWith('/send ') || cmd === '/send') {
3379
- const text = cmd.startsWith('/send ') ? cmd.slice(6).trim() : '';
3375
+ // 解析 --encrypt 标志和消息文本
3376
+ const raw = cmd.startsWith('/send ') ? cmd.slice(6).trim() : '';
3377
+ const forceEncrypt = raw.startsWith('--encrypt ');
3378
+ const text = forceEncrypt ? raw.slice(10).trim() : raw;
3380
3379
  if (!text)
3381
3380
  return { ok: false, error: '消息内容不能为空' };
3382
3381
  const adapter = this.adapters.get(session.channel);
@@ -3386,8 +3385,13 @@ export class CommandHandler {
3386
3385
  const replyContext = this.buildCtlReplyContext(session);
3387
3386
  const taskId = replyContext?.metadata?.taskId;
3388
3387
  const chatmode = replyContext?.metadata?.chatmode ?? 'interactive';
3389
- await adapter.send(buildEnvelope({ taskId, channel: adapter.channelName, channelId: session.channelId, chatmode, replyContext }), { kind: 'result.text', text, isFinal: true });
3390
- return { ok: true, result: '已发送' };
3388
+ // --encrypt 覆盖 session 加密状态
3389
+ const enrichedReplyContext = forceEncrypt
3390
+ ? { ...(replyContext ?? {}), metadata: { ...(replyContext?.metadata ?? {}), encrypted: true } }
3391
+ : replyContext;
3392
+ await adapter.send(buildEnvelope({ taskId, channel: adapter.channelName, channelId: session.channelId, chatmode, replyContext: enrichedReplyContext }), { kind: 'result.text', text, isFinal: true });
3393
+ // 出方向 jsonl 写入已下沉到 aun.ts:deliverTextEntry,message.send 成功后统一写入。
3394
+ return { ok: true, result: 'ok' };
3391
3395
  }
3392
3396
  catch (err) {
3393
3397
  return { ok: false, error: err.message || String(err) };
@@ -3,6 +3,21 @@ import { summarizeToolInput } from '../permission.js';
3
3
  import fs from 'fs';
4
4
  import path from 'path';
5
5
  import { resolvePaths } from '../../paths.js';
6
+ /**
7
+ * 检测是否为上下文过长错误
8
+ * 统一的检测逻辑,覆盖所有已知的错误文本模式
9
+ */
10
+ function isContextTooLongError(text) {
11
+ if (!text)
12
+ return false;
13
+ const lower = text.toLowerCase();
14
+ return (lower.includes('prompt is too long') ||
15
+ lower.includes('input is too long') ||
16
+ lower.includes('context too long') ||
17
+ lower.includes('context limit') ||
18
+ lower.includes('context_length_exceeded') ||
19
+ text.includes('上下文过长'));
20
+ }
6
21
  let diagStream = null;
7
22
  function getDiagStream() {
8
23
  if (!diagStream) {
@@ -129,7 +144,8 @@ export class IMRenderer {
129
144
  }
130
145
  // ── 文本/活动注入(替代 StreamFlusher.addText/addActivity)──
131
146
  /** 添加文本片段(流式 text) */
132
- addText(text) {
147
+ addText(text, outputTokens, turn) {
148
+ this.emitProgress('text', outputTokens, turn);
133
149
  if (this.opts.envelope.chatmode === 'proactive')
134
150
  return;
135
151
  if (!text)
@@ -155,7 +171,8 @@ export class IMRenderer {
155
171
  this.scheduleFlush();
156
172
  }
157
173
  /** 添加工具调用 */
158
- addToolCall(name, input, callId, descText) {
174
+ addToolCall(name, input, callId, descText, turn, outputTokens) {
175
+ this.emitProgress('tool_call', outputTokens, turn);
159
176
  if (this.opts.envelope.chatmode === 'proactive')
160
177
  return;
161
178
  if (this.opts.suppressActivities)
@@ -174,6 +191,7 @@ export class IMRenderer {
174
191
  }
175
192
  /** 添加工具结果 */
176
193
  addToolResult(name, ok, result, error, callId, durationMs, descText) {
194
+ this.emitProgress('tool_result');
177
195
  if (this.opts.envelope.chatmode === 'proactive')
178
196
  return;
179
197
  if (this.opts.suppressActivities)
@@ -351,6 +369,11 @@ export class IMRenderer {
351
369
  this.flushCount++;
352
370
  }
353
371
  }
372
+ // ── 内部:status.progress 发送 ──
373
+ emitProgress(activityType, outputTokens, turn) {
374
+ const payload = { kind: 'status.progress', metadata: { activityType, ...(turn != null && { turn }), ...(outputTokens != null && { outputTokens }) } };
375
+ this.opts.send(payload).catch(() => { });
376
+ }
354
377
  // ── 内部:proactive 模式(逐事件 activity.batch[1 item]) ──
355
378
  emitProactive(event) {
356
379
  // 对齐 interactive 的 dedup:流式 text 已推过时,complete.result 不再重复发 summary
@@ -367,6 +390,10 @@ export class IMRenderer {
367
390
  this.hasEmittedText = true;
368
391
  this.allText += item.text;
369
392
  }
393
+ const outputTokens = event.outputTokens;
394
+ const turn = event.turn;
395
+ const activityType = item.kind === 'text' ? 'text' : item.kind === 'tool_call' ? 'tool_call' : 'tool_result';
396
+ this.emitProgress(activityType, outputTokens, turn);
370
397
  const payload = { kind: 'activity.batch', items: [item] };
371
398
  // fire-and-forget
372
399
  this.opts.send(payload).catch(err => {
@@ -430,9 +457,20 @@ export class IMRenderer {
430
457
  duration_ms: event.durationMs,
431
458
  };
432
459
  }
433
- case 'error':
460
+ case 'error': {
461
+ // 上下文过长错误不输出(留给外层 auto-compact 处理)
462
+ if (isContextTooLongError(event.error || ''))
463
+ return null;
434
464
  return { kind: 'notice', text: event.error, severity: 'warn' };
435
- case 'complete':
465
+ }
466
+ case 'complete': {
467
+ // 上下文过长错误不输出(留给外层 auto-compact 处理)
468
+ const hasContextError = event.terminalReason === 'prompt_too_long'
469
+ || isContextTooLongError(event.errors?.join(' ') || '')
470
+ || isContextTooLongError(event.result || '');
471
+ if (event.isError && hasContextError) {
472
+ return null;
473
+ }
436
474
  if (event.isError) {
437
475
  const errText = event.errors?.join('; ') || event.result || '任务失败';
438
476
  return {
@@ -452,6 +490,7 @@ export class IMRenderer {
452
490
  };
453
491
  }
454
492
  return null;
493
+ }
455
494
  case 'session_id':
456
495
  case 'state_changed':
457
496
  case 'status':
@@ -133,6 +133,8 @@ export class MessageBridge {
133
133
  };
134
134
  // 5.5 写入消息记录(入方向)
135
135
  const chatDir = this.sessionManager.getChatDir(session);
136
+ const inboundEncrypt = msg.replyContext?.metadata?.encrypted != null ? !!(msg.replyContext.metadata.encrypted) : undefined;
137
+ const inboundChatmode = msg.replyContext?.metadata?.chatmode;
136
138
  appendMessageLog(chatDir, buildInboundEntry({
137
139
  from: msg.peerId || 'unknown',
138
140
  to: msg.selfId || 'self',
@@ -143,6 +145,8 @@ export class MessageBridge {
143
145
  replyTo: msg.replyContext?.replyToMessageId ?? null,
144
146
  permMode: session.identity?.role ?? null,
145
147
  timestamp: fullMessage.timestamp,
148
+ encrypt: inboundEncrypt,
149
+ chatmode: inboundChatmode,
146
150
  }));
147
151
  // 6. ACK + debounce/enqueue
148
152
  // ACK 在到达时立即做(每条独立 ACK),不等合并
@@ -66,6 +66,8 @@ export function buildInboundEntry(opts) {
66
66
  permMode: opts.permMode ?? null,
67
67
  cmdParsed: isCommand ? opts.content.split(/\s/)[0] : null,
68
68
  durationMs: null,
69
+ encrypt: opts.encrypt,
70
+ chatmode: opts.chatmode,
69
71
  };
70
72
  }
71
73
  export function buildOutboundEntry(opts) {
@@ -79,7 +81,7 @@ export function buildOutboundEntry(opts) {
79
81
  chatType: opts.chatType,
80
82
  groupId: opts.groupId ?? null,
81
83
  msgId: opts.msgId ?? null,
82
- msgType: 'text',
84
+ msgType: opts.msgType ?? 'text',
83
85
  content: opts.content,
84
86
  replyTo: opts.replyTo ?? null,
85
87
  agent: opts.agent ?? null,
@@ -89,5 +91,8 @@ export function buildOutboundEntry(opts) {
89
91
  durationMs: opts.durationMs ?? null,
90
92
  numTurns: opts.numTurns ?? null,
91
93
  usage: opts.usage ?? null,
94
+ encrypt: opts.encrypt,
95
+ chatmode: opts.chatmode,
96
+ source: opts.source ?? 'daemon',
92
97
  };
93
98
  }