evolclaw 2.8.3 → 3.1.0

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 (142) hide show
  1. package/README.md +21 -12
  2. package/bin/ec.js +29 -0
  3. package/dist/agents/baseagent-normalize.js +19 -0
  4. package/dist/agents/claude-runner.js +108 -46
  5. package/dist/agents/codex-runner.js +13 -14
  6. package/dist/agents/gemini-runner.js +15 -17
  7. package/dist/agents/kit-renderer.js +281 -0
  8. package/dist/agents/resolve.js +134 -0
  9. package/dist/aun/aid/agentmd.js +186 -0
  10. package/dist/aun/aid/client.js +134 -0
  11. package/dist/aun/aid/identity.js +159 -0
  12. package/dist/aun/aid/index.js +3 -0
  13. package/dist/aun/aid/lifecycle-log.js +33 -0
  14. package/dist/aun/aid/types.js +1 -0
  15. package/dist/aun/aid/validation.js +21 -0
  16. package/dist/aun/msg/group.js +293 -0
  17. package/dist/aun/msg/index.js +4 -0
  18. package/dist/aun/msg/p2p.js +147 -0
  19. package/dist/aun/msg/payload-type.js +27 -0
  20. package/dist/aun/msg/upload.js +98 -0
  21. package/dist/aun/outbox.js +138 -0
  22. package/dist/aun/rpc/caller.js +42 -0
  23. package/dist/aun/rpc/connection.js +34 -0
  24. package/dist/aun/rpc/index.js +2 -0
  25. package/dist/aun/storage/download.js +29 -0
  26. package/dist/aun/storage/index.js +3 -0
  27. package/dist/aun/storage/manage.js +10 -0
  28. package/dist/aun/storage/upload.js +35 -0
  29. package/dist/channels/aun.js +1340 -349
  30. package/dist/channels/dingtalk.js +59 -5
  31. package/dist/channels/feishu.js +381 -32
  32. package/dist/channels/qqbot.js +68 -12
  33. package/dist/channels/wechat.js +63 -4
  34. package/dist/channels/wecom.js +59 -5
  35. package/dist/cli/agent.js +800 -0
  36. package/dist/cli/bench.js +1219 -0
  37. package/dist/cli/index.js +4513 -0
  38. package/dist/{utils → cli}/init-channel.js +211 -621
  39. package/dist/cli/init.js +178 -0
  40. package/dist/cli/link-rules.js +245 -0
  41. package/dist/cli/net-check.js +640 -0
  42. package/dist/cli/watch-msg.js +589 -0
  43. package/dist/config-store.js +645 -0
  44. package/dist/core/{agent-loader.js → baseagent-loader.js} +6 -12
  45. package/dist/core/channel-loader.js +176 -12
  46. package/dist/core/command-handler.js +883 -848
  47. package/dist/core/evolagent-registry.js +191 -371
  48. package/dist/core/evolagent.js +202 -238
  49. package/dist/core/interaction-router.js +52 -5
  50. package/dist/core/message/im-renderer.js +486 -0
  51. package/dist/core/message/items-formatter.js +68 -0
  52. package/dist/core/message/message-bridge.js +109 -56
  53. package/dist/core/message/message-log.js +93 -0
  54. package/dist/core/message/message-processor.js +430 -212
  55. package/dist/core/message/message-queue.js +13 -6
  56. package/dist/core/permission.js +116 -11
  57. package/dist/core/session/adapters/codex-session-file-adapter.js +24 -2
  58. package/dist/core/session/session-fs-store.js +230 -0
  59. package/dist/core/session/session-manager.js +740 -777
  60. package/dist/core/session/session-mapper.js +87 -0
  61. package/dist/core/trigger/manager.js +122 -0
  62. package/dist/core/trigger/parser.js +128 -0
  63. package/dist/core/trigger/scheduler.js +224 -0
  64. package/dist/data/error-dict.json +118 -0
  65. package/dist/eck/baseagent-caps.js +18 -0
  66. package/dist/eck/detect.js +47 -0
  67. package/dist/eck/init.js +77 -0
  68. package/dist/eck/rules-loader.js +28 -0
  69. package/dist/index.js +560 -283
  70. package/dist/ipc.js +49 -0
  71. package/dist/net-check.js +640 -0
  72. package/dist/paths.js +73 -9
  73. package/dist/types.js +8 -2
  74. package/dist/utils/aid-lifecycle-log.js +33 -0
  75. package/dist/utils/atomic-write.js +89 -0
  76. package/dist/utils/channel-helpers.js +46 -0
  77. package/dist/utils/cross-platform.js +17 -26
  78. package/dist/utils/error-utils.js +10 -2
  79. package/dist/utils/instance-registry.js +434 -0
  80. package/dist/utils/log-writer.js +217 -0
  81. package/dist/utils/logger.js +34 -77
  82. package/dist/utils/media-cache.js +23 -0
  83. package/dist/utils/npm-ops.js +163 -0
  84. package/dist/utils/process-introspect.js +122 -0
  85. package/dist/utils/stats.js +192 -0
  86. package/dist/watch-msg.js +544 -0
  87. package/evolclaw-install-aun.md +127 -47
  88. package/kits/docs/GUIDE.md +20 -0
  89. package/kits/docs/INDEX.md +52 -0
  90. package/kits/docs/aun/CHEATSHEET.md +17 -0
  91. package/kits/docs/aun/SYNC_PROTOCOL.md +15 -0
  92. package/kits/docs/channels/aun.md +25 -0
  93. package/kits/docs/channels/feishu.md +27 -0
  94. package/kits/docs/eck_templates/GUIDE.template.md +22 -0
  95. package/kits/docs/eck_templates/INDEX.template.md +28 -0
  96. package/kits/docs/eck_templates/path-registry.template.md +33 -0
  97. package/kits/docs/eck_templates/runtime.template.md +19 -0
  98. package/kits/docs/evolclaw/AGENT_CMD.md +31 -0
  99. package/kits/docs/evolclaw/MSG_GROUP.md +30 -0
  100. package/kits/docs/evolclaw/MSG_PRIVATE.md +25 -0
  101. package/kits/docs/evolclaw/self-summary.md +29 -0
  102. package/kits/docs/evolclaw/tools.md +25 -0
  103. package/kits/docs/identity/AID_PROFILE_SPEC.md +27 -0
  104. package/kits/docs/identity/PATH_OPS.md +16 -0
  105. package/kits/docs/identity/ROLE_DETAIL.md +20 -0
  106. package/kits/docs/identity/identity-tools.md +26 -0
  107. package/kits/docs/path-registry.md +43 -0
  108. package/kits/eck_manifest.json +95 -0
  109. package/kits/rules/01-overview.md +120 -0
  110. package/kits/rules/02-navigation.md +75 -0
  111. package/kits/rules/03-identity.md +34 -0
  112. package/kits/rules/04-relation.md +49 -0
  113. package/kits/rules/05-venue.md +45 -0
  114. package/kits/rules/06-channel.md +43 -0
  115. package/kits/templates/system-fragments/baseagent.md +2 -0
  116. package/kits/templates/system-fragments/channel.md +10 -0
  117. package/kits/templates/system-fragments/identity.md +12 -0
  118. package/kits/templates/system-fragments/relation.md +9 -0
  119. package/kits/templates/system-fragments/runtime.md +19 -0
  120. package/kits/templates/system-fragments/venue.md +5 -0
  121. package/package.json +10 -6
  122. package/data/evolclaw.sample.json +0 -60
  123. package/dist/agents/templates.js +0 -122
  124. package/dist/channels/aun-ops.js +0 -275
  125. package/dist/cli.js +0 -2178
  126. package/dist/config.js +0 -591
  127. package/dist/core/agent-registry.js +0 -450
  128. package/dist/core/evolagent-schema.js +0 -72
  129. package/dist/core/message/stream-flusher.js +0 -238
  130. package/dist/core/message/thought-emitter.js +0 -162
  131. package/dist/core/reload-hooks.js +0 -87
  132. package/dist/prompts/templates.js +0 -122
  133. package/dist/templates/prompts.md +0 -104
  134. package/dist/templates/skills.md +0 -66
  135. package/dist/utils/channel-fingerprint.js +0 -59
  136. package/dist/utils/error-dict.js +0 -63
  137. package/dist/utils/format.js +0 -32
  138. package/dist/utils/init.js +0 -645
  139. package/dist/utils/migrate-project.js +0 -122
  140. package/dist/utils/reload-hooks.js +0 -87
  141. package/dist/utils/stats-collector.js +0 -99
  142. package/dist/utils/upgrade.js +0 -100
