evolclaw 3.1.7 → 3.1.8

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/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  # Changelog
2
2
 
3
+ ## v3.1.8 (2026-06-04)
4
+
5
+ ### Bug Fixes
6
+
7
+ - **npm 包瘦身** — 移除误入 npm 包的 10 个多余文件:`assets/wechat-group-qr.jpeg`(232 KB 运营素材,代码零引用)+ `dist/cli/watch-web/` 的 9 个陈旧构建产物(源码已迁出为独立 `evolclaw-web` 包,但旧 `.js` 残留在 dist)。包大小 729 KB → 493 KB(-236 KB),文件数 168 → 159
8
+ - **build 脚本清理 dist** — `tsc` 前先 `rm -rf dist`,避免源码删除后陈旧 `.js` 残留被打包
9
+
3
10
  ## v3.1.7 (2026-06-04)
4
11
 
5
12
  ### New Features
@@ -41,6 +41,9 @@ function renderOneItem(item, sessionVars, sessionCache, contentSentinel) {
41
41
  peerId: item.peerId ?? sessionVars.peerId,
42
42
  peerName: item.peerName ?? sessionVars.peerName,
43
43
  peerType: item.peerType ?? sessionVars.peerType,
44
+ sameDevice: item.sameDevice ?? sessionVars.sameDevice,
45
+ sameNetwork: item.sameNetwork ?? sessionVars.sameNetwork,
46
+ sameEgressIp: item.sameEgressIp ?? sessionVars.sameEgressIp,
44
47
  now: formatLocalTime(item.timestamp ?? Date.now(), sessionVars.timezone ? String(sessionVars.timezone) : undefined),
45
48
  // content held as a per-call random sentinel, swapped back post-render.
46
49
  // Using a UUID means no real message can collide with it.
@@ -2629,25 +2629,25 @@ export class AUNChannelPlugin {
2629
2629
  return;
2630
2630
  }
2631
2631
  case 'status.progress':
2632
- channel.sendProcessingStatus(channelId, 'progress', envelope.taskId, envelope.taskId, ctx, payload.metadata);
2632
+ channel.sendProcessingStatus(channelId, 'progress', envelope.sessionId ?? envelope.taskId, envelope.taskId, ctx, payload.metadata);
2633
2633
  return;
2634
2634
  case 'status.started':
2635
- channel.sendProcessingStatus(channelId, 'start', envelope.taskId, envelope.taskId, ctx, payload.metadata);
2635
+ channel.sendProcessingStatus(channelId, 'start', envelope.sessionId ?? envelope.taskId, envelope.taskId, ctx, payload.metadata);
2636
2636
  return;
2637
2637
  case 'status.queued':
2638
- channel.sendProcessingStatus(channelId, 'queued', envelope.taskId, envelope.taskId, ctx, payload.metadata);
2638
+ channel.sendProcessingStatus(channelId, 'queued', envelope.sessionId ?? envelope.taskId, envelope.taskId, ctx, payload.metadata);
2639
2639
  return;
2640
2640
  case 'status.completed':
2641
- channel.sendProcessingStatus(channelId, 'done', envelope.taskId, envelope.taskId, ctx, payload.metadata);
2641
+ channel.sendProcessingStatus(channelId, 'done', envelope.sessionId ?? envelope.taskId, envelope.taskId, ctx, payload.metadata);
2642
2642
  return;
2643
2643
  case 'status.interrupted':
2644
- channel.sendProcessingStatus(channelId, 'interrupted', envelope.taskId, envelope.taskId, ctx, payload.metadata);
2644
+ channel.sendProcessingStatus(channelId, 'interrupted', envelope.sessionId ?? envelope.taskId, envelope.taskId, ctx, payload.metadata);
2645
2645
  return;
2646
2646
  case 'status.error':
2647
- channel.sendProcessingStatus(channelId, 'error', envelope.taskId, envelope.taskId, ctx, payload.metadata);
2647
+ channel.sendProcessingStatus(channelId, 'error', envelope.sessionId ?? envelope.taskId, envelope.taskId, ctx, payload.metadata);
2648
2648
  return;
2649
2649
  case 'status.timeout':
2650
- channel.sendProcessingStatus(channelId, 'timeout', envelope.taskId, envelope.taskId, ctx, payload.metadata);
2650
+ channel.sendProcessingStatus(channelId, 'timeout', envelope.sessionId ?? envelope.taskId, envelope.taskId, ctx, payload.metadata);
2651
2651
  return;
2652
2652
  case 'interaction': {
2653
2653
  const req = payload.interaction;
@@ -76,6 +76,7 @@ function canCompactAgent(agent) {
76
76
  export function buildEnvelope(opts) {
77
77
  return {
78
78
  taskId: opts.taskId ?? `interaction-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
79
+ sessionId: opts.sessionId,
79
80
  channel: opts.channel,
80
81
  channelId: opts.channelId,
81
82
  agentName: opts.agentName ?? '<unknown>',
@@ -448,6 +449,7 @@ export class MessageProcessor {
448
449
  const isAutonomous = session.sessionMode === 'autonomous';
449
450
  const envelope = buildEnvelope({
450
451
  taskId,
452
+ sessionId: session.id,
451
453
  channel: message.channel,
452
454
  channelId: message.channelId,
453
455
  agentName: agentNameForStats,
@@ -678,9 +680,9 @@ export class MessageProcessor {
678
680
  peerName: peerName || undefined,
679
681
  peerRole: session.identity?.role || 'anonymous',
680
682
  peerType: message.peerType || undefined,
681
- sameDevice: message.sameDevice || undefined,
682
- sameNetwork: message.sameNetwork || undefined,
683
- sameEgressIp: message.sameEgressIp || undefined,
683
+ sameDevice: message.sameDevice ?? false,
684
+ sameNetwork: message.sameNetwork ?? false,
685
+ sameEgressIp: message.sameEgressIp ?? false,
684
686
  groupId: session.metadata?.groupId || undefined,
685
687
  chatType: session.chatType || null,
686
688
  channel: currentChannelType || null,
@@ -729,8 +731,9 @@ export class MessageProcessor {
729
731
  ? message.items
730
732
  : [{
731
733
  peerId: message.peerId, peerName: peerName || undefined,
732
- peerType: message.peerType, content: message.content,
733
- timestamp: message.timestamp,
734
+ peerType: message.peerType,
735
+ sameDevice: message.sameDevice, sameNetwork: message.sameNetwork, sameEgressIp: message.sameEgressIp,
736
+ content: message.content, timestamp: message.timestamp,
734
737
  images: message.images,
735
738
  }];
736
739
  renderResult = renderMessageBody(renderItems, kitCtx.vars, session.id);
@@ -209,6 +209,7 @@ export class MessageQueue {
209
209
  else {
210
210
  subMessages.push({
211
211
  peerId: m.peerId, peerName: m.peerName, peerType: m.peerType,
212
+ sameDevice: m.sameDevice, sameNetwork: m.sameNetwork, sameEgressIp: m.sameEgressIp,
212
213
  content: m.content, timestamp: m.timestamp,
213
214
  images: m.images && m.images.length > 0 ? m.images : undefined,
214
215
  });
@@ -67,6 +67,44 @@ manifest 是 `{ "$schema_version": 1, "sections": [...] }`。每个 section:
67
67
 
68
68
  合并后统一按 `order` 升序排序。改某段行为(如关掉某层、调顺序、换条件)优先用覆盖文件,不动基础 manifest。
69
69
 
70
+ ### 覆盖示例
71
+
72
+ 禁用某段:
73
+
74
+ ```json
75
+ { "sections": [ { "id": "venue-client", "enabled": false } ] }
76
+ ```
77
+
78
+ 改加载条件(仅 owner/admin 才载入对端档案):
79
+
80
+ ```json
81
+ { "sections": [ { "id": "peer-profile", "when": { "var": "peerRole", "in": ["owner", "admin"] } } ] }
82
+ ```
83
+
84
+ 新增自定义段:
85
+
86
+ ```json
87
+ {
88
+ "sections": [
89
+ {
90
+ "id": "my-custom-context",
91
+ "type": "file",
92
+ "file": "$AGENT_DIR/custom/my-rules.md",
93
+ "order": 25,
94
+ "needsInjection": false,
95
+ "when": "always",
96
+ "description": "我的自定义规则"
97
+ }
98
+ ]
99
+ }
100
+ ```
101
+
102
+ 完全替换(忽略基础 manifest):
103
+
104
+ ```json
105
+ { "$schema_version": 1, "mode": "replace", "sections": [ ... ] }
106
+ ```
107
+
70
108
  ## 路径与模板渲染
71
109
 
72
110
  ### 路径占位符(所有 section 的 file/path)
@@ -74,6 +112,23 @@ manifest 是 `{ "$schema_version": 1, "sections": [...] }`。每个 section:
74
112
  - `$NAME`(大写)→ 从 vars 取真值,如 `$KITS_DOCS` → 包内文档目录。
75
113
  - `{{key}}` → 从 vars 取真值,如 `{{chatType}}` → `private`,`{{peerKey}}` → `aun#alice.aid.pub`。
76
114
 
115
+ manifest 中常用路径变量的展开值:
116
+
117
+ | 变量 | 展开为 |
118
+ |------|--------|
119
+ | `$PACKAGE_ROOT` | evolclaw 包根 |
120
+ | `$EVOLCLAW_HOME` | 用户数据根(默认 `~/.evolclaw`) |
121
+ | `$KITS` | `$PACKAGE_ROOT/kits` |
122
+ | `$KITS_RULES` | `$KITS/rules` |
123
+ | `$KITS_DOCS` | `$KITS/docs` |
124
+ | `$KITS_TEMPLATES` | `$KITS/templates` |
125
+ | `$KITS_FRAGMENTS` | `$KITS_TEMPLATES/system-fragments` |
126
+ | `$ECK` | `$EVOLCLAW_HOME/eck` |
127
+ | `$AGENT_DIR` | `$EVOLCLAW_HOME/agents/<selfAid>` |
128
+ | `$PERSONAL_DIR` | `$AGENT_DIR/personal` |
129
+ | `$RELATIONS_DIR` | `$AGENT_DIR/relations` |
130
+ | `$VENUES_DIR` | `$AGENT_DIR/venues` |
131
+
77
132
  任一占位符解析为空 → 该 section 视为"未解析",跳过(调试输出标 `unresolved-vars`)。文件不存在 → 标 `not-exist`,也跳过。这是**正常机制**:很多 section 靠"路径解析不出来"自然落选(如 coding 场景没有 `$PERSONAL_DIR`)。
78
133
 
79
134
  ### 模板渲染(仅 needsInjection:true 的文件)
@@ -118,6 +173,16 @@ vars 由 evolclaw 在 `message-processor.ts` 按当前会话构造。分两类
118
173
 
119
174
  coding 场景(无 channel/无身份)下,`chatType`、`channel`、`selfAid`、`peer*` 等均为空——这正是身份/关系/环境/渠道层落选的原因。
120
175
 
176
+ ## 三种加载方式
177
+
178
+ | 加载方式 | 决策者 | 驱动方式 | 例子 |
179
+ |---------|-------|---------|------|
180
+ | **全量加载** | manifest | `when: "always"` | rules/ 核心规则、session 参数 |
181
+ | **按条件自动加载** | manifest | `when` 条件 + 路径存在性 | 身份层(chatType 非空时)、对端档案(peerKey 非空时) |
182
+ | **按需加载** | agent 自主 | agent 在对话中主动 Read 文件 | 查阅 `$KITS_DOCS/` 下的详细参考文档 |
183
+
184
+ 前两种由 manifest 控制,第三种由 agent 根据各层文档中的"按需加载指引"自主决定。
185
+
121
186
  ## 默认 manifest 的段(按 order)
122
187
 
123
188
  | order | id | 类型 | 加载条件(when) | inject |
@@ -140,6 +205,27 @@ coding 场景(无 channel/无身份)下,`chatType`、`channel`、`selfAid`
140
205
 
141
206
  > 注意:`session`(60) 是 `always`,`baseagent`(70) 只看 `baseAgent` 是否注入——这两段**与 chatType 无关**,coding 场景也会加载。所谓"coding 仅 rules"是近似说法:精确地说 coding 场景命中的是 rules + session + baseagent(其余因 chatType/channel 为空而落选)。
142
207
 
208
+ ## 环境层文档目录结构
209
+
210
+ venue-* 段从两棵目录树取文件——随包发布的通用文档(只读)和 agent 私有的具体环境文档(按需创建):
211
+
212
+ ```
213
+ $KITS_DOCS/venues/ 通用环境文档(随包发布,只读)
214
+ ├── private.md 单聊场景通用指引
215
+ ├── group.md 群聊场景通用指引
216
+ ├── aun-private.md AUN 单聊特有
217
+ ├── aun-group.md AUN 群聊特有
218
+ ├── feishu-private.md 飞书单聊特有
219
+ ├── feishu-group.md 飞书群聊特有
220
+ ├── client-desktop.md 桌面端环境
221
+ ├── client-mobile.md 移动端环境
222
+ └── ...
223
+
224
+ $AGENT_DIR/venues/ agent 私有环境文档(按需创建)
225
+ └── <channel>#<urlEncode(groupId)>/
226
+ └── profile.md 具体群的特别内容
227
+ ```
228
+
143
229
  ## 输出结构
144
230
 
145
231
  所有命中 section 的文件内容拼成一个块,注入 system prompt:
@@ -0,0 +1,234 @@
1
+ # EvolClaw 提示词装载全景(Prompt Loading Architecture)
2
+
3
+ > 文档范围:消息从渠道到 base agent 的完整提示词装载流程,涵盖系统提示词渲染层与消息渲染层两套机制。
4
+ > 最后更新:2026-06-04
5
+
6
+ ---
7
+
8
+ ## 总体数据流
9
+
10
+ ```
11
+ 收到一条消息
12
+
13
+
14
+ ┌──────────────────────────────────────────────────────────┐
15
+ │ MessageBridge │
16
+ │ • 消息预处理(去重/拦截/chatType 填充) │
17
+ │ • messagePrefix 硬编码已移除(归消息渲染层) │
18
+ │ • 构造 Message(含 items=undefined 此时) │
19
+ └──────────────────────┬───────────────────────────────────┘
20
+
21
+
22
+ ┌──────────────────────────────────────────────────────────┐
23
+ │ MessageQueue │
24
+ │ • 去重 / 单聊 interrupt / 群聊 FIFO │
25
+ │ • dequeueGreedy:弹出连续同 peerId 消息 │
26
+ │ • mergeItems(多条时): │
27
+ │ - content = join('\n')(兜底) │
28
+ │ - items[] = 每条 SubMessage{peer,time,images,...} │
29
+ │ - images/mentions 扁平合并(给 runQuery 的总量) │
30
+ └──────────────────────┬───────────────────────────────────┘
31
+ │ Message(可能带 items[])
32
+
33
+ ┌──────────────────────────────────────────────────────────┐
34
+ │ MessageProcessor.processMessage() │
35
+ │ │
36
+ │ ① wrapPrompt 准备(中断包装函数) │
37
+ │ effectivePrompt = wrapPrompt(message.content) ← 兜底 │
38
+ │ │
39
+ │ ② 构造 kitCtx.vars(~47 个变量,见下节) │
40
+ │ │
41
+ │ ③ 系统提示词渲染层 │
42
+ │ renderKitSections(kitCtx) │
43
+ │ effectiveSystemPrompt = persona + kitContext │
44
+ │ │
45
+ │ ④ 消息渲染层 │
46
+ │ renderMessageBody(items, kitCtx.vars, sessionId) │
47
+ │ effectivePrompt = wrapPrompt(body) ← 覆盖兜底 │
48
+ │ renderImages = result.images │
49
+ │ │
50
+ │ ⑤ agent.runQuery( │
51
+ │ effectivePrompt, │
52
+ │ renderImages ?? message.images, │
53
+ │ effectiveSystemPrompt, │
54
+ │ modelOverride │
55
+ │ ) │
56
+ └──────────────────────────────────────────────────────────┘
57
+ ```
58
+
59
+ ---
60
+
61
+ ## 两个渲染层对比
62
+
63
+ | | 系统提示词渲染层 | 消息渲染层 |
64
+ |--|--|--|
65
+ | **驱动文件** | `kits/eck_manifest.json` | `kits/eck_message_manifest.json` |
66
+ | **覆盖文件** | `$EVOLCLAW_HOME/eck/eck_manifest.json` | `$EVOLCLAW_HOME/eck/eck_message_manifest.json` |
67
+ | **模板目录** | `kits/templates/system-fragments/` | `kits/templates/message-fragments/` |
68
+ | **输出去向** | `systemPrompt.append`(每轮覆盖,不进 transcript) | `effectivePrompt`(进 transcript,成永久历史) |
69
+ | **vars 粒度** | 会话级(一次构造,整批复用) | item 级(每条叠加 peerName/now/images) |
70
+ | **模板空行** | 删除(stripBlankLines=true,紧凑) | 保留(stripBlankLines=false,消息多段) |
71
+ | **content 注入** | N/A | 哨兵末步字面量注入(防二次解析) |
72
+ | **缓存** | per-sessionId(跨消息复用文件内容) | per-renderMessageBody 调用(局部) |
73
+ | **debug 输出** | `eck-debug/context-*.md` `fragments-*.md` `manifest-*.md` `vars-*.json` | `eck-debug/msg-render-*.md` |
74
+
75
+ ---
76
+
77
+ ## manifest 共享引擎(manifest-engine.ts)
78
+
79
+ 两个渲染层共用同一套原语,由 `src/agents/manifest-engine.ts` 提供:
80
+
81
+ | 函数 | 作用 |
82
+ |------|------|
83
+ | `loadManifest(filename)` | 加载并合并 manifest,按文件名缓存 |
84
+ | `evaluateWhen(when, vars)` | 求值 section 加载条件 |
85
+ | `renderTemplate(tpl, vars, stripBlankLines)` | 条件块 + 变量替换,可选删空行 |
86
+ | `resolvePathWithDiag(rawPath, vars)` | `$VAR` / `{{key}}` 路径展开 + 诊断 |
87
+ | `loadSectionFiles(section, vars, cache)` | 按 section 类型加载文件内容 |
88
+ | `buildPathMappings(vars)` / `shortenPath` | debug 输出路径别名化 |
89
+ | `invalidateManifestCache()` | 清全部 manifest 缓存 |
90
+
91
+ ---
92
+
93
+ ## 系统提示词渲染层详解
94
+
95
+ ### 执行位置
96
+
97
+ `src/core/message/message-processor.ts` → `renderKitSections(kitCtx)` → `src/agents/kit-renderer.ts`
98
+
99
+ ### manifest 默认段(按 order)
100
+
101
+ | order | id | 加载条件 | needsInjection | 内容 |
102
+ |-------|----|----|--------|------|
103
+ | 10 | rules | always | ✗ | `$KITS_RULES/` 目录(ECK 核心规则) |
104
+ | 20 | identity-layer | chatType≠null | ✓ | 身份层 fragment |
105
+ | 21 | persona | chatType≠null | ✗ | `$PERSONAL_DIR/persona.md` |
106
+ | 22 | working-memory | chatType≠null | ✗ | `$PERSONAL_DIR/memory/working.md` |
107
+ | 30 | relation-layer | chatType∈{private,group} | ✓ | 关系层 fragment |
108
+ | 35 | peer-profile | peerKey≠null | ✗ | 对端 profile.md |
109
+ | 40 | venue-fragment | chatType≠null | ✓ | 环境层 fragment |
110
+ | 41 | venue-chattype | chatType≠null | ✗ | `venues/{{chatType}}.md` |
111
+ | 42 | venue-channel-chattype | chatType≠null | ✗ | `venues/{{channel}}-{{chatType}}.md` |
112
+ | 43 | venue-group-profile | groupId≠null | ✗ | 群 venue profile.md |
113
+ | 44 | venue-client | clientType≠null | ✗ | `venues/client-{{clientType}}.md` |
114
+ | 50 | channel-layer | channel≠null | ✓ | 渠道层 fragment |
115
+ | 55 | commands | channel≠null | ✓ | 命令集能力卡 |
116
+ | 60 | session | always | ✓ | 会话层 fragment(含 localDate/weekday) |
117
+ | 70 | baseagent | baseAgent≠null | ✓ | base agent 配置 fragment |
118
+
119
+ ### 输出结构
120
+
121
+ ```
122
+ <system-reminder>
123
+ EvolClaw Context Kit documents are shown below.
124
+
125
+ Contenu de $KITS_RULES/01-overview.md (rules — ECK 核心规则):
126
+ ...(各 section 内容,按 order)
127
+
128
+ IMPORTANT: Use this context when it affects the current interaction.
129
+ </system-reminder>
130
+ ```
131
+
132
+ ---
133
+
134
+ ## 消息渲染层详解
135
+
136
+ ### 执行位置
137
+
138
+ `src/core/message/message-processor.ts` → `renderMessageBody(items, vars, sessionId)` → `src/agents/message-renderer.ts`
139
+
140
+ ### 渲染流程
141
+
142
+ ```
143
+ for each SubMessage in items:
144
+ sentinel = '\x00ECMSG-<UUID>\x00' ← 每次调用独立,null 字节在渠道消息中不可能出现
145
+ itemVars = {
146
+ ...sessionVars,
147
+ peerId / peerName / peerType, ← 本条消息自己的发送者
148
+ now = formatLocalTime(timestamp), ← 本条消息自己的时刻
149
+ content = sentinel, ← 占位,末步换回
150
+ }
151
+ loadManifest(eck_message_manifest) → 选段 → renderTemplate(stripBlankLines=false)
152
+ rendered.split(sentinel).join(item.content) ← 字面量注入,不二次解析
153
+ 收集 item.images
154
+
155
+ join('\n\n') → { body: string, images: ImageData[] }
156
+ ```
157
+
158
+ ### 初始 manifest(一个段)
159
+
160
+ ```json
161
+ {
162
+ "id": "msg-item",
163
+ "file": "$KITS_MESSAGE_FRAGMENTS/item.md",
164
+ "when": "always",
165
+ "needsInjection": true
166
+ }
167
+ ```
168
+
169
+ ### 紧凑模板(item.md)
170
+
171
+ ```
172
+ ‹{{now}}{{?chatType=group}} · {{peerName}}{{/}}›
173
+ {{content}}
174
+ ```
175
+
176
+ - 私聊:`{{?chatType=group}}` 块收敛,只剩时间
177
+ - 群聊:时间 + 发送者名
178
+ - 改格式只动这一个文件,不动代码
179
+
180
+ ---
181
+
182
+ ## vars 时变性归属
183
+
184
+ ### 进系统提示词(每轮覆盖,缓存友好)
185
+
186
+ | 类别 | 变量 |
187
+ |------|------|
188
+ | 静态(进程级) | `PACKAGE_ROOT` `KITS*` `evolclawMode` `osInfo` `baseAgent` |
189
+ | 会话稳定 | `selfAid` `sessionId` `sessionKey` `chatType` `channel` `timezone` `tzOffset` `capabilities` `CURRENT_PROJECT` |
190
+ | 慢变(配置驱动) | `effectiveModel` `permissionMode` `peerRole` `readonly` `chatMode` `modelFallback*` |
191
+ | 新增(日期级) | `localDate`(YYYY-MM-DD)`weekday`(星期四),一天才变一次,缓存暖 ~24h |
192
+
193
+ ### 进消息渲染层(每条独立,进 transcript 永久保真)
194
+
195
+ | 变量 | 说明 |
196
+ |------|------|
197
+ | `now` | 精确到秒的本地时间(含时区偏移),每条自己的发生时刻 |
198
+ | `peerName` / `peerId` / `peerType` | 群聊每条消息的发送者,单聊复用会话值 |
199
+ | `content` | 原始消息文本(字面量注入,不参与模板解析) |
200
+ | `images` | 按条归属的图片(SubMessage.images) |
201
+
202
+ ---
203
+
204
+ ## 关键保证
205
+
206
+ | 保证 | 实现方式 |
207
+ |------|---------|
208
+ | **模板注入防护** | 哨兵 `\x00ECMSG-<UUID>\x00`,per-call 独立,null 字节在任何渠道消息中不可能出现 |
209
+ | **消息不丢失** | manifest 无产出或渲染抛异常均显式 fallback 到 `wrapPrompt(message.content)` |
210
+ | **系统提示词不累积** | `systemPrompt.append` 是 SDK 每次 `query()` 的独立参数,随调用重传覆盖,不进 transcript |
211
+ | **图片归属** | `SubMessage.images` 按条保留,`renderMessageBody` 返回按顺序收集的 images;`runQuery` 用此数组而非 flat-merge 的 `message.images` |
212
+ | **群聊发送者保真** | 每条 SubMessage 携带自己的 peerName/peerId,进 transcript 成永久历史,模型可推理"谁说的、隔了多久" |
213
+ | **空消息安全** | `hasContent` guard,空消息不进渲染层,不传 `runQuery` |
214
+
215
+ ---
216
+
217
+ ## 文件索引
218
+
219
+ | 文件 | 角色 |
220
+ |------|------|
221
+ | `src/agents/manifest-engine.ts` | 共享渲染引擎原语 |
222
+ | `src/agents/kit-renderer.ts` | 系统提示词渲染,输出 `<system-reminder>` |
223
+ | `src/agents/message-renderer.ts` | 消息渲染层,输出 `{ body, images }` |
224
+ | `src/core/message/message-processor.ts` | 装配点:构造 vars,调两层渲染,调 runQuery |
225
+ | `src/core/message/message-queue.ts` | 队列合并:mergeItems 保留 items[] 和 per-item images |
226
+ | `src/core/message/message-bridge.ts` | 消息入口:已移除 messagePrefix 硬编码 |
227
+ | `src/types.ts` | SubMessage / Message.items 类型定义 |
228
+ | `kits/eck_manifest.json` | 系统提示词 manifest |
229
+ | `kits/eck_message_manifest.json` | 消息渲染 manifest |
230
+ | `kits/templates/system-fragments/session.md` | 含 localDate/weekday |
231
+ | `kits/templates/message-fragments/item.md` | 消息渲染紧凑模板 |
232
+ | `$EVOLCLAW_HOME/eck/eck_manifest.json` | 系统提示词 manifest 用户覆盖(可选) |
233
+ | `$EVOLCLAW_HOME/eck/eck_message_manifest.json` | 消息渲染 manifest 用户覆盖(可选) |
234
+ | `$EVOLCLAW_HOME/data/eck-debug/` | 调试输出目录(保留 24h) |
@@ -1,2 +1,2 @@
1
- ‹{{now}}{{?chatType=group}} · {{peerName}}{{/}}›
1
+ ‹{{now}}{{?chatType=group}} · {{peerName}}{{/}}{{?sameDevice}} · 📍同设备{{/}}{{?sameNetwork}} · 🌐同网络{{/}}{{?sameEgressIp}} · 🔀同出口IP{{/}}
2
2
  {{content}}
@@ -21,10 +21,10 @@ clientType: {{clientType}} # 客户端类型:desktop / web / mobile
21
21
  groupId: {{groupId}}
22
22
  {{/}}
23
23
  {{?sameDevice}}
24
- sameDevice: true # 对端与你运行在同一台设备上(E2EE 消息 proximity)
24
+ sameDevice: true # 对端与你运行在同一台设备上
25
25
  {{/}}
26
26
  {{?sameNetwork}}
27
- sameNetwork: true # 对端与你在同一网络内
27
+ sameNetwork: true # 对端与你在同一网络内(同域)
28
28
  {{/}}
29
29
  {{?sameEgressIp}}
30
30
  sameEgressIp: true # 对端与你共享同一出口 IP
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "evolclaw",
3
- "version": "3.1.7",
3
+ "version": "3.1.8",
4
4
  "description": "Lightweight AI Agent gateway connecting Claude Agent SDK to messaging channels (Feishu, ACP) with multi-project session management",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -12,6 +12,7 @@
12
12
  "dist/",
13
13
  "bin/",
14
14
  "assets/",
15
+ "!assets/wechat-group-qr.jpeg",
15
16
  "!dist/experimental/",
16
17
  "kits/",
17
18
  "!kits/.kits-version",
@@ -22,7 +23,7 @@
22
23
  ],
23
24
  "scripts": {
24
25
  "dev": "tsx watch src/index.ts",
25
- "build": "tsc && node -e \"const f='dist/cli/index.js',c=require('fs').readFileSync(f,'utf8');if(!c.startsWith('#!'))require('fs').writeFileSync(f,'#!/usr/bin/env node\\n'+c)\" && node -e \"try{require('child_process').execFileSync('chmod',['+x','dist/cli/index.js'])}catch{}\" && node -e \"require('fs').mkdirSync('dist/data',{recursive:true});require('fs').copyFileSync('src/data/error-dict.json','dist/data/error-dict.json')\"",
26
+ "build": "node -e \"require('fs').rmSync('dist',{recursive:true,force:true})\" && tsc && node -e \"const f='dist/cli/index.js',c=require('fs').readFileSync(f,'utf8');if(!c.startsWith('#!'))require('fs').writeFileSync(f,'#!/usr/bin/env node\\n'+c)\" && node -e \"try{require('child_process').execFileSync('chmod',['+x','dist/cli/index.js'])}catch{}\" && node -e \"require('fs').mkdirSync('dist/data',{recursive:true});require('fs').copyFileSync('src/data/error-dict.json','dist/data/error-dict.json')\"",
26
27
  "start": "node dist/index.js",
27
28
  "test": "vitest run",
28
29
  "test:watch": "vitest",
Binary file
@@ -1,18 +0,0 @@
1
- /**
2
- * watch-web 调试日志 — 写入 $EVOLCLAW_HOME/logs/watch-web.log。
3
- *
4
- * cmdWatchWeb 启动时清空该文件并调用 setDebugLog 注入 writer,
5
- * 各 source / server 通过 dlog() 写调试信息,建立「运行→看日志→定位」的闭环。
6
- */
7
- let _writer = null;
8
- export function setDebugLog(writer) {
9
- _writer = writer;
10
- }
11
- export function dlog(line) {
12
- if (_writer) {
13
- try {
14
- _writer(line);
15
- }
16
- catch { /* ignore */ }
17
- }
18
- }