evolclaw 3.1.4 → 3.1.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +60 -0
- package/dist/agents/claude-runner.js +398 -161
- package/dist/agents/kit-renderer.js +191 -25
- package/dist/aun/aid/agentmd.js +75 -103
- package/dist/aun/aid/client.js +1 -29
- package/dist/aun/aid/identity.js +105 -64
- package/dist/aun/aid/index.js +2 -1
- package/dist/aun/aid/store.js +74 -0
- package/dist/aun/msg/group.js +2 -2
- package/dist/aun/msg/p2p.js +26 -2
- package/dist/aun/rpc/connection.js +23 -30
- package/dist/channels/aun.js +174 -99
- package/dist/channels/dingtalk.js +2 -1
- package/dist/channels/feishu.js +301 -199
- package/dist/channels/qqbot.js +2 -1
- package/dist/channels/wechat.js +2 -1
- package/dist/channels/wecom.js +2 -1
- package/dist/cli/agent.js +21 -16
- package/dist/cli/bench.js +41 -28
- package/dist/cli/help.js +8 -0
- package/dist/cli/index.js +176 -87
- package/dist/cli/init-channel.js +5 -1
- package/dist/cli/init.js +37 -21
- package/dist/cli/link-rules.js +1 -7
- package/dist/cli/model.js +549 -0
- package/dist/cli/net-check.js +133 -50
- package/dist/cli/watch-msg.js +7 -7
- package/dist/cli/watch-web/debug-log.js +18 -0
- package/dist/cli/watch-web/server.js +306 -0
- package/dist/cli/watch-web/sources/aid.js +63 -0
- package/dist/cli/watch-web/sources/msg.js +70 -0
- package/dist/cli/watch-web/sources/session.js +638 -0
- package/dist/cli/watch-web/sources/types.js +10 -0
- package/dist/cli/watch-web/static/app.js +546 -0
- package/dist/cli/watch-web/static/index.html +54 -0
- package/dist/cli/watch-web/static/style.css +247 -0
- package/dist/config-store.js +1 -22
- package/dist/core/channel-loader.js +7 -4
- package/dist/core/command-handler.js +261 -133
- package/dist/core/evolagent-registry.js +1 -1
- package/dist/core/evolagent.js +4 -22
- package/dist/core/interaction-router.js +59 -0
- package/dist/core/message/im-renderer.js +9 -20
- package/dist/core/message/message-bridge.js +13 -9
- package/dist/core/message/message-log.js +2 -2
- package/dist/core/message/message-processor.js +211 -123
- package/dist/core/message/stream-idle-monitor.js +21 -0
- package/dist/core/model/model-catalog.js +215 -0
- package/dist/core/model/model-scope.js +250 -0
- package/dist/core/relation/peer-identity.js +58 -55
- package/dist/core/relation/peer-key.js +16 -0
- package/dist/core/session/session-fs-store.js +34 -55
- package/dist/core/session/session-key.js +24 -0
- package/dist/core/session/session-manager.js +308 -251
- package/dist/core/session/session-mapper.js +9 -4
- package/dist/core/trigger/manager.js +3 -3
- package/dist/core/trigger/parser.js +4 -4
- package/dist/core/trigger/scheduler.js +22 -7
- package/dist/index.js +61 -7
- package/dist/ipc.js +23 -1
- package/dist/utils/error-utils.js +6 -0
- package/dist/utils/process-introspect.js +7 -5
- package/kits/docs/GUIDE.md +2 -2
- package/kits/docs/INDEX.md +8 -8
- package/kits/docs/channels/aun.md +56 -17
- package/kits/docs/channels/feishu.md +41 -12
- package/kits/docs/context-assembly.md +182 -0
- package/kits/docs/evolclaw/INDEX.md +43 -0
- package/kits/docs/evolclaw/agent.md +49 -0
- package/kits/docs/evolclaw/aid.md +49 -0
- package/kits/docs/evolclaw/ctl.md +46 -0
- package/kits/docs/evolclaw/group.md +89 -0
- package/kits/docs/evolclaw/model.md +51 -0
- package/kits/docs/evolclaw/msg.md +91 -0
- package/kits/docs/evolclaw/rpc.md +35 -0
- package/kits/docs/evolclaw/storage.md +49 -0
- package/kits/docs/venues/aun-group.md +10 -0
- package/kits/docs/venues/aun-private.md +10 -0
- package/kits/docs/venues/client-desktop.md +10 -0
- package/kits/docs/venues/client-mobile.md +10 -0
- package/kits/docs/venues/feishu-group.md +13 -0
- package/kits/docs/venues/feishu-private.md +9 -0
- package/kits/docs/venues/group.md +23 -0
- package/kits/docs/venues/private.md +10 -0
- package/kits/eck_manifest.json +81 -36
- package/kits/rules/01-overview.md +20 -10
- package/kits/rules/06-channel.md +34 -27
- package/kits/templates/system-fragments/baseagent.md +7 -1
- package/kits/templates/system-fragments/channel.md +7 -5
- package/kits/templates/system-fragments/commands.md +19 -0
- package/kits/templates/system-fragments/session.md +19 -3
- package/kits/templates/system-fragments/venue.md +24 -0
- package/package.json +10 -5
- package/dist/aun/aid/lifecycle-log.js +0 -33
- package/dist/utils/aid-lifecycle-log.js +0 -33
- package/kits/docs/evolclaw/AGENT_CMD.md +0 -31
- package/kits/docs/evolclaw/MSG_GROUP.md +0 -30
- package/kits/docs/evolclaw/MSG_PRIVATE.md +0 -72
- package/kits/docs/evolclaw/tools.md +0 -25
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { formatTimestamp } from './session-fs-store.js';
|
|
2
|
+
import { formatSessionKey, DEFAULT_THREAD_ID } from './session-key.js';
|
|
2
3
|
export function sessionToFile(session) {
|
|
3
4
|
const metadata = {};
|
|
4
5
|
if (session.metadata) {
|
|
@@ -15,19 +16,22 @@ export function sessionToFile(session) {
|
|
|
15
16
|
if (session.metadata.resumeAt)
|
|
16
17
|
metadata.resumeAt = session.metadata.resumeAt;
|
|
17
18
|
for (const [k, v] of Object.entries(session.metadata)) {
|
|
18
|
-
if (['isActive', 'channelName', 'permissionMode', 'peerId', 'peerName', 'groupId', 'replyContext', 'agentSessions', 'resumeAt'].includes(k))
|
|
19
|
+
if (['isActive', 'channelKey', 'channelName', 'permissionMode', 'peerId', 'peerName', 'groupId', 'replyContext', 'agentSessions', 'resumeAt'].includes(k))
|
|
19
20
|
continue;
|
|
20
21
|
if (v !== undefined)
|
|
21
22
|
metadata[k] = v;
|
|
22
23
|
}
|
|
23
24
|
}
|
|
24
25
|
const now = session.updatedAt || Date.now();
|
|
26
|
+
const channelType = session.channelType || session.channel;
|
|
27
|
+
const threadId = session.threadId || DEFAULT_THREAD_ID;
|
|
25
28
|
return {
|
|
26
29
|
id: session.id,
|
|
27
30
|
channel: session.channel,
|
|
28
|
-
channelType
|
|
31
|
+
channelType,
|
|
29
32
|
channelId: session.channelId,
|
|
30
|
-
|
|
33
|
+
sessionKey: formatSessionKey(channelType, session.channelId, threadId),
|
|
34
|
+
selfAID: session.selfAID,
|
|
31
35
|
agentType: session.agentId || 'claude',
|
|
32
36
|
threadId: session.threadId || '',
|
|
33
37
|
chatType: session.chatType || 'private',
|
|
@@ -71,7 +75,8 @@ export function fileToSession(file) {
|
|
|
71
75
|
channel: file.channel,
|
|
72
76
|
channelType: file.channelType,
|
|
73
77
|
channelId: file.channelId,
|
|
74
|
-
|
|
78
|
+
sessionKey: file.sessionKey || formatSessionKey(file.channelType, file.channelId, file.threadId || DEFAULT_THREAD_ID),
|
|
79
|
+
selfAID: file.selfAID,
|
|
75
80
|
agentId: file.agentType,
|
|
76
81
|
threadId: file.threadId,
|
|
77
82
|
chatType: file.chatType,
|
|
@@ -23,7 +23,7 @@ export class TriggerManager {
|
|
|
23
23
|
const raw = fs.readFileSync(this.triggersPath, 'utf8');
|
|
24
24
|
const data = JSON.parse(raw);
|
|
25
25
|
this.triggers = new Map(Object.entries(data.triggers ?? {}));
|
|
26
|
-
// Migrate old channel key format: "
|
|
26
|
+
// Migrate old channel key format: "selfAID#type#name" → "type#selfAID#name"
|
|
27
27
|
let migrated = false;
|
|
28
28
|
for (const trigger of this.triggers.values()) {
|
|
29
29
|
const fixed = this.migrateChannelKey(trigger.targetChannel);
|
|
@@ -52,7 +52,7 @@ export class TriggerManager {
|
|
|
52
52
|
}
|
|
53
53
|
}
|
|
54
54
|
/**
|
|
55
|
-
* Detect and fix old format "
|
|
55
|
+
* Detect and fix old format "selfAID#type#name" → current "type#selfAID#name".
|
|
56
56
|
* Old format: first segment contains '.' (AID like "evolai.agentid.pub").
|
|
57
57
|
*/
|
|
58
58
|
migrateChannelKey(key) {
|
|
@@ -63,7 +63,7 @@ export class TriggerManager {
|
|
|
63
63
|
return key;
|
|
64
64
|
const [first, second, third] = parts;
|
|
65
65
|
if (first.includes('.') && !second.includes('.')) {
|
|
66
|
-
return formatChannelKey({ type: second,
|
|
66
|
+
return formatChannelKey({ type: second, selfAID: first, name: third });
|
|
67
67
|
}
|
|
68
68
|
return key;
|
|
69
69
|
}
|
|
@@ -127,8 +127,8 @@ export function parseTriggerUpdate(args) {
|
|
|
127
127
|
}
|
|
128
128
|
if (flags.has('session')) {
|
|
129
129
|
const sv = flags.get('session');
|
|
130
|
-
if (sv !== 'latest' && sv !== '
|
|
131
|
-
return { ok: false, error: '--session 只接受 latest 或
|
|
130
|
+
if (sv !== 'latest' && sv !== 'current' && sv !== 'thread')
|
|
131
|
+
return { ok: false, error: '--session 只接受 latest、current 或 thread' };
|
|
132
132
|
result.targetSessionStrategy = sv;
|
|
133
133
|
}
|
|
134
134
|
if (flags.has('agent')) {
|
|
@@ -216,8 +216,8 @@ export function parseTriggerSet(args) {
|
|
|
216
216
|
let targetSessionStrategy = 'latest';
|
|
217
217
|
if (hasSession) {
|
|
218
218
|
const sv = flags.get('session');
|
|
219
|
-
if (sv !== 'latest' && sv !== '
|
|
220
|
-
return { ok: false, error: '--session 只接受 latest 或
|
|
219
|
+
if (sv !== 'latest' && sv !== 'current' && sv !== 'thread') {
|
|
220
|
+
return { ok: false, error: '--session 只接受 latest、current 或 thread' };
|
|
221
221
|
}
|
|
222
222
|
targetSessionStrategy = sv;
|
|
223
223
|
}
|
|
@@ -209,11 +209,12 @@ export class TriggerScheduler {
|
|
|
209
209
|
this.inflightCron.delete(triggerId);
|
|
210
210
|
}
|
|
211
211
|
buildSyntheticMessage(trigger, messageId) {
|
|
212
|
-
|
|
212
|
+
const base = {
|
|
213
213
|
channel: trigger.targetChannel,
|
|
214
|
+
channelType: trigger.targetChannelType,
|
|
214
215
|
channelId: trigger.targetChannelId,
|
|
215
|
-
|
|
216
|
-
threadId:
|
|
216
|
+
selfAID: this.aid,
|
|
217
|
+
threadId: '',
|
|
217
218
|
agentId: trigger.agentId,
|
|
218
219
|
chatType: 'private',
|
|
219
220
|
peerId: `__trigger__:${trigger.id}`, // unique per trigger to prevent greedy merge
|
|
@@ -221,10 +222,24 @@ export class TriggerScheduler {
|
|
|
221
222
|
messageId,
|
|
222
223
|
timestamp: Date.now(),
|
|
223
224
|
source: 'trigger',
|
|
224
|
-
triggerMeta: {
|
|
225
|
-
triggerId: trigger.id,
|
|
226
|
-
silent: trigger.targetSessionStrategy === 'silent',
|
|
227
|
-
},
|
|
228
225
|
};
|
|
226
|
+
if (trigger.targetSessionStrategy === 'current') {
|
|
227
|
+
base.triggerMeta = { triggerId: trigger.id, boundSessionId: trigger.boundSessionId };
|
|
228
|
+
}
|
|
229
|
+
else if (trigger.targetSessionStrategy === 'thread') {
|
|
230
|
+
if (trigger.threadKind === 'feishu' && trigger.pendingThread) {
|
|
231
|
+
base.triggerMeta = { triggerId: trigger.id, pendingThread: true, rootMessageId: trigger.rootMessageId };
|
|
232
|
+
// threadId intentionally empty — first fire builds the thread via reply_in_thread
|
|
233
|
+
}
|
|
234
|
+
else {
|
|
235
|
+
base.threadId = trigger.targetThreadId ?? '';
|
|
236
|
+
base.triggerMeta = { triggerId: trigger.id };
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
else {
|
|
240
|
+
// latest
|
|
241
|
+
base.triggerMeta = { triggerId: trigger.id };
|
|
242
|
+
}
|
|
243
|
+
return base;
|
|
229
244
|
}
|
|
230
245
|
}
|
package/dist/index.js
CHANGED
|
@@ -26,7 +26,7 @@ import { StatsCollector } from './utils/stats.js';
|
|
|
26
26
|
import { AidStatsCollector } from './utils/stats.js';
|
|
27
27
|
import { PermissionGateway } from './core/permission.js';
|
|
28
28
|
import { InteractionRouter } from './core/interaction-router.js';
|
|
29
|
-
import { ChannelLoader } from './core/channel-loader.js';
|
|
29
|
+
import { ChannelLoader, tryParseChannelKey } from './core/channel-loader.js';
|
|
30
30
|
import { AgentLoader } from './core/baseagent-loader.js';
|
|
31
31
|
import { EvolAgentRegistry } from './core/evolagent-registry.js';
|
|
32
32
|
import { buildReloadHooks } from './core/channel-loader.js';
|
|
@@ -452,11 +452,58 @@ async function main() {
|
|
|
452
452
|
continue;
|
|
453
453
|
const scheduler = agent.triggerScheduler;
|
|
454
454
|
const primaryProjectPath = agent.config.projects?.defaultPath ?? primaryAgent.projectPath;
|
|
455
|
+
function scheduleRetryWhenIdle(boundId, msg, trigger) {
|
|
456
|
+
let done = false;
|
|
457
|
+
const handler = (ev) => {
|
|
458
|
+
if (ev.sessionId !== boundId || done)
|
|
459
|
+
return;
|
|
460
|
+
done = true;
|
|
461
|
+
clearTimeout(timer);
|
|
462
|
+
eventBus.unsubscribe('task:completed', handler);
|
|
463
|
+
retry();
|
|
464
|
+
};
|
|
465
|
+
const timer = setTimeout(() => {
|
|
466
|
+
if (done)
|
|
467
|
+
return;
|
|
468
|
+
done = true;
|
|
469
|
+
eventBus.unsubscribe('task:completed', handler);
|
|
470
|
+
retry();
|
|
471
|
+
}, 30_000);
|
|
472
|
+
eventBus.subscribe('task:completed', handler);
|
|
473
|
+
function retry() {
|
|
474
|
+
if (messageQueue.isProcessing(boundId)) {
|
|
475
|
+
scheduleRetryWhenIdle(boundId, msg, trigger);
|
|
476
|
+
return;
|
|
477
|
+
}
|
|
478
|
+
sessionManager.getSessionById(boundId).then(bound => {
|
|
479
|
+
if (!bound) {
|
|
480
|
+
logger.warn(`[Trigger] Bound session ${boundId} deleted, aborting`);
|
|
481
|
+
return;
|
|
482
|
+
}
|
|
483
|
+
messageQueue.enqueue(boundId, msg, bound.projectPath, { interruptible: false })
|
|
484
|
+
.catch(err => logger.error(`[Trigger] Retry failed ${trigger.id}: ${err}`));
|
|
485
|
+
});
|
|
486
|
+
}
|
|
487
|
+
}
|
|
455
488
|
scheduler.setFireCallback((msg, trigger) => {
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
489
|
+
if (trigger.targetSessionStrategy === 'current' && trigger.boundSessionId) {
|
|
490
|
+
const boundId = trigger.boundSessionId;
|
|
491
|
+
if (messageQueue.isProcessing(boundId)) {
|
|
492
|
+
scheduleRetryWhenIdle(boundId, msg, trigger);
|
|
493
|
+
return;
|
|
494
|
+
}
|
|
495
|
+
sessionManager.getSessionById(boundId).then(bound => {
|
|
496
|
+
if (!bound) {
|
|
497
|
+
logger.warn(`[Trigger] Bound session ${boundId} not found`);
|
|
498
|
+
return;
|
|
499
|
+
}
|
|
500
|
+
messageQueue.enqueue(boundId, msg, bound.projectPath, { interruptible: false })
|
|
501
|
+
.catch(err => logger.error(`[Trigger] Enqueue failed ${trigger.id}: ${err}`));
|
|
502
|
+
});
|
|
503
|
+
return;
|
|
504
|
+
}
|
|
505
|
+
messageQueue.enqueue(`${msg.channel}:${msg.channelId}`, msg, primaryProjectPath, { interruptible: false })
|
|
506
|
+
.catch(err => logger.error(`[Trigger] Enqueue failed ${trigger.id}: ${err}`));
|
|
460
507
|
});
|
|
461
508
|
// Subscribe to trigger:completed/failed/skipped to update cron inflight state
|
|
462
509
|
eventBus.subscribe('trigger:completed', (ev) => scheduler.onTriggerComplete(ev.triggerId, 'completed'));
|
|
@@ -504,7 +551,7 @@ async function main() {
|
|
|
504
551
|
nextFireAt: calcNextFireAt('cron', cronExpr),
|
|
505
552
|
targetChannel: firstChannel,
|
|
506
553
|
targetChannelId: '__system__',
|
|
507
|
-
targetSessionStrategy: '
|
|
554
|
+
targetSessionStrategy: 'latest',
|
|
508
555
|
prompt: '检查 evolclaw 是否有新版本可用。执行 `npm view evolclaw version` 获取最新版本,与当前版本(执行 `evolclaw --version`)对比。如果有新版本,执行 /restart 进行升级。如果已是最新版本,无需任何操作。',
|
|
509
556
|
createdByPeerId: '__system__',
|
|
510
557
|
createdByChannel: '__system__',
|
|
@@ -553,7 +600,8 @@ async function main() {
|
|
|
553
600
|
const owningAgent = agentRegistry.resolveByChannel(inst.adapter.channelKey);
|
|
554
601
|
const effectiveDefault = owningAgent?.projectPath
|
|
555
602
|
?? primaryAgent.projectPath;
|
|
556
|
-
const
|
|
603
|
+
const parsedKey = tryParseChannelKey(inst.adapter.channelKey);
|
|
604
|
+
const session = await sessionManager.getOrCreateSession(inst.adapter.channelKey, channelId, effectiveDefault, undefined, undefined, undefined, undefined, undefined, undefined, parsedKey?.selfAID, parsedKey?.type);
|
|
557
605
|
return path.isAbsolute(session.projectPath)
|
|
558
606
|
? session.projectPath
|
|
559
607
|
: path.resolve(process.cwd(), session.projectPath);
|
|
@@ -755,8 +803,11 @@ async function main() {
|
|
|
755
803
|
continue;
|
|
756
804
|
}
|
|
757
805
|
logger.info(`[Resume] Resuming session: ${session.id} (agent: ${evolName}::${baseagentName})`);
|
|
806
|
+
const parsedResumeKey = tryParseChannelKey(session.channel);
|
|
758
807
|
const resumeMessage = {
|
|
759
808
|
channel: session.channel,
|
|
809
|
+
channelType: session.channelType || parsedResumeKey?.type,
|
|
810
|
+
selfAID: parsedResumeKey?.selfAID,
|
|
760
811
|
channelId: session.channelId,
|
|
761
812
|
content: '服务已重启,请继续之前未完成的任务。',
|
|
762
813
|
timestamp: Date.now(),
|
|
@@ -866,6 +917,9 @@ async function main() {
|
|
|
866
917
|
queued: messageQueue.getQueueLengthByAgent(agentName),
|
|
867
918
|
}));
|
|
868
919
|
ipcServer.setAunAidStatsProvider(() => aidStatsCollector.getAllSnapshots());
|
|
920
|
+
ipcServer.setAunAidStatsRecorder((params) => {
|
|
921
|
+
aidStatsCollector.recordOutbound(params.aid, params.toPeer, Buffer.byteLength(params.text || '', 'utf-8'), params.text, false, params.encrypt, params.chatmode);
|
|
922
|
+
});
|
|
869
923
|
// ── Reload hooks: enable agentRegistry.reload() to drain/disconnect/restart channels ──
|
|
870
924
|
const reloadHooks = buildReloadHooks({
|
|
871
925
|
channelLoader,
|
package/dist/ipc.js
CHANGED
|
@@ -11,6 +11,7 @@ export class IpcServer {
|
|
|
11
11
|
agentRegistry;
|
|
12
12
|
aunAidProvider;
|
|
13
13
|
aunAidStatsProvider;
|
|
14
|
+
aunAidStatsRecorder;
|
|
14
15
|
constructor(socketPath, getStatus, commandExecutor) {
|
|
15
16
|
this.socketPath = socketPath;
|
|
16
17
|
this.getStatus = getStatus;
|
|
@@ -28,6 +29,10 @@ export class IpcServer {
|
|
|
28
29
|
setAunAidStatsProvider(provider) {
|
|
29
30
|
this.aunAidStatsProvider = provider;
|
|
30
31
|
}
|
|
32
|
+
/** Inject AUN AID stats recorder for aun-aid-stats-record-outbound IPC handler */
|
|
33
|
+
setAunAidStatsRecorder(recorder) {
|
|
34
|
+
this.aunAidStatsRecorder = recorder;
|
|
35
|
+
}
|
|
31
36
|
start() {
|
|
32
37
|
// Remove stale socket file (Unix only — named pipes auto-cleanup on process exit)
|
|
33
38
|
if (!isNamedPipe(this.socketPath)) {
|
|
@@ -88,7 +93,7 @@ export class IpcServer {
|
|
|
88
93
|
case 'status':
|
|
89
94
|
return this.getStatus();
|
|
90
95
|
case 'ping':
|
|
91
|
-
return { pong: true, pid: process.pid };
|
|
96
|
+
return { pong: true, pid: process.pid, protocolVersion: 1 };
|
|
92
97
|
case 'aun-aids': {
|
|
93
98
|
const aids = this.aunAidProvider ? this.aunAidProvider() : [];
|
|
94
99
|
return { ok: true, aids };
|
|
@@ -97,6 +102,23 @@ export class IpcServer {
|
|
|
97
102
|
const stats = this.aunAidStatsProvider ? this.aunAidStatsProvider() : [];
|
|
98
103
|
return { ok: true, stats };
|
|
99
104
|
}
|
|
105
|
+
case 'aun-aid-stats-record-outbound': {
|
|
106
|
+
if (!this.aunAidStatsRecorder)
|
|
107
|
+
return { ok: false, error: 'recorder not configured' };
|
|
108
|
+
try {
|
|
109
|
+
this.aunAidStatsRecorder({
|
|
110
|
+
aid: cmd.aid,
|
|
111
|
+
toPeer: cmd.toPeer,
|
|
112
|
+
text: cmd.text ?? '',
|
|
113
|
+
encrypt: cmd.encrypt,
|
|
114
|
+
chatmode: cmd.chatmode,
|
|
115
|
+
});
|
|
116
|
+
return { ok: true };
|
|
117
|
+
}
|
|
118
|
+
catch (e) {
|
|
119
|
+
return { ok: false, error: String(e?.message || e) };
|
|
120
|
+
}
|
|
121
|
+
}
|
|
100
122
|
case 'ctl': {
|
|
101
123
|
if (!this.commandExecutor)
|
|
102
124
|
return { ok: false, error: 'ctl not configured' };
|
|
@@ -10,6 +10,7 @@ export var ErrorType;
|
|
|
10
10
|
ErrorType["FILE_CORRUPT"] = "file_corrupt";
|
|
11
11
|
ErrorType["STREAM_ERROR"] = "stream_error";
|
|
12
12
|
ErrorType["CONTEXT_TOO_LONG"] = "context_too_long";
|
|
13
|
+
ErrorType["MODEL_UNAVAILABLE"] = "model_unavailable";
|
|
13
14
|
ErrorType["UNKNOWN"] = "unknown";
|
|
14
15
|
})(ErrorType || (ErrorType = {}));
|
|
15
16
|
/**
|
|
@@ -210,6 +211,11 @@ export function classifyError(error) {
|
|
|
210
211
|
|| msg.includes('上下文过长')) {
|
|
211
212
|
return ErrorType.CONTEXT_TOO_LONG;
|
|
212
213
|
}
|
|
214
|
+
if (msg.includes('invalid_model') || msg.includes('model_not_found')
|
|
215
|
+
|| msg.includes('no such model') || msg.includes('unknown model')
|
|
216
|
+
|| /api error: 404\b/.test(msg)) {
|
|
217
|
+
return ErrorType.MODEL_UNAVAILABLE;
|
|
218
|
+
}
|
|
213
219
|
if (msg.includes('401') || msg.includes('authentication_error')) {
|
|
214
220
|
return ErrorType.AUTH_ERROR;
|
|
215
221
|
}
|
|
@@ -51,18 +51,20 @@ function getStartTimeLinux(pid) {
|
|
|
51
51
|
const starttimeJiffies = parseInt(fields[19], 10);
|
|
52
52
|
if (isNaN(starttimeJiffies))
|
|
53
53
|
return null;
|
|
54
|
-
let
|
|
54
|
+
let btimeSec;
|
|
55
55
|
try {
|
|
56
|
-
|
|
56
|
+
const m = fs.readFileSync('/proc/stat', 'utf-8').match(/^btime (\d+)/m);
|
|
57
|
+
if (!m)
|
|
58
|
+
return null;
|
|
59
|
+
btimeSec = parseInt(m[1], 10);
|
|
57
60
|
}
|
|
58
61
|
catch {
|
|
59
62
|
return null;
|
|
60
63
|
}
|
|
61
|
-
if (isNaN(
|
|
64
|
+
if (isNaN(btimeSec))
|
|
62
65
|
return null;
|
|
63
66
|
const clkTck = 100;
|
|
64
|
-
|
|
65
|
-
return bootTimeMs + (starttimeJiffies / clkTck) * 1000;
|
|
67
|
+
return (btimeSec + starttimeJiffies / clkTck) * 1000;
|
|
66
68
|
}
|
|
67
69
|
// ── macOS ──
|
|
68
70
|
function getStartTimeMacOS(pid) {
|
package/kits/docs/GUIDE.md
CHANGED
|
@@ -2,14 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
## 查阅流程
|
|
4
4
|
|
|
5
|
-
1. 先看 `$KITS_RULES`(自动加载的
|
|
5
|
+
1. 先看 `$KITS_RULES`(自动加载的 6 个规则文件 01-06)了解机制骨架
|
|
6
6
|
2. 需要详细信息时,按 `INDEX.md` 找到对应文档路径
|
|
7
7
|
3. Read 对应文档
|
|
8
8
|
|
|
9
9
|
## 路径解析
|
|
10
10
|
|
|
11
11
|
文档中用 `$名称` 引用路径。解析步骤:
|
|
12
|
-
1. 查 `$KITS_RULES/
|
|
12
|
+
1. 查 `$KITS_RULES/02-navigation.md` 的路径体系速查表
|
|
13
13
|
2. 如需完整定义,Read `$KITS_DOCS/path-registry.md`
|
|
14
14
|
3. 如需运行时实际值,Read `$ECK/path-registry.md`
|
|
15
15
|
|
package/kits/docs/INDEX.md
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
|------|------|------|
|
|
9
9
|
| 查阅指南 | `GUIDE.md` | 文档查阅流程 |
|
|
10
10
|
| 路径定义 | `path-registry.md` | 所有预定义路径及派生规则 |
|
|
11
|
+
| 上下文组装机制 | `context-assembly.md` | manifest 装配机制:when 条件/合并覆盖/模板渲染/运行时变量/调试输出 |
|
|
11
12
|
|
|
12
13
|
## AUN 协议
|
|
13
14
|
|
|
@@ -18,13 +19,12 @@
|
|
|
18
19
|
|
|
19
20
|
## EvolClaw 命令
|
|
20
21
|
|
|
22
|
+
所有 `ec` 命令集的专属目录(用途/触发词/适用场景/文档)见 `evolclaw/INDEX.md`。
|
|
23
|
+
运行时由 `commands` fragment 按场景注入精简能力卡。
|
|
24
|
+
|
|
21
25
|
| 文档 | 路径 | 说明 |
|
|
22
26
|
|------|------|------|
|
|
23
|
-
|
|
|
24
|
-
| 运行时工具 | `evolclaw/tools.md` | ctl 命令集 |
|
|
25
|
-
| 自我总结 | `evolclaw/self-summary.md` | 自我总结流程指南 |
|
|
26
|
-
| 私聊消息 | `evolclaw/MSG_PRIVATE.md` | 私聊消息命令 |
|
|
27
|
-
| 群聊消息 | `evolclaw/MSG_GROUP.md` | 群聊消息命令 |
|
|
27
|
+
| 命令集目录 | `evolclaw/INDEX.md` | msg/group/agent/aid/storage/ctl/rpc/model/bench 全集索引 |
|
|
28
28
|
|
|
29
29
|
## 身份
|
|
30
30
|
|
|
@@ -35,12 +35,12 @@
|
|
|
35
35
|
| AID 档案规范 | `identity/AID_PROFILE_SPEC.md` | AID 档案格式规范 |
|
|
36
36
|
| 路径运维 | `identity/PATH_OPS.md` | 路径运维操作 |
|
|
37
37
|
|
|
38
|
-
##
|
|
38
|
+
## 渠道(知识文档·按需加载,不依赖当前渠道)
|
|
39
39
|
|
|
40
40
|
| 文档 | 路径 | 说明 |
|
|
41
41
|
|------|------|------|
|
|
42
|
-
| AUN 渠道 | `channels/aun.md` | AUN
|
|
43
|
-
| 飞书渠道 | `channels/feishu.md` |
|
|
42
|
+
| AUN 渠道 | `channels/aun.md` | AUN 渠道配置、参数与特有机制(网关发现/E2EE/群 ID/证书链) |
|
|
43
|
+
| 飞书渠道 | `channels/feishu.md` | 飞书渠道配置、参数与特有机制(appId/合并转发/卡片/user_id) |
|
|
44
44
|
|
|
45
45
|
## ECK 模板
|
|
46
46
|
|
|
@@ -1,25 +1,64 @@
|
|
|
1
|
-
# AUN
|
|
1
|
+
# AUN 渠道
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
> 知识性文档:AUN 渠道的配置、参数与特有机制。**不依赖当前会话渠道**——任意会话都可按需 Read。
|
|
4
|
+
> 运行时"怎么发消息"由注入的 `[channel]` 段决定(aun 渠道用 `ec msg send` / `ec group send`),不在本文。
|
|
5
|
+
> 底层协议方法与 RPC 逃生通道见 `evolclaw/rpc.md`。
|
|
4
6
|
|
|
5
|
-
|
|
6
|
-
- 群聊:`group.message.send` / `group.message.receive`
|
|
7
|
-
- 消息格式:纯文本 / Markdown / 文件引用
|
|
7
|
+
## 概述
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
AUN 渠道把 agent 接入 AUN(Agent Union Network),对端以 **AID** 为身份标识(既是身份也是地址)。evolclaw 通过 AUN SDK(`@agentunion/fastaun`)维护到 Gateway 的 WebSocket 长连接,支持私聊、群聊、文件、E2EE 加密。
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
- 通过 `https://<aid>/agent.md` 获取对端名片
|
|
13
|
-
- 名片包含:名称、能力声明、联系方式
|
|
11
|
+
## 配置
|
|
14
12
|
|
|
15
|
-
|
|
13
|
+
AUN 渠道是**隐式**的——它不写在 `channels[]` 数组里,而是从 agent 自身的 AID 自动创建。一个 agent 就是一个 AID,启动时 evolclaw 用顶层 `aid` 字段隐式构造唯一的 aun 实例(`channel-loader.ts`)。所以真实的 agent config.json 里 `channels` 数组通常是空的(飞书等非 aun 渠道才往里加)。
|
|
16
14
|
|
|
17
|
-
|
|
18
|
-
- 断线自动重连(SDK 内置退避策略)
|
|
19
|
-
- 连接状态可通过 `evolclaw ctl aid` 查看
|
|
15
|
+
### agent 配置(`agents/<aid>/config.json`)
|
|
20
16
|
|
|
21
|
-
|
|
17
|
+
```json
|
|
18
|
+
{
|
|
19
|
+
"$schema_version": 1,
|
|
20
|
+
"aid": "myagent.agentid.pub",
|
|
21
|
+
"enabled": true,
|
|
22
|
+
"owners": ["owner.agentid.pub"],
|
|
23
|
+
"channels": [],
|
|
24
|
+
"active_baseagent": "claude"
|
|
25
|
+
}
|
|
26
|
+
```
|
|
22
27
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
28
|
+
| 字段 | 说明 |
|
|
29
|
+
|------|------|
|
|
30
|
+
| `aid` | agent 的 AID,**即 aun 渠道身份**(隐式创建,无需在 channels[] 声明) |
|
|
31
|
+
| `enabled` | agent 是否启用 |
|
|
32
|
+
| `owners` | owner 的 **AID** 列表(最高权限);首个 owner 用于首次连接发欢迎消息 |
|
|
33
|
+
| `admins` | admin 的 AID 列表 |
|
|
34
|
+
| `channels` | 非 aun 渠道(飞书等)的实例数组;aun 不在此 |
|
|
35
|
+
|
|
36
|
+
> `owners`/`admins` 用 **AID**,不是渠道原生 ID。
|
|
37
|
+
|
|
38
|
+
### 进程级配置(`$EVOLCLAW_HOME/config.json`)
|
|
39
|
+
|
|
40
|
+
E2EE 加密种子是**进程级**的,所有 agent 共享,不在 agent config 里:
|
|
41
|
+
|
|
42
|
+
```json
|
|
43
|
+
{
|
|
44
|
+
"aun": {
|
|
45
|
+
"encryptionSeed": "<seed>"
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
留空时 SDK 依次回退 `AUN_ENCRYPTION_SEED` 环境变量 → 默认 `'evol'`。
|
|
51
|
+
|
|
52
|
+
> Gateway 不在配置里手填:连接时从 AID 自动发现(见下)。Keystore 默认落在 `$EVOLCLAW_HOME`。
|
|
53
|
+
|
|
54
|
+
## 特有机制
|
|
55
|
+
|
|
56
|
+
- **隐式创建**:aun 渠道由顶层 `aid` 派生,无独立配置实例;这是它与飞书等渠道最大的不同。
|
|
57
|
+
- **网关发现**:连接前查询 `https://<aid>/.well-known/aun-gateway` 获取网关地址(AID 本身即域名);per-agent 流程不提供手填 gateway 的入口,发现失败即连接失败。
|
|
58
|
+
- **身份与信任**:每条入站消息携带发送者 AID,经四级 X.509 证书链(Root CA → Registry CA → Issuer CA → Agent)验签。
|
|
59
|
+
- **agent.md 缓存**:对端名片 `https://<aid>/agent.md` 由 **AUN SDK 自动拉取并缓存**(`AgentMdManager`,带 ETag/TTL,默认 1 天内不重复探测)。缓存落在 **`$EVOLCLAW_HOME/AIDs/<aid>/`**,每个 AID 两个文件:`agent.md`(带签名正文)+ `agentmd.json`(元数据:etag/last_modified/checked_at/verify_status)。本端与对端的 agent.md 都缓存于此。
|
|
60
|
+
- 关系层另存一份**派生**的精简身份 `relations/<peerKey>/peer-identity.json`(type/isAgent/name),由 evolclaw 从 agent.md 提取,不是 agent.md 本体。
|
|
61
|
+
- **E2EE 加密**:可选端到端加密,回复默认跟随对端消息加密状态(密文回密文,明文回明文);种子由进程级 `aun.encryptionSeed` 提供。
|
|
62
|
+
- **断线重连**:SDK 内置退避策略自动重连。实时连接状态用 `ec watch aid` 查看(`ec aid` 是身份/证书管理,不显示连接状态)。
|
|
63
|
+
- **群 ID 格式**:`group.{issuer}/{group_name}`(新格式)或 `{group_no}.{issuer}`(数字群号,如 `11117.agentid.pub`)。
|
|
64
|
+
- **身份体系**:对端以 AID 标识,关系层 peerKey 形如 `aun#alice.aid.pub`。
|
|
@@ -1,27 +1,56 @@
|
|
|
1
1
|
# 飞书渠道
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
> 知识性文档:飞书渠道的配置、参数与特有机制。**不依赖当前会话渠道**——任意会话都可按需 Read(在 aun 会话里也可查飞书)。
|
|
4
|
+
> 运行时"怎么发消息"由注入的 `[channel]` 段决定(飞书是 evolclaw 自动回复,agent 不调 CLI),不在本文。
|
|
4
5
|
|
|
5
6
|
## 概述
|
|
6
7
|
|
|
7
|
-
飞书渠道通过 evolclaw 的 feishu channel
|
|
8
|
-
- 单聊消息收发
|
|
9
|
-
- 群聊消息收发
|
|
10
|
-
- 合并转发消息解析
|
|
11
|
-
- 文件/图片/视频消息
|
|
8
|
+
飞书渠道通过 evolclaw 的 feishu channel 插件接入,对端以飞书 `user_id`(如 `ou_xxx`)为身份标识。支持单聊/群聊收发、合并转发解析、富文本卡片、文件/图片/视频消息。
|
|
12
9
|
|
|
13
10
|
## 配置
|
|
14
11
|
|
|
15
|
-
|
|
12
|
+
飞书渠道按 **per-agent** 配置,写在 `agents/<aid>/config.json` 的 `channels` 数组里。每个元素是一个独立实例,靠 `name` 区分;同一 agent 可配多个飞书实例。
|
|
16
13
|
|
|
17
14
|
```json
|
|
18
15
|
{
|
|
19
|
-
"
|
|
20
|
-
|
|
16
|
+
"aid": "myagent.aid.pub",
|
|
17
|
+
"channels": [
|
|
18
|
+
{
|
|
19
|
+
"type": "feishu",
|
|
20
|
+
"name": "main",
|
|
21
21
|
"enabled": true,
|
|
22
|
-
"appId": "
|
|
23
|
-
"appSecret": "<app-secret>"
|
|
22
|
+
"appId": "cli_xxx",
|
|
23
|
+
"appSecret": "<app-secret>",
|
|
24
|
+
"owners": ["ou_owner_user_id"],
|
|
25
|
+
"admins": ["ou_admin_user_id"]
|
|
24
26
|
}
|
|
25
|
-
|
|
27
|
+
]
|
|
26
28
|
}
|
|
27
29
|
```
|
|
30
|
+
|
|
31
|
+
### 实例字段
|
|
32
|
+
|
|
33
|
+
| 字段 | 必填 | 说明 |
|
|
34
|
+
|------|------|------|
|
|
35
|
+
| `type` | 是 | 固定 `"feishu"` |
|
|
36
|
+
| `name` | 是(数组形式) | 该 agent 内飞书实例的本地标识,不含 `#` |
|
|
37
|
+
| `enabled` | 否 | 默认启用;`false` 关闭该实例 |
|
|
38
|
+
| `appId` | 是 | 飞书自建应用 App ID |
|
|
39
|
+
| `appSecret` | 是 | 飞书自建应用 App Secret |
|
|
40
|
+
| `owners` | 否 | owner 的飞书 `user_id` 列表(最高权限) |
|
|
41
|
+
| `admins` | 否 | admin 的飞书 `user_id` 列表 |
|
|
42
|
+
| `flushDelay` | 否 | flush 间隔(秒),覆盖全局值 |
|
|
43
|
+
| `debounce` | 否 | 入站消息去抖间隔(秒),覆盖全局值 |
|
|
44
|
+
| `showActivities` | 否 | 思考过程展示范围:`all` / `dm-only` / `owner-dm-only` / `none` |
|
|
45
|
+
|
|
46
|
+
> `owners`/`admins` 用飞书原生 `user_id`,**不是 AID**。
|
|
47
|
+
|
|
48
|
+
agent 级(config.json 顶层)相关开关:
|
|
49
|
+
- `enable_rich_content`:启用富文本卡片渲染,默认关闭。
|
|
50
|
+
|
|
51
|
+
## 特有机制
|
|
52
|
+
|
|
53
|
+
- **合并转发解析**:飞书的 `merge_forward` 消息会被自动展开为文本注入上下文(含"以下是引用的原消息"包裹)。
|
|
54
|
+
- **交互卡片**:富内容开启时,部分回复以飞书卡片形式发送;卡片按钮**仅发起者可操作**,他人点击返回提示。
|
|
55
|
+
- **身份体系**:对端以 `user_id` 标识,关系层 peerKey 形如 `feishu#ou_xxx`。
|
|
56
|
+
- **自动回复**:飞书是非 aun 渠道,回复由 evolclaw 自动完成,agent 无需调用 `ec msg send`。
|