evolclaw 3.0.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.
Files changed (104) hide show
  1. package/README.md +1 -1
  2. package/bin/ec.js +29 -0
  3. package/dist/agents/baseagent-normalize.js +19 -0
  4. package/dist/agents/claude-runner.js +47 -12
  5. package/dist/agents/codex-runner.js +2 -0
  6. package/dist/agents/gemini-runner.js +9 -9
  7. package/dist/agents/kit-renderer.js +281 -0
  8. package/dist/aun/aid/identity.js +28 -0
  9. package/dist/aun/aid/index.js +1 -1
  10. package/dist/aun/aid/lifecycle-log.js +33 -0
  11. package/dist/aun/msg/group.js +3 -1
  12. package/dist/aun/msg/p2p.js +42 -1
  13. package/dist/channels/aun.js +427 -146
  14. package/dist/channels/dingtalk.js +3 -1
  15. package/dist/channels/feishu.js +128 -7
  16. package/dist/channels/qqbot.js +3 -1
  17. package/dist/channels/wechat.js +4 -1
  18. package/dist/channels/wecom.js +3 -1
  19. package/dist/cli/bench.js +1219 -0
  20. package/dist/cli/index.js +418 -40
  21. package/dist/cli/init.js +3 -4
  22. package/dist/cli/link-rules.js +245 -0
  23. package/dist/cli/net-check.js +640 -0
  24. package/dist/cli/watch-msg.js +666 -0
  25. package/dist/config-store.js +82 -5
  26. package/dist/core/channel-loader.js +23 -10
  27. package/dist/core/command-handler.js +127 -99
  28. package/dist/core/evolagent.js +5 -10
  29. package/dist/core/message/im-renderer.js +93 -48
  30. package/dist/core/message/items-formatter.js +11 -4
  31. package/dist/core/message/message-bridge.js +11 -2
  32. package/dist/core/message/message-log.js +8 -1
  33. package/dist/core/message/message-processor.js +194 -127
  34. package/dist/core/message/message-queue.js +10 -3
  35. package/dist/core/permission.js +95 -3
  36. package/dist/core/relation/peer-identity.js +161 -0
  37. package/dist/core/session/session-manager.js +103 -65
  38. package/dist/core/trigger/manager.js +16 -0
  39. package/dist/core/trigger/parser.js +110 -0
  40. package/dist/core/trigger/scheduler.js +7 -1
  41. package/dist/data/error-dict.json +118 -0
  42. package/dist/eck/baseagent-caps.js +18 -0
  43. package/dist/eck/detect.js +47 -0
  44. package/dist/eck/init.js +77 -0
  45. package/dist/eck/rules-loader.js +28 -0
  46. package/dist/index.js +186 -19
  47. package/dist/net-check.js +640 -0
  48. package/dist/paths.js +31 -40
  49. package/dist/utils/aid-lifecycle-log.js +33 -0
  50. package/dist/utils/atomic-write.js +10 -0
  51. package/dist/utils/cross-platform.js +17 -8
  52. package/dist/utils/error-utils.js +27 -15
  53. package/dist/utils/instance-registry.js +6 -5
  54. package/dist/utils/log-writer.js +2 -1
  55. package/dist/utils/logger.js +10 -0
  56. package/dist/utils/npm-ops.js +35 -3
  57. package/dist/utils/process-introspect.js +16 -38
  58. package/dist/utils/stats.js +216 -2
  59. package/dist/watch-msg.js +26 -11
  60. package/evolclaw-install-aun.md +14 -2
  61. package/kits/docs/GUIDE.md +20 -0
  62. package/kits/docs/INDEX.md +52 -0
  63. package/kits/docs/aun/CHEATSHEET.md +17 -0
  64. package/kits/docs/aun/SYNC_PROTOCOL.md +15 -0
  65. package/kits/docs/channels/feishu.md +27 -0
  66. package/kits/docs/eck_templates/GUIDE.template.md +22 -0
  67. package/kits/docs/eck_templates/INDEX.template.md +28 -0
  68. package/kits/docs/eck_templates/path-registry.template.md +33 -0
  69. package/kits/docs/eck_templates/runtime.template.md +19 -0
  70. package/kits/docs/evolclaw/MSG_GROUP.md +30 -0
  71. package/kits/docs/evolclaw/MSG_PRIVATE.md +72 -0
  72. package/kits/docs/identity/AID_PROFILE_SPEC.md +27 -0
  73. package/kits/docs/identity/PATH_OPS.md +16 -0
  74. package/kits/docs/identity/ROLE_DETAIL.md +20 -0
  75. package/kits/docs/path-registry.md +43 -0
  76. package/kits/eck_manifest.json +95 -0
  77. package/kits/rules/01-overview.md +120 -0
  78. package/kits/rules/02-navigation.md +75 -0
  79. package/kits/rules/03-identity.md +34 -0
  80. package/kits/rules/04-relation.md +49 -0
  81. package/kits/rules/05-venue.md +45 -0
  82. package/kits/rules/06-channel.md +73 -0
  83. package/kits/templates/system-fragments/baseagent.md +2 -0
  84. package/kits/templates/system-fragments/channel.md +10 -0
  85. package/kits/templates/system-fragments/identity.md +12 -0
  86. package/kits/templates/system-fragments/relation.md +9 -0
  87. package/kits/templates/system-fragments/runtime.md +19 -0
  88. package/kits/templates/system-fragments/venue.md +5 -0
  89. package/package.json +7 -5
  90. package/dist/agents/templates.js +0 -122
  91. package/dist/data/prompts.md +0 -137
  92. package/kits/aun/meta.md +0 -25
  93. package/kits/aun/role.md +0 -25
  94. package/kits/templates/group.md +0 -20
  95. package/kits/templates/private.md +0 -9
  96. package/kits/templates/system-fragments/personal-context.md +0 -3
  97. package/kits/templates/system-fragments/self-intro.md +0 -5
  98. package/kits/templates/system-fragments/speaker-intro.md +0 -5
  99. package/kits/templates/system-fragments/venue-intro.md +0 -5
  100. /package/kits/{channels → docs/channels}/aun.md +0 -0
  101. /package/kits/{evolclaw/commands.md → docs/evolclaw/AGENT_CMD.md} +0 -0
  102. /package/kits/{evolclaw → docs/evolclaw}/self-summary.md +0 -0
  103. /package/kits/{evolclaw → docs/evolclaw}/tools.md +0 -0
  104. /package/kits/{evolclaw → docs/identity}/identity-tools.md +0 -0
