evolclaw 3.1.6 → 3.1.7
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 +15 -0
- package/dist/agents/kit-renderer.js +78 -332
- package/dist/agents/manifest-engine.js +243 -0
- package/dist/agents/message-renderer.js +112 -0
- package/dist/channels/aun.js +56 -6
- package/dist/cli/index.js +6 -6
- package/dist/core/message/message-bridge.js +2 -7
- package/dist/core/message/message-processor.js +50 -4
- package/dist/core/message/message-queue.js +15 -1
- package/dist/core/trigger/scheduler.js +23 -7
- package/dist/index.js +48 -48
- package/kits/eck_message_manifest.json +14 -0
- package/kits/templates/message-fragments/item.md +2 -0
- package/kits/templates/system-fragments/session.md +3 -0
- package/package.json +1 -1
|
@@ -164,15 +164,16 @@ export class TriggerScheduler {
|
|
|
164
164
|
onFire() {
|
|
165
165
|
this.timer = null;
|
|
166
166
|
const now = Date.now();
|
|
167
|
-
//
|
|
168
|
-
|
|
167
|
+
// Loop guard reads a LIVE clock (not the frozen `now`): if rescheduling ever regresses
|
|
168
|
+
// and pushes an occurrence back inside the window, re-reading the clock each iteration
|
|
169
|
+
// still lets time advance past it so the loop drains and terminates.
|
|
170
|
+
while (this.heap.peek() && this.heap.peek().nextFireAt <= Date.now() + 50) {
|
|
169
171
|
const trigger = this.heap.pop();
|
|
170
172
|
if (trigger.scheduleType === 'cron' && this.inflightCron.has(trigger.id)) {
|
|
171
|
-
// Previous run still in flight — skip
|
|
173
|
+
// Previous run still in flight — skip this occurrence, reschedule the next one.
|
|
172
174
|
logger.warn(`[${this.aid}] Cron trigger ${trigger.name} still running, skipping`);
|
|
173
175
|
this.eventBus.publish({ type: 'trigger:skipped', triggerId: trigger.id, reason: 'overlap' });
|
|
174
|
-
|
|
175
|
-
const next = calcNextFireAt('cron', trigger.scheduleValue, now);
|
|
176
|
+
const next = this.nextCronFireAt(trigger.scheduleValue, Date.now());
|
|
176
177
|
this.manager.updateNextFireAt(trigger.id, next);
|
|
177
178
|
this.heap.push({ ...trigger, nextFireAt: next });
|
|
178
179
|
continue;
|
|
@@ -181,6 +182,21 @@ export class TriggerScheduler {
|
|
|
181
182
|
}
|
|
182
183
|
this.resetTimer();
|
|
183
184
|
}
|
|
185
|
+
/**
|
|
186
|
+
* Next cron occurrence strictly outside the firing window.
|
|
187
|
+
*
|
|
188
|
+
* cron-parser returns the next match at-or-after the reference instant. When the timer
|
|
189
|
+
* wakes a hair early — e.g. 08:59:59.999 for a `0 9 * * *` trigger — the "next" occurrence
|
|
190
|
+
* computes to 09:00:00.000, only ~1ms ahead and inside onFire's `<= now + 50` window. That
|
|
191
|
+
* occurrence gets popped and re-fired in the same pass, producing a tight loop. Recomputing
|
|
192
|
+
* from past the window forces the genuine next occurrence (e.g. tomorrow 09:00).
|
|
193
|
+
*/
|
|
194
|
+
nextCronFireAt(scheduleValue, ref) {
|
|
195
|
+
let next = calcNextFireAt('cron', scheduleValue, ref);
|
|
196
|
+
if (next <= ref + 50)
|
|
197
|
+
next = calcNextFireAt('cron', scheduleValue, ref + 51);
|
|
198
|
+
return next;
|
|
199
|
+
}
|
|
184
200
|
fireTrigger(trigger, now) {
|
|
185
201
|
const messageId = `trigger:${trigger.id}:${now}`;
|
|
186
202
|
const msg = this.buildSyntheticMessage(trigger, messageId);
|
|
@@ -189,8 +205,8 @@ export class TriggerScheduler {
|
|
|
189
205
|
this.manager.updateFireStats(trigger.id, now);
|
|
190
206
|
if (trigger.scheduleType === 'cron') {
|
|
191
207
|
this.inflightCron.add(trigger.id);
|
|
192
|
-
// Re-schedule next occurrence
|
|
193
|
-
const next =
|
|
208
|
+
// Re-schedule next occurrence (outside the firing window — see nextCronFireAt)
|
|
209
|
+
const next = this.nextCronFireAt(trigger.scheduleValue, now);
|
|
194
210
|
this.manager.updateNextFireAt(trigger.id, next);
|
|
195
211
|
this.heap.push({ ...trigger, nextFireAt: next });
|
|
196
212
|
}
|
package/dist/index.js
CHANGED
|
@@ -522,54 +522,6 @@ async function main() {
|
|
|
522
522
|
logger.error(`[Trigger] Scheduler init failed for ${agent.aid}: ${err}`);
|
|
523
523
|
}
|
|
524
524
|
}
|
|
525
|
-
// Inject primary agent's trigger scheduler as fallback (used when owning agent has no scheduler)
|
|
526
|
-
const primaryAgentForTrigger = agentRegistry.runnableAgents()[0];
|
|
527
|
-
if (primaryAgentForTrigger?.triggerScheduler && primaryAgentForTrigger?.triggerManager) {
|
|
528
|
-
cmdHandler.setTriggerScheduler(primaryAgentForTrigger.triggerScheduler, primaryAgentForTrigger.triggerManager);
|
|
529
|
-
}
|
|
530
|
-
// Seed default __upgrade-check trigger (daily at random time 3:00~3:59)
|
|
531
|
-
// 用户可通过 /trigger cancel __upgrade-check 永久禁用(不会再自动重建)
|
|
532
|
-
if (!isLinkedInstall() && primaryAgentForTrigger?.triggerManager && primaryAgentForTrigger?.triggerScheduler) {
|
|
533
|
-
const mgr = primaryAgentForTrigger.triggerManager;
|
|
534
|
-
const sched = primaryAgentForTrigger.triggerScheduler;
|
|
535
|
-
const UPGRADE_TRIGGER_NAME = '__upgrade-check';
|
|
536
|
-
if (!mgr.getByName(UPGRADE_TRIGGER_NAME)) {
|
|
537
|
-
// Check history: if user cancelled it before, respect that decision
|
|
538
|
-
const { history } = mgr.listAll();
|
|
539
|
-
const wasCancelled = history.some(h => h.name === UPGRADE_TRIGGER_NAME && h.doneReason === 'cancelled');
|
|
540
|
-
if (!wasCancelled) {
|
|
541
|
-
// Random minute in 3:00~3:59 to avoid all instances hitting registry simultaneously
|
|
542
|
-
const randomMinute = Math.floor(Math.random() * 60);
|
|
543
|
-
const cronExpr = `${randomMinute} 3 * * *`;
|
|
544
|
-
// Use first channel instance as target (command doesn't need real channelId)
|
|
545
|
-
const firstChannel = channelInstances[0]?.adapter?.channelName || 'system';
|
|
546
|
-
const trigger = {
|
|
547
|
-
id: crypto.randomUUID(),
|
|
548
|
-
name: UPGRADE_TRIGGER_NAME,
|
|
549
|
-
scheduleType: 'cron',
|
|
550
|
-
scheduleValue: cronExpr,
|
|
551
|
-
nextFireAt: calcNextFireAt('cron', cronExpr),
|
|
552
|
-
targetChannel: firstChannel,
|
|
553
|
-
targetChannelId: '__system__',
|
|
554
|
-
targetSessionStrategy: 'latest',
|
|
555
|
-
prompt: '检查 evolclaw 是否有新版本可用。执行 `npm view evolclaw version` 获取最新版本,与当前版本(执行 `evolclaw --version`)对比。如果有新版本,执行 /restart 进行升级。如果已是最新版本,无需任何操作。',
|
|
556
|
-
createdByPeerId: '__system__',
|
|
557
|
-
createdByChannel: '__system__',
|
|
558
|
-
fireCount: 0,
|
|
559
|
-
createdAt: Date.now(),
|
|
560
|
-
updatedAt: Date.now(),
|
|
561
|
-
};
|
|
562
|
-
try {
|
|
563
|
-
mgr.register(trigger);
|
|
564
|
-
sched.register(trigger);
|
|
565
|
-
logger.info(`[Trigger] Seeded default trigger: ${UPGRADE_TRIGGER_NAME} (cron ${cronExpr})`);
|
|
566
|
-
}
|
|
567
|
-
catch (e) {
|
|
568
|
-
logger.warn(`[Trigger] Failed to seed ${UPGRADE_TRIGGER_NAME}: ${e}`);
|
|
569
|
-
}
|
|
570
|
-
}
|
|
571
|
-
}
|
|
572
|
-
}
|
|
573
525
|
// 默认策略
|
|
574
526
|
const defaultPolicy = {
|
|
575
527
|
canSwitchProject: (chatType, role) => chatType === 'private' ? (role === 'owner' || role === 'admin') : role === 'owner',
|
|
@@ -641,6 +593,54 @@ async function main() {
|
|
|
641
593
|
for (const inst of channelInstances) {
|
|
642
594
|
registerChannelInstance(inst);
|
|
643
595
|
}
|
|
596
|
+
// Inject primary agent's trigger scheduler after all channels are registered so
|
|
597
|
+
// channelTypeMap is fully populated when setTriggerScheduler backfills old triggers.
|
|
598
|
+
// Seed __upgrade-check here too — needs channelInstances[0].channelType to be resolved.
|
|
599
|
+
const primaryAgentForTrigger = agentRegistry.runnableAgents()[0];
|
|
600
|
+
if (primaryAgentForTrigger?.triggerScheduler && primaryAgentForTrigger?.triggerManager) {
|
|
601
|
+
cmdHandler.setTriggerScheduler(primaryAgentForTrigger.triggerScheduler, primaryAgentForTrigger.triggerManager);
|
|
602
|
+
}
|
|
603
|
+
// Seed default __upgrade-check trigger (daily at random time 3:00~3:59)
|
|
604
|
+
// 用户可通过 /trigger cancel __upgrade-check 永久禁用(不会再自动重建)
|
|
605
|
+
if (!isLinkedInstall() && primaryAgentForTrigger?.triggerManager && primaryAgentForTrigger?.triggerScheduler) {
|
|
606
|
+
const mgr = primaryAgentForTrigger.triggerManager;
|
|
607
|
+
const sched = primaryAgentForTrigger.triggerScheduler;
|
|
608
|
+
const UPGRADE_TRIGGER_NAME = '__upgrade-check';
|
|
609
|
+
if (!mgr.getByName(UPGRADE_TRIGGER_NAME)) {
|
|
610
|
+
const { history } = mgr.listAll();
|
|
611
|
+
const wasCancelled = history.some(h => h.name === UPGRADE_TRIGGER_NAME && h.doneReason === 'cancelled');
|
|
612
|
+
if (!wasCancelled) {
|
|
613
|
+
const randomMinute = Math.floor(Math.random() * 60);
|
|
614
|
+
const cronExpr = `${randomMinute} 3 * * *`;
|
|
615
|
+
const firstChannelInst = channelInstances[0];
|
|
616
|
+
const firstChannel = firstChannelInst?.adapter?.channelName || 'system';
|
|
617
|
+
const trigger = {
|
|
618
|
+
id: crypto.randomUUID(),
|
|
619
|
+
name: UPGRADE_TRIGGER_NAME,
|
|
620
|
+
scheduleType: 'cron',
|
|
621
|
+
scheduleValue: cronExpr,
|
|
622
|
+
nextFireAt: calcNextFireAt('cron', cronExpr),
|
|
623
|
+
targetChannel: firstChannel,
|
|
624
|
+
targetChannelId: '__system__',
|
|
625
|
+
targetSessionStrategy: 'latest',
|
|
626
|
+
prompt: '检查 evolclaw 是否有新版本可用。执行 `npm view evolclaw version` 获取最新版本,与当前版本(执行 `evolclaw --version`)对比。如果有新版本,执行 /restart 进行升级。如果已是最新版本,无需任何操作。',
|
|
627
|
+
createdByPeerId: '__system__',
|
|
628
|
+
createdByChannel: '__system__',
|
|
629
|
+
fireCount: 0,
|
|
630
|
+
createdAt: Date.now(),
|
|
631
|
+
updatedAt: Date.now(),
|
|
632
|
+
};
|
|
633
|
+
try {
|
|
634
|
+
mgr.register(trigger);
|
|
635
|
+
sched.register(trigger);
|
|
636
|
+
logger.info(`[Trigger] Seeded default trigger: ${UPGRADE_TRIGGER_NAME} (cron ${cronExpr})`);
|
|
637
|
+
}
|
|
638
|
+
catch (e) {
|
|
639
|
+
logger.warn(`[Trigger] Failed to seed ${UPGRADE_TRIGGER_NAME}: ${e}`);
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
644
|
// Bind adapters to their owning agents and mark running
|
|
645
645
|
for (const inst of channelInstances) {
|
|
646
646
|
const agent = agentRegistry.resolveByChannel(inst.adapter.channelKey);
|
|
@@ -9,6 +9,9 @@ sessionKey: {{sessionKey}} # 会话路由键(channelType#urlEncode(channelId)#
|
|
|
9
9
|
sessionName: {{sessionName}}
|
|
10
10
|
{{/}}
|
|
11
11
|
sessionCreatedAt: {{sessionCreatedAt}}
|
|
12
|
+
{{?localDate}}
|
|
13
|
+
localDate: {{localDate}} {{weekday}} # 当前日期与星期(每条消息的精确时刻见消息正文前缀 ‹…›)
|
|
14
|
+
{{/}}
|
|
12
15
|
{{?timezone}}
|
|
13
16
|
timezone: {{timezone}} # 时区 IANA 名:把消息/记忆里的 ISO 时间戳转成本地时间字符串时按此换算
|
|
14
17
|
{{/}}
|
package/package.json
CHANGED