evolcore 0.0.2 → 0.0.4
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 +93 -778
- 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 +198 -48
- package/dist/agents/codex-app-server-client.js +68 -0
- package/dist/agents/codex-runner.js +203 -8
- 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/aun/outbox.js +8 -0
- package/dist/channels/aun.js +241 -116
- package/dist/channels/daemon.js +18 -4
- package/dist/channels/dingtalk.js +1005 -260
- package/dist/channels/feishu.js +184 -56
- package/dist/channels/qqbot.js +23 -1
- package/dist/channels/wechat.js +303 -155
- package/dist/channels/wecom.js +1132 -164
- package/dist/cli/agent-command.js +2 -1
- package/dist/cli/aun-commands.js +98 -43
- package/dist/cli/bench.js +2 -2
- package/dist/cli/contact.js +71 -0
- package/dist/cli/ctl-command.js +2 -2
- package/dist/cli/daemon-commands.js +77 -214
- package/dist/cli/handoff-command.js +4 -3
- package/dist/cli/help.js +9 -5
- package/dist/cli/index.js +132 -116
- package/dist/cli/init-channel.js +242 -129
- package/dist/cli/init.js +70 -31
- package/dist/cli/model.js +2 -1
- package/dist/cli/net-check.js +2 -2
- package/dist/cli/queue-command.js +30 -6
- package/dist/cli/raw-key-input.js +25 -0
- package/dist/cli/response.js +5 -6
- package/dist/cli/restart-monitor.js +25 -1
- package/dist/cli/stats.js +6 -4
- package/dist/cli/trigger-command.js +146 -25
- package/dist/cli/version.js +6 -1
- package/dist/config/access-policy-domain.js +38 -0
- package/dist/config/access-policy.js +134 -0
- package/dist/config/builtin-role-templates.js +42 -17
- package/dist/config/builtin-roles.js +31 -6
- package/dist/config/config-field-policy.js +17 -5
- package/dist/config/config-manager.js +409 -29
- package/dist/config/config-operation-service.js +35 -38
- package/dist/config/contact-alias.js +68 -0
- package/dist/config/contact-bind-code.js +274 -0
- package/dist/config/contact-book-store.js +622 -0
- package/dist/config/contact-book-v2-startup.js +35 -0
- package/dist/config/contact-book.js +185 -300
- package/dist/config/contact-operation-service.js +116 -0
- package/dist/config/contact-request-service.js +331 -0
- package/dist/config/peer-role-resolver.js +136 -55
- package/dist/config/role-ranks.js +18 -0
- package/dist/config/role-service.js +16 -19
- package/dist/config/role-store.js +16 -5
- package/dist/config/roles.js +10 -1
- package/dist/config/schema-registry.js +49 -24
- package/dist/config-store.js +14 -4
- package/dist/core/auth/agent-delegation.js +105 -11
- package/dist/core/auth/authorization-audit.js +16 -1
- package/dist/core/auth/operation-authorizer.js +10 -0
- package/dist/core/auth/operation-catalog.js +105 -1
- 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 +113 -21
- package/dist/core/command/connect-menu.js +599 -0
- 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 +382 -100
- 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 +128 -29
- package/dist/core/command/slash-gate.js +1 -1
- package/dist/core/command/slash-handler.js +186 -49
- package/dist/core/daemon-file-cache.js +40 -6
- package/dist/core/event-catalog.js +99 -0
- package/dist/core/evolagent-registry.js +4 -40
- package/dist/core/evolagent.js +20 -9
- 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 +20 -14
- package/dist/core/message/inbound-admission.js +124 -0
- package/dist/core/message/message-bridge.js +670 -57
- package/dist/core/message/message-log.js +1 -0
- package/dist/core/message/message-queue.js +195 -10
- package/dist/core/message/message-utils.js +8 -2
- package/dist/core/message/peer-mode.js +7 -8
- package/dist/core/message/response-engine.js +642 -96
- package/dist/core/message/send-receipt.js +24 -0
- package/dist/core/message/stream-debouncer.js +11 -2
- 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 +115 -38
- package/dist/core/protected-paths.js +38 -11
- package/dist/core/session/session-fs-store.js +44 -3
- package/dist/core/session/session-manager.js +86 -8
- 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 +285 -94
- package/dist/ipc.js +114 -8
- 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/stats/billing.js +20 -8
- package/dist/trigger/manager.js +3 -0
- package/dist/trigger/parser.js +126 -11
- package/dist/trigger/patch.js +12 -2
- package/dist/trigger/scheduler.js +444 -40
- package/dist/trigger/validation.js +13 -0
- package/dist/utils/aid-bind.js +43 -29
- package/dist/utils/error-dict.json +7 -0
- package/dist/utils/evolcore-version.js +21 -0
- package/dist/utils/instance-registry.js +14 -7
- package/dist/utils/log-writer.js +46 -0
- package/dist/utils/logger.js +2 -2
- package/dist/utils/media-cache.js +4 -1
- package/dist/utils/model-prices.jsonl +6 -3
- package/dist/utils/restart-safety.js +31 -0
- package/dist/utils/stable-semver.js +21 -0
- package/dist/utils/stats.js +33 -9
- package/dist/utils/system-memory.js +62 -0
- package/kits/docs/INDEX.md +2 -1
- package/kits/docs/channels/aun.md +4 -13
- package/kits/docs/evolcore/INDEX.md +5 -3
- package/kits/docs/evolcore/agent.md +9 -1
- package/kits/docs/evolcore/aid.md +5 -2
- package/kits/docs/evolcore/config.md +47 -2
- package/kits/docs/evolcore/contact.md +61 -0
- package/kits/docs/evolcore/fs.md +9 -0
- package/kits/docs/evolcore/group-rules.md +46 -4
- package/kits/docs/evolcore/group.md +15 -6
- package/kits/docs/evolcore/model.md +4 -1
- package/kits/docs/evolcore/msg.md +12 -6
- package/kits/docs/evolcore/response.md +16 -21
- package/kits/docs/evolcore/rpc.md +2 -0
- package/kits/docs/evolcore/stats.md +15 -2
- package/kits/docs/evolcore/storage.md +1 -0
- package/kits/docs/evolcore/trigger.md +42 -10
- package/kits/docs/path-registry.md +36 -17
- package/kits/eck_manifest.json +12 -0
- package/kits/eck_message_manifest.json +12 -1
- package/kits/migrations/migrate-contact-book-v2.mjs +754 -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 +21 -7
- package/kits/schemas/agent-config.schema.6.json +322 -0
- 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 +374 -0
- package/kits/schemas/contact-book.schema.2.json +43 -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.5.json +47 -0
- package/kits/schemas/relation-config.schema.6.json +46 -0
- package/kits/schemas/relation-config.schema.7.json +59 -0
- package/kits/schemas/role-config.schema.1.json +1 -0
- package/kits/schemas/role-registry.schema.1.json +2 -2
- 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 +47 -0
- package/kits/templates/roles/member.json +5 -0
- package/kits/templates/roles/owner.json +1 -0
- package/kits/templates/roles/visitor.json +5 -0
- package/kits/templates/system-fragments/channel.md +12 -2
- package/kits/templates/system-fragments/commands.md +3 -1
- 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 +7 -5
- package/MIGRATION-0.5.0.md +0 -378
- package/ROLE_ACCESS_CONTROL.md +0 -174
- package/assets/brand/evolcore/README.md +0 -19
- package/assets/brand/evolcore/evolcore-app-icon.png +0 -0
- package/assets/brand/evolcore/evolcore-app-icon.svg +0 -13
- package/assets/brand/evolcore/evolcore-brand-board.png +0 -0
- package/assets/brand/evolcore/evolcore-brand-board.svg +0 -126
- package/assets/brand/evolcore/evolcore-logo-kit.zip +0 -0
- package/assets/brand/evolcore/evolcore-logo-reverse.png +0 -0
- package/assets/brand/evolcore/evolcore-logo-reverse.svg +0 -14
- package/assets/brand/evolcore/evolcore-logo.png +0 -0
- package/assets/brand/evolcore/evolcore-logo.svg +0 -14
- package/assets/brand/evolcore/evolcore-mark.png +0 -0
- package/assets/brand/evolcore/evolcore-mark.svg +0 -10
|
@@ -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,
|
|
@@ -729,7 +778,9 @@ export class TriggerRuntimeScheduler {
|
|
|
729
778
|
return { branch: branchFromReply(reply), script: null, processing, reply };
|
|
730
779
|
}
|
|
731
780
|
try {
|
|
732
|
-
const runtimeBaseagent =
|
|
781
|
+
const runtimeBaseagent = definition.execution.baseagent
|
|
782
|
+
?? this.runtime.getBaseagent?.()
|
|
783
|
+
?? this.runtime.baseagent;
|
|
733
784
|
const originPath = causation.trigger?.path ?? [];
|
|
734
785
|
logger.info(`[Trigger] execution context triggerId=${definition.id}`
|
|
735
786
|
+ ` triggerRunId=${runId}`
|
|
@@ -738,17 +789,33 @@ export class TriggerRuntimeScheduler {
|
|
|
738
789
|
+ ` baseagent=${runtimeBaseagent}`
|
|
739
790
|
+ ` model=${definition.execution.model ?? '<agent-default>'}`
|
|
740
791
|
+ ` effort=${definition.execution.effort ?? '<agent-default>'}`);
|
|
741
|
-
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
|
+
});
|
|
742
808
|
const daemonReply = await this.daemonChannel.converse(prompt, {
|
|
743
809
|
trigger: definition,
|
|
744
810
|
runId,
|
|
745
|
-
attemptId
|
|
811
|
+
attemptId,
|
|
746
812
|
firedAt,
|
|
747
813
|
projectPath: this.runtime.projectPath,
|
|
748
814
|
baseagent: runtimeBaseagent,
|
|
749
815
|
session: executionSession,
|
|
750
816
|
causation,
|
|
751
817
|
signal,
|
|
818
|
+
executionDeadlineAt: activeRun?.executionDeadlineAt ?? executionDeadlineAt,
|
|
752
819
|
onStateChange: (state, metadata) => {
|
|
753
820
|
if (state === 'requires_action') {
|
|
754
821
|
this.state.setPhase(definition.id, runId, 'requires-action');
|
|
@@ -832,41 +899,338 @@ export class TriggerRuntimeScheduler {
|
|
|
832
899
|
if (activeRuns.length === 0)
|
|
833
900
|
continue;
|
|
834
901
|
for (const run of activeRuns) {
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
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,
|
|
848
1025
|
});
|
|
849
|
-
this.
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
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,
|
|
861
1163
|
});
|
|
862
1164
|
}
|
|
863
|
-
|
|
864
|
-
|
|
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
|
+
});
|
|
865
1172
|
}
|
|
866
|
-
}
|
|
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)}`);
|
|
867
1231
|
}
|
|
868
|
-
this.state.clear(definition.id);
|
|
869
1232
|
}
|
|
1233
|
+
this.state.remove(definition.id, run.runId);
|
|
870
1234
|
}
|
|
871
1235
|
resolveScheduledAt(definition) {
|
|
872
1236
|
const now = Date.now();
|
|
@@ -1100,6 +1464,13 @@ export class TriggerRuntimeScheduler {
|
|
|
1100
1464
|
}
|
|
1101
1465
|
async settleTerminatedRun(runId, runtime, termination) {
|
|
1102
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
|
+
}
|
|
1103
1474
|
const completedAnomalies = mergeTriggerExecutionAnomalies(runtime.anomalies, completion.settled ? completion.value.anomalies : undefined);
|
|
1104
1475
|
const anomalies = accumulateTriggerExecutionAnomalies(completedAnomalies, drainTriggerExecutionAnomalies(runId));
|
|
1105
1476
|
if (!completion.settled) {
|
|
@@ -1146,16 +1517,49 @@ export class TriggerRuntimeScheduler {
|
|
|
1146
1517
|
}
|
|
1147
1518
|
this.finishRun(runtime.definition.id, runId);
|
|
1148
1519
|
}
|
|
1149
|
-
|
|
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) {
|
|
1150
1539
|
const runs = this.running.get(triggerId);
|
|
1151
1540
|
runs?.delete(runId);
|
|
1152
1541
|
if (runs && runs.size === 0)
|
|
1153
1542
|
this.running.delete(triggerId);
|
|
1543
|
+
}
|
|
1544
|
+
finishRun(triggerId, runId) {
|
|
1545
|
+
this.removeRunning(triggerId, runId);
|
|
1154
1546
|
this.state.remove(triggerId, runId);
|
|
1155
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
|
+
}
|
|
1156
1557
|
isRunning(triggerId, runId) {
|
|
1157
1558
|
return this.running.get(triggerId)?.has(runId) === true;
|
|
1158
1559
|
}
|
|
1560
|
+
daemonExecutionDeadlineAt(startedAt) {
|
|
1561
|
+
return this.daemonChannel.executionDeadlineAt?.(startedAt) ?? startedAt + 60 * 60 * 1_000;
|
|
1562
|
+
}
|
|
1159
1563
|
clearTimer(triggerId) {
|
|
1160
1564
|
const timer = this.timers.get(triggerId);
|
|
1161
1565
|
if (timer)
|
|
@@ -360,6 +360,7 @@ function normalizeExecution(value, _opts) {
|
|
|
360
360
|
prompt: type === 'script' ? optionalString(raw.prompt) : requiredString(raw.prompt, 'execution.prompt'),
|
|
361
361
|
script: type === 'script' ? normalizeScript(raw.script) : undefined,
|
|
362
362
|
thread: type === 'trigger_session' ? (thread ?? 'by_trigger') : thread,
|
|
363
|
+
baseagent: normalizeExecutionBaseagent(raw.baseagent),
|
|
363
364
|
model: optionalString(raw.model),
|
|
364
365
|
effort: normalizeEffort(raw.effort),
|
|
365
366
|
permissionMode: normalizeTriggerPermissionMode(raw.permissionMode),
|
|
@@ -368,6 +369,14 @@ function normalizeExecution(value, _opts) {
|
|
|
368
369
|
};
|
|
369
370
|
return execution;
|
|
370
371
|
}
|
|
372
|
+
function normalizeExecutionBaseagent(value) {
|
|
373
|
+
if (value === undefined)
|
|
374
|
+
return undefined;
|
|
375
|
+
if (typeof value !== 'string' || !value.trim()) {
|
|
376
|
+
throw new Error('execution.baseagent must be a non-empty string');
|
|
377
|
+
}
|
|
378
|
+
return value.trim();
|
|
379
|
+
}
|
|
371
380
|
function normalizeExecutionThread(value) {
|
|
372
381
|
const thread = optionalString(value);
|
|
373
382
|
if (thread === undefined)
|
|
@@ -533,6 +542,8 @@ function validateTriggerSemantics(definition) {
|
|
|
533
542
|
throw new Error('execution.prompt is not allowed when execution.type=script');
|
|
534
543
|
if (execution.thread !== undefined)
|
|
535
544
|
throw new Error('execution.thread is not allowed when execution.type=script');
|
|
545
|
+
if (execution.baseagent !== undefined)
|
|
546
|
+
throw new Error('execution.baseagent is only allowed when execution.type=trigger_session');
|
|
536
547
|
}
|
|
537
548
|
if (execution.type === 'trigger_session') {
|
|
538
549
|
if (!execution.prompt)
|
|
@@ -547,6 +558,8 @@ function validateTriggerSemantics(definition) {
|
|
|
547
558
|
throw new Error('execution.script is not allowed when execution.type=target_session');
|
|
548
559
|
if (execution.thread !== undefined)
|
|
549
560
|
throw new Error('execution.thread is not allowed when execution.type=target_session');
|
|
561
|
+
if (execution.baseagent !== undefined)
|
|
562
|
+
throw new Error('execution.baseagent is only allowed when execution.type=trigger_session');
|
|
550
563
|
if (definition.feedback.strategy === 'silent')
|
|
551
564
|
throw new Error('feedback.strategy=silent is not allowed when execution.type=target_session');
|
|
552
565
|
}
|