evolclaw 3.1.3 → 3.1.5

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 (100) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/assets/.env.template +4 -0
  3. package/assets/config.json.template +6 -0
  4. package/assets/wechat-group-qr.jpeg +0 -0
  5. package/dist/agents/claude-runner.js +348 -156
  6. package/dist/agents/kit-renderer.js +211 -42
  7. package/dist/aun/aid/agentmd.js +75 -139
  8. package/dist/aun/aid/client.js +1 -14
  9. package/dist/aun/aid/identity.js +381 -54
  10. package/dist/aun/aid/index.js +3 -2
  11. package/dist/aun/aid/store.js +74 -0
  12. package/dist/aun/msg/p2p.js +26 -2
  13. package/dist/aun/rpc/connection.js +23 -35
  14. package/dist/channels/aun.js +92 -144
  15. package/dist/channels/dingtalk.js +1 -0
  16. package/dist/channels/feishu.js +270 -190
  17. package/dist/channels/qqbot.js +1 -0
  18. package/dist/channels/wechat.js +1 -0
  19. package/dist/channels/wecom.js +1 -0
  20. package/dist/cli/agent.js +26 -27
  21. package/dist/cli/bench.js +45 -34
  22. package/dist/cli/help.js +23 -0
  23. package/dist/cli/index.js +538 -77
  24. package/dist/cli/init-channel.js +7 -4
  25. package/dist/cli/link-rules.js +2 -1
  26. package/dist/cli/model.js +324 -0
  27. package/dist/cli/net-check.js +138 -56
  28. package/dist/cli/watch-msg.js +7 -7
  29. package/dist/cli/watch-web/debug-log.js +18 -0
  30. package/dist/cli/watch-web/server.js +306 -0
  31. package/dist/cli/watch-web/sources/aid.js +63 -0
  32. package/dist/cli/watch-web/sources/msg.js +70 -0
  33. package/dist/cli/watch-web/sources/session.js +638 -0
  34. package/dist/cli/watch-web/sources/types.js +10 -0
  35. package/dist/cli/watch-web/static/app.js +546 -0
  36. package/dist/cli/watch-web/static/index.html +54 -0
  37. package/dist/cli/watch-web/static/style.css +247 -0
  38. package/dist/core/channel-loader.js +7 -4
  39. package/dist/core/command-handler.js +87 -93
  40. package/dist/core/evolagent-registry.js +1 -1
  41. package/dist/core/evolagent.js +4 -4
  42. package/dist/core/interaction-router.js +59 -0
  43. package/dist/core/message/message-bridge.js +6 -6
  44. package/dist/core/message/message-log.js +2 -2
  45. package/dist/core/message/message-processor.js +104 -118
  46. package/dist/core/message/stream-idle-monitor.js +21 -0
  47. package/dist/core/model/model-catalog.js +215 -0
  48. package/dist/core/model/model-scope.js +250 -0
  49. package/dist/core/relation/peer-identity.js +78 -44
  50. package/dist/core/relation/peer-key.js +16 -0
  51. package/dist/core/session/session-fs-store.js +34 -55
  52. package/dist/core/session/session-key.js +24 -0
  53. package/dist/core/session/session-manager.js +312 -251
  54. package/dist/core/session/session-mapper.js +9 -4
  55. package/dist/core/trigger/manager.js +37 -0
  56. package/dist/core/trigger/scheduler.js +2 -1
  57. package/dist/index.js +10 -3
  58. package/dist/ipc.js +22 -0
  59. package/dist/paths.js +87 -16
  60. package/dist/utils/npm-ops.js +18 -11
  61. package/kits/docs/GUIDE.md +2 -2
  62. package/kits/docs/INDEX.md +11 -7
  63. package/kits/docs/channels/aun.md +56 -17
  64. package/kits/docs/channels/feishu.md +41 -12
  65. package/kits/docs/context-assembly.md +181 -0
  66. package/kits/docs/evolclaw/agent.md +49 -0
  67. package/kits/docs/evolclaw/aid.md +49 -0
  68. package/kits/docs/evolclaw/ctl.md +46 -0
  69. package/kits/docs/evolclaw/group.md +82 -0
  70. package/kits/docs/evolclaw/msg.md +86 -0
  71. package/kits/docs/evolclaw/rpc.md +35 -0
  72. package/kits/docs/evolclaw/storage.md +49 -0
  73. package/kits/docs/venues/aun-group.md +10 -0
  74. package/kits/docs/venues/aun-private.md +10 -0
  75. package/kits/docs/venues/client-desktop.md +10 -0
  76. package/kits/docs/venues/client-mobile.md +10 -0
  77. package/kits/docs/venues/feishu-group.md +13 -0
  78. package/kits/docs/venues/feishu-private.md +9 -0
  79. package/kits/docs/venues/group.md +11 -0
  80. package/kits/docs/venues/private.md +10 -0
  81. package/kits/eck_manifest.json +75 -39
  82. package/kits/rules/01-overview.md +20 -10
  83. package/kits/rules/05-venue.md +2 -2
  84. package/kits/rules/06-channel.md +30 -27
  85. package/kits/templates/system-fragments/baseagent.md +7 -1
  86. package/kits/templates/system-fragments/channel.md +4 -1
  87. package/kits/templates/system-fragments/identity.md +4 -4
  88. package/kits/templates/system-fragments/relation.md +8 -5
  89. package/kits/templates/system-fragments/session.md +27 -0
  90. package/kits/templates/system-fragments/venue.md +13 -1
  91. package/package.json +13 -6
  92. package/dist/aun/aid/lifecycle-log.js +0 -33
  93. package/dist/net-check.js +0 -640
  94. package/dist/utils/aid-lifecycle-log.js +0 -33
  95. package/dist/watch-msg.js +0 -544
  96. package/kits/docs/evolclaw/AGENT_CMD.md +0 -31
  97. package/kits/docs/evolclaw/MSG_GROUP.md +0 -30
  98. package/kits/docs/evolclaw/MSG_PRIVATE.md +0 -72
  99. package/kits/docs/evolclaw/tools.md +0 -25
  100. package/kits/templates/system-fragments/eckruntime.md +0 -14
