evolcore 0.0.11 → 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 +44 -0
- package/bin/codex-managed-hook.mjs +80 -0
- package/bin/ec-safe-output.js +26 -7
- package/dist/agents/claude-runner.js +86 -7
- package/dist/agents/codex-app-server-client.js +174 -20
- package/dist/agents/codex-runner.js +375 -92
- package/dist/agents/ecagent-runner.js +39 -4
- package/dist/agents/gemini-runner.js +1 -0
- package/dist/aun/aid/client.js +11 -21
- package/dist/aun/aid/control-aid.js +36 -13
- package/dist/aun/aid/managed-operation.js +107 -0
- 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 +91 -44
- package/dist/aun/msg/index.js +1 -2
- package/dist/aun/msg/managed-operation.js +865 -0
- package/dist/aun/msg/mention-schema.js +20 -0
- package/dist/aun/msg/p2p.js +7 -0
- 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 +655 -240
- package/dist/channels/daemon.js +13 -0
- package/dist/cli/agent-command.js +56 -8
- package/dist/cli/agent.js +108 -31
- package/dist/cli/aun-commands.js +270 -69
- package/dist/cli/bench.js +12 -3
- 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 +70 -55
- package/dist/cli/fs-command.js +79 -10
- package/dist/cli/handoff-command.js +3 -0
- package/dist/cli/index.js +30 -2
- package/dist/cli/init.js +77 -8
- package/dist/cli/managed-command-guard.js +41 -0
- package/dist/cli/model.js +11 -4
- package/dist/cli/queue-command.js +53 -1
- package/dist/cli/response.js +71 -0
- package/dist/cli/restart-monitor.js +7 -32
- package/dist/cli/trigger-command.js +11 -0
- package/dist/config/access-policy.js +1 -2
- package/dist/config/builtin-roles.js +9 -0
- package/dist/config/config-manager.js +21 -1
- package/dist/config/contact-book-store.js +11 -3
- package/dist/config/contact-request-service.js +161 -15
- package/dist/config/mention-mode.js +8 -24
- 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 -2
- 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 +31 -7
- package/dist/core/auth/operation-catalog.js +196 -25
- package/dist/core/bootstrap-messages.js +25 -0
- package/dist/core/bootstrap-service.js +64 -4
- package/dist/core/capability/providers/claude-capability-provider.js +9 -31
- package/dist/core/capability/providers/codex-capability-provider.js +4 -15
- package/dist/core/capability/skill-discovery.js +55 -0
- package/dist/core/channel-loader.js +1 -1
- package/dist/core/command/cli-intent-parser.js +9 -0
- package/dist/core/command/command-handler.js +650 -42
- package/dist/core/command/connect-menu.js +15 -0
- package/dist/core/command/group-menu.js +4 -4
- package/dist/core/command/menu-catalog.js +370 -0
- package/dist/core/command/menu-handler.js +230 -51
- package/dist/core/command/menu-protocol.js +94 -2
- package/dist/core/command/slash-gate.js +5 -2
- package/dist/core/command/slash-handler.js +99 -31
- package/dist/core/daemon-file-cache.js +2 -1
- package/dist/core/data-migration.js +9 -2
- package/dist/core/event-catalog.js +6 -0
- package/dist/core/evolagent.js +4 -2
- package/dist/core/handoff/dispatcher.js +5 -2
- package/dist/core/handoff/runtime.js +157 -22
- package/dist/core/handoff/store.js +29 -1
- package/dist/core/handoff/types.js +1 -0
- package/dist/core/interaction-router.js +10 -0
- package/dist/core/message/im-renderer.js +19 -0
- package/dist/core/message/logical-queue-bridge.js +1 -0
- package/dist/core/message/mention-schema.js +126 -0
- package/dist/core/message/message-bridge.js +298 -88
- package/dist/core/message/message-log.js +3 -0
- package/dist/core/message/message-queue.js +268 -39
- package/dist/core/message/message-utils.js +81 -4
- package/dist/core/message/response-engine.js +161 -77
- 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 +243 -18
- 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 +43 -1
- package/dist/core/permission/tool-policy.js +322 -57
- package/dist/core/protected-paths.js +114 -4
- package/dist/core/session/session-manager.js +42 -4
- 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/eck/manifest-engine.js +39 -13
- package/dist/index.js +541 -180
- package/dist/ipc.js +232 -10
- package/dist/paths.js +3 -0
- package/dist/response-system/coordinator.js +6 -4
- package/dist/response-system/engines/v1/proactive-flow.js +26 -8
- package/dist/response-system/modes/single-session/index.js +14 -1
- package/dist/response-system/selector.js +6 -5
- package/dist/trigger/anomaly-store.js +4 -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/script-executor.js +1 -0
- package/dist/trigger/validation.js +67 -6
- package/dist/utils/codex-app-server-registry.js +90 -0
- package/dist/utils/codex-cli.js +5 -1
- package/dist/utils/cross-platform.js +15 -0
- package/dist/utils/ecweb-supervisor.js +332 -0
- package/dist/utils/error-utils.js +10 -0
- package/dist/utils/npm-ops.js +5 -12
- package/dist/utils/tool-summary.js +11 -0
- package/kits/docs/channels/aun.md +1 -1
- package/kits/docs/context-assembly.md +2 -2
- package/kits/docs/evolcore/agent.md +11 -6
- package/kits/docs/evolcore/aid.md +14 -0
- package/kits/docs/evolcore/trigger.md +17 -1
- package/kits/docs/prompt-loading-architecture.md +1 -2
- package/kits/docs/venues/group.md +1 -1
- package/kits/eck_manifest.json +0 -12
- package/kits/migrations/migrate-contact-book-v2.mjs +1 -2
- package/kits/rules/04-relation.md +1 -1
- package/kits/rules/05-venue.md +2 -2
- package/kits/schemas/role-config.schema.1.json +1 -0
- package/kits/schemas/single-session.schema.1.json +3 -3
- package/kits/schemas/single-session.schema.2.json +3 -3
- package/kits/templates/roles/admin.json +27 -0
- package/kits/templates/roles/member.json +34 -1
- package/kits/templates/roles/visitor.json +35 -3
- package/kits/templates/system-fragments/bootstrap.md +1 -1
- package/kits/templates/system-fragments/channel.md +1 -1
- package/kits/templates/system-fragments/session.md +1 -1
- package/package.json +2 -2
- 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/command/evol-menu-version-gate.js +0 -39
- 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
|
@@ -8,7 +8,7 @@ import { formatChannelKey, tryParseChannelKey } from '../channel-loader.js';
|
|
|
8
8
|
import { agentDir, resolvePaths } from '../../paths.js';
|
|
9
9
|
import { listStaticAgentAdmins, listStaticAgentOwners, resolvePeerRoleDetail, } from '../../config/peer-role-resolver.js';
|
|
10
10
|
import { resolveContactView, resolvePeerDisplayLabel, resolvePrincipal } from '../../config/contact-book.js';
|
|
11
|
-
import { findContactRequestNote, expirePendingContactRequests, reviewContactRequest, submitContactRequest, } from '../../config/contact-request-service.js';
|
|
11
|
+
import { findContactRequestNote, expirePendingContactRequests, reviewContactRequest, submitContactRequest, contactRequestSubmissionProtocolResult, queryOwnContactRequestStatus, } from '../../config/contact-request-service.js';
|
|
12
12
|
import { isValidAid } from '../../aun/aid/validation.js';
|
|
13
13
|
import { PeerIdentityCache } from '../relation/peer-identity.js';
|
|
14
14
|
import { handlePendingDingtalkContactBindMessage, registerPendingDingtalkContactBind } from '../../channels/dingtalk.js';
|
|
@@ -17,8 +17,8 @@ import { handlePendingQQBotContactBindMessage, registerPendingQQBotContactBind }
|
|
|
17
17
|
import { handlePendingWecomContactBindMessage, registerPendingWecomContactBind } from '../../channels/wecom.js';
|
|
18
18
|
import { handlePendingWechatContactBindMessage, registerPendingWechatContactBind } from '../../channels/wechat.js';
|
|
19
19
|
import { authorizeAccess, buildAuthSubject } from '../auth/auth-gateway.js';
|
|
20
|
-
import { MenuDiagnosticLimiter, MenuRequestDeduper, hasValidMenuId, menuFailure, menuPayloadFingerprint, menuSuccess, normalizeMenuError, parseMenuControl, validateMenuRequest, } from '../command/menu-protocol.js';
|
|
21
|
-
import {
|
|
20
|
+
import { MenuDiagnosticLimiter, MenuRequestDeduper, hasValidMenuId, menuFailure, menuPayloadFingerprint, menuSuccess, normalizeMenuError, parseMenuControl, validateMenuRequest, evaluateEvolMenuVersionGate, evolMenuResponseTransportMetadata, isAunMenuTokenRequired, menuCommandForName, } from '../command/menu-protocol.js';
|
|
21
|
+
import { resolveExplicitMenuExecutionContext, validateMenuCatalogArgs, validateMenuValueBatchArgs } from '../command/menu-catalog.js';
|
|
22
22
|
import { MenuTokenStore } from '../command/menu-token-store.js';
|
|
23
23
|
import { SessionRenewService } from '../session/session-renew.js';
|
|
24
24
|
import { createRootCausation, deriveCausation, normalizeCausation } from '../causation/context.js';
|
|
@@ -306,42 +306,171 @@ export class MessageBridge {
|
|
|
306
306
|
return true;
|
|
307
307
|
}
|
|
308
308
|
}
|
|
309
|
+
return false;
|
|
310
|
+
}
|
|
311
|
+
async handleContactRequestProtocol(input) {
|
|
312
|
+
const { context, msg, content, owningAgent, adapter, sendReply } = input;
|
|
309
313
|
const requestMatch = /^\s*\/request(?:\s+([\s\S]*?))?\s*$/i.exec(content);
|
|
310
|
-
|
|
314
|
+
let protocolType;
|
|
315
|
+
let protocolPayload;
|
|
316
|
+
try {
|
|
317
|
+
const parsed = JSON.parse(content);
|
|
318
|
+
protocolPayload = parsed && typeof parsed === 'object' && !Array.isArray(parsed)
|
|
319
|
+
? parsed
|
|
320
|
+
: undefined;
|
|
321
|
+
protocolType = protocolPayload?.type === 'contact.request.query'
|
|
322
|
+
? 'query'
|
|
323
|
+
: protocolPayload?.type === 'contact.request.submit' ? 'submit' : undefined;
|
|
324
|
+
}
|
|
325
|
+
catch { /* slash command */ }
|
|
326
|
+
if (!requestMatch && !protocolType)
|
|
311
327
|
return false;
|
|
312
|
-
|
|
313
|
-
|
|
328
|
+
// Slash commands keep their human-facing response semantics. Structured
|
|
329
|
+
// App requests use contact.request.response and never fall through to the
|
|
330
|
+
// normal command/session pipeline.
|
|
331
|
+
if (requestMatch) {
|
|
332
|
+
// Blacklist remains the first gate even though slash controls are
|
|
333
|
+
// recognized before ordinary admission. Let the normal blocked path
|
|
334
|
+
// decide whether an agent/bot/service gets a limited rejection.
|
|
335
|
+
if (context.contactStatus === 'blocked')
|
|
336
|
+
return false;
|
|
337
|
+
if (context.channelType !== 'aun') {
|
|
338
|
+
await this.sendAdmissionControlReply(context, msg, sendReply, '联系人申请仅支持 AUN 私聊。');
|
|
339
|
+
return true;
|
|
340
|
+
}
|
|
341
|
+
if (context.chatType !== 'private') {
|
|
342
|
+
await this.sendAdmissionControlReply(context, msg, sendReply, '联系人申请仅支持 AUN 私聊。');
|
|
343
|
+
return true;
|
|
344
|
+
}
|
|
345
|
+
if (!context.primaryId || context.principal?.principalStatus !== 'native_aid') {
|
|
346
|
+
await this.sendAdmissionControlReply(context, msg, sendReply, '无法验证当前 AUN 身份,未提交联系人申请。');
|
|
347
|
+
return true;
|
|
348
|
+
}
|
|
349
|
+
try {
|
|
350
|
+
const submission = await submitContactRequest({
|
|
351
|
+
selfAid: context.selfAid,
|
|
352
|
+
applicantAid: context.primaryId,
|
|
353
|
+
note: requestMatch[1],
|
|
354
|
+
sourceChannelKey: context.channelKey,
|
|
355
|
+
});
|
|
356
|
+
if ((submission.code === 'submitted' || submission.code === 'resubmitted') && submission.requestId) {
|
|
357
|
+
void this.deliverContactReviewCard(context.selfAid, context.primaryId, submission, owningAgent)
|
|
358
|
+
.catch(error => logger.warn(`[MessageBridge] Contact review delivery failed: ${error instanceof Error ? error.message : String(error)}`));
|
|
359
|
+
}
|
|
360
|
+
await this.sendAdmissionControlReply(context, msg, sendReply, this.contactRequestReply(submission));
|
|
361
|
+
}
|
|
362
|
+
catch (error) {
|
|
363
|
+
logger.warn(`[MessageBridge] Contact request submission failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
364
|
+
await this.sendAdmissionControlReply(context, msg, sendReply, '当前无法受理联系人申请,请稍后再试。');
|
|
365
|
+
}
|
|
314
366
|
return true;
|
|
315
367
|
}
|
|
316
|
-
|
|
317
|
-
|
|
368
|
+
// The structured protocol is AUN-only. For a blocked external alias,
|
|
369
|
+
// return false so the existing blacklist response/silence policy applies;
|
|
370
|
+
// never emit a custom payload through a non-AUN adapter.
|
|
371
|
+
if (context.contactStatus === 'blocked' && context.channelType !== 'aun')
|
|
372
|
+
return false;
|
|
373
|
+
if (context.channelType !== 'aun')
|
|
374
|
+
return true;
|
|
375
|
+
if (context.chatType !== 'private')
|
|
376
|
+
return true;
|
|
377
|
+
if (context.principal?.principalStatus !== 'native_aid' || !context.primaryId) {
|
|
378
|
+
await this.sendContactRequestProtocolResponse(adapter, msg, {
|
|
379
|
+
status: 'NOT_ALLOWED', requestId: null, expiresAt: null,
|
|
380
|
+
}, owningAgent?.name);
|
|
318
381
|
return true;
|
|
319
382
|
}
|
|
320
|
-
if (
|
|
321
|
-
await this.
|
|
383
|
+
if (context.contactStatus === 'blocked') {
|
|
384
|
+
await this.sendContactRequestProtocolResponse(adapter, msg, {
|
|
385
|
+
status: 'NOT_ALLOWED', requestId: null, expiresAt: null,
|
|
386
|
+
}, owningAgent?.name);
|
|
322
387
|
return true;
|
|
323
388
|
}
|
|
324
389
|
let result;
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
390
|
+
if (protocolType === 'query') {
|
|
391
|
+
try {
|
|
392
|
+
result = await queryOwnContactRequestStatus({
|
|
393
|
+
selfAid: context.selfAid,
|
|
394
|
+
applicantAid: context.primaryId,
|
|
395
|
+
requestId: protocolPayload && Object.prototype.hasOwnProperty.call(protocolPayload, 'request_id')
|
|
396
|
+
? protocolPayload.request_id
|
|
397
|
+
: undefined,
|
|
398
|
+
});
|
|
399
|
+
}
|
|
400
|
+
catch (error) {
|
|
401
|
+
logger.warn(`[MessageBridge] Contact request query failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
402
|
+
result = { status: 'NOT_ALLOWED', requestId: null, expiresAt: null };
|
|
403
|
+
}
|
|
337
404
|
}
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
405
|
+
else {
|
|
406
|
+
try {
|
|
407
|
+
if (protocolPayload && Object.prototype.hasOwnProperty.call(protocolPayload, 'note')
|
|
408
|
+
&& typeof protocolPayload?.note !== 'string') {
|
|
409
|
+
await this.sendContactRequestProtocolResponse(adapter, msg, {
|
|
410
|
+
status: 'NOT_ALLOWED', requestId: null, expiresAt: null,
|
|
411
|
+
}, owningAgent?.name);
|
|
412
|
+
return true;
|
|
413
|
+
}
|
|
414
|
+
const submission = await submitContactRequest({
|
|
415
|
+
selfAid: context.selfAid,
|
|
416
|
+
applicantAid: context.primaryId,
|
|
417
|
+
note: typeof protocolPayload?.note === 'string' ? protocolPayload.note : undefined,
|
|
418
|
+
sourceChannelKey: context.channelKey,
|
|
419
|
+
});
|
|
420
|
+
result = contactRequestSubmissionProtocolResult(submission);
|
|
421
|
+
if ((submission.code === 'submitted' || submission.code === 'resubmitted') && submission.requestId) {
|
|
422
|
+
void this.deliverContactReviewCard(context.selfAid, context.primaryId, submission, owningAgent)
|
|
423
|
+
.catch(error => logger.warn(`[MessageBridge] Contact review delivery failed: ${error instanceof Error ? error.message : String(error)}`));
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
catch (error) {
|
|
427
|
+
logger.warn(`[MessageBridge] Contact request submission failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
428
|
+
result = { status: 'NOT_ALLOWED', requestId: null, expiresAt: null };
|
|
429
|
+
}
|
|
341
430
|
}
|
|
342
|
-
await this.
|
|
431
|
+
await this.sendContactRequestProtocolResponse(adapter, msg, result, owningAgent?.name);
|
|
343
432
|
return true;
|
|
344
433
|
}
|
|
434
|
+
contactRequestReply(result) {
|
|
435
|
+
switch (result.code) {
|
|
436
|
+
case 'submitted': return '联系人申请已提交,等待管理员审核。';
|
|
437
|
+
case 'resubmitted': return '联系人申请已更新,等待管理员审核。';
|
|
438
|
+
case 'already-contact': return '你已经是联系人。';
|
|
439
|
+
case 'owner': return '你已经是 Owner。';
|
|
440
|
+
case 'blocked': return CONTACT_REJECTION_NOTICE;
|
|
441
|
+
case 'rate-limited': return '联系人申请过于频繁,请稍后再试。';
|
|
442
|
+
case 'capacity': return '当前申请较多,请稍后再试。';
|
|
443
|
+
case 'no-owner': return '当前无法受理联系人申请。';
|
|
444
|
+
case 'invalid-applicant': return '无法验证当前 AUN 身份,未提交联系人申请。';
|
|
445
|
+
case 'invalid-note': return '申请说明不能超过 500 个字符。';
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
async sendContactRequestProtocolResponse(adapter, msg, result, agentName) {
|
|
449
|
+
if (!adapter?.send)
|
|
450
|
+
return;
|
|
451
|
+
await adapter.send(buildEnvelope({
|
|
452
|
+
taskId: `contact-request-${randomBytes(4).toString('hex')}`,
|
|
453
|
+
channel: adapter.channelName,
|
|
454
|
+
channelId: msg.channelId,
|
|
455
|
+
agentName: agentName ?? '<unknown>',
|
|
456
|
+
replyContext: {
|
|
457
|
+
...(msg.replyContext ?? {}),
|
|
458
|
+
metadata: {
|
|
459
|
+
...(msg.replyContext?.metadata ?? {}),
|
|
460
|
+
...evolMenuResponseTransportMetadata(),
|
|
461
|
+
},
|
|
462
|
+
},
|
|
463
|
+
}), {
|
|
464
|
+
kind: 'custom',
|
|
465
|
+
channelType: 'aun',
|
|
466
|
+
payload: {
|
|
467
|
+
type: 'contact.request.response',
|
|
468
|
+
status: result.status,
|
|
469
|
+
request_id: result.requestId,
|
|
470
|
+
expires_at: result.expiresAt,
|
|
471
|
+
},
|
|
472
|
+
});
|
|
473
|
+
}
|
|
345
474
|
async sendAdmissionControlReply(context, msg, sendReply, text) {
|
|
346
475
|
if (!text || !consumeAdmissionReplyBudget(context))
|
|
347
476
|
return false;
|
|
@@ -360,20 +489,6 @@ export class MessageBridge {
|
|
|
360
489
|
}
|
|
361
490
|
return '当前不接受陌生人消息。联系人申请仅支持 AUN 私聊。';
|
|
362
491
|
}
|
|
363
|
-
contactRequestReply(result) {
|
|
364
|
-
switch (result.code) {
|
|
365
|
-
case 'submitted': return '联系人申请已提交,等待管理员审核。';
|
|
366
|
-
case 'resubmitted': return '联系人申请已更新,等待管理员审核。';
|
|
367
|
-
case 'already-contact': return '你已经是联系人。';
|
|
368
|
-
case 'owner': return '你已经是 Owner。';
|
|
369
|
-
case 'blocked': return CONTACT_REJECTION_NOTICE;
|
|
370
|
-
case 'rate-limited': return '联系人申请过于频繁,请稍后再试。';
|
|
371
|
-
case 'capacity': return '当前申请较多,请稍后再试。';
|
|
372
|
-
case 'no-owner': return '当前无法受理联系人申请。';
|
|
373
|
-
case 'invalid-applicant': return '无法验证当前 AUN 身份,未提交联系人申请。';
|
|
374
|
-
case 'invalid-note': return '申请说明不能超过 500 个字符。';
|
|
375
|
-
}
|
|
376
|
-
}
|
|
377
492
|
async deliverContactReviewCard(selfAid, applicantAid, submission, owningAgent) {
|
|
378
493
|
if (!this.interactionRouter || !submission.requestId || !submission.expiresAt || !submission.submittedAt) {
|
|
379
494
|
return false;
|
|
@@ -463,7 +578,7 @@ export class MessageBridge {
|
|
|
463
578
|
}
|
|
464
579
|
if (reviewed.code === 'approved' || reviewed.code === 'rejected' || reviewed.code === 'blocked') {
|
|
465
580
|
try {
|
|
466
|
-
await this.notifyContactApplicant(route.adapter, applicantAid, reviewed.code, owningAgent?.name);
|
|
581
|
+
await this.notifyContactApplicant(route.adapter, applicantAid, reviewed.code, submission.requestId, submission.expiresAt, owningAgent?.name);
|
|
467
582
|
}
|
|
468
583
|
catch (error) {
|
|
469
584
|
logger.warn(`[MessageBridge] Contact applicant notification failed: request=${submission.requestId} error=${error instanceof Error ? error.message : String(error)}`);
|
|
@@ -485,7 +600,7 @@ export class MessageBridge {
|
|
|
485
600
|
channelId: route.approverOperatorId,
|
|
486
601
|
agentName: owningAgent?.name ?? selfAid,
|
|
487
602
|
chatmode: 'interactive',
|
|
488
|
-
replyContext: { metadata: { source: 'handoff', chatmode: 'interactive' } },
|
|
603
|
+
replyContext: { delivery: { chatType: 'private' }, metadata: { source: 'handoff', chatmode: 'interactive' } },
|
|
489
604
|
});
|
|
490
605
|
const sent = await sendInteractionPayload(route.adapter, envelope, interaction, renderActionAsText(interaction), envelope.replyContext);
|
|
491
606
|
if (!sent)
|
|
@@ -500,13 +615,24 @@ export class MessageBridge {
|
|
|
500
615
|
: code === 'request-stale' ? '申请已更新或已被处理。'
|
|
501
616
|
: code === 'revision-conflict' ? '联系人列表已变化,请从联系人列表重新审核。'
|
|
502
617
|
: '当前无审核权限。';
|
|
503
|
-
await adapter.send(buildEnvelope({ channel: adapter.channelName, channelId: ownerAid, agentName: agentName ?? '<unknown>' }), { kind: 'system.notice', text, subtype: 'contact-review' });
|
|
618
|
+
await adapter.send(buildEnvelope({ channel: adapter.channelName, channelId: ownerAid, agentName: agentName ?? '<unknown>', delivery: { chatType: 'private' } }), { kind: 'system.notice', text, subtype: 'contact-review' });
|
|
504
619
|
}
|
|
505
|
-
async notifyContactApplicant(adapter, applicantAid, code, agentName) {
|
|
506
|
-
|
|
507
|
-
:
|
|
508
|
-
|
|
509
|
-
|
|
620
|
+
async notifyContactApplicant(adapter, applicantAid, code, requestId, expiresAt, agentName) {
|
|
621
|
+
await adapter.send(buildEnvelope({
|
|
622
|
+
channel: adapter.channelName,
|
|
623
|
+
channelId: applicantAid,
|
|
624
|
+
agentName: agentName ?? '<unknown>',
|
|
625
|
+
replyContext: { delivery: { chatType: 'private' }, metadata: evolMenuResponseTransportMetadata() },
|
|
626
|
+
}), {
|
|
627
|
+
kind: 'custom',
|
|
628
|
+
channelType: 'aun',
|
|
629
|
+
payload: {
|
|
630
|
+
type: 'contact.request.response',
|
|
631
|
+
status: code === 'approved' ? 'APPROVED' : code === 'rejected' ? 'REJECTED' : 'NOT_ALLOWED',
|
|
632
|
+
request_id: requestId,
|
|
633
|
+
expires_at: expiresAt,
|
|
634
|
+
},
|
|
635
|
+
});
|
|
510
636
|
}
|
|
511
637
|
getDebouncer(channelName, channelType) {
|
|
512
638
|
let d = this.debouncers.get(channelName);
|
|
@@ -561,11 +687,23 @@ export class MessageBridge {
|
|
|
561
687
|
onMessage(async (msg) => {
|
|
562
688
|
try {
|
|
563
689
|
let content = msg.content.trim();
|
|
690
|
+
// Channels that expose adapter receive time (notably AUN) must keep
|
|
691
|
+
// that timestamp through session resolution and queue processing.
|
|
692
|
+
// The fallback is only for legacy adapters that do not provide it.
|
|
693
|
+
const receivedAt = msg.receivedAt ?? Date.now();
|
|
564
694
|
const channelKey = adapter?.channelKey || channelName;
|
|
565
695
|
const owningAgent = this.agentRegistry?.resolveByChannel(channelKey)
|
|
566
696
|
?? this.agentRegistry?.resolveByChannel(channelName);
|
|
567
697
|
const parsedChannelKey = tryParseChannelKey(channelKey);
|
|
568
698
|
const chatType = msg.chatType || 'private';
|
|
699
|
+
const delivery = chatType === 'group'
|
|
700
|
+
? { chatType: 'group', groupId: msg.groupId || msg.channelId }
|
|
701
|
+
: { chatType: 'private' };
|
|
702
|
+
msg.groupId = delivery.chatType === 'group' ? delivery.groupId : undefined;
|
|
703
|
+
msg.replyContext = {
|
|
704
|
+
...(msg.replyContext ?? {}),
|
|
705
|
+
delivery,
|
|
706
|
+
};
|
|
569
707
|
const actorId = msg.peerId;
|
|
570
708
|
const conversationId = chatType === 'group' ? (msg.groupId || msg.channelId) : msg.peerId;
|
|
571
709
|
const selfAid = msg.selfAID || owningAgent?.aid || parsedChannelKey?.selfAID;
|
|
@@ -582,6 +720,16 @@ export class MessageBridge {
|
|
|
582
720
|
actorId,
|
|
583
721
|
trustedHandoffEcho,
|
|
584
722
|
});
|
|
723
|
+
if (!trustedHandoffEcho && await this.handleContactRequestProtocol({
|
|
724
|
+
context: admissionPreflight.context,
|
|
725
|
+
msg,
|
|
726
|
+
content,
|
|
727
|
+
owningAgent,
|
|
728
|
+
adapter,
|
|
729
|
+
sendReply,
|
|
730
|
+
})) {
|
|
731
|
+
return;
|
|
732
|
+
}
|
|
585
733
|
if (!admissionPreflight.allow) {
|
|
586
734
|
const requestCommand = /^\s*\/request(?:\s|$)/i.test(content);
|
|
587
735
|
if (admissionPreflight.reason === 'blocked' && admissionPreflight.context.primaryId) {
|
|
@@ -789,7 +937,9 @@ export class MessageBridge {
|
|
|
789
937
|
content: msg.messageLogContent ?? content,
|
|
790
938
|
replyTo: inboundReplyTo,
|
|
791
939
|
permMode: null,
|
|
792
|
-
timestamp:
|
|
940
|
+
timestamp: receivedAt,
|
|
941
|
+
receivedAt,
|
|
942
|
+
gatewaySeq: msg.gatewaySeq,
|
|
793
943
|
encrypt: inboundEncrypt,
|
|
794
944
|
chatmode: inboundChatmode,
|
|
795
945
|
peerName: msg.peerName,
|
|
@@ -881,9 +1031,12 @@ export class MessageBridge {
|
|
|
881
1031
|
channelType: msg.channelType || effectiveChannelType,
|
|
882
1032
|
channelId: msg.channelId, content,
|
|
883
1033
|
selfAID: msg.selfAID,
|
|
1034
|
+
groupId: msg.groupId,
|
|
884
1035
|
baseagent: session.baseagent,
|
|
885
1036
|
chatType,
|
|
886
|
-
images: msg.images, timestamp:
|
|
1037
|
+
images: msg.images, timestamp: receivedAt,
|
|
1038
|
+
receivedAt,
|
|
1039
|
+
gatewaySeq: msg.gatewaySeq,
|
|
887
1040
|
peerId: msg.peerId, peerName: msg.peerName,
|
|
888
1041
|
peerType: msg.peerType,
|
|
889
1042
|
sameDevice: msg.sameDevice,
|
|
@@ -900,7 +1053,7 @@ export class MessageBridge {
|
|
|
900
1053
|
source: msg.source,
|
|
901
1054
|
boundSessionId: session.id,
|
|
902
1055
|
resolvedIdentity: identity,
|
|
903
|
-
|
|
1056
|
+
mentionMode: msg.mentionMode,
|
|
904
1057
|
causation: inboundCausation,
|
|
905
1058
|
};
|
|
906
1059
|
recordCausationSpan(inboundCausation, 'message.inbound', {
|
|
@@ -926,6 +1079,8 @@ export class MessageBridge {
|
|
|
926
1079
|
replyTo: inboundReplyTo,
|
|
927
1080
|
permMode: session.identity?.role ?? null,
|
|
928
1081
|
timestamp: fullMessage.timestamp,
|
|
1082
|
+
receivedAt: msg.receivedAt,
|
|
1083
|
+
gatewaySeq: msg.gatewaySeq,
|
|
929
1084
|
encrypt: inboundEncrypt,
|
|
930
1085
|
chatmode: inboundChatmode,
|
|
931
1086
|
peerName: msg.peerName,
|
|
@@ -1027,28 +1182,6 @@ export class MessageBridge {
|
|
|
1027
1182
|
});
|
|
1028
1183
|
}
|
|
1029
1184
|
// ── Menu Protocol ──
|
|
1030
|
-
static MENU_NAME_MAP = {
|
|
1031
|
-
pwd: '/pwd',
|
|
1032
|
-
session: '/session',
|
|
1033
|
-
topic: '/topic',
|
|
1034
|
-
baseagent: '/baseagent',
|
|
1035
|
-
model: '/model',
|
|
1036
|
-
effort: '/effort',
|
|
1037
|
-
chatmode: '/chatmode',
|
|
1038
|
-
mentionmode: '/mentionmode',
|
|
1039
|
-
group: '/group',
|
|
1040
|
-
permission: '/perm',
|
|
1041
|
-
activity: '/activity',
|
|
1042
|
-
observable: '/observable',
|
|
1043
|
-
system: '/system',
|
|
1044
|
-
cli: '/cli',
|
|
1045
|
-
agent: '/agent',
|
|
1046
|
-
trigger: '/trigger',
|
|
1047
|
-
file: '/file',
|
|
1048
|
-
capability: '/capability',
|
|
1049
|
-
role: '/role',
|
|
1050
|
-
connect: '/connect',
|
|
1051
|
-
};
|
|
1052
1185
|
extractTopicName(msg) {
|
|
1053
1186
|
const raw = msg.topicName
|
|
1054
1187
|
?? msg.replyContext?.title
|
|
@@ -1077,7 +1210,7 @@ export class MessageBridge {
|
|
|
1077
1210
|
resolveCmd(name, cmd) {
|
|
1078
1211
|
if (cmd)
|
|
1079
1212
|
return cmd;
|
|
1080
|
-
const mapped =
|
|
1213
|
+
const mapped = menuCommandForName(name);
|
|
1081
1214
|
if (!mapped)
|
|
1082
1215
|
throw { code: 'NOT_SUPPORTED', message: `Unsupported menu name: ${name}` };
|
|
1083
1216
|
return mapped;
|
|
@@ -1086,6 +1219,31 @@ export class MessageBridge {
|
|
|
1086
1219
|
if (!hasValidMenuId(parsed))
|
|
1087
1220
|
return;
|
|
1088
1221
|
const header = { id: parsed.id, ...(parsed.name?.trim() ? { name: parsed.name } : {}) };
|
|
1222
|
+
// Menu IDs are the protocol's stable correlation key. Bind it, together
|
|
1223
|
+
// with the active EvolCore session, before authorization so every
|
|
1224
|
+
// CommandAudit record and denial can be joined to the same request.
|
|
1225
|
+
let activeSession;
|
|
1226
|
+
if (msg.threadId && typeof this.sessionManager.getThreadSession === 'function') {
|
|
1227
|
+
try {
|
|
1228
|
+
activeSession = await this.sessionManager.getThreadSession(channel, msg.channelId, msg.threadId);
|
|
1229
|
+
}
|
|
1230
|
+
catch {
|
|
1231
|
+
activeSession = undefined;
|
|
1232
|
+
}
|
|
1233
|
+
}
|
|
1234
|
+
if (!activeSession && typeof this.sessionManager.getActiveSession === 'function') {
|
|
1235
|
+
try {
|
|
1236
|
+
activeSession = await this.sessionManager.getActiveSession(channel, msg.channelId);
|
|
1237
|
+
}
|
|
1238
|
+
catch {
|
|
1239
|
+
activeSession = undefined;
|
|
1240
|
+
}
|
|
1241
|
+
}
|
|
1242
|
+
const menuAuthSubject = {
|
|
1243
|
+
...authSubject,
|
|
1244
|
+
requestId: parsed.id,
|
|
1245
|
+
...(activeSession?.id ? { sessionId: activeSession.id } : {}),
|
|
1246
|
+
};
|
|
1089
1247
|
const isTokenRequest = parsed.type === 'menu.token.request';
|
|
1090
1248
|
if (isTokenRequest) {
|
|
1091
1249
|
if (msg.chatType !== 'private') {
|
|
@@ -1104,7 +1262,7 @@ export class MessageBridge {
|
|
|
1104
1262
|
}
|
|
1105
1263
|
}
|
|
1106
1264
|
if (!isTokenRequest) {
|
|
1107
|
-
const access = authorizeAccess(
|
|
1265
|
+
const access = authorizeAccess(menuAuthSubject);
|
|
1108
1266
|
if (!access.allow) {
|
|
1109
1267
|
this.logMenuDiagnostic('access-denied', channel, msg, parsed, access.code);
|
|
1110
1268
|
await this.sendMenuResponse(adapter, channel, msg, menuFailure(header, {
|
|
@@ -1119,7 +1277,7 @@ export class MessageBridge {
|
|
|
1119
1277
|
let afterResponse;
|
|
1120
1278
|
const deduped = await this.menuDeduper.execute(dedupKey, menuPayloadFingerprint(parsed.raw), () => isTokenRequest
|
|
1121
1279
|
? this.issueMenuTokenResponse(header, channel, msg)
|
|
1122
|
-
: this.dispatchMenuRequest(parsed.request, header, channel, msg,
|
|
1280
|
+
: this.dispatchMenuRequest(parsed.request, header, channel, msg, menuAuthSubject, effect => { afterResponse = effect; }));
|
|
1123
1281
|
if ('conflict' in deduped) {
|
|
1124
1282
|
this.logMenuDiagnostic('request-conflict', channel, msg, parsed, 'CONFLICT');
|
|
1125
1283
|
await this.sendMenuResponse(adapter, channel, msg, menuFailure(header, {
|
|
@@ -1130,6 +1288,8 @@ export class MessageBridge {
|
|
|
1130
1288
|
}
|
|
1131
1289
|
if (deduped.replayed)
|
|
1132
1290
|
this.logMenuDiagnostic('request-replay', channel, msg, parsed);
|
|
1291
|
+
if (deduped.retry)
|
|
1292
|
+
this.logMenuDiagnostic('request-retry', channel, msg, parsed);
|
|
1133
1293
|
const response = responseWarning && 'data' in deduped.value
|
|
1134
1294
|
? { ...deduped.value, warning: responseWarning }
|
|
1135
1295
|
: deduped.value;
|
|
@@ -1152,29 +1312,73 @@ export class MessageBridge {
|
|
|
1152
1312
|
const args = request.args;
|
|
1153
1313
|
switch (request.type) {
|
|
1154
1314
|
case 'menu.list':
|
|
1315
|
+
if (args?.view === 'catalog') {
|
|
1316
|
+
validateMenuCatalogArgs(args);
|
|
1317
|
+
const data = await this.cmdHandler.getMenuCatalog(channel, msg.channelId, authSubject, args.context, msg.isControlChannel ? 'control' : 'agent');
|
|
1318
|
+
return menuSuccess(header, data);
|
|
1319
|
+
}
|
|
1320
|
+
if (args?.view !== undefined) {
|
|
1321
|
+
return menuFailure(header, { code: 'NOT_SUPPORTED', message: `Unsupported menu.list view: ${String(args.view)}` });
|
|
1322
|
+
}
|
|
1155
1323
|
return menuSuccess(header, this.cmdHandler.getMenuItems(identity.role, msg.chatType || 'private', msg.isControlChannel ? 'control' : 'agent', authSubject));
|
|
1156
1324
|
case 'menu.query': {
|
|
1157
|
-
|
|
1325
|
+
if (name === 'menu' && args?.view === 'values') {
|
|
1326
|
+
validateMenuValueBatchArgs(args);
|
|
1327
|
+
const data = await this.cmdHandler.execMenuValueBatch(channel, msg.channelId, authSubject, args, args.context, msg.isControlChannel ? 'control' : 'agent');
|
|
1328
|
+
return menuSuccess(header, data);
|
|
1329
|
+
}
|
|
1330
|
+
if (name === 'menu') {
|
|
1331
|
+
return menuFailure(header, { code: 'NOT_SUPPORTED', message: `Unsupported menu.query view: ${String(args?.view)}` });
|
|
1332
|
+
}
|
|
1333
|
+
const execution = await resolveExplicitMenuExecutionContext({
|
|
1334
|
+
subject: authSubject,
|
|
1335
|
+
channelType: authSubject.channelType,
|
|
1336
|
+
channelScope: msg.isControlChannel ? 'control' : 'agent',
|
|
1337
|
+
channelId: msg.channelId,
|
|
1338
|
+
args,
|
|
1339
|
+
});
|
|
1340
|
+
const result = await this.cmdHandler.execMenuQuery(this.resolveCmd(name, cmd), channel, execution.channelId, execution.subject.actorId, execution.args, execution.subject.chatType, execution.subject.fromControlChannel, execution.subject.identity, execution.subject);
|
|
1158
1341
|
if ('error' in result)
|
|
1159
1342
|
throw result;
|
|
1160
1343
|
return menuSuccess(header, result.data);
|
|
1161
1344
|
}
|
|
1162
1345
|
case 'menu.options': {
|
|
1163
|
-
|
|
1346
|
+
const execution = await resolveExplicitMenuExecutionContext({
|
|
1347
|
+
subject: authSubject,
|
|
1348
|
+
channelType: authSubject.channelType,
|
|
1349
|
+
channelScope: msg.isControlChannel ? 'control' : 'agent',
|
|
1350
|
+
channelId: msg.channelId,
|
|
1351
|
+
args,
|
|
1352
|
+
});
|
|
1353
|
+
let optionArgs = execution.args;
|
|
1164
1354
|
if ((name === 'topic' || cmd === '/topic') && args?.mode === 'fork-turns' && msg.threadId && !args?.sourceThreadId) {
|
|
1165
|
-
optionArgs = { ...(args ?? {}), sourceThreadId: msg.threadId };
|
|
1355
|
+
optionArgs = { ...(execution.args ?? {}), sourceThreadId: msg.threadId };
|
|
1166
1356
|
}
|
|
1167
|
-
const data = await this.cmdHandler.getSubMenuItems(this.resolveCmd(name, cmd), channel,
|
|
1357
|
+
const data = await this.cmdHandler.getSubMenuItems(this.resolveCmd(name, cmd), channel, execution.channelId, execution.subject.actorId, optionArgs, execution.subject.identity, execution.subject.chatType, execution.subject.fromControlChannel, execution.subject) ?? [];
|
|
1168
1358
|
return menuSuccess(header, data);
|
|
1169
1359
|
}
|
|
1170
1360
|
case 'menu.update': {
|
|
1171
|
-
const
|
|
1361
|
+
const execution = await resolveExplicitMenuExecutionContext({
|
|
1362
|
+
subject: authSubject,
|
|
1363
|
+
channelType: authSubject.channelType,
|
|
1364
|
+
channelScope: msg.isControlChannel ? 'control' : 'agent',
|
|
1365
|
+
channelId: msg.channelId,
|
|
1366
|
+
args,
|
|
1367
|
+
});
|
|
1368
|
+
const result = await this.cmdHandler.execMenuUpdate(this.resolveCmd(name, cmd), request.value, channel, execution.channelId, execution.subject.actorId, execution.subject.identity, execution.subject.fromControlChannel, execution.args, execution.subject);
|
|
1172
1369
|
if ('error' in result)
|
|
1173
1370
|
throw result;
|
|
1174
1371
|
return menuSuccess(header, result.data);
|
|
1175
1372
|
}
|
|
1176
1373
|
case 'menu.action': {
|
|
1177
|
-
|
|
1374
|
+
const execution = await resolveExplicitMenuExecutionContext({
|
|
1375
|
+
subject: authSubject,
|
|
1376
|
+
channelType: authSubject.channelType,
|
|
1377
|
+
channelScope: msg.isControlChannel ? 'control' : 'agent',
|
|
1378
|
+
channelId: msg.channelId,
|
|
1379
|
+
args,
|
|
1380
|
+
});
|
|
1381
|
+
let actionArgs = execution.args;
|
|
1178
1382
|
if ((name === 'topic' || cmd === '/topic') && request.action === 'fork' && msg.threadId) {
|
|
1179
1383
|
if (typeof args?.targetThreadId === 'string' && args.targetThreadId !== msg.threadId) {
|
|
1180
1384
|
return menuFailure(header, {
|
|
@@ -1182,11 +1386,11 @@ export class MessageBridge {
|
|
|
1182
1386
|
message: 'args.targetThreadId 必须与消息 thread_id 一致',
|
|
1183
1387
|
});
|
|
1184
1388
|
}
|
|
1185
|
-
actionArgs = { ...(args ?? {}), targetThreadId: msg.threadId };
|
|
1389
|
+
actionArgs = { ...(execution.args ?? {}), targetThreadId: msg.threadId };
|
|
1186
1390
|
}
|
|
1187
1391
|
const resolvedCmd = this.resolveCmd(name, cmd);
|
|
1188
1392
|
const isFileFetch = resolvedCmd.trim().split(' ')[0] === '/file' && request.action === 'fetch';
|
|
1189
|
-
const result = await this.cmdHandler.execMenuAction(resolvedCmd, request.action, actionArgs, channel,
|
|
1393
|
+
const result = await this.cmdHandler.execMenuAction(resolvedCmd, request.action, actionArgs, channel, execution.channelId, execution.subject.actorId, execution.subject.identity, execution.subject.chatType, header.id, execution.subject.fromControlChannel, execution.subject, 'menu', msg.replyContext, isFileFetch ? registerAfterResponse : undefined);
|
|
1190
1394
|
if ('error' in result)
|
|
1191
1395
|
throw result;
|
|
1192
1396
|
return menuSuccess(header, result.data);
|
|
@@ -1222,6 +1426,10 @@ export class MessageBridge {
|
|
|
1222
1426
|
...evolMenuResponseTransportMetadata(),
|
|
1223
1427
|
},
|
|
1224
1428
|
};
|
|
1429
|
+
if (msg.channelType === 'aun' && !replyContext.delivery) {
|
|
1430
|
+
logger.warn(`[MessageBridge] dropping AUN menu response without trusted delivery route: channel=${channel} channelId=${msg.channelId}`);
|
|
1431
|
+
return;
|
|
1432
|
+
}
|
|
1225
1433
|
const envelope = buildEnvelope({
|
|
1226
1434
|
taskId: `menu-${randomBytes(4).toString('hex')}`,
|
|
1227
1435
|
channel,
|
|
@@ -1280,6 +1488,8 @@ export class MessageBridge {
|
|
|
1280
1488
|
logger.warn(`[MenuControl] category=${category} request=${parsed.id || '<missing>'}`
|
|
1281
1489
|
+ ` type=${parsed.type} name=${parsed.name || '<none>'} action=${parsed.action || '<none>'}`
|
|
1282
1490
|
+ ` transport=${channel} scope=${this.shortHash(scope)} sender=${this.shortHash(msg.peerId)}`
|
|
1491
|
+
+ (category === 'request-replay' ? ' replay=true' : '')
|
|
1492
|
+
+ (category === 'request-retry' ? ' retry=true' : '')
|
|
1283
1493
|
+ `${code ? ` code=${code}` : ''}${decision.suppressed ? ` suppressed=${decision.suppressed}` : ''}`);
|
|
1284
1494
|
}
|
|
1285
1495
|
shortHash(value) {
|
|
@@ -1398,17 +1608,17 @@ export class MessageBridge {
|
|
|
1398
1608
|
* 撤回消息:先查 debounce 窗口,再查 message queue,最后查正在执行的任务。
|
|
1399
1609
|
* @returns true 如果找到并取消/中断
|
|
1400
1610
|
*/
|
|
1401
|
-
cancel(messageId) {
|
|
1611
|
+
cancel(messageId, selfAID) {
|
|
1402
1612
|
// 阶段 1: debounce 窗口(尚未入队)
|
|
1403
1613
|
for (const d of this.debouncers.values()) {
|
|
1404
|
-
if (d.cancel(messageId))
|
|
1614
|
+
if (d.cancel(messageId, selfAID))
|
|
1405
1615
|
return true;
|
|
1406
1616
|
}
|
|
1407
1617
|
// 阶段 2: 已入队但未处理(合并后 messageId 可能是逗号分隔的多个 id)
|
|
1408
|
-
if (this.messageQueue.cancel(messageId))
|
|
1618
|
+
if (this.messageQueue.cancel(messageId, selfAID))
|
|
1409
1619
|
return true;
|
|
1410
1620
|
// 阶段 3: 正在执行的任务 → 触发 interrupt
|
|
1411
|
-
return this.messageQueue.cancelActive(messageId);
|
|
1621
|
+
return this.messageQueue.cancelActive(messageId, selfAID);
|
|
1412
1622
|
}
|
|
1413
1623
|
/** 清理资源 */
|
|
1414
1624
|
dispose() {
|
|
@@ -3,6 +3,7 @@ import path from 'path';
|
|
|
3
3
|
import { appendJsonl, chatDirPath } from '../session/session-fs-store.js';
|
|
4
4
|
import { logger } from '../../utils/logger.js';
|
|
5
5
|
const TRANSIENT_PROTOCOL_TYPE_PREFIXES = [
|
|
6
|
+
'contact.request.',
|
|
6
7
|
'menu.',
|
|
7
8
|
'status.',
|
|
8
9
|
'event.',
|
|
@@ -288,6 +289,8 @@ export function buildInboundEntry(opts) {
|
|
|
288
289
|
const isCommand = isCommandCapable && opts.content.startsWith('/');
|
|
289
290
|
return {
|
|
290
291
|
ts,
|
|
292
|
+
...(opts.receivedAt !== undefined ? { receivedAt: opts.receivedAt } : {}),
|
|
293
|
+
...(opts.gatewaySeq !== undefined ? { gatewaySeq: opts.gatewaySeq } : {}),
|
|
291
294
|
time: formatTimestampMs(ts),
|
|
292
295
|
sessionId: opts.sessionId,
|
|
293
296
|
threadId: opts.threadId ?? null,
|