evolclaw 3.1.4 → 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 (85) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/dist/agents/claude-runner.js +348 -156
  3. package/dist/agents/kit-renderer.js +176 -21
  4. package/dist/aun/aid/agentmd.js +68 -103
  5. package/dist/aun/aid/client.js +1 -29
  6. package/dist/aun/aid/identity.js +105 -64
  7. package/dist/aun/aid/index.js +2 -1
  8. package/dist/aun/aid/store.js +74 -0
  9. package/dist/aun/msg/p2p.js +26 -2
  10. package/dist/aun/rpc/connection.js +23 -30
  11. package/dist/channels/aun.js +77 -88
  12. package/dist/channels/dingtalk.js +1 -0
  13. package/dist/channels/feishu.js +270 -190
  14. package/dist/channels/qqbot.js +1 -0
  15. package/dist/channels/wechat.js +1 -0
  16. package/dist/channels/wecom.js +1 -0
  17. package/dist/cli/agent.js +11 -5
  18. package/dist/cli/bench.js +40 -23
  19. package/dist/cli/index.js +170 -44
  20. package/dist/cli/init-channel.js +5 -1
  21. package/dist/cli/model.js +324 -0
  22. package/dist/cli/net-check.js +133 -50
  23. package/dist/cli/watch-msg.js +7 -7
  24. package/dist/cli/watch-web/debug-log.js +18 -0
  25. package/dist/cli/watch-web/server.js +306 -0
  26. package/dist/cli/watch-web/sources/aid.js +63 -0
  27. package/dist/cli/watch-web/sources/msg.js +70 -0
  28. package/dist/cli/watch-web/sources/session.js +638 -0
  29. package/dist/cli/watch-web/sources/types.js +10 -0
  30. package/dist/cli/watch-web/static/app.js +546 -0
  31. package/dist/cli/watch-web/static/index.html +54 -0
  32. package/dist/cli/watch-web/static/style.css +247 -0
  33. package/dist/core/channel-loader.js +7 -4
  34. package/dist/core/command-handler.js +81 -86
  35. package/dist/core/evolagent-registry.js +1 -1
  36. package/dist/core/evolagent.js +4 -4
  37. package/dist/core/interaction-router.js +59 -0
  38. package/dist/core/message/message-bridge.js +6 -6
  39. package/dist/core/message/message-log.js +2 -2
  40. package/dist/core/message/message-processor.js +86 -101
  41. package/dist/core/message/stream-idle-monitor.js +21 -0
  42. package/dist/core/model/model-catalog.js +215 -0
  43. package/dist/core/model/model-scope.js +250 -0
  44. package/dist/core/relation/peer-identity.js +40 -49
  45. package/dist/core/relation/peer-key.js +16 -0
  46. package/dist/core/session/session-fs-store.js +34 -55
  47. package/dist/core/session/session-key.js +24 -0
  48. package/dist/core/session/session-manager.js +308 -251
  49. package/dist/core/session/session-mapper.js +9 -4
  50. package/dist/core/trigger/manager.js +3 -3
  51. package/dist/core/trigger/scheduler.js +2 -1
  52. package/dist/index.js +6 -2
  53. package/dist/ipc.js +22 -0
  54. package/kits/docs/GUIDE.md +2 -2
  55. package/kits/docs/INDEX.md +11 -7
  56. package/kits/docs/channels/aun.md +56 -17
  57. package/kits/docs/channels/feishu.md +41 -12
  58. package/kits/docs/context-assembly.md +181 -0
  59. package/kits/docs/evolclaw/agent.md +49 -0
  60. package/kits/docs/evolclaw/aid.md +49 -0
  61. package/kits/docs/evolclaw/ctl.md +46 -0
  62. package/kits/docs/evolclaw/group.md +82 -0
  63. package/kits/docs/evolclaw/msg.md +86 -0
  64. package/kits/docs/evolclaw/rpc.md +35 -0
  65. package/kits/docs/evolclaw/storage.md +49 -0
  66. package/kits/docs/venues/aun-group.md +10 -0
  67. package/kits/docs/venues/aun-private.md +10 -0
  68. package/kits/docs/venues/client-desktop.md +10 -0
  69. package/kits/docs/venues/client-mobile.md +10 -0
  70. package/kits/docs/venues/feishu-group.md +13 -0
  71. package/kits/docs/venues/feishu-private.md +9 -0
  72. package/kits/docs/venues/group.md +11 -0
  73. package/kits/docs/venues/private.md +10 -0
  74. package/kits/eck_manifest.json +72 -36
  75. package/kits/rules/01-overview.md +20 -10
  76. package/kits/rules/06-channel.md +30 -27
  77. package/kits/templates/system-fragments/session.md +10 -3
  78. package/kits/templates/system-fragments/venue.md +9 -0
  79. package/package.json +11 -6
  80. package/dist/aun/aid/lifecycle-log.js +0 -33
  81. package/dist/utils/aid-lifecycle-log.js +0 -33
  82. package/kits/docs/evolclaw/AGENT_CMD.md +0 -31
  83. package/kits/docs/evolclaw/MSG_GROUP.md +0 -30
  84. package/kits/docs/evolclaw/MSG_PRIVATE.md +0 -72
  85. package/kits/docs/evolclaw/tools.md +0 -25