@@ -0,0 +1,247 @@
1
+ :root {
2
+ --bg: #0d1117;
3
+ --bg2: #161b22;
4
+ --bg3: #21262d;
5
+ --border: #30363d;
6
+ --fg: #e6edf3;
7
+ --dim: #8b949e;
8
+ --accent: #58a6ff;
9
+ --green: #3fb950;
10
+ --red: #f85149;
11
+ --orange: #db8e3c;
12
+ --blue: #58a6ff;
13
+ --magenta: #bc8cff;
14
+ }
15
+
16
+ * { box-sizing: border-box; margin: 0; padding: 0; }
17
+
18
+ body {
19
+ font-family: -apple-system, "Segoe UI", "PingFang SC", "Microsoft YaHei", monospace;
20
+ background: var(--bg);
21
+ color: var(--fg);
22
+ font-size: 13px;
23
+ height: 100vh;
24
+ overflow: hidden;
25
+ }
26
+
27
+ /* ── 配对页 ── */
28
+ .pair-page {
29
+ display: flex; align-items: center; justify-content: center;
30
+ height: 100vh;
31
+ }
32
+ .pair-box {
33
+ background: var(--bg2);
34
+ border: 1px solid var(--border);
35
+ border-radius: 12px;
36
+ padding: 40px 48px;
37
+ text-align: center;
38
+ min-width: 320px;
39
+ }
40
+ .pair-box h1 { font-size: 22px; margin-bottom: 8px; }
41
+ .pair-hint { color: var(--dim); margin-bottom: 24px; }
42
+ .pair-box input {
43
+ width: 100%;
44
+ font-size: 28px;
45
+ letter-spacing: 12px;
46
+ text-align: center;
47
+ padding: 12px;
48
+ background: var(--bg);
49
+ border: 1px solid var(--border);
50
+ border-radius: 8px;
51
+ color: var(--fg);
52
+ margin-bottom: 16px;
53
+ }
54
+ .pair-box input:focus { outline: none; border-color: var(--accent); }
55
+ .pair-box button {
56
+ width: 100%;
57
+ padding: 12px;
58
+ font-size: 15px;
59
+ background: var(--accent);
60
+ color: #fff;
61
+ border: none;
62
+ border-radius: 8px;
63
+ cursor: pointer;
64
+ }
65
+ .pair-box button:hover { opacity: 0.9; }
66
+ .pair-error { color: var(--red); margin-top: 12px; min-height: 18px; }
67
+
68
+ /* ── 主面板布局 ── */
69
+ .app { display: flex; flex-direction: column; height: 100vh; }
70
+ .topbar {
71
+ display: flex; align-items: center; gap: 24px;
72
+ padding: 0 16px; height: 44px;
73
+ background: var(--bg2);
74
+ border-bottom: 1px solid var(--border);
75
+ flex-shrink: 0;
76
+ }
77
+ .brand { font-weight: 600; }
78
+ .tabs { display: flex; gap: 4px; }
79
+ .tab {
80
+ background: none; border: none; color: var(--dim);
81
+ padding: 6px 14px; cursor: pointer; border-radius: 6px;
82
+ font-size: 13px; font-family: inherit;
83
+ }
84
+ .tab:hover { color: var(--fg); background: var(--bg3); }
85
+ .tab.active { color: var(--accent); background: var(--bg3); }
86
+ .conn-status { margin-left: auto; color: var(--dim); font-size: 12px; }
87
+ .conn-status.ok { color: var(--green); }
88
+ .conn-status.err { color: var(--red); }
89
+
90
+ .content { flex: 1; overflow: hidden; position: relative; }
91
+ .view { display: none; height: 100%; overflow: auto; }
92
+ .view.active { display: block; }
93
+
94
+ /* ── AID 表格 ── */
95
+ table { width: 100%; border-collapse: collapse; font-size: 12px; }
96
+ th, td { text-align: left; padding: 7px 12px; border-bottom: 1px solid var(--border); white-space: nowrap; }
97
+ th { color: var(--dim); font-weight: 500; position: sticky; top: 0; background: var(--bg2); }
98
+ tr:hover td { background: var(--bg2); }
99
+ .dot { display: inline-block; width: 8px; height: 8px; border-radius: 50%; margin-right: 6px; }
100
+ .dot.on { background: var(--green); }
101
+ .dot.off { background: var(--red); }
102
+ .dot.idle { background: var(--orange); }
103
+ .preview { color: var(--dim); max-width: 360px; overflow: hidden; text-overflow: ellipsis; }
104
+ .banner { padding: 10px 16px; background: #3d2a1a; color: var(--orange); border-bottom: 1px solid var(--border); }
105
+ .empty { padding: 40px; text-align: center; color: var(--dim); }
106
+
107
+ /* ── Messages 三栏 ── */
108
+ .msg-layout, .sess-layout { display: flex; height: 100%; }
109
+ .msg-col { overflow-y: auto; height: 100%; }
110
+ .msg-aids { width: 220px; border-right: 1px solid var(--border); flex-shrink: 0; }
111
+ .msg-peers { width: 240px; border-right: 1px solid var(--border); flex-shrink: 0; }
112
+ .msg-stream { flex: 1; padding: 12px; }
113
+ .list-item {
114
+ padding: 8px 12px; cursor: pointer; border-bottom: 1px solid var(--border);
115
+ }
116
+ .list-item:hover { background: var(--bg2); }
117
+ .list-item.sel { background: var(--bg3); border-left: 2px solid var(--accent); }
118
+ .list-item .name { font-weight: 500; }
119
+ .list-item .sub { color: var(--dim); font-size: 11px; margin-top: 2px; }
120
+ .col-title { padding: 8px 12px; color: var(--dim); font-size: 11px; text-transform: uppercase; position: sticky; top: 0; background: var(--bg); border-bottom: 1px solid var(--border); }
121
+
122
+ .bubble { margin-bottom: 12px; max-width: 80%; }
123
+ .bubble.in { margin-right: auto; }
124
+ .bubble.out { margin-left: auto; }
125
+ .bubble .meta { font-size: 11px; color: var(--dim); margin-bottom: 3px; }
126
+ .bubble .body {
127
+ padding: 8px 10px; border-radius: 8px; white-space: pre-wrap; word-break: break-word;
128
+ background: var(--bg2); border: 1px solid var(--border);
129
+ }
130
+ .bubble.in .body { border-left: 2px solid var(--green); }
131
+ .bubble.out .body { border-left: 2px solid var(--blue); }
132
+ .tag { display: inline-block; padding: 0 5px; border-radius: 3px; font-size: 10px; margin-left: 4px; background: var(--bg3); color: var(--magenta); }
133
+
134
+ /* ── Sessions ── */
135
+ .sess-list { width: 360px; border-right: 1px solid var(--border); flex-shrink: 0; display: flex; flex-direction: column; overflow: hidden; }
136
+ .sess-filter { padding: 10px; border-bottom: 1px solid var(--border); display: flex; flex-direction: column; gap: 8px; flex-shrink: 0; }
137
+ .sess-filter select, .sess-filter input {
138
+ width: 100%; padding: 6px 8px; background: var(--bg); color: var(--fg);
139
+ border: 1px solid var(--border); border-radius: 6px; font-size: 12px; font-family: inherit;
140
+ }
141
+ .sess-filter select:focus, .sess-filter input:focus { outline: none; border-color: var(--accent); }
142
+ .sess-count { font-size: 11px; color: var(--dim); }
143
+ .sess-items { flex: 1; overflow-y: auto; }
144
+
145
+ .sess-items .list-item .name { font-weight: 500; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
146
+ .sess-items .list-item .sub { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
147
+ .bind-badge { display: inline-flex; align-items: center; gap: 3px; padding: 0 5px; border-radius: 3px; background: var(--bg3); color: var(--accent); font-size: 10px; }
148
+ .bind-badge .dot { width: 6px; height: 6px; margin: 0; }
149
+ .msg-count { color: var(--dim); }
150
+
151
+ .sess-detail { flex: 1; padding: 0; overflow-y: auto; }
152
+
153
+ /* 会话头 */
154
+ .sess-header { position: sticky; top: 0; background: var(--bg2); border-bottom: 1px solid var(--border); padding: 12px 20px; z-index: 1; }
155
+ .sh-title { font-size: 15px; font-weight: 600; margin-bottom: 8px; }
156
+ .sh-stats { display: flex; flex-wrap: wrap; gap: 12px; font-size: 12px; color: var(--dim); }
157
+ .sh-stat { display: inline-flex; align-items: center; gap: 4px; }
158
+ .sh-stat .dot { width: 7px; height: 7px; margin: 0 2px; }
159
+ .sh-path { font-size: 11px; color: var(--dim); margin-top: 6px; font-family: ui-monospace, Consolas, monospace; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
160
+
161
+ /* 顶部 4 类分类条 */
162
+ .sh-cats { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 8px; }
163
+ .cat-chip { display: inline-flex; align-items: center; gap: 5px; padding: 2px 8px; border-radius: 10px; font-size: 11px; background: var(--bg3); }
164
+ .cat-swatch { width: 8px; height: 8px; border-radius: 50%; display: inline-block; }
165
+ .cat-chip.cat-user_input .cat-swatch { background: var(--green); }
166
+ .cat-chip.cat-model_output .cat-swatch { background: var(--blue); }
167
+ .cat-chip.cat-tool_call .cat-swatch { background: var(--magenta); }
168
+ .cat-chip.cat-tool_result .cat-swatch { background: var(--orange); }
169
+ .cat-chip.cat-msg_send .cat-swatch { background: var(--cyan, #22d3a8); }
170
+
171
+ /* 视图切换工具条 */
172
+ .sess-toolbar { display: flex; align-items: center; gap: 12px; padding: 8px 20px; border-bottom: 1px solid var(--border); position: sticky; top: 0; background: var(--bg); z-index: 1; }
173
+ .view-toggle { padding: 4px 12px; border-radius: 14px; border: 1px solid var(--border); background: var(--bg3); color: var(--fg); cursor: pointer; font-size: 12px; font-family: inherit; }
174
+ .view-toggle.active { background: var(--accent); border-color: var(--accent); color: #fff; }
175
+ .toolbar-hint { font-size: 11px; color: var(--dim); }
176
+ .turn-list { padding-top: 8px; }
177
+
178
+ /* 对话视图:仿微信气泡 */
179
+ .chat-row { display: flex; flex-direction: column; margin: 10px 20px; max-width: 72%; }
180
+ .chat-row.in { align-items: flex-start; margin-right: auto; }
181
+ .chat-row.out { align-items: flex-end; margin-left: auto; }
182
+ .chat-bubble { padding: 9px 13px; border-radius: 12px; white-space: pre-wrap; word-break: break-word; line-height: 1.5; font-size: 13px; }
183
+ .chat-row.in .chat-bubble { background: var(--bg3); border-bottom-left-radius: 3px; }
184
+ .chat-row.out .chat-bubble { background: #2a6b3f; color: #eafff0; border-bottom-right-radius: 3px; }
185
+ .chat-time { font-size: 10px; color: var(--dim); margin-top: 3px; }
186
+
187
+ /* 处理过程折叠组 */
188
+ .proc-group { margin: 8px 20px; }
189
+ .proc-group > summary { cursor: pointer; color: var(--dim); font-size: 11px; padding: 4px 0; text-align: center; list-style: none; }
190
+ .proc-group > summary::-webkit-details-marker { display: none; }
191
+ .proc-group > summary:hover { color: var(--fg); }
192
+ .proc-body { border-left: 2px dashed var(--border); margin-left: 8px; padding-left: 4px; }
193
+ .proc-body .turn { margin: 8px 12px; }
194
+
195
+ /* 轮次:按 4 类着色(左色条 + 角色标签色)*/
196
+ .turn { margin: 0 20px 18px; border-left: 3px solid var(--border); padding-left: 12px; }
197
+ .turn:first-of-type { margin-top: 16px; }
198
+ .turn.cat-user_input { border-left-color: var(--green); }
199
+ .turn.cat-model_output { border-left-color: var(--blue); }
200
+ .turn.cat-tool_call { border-left-color: var(--magenta); }
201
+ .turn.cat-tool_result { border-left-color: var(--orange); }
202
+ .turn.cat-system { border-left-color: var(--dim); }
203
+
204
+ .turn-head { display: flex; align-items: baseline; gap: 10px; margin-bottom: 6px; }
205
+ .turn-role { font-weight: 600; font-size: 12px; }
206
+ .turn.cat-user_input .turn-role { color: var(--green); }
207
+ .turn.cat-model_output .turn-role { color: var(--blue); }
208
+ .turn.cat-tool_call .turn-role { color: var(--magenta); }
209
+ .turn.cat-tool_result .turn-role { color: var(--orange); }
210
+ .turn.cat-system .turn-role { color: var(--dim); }
211
+ .turn-time { font-size: 11px; color: var(--dim); }
212
+ .turn-usage { font-size: 11px; color: var(--dim); margin-left: auto; }
213
+ .turn-blocks { display: flex; flex-direction: column; gap: 8px; }
214
+
215
+ /* 普通文本块 */
216
+ .blk-text { white-space: pre-wrap; word-break: break-word; line-height: 1.55; }
217
+
218
+ /* 思考块(折叠) */
219
+ .blk-thinking { font-size: 12px; }
220
+ .blk-thinking summary { cursor: pointer; color: var(--magenta); opacity: 0.8; }
221
+ .blk-thinking-body { white-space: pre-wrap; word-break: break-word; color: var(--dim); margin-top: 6px; padding-left: 12px; border-left: 2px solid var(--border); font-style: italic; }
222
+
223
+ /* 工具调用块 */
224
+ .blk-tool { background: var(--bg2); border: 1px solid var(--border); border-radius: 8px; padding: 8px 10px; }
225
+ .tool-head { font-size: 12px; margin-bottom: 4px; }
226
+ .tool-name { font-weight: 600; color: var(--accent); font-family: ui-monospace, "SF Mono", Consolas, monospace; }
227
+ .tool-param { display: flex; gap: 8px; margin-top: 3px; font-size: 12px; align-items: baseline; }
228
+ .tool-param .pk { color: var(--dim); flex-shrink: 0; min-width: 64px; }
229
+ .tool-param .pv {
230
+ font-family: ui-monospace, "SF Mono", Consolas, monospace; color: var(--fg);
231
+ background: var(--bg); padding: 1px 6px; border-radius: 4px; word-break: break-all;
232
+ white-space: pre-wrap; display: block; flex: 1;
233
+ }
234
+
235
+ /* 工具结果块(折叠,默认收起) */
236
+ .blk-result { font-size: 12px; }
237
+ .blk-result summary { cursor: pointer; color: var(--dim); }
238
+ .blk-result.err summary { color: var(--red); }
239
+ .result-body {
240
+ margin-top: 6px; padding: 8px 10px; background: var(--bg); border: 1px solid var(--border);
241
+ border-radius: 6px; white-space: pre-wrap; word-break: break-word; max-height: 280px; overflow-y: auto;
242
+ font-family: ui-monospace, "SF Mono", Consolas, monospace; font-size: 11.5px; line-height: 1.5;
243
+ }
244
+ .blk-result.err .result-body { border-color: var(--red); }
245
+
246
+
247
+
@@ -128,18 +128,21 @@ export class ChannelLoader {
128
128
  }
129
129
  const SEP = '#';
130
130
  export function formatChannelKey(k) {
131
- return `${k.type}${SEP}${encodeURIComponent(k.selfPeerId)}${SEP}${k.name}`;
131
+ if (k.selfAID.includes(SEP)) {
132
+ throw new Error(`Invalid selfAID (contains '#'): ${k.selfAID}`);
133
+ }
134
+ return `${k.type}${SEP}${k.selfAID}${SEP}${k.name}`;
132
135
  }
133
136
  export function parseChannelKey(key) {
134
137
  const parts = key.split(SEP);
135
138
  if (parts.length !== 3) {
136
139
  throw new Error(`Invalid channel key (expected 3 segments separated by '#'): ${key}`);
137
140
  }
138
- const [type, encodedSelfPeerId, name] = parts;
139
- if (!type || !encodedSelfPeerId || !name) {
141
+ const [type, selfAID, name] = parts;
142
+ if (!type || !selfAID || !name) {
140
143
  throw new Error(`Invalid channel key (empty segment): ${key}`);
141
144
  }
142
- return { type, selfPeerId: decodeURIComponent(encodedSelfPeerId), name };
145
+ return { type, selfAID, name };
143
146
  }
144
147
  export function tryParseChannelKey(key) {
145
148
  try {
@@ -1,7 +1,6 @@
1
1
  import { DEFAULT_PERMISSION_MODE } from '../types.js';
2
2
  import { hasModelSwitcher, hasPermissionController } from '../agents/claude-runner.js';
3
3
  import { getCodexEfforts } from '../agents/codex-runner.js';
4
- import { resolveAnthropicConfig, resolveOpenaiConfig } from '../agents/resolve.js';
5
4
  import { renderCommandCardAsText } from './interaction-router.js';
6
5
  import { buildEnvelope, sendInteractionPayload } from './message/message-processor.js';
7
6
  import { resolvePaths, getPackageRoot } from '../paths.js';
@@ -27,33 +26,13 @@ function getAvailableEfforts(agent, model) {
27
26
  function formatModelUsage(_agent, _model) {
28
27
  return '用法: /model <模型>';
29
28
  }
30
- function getModelListSource(owning, agent) {
31
- const codexConfig = owning?.config?.baseagents?.codex;
32
- const claudeConfig = owning?.config?.baseagents?.claude;
33
- if (agent.name === 'codex') {
34
- let resolved = {};
35
- try {
36
- resolved = resolveOpenaiConfig({ agents: { codex: codexConfig } }, codexConfig);
37
- }
38
- catch { }
39
- return {
40
- apiBaseUrl: resolved.baseUrl,
41
- apiKey: resolved.apiKey,
42
- fallbackModels: agent.listModels?.() || [],
43
- owner: 'openai',
44
- };
45
- }
46
- let resolved = {};
47
- try {
48
- resolved = resolveAnthropicConfig({ agents: { claude: claudeConfig } }, claudeConfig);
49
- }
50
- catch { }
51
- return {
52
- apiBaseUrl: resolved.baseUrl,
53
- apiKey: resolved.apiKey,
54
- fallbackModels: ['claude-opus-4-7', 'claude-opus-4-6', 'claude-sonnet-4-6'],
55
- owner: 'anthropic',
56
- };
29
+ /**
30
+ * 模型展示标签:短别名 + 实际完整 ID(如 "opus (claude-opus-4-8)")。
31
+ * 仅用于展示;命令值/持久化仍使用短别名。完整 ID 不可用或与短名相同时只显示短名。
32
+ */
33
+ function modelDisplayLabel(agent, model) {
34
+ const full = agent.resolveModelId?.(model);
35
+ return full && full !== model ? `${model} (${full})` : model;
57
36
  }
58
37
  /**
59
38
  * 写入用户级 ~/.claude/settings.json(与 Claude CLI 行为一致)
@@ -410,8 +389,9 @@ export class CommandHandler {
410
389
  return { session };
411
390
  }
412
391
  const ct = chatType === 'group' ? 'group' : chatType === 'private' ? 'private' : undefined;
392
+ const channelType = this.resolveChannelType(channel);
413
393
  const session = await this.sessionManager.getActiveSession(channel, channelId)
414
- ?? await this.sessionManager.getOrCreateSession(channel, channelId, this.getEffectiveDefaultPath(channel), undefined, undefined, undefined, undefined, ct);
394
+ ?? await this.sessionManager.getOrCreateSession(channel, channelId, this.getEffectiveDefaultPath(channel), undefined, undefined, undefined, undefined, ct, undefined, undefined, channelType);
415
395
  // 如果 session 已存在但 chatType 跟传入的不一致,更新
416
396
  if (ct && session.chatType !== ct) {
417
397
  await this.sessionManager.updateSession(session.id, { chatType: ct });
@@ -635,7 +615,7 @@ export class CommandHandler {
635
615
  const models = await agent.listModels() ?? [];
636
616
  const currentModel = agent.getModel();
637
617
  if (models.length > 0)
638
- return models.map((m) => ({ value: m, label: m, selected: m === currentModel }));
618
+ return models.map((m) => ({ value: m, label: modelDisplayLabel(agent, m), selected: m === currentModel }));
639
619
  }
640
620
  return null;
641
621
  }
@@ -1043,7 +1023,7 @@ export class CommandHandler {
1043
1023
  env: { ...process.env, EVOLCLAW_HOME: resolvePaths().root }
1044
1024
  }).unref();
1045
1025
  this.eventBus.publish({ type: 'system:restart', channel, channelId });
1046
- setTimeout(() => { process.kill(process.pid, 'SIGTERM'); }, 500);
1026
+ setTimeout(() => { process.kill(process.pid, 'SIGTERM'); }, 1000);
1047
1027
  return { data: { action: 'restart', success: true } };
1048
1028
  }
1049
1029
  if (action === 'check') {
@@ -1160,12 +1140,12 @@ export class CommandHandler {
1160
1140
  }
1161
1141
  // 空闲检查:某些命令需要等待当前会话空闲
1162
1142
  // 原则:仅对"写/破坏性"形态拦截,纯读/用法提示的无参形态始终放行
1163
- // - 始终需要 idle(无参即写):/new /clear /compact /repair /fork
1143
+ // - 始终需要 idle(无参即写):/clear /compact /repair /fork /new
1164
1144
  // - 仅带参时需要 idle(无参是列表/用法):/session /baseagent /rewind
1165
1145
  // - /chatmode:在 handler 内部自行做写操作的 idle 检查
1166
1146
  // - /dispatch:在 handler 内部自行做写操作的 idle 检查
1167
1147
  // - /safe:已禁用 no-op,不再要求 idle
1168
- const idleAlways = ['/new', '/clear', '/compact', '/repair', '/fork'];
1148
+ const idleAlways = ['/clear', '/compact', '/repair', '/fork', '/new'];
1169
1149
  const idleWhenArg = ['/session', '/baseagent', '/rewind'];
1170
1150
  const needsIdle = idleAlways.some(cmd => normalizedContent === cmd || normalizedContent.startsWith(cmd + ' ')) ||
1171
1151
  idleWhenArg.some(cmd => normalizedContent.startsWith(cmd + ' '));
@@ -1175,13 +1155,19 @@ export class CommandHandler {
1175
1155
  const threadSession = await this.sessionManager.getThreadSession(channel, channelId, threadId);
1176
1156
  if (threadSession) {
1177
1157
  const threadAgent = this.getAgent(channel, threadSession.agentId);
1178
- if (threadAgent.hasActiveStream(threadSession.id)) {
1158
+ const isBusy = threadAgent.hasActiveStream(threadSession.id) ||
1159
+ this.messageQueue?.isProcessing(threadSession.id);
1160
+ if (isBusy) {
1179
1161
  return { kind: 'command.error', text: '⚠️ 当前正在处理消息,请稍后再试\n使用 /stop 中断当前任务后重试' };
1180
1162
  }
1181
1163
  }
1182
1164
  }
1183
- else if (activeSession && agent.hasActiveStream(activeSession.id)) {
1184
- return { kind: 'command.error', text: '⚠️ 当前正在处理消息,请稍后再试\n使用 /stop 中断当前任务后重试' };
1165
+ else if (activeSession) {
1166
+ const isBusy = agent.hasActiveStream(activeSession.id) ||
1167
+ this.messageQueue?.isProcessing(activeSession.id);
1168
+ if (isBusy) {
1169
+ return { kind: 'command.error', text: '⚠️ 当前正在处理消息,请稍后再试\n使用 /stop 中断当前任务后重试' };
1170
+ }
1185
1171
  }
1186
1172
  }
1187
1173
  // 检查是否以 / 开头(可能是命令)
@@ -1626,37 +1612,12 @@ export class CommandHandler {
1626
1612
  const currentModel = hasModelSwitcher(setmodelAgent) ? setmodelAgent.getModel() : setmodelAgent.name;
1627
1613
  const efforts = getAvailableEfforts(setmodelAgent, currentModel);
1628
1614
  const currentEffort = setmodelAgent.getEffort?.() || 'auto';
1629
- const modelListSource = getModelListSource(this.getOwningAgent(channel), setmodelAgent);
1630
- let modelListData = null;
1631
- if (modelListSource.apiBaseUrl) {
1632
- try {
1633
- const modelsUrl = modelListSource.apiBaseUrl.replace(/\/+$/, '') + '/v1/models';
1634
- const controller = new AbortController();
1635
- const timeout = setTimeout(() => controller.abort(), 5000);
1636
- const resp = await fetch(modelsUrl, {
1637
- signal: controller.signal,
1638
- headers: { 'Authorization': `Bearer ${modelListSource.apiKey || ''}` },
1639
- });
1640
- clearTimeout(timeout);
1641
- if (resp.ok) {
1642
- modelListData = await resp.json();
1643
- }
1644
- }
1645
- catch { }
1646
- }
1647
- // 兜底模型列表
1648
- if (!modelListData || !modelListData.data || modelListData.data.length === 0) {
1649
- const now = Math.floor(Date.now() / 1000);
1650
- modelListData = {
1651
- object: 'list',
1652
- data: modelListSource.fallbackModels.map(id => ({
1653
- id,
1654
- object: 'model',
1655
- created: now,
1656
- owned_by: modelListSource.owner,
1657
- })),
1658
- };
1659
- }
1615
+ const now = Math.floor(Date.now() / 1000);
1616
+ const modelIds = hasModelSwitcher(setmodelAgent) ? setmodelAgent.listModels() : [];
1617
+ const modelListData = {
1618
+ object: 'list',
1619
+ data: modelIds.map(id => ({ id, object: 'model', created: now, owned_by: setmodelAgent.name === 'codex' ? 'openai' : 'anthropic' })),
1620
+ };
1660
1621
  return { kind: 'command.result', text: JSON.stringify({
1661
1622
  current_model: currentModel,
1662
1623
  current_effort: currentEffort,
@@ -1689,12 +1650,15 @@ export class CommandHandler {
1689
1650
  kind: {
1690
1651
  kind: 'command-card',
1691
1652
  title: '🤖 切换模型',
1692
- buttons: models.map((m) => ({
1693
- label: m === currentModel ? `✓ ${m}` : m,
1694
- command: `/model ${m}`,
1695
- style: (m === currentModel ? 'primary' : 'default'),
1696
- disabled: m === currentModel,
1697
- })),
1653
+ buttons: models.map((m) => {
1654
+ const display = modelDisplayLabel(modelAgent, m);
1655
+ return {
1656
+ label: m === currentModel ? `✓ ${display}` : display,
1657
+ command: `/model ${m}`,
1658
+ style: (m === currentModel ? 'primary' : 'default'),
1659
+ disabled: m === currentModel,
1660
+ };
1661
+ }),
1698
1662
  },
1699
1663
  };
1700
1664
  const replyCtx = this.getReplyContext(modelSession);
@@ -1704,14 +1668,14 @@ export class CommandHandler {
1704
1668
  return { kind: 'command.result', text: cardResult };
1705
1669
  }
1706
1670
  // 降级:文本
1707
- const modelList = models.map((m) => ` ${m === currentModel ? '✓' : ' '} ${m}`).join('\n');
1671
+ const modelList = models.map((m) => ` ${m === currentModel ? '✓' : ' '} ${modelDisplayLabel(modelAgent, m)}`).join('\n');
1708
1672
  const effortHint = efforts.length > 0
1709
1673
  ? `\n推理强度: ${currentEffort === 'auto' ? 'auto (SDK默认)' : currentEffort} (使用 /effort 调整)`
1710
1674
  : '';
1711
1675
  if (isAdmin) {
1712
- return { kind: 'command.result', text: `当前模型: ${currentModel}${effortHint}\n\n可用模型:\n${modelList}\n\n用法: /model <模型>` };
1676
+ return { kind: 'command.result', text: `当前模型: ${modelDisplayLabel(modelAgent, currentModel)}${effortHint}\n\n可用模型:\n${modelList}\n\n用法: /model <模型>` };
1713
1677
  }
1714
- return { kind: 'command.result', text: `当前模型: ${currentModel}${effortHint}` };
1678
+ return { kind: 'command.result', text: `当前模型: ${modelDisplayLabel(modelAgent, currentModel)}${effortHint}` };
1715
1679
  }
1716
1680
  // 带参(切换/调整)需 admin+;无参查询已在上方返回
1717
1681
  if (!isAdmin)
@@ -1731,20 +1695,29 @@ export class CommandHandler {
1731
1695
  else if (allEfforts.includes(arg)) {
1732
1696
  return { kind: 'command.error', text: `⚠️ 请使用 /effort ${arg} 调整推理强度` };
1733
1697
  }
1734
- else if (models.includes(arg)) {
1735
- newModel = arg;
1736
- }
1737
1698
  else {
1738
- const modelList = models.map((m) => ` ${m === currentModel ? '✓' : ' '} ${m}`).join('\n');
1739
- const effortHint = efforts.length > 0 ? `\n\n推理强度请使用 /effort 命令` : '';
1740
- return { kind: 'command.error', text: `❌ 无效参数: ${arg}\n\n可用模型:\n${modelList}${effortHint}` };
1699
+ const resolvedArg = hasModelSwitcher(modelAgent) ? (modelAgent.resolveModelId?.(arg) ?? arg) : arg;
1700
+ if (models.includes(resolvedArg)) {
1701
+ newModel = resolvedArg;
1702
+ }
1703
+ else if (models.includes(arg)) {
1704
+ newModel = arg;
1705
+ }
1706
+ else {
1707
+ const modelList = models.map((m) => ` ${m === currentModel ? '✓' : ' '} ${modelDisplayLabel(modelAgent, m)}`).join('\n');
1708
+ const effortHint = efforts.length > 0 ? `\n\n推理强度请使用 /effort 命令` : '';
1709
+ return { kind: 'command.error', text: `❌ 无效参数: ${arg}\n\n可用模型:\n${modelList}${effortHint}` };
1710
+ }
1741
1711
  }
1742
1712
  }
1743
1713
  else {
1744
1714
  // 双参数:model effort
1745
- const [modelArg, effortArg] = parts;
1715
+ const [modelArgRaw, effortArg] = parts;
1716
+ const modelArg = hasModelSwitcher(modelAgent)
1717
+ ? (models.includes(modelArgRaw) ? modelArgRaw : (modelAgent.resolveModelId?.(modelArgRaw) ?? modelArgRaw))
1718
+ : modelArgRaw;
1746
1719
  if (!models.includes(modelArg)) {
1747
- return { kind: 'command.error', text: `❌ 无效的模型ID: ${modelArg}` };
1720
+ return { kind: 'command.error', text: `❌ 无效的模型ID: ${modelArgRaw}` };
1748
1721
  }
1749
1722
  const targetEfforts = getAvailableEfforts(modelAgent, modelArg);
1750
1723
  if (targetEfforts.length === 0) {
@@ -2015,12 +1988,12 @@ export class CommandHandler {
2015
1988
  const threadSession = await this.sessionManager.getThreadSession(channel, channelId, threadId);
2016
1989
  if (threadSession) {
2017
1990
  const threadAgent = this.getAgent(channel, threadSession.agentId);
2018
- if (threadAgent.hasActiveStream(threadSession.id)) {
1991
+ if (threadAgent.hasActiveStream(threadSession.id) || this.messageQueue?.isProcessing(threadSession.id)) {
2019
1992
  return { kind: 'command.error', text: '⚠️ 当前正在处理消息,请稍后再试\n使用 /stop 中断当前任务后重试' };
2020
1993
  }
2021
1994
  }
2022
1995
  }
2023
- else if (agent.hasActiveStream(chatmodeSession.id)) {
1996
+ else if (agent.hasActiveStream(chatmodeSession.id) || this.messageQueue?.isProcessing(chatmodeSession.id)) {
2024
1997
  return { kind: 'command.error', text: '⚠️ 当前正在处理消息,请稍后再试\n使用 /stop 中断当前任务后重试' };
2025
1998
  }
2026
1999
  await this.sessionManager.updateSession(chatmodeSession.id, { sessionMode: arg });
@@ -2185,16 +2158,14 @@ export class CommandHandler {
2185
2158
  // 尝试获取活跃会话(话题时直接查找话题 session)
2186
2159
  let session;
2187
2160
  if (threadId) {
2188
- session = await this.sessionManager.getOrCreateSession(channel, channelId, this.getEffectiveDefaultPath(channel), threadId);
2161
+ session = await this.sessionManager.getOrCreateSession(channel, channelId, this.getEffectiveDefaultPath(channel), threadId, undefined, undefined, undefined, chatType, undefined, undefined, this.resolveChannelType(channel));
2189
2162
  }
2190
2163
  else {
2191
2164
  session = await this.sessionManager.getActiveSession(channel, channelId);
2192
2165
  }
2193
- // 对于需要会话的命令,如果没有会话则使用默认项目创建临时会话
2194
- if (!session && (normalizedContent.startsWith('/new') ||
2195
- normalizedContent === '/pwd' ||
2196
- normalizedContent === '/status')) {
2197
- session = await this.sessionManager.getOrCreateSession(channel, channelId, this.getEffectiveDefaultPath(channel));
2166
+ // 如果没有会话,自动创建(所有后续命令都需要 session)
2167
+ if (!session) {
2168
+ session = await this.sessionManager.getOrCreateSession(channel, channelId, this.getEffectiveDefaultPath(channel), undefined, undefined, undefined, undefined, chatType, undefined, undefined, this.resolveChannelType(channel));
2198
2169
  }
2199
2170
  // /status 命令:显示会话状态
2200
2171
  if (normalizedContent === '/status') {
@@ -2418,6 +2389,27 @@ export class CommandHandler {
2418
2389
  env: { ...process.env, EVOLCLAW_HOME: resolvePaths().root }
2419
2390
  }).unref();
2420
2391
  this.eventBus.publish({ type: 'system:restart', channel, channelId });
2392
+ // 先发送重启反馈消息,等待发送完成后再 kill 进程
2393
+ // 避免消息还没发出去进程就退出了
2394
+ const adapter = this.adapters.get(channel);
2395
+ if (adapter) {
2396
+ try {
2397
+ const envelope = buildEnvelope({
2398
+ taskId: `restart-${Date.now()}`,
2399
+ channel,
2400
+ channelId,
2401
+ agentName: 'system',
2402
+ chatmode: 'interactive',
2403
+ replyContext,
2404
+ });
2405
+ await adapter.send(envelope, { kind: 'command.result', text: '🔄 服务正在重启,请稍候...(约 5 秒后恢复)' });
2406
+ // 等待消息发送完成后再延迟 kill
2407
+ await new Promise(resolve => setTimeout(resolve, 500));
2408
+ }
2409
+ catch (err) {
2410
+ logger.error('[System] Failed to send restart notification:', err);
2411
+ }
2412
+ }
2421
2413
  // 发 SIGTERM 而非直接 process.exit(0),让 index.ts 的 shutdown() 先
2422
2414
  // 正常关闭所有 channel(包括 Feishu WebSocket close frame),
2423
2415
  // 避免 Feishu 服务端因连接异常断开而重推未 ack 的消息给新进程。
@@ -2448,7 +2440,8 @@ export class CommandHandler {
2448
2440
  }
2449
2441
  }
2450
2442
  await executeRestart();
2451
- return { kind: 'command.result', text: '🔄 服务正在重启,请稍候...(约 5 秒后恢复)' };
2443
+ // executeRestart 内部已经发送了反馈消息,这里返回 null 避免重复发送
2444
+ return null;
2452
2445
  }
2453
2446
  // /upgrade 命令:检查版本更新,提示用户手动重启
2454
2447
  if (normalizedContent === '/upgrade') {
@@ -3167,6 +3160,7 @@ export class CommandHandler {
3167
3160
  nextFireAt,
3168
3161
  targetChannel: parsed.targetChannel ?? channel,
3169
3162
  targetChannelId: parsed.targetChannelId ?? channelId,
3163
+ targetChannelType: this.resolveChannelType(parsed.targetChannel ?? channel),
3170
3164
  targetThreadId: parsed.targetThreadId,
3171
3165
  targetSessionStrategy: parsed.targetSessionStrategy,
3172
3166
  agentId: parsed.agentId,
@@ -51,7 +51,7 @@ export function detectDuplicates(agents) {
51
51
  export class EvolAgentRegistry {
52
52
  _agentsDir;
53
53
  agents = new Map();
54
- /** channel key (`<type>#<selfPeerId>#<name>`) → agent aid */
54
+ /** channel key (`<type>#<selfAID>#<name>`) → agent aid */
55
55
  channelIndex = new Map();
56
56
  /** 启动期被 ConfigStore 跳过的目录(命名非法 / 缺 config.json / 校验失败等) */
57
57
  skipped = [];
@@ -62,11 +62,11 @@ export class EvolAgent {
62
62
  }
63
63
  // ── Channels ──────────────────────────────────────────────────────────
64
64
  /**
65
- * effective channel key:`<type>#<urlEncode(selfPeerId)>#<name>`。
66
- * AUN channel 的 selfPeerId 是 agent.aid,name 固定为 'main'。
65
+ * effective channel key:`<type>#<selfAID>#<name>`。
66
+ * AUN channel 的 selfAID 是 agent.aid,name 固定为 'main'。
67
67
  */
68
68
  effectiveChannelName(type, rawName) {
69
- return formatChannelKey({ type, selfPeerId: this.aid, name: rawName });
69
+ return formatChannelKey({ type, selfAID: this.aid, name: rawName });
70
70
  }
71
71
  channelInstanceNames() {
72
72
  // AUN channel 隐式存在(从 agent.aid 派生),不需要在 channels[] 里声明
@@ -97,7 +97,7 @@ export class EvolAgent {
97
97
  */
98
98
  isAunChannelKey(channelKey) {
99
99
  const parsed = tryParseChannelKey(channelKey);
100
- return parsed?.type === 'aun' && parsed.selfPeerId === this.aid;
100
+ return parsed?.type === 'aun' && parsed.selfAID === this.aid;
101
101
  }
102
102
  getOwner(channelKey) {
103
103
  if (this.isAunChannelKey(channelKey)) {