@@ -150,17 +150,12 @@ export class EvolAgent {
150
150
  this.persist();
151
151
  }
152
152
  // ── ShowActivities ────────────────────────────────────────────────────
153
- getShowActivities(channelKey) {
154
- const inst = this.findChannelInstance(channelKey);
155
- return inst?.showActivities ?? this.merged.show_activities ?? 'all';
153
+ getShowActivities(_channelKey) {
154
+ return this.merged.show_activities ?? 'all';
156
155
  }
157
- setShowActivities(channelKey, mode) {
158
- const inst = this.findRawChannelInstance(channelKey);
159
- if (!inst) {
160
- logger.warn(`[EvolAgent ${this.aid}] setShowActivities: channel "${channelKey}" not found`);
161
- return;
162
- }
163
- inst.showActivities = mode;
156
+ setShowActivities(_channelKey, mode) {
157
+ this.rawAgent.show_activities = mode;
158
+ this.merged.show_activities = mode;
164
159
  this.persist();
165
160
  }
166
161
  // ── Baseagent 字段写入 ────────────────────────────────────────────────
@@ -1,7 +1,23 @@
1
1
  import { logger } from '../../utils/logger.js';
2
+ import { summarizeToolInput } from '../permission.js';
2
3
  import fs from 'fs';
3
4
  import path from 'path';
4
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
+ }
5
21
  let diagStream = null;
6
22
  function getDiagStream() {
7
23
  if (!diagStream) {
@@ -29,8 +45,8 @@ export class IMRenderer {
29
45
  diagEnabled;
30
46
  /** 串行发送队列:保证消息按序到达 */
31
47
  sendChain = Promise.resolve();
32
- /** proactive:是否已发过 thinking 文本(用于去重 complete.result) */
33
- hasEmittedThinking = false;
48
+ /** proactive:是否已发过 text 文本(用于去重 complete.result) */
49
+ hasEmittedText = false;
34
50
  /** 自增 callId 兜底(runner 没提供时用) */
35
51
  syntheticCallSeq = 0;
36
52
  constructor(opts) {
@@ -75,7 +91,34 @@ export class IMRenderer {
75
91
  }
76
92
  /** 是否有 pending 内容 */
77
93
  hasContent() {
78
- return this.textBuffer.length > 0 || this.itemsQueue.some(it => it.kind !== 'thinking');
94
+ return this.textBuffer.length > 0 || this.itemsQueue.some(it => it.kind !== 'text');
95
+ }
96
+ /** 是否有待发送的文本 */
97
+ hasTextPending() {
98
+ return this.textBuffer.length > 0;
99
+ }
100
+ /** flush 当前 textBuffer 作为独立的 result.text(非 final),然后清空 buffer */
101
+ async flushText() {
102
+ if (this.opts.envelope.chatmode === 'proactive')
103
+ return;
104
+ if (this.textBuffer.length === 0)
105
+ return;
106
+ if (this.timer) {
107
+ clearTimeout(this.timer);
108
+ this.timer = undefined;
109
+ }
110
+ const text = this.textBuffer;
111
+ this.textBuffer = '';
112
+ // 清掉 itemsQueue 中的 text items(已发出)
113
+ this.itemsQueue = this.itemsQueue.filter(it => it.kind !== 'text');
114
+ const payload = { kind: 'result.text', text, isFinal: false };
115
+ this.sentContent = true;
116
+ this.sendChain = this.sendChain
117
+ .then(() => this.opts.send(payload))
118
+ .catch(e => logger.warn('[IMRenderer] flushText send failed:', e));
119
+ await this.sendChain;
120
+ this.lastFlush = Date.now();
121
+ this.flushCount++;
79
122
  }
80
123
  /** 是否已发送过内容(用于决定最终 flush 是否带 isFinal 标题) */
81
124
  hasSentContent() {
@@ -92,27 +135,28 @@ export class IMRenderer {
92
135
  /** 从 buffer 中移除指定 pattern(用于文件标记预处理) */
93
136
  stripFromBuffer(pattern) {
94
137
  this.textBuffer = this.textBuffer.replace(pattern, '').trim();
95
- // itemsQueue 中的 thinking items 也同步过滤
138
+ // itemsQueue 中的 text items 也同步过滤
96
139
  for (const item of this.itemsQueue) {
97
- if (item.kind === 'thinking') {
140
+ if (item.kind === 'text') {
98
141
  item.text = item.text.replace(pattern, '');
99
142
  }
100
143
  }
101
144
  }
102
145
  // ── 文本/活动注入(替代 StreamFlusher.addText/addActivity)──
103
146
  /** 添加文本片段(流式 text) */
104
- addText(text) {
147
+ addText(text, outputTokens, turn) {
148
+ this.emitProgress('text', outputTokens, turn);
105
149
  if (this.opts.envelope.chatmode === 'proactive')
106
150
  return;
107
151
  if (!text)
108
152
  return;
109
- // 同一窗口内连续 text delta 合并到最后一个 thinking item
153
+ // 同一窗口内连续 text delta 合并到最后一个 text item
110
154
  const last = this.itemsQueue[this.itemsQueue.length - 1];
111
- if (last && last.kind === 'thinking') {
155
+ if (last && last.kind === 'text') {
112
156
  last.text += text;
113
157
  }
114
158
  else {
115
- this.itemsQueue.push({ kind: 'thinking', text });
159
+ this.itemsQueue.push({ kind: 'text', text });
116
160
  }
117
161
  this.textBuffer += text;
118
162
  this.allText += text;
@@ -127,7 +171,8 @@ export class IMRenderer {
127
171
  this.scheduleFlush();
128
172
  }
129
173
  /** 添加工具调用 */
130
- addToolCall(name, input, callId, descText) {
174
+ addToolCall(name, input, callId, descText, turn, outputTokens) {
175
+ this.emitProgress('tool_call', outputTokens, turn);
131
176
  if (this.opts.envelope.chatmode === 'proactive')
132
177
  return;
133
178
  if (this.opts.suppressActivities)
@@ -146,6 +191,7 @@ export class IMRenderer {
146
191
  }
147
192
  /** 添加工具结果 */
148
193
  addToolResult(name, ok, result, error, callId, durationMs, descText) {
194
+ this.emitProgress('tool_result');
149
195
  if (this.opts.envelope.chatmode === 'proactive')
150
196
  return;
151
197
  if (this.opts.suppressActivities)
@@ -241,17 +287,17 @@ export class IMRenderer {
241
287
  const maxDelay = interval * 2.5;
242
288
  return Math.max(minDelay, Math.min(maxDelay, dynamicDelay));
243
289
  }
244
- /** 仅 flush 非 thinking items(保留 textBuffer 用于后续 final flush) */
290
+ /** 仅 flush 非 text items(text items 和 textBuffer 保留,等待下次完整 flush) */
245
291
  async flushActivitiesInternal() {
246
- const nonThinking = this.itemsQueue.filter(it => it.kind !== 'thinking');
292
+ const nonThinking = this.itemsQueue.filter(it => it.kind !== 'text');
247
293
  if (nonThinking.length === 0)
248
294
  return;
249
295
  if (this.timer) {
250
296
  clearTimeout(this.timer);
251
297
  this.timer = undefined;
252
298
  }
253
- // 移除已 flush 的 non-thinking items,保留 thinking items
254
- this.itemsQueue = this.itemsQueue.filter(it => it.kind === 'thinking');
299
+ // 移除已 flush 的 non-text items,保留 text items
300
+ this.itemsQueue = this.itemsQueue.filter(it => it.kind === 'text');
255
301
  const payload = { kind: 'activity.batch', items: nonThinking };
256
302
  if (this.diagEnabled)
257
303
  diag(this.instanceId, 'flushActivitiesOnly', { itemCount: nonThinking.length });
@@ -276,13 +322,13 @@ export class IMRenderer {
276
322
  if (this.opts.fileMarkerPattern) {
277
323
  this.textBuffer = this.textBuffer.replace(this.opts.fileMarkerPattern, '').trim();
278
324
  for (const item of this.itemsQueue) {
279
- if (item.kind === 'thinking')
325
+ if (item.kind === 'text')
280
326
  item.text = item.text.replace(this.opts.fileMarkerPattern, '');
281
327
  }
282
328
  }
283
- // 清掉空 thinking items
329
+ // 清掉空 text items
284
330
  const items = this.itemsQueue.filter(it => {
285
- if (it.kind === 'thinking')
331
+ if (it.kind === 'text')
286
332
  return it.text.length > 0;
287
333
  return true;
288
334
  });
@@ -290,7 +336,6 @@ export class IMRenderer {
290
336
  const finalText = isFinal ? this.textBuffer : '';
291
337
  if (isFinal)
292
338
  this.textBuffer = '';
293
- // 非 final flush 保留 textBuffer,供最终 result.text 使用
294
339
  if (this.diagEnabled) {
295
340
  diag(this.instanceId, 'flush', {
296
341
  isFinal,
@@ -300,12 +345,8 @@ export class IMRenderer {
300
345
  sinceLastFlush: Date.now() - this.lastFlush,
301
346
  });
302
347
  }
303
- // 1. interactive 模式下:isFinal=true 时不发 thinking-only batch
304
- // (避免和最终 result.text 重复——最终回复已在 textBuffer 里)
305
- let itemsForBatch = items;
306
- if (isFinal) {
307
- itemsForBatch = items.filter(it => it.kind !== 'thinking');
308
- }
348
+ // 1. interactive 模式下:不发 text items(由 result.text 统一发送最终文本)
349
+ let itemsForBatch = items.filter(it => it.kind !== 'text');
309
350
  if (itemsForBatch.length > 0) {
310
351
  const payload = { kind: 'activity.batch', items: itemsForBatch };
311
352
  this.sentContent = true;
@@ -328,22 +369,31 @@ export class IMRenderer {
328
369
  this.flushCount++;
329
370
  }
330
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
+ }
331
377
  // ── 内部:proactive 模式(逐事件 activity.batch[1 item]) ──
332
378
  emitProactive(event) {
333
379
  // 对齐 interactive 的 dedup:流式 text 已推过时,complete.result 不再重复发 summary
334
380
  if (event.type === 'complete' &&
335
381
  !event.isError &&
336
382
  event.result &&
337
- this.hasEmittedThinking) {
383
+ this.hasEmittedText) {
338
384
  return;
339
385
  }
340
386
  const item = this.mapEventToItem(event);
341
387
  if (!item)
342
388
  return;
343
- if (item.kind === 'thinking') {
344
- this.hasEmittedThinking = true;
389
+ if (item.kind === 'text') {
390
+ this.hasEmittedText = true;
345
391
  this.allText += item.text;
346
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);
347
397
  const payload = { kind: 'activity.batch', items: [item] };
348
398
  // fire-and-forget
349
399
  this.opts.send(payload).catch(err => {
@@ -355,9 +405,9 @@ export class IMRenderer {
355
405
  case 'text':
356
406
  if (!event.text)
357
407
  return null;
358
- return { kind: 'thinking', text: event.text };
408
+ return { kind: 'text', text: event.text };
359
409
  case 'tool_use': {
360
- const desc = this.summarizeInput(event.input, event.name);
410
+ const desc = summarizeToolInput(event.name, event.input || {});
361
411
  return {
362
412
  kind: 'tool_call',
363
413
  call_id: event.callId || this.synthCallId(),
@@ -407,9 +457,20 @@ export class IMRenderer {
407
457
  duration_ms: event.durationMs,
408
458
  };
409
459
  }
410
- case 'error':
460
+ case 'error': {
461
+ // 上下文过长错误不输出(留给外层 auto-compact 处理)
462
+ if (isContextTooLongError(event.error || ''))
463
+ return null;
411
464
  return { kind: 'notice', text: event.error, severity: 'warn' };
412
- 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
+ }
413
474
  if (event.isError) {
414
475
  const errText = event.errors?.join('; ') || event.result || '任务失败';
415
476
  return {
@@ -429,6 +490,7 @@ export class IMRenderer {
429
490
  };
430
491
  }
431
492
  return null;
493
+ }
432
494
  case 'session_id':
433
495
  case 'state_changed':
434
496
  case 'status':
@@ -437,23 +499,6 @@ export class IMRenderer {
437
499
  return null;
438
500
  }
439
501
  }
440
- summarizeInput(input, toolName) {
441
- if (!input || typeof input !== 'object')
442
- return '';
443
- if (toolName === 'Bash' && typeof input.command === 'string') {
444
- const cmd = input.command;
445
- if (cmd.includes('evolclaw ctl send') || cmd.includes('evolclaw ctl file')) {
446
- return cmd;
447
- }
448
- }
449
- return (input.description ||
450
- input.file_path ||
451
- input.pattern ||
452
- (typeof input.command === 'string' ? input.command.substring(0, 80) : '') ||
453
- (typeof input.prompt === 'string' ? input.prompt.substring(0, 80) : '') ||
454
- (typeof input.query === 'string' ? input.query.substring(0, 80) : '') ||
455
- '');
456
- }
457
502
  stringifyResult(result) {
458
503
  if (result === null || result === undefined)
459
504
  return '';
@@ -5,7 +5,7 @@
5
5
  */
6
6
  export function formatItemsAsText(items) {
7
7
  if (!items || items.length === 0)
8
- return '';
8
+ return ''; // early exit
9
9
  const lines = [];
10
10
  for (const item of items) {
11
11
  const line = formatItem(item);
@@ -16,20 +16,27 @@ export function formatItemsAsText(items) {
16
16
  }
17
17
  function formatItem(item) {
18
18
  switch (item.kind) {
19
- case 'thinking':
19
+ case 'text':
20
20
  return item.text;
21
21
  case 'reasoning':
22
22
  return `💭 ${item.text}`;
23
23
  case 'tool_call': {
24
24
  const desc = item.text || summarizeArgs(item.arguments);
25
- return desc ? `🔧 ${item.name}: ${desc}` : `🔧 ${item.name}`;
25
+ if (!desc)
26
+ return `🔧 ${item.name}`;
27
+ // 多行 desc(如 Edit diff):第一行跟工具名同行,代码块从新行开始
28
+ if (desc.includes('\n')) {
29
+ const nlIdx = desc.indexOf('\n');
30
+ return `🔧 ${item.name} ${desc.slice(0, nlIdx)}\n${desc.slice(nlIdx + 1)}`;
31
+ }
32
+ return `🔧 ${item.name}: ${desc}`;
26
33
  }
27
34
  case 'tool_result': {
28
35
  if (!item.ok) {
29
36
  const errMsg = item.error || (typeof item.result === 'string' ? item.result : '执行失败');
30
37
  return `⚠️ ${item.name}: ${errMsg}`;
31
38
  }
32
- return item.text ? `✅ ${item.name}: ${item.text}` : `✅ ${item.name}`;
39
+ return item.text ? `✓ ${item.name}: ${item.text}` : `✓ ${item.name}`;
33
40
  }
34
41
  case 'progress':
35
42
  return `⏳ ${item.text}`;
@@ -63,6 +63,8 @@ export class MessageBridge {
63
63
  onMessage(async (msg) => {
64
64
  try {
65
65
  let content = msg.content.trim();
66
+ // 渠道入站日志
67
+ logger.channelIn({ channel: channelName, channelId: msg.channelId, peerId: msg.peerId, peerName: msg.peerName, chatType: msg.chatType, msgId: msg.messageId, threadId: msg.threadId, content, images: msg.images?.length ?? 0, mentions: msg.mentions, replyContext: msg.replyContext });
66
68
  // 0. 自定义消息快速路径(menu.query 等)
67
69
  if (await this.handleCustomPayload(content, channelName, msg, sendReply, adapter))
68
70
  return;
@@ -75,7 +77,10 @@ export class MessageBridge {
75
77
  if (this.cmdHandler.isCommand(cmdContent)) {
76
78
  logger.debug(`[MessageBridge] Command detected: "${cmdContent}", routing to handler`);
77
79
  }
78
- if (await this.handleCommand(cmdContent, channelName, msg.channelId, (text) => sendReply(msg.channelId, text, msg.replyContext), msg.peerId, msg.threadId, msg.chatType, msg.source, msg.replyContext))
80
+ if (await this.handleCommand(cmdContent, channelName, msg.channelId, (text) => {
81
+ logger.channelOut({ channel: channelName, channelId: msg.channelId, taskId: `cmd-${msg.messageId || Date.now()}`, payload: { kind: 'command.result', text } });
82
+ return sendReply(msg.channelId, text, msg.replyContext);
83
+ }, msg.peerId, msg.threadId, msg.chatType, msg.source, msg.replyContext))
79
84
  return;
80
85
  // 3. session 解析(使用 Channel 层填充的 chatType)
81
86
  const chatType = msg.chatType || 'private';
@@ -104,7 +109,7 @@ export class MessageBridge {
104
109
  const owningAgent = this.agentRegistry?.resolveByChannel(channelName);
105
110
  const effectiveProjectPath = owningAgent?.projectPath
106
111
  ?? this.defaultProjectPath;
107
- const session = await this.sessionManager.getOrCreateSession(channelName, msg.channelId, effectiveProjectPath, msg.threadId, Object.keys(metadata).length ? metadata : undefined, undefined, msg.peerId, chatType, undefined, msg.selfId, msg.channelType || effectiveChannelType);
112
+ const session = await this.sessionManager.getOrCreateSession(channelName, msg.channelId, effectiveProjectPath, msg.threadId, Object.keys(metadata).length ? metadata : undefined, undefined, msg.peerId, chatType, undefined, msg.selfId, msg.channelType || effectiveChannelType, msg.peerType);
108
113
  // 4. 消息前缀(由 policy 决定)
109
114
  const channelInfo = this.processor.getChannelInfo?.(channelName);
110
115
  if (channelInfo?.policy) {
@@ -128,6 +133,8 @@ export class MessageBridge {
128
133
  };
129
134
  // 5.5 写入消息记录(入方向)
130
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;
131
138
  appendMessageLog(chatDir, buildInboundEntry({
132
139
  from: msg.peerId || 'unknown',
133
140
  to: msg.selfId || 'self',
@@ -138,6 +145,8 @@ export class MessageBridge {
138
145
  replyTo: msg.replyContext?.replyToMessageId ?? null,
139
146
  permMode: session.identity?.role ?? null,
140
147
  timestamp: fullMessage.timestamp,
148
+ encrypt: inboundEncrypt,
149
+ chatmode: inboundChatmode,
141
150
  }));
142
151
  // 6. ACK + debounce/enqueue
143
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,
@@ -87,5 +89,10 @@ export function buildOutboundEntry(opts) {
87
89
  permMode: null,
88
90
  cmdParsed: null,
89
91
  durationMs: opts.durationMs ?? null,
92
+ numTurns: opts.numTurns ?? null,
93
+ usage: opts.usage ?? null,
94
+ encrypt: opts.encrypt,
95
+ chatmode: opts.chatmode,
96
+ source: opts.source ?? 'daemon',
90
97
  };
91
98
  }