@@ -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 行为一致)
@@ -636,7 +615,7 @@ export class CommandHandler {
636
615
  const models = await agent.listModels() ?? [];
637
616
  const currentModel = agent.getModel();
638
617
  if (models.length > 0)
639
- 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 }));
640
619
  }
641
620
  return null;
642
621
  }
@@ -1044,7 +1023,7 @@ export class CommandHandler {
1044
1023
  env: { ...process.env, EVOLCLAW_HOME: resolvePaths().root }
1045
1024
  }).unref();
1046
1025
  this.eventBus.publish({ type: 'system:restart', channel, channelId });
1047
- setTimeout(() => { process.kill(process.pid, 'SIGTERM'); }, 500);
1026
+ setTimeout(() => { process.kill(process.pid, 'SIGTERM'); }, 1000);
1048
1027
  return { data: { action: 'restart', success: true } };
1049
1028
  }
1050
1029
  if (action === 'check') {
@@ -1161,12 +1140,12 @@ export class CommandHandler {
1161
1140
  }
1162
1141
  // 空闲检查:某些命令需要等待当前会话空闲
1163
1142
  // 原则:仅对"写/破坏性"形态拦截,纯读/用法提示的无参形态始终放行
1164
- // - 始终需要 idle(无参即写):/new /clear /compact /repair /fork
1143
+ // - 始终需要 idle(无参即写):/clear /compact /repair /fork /new
1165
1144
  // - 仅带参时需要 idle(无参是列表/用法):/session /baseagent /rewind
1166
1145
  // - /chatmode:在 handler 内部自行做写操作的 idle 检查
1167
1146
  // - /dispatch:在 handler 内部自行做写操作的 idle 检查
1168
1147
  // - /safe:已禁用 no-op,不再要求 idle
1169
- const idleAlways = ['/new', '/clear', '/compact', '/repair', '/fork'];
1148
+ const idleAlways = ['/clear', '/compact', '/repair', '/fork', '/new'];
1170
1149
  const idleWhenArg = ['/session', '/baseagent', '/rewind'];
1171
1150
  const needsIdle = idleAlways.some(cmd => normalizedContent === cmd || normalizedContent.startsWith(cmd + ' ')) ||
1172
1151
  idleWhenArg.some(cmd => normalizedContent.startsWith(cmd + ' '));
@@ -1176,13 +1155,19 @@ export class CommandHandler {
1176
1155
  const threadSession = await this.sessionManager.getThreadSession(channel, channelId, threadId);
1177
1156
  if (threadSession) {
1178
1157
  const threadAgent = this.getAgent(channel, threadSession.agentId);
1179
- if (threadAgent.hasActiveStream(threadSession.id)) {
1158
+ const isBusy = threadAgent.hasActiveStream(threadSession.id) ||
1159
+ this.messageQueue?.isProcessing(threadSession.id);
1160
+ if (isBusy) {
1180
1161
  return { kind: 'command.error', text: '⚠️ 当前正在处理消息,请稍后再试\n使用 /stop 中断当前任务后重试' };
1181
1162
  }
1182
1163
  }
1183
1164
  }
1184
- else if (activeSession && agent.hasActiveStream(activeSession.id)) {
1185
- 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
+ }
1186
1171
  }
1187
1172
  }
