evolcore 0.0.12 → 0.0.13
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 +22 -0
- package/bin/ec-safe-output.js +26 -7
- package/dist/agents/claude-runner.js +53 -7
- package/dist/agents/codex-app-server-client.js +53 -5
- package/dist/agents/codex-runner.js +104 -59
- package/dist/agents/ecagent-runner.js +4 -1
- package/dist/aun/aid/control-aid.js +36 -13
- package/dist/aun/aid/store.js +13 -7
- package/dist/aun/group-fs-download.js +56 -0
- package/dist/aun/group-identity.js +1 -2
- package/dist/aun/msg/group.js +11 -33
- package/dist/aun/msg/index.js +1 -2
- package/dist/aun/msg/managed-operation.js +116 -55
- package/dist/aun/msg/upload.js +16 -1
- package/dist/aun/outbox.js +31 -2
- package/dist/aun/rpc/client.js +66 -0
- package/dist/aun/rpc/index.js +1 -2
- package/dist/aun/storage/{upload.js → client.js} +11 -2
- package/dist/aun/storage/index.js +1 -2
- package/dist/channels/aun.js +371 -136
- package/dist/cli/agent.js +9 -96
- package/dist/cli/cli-argv.js +10 -0
- package/dist/cli/config.js +9 -17
- package/dist/cli/ctl-command.js +1 -1
- package/dist/cli/daemon-commands.js +39 -24
- package/dist/cli/fs-command.js +19 -10
- package/dist/cli/init.js +77 -8
- package/dist/cli/queue-command.js +42 -0
- package/dist/cli/restart-monitor.js +3 -14
- package/dist/cli/trigger-command.js +7 -0
- package/dist/config/access-policy.js +1 -2
- package/dist/config/builtin-roles.js +5 -0
- package/dist/config/config-manager.js +21 -1
- package/dist/config/peer-role-resolver.js +6 -2
- package/dist/config/role-migration-startup.js +53 -0
- package/dist/config/role-store.js +1 -1
- package/dist/core/auth/agent-delegation.js +1 -1
- package/dist/core/auth/auth-gateway.js +29 -1
- package/dist/core/auth/authorization-audit.js +15 -0
- package/dist/core/auth/operation-authorizer.js +1 -0
- package/dist/core/auth/operation-catalog.js +9 -0
- package/dist/core/bootstrap-messages.js +9 -2
- package/dist/core/bootstrap-service.js +1 -0
- package/dist/core/command/command-handler.js +198 -51
- package/dist/core/command/menu-catalog.js +4 -0
- package/dist/core/command/menu-handler.js +85 -18
- package/dist/core/command/menu-protocol.js +23 -2
- package/dist/core/command/slash-handler.js +58 -10
- package/dist/core/event-catalog.js +6 -0
- package/dist/core/handoff/runtime.js +12 -1
- package/dist/core/handoff/store.js +5 -1
- package/dist/core/interaction-router.js +10 -0
- package/dist/core/message/im-renderer.js +19 -0
- package/dist/core/message/message-bridge.js +63 -11
- package/dist/core/message/message-log.js +2 -0
- package/dist/core/message/message-queue.js +267 -39
- package/dist/core/message/message-utils.js +81 -4
- package/dist/core/message/response-engine.js +107 -41
- package/dist/core/message/stream-debouncer.js +10 -2
- package/dist/core/permission/approval-gateway.js +1 -0
- package/dist/core/permission/ec-command-parser.js +93 -14
- package/dist/core/permission/index.js +1 -1
- package/dist/core/permission/mode.js +15 -0
- package/dist/core/permission/readonly-shell-query.js +148 -8
- package/dist/core/permission/sandbox-runtime.js +21 -0
- package/dist/core/permission/tool-policy.js +249 -27
- package/dist/core/protected-paths.js +107 -2
- package/dist/core/session/session-manager.js +21 -2
- package/dist/core/session/session-mapper.js +20 -1
- package/dist/eck/detect.js +18 -1
- package/dist/eck/group-rules-sync.js +15 -44
- package/dist/index.js +161 -86
- package/dist/ipc.js +16 -3
- package/dist/paths.js +3 -0
- package/dist/trigger/feedback.js +64 -11
- package/dist/trigger/manager.js +21 -1
- package/dist/trigger/parser.js +14 -2
- package/dist/trigger/scheduler.js +1 -0
- package/dist/trigger/validation.js +67 -6
- package/dist/utils/ecweb-supervisor.js +332 -0
- package/dist/utils/error-utils.js +10 -0
- package/kits/docs/evolcore/trigger.md +17 -1
- package/kits/migrations/migrate-contact-book-v2.mjs +1 -2
- package/kits/schemas/role-config.schema.1.json +1 -0
- package/kits/templates/roles/admin.json +5 -0
- package/kits/templates/roles/member.json +7 -1
- package/kits/templates/roles/visitor.json +2 -0
- package/package.json +1 -1
- package/dist/aun/msg/payload-type.js +0 -27
- package/dist/aun/rpc/caller.js +0 -42
- package/dist/aun/rpc/connection.js +0 -25
- package/dist/aun/storage/manage.js +0 -10
- package/dist/config/access-policy-domain.js +0 -18
- package/dist/config/config-batch-get.js +0 -11
- package/dist/config/owner-policy.js +0 -4
- package/dist/config/role-config-v4-startup.js +0 -32
- package/dist/config/role-config-v5-startup.js +0 -27
- package/dist/core/auth/trigger-authorization.js +0 -15
- package/dist/core/interaction-registration.js +0 -10
- package/dist/core/permission/execution-sandbox.js +0 -16
- package/dist/core/relation/peer-key.js +0 -1
- package/dist/core/session/session-key.js +0 -24
- package/dist/eck/baseagent-caps.js +0 -18
- package/dist/eck/rules-loader.js +0 -28
package/dist/paths.js
CHANGED
|
@@ -149,6 +149,9 @@ export function daemonControlDir() {
|
|
|
149
149
|
export function daemonQueuePath() {
|
|
150
150
|
return path.join(daemonDataDir(), 'message-queue.json');
|
|
151
151
|
}
|
|
152
|
+
export function daemonMessageDedupPath() {
|
|
153
|
+
return path.join(daemonDataDir(), 'message-dedup.json');
|
|
154
|
+
}
|
|
152
155
|
export function channelStateDir(channelKey) {
|
|
153
156
|
if (!channelKey || channelKey === '.' || channelKey === '..') {
|
|
154
157
|
throw new Error('channelKey is required');
|
package/dist/trigger/feedback.js
CHANGED
|
@@ -23,9 +23,13 @@ export class TriggerFeedbackDispatcher {
|
|
|
23
23
|
dryRunText: renderedText,
|
|
24
24
|
};
|
|
25
25
|
}
|
|
26
|
-
const
|
|
27
|
-
if (!
|
|
26
|
+
const unresolvedTarget = this.resolveDeliveryTarget(input.trigger);
|
|
27
|
+
if (!unresolvedTarget)
|
|
28
28
|
return this.failed(feedback, [], 'target_missing', 'trigger feedback target is missing');
|
|
29
|
+
const target = unresolvedTarget;
|
|
30
|
+
if (channelTypeFromKey(target.channelKey) === 'aun' && !targetChatType(target)) {
|
|
31
|
+
return this.failed(feedback, [], 'route_missing', 'AUN trigger feedback has no explicit delivery route');
|
|
32
|
+
}
|
|
29
33
|
feedback.target = target;
|
|
30
34
|
if (input.dryRun) {
|
|
31
35
|
return { status: 'dry-run', feedback, effects: [], error: null, dryRunText: renderedText };
|
|
@@ -43,9 +47,13 @@ export class TriggerFeedbackDispatcher {
|
|
|
43
47
|
}
|
|
44
48
|
async dispatchTargetSession(input) {
|
|
45
49
|
const feedback = this.auditFeedback(input.trigger);
|
|
46
|
-
const
|
|
47
|
-
if (!
|
|
50
|
+
const unresolvedTarget = this.resolveDeliveryTarget(input.trigger);
|
|
51
|
+
if (!unresolvedTarget)
|
|
48
52
|
return this.failed(feedback, [], 'target_missing', 'target_session feedback target is missing');
|
|
53
|
+
const target = unresolvedTarget;
|
|
54
|
+
if (channelTypeFromKey(target.channelKey) === 'aun' && !targetChatType(target)) {
|
|
55
|
+
return this.failed(feedback, [], 'route_missing', 'AUN target_session has no explicit delivery route');
|
|
56
|
+
}
|
|
49
57
|
feedback.target = target;
|
|
50
58
|
feedback.renderedTextHash = sha256(input.prompt);
|
|
51
59
|
feedback.renderedTextPreview = previewText(input.prompt);
|
|
@@ -105,7 +113,14 @@ export class TriggerFeedbackDispatcher {
|
|
|
105
113
|
? undefined
|
|
106
114
|
: await raceWithAbort(Promise.resolve(mainLookup), delivery.signal);
|
|
107
115
|
const mainMetadata = main?.metadata;
|
|
108
|
-
const
|
|
116
|
+
const targetDelivery = targetDeliveryFor(target);
|
|
117
|
+
const chatType = targetDelivery?.chatType
|
|
118
|
+
?? (main?.chatType === 'group' ? 'group' : 'private');
|
|
119
|
+
const groupId = targetDelivery?.chatType === 'group'
|
|
120
|
+
? targetDelivery.groupId
|
|
121
|
+
: targetDelivery?.chatType === 'private'
|
|
122
|
+
? undefined
|
|
123
|
+
: mainMetadata?.groupId;
|
|
109
124
|
const peerId = mainMetadata?.peerId ?? `trigger:${input.trigger.id}`;
|
|
110
125
|
const peerType = mainMetadata?.peerType ?? 'system';
|
|
111
126
|
session = await raceWithAbort(this.deps.sessionManager.getOrCreateSession(target.channelKey, target.channelId, main?.projectPath ?? binding.projectPath, target.threadId, {
|
|
@@ -113,13 +128,20 @@ export class TriggerFeedbackDispatcher {
|
|
|
113
128
|
peerId,
|
|
114
129
|
peerName: mainMetadata?.peerName ?? input.trigger.name,
|
|
115
130
|
peerType,
|
|
116
|
-
...(
|
|
131
|
+
...(groupId ? { groupId } : {}),
|
|
117
132
|
...(mainMetadata?.groupName ? { groupName: mainMetadata.groupName } : {}),
|
|
118
133
|
replyContext: replyContextFromTarget(target),
|
|
119
134
|
}, input.trigger.name, peerId, chatType, main?.baseagent ?? binding.baseagent, main?.selfAID ?? input.trigger.agentAid, main?.channelType ?? channelType, peerType), delivery.signal);
|
|
120
135
|
}
|
|
121
136
|
else {
|
|
122
|
-
|
|
137
|
+
const targetDelivery = targetDeliveryFor(target);
|
|
138
|
+
session = await raceWithAbort(this.deps.sessionManager.getOrCreateSession(target.channelKey, target.channelId, binding.projectPath, undefined, {
|
|
139
|
+
channelKey: target.channelKey,
|
|
140
|
+
peerId: `trigger:${input.trigger.id}`,
|
|
141
|
+
peerName: input.trigger.name,
|
|
142
|
+
...(targetDelivery?.chatType === 'group' ? { groupId: targetDelivery.groupId } : {}),
|
|
143
|
+
replyContext: replyContextFromTarget(target),
|
|
144
|
+
}, input.trigger.name, undefined, targetDelivery?.chatType === 'group' ? 'group' : 'private', binding.baseagent, input.trigger.agentAid, channelType, 'system'), delivery.signal);
|
|
123
145
|
}
|
|
124
146
|
}
|
|
125
147
|
catch (err) {
|
|
@@ -143,6 +165,12 @@ export class TriggerFeedbackDispatcher {
|
|
|
143
165
|
}
|
|
144
166
|
releaseSessionLock = access.release;
|
|
145
167
|
}
|
|
168
|
+
// The persisted session can predate an explicit target route, and the
|
|
169
|
+
// same channelId may legally be used by both private and group sends.
|
|
170
|
+
// Keep the per-trigger route authoritative for this queued message.
|
|
171
|
+
const targetDelivery = targetDeliveryFor(target);
|
|
172
|
+
const targetChatType = targetDelivery?.chatType;
|
|
173
|
+
const messageChatType = targetChatType ?? (session.chatType === 'group' ? 'group' : 'private');
|
|
146
174
|
const message = {
|
|
147
175
|
channel: target.channelKey,
|
|
148
176
|
channelType,
|
|
@@ -150,7 +178,7 @@ export class TriggerFeedbackDispatcher {
|
|
|
150
178
|
selfAID: input.trigger.agentAid,
|
|
151
179
|
baseagent: session.baseagent || binding.baseagent,
|
|
152
180
|
threadId: session.threadId || '',
|
|
153
|
-
chatType:
|
|
181
|
+
chatType: messageChatType,
|
|
154
182
|
peerId: `trigger:${input.trigger.id}`,
|
|
155
183
|
peerName: input.trigger.name,
|
|
156
184
|
peerType: 'system',
|
|
@@ -159,6 +187,8 @@ export class TriggerFeedbackDispatcher {
|
|
|
159
187
|
timestamp: Date.now(),
|
|
160
188
|
source: 'trigger',
|
|
161
189
|
causation: input.causation,
|
|
190
|
+
groupId: targetChatType === 'group' ? targetDelivery.groupId : undefined,
|
|
191
|
+
replyContext: replyContextFromTarget(target),
|
|
162
192
|
triggerMeta: {
|
|
163
193
|
triggerId: input.trigger.id,
|
|
164
194
|
runId: input.runId,
|
|
@@ -346,6 +376,9 @@ export class TriggerFeedbackDispatcher {
|
|
|
346
376
|
const startedAt = Date.now();
|
|
347
377
|
try {
|
|
348
378
|
const isAunTarget = channelTypeFromKey(target.channelKey) === 'aun';
|
|
379
|
+
if (isAunTarget && !targetChatType(target)) {
|
|
380
|
+
throw Object.assign(new Error('AUN trigger feedback requires an explicit delivery route'), { code: 'AUN_OUTBOUND_ROUTE_REQUIRED' });
|
|
381
|
+
}
|
|
349
382
|
await raceWithAbort(binding.adapter.send(buildEnvelope({
|
|
350
383
|
taskId: `trigger:${input.runId}`,
|
|
351
384
|
channel: binding.adapter.channelKey,
|
|
@@ -579,12 +612,32 @@ function originAsTarget(origin) {
|
|
|
579
612
|
channelId: origin.channelId,
|
|
580
613
|
session: origin.session,
|
|
581
614
|
threadId: origin.threadId,
|
|
615
|
+
chatType: origin.delivery?.chatType,
|
|
582
616
|
};
|
|
583
617
|
}
|
|
584
618
|
function replyContextFromTarget(target) {
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
619
|
+
const ctx = {};
|
|
620
|
+
const delivery = targetDeliveryFor(target);
|
|
621
|
+
if (delivery)
|
|
622
|
+
ctx.delivery = delivery;
|
|
623
|
+
if (target.session === 'thread' && target.threadId)
|
|
624
|
+
ctx.threadId = target.threadId;
|
|
625
|
+
return Object.keys(ctx).length > 0 ? ctx : undefined;
|
|
626
|
+
}
|
|
627
|
+
function targetChatType(target) {
|
|
628
|
+
return target.chatType;
|
|
629
|
+
}
|
|
630
|
+
function targetDeliveryFor(target) {
|
|
631
|
+
const chatType = targetChatType(target);
|
|
632
|
+
if (chatType === 'private')
|
|
633
|
+
return { chatType: 'private' };
|
|
634
|
+
if (chatType === 'group') {
|
|
635
|
+
return {
|
|
636
|
+
chatType: 'group',
|
|
637
|
+
groupId: target.channelId,
|
|
638
|
+
};
|
|
639
|
+
}
|
|
640
|
+
return undefined;
|
|
588
641
|
}
|
|
589
642
|
function replyContextWithCausation(target, causation) {
|
|
590
643
|
const base = replyContextFromTarget(target);
|
package/dist/trigger/manager.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import { agentTriggersDir } from '../paths.js';
|
|
4
|
-
import { definitionRevision, normalizeTriggerDefinition, resolveScriptPath, safeRelativePath, } from './validation.js';
|
|
4
|
+
import { assertExplicitAunDeliveryRoutes, definitionRevision, normalizeTriggerDefinition, resolveScriptPath, safeRelativePath, } from './validation.js';
|
|
5
5
|
import { atomicWriteJson } from '../core/session/session-fs-store.js';
|
|
6
6
|
import { TriggerHistoryStore } from './history.js';
|
|
7
7
|
import { rewriteLegacyTriggerSessionPaths } from './legacy-session-history.js';
|
|
@@ -36,6 +36,12 @@ export class TriggerDefinitionManager {
|
|
|
36
36
|
return undefined;
|
|
37
37
|
const raw = JSON.parse(fs.readFileSync(file, 'utf-8'));
|
|
38
38
|
const definition = normalizeTriggerDefinition(raw);
|
|
39
|
+
if (legacyFeedbackTargetNeedsMigration(raw, definition)) {
|
|
40
|
+
definition.feedback.target.chatType ??= 'private';
|
|
41
|
+
definition.updatedAt = Date.now();
|
|
42
|
+
this.writeDefinition(definition);
|
|
43
|
+
this.history.recordDefinition('trigger.updated', definition, { reason: 'legacy-aun-target-chat-type' });
|
|
44
|
+
}
|
|
39
45
|
if (typeof definition.execution.prompt === 'string') {
|
|
40
46
|
const rewritten = rewriteLegacyTriggerSessionPaths(definition.execution.prompt, definition.agentAid);
|
|
41
47
|
if (rewritten !== definition.execution.prompt) {
|
|
@@ -56,6 +62,7 @@ export class TriggerDefinitionManager {
|
|
|
56
62
|
create(input, files = [], opts = {}) {
|
|
57
63
|
const now = Date.now();
|
|
58
64
|
const definition = normalizeTriggerDefinition(input, { now });
|
|
65
|
+
assertExplicitAunDeliveryRoutes(definition);
|
|
59
66
|
this.assertAgent(definition);
|
|
60
67
|
if (opts.enable !== undefined)
|
|
61
68
|
definition.enabled = opts.enable;
|
|
@@ -82,6 +89,7 @@ export class TriggerDefinitionManager {
|
|
|
82
89
|
id: triggerId,
|
|
83
90
|
...(origin ? { origin } : {}),
|
|
84
91
|
}, { now: Date.now() });
|
|
92
|
+
assertExplicitAunDeliveryRoutes(updated);
|
|
85
93
|
this.assertAgent(updated);
|
|
86
94
|
if (existing.execution.baseagent !== updated.execution.baseagent) {
|
|
87
95
|
throw new Error('execution.baseagent is immutable; create a new Trigger to use another baseagent');
|
|
@@ -101,6 +109,8 @@ export class TriggerDefinitionManager {
|
|
|
101
109
|
}
|
|
102
110
|
setEnabled(triggerId, enabled) {
|
|
103
111
|
const definition = this.require(triggerId);
|
|
112
|
+
if (enabled)
|
|
113
|
+
assertExplicitAunDeliveryRoutes(definition);
|
|
104
114
|
definition.enabled = enabled;
|
|
105
115
|
definition.updatedAt = Date.now();
|
|
106
116
|
this.writeDefinition(definition);
|
|
@@ -270,6 +280,16 @@ export class TriggerDefinitionManager {
|
|
|
270
280
|
function optionalString(value) {
|
|
271
281
|
return typeof value === 'string' && value.length > 0 ? value : undefined;
|
|
272
282
|
}
|
|
283
|
+
function legacyFeedbackTargetNeedsMigration(raw, definition) {
|
|
284
|
+
if (!isRecord(raw) || !definition.feedback.target)
|
|
285
|
+
return false;
|
|
286
|
+
const feedback = isRecord(raw.feedback) ? raw.feedback : undefined;
|
|
287
|
+
const target = feedback && isRecord(feedback.target) ? feedback.target : undefined;
|
|
288
|
+
if (!target || target.chatType !== undefined)
|
|
289
|
+
return false;
|
|
290
|
+
const channelKey = optionalString(target.channelKey);
|
|
291
|
+
return !!channelKey && (channelKey === 'aun' || channelKey.startsWith('aun#'));
|
|
292
|
+
}
|
|
273
293
|
function mergeOrigin(existing, incoming) {
|
|
274
294
|
if (existing?.channelKey && existing.channelId)
|
|
275
295
|
return existing;
|
package/dist/trigger/parser.js
CHANGED
|
@@ -9,7 +9,7 @@ const TRIGGER_CREATE_FLAGS = new Set([
|
|
|
9
9
|
'once', 'delay', 'at', 'cron', 'every', 'event', 'tz',
|
|
10
10
|
'exec', 'prompt', 'name', 'agent',
|
|
11
11
|
'script-path', 'script-runtime', 'script-args', 'script-timeout',
|
|
12
|
-
'feedback', 'target-channel', 'target-channel-id', 'target-session', 'target-thread-id',
|
|
12
|
+
'feedback', 'target-channel', 'target-channel-id', 'target-chat-type', 'target-session', 'target-thread-id',
|
|
13
13
|
'trigger-thread', 'baseagent',
|
|
14
14
|
'model', 'effort', 'permission', 'max-runs', 'max-duration',
|
|
15
15
|
]);
|
|
@@ -375,13 +375,22 @@ function commonParsed(flags, opts = {}) {
|
|
|
375
375
|
const targetChannelId = flags.get('target-channel-id');
|
|
376
376
|
if (targetChannelId === true)
|
|
377
377
|
return { ok: false, error: '--target-channel-id 不能为空' };
|
|
378
|
+
const targetChatType = flags.get('target-chat-type');
|
|
379
|
+
if (targetChatType === true)
|
|
380
|
+
return { ok: false, error: '--target-chat-type 不能为空' };
|
|
381
|
+
if (targetChatType !== undefined && targetChatType !== 'private' && targetChatType !== 'group') {
|
|
382
|
+
return { ok: false, error: '--target-chat-type 只接受 private 或 group' };
|
|
383
|
+
}
|
|
378
384
|
const targetThreadId = flags.get('target-thread-id');
|
|
379
385
|
if (targetThreadId === true)
|
|
380
386
|
return { ok: false, error: '--target-thread-id 不能为空' };
|
|
381
|
-
const hasTargetField = targetChannel !== undefined || targetChannelId !== undefined || targetSession.value !== undefined || targetThreadId !== undefined;
|
|
387
|
+
const hasTargetField = targetChannel !== undefined || targetChannelId !== undefined || targetChatType !== undefined || targetSession.value !== undefined || targetThreadId !== undefined;
|
|
382
388
|
if ((targetChannel === undefined) !== (targetChannelId === undefined)) {
|
|
383
389
|
return { ok: false, error: '--target-channel 与 --target-channel-id 必须同时指定或同时省略' };
|
|
384
390
|
}
|
|
391
|
+
if (targetChatType !== undefined && targetChannelId === undefined) {
|
|
392
|
+
return { ok: false, error: '--target-chat-type 必须与 --target-channel/--target-channel-id 一起使用' };
|
|
393
|
+
}
|
|
385
394
|
if (!opts.update && !execution.value) {
|
|
386
395
|
return { ok: false, error: '创建 trigger 必须显式指定 --exec script|trigger-session|target-session' };
|
|
387
396
|
}
|
|
@@ -409,6 +418,7 @@ function commonParsed(flags, opts = {}) {
|
|
|
409
418
|
feedbackStrategy,
|
|
410
419
|
targetChannel: targetChannel,
|
|
411
420
|
targetChannelId: targetChannelId,
|
|
421
|
+
targetChatType: targetChatType,
|
|
412
422
|
targetSession: targetSessionValue,
|
|
413
423
|
targetThreadId: targetThreadId,
|
|
414
424
|
agentId: flags.has('agent') ? flags.get('agent') : undefined,
|
|
@@ -447,6 +457,7 @@ export function parseTriggerSet(args) {
|
|
|
447
457
|
feedbackStrategy: value.feedbackStrategy,
|
|
448
458
|
targetChannel: value.targetChannel,
|
|
449
459
|
targetChannelId: value.targetChannelId,
|
|
460
|
+
targetChatType: value.targetChatType,
|
|
450
461
|
targetSession: value.targetSession,
|
|
451
462
|
targetThreadId: value.targetThreadId,
|
|
452
463
|
agentId: value.agentId,
|
|
@@ -486,6 +497,7 @@ export function parseTriggerSetArgv(args, opts = {}) {
|
|
|
486
497
|
feedbackStrategy: value.feedbackStrategy,
|
|
487
498
|
targetChannel: value.targetChannel,
|
|
488
499
|
targetChannelId: value.targetChannelId,
|
|
500
|
+
targetChatType: value.targetChatType,
|
|
489
501
|
targetSession: value.targetSession,
|
|
490
502
|
targetThreadId: value.targetThreadId,
|
|
491
503
|
agentId: value.agentId,
|
|
@@ -2016,6 +2016,7 @@ function primaryTarget(definition) {
|
|
|
2016
2016
|
channelId: definition.origin.channelId,
|
|
2017
2017
|
session: definition.origin.session,
|
|
2018
2018
|
threadId: definition.origin.threadId,
|
|
2019
|
+
chatType: definition.origin.delivery?.chatType,
|
|
2019
2020
|
};
|
|
2020
2021
|
}
|
|
2021
2022
|
function eventTestMatchError(reason, eventPattern) {
|
|
@@ -26,6 +26,34 @@ export function normalizeTriggerDefinition(input, opts = {}) {
|
|
|
26
26
|
}
|
|
27
27
|
return normalizeV3Phase2(raw, opts);
|
|
28
28
|
}
|
|
29
|
+
/**
|
|
30
|
+
* Write-time route contract for trigger definitions.
|
|
31
|
+
*
|
|
32
|
+
* Normalization keeps old AUN definitions readable so operators can inspect
|
|
33
|
+
* and remove them, but new writes must never persist a target that would need
|
|
34
|
+
* channelId shape inference at runtime.
|
|
35
|
+
*/
|
|
36
|
+
export function assertExplicitAunDeliveryRoutes(definition) {
|
|
37
|
+
if (definition.feedback.strategy === 'silent')
|
|
38
|
+
return;
|
|
39
|
+
const target = definition.feedback.strategy === 'target'
|
|
40
|
+
? definition.feedback.target
|
|
41
|
+
: definition.origin;
|
|
42
|
+
if (!target)
|
|
43
|
+
return;
|
|
44
|
+
const channelType = ('channelType' in target ? target.channelType : undefined)
|
|
45
|
+
?? target.channelKey.split('#', 1)[0];
|
|
46
|
+
if (channelType !== 'aun')
|
|
47
|
+
return;
|
|
48
|
+
const chatType = definition.feedback.strategy === 'target'
|
|
49
|
+
? definition.feedback.target?.chatType
|
|
50
|
+
: definition.origin?.delivery?.chatType;
|
|
51
|
+
if (!chatType) {
|
|
52
|
+
throw Object.assign(new Error('AUN trigger feedback target requires an explicit delivery route'), {
|
|
53
|
+
code: 'AUN_OUTBOUND_ROUTE_REQUIRED',
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
}
|
|
29
57
|
export function validateTriggerDefinition(definition) {
|
|
30
58
|
normalizeTriggerDefinition(definition);
|
|
31
59
|
}
|
|
@@ -232,16 +260,30 @@ function normalizeOrigin(value) {
|
|
|
232
260
|
throw new Error('origin.threadId is required when origin.session=thread');
|
|
233
261
|
const rawChannelId = requiredString(raw.channelId, 'origin.channelId');
|
|
234
262
|
const peerId = optionalString(raw.peerId);
|
|
235
|
-
const
|
|
263
|
+
const channelKey = requiredString(raw.channelKey, 'origin.channelKey');
|
|
264
|
+
const channelType = optionalString(raw.channelType);
|
|
265
|
+
const isAunOrigin = channelType === 'aun' || channelKey === 'aun' || channelKey.startsWith('aun#');
|
|
266
|
+
// Legacy non-AUN definitions used channelId/peerId shape to represent a
|
|
267
|
+
// group-created trigger. AUN routes deliberately do not get that recovery:
|
|
268
|
+
// the transport type must have been persisted explicitly.
|
|
269
|
+
const legacyGroupOrigin = !isAunOrigin && raw.delivery === undefined && !!peerId && rawChannelId !== peerId;
|
|
270
|
+
const legacyPrivateOrigin = !isAunOrigin && raw.delivery === undefined && !!peerId && rawChannelId === peerId;
|
|
236
271
|
const session = legacyGroupOrigin ? 'main' : rawSession;
|
|
272
|
+
const channelId = legacyGroupOrigin ? peerId : rawChannelId;
|
|
273
|
+
const delivery = raw.delivery === undefined
|
|
274
|
+
? legacyGroupOrigin || legacyPrivateOrigin || !isAunOrigin
|
|
275
|
+
? { chatType: 'private' }
|
|
276
|
+
: undefined
|
|
277
|
+
: normalizeDeliveryTarget(raw.delivery, 'origin.delivery', channelId);
|
|
237
278
|
return {
|
|
238
|
-
channelKey
|
|
239
|
-
channelType
|
|
240
|
-
channelId
|
|
279
|
+
channelKey,
|
|
280
|
+
channelType,
|
|
281
|
+
channelId,
|
|
241
282
|
session,
|
|
242
283
|
threadId: session === 'thread' ? threadId : undefined,
|
|
243
284
|
peerId,
|
|
244
285
|
sessionKey: legacyGroupOrigin ? undefined : optionalString(raw.sessionKey),
|
|
286
|
+
delivery,
|
|
245
287
|
};
|
|
246
288
|
}
|
|
247
289
|
function normalizeSource(value) {
|
|
@@ -486,14 +528,33 @@ function normalizeFeedbackTarget(value, label, agentAid) {
|
|
|
486
528
|
if (session === 'thread' && !threadId)
|
|
487
529
|
throw new Error(`${label}.threadId is required when ${label}.session=thread`);
|
|
488
530
|
const channelKey = requiredString(raw.channelKey, `${label}.channelKey`);
|
|
489
|
-
|
|
531
|
+
const channelId = requiredString(raw.channelId, `${label}.channelId`);
|
|
532
|
+
const rawChatType = raw.chatType;
|
|
533
|
+
if (rawChatType !== undefined && rawChatType !== 'private' && rawChatType !== 'group') {
|
|
534
|
+
throw new Error(`${label}.chatType must be private or group`);
|
|
535
|
+
}
|
|
536
|
+
const target = {
|
|
490
537
|
channelKey: channelKey === 'aun'
|
|
491
538
|
? formatChannelKey({ type: 'aun', selfAID: agentAid, name: 'main' })
|
|
492
539
|
: channelKey,
|
|
493
|
-
channelId
|
|
540
|
+
channelId,
|
|
494
541
|
session,
|
|
495
542
|
threadId,
|
|
543
|
+
chatType: rawChatType,
|
|
496
544
|
};
|
|
545
|
+
return target;
|
|
546
|
+
}
|
|
547
|
+
function normalizeDeliveryTarget(value, label, channelId) {
|
|
548
|
+
if (!isObject(value))
|
|
549
|
+
throw new Error(`${label} must be an object`);
|
|
550
|
+
if (value.chatType === 'private')
|
|
551
|
+
return { chatType: 'private' };
|
|
552
|
+
if (value.chatType !== 'group')
|
|
553
|
+
throw new Error(`${label}.chatType must be private or group`);
|
|
554
|
+
const groupId = requiredString(value.groupId, `${label}.groupId`);
|
|
555
|
+
if (groupId !== channelId)
|
|
556
|
+
throw new Error(`${label}.groupId must match channelId`);
|
|
557
|
+
return { chatType: 'group', groupId };
|
|
497
558
|
}
|
|
498
559
|
function normalizeFeedbackTemplate(value, label) {
|
|
499
560
|
if (value === undefined)
|