@@ -1,238 +0,0 @@
1
- import { logger } from '../../utils/logger.js';
2
- import fs from 'fs';
3
- import path from 'path';
4
- import { resolvePaths } from '../../paths.js';
5
- // 诊断日志(按需启用,通过 config.debug.flusherDiag 控制)
6
- let diagStream = null;
7
- function getDiagStream() {
8
- if (!diagStream) {
9
- const logDir = resolvePaths().logs;
10
- diagStream = fs.createWriteStream(path.join(logDir, 'flusher-diag.log'), { flags: 'a' });
11
- }
12
- return diagStream;
13
- }
14
- function diag(instanceId, action, meta = {}) {
15
- const line = JSON.stringify({ ts: new Date().toISOString(), id: instanceId, action, ...meta });
16
- getDiagStream().write(line + '\n');
17
- }
18
- /**
19
- * 流式输出缓冲器
20
- * 按时间窗口批量推送文本和活动事件
21
- *
22
- * 延迟策略:
23
- * - 第1次:立即发送(0ms)
24
- * - 第2-4次:半延迟(interval / 2)
25
- * - 第5次起:动态自适应延迟
26
- * - 计算最近10条消息的平均间隔
27
- * - 动态延迟 = 平均间隔 * 3
28
- * - 下限:interval(额定值)
29
- * - 上限:interval * 2.5
30
- */
31
- let instanceCounter = 0;
32
- export class StreamFlusher {
33
- send;
34
- interval;
35
- silent;
36
- buffer = '';
37
- queue = []; // 按入队顺序记录 activity 和 text 段
38
- timer;
39
- lastFlush = Date.now();
40
- allText = '';
41
- sentContent = false;
42
- fileMarkerPattern;
43
- flushCount = 0;
44
- messageTimestamps = [];
45
- instanceId;
46
- createTime = Date.now();
47
- diagEnabled;
48
- sendChain = Promise.resolve(); // 串行发送队列,保证消息按序到达
49
- constructor(send, interval = 4000, fileMarkerPattern, diagEnabled = false, silent = false) {
50
- this.send = send;
51
- this.interval = interval;
52
- this.silent = silent;
53
- this.fileMarkerPattern = fileMarkerPattern;
54
- this.diagEnabled = diagEnabled;
55
- this.instanceId = `F${++instanceCounter}`;
56
- if (this.diagEnabled)
57
- diag(this.instanceId, 'created', { interval, silent });
58
- }
59
- addText(text) {
60
- if (this.buffer.length === 0 && text.length > 0) {
61
- this.queue.push({ kind: 'text' });
62
- }
63
- this.buffer += text;
64
- this.allText += text;
65
- this.messageTimestamps.push(Date.now());
66
- if (this.diagEnabled)
67
- diag(this.instanceId, 'addText', { len: text.length, preview: text.substring(0, 60), bufLen: this.buffer.length, queueLen: this.queue.length });
68
- this.scheduleFlush();
69
- }
70
- addTextBlock(text) {
71
- if (this.buffer && !this.buffer.endsWith('\n')) {
72
- this.buffer += '\n\n';
73
- this.allText += '\n\n';
74
- }
75
- this.buffer += text;
76
- this.allText += text;
77
- this.queue.push({ kind: 'text' });
78
- this.messageTimestamps.push(Date.now());
79
- if (this.diagEnabled)
80
- diag(this.instanceId, 'addTextBlock', { len: text.length, preview: text.substring(0, 60), bufLen: this.buffer.length });
81
- this.scheduleFlush();
82
- }
83
- addActivity(desc) {
84
- this.queue.push({ kind: 'activity', text: desc });
85
- this.messageTimestamps.push(Date.now());
86
- if (this.diagEnabled)
87
- diag(this.instanceId, 'addActivity', { desc: desc.substring(0, 80), queueLen: this.queue.length });
88
- this.scheduleFlush();
89
- }
90
- hasContent() {
91
- return this.buffer.length > 0 || this.queue.some(e => e.kind === 'activity');
92
- }
93
- hasSentContent() {
94
- return this.sentContent;
95
- }
96
- getFinalText() {
97
- return this.allText;
98
- }
99
- getRemainingText() {
100
- return this.buffer;
101
- }
102
- stripFromBuffer(pattern) {
103
- this.buffer = this.buffer.replace(pattern, '').trim();
104
- }
105
- scheduleFlush() {
106
- if (this.silent)
107
- return; // proactive 模式:不调度发送
108
- if (this.timer) {
109
- if (this.diagEnabled)
110
- diag(this.instanceId, 'scheduleFlush:skip', { reason: 'timer_exists' });
111
- return;
112
- }
113
- let targetDelay;
114
- if (this.flushCount === 0) {
115
- targetDelay = 500;
116
- }
117
- else if (this.flushCount <= 3) {
118
- targetDelay = Math.ceil(this.interval / 2);
119
- }
120
- else if (this.messageTimestamps.length >= 5) {
121
- targetDelay = this.calculateDynamicDelay();
122
- }
123
- else {
124
- targetDelay = this.interval;
125
- }
126
- const elapsed = Date.now() - this.lastFlush;
127
- const delay = Math.max(0, targetDelay - elapsed);
128
- if (this.diagEnabled)
129
- diag(this.instanceId, 'scheduleFlush:set', { flushCount: this.flushCount, targetDelay, elapsed, actualDelay: delay });
130
- this.timer = setTimeout(() => this.flush(), delay);
131
- }
132
- calculateDynamicDelay() {
133
- const recent = this.messageTimestamps.slice(-10);
134
- const intervals = [];
135
- for (let i = 1; i < recent.length; i++) {
136
- intervals.push(recent[i] - recent[i - 1]);
137
- }
138
- if (intervals.length === 0)
139
- return this.interval;
140
- const avgInterval = intervals.reduce((a, b) => a + b) / intervals.length;
141
- let dynamicDelay = avgInterval * 3;
142
- const minDelay = this.interval;
143
- const maxDelay = this.interval * 2.5;
144
- return Math.max(minDelay, Math.min(maxDelay, dynamicDelay));
145
- }
146
- /**
147
- * 只 flush activities,保留 text buffer 不动
148
- * 用于 complete 事件前清空 pending activities,让最终文本留给 flush(true) 发送
149
- */
150
- async flushActivitiesOnly() {
151
- if (this.silent)
152
- return;
153
- const hasActivities = this.queue.some(e => e.kind === 'activity');
154
- if (!hasActivities)
155
- return;
156
- if (this.timer) {
157
- clearTimeout(this.timer);
158
- this.timer = undefined;
159
- }
160
- // 只取 activity 条目,保留 text 条目在 queue 中
161
- const activities = this.queue.filter(e => e.kind === 'activity');
162
- this.queue = this.queue.filter(e => e.kind === 'text');
163
- let output = activities.map(e => e.text).join('\n') + '\n\n';
164
- if (output && this.fileMarkerPattern) {
165
- output = output.replace(this.fileMarkerPattern, '').trim();
166
- }
167
- if (this.diagEnabled)
168
- diag(this.instanceId, 'flushActivitiesOnly', { outputLen: output.length });
169
- if (output) {
170
- this.sentContent = true; // 同步标记,避免 timer flush 未 await 时的竞态
171
- const text = output;
172
- // chain 保持不断裂:单条失败不阻塞后续(catch → resolve)
173
- this.sendChain = this.sendChain
174
- .then(() => this.send(text, false, false))
175
- .catch(e => { logger.warn('[StreamFlusher] send failed:', e); });
176
- await this.sendChain;
177
- this.lastFlush = Date.now();
178
- this.flushCount++;
179
- }
180
- }
181
- async flush(isFinal) {
182
- if (this.silent) {
183
- // 清理内部状态,避免后续误用
184
- if (this.timer) {
185
- clearTimeout(this.timer);
186
- this.timer = undefined;
187
- }
188
- this.queue = [];
189
- this.buffer = '';
190
- return;
191
- }
192
- if (this.timer) {
193
- clearTimeout(this.timer);
194
- this.timer = undefined;
195
- }
196
- let output = '';
197
- const hasText = this.buffer.length > 0;
198
- // 按入队顺序合并:遇到 text 条目时插入 buffer 内容,遇到 activity 直接追加
199
- let textInserted = false;
200
- for (const entry of this.queue) {
201
- if (entry.kind === 'activity') {
202
- // 确保 activity 前有换行分隔(text 末尾可能没有换行)
203
- if (output && !output.endsWith('\n'))
204
- output += '\n';
205
- output += entry.text + '\n';
206
- }
207
- else if (!textInserted) {
208
- if (output)
209
- output += output.endsWith('\n') ? '\n' : '\n\n';
210
- output += this.buffer;
211
- textInserted = true;
212
- }
213
- }
214
- // 如果 queue 为空但有 buffer(纯文本情况)
215
- if (!textInserted && hasText) {
216
- output += this.buffer;
217
- }
218
- this.queue = [];
219
- this.buffer = '';
220
- if (output && this.fileMarkerPattern) {
221
- output = output.replace(this.fileMarkerPattern, '').trim();
222
- }
223
- if (this.diagEnabled)
224
- diag(this.instanceId, 'flush', { isFinal, outputLen: output.length, flushCount: this.flushCount, sinceLastFlush: Date.now() - this.lastFlush, preview: output.substring(0, 80) });
225
- if (output) {
226
- this.sentContent = true; // 同步标记,避免 timer flush 未 await 时的竞态
227
- const text = output;
228
- const final = isFinal;
229
- const ht = hasText;
230
- this.sendChain = this.sendChain
231
- .then(() => this.send(text, final, ht))
232
- .catch(e => { logger.warn('[StreamFlusher] send failed:', e); });
233
- await this.sendChain;
234
- this.lastFlush = Date.now();
235
- this.flushCount++;
236
- }
237
- }
238
- }
@@ -1,162 +0,0 @@
1
- import { logger } from '../../utils/logger.js';
2
- /**
3
- * ThoughtEmitter — 将 Proactive 模式下的流式 AgentEvent 实时发送为 thought
4
- *
5
- * 设计特点:
6
- * - 不做聚合/batching,逐事件调用 adapter.putThought()
7
- * - 不感知 group vs P2P,通道差异由 adapter 内部处理
8
- * - taskId 映射为 context: { type: 'task', id: taskId }(协议 selector)
9
- * 同时写入 payload.task_id / payload.chatmode,与 message.send/group.send 保持一致
10
- * - fire-and-forget:调用方不 await emit(),错误被内部捕获
11
- */
12
- export class ThoughtEmitter {
13
- adapter;
14
- channelId;
15
- taskId;
16
- chatmode;
17
- replyContext;
18
- hasEmittedText = false;
19
- constructor(adapter, channelId, taskId, chatmode = 'proactive', replyContext) {
20
- if (!taskId) {
21
- throw new Error('[ThoughtEmitter] taskId is required at construction');
22
- }
23
- this.adapter = adapter;
24
- this.channelId = channelId;
25
- this.taskId = taskId;
26
- this.chatmode = chatmode;
27
- this.replyContext = replyContext;
28
- logger.info(`[ThoughtEmitter] created channel=${channelId} task=${taskId} chatmode=${chatmode}`);
29
- }
30
- async emit(event) {
31
- // 对齐 interactive 的 dedup:流式 text 已推过时,complete.result 不再重复发 summary
32
- if (event.type === 'complete' &&
33
- !event.isError &&
34
- event.result &&
35
- this.hasEmittedText) {
36
- return;
37
- }
38
- const payload = this.mapEventToPayload(event);
39
- if (!payload)
40
- return;
41
- if (!this.adapter.putThought)
42
- return;
43
- if (payload.stage === 'thinking') {
44
- this.hasEmittedText = true;
45
- }
46
- // payload 也带上 task_id / chatmode(与 message.send/group.send 对齐)
47
- payload.task_id = this.taskId;
48
- payload.chatmode = this.chatmode;
49
- try {
50
- await this.adapter.putThought(this.channelId, this.taskId, payload, this.replyContext);
51
- }
52
- catch (err) {
53
- logger.debug(`[ThoughtEmitter] putThought failed: ${err.message}`);
54
- }
55
- }
56
- mapEventToPayload(event) {
57
- switch (event.type) {
58
- case 'text':
59
- if (!event.text)
60
- return null;
61
- return { type: 'thought', text: event.text, stage: 'thinking' };
62
- case 'tool_use': {
63
- const desc = this.summarizeInput(event.input, event.name);
64
- return {
65
- type: 'thought',
66
- text: desc ? `🔧 ${event.name}: ${desc}` : `🔧 ${event.name}`,
67
- stage: 'tool',
68
- metadata: { tool: event.name, input: desc },
69
- };
70
- }
71
- case 'tool_result':
72
- if (event.isError) {
73
- return {
74
- type: 'thought',
75
- text: `⚠️ ${event.name}: ${event.error || '执行失败'}`,
76
- stage: 'tool',
77
- metadata: { tool: event.name, ok: false },
78
- };
79
- }
80
- {
81
- const resultText = this.truncate(this.stringifyResult(event.result), 200);
82
- return {
83
- type: 'thought',
84
- text: resultText ? `✅ ${event.name}: ${resultText}` : `✅ ${event.name}`,
85
- stage: 'tool',
86
- metadata: { tool: event.name, ok: true },
87
- };
88
- }
89
- case 'compact':
90
- return {
91
- type: 'thought',
92
- text: `💡 会话压缩完成 (压缩前 tokens: ${event.preTokens})`,
93
- stage: 'system',
94
- };
95
- case 'task_progress': {
96
- const stats = this.formatTaskStats(event);
97
- const text = event.summary
98
- ? `⏳ 子任务: ${event.summary}${stats ? ` (${stats})` : ''}`
99
- : `⏳ 子任务进行中${stats ? `: ${stats}` : ''}`;
100
- return { type: 'thought', text, stage: 'planning' };
101
- }
102
- case 'error':
103
- return { type: 'thought', text: `❌ ${event.error}`, stage: 'error' };
104
- case 'complete':
105
- if (event.isError) {
106
- const errText = event.errors?.join('; ') || event.result || '任务失败';
107
- return { type: 'thought', text: `❌ ${errText}`, stage: 'error' };
108
- }
109
- if (event.result) {
110
- return { type: 'thought', text: event.result, stage: 'summary' };
111
- }
112
- return null;
113
- case 'session_id':
114
- case 'state_changed':
115
- case 'status':
116
- return null;
117
- default:
118
- return null;
119
- }
120
- }
121
- summarizeInput(input, toolName) {
122
- if (!input || typeof input !== 'object')
123
- return '';
124
- // Bash + ctl send/file: 显示完整命令内容(含发送的消息正文)
125
- if (toolName === 'Bash' && typeof input.command === 'string') {
126
- const cmd = input.command;
127
- if (cmd.includes('evolclaw ctl send') || cmd.includes('evolclaw ctl file')) {
128
- return cmd;
129
- }
130
- }
131
- return (input.description ||
132
- input.file_path ||
133
- input.pattern ||
134
- (typeof input.command === 'string' ? input.command.substring(0, 80) : '') ||
135
- (typeof input.prompt === 'string' ? input.prompt.substring(0, 80) : '') ||
136
- (typeof input.query === 'string' ? input.query.substring(0, 80) : '') ||
137
- '');
138
- }
139
- stringifyResult(result) {
140
- if (result === null || result === undefined)
141
- return '';
142
- if (typeof result === 'string')
143
- return result;
144
- try {
145
- return JSON.stringify(result);
146
- }
147
- catch {
148
- return String(result);
149
- }
150
- }
151
- truncate(text, maxLen) {
152
- return text.length > maxLen ? text.substring(0, maxLen) + '...' : text;
153
- }
154
- formatTaskStats(event) {
155
- const parts = [];
156
- if (event.toolUses)
157
- parts.push(`${event.toolUses} tools`);
158
- if (event.durationMs)
159
- parts.push(`${Math.round(event.durationMs / 1000)}s`);
160
- return parts.join(', ');
161
- }
162
- }
@@ -1,87 +0,0 @@
1
- /**
2
- * Reload Hooks
3
- *
4
- * Extracted from index.ts main() for testability. Builds the ReloadHooks
5
- * implementation used by AgentRegistry.reload() to drain/disconnect/start
6
- * channels during a hot reload.
7
- */
8
- import { logger } from '../utils/logger.js';
9
- export function buildReloadHooks(deps) {
10
- const { channelLoader, channelInstances, registerChannelInstance, messageQueue } = deps;
11
- const drainDelayMs = deps.drainDelayMs ?? 500;
12
- const drainTimeoutMs = deps.drainTimeoutMs ?? 30000;
13
- return {
14
- async drainChannel(channelName) {
15
- logger.info(`[Reload] Draining channel: ${channelName}`);
16
- if (messageQueue) {
17
- // Real drain: poll until empty or timeout
18
- const pollMs = 100;
19
- const start = Date.now();
20
- while (messageQueue.isChannelProcessing(channelName)) {
21
- if (Date.now() - start > drainTimeoutMs) {
22
- logger.warn(`[Reload] Drain timeout (${drainTimeoutMs}ms) for channel: ${channelName}, proceeding anyway`);
23
- return;
24
- }
25
- await new Promise(r => setTimeout(r, pollMs));
26
- }
27
- logger.info(`[Reload] Drain complete: ${channelName}`);
28
- }
29
- else if (drainDelayMs > 0) {
30
- await new Promise(r => setTimeout(r, drainDelayMs));
31
- }
32
- },
33
- async disconnectChannel(channelName) {
34
- const inst = channelInstances.find(i => i.adapter.channelName === channelName);
35
- if (!inst) {
36
- logger.warn(`[Reload] Channel ${channelName} not found, skipping disconnect`);
37
- return;
38
- }
39
- try {
40
- await inst.disconnect();
41
- const idx = channelInstances.indexOf(inst);
42
- if (idx >= 0)
43
- channelInstances.splice(idx, 1);
44
- logger.info(`[Reload] Disconnected channel: ${channelName}`);
45
- }
46
- catch (e) {
47
- logger.error(`[Reload] Failed to disconnect ${channelName}: ${e}`);
48
- throw e;
49
- }
50
- },
51
- async startChannel(agent, channelName) {
52
- const channels = agent.config.channels;
53
- let channelType = null;
54
- for (const [type, raw] of Object.entries(channels)) {
55
- const instances = Array.isArray(raw) ? raw : [raw];
56
- for (const inst of instances) {
57
- const name = inst.name ?? type;
58
- if (name === channelName) {
59
- channelType = type;
60
- break;
61
- }
62
- }
63
- if (channelType)
64
- break;
65
- }
66
- if (!channelType) {
67
- const msg = `[Reload] Channel ${channelName} not found in agent ${agent.name} config`;
68
- logger.error(msg);
69
- throw new Error(msg);
70
- }
71
- const partialConfig = {
72
- agents: agent.config.agents,
73
- channels: { [channelType]: channels[channelType] },
74
- projects: agent.config.projects,
75
- };
76
- const newInstances = await channelLoader.createAll(partialConfig);
77
- const newInst = newInstances.find(i => i.adapter.channelName === channelName);
78
- if (!newInst) {
79
- throw new Error(`[Reload] Failed to create instance ${channelName}`);
80
- }
81
- registerChannelInstance(newInst);
82
- await newInst.connect();
83
- channelInstances.push(newInst);
84
- logger.info(`[Reload] Started channel: ${channelName}`);
85
- },
86
- };
87
- }
@@ -1,122 +0,0 @@
1
- import fs from 'fs';
2
- import path from 'path';
3
- import { getPackageRoot, resolveRoot } from '../paths.js';
4
- import { logger } from '../utils/logger.js';
5
- const KNOWN_SECTIONS = new Set(['runtime', 'group', 'proactive']);
6
- const SECTION_RE = /^##\s+(\w+)\s*$/;
7
- let sections = null;
8
- let builtinSections = null;
9
- function parseTemplate(content) {
10
- const result = new Map();
11
- let currentSection = null;
12
- let currentLines = [];
13
- for (const line of content.split('\n')) {
14
- // Stop parsing at horizontal rule separator (documentation follows)
15
- if (/^---\s*$/.test(line)) {
16
- if (currentSection) {
17
- result.set(currentSection, currentLines.join('\n').trim());
18
- }
19
- break;
20
- }
21
- const m = line.match(SECTION_RE);
22
- if (m) {
23
- if (currentSection) {
24
- result.set(currentSection, currentLines.join('\n').trim());
25
- }
26
- const name = m[1];
27
- if (KNOWN_SECTIONS.has(name)) {
28
- currentSection = name;
29
- currentLines = [];
30
- }
31
- else {
32
- currentSection = null;
33
- currentLines = [];
34
- }
35
- }
36
- else if (currentSection) {
37
- currentLines.push(line);
38
- }
39
- }
40
- if (currentSection) {
41
- result.set(currentSection, currentLines.join('\n').trim());
42
- }
43
- return result;
44
- }
45
- function loadBuiltinTemplate() {
46
- const builtinPath = path.join(getPackageRoot(), 'dist', 'templates', 'prompts.md');
47
- const srcPath = path.join(getPackageRoot(), 'src', 'templates', 'prompts.md');
48
- const filePath = fs.existsSync(builtinPath) ? builtinPath : srcPath;
49
- const content = fs.readFileSync(filePath, 'utf-8');
50
- return parseTemplate(content);
51
- }
52
- export function loadPromptTemplates() {
53
- builtinSections = loadBuiltinTemplate();
54
- const userPath = path.join(resolveRoot(), 'data', 'prompts.md');
55
- if (fs.existsSync(userPath)) {
56
- try {
57
- const content = fs.readFileSync(userPath, 'utf-8');
58
- const parsed = parseTemplate(content);
59
- sections = new Map(builtinSections);
60
- for (const [key, value] of parsed) {
61
- sections.set(key, value);
62
- }
63
- logger.info(`[PromptTemplates] Loaded user override: ${userPath}`);
64
- }
65
- catch (err) {
66
- logger.warn(`[PromptTemplates] Failed to load user override (${userPath}), using builtin:`, err);
67
- sections = builtinSections;
68
- }
69
- }
70
- else {
71
- sections = builtinSections;
72
- logger.info(`[PromptTemplates] Using builtin templates`);
73
- }
74
- for (const name of KNOWN_SECTIONS) {
75
- if (!sections.has(name)) {
76
- logger.warn(`[PromptTemplates] Section "${name}" missing, using builtin fallback`);
77
- const fallback = builtinSections.get(name);
78
- if (fallback)
79
- sections.set(name, fallback);
80
- }
81
- }
82
- }
83
- function isTruthy(val) {
84
- if (val === undefined || val === null || val === false || val === '' || val === 0)
85
- return false;
86
- return true;
87
- }
88
- function renderTemplate(template, vars) {
89
- // Pass 1: conditional sections {{?key}}...{{/}}
90
- let result = template.replace(/\{\{\?(\w+)\}\}([\s\S]*?)\{\{\/\}\}/g, (_match, key, body) => {
91
- return isTruthy(vars[key]) ? body : '';
92
- });
93
- // Pass 2: variable substitution {{key}}
94
- result = result.replace(/\{\{(\w+)\}\}/g, (_match, key) => {
95
- const val = vars[key];
96
- if (!isTruthy(val))
97
- return '';
98
- return String(val);
99
- });
100
- // Pass 3: remove blank lines
101
- return result.split('\n').filter(line => line.trim() !== '').join('\n');
102
- }
103
- export function renderPromptSection(section, vars) {
104
- if (!sections)
105
- loadPromptTemplates();
106
- const template = sections.get(section);
107
- if (!template) {
108
- logger.warn(`[PromptTemplates] Section "${section}" not found`);
109
- return '';
110
- }
111
- return renderTemplate(template, vars);
112
- }
113
- /** Reset loaded templates (for testing) */
114
- export function _resetTemplates() {
115
- sections = null;
116
- builtinSections = null;
117
- }
118
- /** Load templates from a raw string (for testing) */
119
- export function _loadFromString(content) {
120
- builtinSections = parseTemplate(content);
121
- sections = builtinSections;
122
- }