evolcore 0.0.3 → 0.0.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.
- package/CHANGELOG.md +83 -0
- package/README.md +45 -10
- package/bin/ec-safe-output.js +89 -24
- package/dist/agents/baseagent.js +46 -0
- package/dist/agents/claude-runner.js +1 -31
- package/dist/agents/codex-app-server-client.js +68 -0
- package/dist/agents/codex-runner.js +157 -5
- package/dist/agents/runner-types.js +2 -2
- package/dist/aun/aid/agentmd.js +79 -0
- package/dist/aun/aid/encryption-seed-policy.js +29 -0
- package/dist/aun/aid/index.js +1 -1
- package/dist/aun/aid/store.js +2 -5
- package/dist/channels/aun.js +220 -112
- package/dist/channels/daemon.js +18 -4
- package/dist/channels/dingtalk.js +52 -137
- package/dist/channels/feishu.js +56 -4
- package/dist/channels/qqbot.js +23 -1
- package/dist/channels/wechat.js +303 -155
- package/dist/channels/wecom.js +383 -7
- package/dist/cli/aun-commands.js +11 -11
- package/dist/cli/daemon-commands.js +46 -15
- package/dist/cli/handoff-command.js +2 -1
- package/dist/cli/init-channel.js +185 -67
- package/dist/cli/init.js +49 -27
- package/dist/cli/trigger-command.js +92 -11
- package/dist/config/access-policy-domain.js +18 -0
- package/dist/config/access-policy.js +110 -0
- package/dist/config/builtin-role-templates.js +27 -14
- package/dist/config/builtin-roles.js +25 -6
- package/dist/config/config-field-policy.js +17 -5
- package/dist/config/config-manager.js +198 -22
- package/dist/config/config-operation-service.js +35 -38
- package/dist/config/contact-bind-code.js +274 -0
- package/dist/config/contact-book-store.js +173 -5
- package/dist/config/contact-book.js +32 -0
- package/dist/config/contact-operation-service.js +9 -3
- package/dist/config/contact-request-service.js +331 -0
- package/dist/config/peer-role-resolver.js +3 -1
- package/dist/config/schema-registry.js +49 -24
- package/dist/config-store.js +14 -2
- package/dist/core/auth/agent-delegation.js +105 -11
- package/dist/core/auth/authorization-audit.js +6 -0
- package/dist/core/auth/operation-authorizer.js +8 -0
- package/dist/core/auth/operation-catalog.js +51 -3
- package/dist/core/auth/trigger-authorization.js +15 -0
- package/dist/core/bootstrap-service.js +2 -9
- package/dist/core/channel-loader.js +6 -2
- package/dist/core/command/command-handler.js +10 -13
- package/dist/core/command/connect-menu.js +229 -26
- package/dist/core/command/evol-menu-version-gate.js +38 -0
- package/dist/core/command/group-menu.js +421 -0
- package/dist/core/command/menu-handler.js +269 -85
- package/dist/core/command/menu-protocol.js +8 -2
- package/dist/core/command/menu-token-store.js +102 -0
- package/dist/core/command/role-menu.js +16 -0
- package/dist/core/command/slash-gate.js +1 -1
- package/dist/core/command/slash-handler.js +158 -31
- package/dist/core/daemon-file-cache.js +28 -0
- package/dist/core/event-catalog.js +29 -0
- package/dist/core/evolagent-registry.js +4 -39
- package/dist/core/handoff/runtime.js +162 -37
- package/dist/core/handoff/store.js +46 -2
- package/dist/core/handoff/types.js +1 -0
- package/dist/core/inference/text-inference.js +16 -74
- package/dist/core/message/file-markers.js +7 -0
- package/dist/core/message/im-renderer.js +18 -14
- package/dist/core/message/inbound-admission.js +134 -0
- package/dist/core/message/message-bridge.js +616 -74
- package/dist/core/message/message-log.js +1 -0
- package/dist/core/message/message-queue.js +185 -10
- package/dist/core/message/peer-mode.js +7 -8
- package/dist/core/message/response-engine.js +381 -79
- package/dist/core/permission/approval-gateway.js +17 -10
- package/dist/core/permission/ec-command-parser.js +101 -46
- package/dist/core/permission/tool-policy.js +69 -24
- package/dist/core/protected-paths.js +36 -11
- package/dist/core/session/session-fs-store.js +19 -4
- package/dist/core/session/session-manager.js +232 -4
- package/dist/core/session/session-mapper.js +2 -0
- package/dist/core/session/session-renew.js +125 -69
- package/dist/core/session/session-turn-coordinator.js +5 -1
- package/dist/eck/kit-renderer.js +12 -1
- package/dist/eck/message-renderer.js +79 -1
- package/dist/index.js +224 -89
- package/dist/ipc.js +81 -4
- package/dist/paths.js +3 -0
- package/dist/response-system/config-resolver.js +29 -0
- package/dist/response-system/coordinator.js +8 -32
- package/dist/response-system/engines/v1/proactive-flow.js +1 -1
- package/dist/response-system/index.js +1 -0
- package/dist/response-system/modes/single-session/index.js +7 -4
- package/dist/trigger/parser.js +55 -15
- package/dist/trigger/patch.js +4 -1
- package/dist/trigger/scheduler.js +441 -39
- package/dist/utils/cross-platform.js +8 -2
- package/dist/utils/error-dict.json +7 -0
- package/dist/utils/evolcore-version.js +21 -0
- package/dist/utils/logger.js +2 -2
- package/dist/utils/process-introspect.js +19 -3
- package/dist/utils/stable-semver.js +21 -0
- package/dist/utils/stats.js +33 -9
- package/kits/docs/channels/aun.md +4 -13
- package/kits/docs/evolcore/INDEX.md +1 -1
- package/kits/docs/evolcore/config.md +47 -2
- package/kits/docs/evolcore/contact.md +8 -3
- package/kits/docs/evolcore/group-rules.md +46 -4
- package/kits/docs/evolcore/group.md +4 -4
- package/kits/docs/evolcore/msg.md +5 -5
- package/kits/docs/evolcore/trigger.md +25 -8
- package/kits/docs/path-registry.md +36 -17
- package/kits/eck_message_manifest.json +12 -1
- package/kits/migrations/migrate-contact-book-v2.mjs +7 -0
- package/kits/rules/01-overview.md +17 -7
- package/kits/rules/02-navigation.md +38 -18
- package/kits/rules/03-identity.md +28 -24
- package/kits/rules/04-relation.md +44 -28
- package/kits/rules/05-venue.md +31 -15
- package/kits/rules/06-channel.md +11 -7
- package/kits/schemas/_meta.json +18 -7
- package/kits/schemas/agent-config.schema.7.json +303 -0
- package/kits/schemas/agent-config.schema.8.json +304 -0
- package/kits/schemas/agent-config.schema.9.json +364 -0
- package/kits/schemas/contact-book.schema.3.json +67 -0
- package/kits/schemas/daemon.schema.1.json +3 -2
- package/kits/schemas/daemon.schema.2.json +101 -0
- package/kits/schemas/daemon.schema.3.json +123 -0
- package/kits/schemas/defaults.schema.2.json +85 -0
- package/kits/schemas/defaults.schema.3.json +73 -0
- package/kits/schemas/relation-config.schema.6.json +46 -0
- package/kits/schemas/relation-config.schema.7.json +59 -0
- package/kits/schemas/single-session.schema.2.json +57 -0
- package/kits/templates/message-fragments/handoff-context-to-target.md +9 -0
- package/kits/templates/message-fragments/handoff-request-to-target.md +14 -8
- package/kits/templates/message-fragments/handoff-response-to-origin.md +5 -6
- package/kits/templates/roles/admin.json +46 -0
- package/kits/templates/roles/member.json +4 -0
- package/kits/templates/roles/visitor.json +4 -0
- package/kits/templates/system-fragments/channel.md +12 -2
- package/kits/templates/system-fragments/identity.md +3 -1
- package/kits/templates/system-fragments/relation.md +1 -1
- package/kits/templates/system-fragments/session.md +6 -2
- package/package.json +4 -2
- package/MIGRATION-0.5.0.md +0 -378
- package/ROLE_ACCESS_CONTROL.md +0 -174
- package/dist/channels/contact-bind-code.js +0 -134
- package/dist/channels/wecom-card.js +0 -101
- package/dist/channels/wecom-onboarding.js +0 -82
- package/dist/channels/wecom-state.js +0 -191
|
@@ -11,6 +11,7 @@ import { accumulateTriggerExecutionAnomalies, drainTriggerExecutionAnomalies, me
|
|
|
11
11
|
const MAX_TIMER_MS = 2_147_483_647;
|
|
12
12
|
const RUN_ABORT_SETTLE_GRACE_MS = 1_000;
|
|
13
13
|
const RUN_COMPLETION_BARRIER_MS = 1_500;
|
|
14
|
+
const TRIGGER_RESTART_RESUME_PROMPT = 'EvolCore 服务已重启。请基于当前会话记录继续完成刚才被中断的 Trigger 任务;不要重新从头执行已经完成的步骤。';
|
|
14
15
|
export class TriggerRuntimeScheduler {
|
|
15
16
|
manager;
|
|
16
17
|
state;
|
|
@@ -23,6 +24,7 @@ export class TriggerRuntimeScheduler {
|
|
|
23
24
|
timers = new Map();
|
|
24
25
|
running = new Map();
|
|
25
26
|
runStartLocks = new Map();
|
|
27
|
+
recoveredOneShotIds = new Set();
|
|
26
28
|
eventSource;
|
|
27
29
|
initialized = false;
|
|
28
30
|
stopping = false;
|
|
@@ -44,10 +46,14 @@ export class TriggerRuntimeScheduler {
|
|
|
44
46
|
async init() {
|
|
45
47
|
await this.stop();
|
|
46
48
|
this.stopping = false;
|
|
49
|
+
this.recoveredOneShotIds.clear();
|
|
47
50
|
await this.recoverOpenRuns();
|
|
48
51
|
for (const definition of this.manager.list()) {
|
|
52
|
+
if (this.recoveredOneShotIds.has(definition.id))
|
|
53
|
+
continue;
|
|
49
54
|
this.schedule(definition);
|
|
50
55
|
}
|
|
56
|
+
this.recoveredOneShotIds.clear();
|
|
51
57
|
this.initialized = true;
|
|
52
58
|
logger.info(`[Trigger] scheduler initialized for ${this.manager.agentAid}`);
|
|
53
59
|
}
|
|
@@ -70,6 +76,35 @@ export class TriggerRuntimeScheduler {
|
|
|
70
76
|
this.initialized = false;
|
|
71
77
|
await Promise.all(settlements);
|
|
72
78
|
}
|
|
79
|
+
/**
|
|
80
|
+
* Stop process-local work while keeping durable runs open for the next daemon.
|
|
81
|
+
* Explicit scheduler replacement/unload must continue to use stop().
|
|
82
|
+
*/
|
|
83
|
+
async suspendForDaemonExit() {
|
|
84
|
+
this.stopping = true;
|
|
85
|
+
for (const timer of this.timers.values())
|
|
86
|
+
clearTimeout(timer);
|
|
87
|
+
this.timers.clear();
|
|
88
|
+
this.eventSource?.stop();
|
|
89
|
+
const settlements = [];
|
|
90
|
+
for (const runs of Array.from(this.running.values())) {
|
|
91
|
+
for (const [runId, runtime] of Array.from(runs.entries())) {
|
|
92
|
+
const durableRun = this.state.list(runtime.definition.id).find(candidate => candidate.runId === runId);
|
|
93
|
+
if (durableRun)
|
|
94
|
+
runtime.run = durableRun;
|
|
95
|
+
const resumable = !!durableRun && this.isResumableCheckpoint(runtime.definition, durableRun);
|
|
96
|
+
settlements.push(this.terminateRun(runId, runtime, {
|
|
97
|
+
reason: resumable ? 'suspended' : 'stopped',
|
|
98
|
+
message: resumable
|
|
99
|
+
? 'trigger scheduler stopped for daemon restart'
|
|
100
|
+
: 'trigger scheduler stopped',
|
|
101
|
+
}));
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
settlements.push(...this.runStartLocks.values());
|
|
105
|
+
this.initialized = false;
|
|
106
|
+
await Promise.all(settlements);
|
|
107
|
+
}
|
|
73
108
|
async waitForIdle(timeoutMs = 5_000) {
|
|
74
109
|
const deadline = Date.now() + timeoutMs;
|
|
75
110
|
while ((this.running.size > 0 || this.runStartLocks.size > 0) && Date.now() < deadline) {
|
|
@@ -341,7 +376,9 @@ export class TriggerRuntimeScheduler {
|
|
|
341
376
|
await runPromise;
|
|
342
377
|
return;
|
|
343
378
|
}
|
|
344
|
-
await runPromise;
|
|
379
|
+
const result = await runPromise;
|
|
380
|
+
if (result.status === 'running' && result.reason === 'daemon_restart_resume_pending')
|
|
381
|
+
return;
|
|
345
382
|
const fresh = this.manager.get(triggerId);
|
|
346
383
|
if (fresh?.enabled && definitionRevision(fresh) === firedRevision) {
|
|
347
384
|
this.setEnabled(triggerId, false);
|
|
@@ -415,6 +452,9 @@ export class TriggerRuntimeScheduler {
|
|
|
415
452
|
triggerId: definition.id,
|
|
416
453
|
runId,
|
|
417
454
|
startedAt,
|
|
455
|
+
definitionRevision: definitionRevision(definition),
|
|
456
|
+
source,
|
|
457
|
+
resumeCount: 0,
|
|
418
458
|
events: [{ seq: 0, event: 'run.started', ts: startedAt }],
|
|
419
459
|
};
|
|
420
460
|
if (!payload.dryRun) {
|
|
@@ -476,9 +516,12 @@ export class TriggerRuntimeScheduler {
|
|
|
476
516
|
if (runningRuntime)
|
|
477
517
|
runningRuntime.anomalies = executionAnomalies;
|
|
478
518
|
if (!payload.dryRun && controller.signal.aborted) {
|
|
479
|
-
const reason = triggerCancellationReason(controller.signal);
|
|
480
519
|
if (this.isRunning(definition.id, runId))
|
|
481
|
-
this.
|
|
520
|
+
this.finishAbortedRun(runningRuntime, definition.id, runId);
|
|
521
|
+
const suspended = this.suspendedRuntimeResult(runningRuntime, definition.id, runId);
|
|
522
|
+
if (suspended)
|
|
523
|
+
return suspended;
|
|
524
|
+
const reason = triggerCancellationReason(controller.signal);
|
|
482
525
|
return {
|
|
483
526
|
ok: false,
|
|
484
527
|
runId,
|
|
@@ -524,9 +567,12 @@ export class TriggerRuntimeScheduler {
|
|
|
524
567
|
signal: controller.signal,
|
|
525
568
|
}), controller.signal, RUN_ABORT_SETTLE_GRACE_MS, 'feedback');
|
|
526
569
|
if (!payload.dryRun && controller.signal.aborted) {
|
|
527
|
-
const reason = triggerCancellationReason(controller.signal);
|
|
528
570
|
if (this.isRunning(definition.id, runId))
|
|
529
|
-
this.
|
|
571
|
+
this.finishAbortedRun(runningRuntime, definition.id, runId);
|
|
572
|
+
const suspended = this.suspendedRuntimeResult(runningRuntime, definition.id, runId);
|
|
573
|
+
if (suspended)
|
|
574
|
+
return suspended;
|
|
575
|
+
const reason = triggerCancellationReason(controller.signal);
|
|
530
576
|
return {
|
|
531
577
|
ok: false,
|
|
532
578
|
runId,
|
|
@@ -581,9 +627,12 @@ export class TriggerRuntimeScheduler {
|
|
|
581
627
|
executionAnomalies = mergeTriggerExecutionAnomalies(executionAnomalies, executionAnomaliesFromError(err));
|
|
582
628
|
const message = err?.message || String(err);
|
|
583
629
|
if (!payload.dryRun && controller.signal.aborted) {
|
|
584
|
-
const reason = triggerCancellationReason(controller.signal);
|
|
585
630
|
if (this.isRunning(definition.id, runId))
|
|
586
|
-
this.
|
|
631
|
+
this.finishAbortedRun(runningRuntime, definition.id, runId);
|
|
632
|
+
const suspended = this.suspendedRuntimeResult(runningRuntime, definition.id, runId);
|
|
633
|
+
if (suspended)
|
|
634
|
+
return suspended;
|
|
635
|
+
const reason = triggerCancellationReason(controller.signal);
|
|
587
636
|
return {
|
|
588
637
|
ok: false,
|
|
589
638
|
runId,
|
|
@@ -740,17 +789,33 @@ export class TriggerRuntimeScheduler {
|
|
|
740
789
|
+ ` baseagent=${runtimeBaseagent}`
|
|
741
790
|
+ ` model=${definition.execution.model ?? '<agent-default>'}`
|
|
742
791
|
+ ` effort=${definition.execution.effort ?? '<agent-default>'}`);
|
|
743
|
-
const
|
|
792
|
+
const attemptId = `${runId}:attempt:${attempt}`;
|
|
793
|
+
const executionDeadlineAt = this.daemonExecutionDeadlineAt(Date.now());
|
|
794
|
+
const executionSession = await this.daemonChannel.getOrCreateConversationSession(definition, this.runtime.projectPath, runtimeBaseagent, runId, attemptId);
|
|
795
|
+
const activeRun = this.state.setPhase?.(definition.id, runId, 'running', {
|
|
796
|
+
attempt,
|
|
797
|
+
attemptId,
|
|
798
|
+
executionSessionId: executionSession.id,
|
|
799
|
+
executionDeadlineAt,
|
|
800
|
+
});
|
|
801
|
+
this.state.appendEvent?.(definition.id, runId, {
|
|
802
|
+
event: 'execution.attempt.started',
|
|
803
|
+
ts: Date.now(),
|
|
804
|
+
attempt,
|
|
805
|
+
attemptId,
|
|
806
|
+
executionSessionId: executionSession.id,
|
|
807
|
+
});
|
|
744
808
|
const daemonReply = await this.daemonChannel.converse(prompt, {
|
|
745
809
|
trigger: definition,
|
|
746
810
|
runId,
|
|
747
|
-
attemptId
|
|
811
|
+
attemptId,
|
|
748
812
|
firedAt,
|
|
749
813
|
projectPath: this.runtime.projectPath,
|
|
750
814
|
baseagent: runtimeBaseagent,
|
|
751
815
|
session: executionSession,
|
|
752
816
|
causation,
|
|
753
817
|
signal,
|
|
818
|
+
executionDeadlineAt: activeRun?.executionDeadlineAt ?? executionDeadlineAt,
|
|
754
819
|
onStateChange: (state, metadata) => {
|
|
755
820
|
if (state === 'requires_action') {
|
|
756
821
|
this.state.setPhase(definition.id, runId, 'requires-action');
|
|
@@ -834,41 +899,338 @@ export class TriggerRuntimeScheduler {
|
|
|
834
899
|
if (activeRuns.length === 0)
|
|
835
900
|
continue;
|
|
836
901
|
for (const run of activeRuns) {
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
902
|
+
if (await this.reclaimOpenRun(definition, run))
|
|
903
|
+
continue;
|
|
904
|
+
await this.closeOpenRunAfterRestart(definition, run);
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
async reclaimOpenRun(definition, run) {
|
|
909
|
+
if (!definition.enabled || !this.isResumableCheckpoint(definition, run))
|
|
910
|
+
return false;
|
|
911
|
+
const executionAuthorization = this.runtime.authorizeExecution?.(definition);
|
|
912
|
+
if (executionAuthorization && !executionAuthorization.allowed)
|
|
913
|
+
return false;
|
|
914
|
+
const session = await this.daemonChannel.getResumableConversationSession(run.executionSessionId);
|
|
915
|
+
if (!session) {
|
|
916
|
+
logger.warn(`[Trigger] resumable session unavailable: trigger=${definition.id} run=${run.runId} session=${run.executionSessionId}`);
|
|
917
|
+
return false;
|
|
918
|
+
}
|
|
919
|
+
const entered = enterTrigger(undefined, definition.id, run.runId);
|
|
920
|
+
if (!entered.ok)
|
|
921
|
+
return false;
|
|
922
|
+
const causation = entered.causation;
|
|
923
|
+
const controller = new AbortController();
|
|
924
|
+
let settleCompletion;
|
|
925
|
+
const completion = new Promise(resolve => { settleCompletion = resolve; });
|
|
926
|
+
const runtime = {
|
|
927
|
+
run,
|
|
928
|
+
definition,
|
|
929
|
+
source: run.source,
|
|
930
|
+
causation,
|
|
931
|
+
controller,
|
|
932
|
+
completion,
|
|
933
|
+
anomalies: [],
|
|
934
|
+
};
|
|
935
|
+
this.addRunning(definition.id, run.runId, runtime);
|
|
936
|
+
if (this.isOneShot(definition.source))
|
|
937
|
+
this.recoveredOneShotIds.add(definition.id);
|
|
938
|
+
const resumeCount = (run.resumeCount ?? 0) + 1;
|
|
939
|
+
const patched = this.state.setPhase(definition.id, run.runId, 'running', { resumeCount });
|
|
940
|
+
if (patched)
|
|
941
|
+
runtime.run = patched;
|
|
942
|
+
this.state.appendEvent(definition.id, run.runId, {
|
|
943
|
+
event: 'execution.recovered',
|
|
944
|
+
ts: Date.now(),
|
|
945
|
+
attempt: run.attempt,
|
|
946
|
+
attemptId: run.attemptId,
|
|
947
|
+
executionSessionId: session.id,
|
|
948
|
+
resumeCount,
|
|
949
|
+
});
|
|
950
|
+
recordCausationSpan(causation, 'trigger.run', {
|
|
951
|
+
status: 'started',
|
|
952
|
+
refs: { triggerId: definition.id, runId: run.runId },
|
|
953
|
+
reason: 'daemon_restart_resume',
|
|
954
|
+
});
|
|
955
|
+
void this.continueRecoveredRun(definition, runtime, session)
|
|
956
|
+
.then(settleCompletion, error => {
|
|
957
|
+
const anomalies = executionAnomaliesFromError(error) ?? [];
|
|
958
|
+
runtime.anomalies = anomalies;
|
|
959
|
+
if (this.isRunning(definition.id, run.runId))
|
|
960
|
+
this.finishAbortedRun(runtime, definition.id, run.runId);
|
|
961
|
+
logger.error(`[Trigger] recovered run crashed: trigger=${definition.id} run=${run.runId} error=${error instanceof Error ? error.message : String(error)}`);
|
|
962
|
+
settleCompletion({ anomalies });
|
|
963
|
+
});
|
|
964
|
+
logger.info(`[Trigger] resumed open run: trigger=${definition.id} run=${run.runId} session=${session.id} attempt=${run.attempt} resume=${resumeCount}`);
|
|
965
|
+
return true;
|
|
966
|
+
}
|
|
967
|
+
isResumableCheckpoint(definition, run) {
|
|
968
|
+
return definition.execution.type === 'trigger_session'
|
|
969
|
+
&& run.phase === 'running'
|
|
970
|
+
&& !!run.source
|
|
971
|
+
&& Number.isFinite(run.source.firedAt)
|
|
972
|
+
&& isRecord(run.source.payload)
|
|
973
|
+
&& Number.isInteger(run.attempt)
|
|
974
|
+
&& run.attempt >= 1
|
|
975
|
+
&& typeof run.attemptId === 'string'
|
|
976
|
+
&& !!run.attemptId
|
|
977
|
+
&& typeof run.executionSessionId === 'string'
|
|
978
|
+
&& !!run.executionSessionId
|
|
979
|
+
&& typeof run.executionDeadlineAt === 'number'
|
|
980
|
+
&& Number.isFinite(run.executionDeadlineAt)
|
|
981
|
+
&& run.executionDeadlineAt > Date.now()
|
|
982
|
+
&& run.definitionRevision === definitionRevision(definition);
|
|
983
|
+
}
|
|
984
|
+
async continueRecoveredRun(definition, runtime, session) {
|
|
985
|
+
const { run, source, controller, causation } = runtime;
|
|
986
|
+
const runId = run.runId;
|
|
987
|
+
let processing = null;
|
|
988
|
+
let reply = null;
|
|
989
|
+
let anomalies = [];
|
|
990
|
+
let incomplete;
|
|
991
|
+
try {
|
|
992
|
+
const originalPrompt = renderTemplate(definition.execution.prompt ?? '', {
|
|
993
|
+
trigger: definition,
|
|
994
|
+
timestamp: source.firedAt,
|
|
995
|
+
event: source.payload,
|
|
996
|
+
source: { type: definition.source.type, payload: source.payload },
|
|
997
|
+
});
|
|
998
|
+
processing = {
|
|
999
|
+
mode: definition.execution.type,
|
|
1000
|
+
renderedTextHash: sha256(originalPrompt),
|
|
1001
|
+
renderedTextPreview: previewText(originalPrompt),
|
|
1002
|
+
};
|
|
1003
|
+
const execution = await awaitWithAbortSettleGrace(this.runRecoveredExecution(definition, runtime, session), controller.signal, RUN_ABORT_SETTLE_GRACE_MS, 'execution');
|
|
1004
|
+
reply = execution.reply ?? null;
|
|
1005
|
+
anomalies = execution.anomalies ?? [];
|
|
1006
|
+
runtime.anomalies = anomalies;
|
|
1007
|
+
if (controller.signal.aborted) {
|
|
1008
|
+
if (this.isRunning(definition.id, runId))
|
|
1009
|
+
this.finishAbortedRun(runtime, definition.id, runId);
|
|
1010
|
+
return { anomalies };
|
|
1011
|
+
}
|
|
1012
|
+
const executionFailed = execution.reply
|
|
1013
|
+
? shouldRetryExecution(execution.reply)
|
|
1014
|
+
: execution.feedbackResult?.status === 'failed';
|
|
1015
|
+
this.state.appendEvent(definition.id, runId, {
|
|
1016
|
+
event: executionFailed ? 'execution.failed' : 'execution.completed',
|
|
1017
|
+
ts: Date.now(),
|
|
1018
|
+
mode: definition.execution.type,
|
|
1019
|
+
outcome: execution.reply?.outcome ?? execution.feedbackResult?.status,
|
|
1020
|
+
...(anomalies.length ? { anomalyCount: anomalies.length } : {}),
|
|
1021
|
+
});
|
|
1022
|
+
if (!execution.feedbackResult) {
|
|
1023
|
+
this.state.setPhase(definition.id, runId, 'feedback-pending', {
|
|
1024
|
+
deadlineAt: Date.now() + 30_000,
|
|
850
1025
|
});
|
|
851
|
-
this.
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
1026
|
+
this.state.appendEvent(definition.id, runId, {
|
|
1027
|
+
event: 'feedback.pending',
|
|
1028
|
+
ts: Date.now(),
|
|
1029
|
+
branch: execution.branch,
|
|
1030
|
+
});
|
|
1031
|
+
}
|
|
1032
|
+
const feedbackResult = execution.feedbackResult ?? await awaitWithAbortSettleGrace(this.feedback.dispatch({
|
|
1033
|
+
trigger: definition,
|
|
1034
|
+
runId,
|
|
1035
|
+
firedAt: source.firedAt,
|
|
1036
|
+
branch: execution.branch,
|
|
1037
|
+
reply: execution.reply,
|
|
1038
|
+
sourcePayload: source.payload,
|
|
1039
|
+
causation,
|
|
1040
|
+
signal: controller.signal,
|
|
1041
|
+
}), controller.signal, RUN_ABORT_SETTLE_GRACE_MS, 'feedback');
|
|
1042
|
+
if (controller.signal.aborted) {
|
|
1043
|
+
if (this.isRunning(definition.id, runId))
|
|
1044
|
+
this.finishAbortedRun(runtime, definition.id, runId);
|
|
1045
|
+
return { anomalies };
|
|
1046
|
+
}
|
|
1047
|
+
const audit = this.buildAudit({
|
|
1048
|
+
definition,
|
|
1049
|
+
runId,
|
|
1050
|
+
startedAt: run.startedAt,
|
|
1051
|
+
status: statusWithExecutionAnomalies(feedbackResult.status, anomalies),
|
|
1052
|
+
reason: feedbackResult.reason,
|
|
1053
|
+
source,
|
|
1054
|
+
processing,
|
|
1055
|
+
script: null,
|
|
1056
|
+
reply,
|
|
1057
|
+
feedback: feedbackResult.feedback,
|
|
1058
|
+
anomalies,
|
|
1059
|
+
effects: feedbackResult.effects,
|
|
1060
|
+
error: feedbackResult.error,
|
|
1061
|
+
causation,
|
|
1062
|
+
});
|
|
1063
|
+
this.audit.write(audit);
|
|
1064
|
+
this.publishTriggerRunOutcome(definition, audit);
|
|
1065
|
+
this.finishRecoveredRun(definition, runId);
|
|
1066
|
+
recordCausationSpan(causation, 'trigger.run', {
|
|
1067
|
+
status: audit.status === 'failed' ? 'failed' : 'completed',
|
|
1068
|
+
refs: { triggerId: definition.id, runId },
|
|
1069
|
+
reason: audit.reason,
|
|
1070
|
+
});
|
|
1071
|
+
return { anomalies };
|
|
1072
|
+
}
|
|
1073
|
+
catch (error) {
|
|
1074
|
+
if (error instanceof AbortSettleGraceError) {
|
|
1075
|
+
incomplete = {
|
|
1076
|
+
reason: 'abort_settle_grace_exceeded',
|
|
1077
|
+
phase: error.phase,
|
|
1078
|
+
timeoutMs: error.graceMs,
|
|
1079
|
+
anomalyCapture: 'truncated',
|
|
1080
|
+
};
|
|
1081
|
+
}
|
|
1082
|
+
anomalies = mergeTriggerExecutionAnomalies(anomalies, executionAnomaliesFromError(error));
|
|
1083
|
+
runtime.anomalies = anomalies;
|
|
1084
|
+
if (controller.signal.aborted) {
|
|
1085
|
+
if (this.isRunning(definition.id, runId))
|
|
1086
|
+
this.finishAbortedRun(runtime, definition.id, runId);
|
|
1087
|
+
return { anomalies, ...(incomplete ? { incomplete } : {}) };
|
|
1088
|
+
}
|
|
1089
|
+
const message = error?.message || String(error);
|
|
1090
|
+
const audit = this.buildAudit({
|
|
1091
|
+
definition,
|
|
1092
|
+
runId,
|
|
1093
|
+
startedAt: run.startedAt,
|
|
1094
|
+
status: 'failed',
|
|
1095
|
+
reason: 'daemon_error',
|
|
1096
|
+
source,
|
|
1097
|
+
processing,
|
|
1098
|
+
script: null,
|
|
1099
|
+
reply,
|
|
1100
|
+
feedback: null,
|
|
1101
|
+
anomalies,
|
|
1102
|
+
incomplete,
|
|
1103
|
+
effects: [],
|
|
1104
|
+
error: { code: 'daemon_error', message },
|
|
1105
|
+
causation,
|
|
1106
|
+
});
|
|
1107
|
+
this.audit.write(audit);
|
|
1108
|
+
this.publishTriggerRunOutcome(definition, audit);
|
|
1109
|
+
this.finishRecoveredRun(definition, runId);
|
|
1110
|
+
recordCausationSpan(causation, 'trigger.run', {
|
|
1111
|
+
status: 'failed',
|
|
1112
|
+
refs: { triggerId: definition.id, runId },
|
|
1113
|
+
reason: 'daemon_error',
|
|
1114
|
+
});
|
|
1115
|
+
return { anomalies, ...(incomplete ? { incomplete } : {}) };
|
|
1116
|
+
}
|
|
1117
|
+
}
|
|
1118
|
+
async runRecoveredExecution(definition, runtime, session) {
|
|
1119
|
+
const { run, source, controller, causation } = runtime;
|
|
1120
|
+
let attempt = run.attempt;
|
|
1121
|
+
let execution = await this.runRecoveredExecutionAttempt(definition, runtime, session);
|
|
1122
|
+
let anomalies = accumulateTriggerExecutionAnomalies([], mergeTriggerExecutionAnomalies(execution.reply?.anomalies, drainTriggerExecutionAnomalies(run.runId)));
|
|
1123
|
+
runtime.anomalies = anomalies;
|
|
1124
|
+
const maxAttempt = 1 + (definition.execution.onError === 'retry'
|
|
1125
|
+
? definition.reliability.retry.maxAttempts
|
|
1126
|
+
: 0);
|
|
1127
|
+
while (shouldRetryExecutionResult(execution) && attempt < maxAttempt && !controller.signal.aborted) {
|
|
1128
|
+
const backoffCompleted = await sleep(definition.reliability.retry.backoffMs, controller.signal);
|
|
1129
|
+
if (!backoffCompleted || controller.signal.aborted)
|
|
1130
|
+
break;
|
|
1131
|
+
attempt += 1;
|
|
1132
|
+
execution = await this.runExecutionAttempt(definition, run.runId, source.firedAt, source.payload, controller.signal, false, causation, attempt);
|
|
1133
|
+
anomalies = accumulateTriggerExecutionAnomalies(anomalies, mergeTriggerExecutionAnomalies(execution.reply?.anomalies, drainTriggerExecutionAnomalies(run.runId)));
|
|
1134
|
+
runtime.anomalies = anomalies;
|
|
1135
|
+
}
|
|
1136
|
+
if (execution.reply && anomalies.length) {
|
|
1137
|
+
execution = { ...execution, reply: { ...execution.reply, anomalies } };
|
|
1138
|
+
}
|
|
1139
|
+
return anomalies.length ? { ...execution, anomalies } : execution;
|
|
1140
|
+
}
|
|
1141
|
+
async runRecoveredExecutionAttempt(definition, runtime, session) {
|
|
1142
|
+
const { run, source, controller, causation } = runtime;
|
|
1143
|
+
try {
|
|
1144
|
+
const daemonReply = await this.daemonChannel.converse(TRIGGER_RESTART_RESUME_PROMPT, {
|
|
1145
|
+
trigger: definition,
|
|
1146
|
+
runId: run.runId,
|
|
1147
|
+
attemptId: run.attemptId,
|
|
1148
|
+
firedAt: source.firedAt,
|
|
1149
|
+
projectPath: session.projectPath,
|
|
1150
|
+
baseagent: session.baseagent,
|
|
1151
|
+
session,
|
|
1152
|
+
causation,
|
|
1153
|
+
signal: controller.signal,
|
|
1154
|
+
executionDeadlineAt: run.executionDeadlineAt,
|
|
1155
|
+
onStateChange: (state, metadata) => {
|
|
1156
|
+
if (state === 'requires_action') {
|
|
1157
|
+
this.state.setPhase(definition.id, run.runId, 'requires-action');
|
|
1158
|
+
this.state.appendEvent(definition.id, run.runId, {
|
|
1159
|
+
event: 'execution.requires_action',
|
|
1160
|
+
ts: Date.now(),
|
|
1161
|
+
requestId: metadata?.requestId,
|
|
1162
|
+
expiresAt: metadata?.expiresAt,
|
|
863
1163
|
});
|
|
864
1164
|
}
|
|
865
|
-
|
|
866
|
-
|
|
1165
|
+
else {
|
|
1166
|
+
this.state.setPhase(definition.id, run.runId, 'running');
|
|
1167
|
+
this.state.appendEvent(definition.id, run.runId, {
|
|
1168
|
+
event: 'execution.resumed',
|
|
1169
|
+
ts: Date.now(),
|
|
1170
|
+
requestId: metadata?.requestId,
|
|
1171
|
+
});
|
|
867
1172
|
}
|
|
868
|
-
}
|
|
1173
|
+
},
|
|
1174
|
+
});
|
|
1175
|
+
const sentinel = definition.execution.noopSentinel ?? '[[NOOP]]';
|
|
1176
|
+
const reply = {
|
|
1177
|
+
...daemonReply,
|
|
1178
|
+
outcome: daemonReply.outcome === 'success' && daemonReply.text.trim() === sentinel ? 'noop' : daemonReply.outcome,
|
|
1179
|
+
text: daemonReply.text.trim() === sentinel ? '' : daemonReply.text,
|
|
1180
|
+
};
|
|
1181
|
+
return {
|
|
1182
|
+
branch: branchFromReply(reply),
|
|
1183
|
+
script: null,
|
|
1184
|
+
processing: { mode: definition.execution.type },
|
|
1185
|
+
reply,
|
|
1186
|
+
};
|
|
1187
|
+
}
|
|
1188
|
+
catch (error) {
|
|
1189
|
+
const reply = errorReply(run.runId, 0, 'agent_execution_error', error?.message || String(error));
|
|
1190
|
+
return {
|
|
1191
|
+
branch: 'onFailure',
|
|
1192
|
+
script: null,
|
|
1193
|
+
processing: { mode: definition.execution.type },
|
|
1194
|
+
reply,
|
|
1195
|
+
};
|
|
1196
|
+
}
|
|
1197
|
+
}
|
|
1198
|
+
async closeOpenRunAfterRestart(definition, run) {
|
|
1199
|
+
const status = run.phase === 'feedback-pending' ? 'skipped' : 'failed';
|
|
1200
|
+
const source = run.source
|
|
1201
|
+
?? this.buildSourceInfo(definition.source, run.startedAt, run.startedAt, this.sourcePayload(definition.source));
|
|
1202
|
+
const audit = this.buildAudit({
|
|
1203
|
+
definition,
|
|
1204
|
+
runId: run.runId,
|
|
1205
|
+
startedAt: run.startedAt,
|
|
1206
|
+
status,
|
|
1207
|
+
reason: 'daemon_restart',
|
|
1208
|
+
source,
|
|
1209
|
+
script: null,
|
|
1210
|
+
reply: null,
|
|
1211
|
+
feedback: null,
|
|
1212
|
+
effects: [],
|
|
1213
|
+
error: status === 'failed' ? { code: 'daemon_restart', message: 'daemon restarted while run was open' } : null,
|
|
1214
|
+
});
|
|
1215
|
+
this.audit.write(audit);
|
|
1216
|
+
if (status === 'failed') {
|
|
1217
|
+
const reply = errorReply(run.runId, Math.max(0, Date.now() - run.startedAt), 'daemon_restart', 'daemon restarted while run was waiting or executing');
|
|
1218
|
+
try {
|
|
1219
|
+
await this.feedback.dispatch({
|
|
1220
|
+
trigger: definition,
|
|
1221
|
+
runId: run.runId,
|
|
1222
|
+
firedAt: source.firedAt,
|
|
1223
|
+
branch: 'onFailure',
|
|
1224
|
+
reply,
|
|
1225
|
+
sourcePayload: source.payload,
|
|
1226
|
+
causation: createRootCausation(),
|
|
1227
|
+
});
|
|
1228
|
+
}
|
|
1229
|
+
catch (error) {
|
|
1230
|
+
logger.warn(`[Trigger] daemon restart feedback failed: trigger=${definition.id} run=${run.runId} error=${error instanceof Error ? error.message : String(error)}`);
|
|
869
1231
|
}
|
|
870
|
-
this.state.clear(definition.id);
|
|
871
1232
|
}
|
|
1233
|
+
this.state.remove(definition.id, run.runId);
|
|
872
1234
|
}
|
|
873
1235
|
resolveScheduledAt(definition) {
|
|
874
1236
|
const now = Date.now();
|
|
@@ -1102,6 +1464,13 @@ export class TriggerRuntimeScheduler {
|
|
|
1102
1464
|
}
|
|
1103
1465
|
async settleTerminatedRun(runId, runtime, termination) {
|
|
1104
1466
|
const completion = await settleWithin(runtime.completion, RUN_COMPLETION_BARRIER_MS);
|
|
1467
|
+
if (termination.reason === 'suspended') {
|
|
1468
|
+
if (!completion.settled) {
|
|
1469
|
+
logger.warn(`[Trigger] suspended run did not settle before daemon exit: trigger=${runtime.definition.id} run=${runId} timeoutMs=${RUN_COMPLETION_BARRIER_MS}`);
|
|
1470
|
+
}
|
|
1471
|
+
this.removeRunning(runtime.definition.id, runId);
|
|
1472
|
+
return;
|
|
1473
|
+
}
|
|
1105
1474
|
const completedAnomalies = mergeTriggerExecutionAnomalies(runtime.anomalies, completion.settled ? completion.value.anomalies : undefined);
|
|
1106
1475
|
const anomalies = accumulateTriggerExecutionAnomalies(completedAnomalies, drainTriggerExecutionAnomalies(runId));
|
|
1107
1476
|
if (!completion.settled) {
|
|
@@ -1148,16 +1517,49 @@ export class TriggerRuntimeScheduler {
|
|
|
1148
1517
|
}
|
|
1149
1518
|
this.finishRun(runtime.definition.id, runId);
|
|
1150
1519
|
}
|
|
1151
|
-
|
|
1520
|
+
finishAbortedRun(runtime, triggerId, runId) {
|
|
1521
|
+
if (runtime?.termination?.reason === 'suspended') {
|
|
1522
|
+
this.removeRunning(triggerId, runId);
|
|
1523
|
+
return;
|
|
1524
|
+
}
|
|
1525
|
+
this.finishRun(triggerId, runId);
|
|
1526
|
+
}
|
|
1527
|
+
suspendedRuntimeResult(runtime, triggerId, runId) {
|
|
1528
|
+
if (runtime?.termination?.reason !== 'suspended')
|
|
1529
|
+
return undefined;
|
|
1530
|
+
return {
|
|
1531
|
+
ok: true,
|
|
1532
|
+
runId,
|
|
1533
|
+
triggerId,
|
|
1534
|
+
status: 'running',
|
|
1535
|
+
reason: 'daemon_restart_resume_pending',
|
|
1536
|
+
};
|
|
1537
|
+
}
|
|
1538
|
+
removeRunning(triggerId, runId) {
|
|
1152
1539
|
const runs = this.running.get(triggerId);
|
|
1153
1540
|
runs?.delete(runId);
|
|
1154
1541
|
if (runs && runs.size === 0)
|
|
1155
1542
|
this.running.delete(triggerId);
|
|
1543
|
+
}
|
|
1544
|
+
finishRun(triggerId, runId) {
|
|
1545
|
+
this.removeRunning(triggerId, runId);
|
|
1156
1546
|
this.state.remove(triggerId, runId);
|
|
1157
1547
|
}
|
|
1548
|
+
finishRecoveredRun(definition, runId) {
|
|
1549
|
+
this.finishRun(definition.id, runId);
|
|
1550
|
+
if (!this.isOneShot(definition.source))
|
|
1551
|
+
return;
|
|
1552
|
+
const fresh = this.manager.get(definition.id);
|
|
1553
|
+
if (fresh?.enabled && definitionRevision(fresh) === definitionRevision(definition)) {
|
|
1554
|
+
this.setEnabled(definition.id, false);
|
|
1555
|
+
}
|
|
1556
|
+
}
|
|
1158
1557
|
isRunning(triggerId, runId) {
|
|
1159
1558
|
return this.running.get(triggerId)?.has(runId) === true;
|
|
1160
1559
|
}
|
|
1560
|
+
daemonExecutionDeadlineAt(startedAt) {
|
|
1561
|
+
return this.daemonChannel.executionDeadlineAt?.(startedAt) ?? startedAt + 60 * 60 * 1_000;
|
|
1562
|
+
}
|
|
1161
1563
|
clearTimer(triggerId) {
|
|
1162
1564
|
const timer = this.timers.get(triggerId);
|
|
1163
1565
|
if (timer)
|
|
@@ -3,6 +3,7 @@ import { fileURLToPath } from 'url';
|
|
|
3
3
|
import { execFileSync, execFile, spawn, spawnSync } from 'child_process';
|
|
4
4
|
import { promisify } from 'util';
|
|
5
5
|
import fs from 'fs';
|
|
6
|
+
import { getProcessStartTime } from './process-introspect.js';
|
|
6
7
|
const execFileAsync = promisify(execFile);
|
|
7
8
|
export const isWindows = process.platform === 'win32';
|
|
8
9
|
const ENCODE_PATH_MAX = 200;
|
|
@@ -133,10 +134,15 @@ export function getProcessInfo(pid) {
|
|
|
133
134
|
}
|
|
134
135
|
}
|
|
135
136
|
else {
|
|
136
|
-
|
|
137
|
+
// procps `etimes` can overflow for freshly spawned processes in some
|
|
138
|
+
// container/time-namespace combinations. /proc start time is reliable
|
|
139
|
+
// on Linux and is already used for PID reuse protection elsewhere.
|
|
140
|
+
const startedAt = process.platform === 'linux' ? getProcessStartTime(pid) : null;
|
|
141
|
+
const uptime = startedAt !== null
|
|
142
|
+
? formatUptime(Math.max(0, Math.floor((Date.now() - startedAt) / 1000)))
|
|
143
|
+
: formatUptime(parseInt(execFileSync('ps', ['-p', String(pid), '-o', 'etimes='], { encoding: 'utf-8' }).trim(), 10));
|
|
137
144
|
const cpu = execFileSync('ps', ['-p', String(pid), '-o', '%cpu='], { encoding: 'utf-8' }).trim();
|
|
138
145
|
const mem = execFileSync('ps', ['-p', String(pid), '-o', 'rss='], { encoding: 'utf-8' }).trim();
|
|
139
|
-
const uptime = formatUptime(parseInt(etimes, 10));
|
|
140
146
|
return { uptime, cpu, memory: mem };
|
|
141
147
|
}
|
|
142
148
|
}
|
|
@@ -56,6 +56,13 @@
|
|
|
56
56
|
"type": "api_error",
|
|
57
57
|
"message": "⚠️ 当前算力池切换中,请稍后重试"
|
|
58
58
|
},
|
|
59
|
+
{
|
|
60
|
+
"id": "retry-attempt-stalled",
|
|
61
|
+
"match": "retry_attempt_stalled",
|
|
62
|
+
"action": "retry",
|
|
63
|
+
"type": "api_error",
|
|
64
|
+
"message": "⚠️ API 重试调用长时间无响应"
|
|
65
|
+
},
|
|
59
66
|
{
|
|
60
67
|
"id": "selected-model-at-capacity",
|
|
61
68
|
"match": "selected model is at capacity",
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { getPackageRoot } from '../paths.js';
|
|
4
|
+
let cachedVersion;
|
|
5
|
+
export function readInstalledEvolcoreVersion() {
|
|
6
|
+
if (cachedVersion !== undefined)
|
|
7
|
+
return cachedVersion;
|
|
8
|
+
let version;
|
|
9
|
+
try {
|
|
10
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(getPackageRoot(), 'package.json'), 'utf-8'));
|
|
11
|
+
version = typeof pkg.version === 'string' && pkg.version ? pkg.version : 'unknown';
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
version = 'unknown';
|
|
15
|
+
}
|
|
16
|
+
cachedVersion = version;
|
|
17
|
+
return version;
|
|
18
|
+
}
|
|
19
|
+
export function _resetInstalledEvolcoreVersionForTests() {
|
|
20
|
+
cachedVersion = undefined;
|
|
21
|
+
}
|
package/dist/utils/logger.js
CHANGED
|
@@ -51,8 +51,8 @@ function log(level, ...args) {
|
|
|
51
51
|
getWriters().main.write(msg);
|
|
52
52
|
}
|
|
53
53
|
/**
|
|
54
|
-
* 设置日志级别(
|
|
55
|
-
* 优先级:
|
|
54
|
+
* 设置日志级别(daemon.json 加载后调用,覆盖环境变量默认值)
|
|
55
|
+
* 优先级:daemon.json debug.logLevel → LOG_LEVEL 环境变量 → 'INFO'
|
|
56
56
|
*/
|
|
57
57
|
export function setLogLevel(level) {
|
|
58
58
|
const upper = level.toUpperCase();
|