evolcore 0.0.2 → 0.0.3
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 +44 -793
- package/dist/agents/claude-runner.js +197 -17
- package/dist/agents/codex-runner.js +46 -3
- package/dist/aun/outbox.js +8 -0
- package/dist/channels/aun.js +21 -4
- package/dist/channels/contact-bind-code.js +134 -0
- package/dist/channels/dingtalk.js +979 -149
- package/dist/channels/feishu.js +130 -54
- package/dist/channels/wecom-card.js +101 -0
- package/dist/channels/wecom-onboarding.js +82 -0
- package/dist/channels/wecom-state.js +191 -0
- package/dist/channels/wecom.js +755 -163
- package/dist/cli/agent-command.js +2 -1
- package/dist/cli/aun-commands.js +88 -33
- 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 +2 -2
- package/dist/cli/help.js +9 -5
- package/dist/cli/index.js +132 -116
- package/dist/cli/init-channel.js +92 -97
- package/dist/cli/init.js +63 -26
- 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 +55 -15
- package/dist/cli/version.js +6 -1
- package/dist/config/builtin-role-templates.js +22 -10
- package/dist/config/builtin-roles.js +7 -1
- package/dist/config/config-manager.js +221 -17
- package/dist/config/contact-alias.js +68 -0
- package/dist/config/contact-book-store.js +454 -0
- package/dist/config/contact-book-v2-startup.js +35 -0
- package/dist/config/contact-book.js +156 -303
- package/dist/config/contact-operation-service.js +110 -0
- package/dist/config/peer-role-resolver.js +133 -54
- 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-store.js +0 -2
- package/dist/core/auth/authorization-audit.js +10 -1
- package/dist/core/auth/operation-authorizer.js +2 -0
- package/dist/core/auth/operation-catalog.js +56 -0
- package/dist/core/command/command-handler.js +105 -10
- package/dist/core/command/connect-menu.js +374 -0
- package/dist/core/command/menu-handler.js +114 -16
- package/dist/core/command/role-menu.js +128 -29
- package/dist/core/command/slash-handler.js +28 -18
- package/dist/core/daemon-file-cache.js +12 -6
- package/dist/core/event-catalog.js +70 -0
- package/dist/core/evolagent-registry.js +0 -1
- package/dist/core/evolagent.js +20 -9
- package/dist/core/message/im-renderer.js +2 -0
- package/dist/core/message/message-bridge.js +79 -8
- package/dist/core/message/message-queue.js +10 -0
- package/dist/core/message/message-utils.js +8 -2
- package/dist/core/message/response-engine.js +269 -25
- package/dist/core/message/send-receipt.js +24 -0
- package/dist/core/message/stream-debouncer.js +11 -2
- package/dist/core/permission/tool-policy.js +47 -15
- package/dist/core/protected-paths.js +2 -0
- package/dist/core/session/session-fs-store.js +44 -3
- package/dist/core/session/session-manager.js +61 -5
- package/dist/index.js +62 -6
- package/dist/ipc.js +34 -5
- package/dist/stats/billing.js +20 -8
- package/dist/trigger/manager.js +3 -0
- package/dist/trigger/parser.js +77 -2
- package/dist/trigger/patch.js +8 -1
- package/dist/trigger/scheduler.js +3 -1
- package/dist/trigger/validation.js +13 -0
- package/dist/utils/aid-bind.js +43 -29
- package/dist/utils/instance-registry.js +14 -7
- package/dist/utils/log-writer.js +46 -0
- 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/system-memory.js +62 -0
- package/kits/docs/INDEX.md +2 -1
- 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/contact.md +57 -0
- package/kits/docs/evolcore/fs.md +9 -0
- package/kits/docs/evolcore/group.md +12 -3
- package/kits/docs/evolcore/model.md +4 -1
- package/kits/docs/evolcore/msg.md +9 -3
- 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 +17 -2
- package/kits/eck_manifest.json +12 -0
- package/kits/migrations/migrate-contact-book-v2.mjs +747 -0
- package/kits/schemas/_meta.json +7 -4
- package/kits/schemas/agent-config.schema.6.json +322 -0
- package/kits/schemas/contact-book.schema.2.json +43 -0
- package/kits/schemas/relation-config.schema.5.json +47 -0
- package/kits/schemas/role-config.schema.1.json +1 -0
- package/kits/schemas/role-registry.schema.1.json +2 -2
- package/kits/templates/roles/admin.json +1 -0
- package/kits/templates/roles/member.json +1 -0
- package/kits/templates/roles/owner.json +1 -0
- package/kits/templates/roles/visitor.json +1 -0
- package/kits/templates/system-fragments/commands.md +3 -1
- package/package.json +4 -4
- 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
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* 策略(reload/重启永远全量失效,无视策略;策略只决定"平时每次读怎么检查"):
|
|
13
13
|
* - on-reload:平时不检查,直接用缓存(kits 文件、persona)。靠 reload/重启刷新。
|
|
14
14
|
* - manual:同 on-reload,额外支持显式 invalidate(file) 单刷。
|
|
15
|
-
* - mtime:每次读 statSync 门控 mtime,变了自动重读(working.md、config.json)。
|
|
15
|
+
* - mtime:每次读 statSync 门控 size + mtime,变了自动重读(working.md、config.json)。
|
|
16
16
|
*
|
|
17
17
|
* 容量:项数可无界增长的组(如 relation-prefs,每 peer 一文件)设 LRU 硬上限
|
|
18
18
|
* (见 GROUP_CAPS),命中/写入移到末尾、超限驱逐同组最旧项;无 timer。
|
|
@@ -71,9 +71,9 @@ export class FileCache {
|
|
|
71
71
|
const read = opts.read ?? readFileOrNull;
|
|
72
72
|
this.bump(opts.group, opts.policy, 'gets');
|
|
73
73
|
if (opts.policy === 'mtime') {
|
|
74
|
-
const
|
|
74
|
+
const fingerprint = statFingerprint(file);
|
|
75
75
|
this.bump(opts.group, opts.policy, 'statChecks');
|
|
76
|
-
if (existing && existing.
|
|
76
|
+
if (existing && sameFingerprint(existing.fingerprint, fingerprint)) {
|
|
77
77
|
this.bump(opts.group, opts.policy, 'hits');
|
|
78
78
|
this.touch(file, existing);
|
|
79
79
|
return existing.value;
|
|
@@ -84,7 +84,7 @@ export class FileCache {
|
|
|
84
84
|
this.bump(opts.group, opts.policy, 'misses');
|
|
85
85
|
const raw = read(file);
|
|
86
86
|
const value = loader(raw);
|
|
87
|
-
this.store(file, { policy: 'mtime', group: opts.group,
|
|
87
|
+
this.store(file, { policy: 'mtime', group: opts.group, fingerprint, value, bytes: approxBytes(raw) });
|
|
88
88
|
return value;
|
|
89
89
|
}
|
|
90
90
|
// on-reload / manual:命中即用,不检查文件
|
|
@@ -192,14 +192,20 @@ export class FileCache {
|
|
|
192
192
|
};
|
|
193
193
|
}
|
|
194
194
|
}
|
|
195
|
-
function
|
|
195
|
+
function statFingerprint(file) {
|
|
196
196
|
try {
|
|
197
|
-
|
|
197
|
+
const stat = fs.statSync(file);
|
|
198
|
+
return { size: stat.size, mtimeMs: stat.mtimeMs };
|
|
198
199
|
}
|
|
199
200
|
catch {
|
|
200
201
|
return null; // 文件不存在
|
|
201
202
|
}
|
|
202
203
|
}
|
|
204
|
+
function sameFingerprint(left, right) {
|
|
205
|
+
if (left === null || right === null)
|
|
206
|
+
return left === right;
|
|
207
|
+
return !!left && left.size === right.size && left.mtimeMs === right.mtimeMs;
|
|
208
|
+
}
|
|
203
209
|
function readFileOrNull(file) {
|
|
204
210
|
try {
|
|
205
211
|
return fs.readFileSync(file, 'utf-8');
|
|
@@ -355,6 +355,76 @@ const CATALOG = [
|
|
|
355
355
|
{ path: 'preTokens', type: 'number' },
|
|
356
356
|
],
|
|
357
357
|
},
|
|
358
|
+
{
|
|
359
|
+
type: 'runner:task-started',
|
|
360
|
+
namespace: 'runner',
|
|
361
|
+
name: 'task-started',
|
|
362
|
+
description: 'Runner 子任务已创建',
|
|
363
|
+
fields: [
|
|
364
|
+
{ path: 'sessionId', type: 'string' },
|
|
365
|
+
{ path: 'taskId', type: 'string' },
|
|
366
|
+
{ path: 'taskKind', type: 'string' },
|
|
367
|
+
{ path: 'toolUseId', type: 'string', optional: true },
|
|
368
|
+
{ path: 'description', type: 'string' },
|
|
369
|
+
{ path: 'subagentType', type: 'string', optional: true },
|
|
370
|
+
{ path: 'taskType', type: 'string', optional: true },
|
|
371
|
+
{ path: 'workflowName', type: 'string', optional: true },
|
|
372
|
+
{ path: 'prompt', type: 'string', optional: true },
|
|
373
|
+
{ path: 'skipTranscript', type: 'boolean', optional: true },
|
|
374
|
+
{ path: 'timestamp', type: 'number', optional: true },
|
|
375
|
+
],
|
|
376
|
+
},
|
|
377
|
+
{
|
|
378
|
+
type: 'runner:task-progress',
|
|
379
|
+
namespace: 'runner',
|
|
380
|
+
name: 'task-progress',
|
|
381
|
+
description: 'Runner 子任务进度',
|
|
382
|
+
fields: [
|
|
383
|
+
{ path: 'sessionId', type: 'string' },
|
|
384
|
+
{ path: 'taskId', type: 'string', optional: true },
|
|
385
|
+
{ path: 'taskKind', type: 'string', optional: true },
|
|
386
|
+
{ path: 'toolUseId', type: 'string', optional: true },
|
|
387
|
+
{ path: 'description', type: 'string', optional: true },
|
|
388
|
+
{ path: 'subagentType', type: 'string', optional: true },
|
|
389
|
+
{ path: 'summary', type: 'string', optional: true },
|
|
390
|
+
{ path: 'lastToolName', type: 'string', optional: true },
|
|
391
|
+
{ path: 'totalTokens', type: 'number', optional: true },
|
|
392
|
+
{ path: 'toolUses', type: 'number', optional: true },
|
|
393
|
+
{ path: 'durationMs', type: 'number', optional: true },
|
|
394
|
+
{ path: 'timestamp', type: 'number', optional: true },
|
|
395
|
+
],
|
|
396
|
+
},
|
|
397
|
+
{
|
|
398
|
+
type: 'runner:task-notification',
|
|
399
|
+
namespace: 'runner',
|
|
400
|
+
name: 'task-notification',
|
|
401
|
+
description: 'Runner 子任务终态和输出',
|
|
402
|
+
fields: [
|
|
403
|
+
{ path: 'sessionId', type: 'string' },
|
|
404
|
+
{ path: 'taskId', type: 'string' },
|
|
405
|
+
{ path: 'taskKind', type: 'string' },
|
|
406
|
+
{ path: 'toolUseId', type: 'string', optional: true },
|
|
407
|
+
{ path: 'description', type: 'string', optional: true },
|
|
408
|
+
{ path: 'subagentType', type: 'string', optional: true },
|
|
409
|
+
{ path: 'status', type: 'string' },
|
|
410
|
+
{ path: 'outputFile', type: 'string', optional: true },
|
|
411
|
+
{ path: 'summary', type: 'string', optional: true },
|
|
412
|
+
{ path: 'usage', type: 'object', optional: true },
|
|
413
|
+
{ path: 'skipTranscript', type: 'boolean', optional: true },
|
|
414
|
+
{ path: 'timestamp', type: 'number', optional: true },
|
|
415
|
+
],
|
|
416
|
+
},
|
|
417
|
+
{
|
|
418
|
+
type: 'runner:background-tasks-changed',
|
|
419
|
+
namespace: 'runner',
|
|
420
|
+
name: 'background-tasks-changed',
|
|
421
|
+
description: 'Runner 后台任务集合变化',
|
|
422
|
+
fields: [
|
|
423
|
+
{ path: 'sessionId', type: 'string' },
|
|
424
|
+
{ path: 'tasks', type: 'array' },
|
|
425
|
+
{ path: 'timestamp', type: 'number', optional: true },
|
|
426
|
+
],
|
|
427
|
+
},
|
|
358
428
|
{
|
|
359
429
|
type: 'runner:model-changed',
|
|
360
430
|
namespace: 'runner',
|
|
@@ -129,7 +129,6 @@ export class EvolAgentRegistry {
|
|
|
129
129
|
aid: raw.aid,
|
|
130
130
|
enabled: raw.enabled,
|
|
131
131
|
owners: raw.owners,
|
|
132
|
-
admins: raw.admins,
|
|
133
132
|
channels: Array.isArray(raw.channels) ? raw.channels : [],
|
|
134
133
|
active_baseagent: raw.active_baseagent,
|
|
135
134
|
baseagents: raw.baseagents,
|
package/dist/core/evolagent.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import path from 'path';
|
|
2
|
-
import { formatChannelKey } from './channel-loader.js';
|
|
2
|
+
import { formatChannelKey, tryParseChannelKey } from './channel-loader.js';
|
|
3
3
|
import { agentPersonalDir } from '../paths.js';
|
|
4
4
|
import { fileCache } from './daemon-file-cache.js';
|
|
5
5
|
import { ConfigTarget, read as cfgRead, write as cfgWrite, ensureFile as cfgEnsure, resolveEffective } from '../config/config-manager.js';
|
|
6
6
|
import { withLifecycleForWrite } from '../config/lifecycle.js';
|
|
7
|
+
import { isStaticAgentAdmin, isStaticAgentOwner } from '../config/peer-role-resolver.js';
|
|
8
|
+
import { resolvePrincipal } from '../config/contact-book.js';
|
|
7
9
|
/**
|
|
8
10
|
* EvolAgent —— 一个 self-agent 的运行时表示。
|
|
9
11
|
*
|
|
@@ -123,19 +125,28 @@ export class EvolAgent {
|
|
|
123
125
|
/**
|
|
124
126
|
* Check if a user has owner role for this agent (private scope).
|
|
125
127
|
*/
|
|
126
|
-
isOwner(
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
return false;
|
|
128
|
+
isOwner(channelKey, userId) {
|
|
129
|
+
const principalId = this.managementPrincipal(channelKey, userId);
|
|
130
|
+
return !!principalId && isStaticAgentOwner(this.aid, principalId);
|
|
130
131
|
}
|
|
131
132
|
/**
|
|
132
133
|
* Check if a user has admin role for this agent (private scope).
|
|
133
|
-
*
|
|
134
|
+
* Owner remains in Agent config; admin is resolved from the direct AUN relation.
|
|
134
135
|
*/
|
|
135
|
-
isAdmin(
|
|
136
|
-
|
|
136
|
+
isAdmin(channelKey, userId) {
|
|
137
|
+
const principalId = this.managementPrincipal(channelKey, userId);
|
|
138
|
+
if (!principalId)
|
|
139
|
+
return false;
|
|
140
|
+
if (isStaticAgentOwner(this.aid, principalId))
|
|
137
141
|
return true;
|
|
138
|
-
return this.
|
|
142
|
+
return isStaticAgentAdmin(this.aid, principalId);
|
|
143
|
+
}
|
|
144
|
+
managementPrincipal(channelKey, userId) {
|
|
145
|
+
const channelType = tryParseChannelKey(channelKey)?.type ?? String(channelKey || '').split('#')[0];
|
|
146
|
+
const resolution = resolvePrincipal(this.aid, channelType, userId, channelKey);
|
|
147
|
+
return resolution.principalStatus === 'native_aid' || resolution.principalStatus === 'mapped'
|
|
148
|
+
? resolution.principalId ?? null
|
|
149
|
+
: null;
|
|
139
150
|
}
|
|
140
151
|
/**
|
|
141
152
|
* Get the first owner of this agent (private scope).
|
|
@@ -589,6 +589,8 @@ export class IMRenderer {
|
|
|
589
589
|
return { kind: 'notice', text, severity: 'warn' };
|
|
590
590
|
}
|
|
591
591
|
case 'complete': {
|
|
592
|
+
if (event.queryFinal === false)
|
|
593
|
+
return null;
|
|
592
594
|
// 上下文过长错误不输出(留给外层 auto-compact 处理)
|
|
593
595
|
const hasContextError = event.terminalReason === 'prompt_too_long'
|
|
594
596
|
|| isContextTooLongText(event.errors?.join(' ') || '')
|
|
@@ -5,14 +5,58 @@ import { appendMessageLog, appendMessageLogStrict, buildInboundEntry, isTransien
|
|
|
5
5
|
import { buildEnvelope } from './message-utils.js';
|
|
6
6
|
import { chatDirPath } from '../session/session-fs-store.js';
|
|
7
7
|
import { tryParseChannelKey } from '../channel-loader.js';
|
|
8
|
-
import { resolvePaths } from '../../paths.js';
|
|
8
|
+
import { agentDir, resolvePaths } from '../../paths.js';
|
|
9
9
|
import { resolvePeerRoleDetail } from '../../config/peer-role-resolver.js';
|
|
10
|
+
import { isBlockedContact, resolveContactView } from '../../config/contact-book.js';
|
|
11
|
+
import { isValidAid } from '../../aun/aid/validation.js';
|
|
12
|
+
import { PeerIdentityCache } from '../relation/peer-identity.js';
|
|
10
13
|
import { handlePendingDingtalkContactBindMessage } from '../../channels/dingtalk.js';
|
|
14
|
+
import { handlePendingWecomContactBindMessage } from '../../channels/wecom.js';
|
|
11
15
|
import { authorizeAccess, buildAuthSubject } from '../auth/auth-gateway.js';
|
|
12
16
|
import { MenuDiagnosticLimiter, MenuRequestDeduper, hasValidMenuId, menuFailure, menuPayloadFingerprint, menuSuccess, normalizeMenuError, parseMenuControl, validateMenuRequest, } from '../command/menu-protocol.js';
|
|
13
17
|
import { SessionRenewService } from '../session/session-renew.js';
|
|
14
18
|
import { createRootCausation, deriveCausation, normalizeCausation } from '../causation/context.js';
|
|
15
19
|
import { recordCausationSpan } from '../causation/audit.js';
|
|
20
|
+
const CONTACT_REJECTION_NOTICE = '[rejection] Not accepting messages from you.';
|
|
21
|
+
const REJECTABLE_PEER_TYPES = new Set(['ai', 'bot', 'agent', 'service']);
|
|
22
|
+
const REJECTION_MAX = 3;
|
|
23
|
+
const REJECTION_WINDOW_MS = 60 * 60 * 1000;
|
|
24
|
+
const rejectionState = new Map();
|
|
25
|
+
export function shouldBlockInboundContact(input) {
|
|
26
|
+
if (String(input.channelType || '').trim().toLowerCase() !== 'aun' || !input.selfAid) {
|
|
27
|
+
return { blocked: false, status: 'unsupported-channel' };
|
|
28
|
+
}
|
|
29
|
+
if (input.chatType !== 'private')
|
|
30
|
+
return { blocked: false, status: 'unsupported-chat' };
|
|
31
|
+
const primaryId = String(input.actorId || '').trim();
|
|
32
|
+
if (!isValidAid(primaryId))
|
|
33
|
+
return { blocked: false, status: 'invalid-aid' };
|
|
34
|
+
if (input.chatType === 'private' && resolveContactView(input.selfAid, primaryId).isOwner) {
|
|
35
|
+
return { blocked: false, status: 'owner-exempt', primaryId };
|
|
36
|
+
}
|
|
37
|
+
return {
|
|
38
|
+
blocked: isBlockedContact(input.selfAid, primaryId),
|
|
39
|
+
status: 'resolved',
|
|
40
|
+
primaryId,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
export function shouldSendContactRejection(peerType, selfAid, primaryId, now = Date.now()) {
|
|
44
|
+
if (!REJECTABLE_PEER_TYPES.has(String(peerType || '').trim().toLowerCase()))
|
|
45
|
+
return false;
|
|
46
|
+
const key = `${selfAid}\0${primaryId}`;
|
|
47
|
+
const state = rejectionState.get(key);
|
|
48
|
+
if (!state || now - state.windowStart >= REJECTION_WINDOW_MS) {
|
|
49
|
+
rejectionState.set(key, { count: 1, windowStart: now });
|
|
50
|
+
return true;
|
|
51
|
+
}
|
|
52
|
+
if (state.count >= REJECTION_MAX)
|
|
53
|
+
return false;
|
|
54
|
+
state.count += 1;
|
|
55
|
+
return true;
|
|
56
|
+
}
|
|
57
|
+
export function clearContactRejectionRateLimits() {
|
|
58
|
+
rejectionState.clear();
|
|
59
|
+
}
|
|
16
60
|
/**
|
|
17
61
|
* MessageBridge — Channel 与 Core 之间的消息桥梁
|
|
18
62
|
*
|
|
@@ -119,6 +163,25 @@ export class MessageBridge {
|
|
|
119
163
|
const conversationId = chatType === 'group' ? (msg.groupId || msg.channelId) : msg.peerId;
|
|
120
164
|
const selfAid = msg.selfAID || owningAgent?.aid || parsedChannelKey?.selfAID;
|
|
121
165
|
const resolvedChannelType = msg.channelType || parsedChannelKey?.type || effectiveChannelType;
|
|
166
|
+
const blockDecision = shouldBlockInboundContact({
|
|
167
|
+
selfAid,
|
|
168
|
+
channelType: resolvedChannelType,
|
|
169
|
+
chatType,
|
|
170
|
+
actorId,
|
|
171
|
+
conversationId,
|
|
172
|
+
});
|
|
173
|
+
if (blockDecision.blocked && selfAid && blockDecision.primaryId) {
|
|
174
|
+
let peerType = msg.peerType;
|
|
175
|
+
if (chatType === 'private' && !peerType) {
|
|
176
|
+
peerType = PeerIdentityCache.get('aun', blockDecision.primaryId, agentDir(selfAid))?.type;
|
|
177
|
+
}
|
|
178
|
+
if (chatType === 'private'
|
|
179
|
+
&& shouldSendContactRejection(peerType, selfAid, blockDecision.primaryId)) {
|
|
180
|
+
await sendReply(msg.channelId, CONTACT_REJECTION_NOTICE, msg.replyContext);
|
|
181
|
+
}
|
|
182
|
+
logger.info(`[MessageBridge] Blocked AUN inbound before business routing: self=${selfAid} peer=${blockDecision.primaryId} type=${peerType ?? 'unknown'} chatType=${chatType}`);
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
122
185
|
const menuControl = resolvedChannelType === 'aun'
|
|
123
186
|
? parseMenuControl(content)
|
|
124
187
|
: { isMenu: false };
|
|
@@ -136,16 +199,21 @@ export class MessageBridge {
|
|
|
136
199
|
}
|
|
137
200
|
}
|
|
138
201
|
if (!menuControl.isMenu) {
|
|
139
|
-
const
|
|
202
|
+
const bindContext = {
|
|
140
203
|
selfAid,
|
|
141
204
|
channelName: channelKey,
|
|
142
205
|
channelType: resolvedChannelType,
|
|
143
206
|
chatType,
|
|
144
207
|
actorId,
|
|
145
208
|
content,
|
|
146
|
-
}
|
|
209
|
+
};
|
|
210
|
+
const contactBind = resolvedChannelType === 'dingtalk'
|
|
211
|
+
? handlePendingDingtalkContactBindMessage(bindContext)
|
|
212
|
+
: resolvedChannelType === 'wecom'
|
|
213
|
+
? handlePendingWecomContactBindMessage(bindContext)
|
|
214
|
+
: { handled: false };
|
|
147
215
|
if (contactBind.handled) {
|
|
148
|
-
logger.info(`[MessageBridge]
|
|
216
|
+
logger.info(`[MessageBridge] ${resolvedChannelType} contact bind handled: channel=${channelKey} actor=${actorId ?? '<none>'} status=${contactBind.status}`);
|
|
149
217
|
if (contactBind.reply) {
|
|
150
218
|
await sendReply(msg.channelId, contactBind.reply, msg.replyContext);
|
|
151
219
|
}
|
|
@@ -236,7 +304,7 @@ export class MessageBridge {
|
|
|
236
304
|
logger.debug(`[MessageBridge] Command detected: "${cmdContent}", routing to handler`);
|
|
237
305
|
// 命令也要记录入方向 jsonl(不创建 session,直接用 chatDirPath 计算路径)
|
|
238
306
|
try {
|
|
239
|
-
const chatDir = chatDirPath(resolvePaths().sessionsDir, msg.channelType || effectiveChannelType, msg.channelId, msg.selfAID || '');
|
|
307
|
+
const chatDir = chatDirPath(resolvePaths().sessionsDir, msg.channelType || effectiveChannelType, msg.channelId, msg.selfAID || '', channelKey);
|
|
240
308
|
const inboundEncrypt = msg.replyContext?.metadata?.encrypted != null ? !!(msg.replyContext.metadata.encrypted) : undefined;
|
|
241
309
|
const inboundChatmode = msg.replyContext?.metadata?.chatmode;
|
|
242
310
|
const inboundReplyTo = typeof msg.replyContext?.metadata?.refMessageId === 'string'
|
|
@@ -429,8 +497,11 @@ export class MessageBridge {
|
|
|
429
497
|
// ACK 在到达时立即做(每条独立 ACK),不等合并
|
|
430
498
|
// Interrupt 模式(单聊)→ 入队前 debounce 合并
|
|
431
499
|
// FIFO 模式(群聊) → 跳过 debouncer,独立入队,出队时贪心合并
|
|
432
|
-
//
|
|
433
|
-
|
|
500
|
+
// 优化:当前会话队列空闲时跳过 Pin,直接在开始执行时添加 CheckMark。
|
|
501
|
+
// 不能使用全局 processing 数,否则其他会话正在执行也会让本会话误显示“已排队”。
|
|
502
|
+
const currentSessionBusy = (this.messageQueue.isProcessing?.(session.id) ?? false)
|
|
503
|
+
|| (this.messageQueue.getQueueLength?.(session.id) ?? 0) > 0;
|
|
504
|
+
if (fullMessage.messageId && currentSessionBusy) {
|
|
434
505
|
adapter?.acknowledge?.(fullMessage.messageId).catch(() => { });
|
|
435
506
|
}
|
|
436
507
|
const isInterrupt = chatType !== 'group' && !handoffCandidate;
|
|
@@ -726,7 +797,7 @@ export class MessageBridge {
|
|
|
726
797
|
if (!this.cmdHandler.isCommand(content))
|
|
727
798
|
return false;
|
|
728
799
|
logger.info(`[${channel}] ${channelId}: ${content}${source === 'card-trigger' ? ' [card]' : ''}`);
|
|
729
|
-
const cmdResult = await this.cmdHandler.handle(content, channel, channelId, (_cid, text, opts) => sendReply(text), userId, threadId, chatType, source, messageId, selfAID, authSubject?.identity, authSubject);
|
|
800
|
+
const cmdResult = await this.cmdHandler.handle(content, channel, channelId, (_cid, text, opts) => sendReply(text), userId, threadId, chatType, source, messageId, selfAID, authSubject?.identity, authSubject, replyContext);
|
|
730
801
|
logger.debug(`[MessageBridge] handleCommand: result type=${typeof cmdResult}`);
|
|
731
802
|
if (cmdResult === undefined)
|
|
732
803
|
return false;
|
|
@@ -856,6 +856,7 @@ export class MessageQueue {
|
|
|
856
856
|
* - items: 保留每条子消息(含各自 peer/timestamp),供消息渲染层逐条渲染
|
|
857
857
|
* - images / mentions: 扁平合并
|
|
858
858
|
* - messageId: 取最新一条的 messageId(用于 thought 锚定与中断追踪)
|
|
859
|
+
* - sourceMessageIds: 保留全部原始入站消息 ID(用于渠道确认状态批量迁移)
|
|
859
860
|
* - replyContext / peerName / 其余字段: 取最后一条
|
|
860
861
|
*/
|
|
861
862
|
mergeItems(items) {
|
|
@@ -864,6 +865,8 @@ export class MessageQueue {
|
|
|
864
865
|
const allMentions = [];
|
|
865
866
|
const subMessages = [];
|
|
866
867
|
let mergedPeerType;
|
|
868
|
+
const sourceMessageIds = [];
|
|
869
|
+
const seenSourceMessageIds = new Set();
|
|
867
870
|
let hasRestartResume = false;
|
|
868
871
|
let hasMessagesAfterRestartResume = false;
|
|
869
872
|
for (const item of items) {
|
|
@@ -879,6 +882,12 @@ export class MessageQueue {
|
|
|
879
882
|
allImages.push(...m.images);
|
|
880
883
|
if (m.mentions)
|
|
881
884
|
allMentions.push(...m.mentions);
|
|
885
|
+
for (const id of [...(m.sourceMessageIds ?? []), ...(m.messageId ? [m.messageId] : [])]) {
|
|
886
|
+
if (!seenSourceMessageIds.has(id)) {
|
|
887
|
+
seenSourceMessageIds.add(id);
|
|
888
|
+
sourceMessageIds.push(id);
|
|
889
|
+
}
|
|
890
|
+
}
|
|
882
891
|
if (m.peerType && m.peerType !== 'human') {
|
|
883
892
|
mergedPeerType = m.peerType;
|
|
884
893
|
}
|
|
@@ -919,6 +928,7 @@ export class MessageQueue {
|
|
|
919
928
|
images: allImages.length > 0 ? allImages : undefined,
|
|
920
929
|
mentions: allMentions.length > 0 ? allMentions : undefined,
|
|
921
930
|
messageId: latestMessageId,
|
|
931
|
+
sourceMessageIds: sourceMessageIds.length > 0 ? sourceMessageIds : undefined,
|
|
922
932
|
// 密文优先:合并批次内任一条密文,则整轮(interactive 自动回复的单值出站)按密文。
|
|
923
933
|
// 任一条 encrypted===true 即 true;否则若有明确的 false 则 false;全 undefined(非 aun)则 undefined。
|
|
924
934
|
encrypted: subMessages.some(s => s.encrypted === true)
|
|
@@ -8,8 +8,10 @@ import { renderCommandCardAsText, renderActionAsText } from '../interaction-rout
|
|
|
8
8
|
* 构造出站消息信封
|
|
9
9
|
*/
|
|
10
10
|
export function buildEnvelope(opts) {
|
|
11
|
+
const taskId = opts.taskId ?? `interaction-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
11
12
|
return {
|
|
12
|
-
taskId
|
|
13
|
+
taskId,
|
|
14
|
+
operationId: opts.operationId ?? taskId,
|
|
13
15
|
sessionId: opts.sessionId,
|
|
14
16
|
channel: opts.channel,
|
|
15
17
|
channelId: opts.channelId,
|
|
@@ -61,7 +63,11 @@ export async function sendInteractionPayload(adapter, envelope, interaction, fal
|
|
|
61
63
|
const enriched = replyCtx
|
|
62
64
|
? { ...envelope, replyContext: replyCtx }
|
|
63
65
|
: envelope;
|
|
64
|
-
await adapter.send(enriched, payload);
|
|
66
|
+
const receipt = await adapter.send(enriched, payload);
|
|
67
|
+
if (receipt?.status === 'suppressed')
|
|
68
|
+
return false;
|
|
69
|
+
if (receipt?.status === 'sent')
|
|
70
|
+
return receipt.messages[0]?.messageId || false;
|
|
65
71
|
return 'sent';
|
|
66
72
|
}
|
|
67
73
|
catch {
|