1188
1173
  // 检查是否以 / 开头(可能是命令)
@@ -1627,37 +1612,12 @@ export class CommandHandler {
1627
1612
  const currentModel = hasModelSwitcher(setmodelAgent) ? setmodelAgent.getModel() : setmodelAgent.name;
1628
1613
  const efforts = getAvailableEfforts(setmodelAgent, currentModel);
1629
1614
  const currentEffort = setmodelAgent.getEffort?.() || 'auto';
1630
- const modelListSource = getModelListSource(this.getOwningAgent(channel), setmodelAgent);
1631
- let modelListData = null;
1632
- if (modelListSource.apiBaseUrl) {
1633
- try {
1634
- const modelsUrl = modelListSource.apiBaseUrl.replace(/\/+$/, '') + '/v1/models';
1635
- const controller = new AbortController();
1636
- const timeout = setTimeout(() => controller.abort(), 5000);
1637
- const resp = await fetch(modelsUrl, {
1638
- signal: controller.signal,
1639
- headers: { 'Authorization': `Bearer ${modelListSource.apiKey || ''}` },
1640
- });
1641
- clearTimeout(timeout);
1642
- if (resp.ok) {
1643
- modelListData = await resp.json();
1644
- }
1645
- }
1646
- catch { }
1647
- }
1648
- // 兜底模型列表
1649
- if (!modelListData || !modelListData.data || modelListData.data.length === 0) {
1650
- const now = Math.floor(Date.now() / 1000);
1651
- modelListData = {
1652
- object: 'list',
1653
- data: modelListSource.fallbackModels.map(id => ({
1654
- id,
1655
- object: 'model',
1656
- created: now,
1657
- owned_by: modelListSource.owner,
1658
- })),
1659
- };
1660
- }
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
+ };
1661
1621
  return { kind: 'command.result', text: JSON.stringify({
1662
1622
  current_model: currentModel,
1663
1623
  current_effort: currentEffort,
@@ -1690,12 +1650,15 @@ export class CommandHandler {
1690
1650
  kind: {
1691
1651
  kind: 'command-card',
1692
1652
  title: '🤖 切换模型',
1693
- buttons: models.map((m) => ({
1694
- label: m === currentModel ? `✓ ${m}` : m,
1695
- command: `/model ${m}`,
1696
- style: (m === currentModel ? 'primary' : 'default'),
1697
- disabled: m === currentModel,
1698
- })),
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
+ }),
1699
1662
  },
1700
1663
  };
1701
1664
  const replyCtx = this.getReplyContext(modelSession);
@@ -1705,14 +1668,14 @@ export class CommandHandler {
1705
1668
  return { kind: 'command.result', text: cardResult };
1706
1669
  }
1707
1670
  // 降级:文本
1708
- const modelList = models.map((m) => ` ${m === currentModel ? '✓' : ' '} ${m}`).join('\n');
1671
+ const modelList = models.map((m) => ` ${m === currentModel ? '✓' : ' '} ${modelDisplayLabel(modelAgent, m)}`).join('\n');
1709
1672
  const effortHint = efforts.length > 0
1710
1673
  ? `\n推理强度: ${currentEffort === 'auto' ? 'auto (SDK默认)' : currentEffort} (使用 /effort 调整)`
1711
1674
  : '';
1712
1675
  if (isAdmin) {
1713
- 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 <模型>` };
1714
1677
  }
1715
- return { kind: 'command.result', text: `当前模型: ${currentModel}${effortHint}` };
1678
+ return { kind: 'command.result', text: `当前模型: ${modelDisplayLabel(modelAgent, currentModel)}${effortHint}` };
1716
1679
  }
1717
1680
  // 带参(切换/调整)需 admin+;无参查询已在上方返回
1718
1681
  if (!isAdmin)
@@ -1732,20 +1695,29 @@ export class CommandHandler {
1732
1695
  else if (allEfforts.includes(arg)) {
1733
1696
  return { kind: 'command.error', text: `⚠️ 请使用 /effort ${arg} 调整推理强度` };
1734
1697
  }
1735
- else if (models.includes(arg)) {
1736
- newModel = arg;
1737
- }
1738
1698
  else {
1739
- const modelList = models.map((m) => ` ${m === currentModel ? '✓' : ' '} ${m}`).join('\n');
1740
- const effortHint = efforts.length > 0 ? `\n\n推理强度请使用 /effort 命令` : '';
1741
- 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
+ }
1742
1711
  }
1743
1712
  }
1744
1713
  else {
1745
1714
  // 双参数:model effort
1746
- 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;
1747
1719
  if (!models.includes(modelArg)) {
1748
- return { kind: 'command.error', text: `❌ 无效的模型ID: ${modelArg}` };
1720
+ return { kind: 'command.error', text: `❌ 无效的模型ID: ${modelArgRaw}` };
1749
1721
  }
1750
1722
  const targetEfforts = getAvailableEfforts(modelAgent, modelArg);
1751
1723
  if (targetEfforts.length === 0) {
@@ -2016,12 +1988,12 @@ export class CommandHandler {
2016
1988
  const threadSession = await this.sessionManager.getThreadSession(channel, channelId, threadId);
2017
1989
  if (threadSession) {
2018
1990
  const threadAgent = this.getAgent(channel, threadSession.agentId);
2019
- if (threadAgent.hasActiveStream(threadSession.id)) {
1991
+ if (threadAgent.hasActiveStream(threadSession.id) || this.messageQueue?.isProcessing(threadSession.id)) {
2020
1992
  return { kind: 'command.error', text: '⚠️ 当前正在处理消息,请稍后再试\n使用 /stop 中断当前任务后重试' };
2021
1993
  }
2022
1994
  }
2023
1995
  }
2024
- else if (agent.hasActiveStream(chatmodeSession.id)) {
1996
+ else if (agent.hasActiveStream(chatmodeSession.id) || this.messageQueue?.isProcessing(chatmodeSession.id)) {
2025
1997
  return { kind: 'command.error', text: '⚠️ 当前正在处理消息,请稍后再试\n使用 /stop 中断当前任务后重试' };
2026
1998
  }
2027
1999
  await this.sessionManager.updateSession(chatmodeSession.id, { sessionMode: arg });
@@ -2417,6 +2389,27 @@ export class CommandHandler {
2417
2389
  env: { ...process.env, EVOLCLAW_HOME: resolvePaths().root }
2418
2390
  }).unref();
2419
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
+ }
2420
2413
  // 发 SIGTERM 而非直接 process.exit(0),让 index.ts 的 shutdown() 先
2421
2414
  // 正常关闭所有 channel(包括 Feishu WebSocket close frame),
2422
2415
  // 避免 Feishu 服务端因连接异常断开而重推未 ack 的消息给新进程。
@@ -2447,7 +2440,8 @@ export class CommandHandler {
2447
2440
  }
2448
2441
  }
2449
2442
  await executeRestart();
2450
- return { kind: 'command.result', text: '🔄 服务正在重启,请稍候...(约 5 秒后恢复)' };
2443
+ // executeRestart 内部已经发送了反馈消息,这里返回 null 避免重复发送
2444
+ return null;
2451
2445
  }
2452
2446
  // /upgrade 命令:检查版本更新,提示用户手动重启
2453
2447
  if (normalizedContent === '/upgrade') {
@@ -3166,6 +3160,7 @@ export class CommandHandler {
3166
3160
  nextFireAt,
3167
3161
  targetChannel: parsed.targetChannel ?? channel,
3168
3162
  targetChannelId: parsed.targetChannelId ?? channelId,
3163
+ targetChannelType: this.resolveChannelType(parsed.targetChannel ?? channel),
3169
3164
  targetThreadId: parsed.targetThreadId,
3170
3165
  targetSessionStrategy: parsed.targetSessionStrategy,
3171
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)) {