evolcore 0.0.10 → 0.0.12
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/README.md +3 -3
- package/bin/codex-managed-hook.mjs +80 -0
- package/dist/agents/baseagent.js +4 -0
- package/dist/agents/claude-runner.js +158 -44
- package/dist/agents/codex-app-server-client.js +153 -23
- package/dist/agents/codex-runner.js +331 -43
- package/dist/agents/ecagent-runner.js +53 -6
- package/dist/agents/gemini-runner.js +1 -0
- package/dist/agents/request-identity.js +55 -0
- package/dist/aun/aid/client.js +11 -21
- package/dist/aun/aid/managed-operation.js +107 -0
- package/dist/aun/msg/group.js +81 -12
- package/dist/aun/msg/managed-operation.js +804 -0
- package/dist/aun/msg/mention-schema.js +20 -0
- package/dist/aun/msg/p2p.js +7 -0
- package/dist/aun/outbox.js +28 -31
- package/dist/channels/aun.js +418 -235
- package/dist/channels/daemon.js +13 -0
- package/dist/cli/agent-command.js +72 -17
- package/dist/cli/agent.js +249 -22
- package/dist/cli/aun-commands.js +270 -69
- package/dist/cli/bench.js +12 -3
- package/dist/cli/daemon-commands.js +52 -33
- package/dist/cli/fs-command.js +60 -0
- package/dist/cli/handoff-command.js +3 -0
- package/dist/cli/index.js +106 -63
- package/dist/cli/init-cancel.js +208 -0
- package/dist/cli/init-channel.js +343 -195
- package/dist/cli/init.js +21 -9
- package/dist/cli/managed-command-guard.js +41 -0
- package/dist/cli/model.js +11 -4
- package/dist/cli/queue-command.js +11 -1
- package/dist/cli/response.js +71 -0
- package/dist/cli/restart-monitor.js +4 -18
- package/dist/cli/trigger-command.js +4 -0
- package/dist/config/builtin-roles.js +5 -0
- package/dist/config/contact-book-store.js +11 -3
- package/dist/config/contact-request-service.js +161 -15
- package/dist/config/gateway-config.js +26 -10
- package/dist/config/mention-mode.js +8 -24
- package/dist/core/agent-reload-coordinator.js +53 -0
- package/dist/core/auth/agent-delegation.js +0 -1
- package/dist/core/auth/operation-authorizer.js +62 -154
- package/dist/core/auth/operation-catalog.js +264 -22
- package/dist/core/bootstrap-messages.js +68 -0
- package/dist/core/bootstrap-service.js +144 -10
- 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 +24 -7
- package/dist/core/command/agent-control.js +14 -11
- package/dist/core/command/cli-intent-parser.js +9 -0
- package/dist/core/command/command-handler.js +467 -6
- 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 +366 -0
- package/dist/core/command/menu-handler.js +213 -110
- package/dist/core/command/menu-protocol.js +71 -0
- package/dist/core/command/slash-gate.js +5 -2
- package/dist/core/command/slash-handler.js +45 -25
- package/dist/core/daemon-file-cache.js +2 -1
- package/dist/core/data-migration.js +9 -2
- package/dist/core/evolagent-registry.js +125 -35
- package/dist/core/evolagent.js +12 -5
- package/dist/core/handoff/dispatcher.js +5 -2
- package/dist/core/handoff/runtime.js +145 -21
- package/dist/core/handoff/store.js +24 -0
- package/dist/core/handoff/types.js +1 -0
- package/dist/core/inference/text-inference.js +38 -4
- 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 +237 -79
- package/dist/core/message/message-log.js +23 -0
- package/dist/core/message/message-queue.js +20 -4
- package/dist/core/message/response-engine.js +54 -36
- package/dist/core/model/model-catalog.js +143 -24
- package/dist/core/model/model-diagnostics.js +28 -10
- package/dist/core/permission/ec-command-parser.js +156 -10
- package/dist/core/permission/index.js +1 -0
- package/dist/core/permission/readonly-shell-query.js +532 -0
- package/dist/core/permission/sandbox-runtime.js +22 -1
- package/dist/core/permission/shell-environment.js +46 -0
- package/dist/core/permission/tool-policy.js +292 -111
- package/dist/core/protected-paths.js +17 -9
- package/dist/core/runner-reload-transaction.js +57 -0
- package/dist/core/session/session-manager.js +21 -2
- package/dist/eck/manifest-engine.js +39 -13
- package/dist/index.js +644 -180
- package/dist/ipc.js +246 -19
- 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/script-executor.js +1 -0
- package/dist/utils/aid-bind.js +3 -8
- 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/log-writer.js +6 -10
- package/dist/utils/logger.js +5 -5
- 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/msg.md +13 -0
- 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/rules/01-overview.md +9 -0
- package/kits/rules/04-relation.md +1 -1
- package/kits/rules/05-venue.md +2 -2
- package/kits/schemas/agent-config.schema.3.json +1 -1
- package/kits/schemas/agent-config.schema.4.json +1 -1
- package/kits/schemas/relation-config.schema.2.json +1 -1
- package/kits/schemas/role-config.schema.1.json +1 -1
- 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 +44 -0
- package/kits/templates/roles/visitor.json +40 -2
- package/kits/templates/system-fragments/bootstrap.md +12 -6
- package/kits/templates/system-fragments/channel.md +6 -0
- package/kits/templates/system-fragments/session.md +2 -0
- package/package.json +3 -2
- package/skills/eclink/SKILL.md +15 -3
- package/skills/eclink/agents/openai.yaml +3 -3
- package/dist/core/command/evol-menu-version-gate.js +0 -39
|
@@ -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)}`);
|
|
@@ -502,11 +617,22 @@ export class MessageBridge {
|
|
|
502
617
|
: '当前无审核权限。';
|
|
503
618
|
await adapter.send(buildEnvelope({ channel: adapter.channelName, channelId: ownerAid, agentName: agentName ?? '<unknown>' }), { 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: { 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);
|
|
@@ -582,6 +708,16 @@ export class MessageBridge {
|
|
|
582
708
|
actorId,
|
|
583
709
|
trustedHandoffEcho,
|
|
584
710
|
});
|
|
711
|
+
if (!trustedHandoffEcho && await this.handleContactRequestProtocol({
|
|
712
|
+
context: admissionPreflight.context,
|
|
713
|
+
msg,
|
|
714
|
+
content,
|
|
715
|
+
owningAgent,
|
|
716
|
+
adapter,
|
|
717
|
+
sendReply,
|
|
718
|
+
})) {
|
|
719
|
+
return;
|
|
720
|
+
}
|
|
585
721
|
if (!admissionPreflight.allow) {
|
|
586
722
|
const requestCommand = /^\s*\/request(?:\s|$)/i.test(content);
|
|
587
723
|
if (admissionPreflight.reason === 'blocked' && admissionPreflight.context.primaryId) {
|
|
@@ -760,7 +896,7 @@ export class MessageBridge {
|
|
|
760
896
|
channelKey,
|
|
761
897
|
channelType: msg.channelType || effectiveChannelType,
|
|
762
898
|
channelId: msg.channelId,
|
|
763
|
-
recipientId: actorId,
|
|
899
|
+
recipientId: roleDetail.actor.principalId || actorId,
|
|
764
900
|
recipientName: msg.peerName,
|
|
765
901
|
source: 'inbound',
|
|
766
902
|
});
|
|
@@ -900,7 +1036,7 @@ export class MessageBridge {
|
|
|
900
1036
|
source: msg.source,
|
|
901
1037
|
boundSessionId: session.id,
|
|
902
1038
|
resolvedIdentity: identity,
|
|
903
|
-
|
|
1039
|
+
mentionMode: msg.mentionMode,
|
|
904
1040
|
causation: inboundCausation,
|
|
905
1041
|
};
|
|
906
1042
|
recordCausationSpan(inboundCausation, 'message.inbound', {
|
|
@@ -1027,28 +1163,6 @@ export class MessageBridge {
|
|
|
1027
1163
|
});
|
|
1028
1164
|
}
|
|
1029
1165
|
// ── 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
1166
|
extractTopicName(msg) {
|
|
1053
1167
|
const raw = msg.topicName
|
|
1054
1168
|
?? msg.replyContext?.title
|
|
@@ -1077,7 +1191,7 @@ export class MessageBridge {
|
|
|
1077
1191
|
resolveCmd(name, cmd) {
|
|
1078
1192
|
if (cmd)
|
|
1079
1193
|
return cmd;
|
|
1080
|
-
const mapped =
|
|
1194
|
+
const mapped = menuCommandForName(name);
|
|
1081
1195
|
if (!mapped)
|
|
1082
1196
|
throw { code: 'NOT_SUPPORTED', message: `Unsupported menu name: ${name}` };
|
|
1083
1197
|
return mapped;
|
|
@@ -1152,29 +1266,73 @@ export class MessageBridge {
|
|
|
1152
1266
|
const args = request.args;
|
|
1153
1267
|
switch (request.type) {
|
|
1154
1268
|
case 'menu.list':
|
|
1269
|
+
if (args?.view === 'catalog') {
|
|
1270
|
+
validateMenuCatalogArgs(args);
|
|
1271
|
+
const data = await this.cmdHandler.getMenuCatalog(channel, msg.channelId, authSubject, args.context, msg.isControlChannel ? 'control' : 'agent');
|
|
1272
|
+
return menuSuccess(header, data);
|
|
1273
|
+
}
|
|
1274
|
+
if (args?.view !== undefined) {
|
|
1275
|
+
return menuFailure(header, { code: 'NOT_SUPPORTED', message: `Unsupported menu.list view: ${String(args.view)}` });
|
|
1276
|
+
}
|
|
1155
1277
|
return menuSuccess(header, this.cmdHandler.getMenuItems(identity.role, msg.chatType || 'private', msg.isControlChannel ? 'control' : 'agent', authSubject));
|
|
1156
1278
|
case 'menu.query': {
|
|
1157
|
-
|
|
1279
|
+
if (name === 'menu' && args?.view === 'values') {
|
|
1280
|
+
validateMenuValueBatchArgs(args);
|
|
1281
|
+
const data = await this.cmdHandler.execMenuValueBatch(channel, msg.channelId, authSubject, args, args.context, msg.isControlChannel ? 'control' : 'agent');
|
|
1282
|
+
return menuSuccess(header, data);
|
|
1283
|
+
}
|
|
1284
|
+
if (name === 'menu') {
|
|
1285
|
+
return menuFailure(header, { code: 'NOT_SUPPORTED', message: `Unsupported menu.query view: ${String(args?.view)}` });
|
|
1286
|
+
}
|
|
1287
|
+
const execution = await resolveExplicitMenuExecutionContext({
|
|
1288
|
+
subject: authSubject,
|
|
1289
|
+
channelType: authSubject.channelType,
|
|
1290
|
+
channelScope: msg.isControlChannel ? 'control' : 'agent',
|
|
1291
|
+
channelId: msg.channelId,
|
|
1292
|
+
args,
|
|
1293
|
+
});
|
|
1294
|
+
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
1295
|
if ('error' in result)
|
|
1159
1296
|
throw result;
|
|
1160
1297
|
return menuSuccess(header, result.data);
|
|
1161
1298
|
}
|
|
1162
1299
|
case 'menu.options': {
|
|
1163
|
-
|
|
1300
|
+
const execution = await resolveExplicitMenuExecutionContext({
|
|
1301
|
+
subject: authSubject,
|
|
1302
|
+
channelType: authSubject.channelType,
|
|
1303
|
+
channelScope: msg.isControlChannel ? 'control' : 'agent',
|
|
1304
|
+
channelId: msg.channelId,
|
|
1305
|
+
args,
|
|
1306
|
+
});
|
|
1307
|
+
let optionArgs = execution.args;
|
|
1164
1308
|
if ((name === 'topic' || cmd === '/topic') && args?.mode === 'fork-turns' && msg.threadId && !args?.sourceThreadId) {
|
|
1165
|
-
optionArgs = { ...(args ?? {}), sourceThreadId: msg.threadId };
|
|
1309
|
+
optionArgs = { ...(execution.args ?? {}), sourceThreadId: msg.threadId };
|
|
1166
1310
|
}
|
|
1167
|
-
const data = await this.cmdHandler.getSubMenuItems(this.resolveCmd(name, cmd), channel,
|
|
1311
|
+
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
1312
|
return menuSuccess(header, data);
|
|
1169
1313
|
}
|
|
1170
1314
|
case 'menu.update': {
|
|
1171
|
-
const
|
|
1315
|
+
const execution = await resolveExplicitMenuExecutionContext({
|
|
1316
|
+
subject: authSubject,
|
|
1317
|
+
channelType: authSubject.channelType,
|
|
1318
|
+
channelScope: msg.isControlChannel ? 'control' : 'agent',
|
|
1319
|
+
channelId: msg.channelId,
|
|
1320
|
+
args,
|
|
1321
|
+
});
|
|
1322
|
+
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
1323
|
if ('error' in result)
|
|
1173
1324
|
throw result;
|
|
1174
1325
|
return menuSuccess(header, result.data);
|
|
1175
1326
|
}
|
|
1176
1327
|
case 'menu.action': {
|
|
1177
|
-
|
|
1328
|
+
const execution = await resolveExplicitMenuExecutionContext({
|
|
1329
|
+
subject: authSubject,
|
|
1330
|
+
channelType: authSubject.channelType,
|
|
1331
|
+
channelScope: msg.isControlChannel ? 'control' : 'agent',
|
|
1332
|
+
channelId: msg.channelId,
|
|
1333
|
+
args,
|
|
1334
|
+
});
|
|
1335
|
+
let actionArgs = execution.args;
|
|
1178
1336
|
if ((name === 'topic' || cmd === '/topic') && request.action === 'fork' && msg.threadId) {
|
|
1179
1337
|
if (typeof args?.targetThreadId === 'string' && args.targetThreadId !== msg.threadId) {
|
|
1180
1338
|
return menuFailure(header, {
|
|
@@ -1182,11 +1340,11 @@ export class MessageBridge {
|
|
|
1182
1340
|
message: 'args.targetThreadId 必须与消息 thread_id 一致',
|
|
1183
1341
|
});
|
|
1184
1342
|
}
|
|
1185
|
-
actionArgs = { ...(args ?? {}), targetThreadId: msg.threadId };
|
|
1343
|
+
actionArgs = { ...(execution.args ?? {}), targetThreadId: msg.threadId };
|
|
1186
1344
|
}
|
|
1187
1345
|
const resolvedCmd = this.resolveCmd(name, cmd);
|
|
1188
1346
|
const isFileFetch = resolvedCmd.trim().split(' ')[0] === '/file' && request.action === 'fetch';
|
|
1189
|
-
const result = await this.cmdHandler.execMenuAction(resolvedCmd, request.action, actionArgs, channel,
|
|
1347
|
+
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
1348
|
if ('error' in result)
|
|
1191
1349
|
throw result;
|
|
1192
1350
|
return menuSuccess(header, result.data);
|
|
@@ -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.',
|
|
@@ -229,6 +230,27 @@ function formatTimestampMs(epochMs) {
|
|
|
229
230
|
export function messageLogPath(chatDir) {
|
|
230
231
|
return path.join(chatDir, MESSAGE_LOG_FILE);
|
|
231
232
|
}
|
|
233
|
+
export function hasMessageLogOperation(chatDir, operationId) {
|
|
234
|
+
const file = messageLogPath(chatDir);
|
|
235
|
+
if (!fs.existsSync(file))
|
|
236
|
+
return false;
|
|
237
|
+
try {
|
|
238
|
+
return fs.readFileSync(file, 'utf-8').split('\n').some(line => {
|
|
239
|
+
if (!line.trim())
|
|
240
|
+
return false;
|
|
241
|
+
try {
|
|
242
|
+
const entry = JSON.parse(line);
|
|
243
|
+
return entry?.dir === 'out' && entry.operationId === operationId;
|
|
244
|
+
}
|
|
245
|
+
catch {
|
|
246
|
+
return false;
|
|
247
|
+
}
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
catch {
|
|
251
|
+
return false;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
232
254
|
export function resolveChatDir(sessionsDir, channelType, channelId, selfAID) {
|
|
233
255
|
return chatDirPath(sessionsDir, channelType, channelId, selfAID);
|
|
234
256
|
}
|
|
@@ -323,6 +345,7 @@ export function buildOutboundEntry(opts) {
|
|
|
323
345
|
transport: opts.transport,
|
|
324
346
|
peerType: opts.peerType,
|
|
325
347
|
source: opts.source ?? 'daemon',
|
|
348
|
+
operationId: opts.operationId,
|
|
326
349
|
handoff_trace: opts.handoff_trace,
|
|
327
350
|
};
|
|
328
351
|
}
|
|
@@ -1075,6 +1075,7 @@ export class MessageQueue {
|
|
|
1075
1075
|
content: m.content, timestamp: m.timestamp,
|
|
1076
1076
|
causation: m.causation,
|
|
1077
1077
|
images: m.images && m.images.length > 0 ? m.images : undefined,
|
|
1078
|
+
mentions: m.mentions && m.mentions.length > 0 ? m.mentions : undefined,
|
|
1078
1079
|
mentionAids: m.mentionAids && m.mentionAids.length > 0 ? m.mentionAids : undefined,
|
|
1079
1080
|
});
|
|
1080
1081
|
}
|
|
@@ -1361,9 +1362,9 @@ export class MessageQueue {
|
|
|
1361
1362
|
isAgentMuted(agentName) {
|
|
1362
1363
|
return this.mutedAgents.has(agentName);
|
|
1363
1364
|
}
|
|
1364
|
-
|
|
1365
|
-
interruptByAgent(agentName) {
|
|
1365
|
+
beginAgentInterrupt(agentName) {
|
|
1366
1366
|
let interrupted = 0;
|
|
1367
|
+
const barriers = [];
|
|
1367
1368
|
for (const [queueKey, name] of this.processingAgent) {
|
|
1368
1369
|
if ((name || DEFAULT_AGENT_NAME) === agentName) {
|
|
1369
1370
|
interrupted++;
|
|
@@ -1378,11 +1379,26 @@ export class MessageQueue {
|
|
|
1378
1379
|
causation: this.activeBatches.get(queueKey)?.message.causation,
|
|
1379
1380
|
});
|
|
1380
1381
|
if (this.interruptCallback) {
|
|
1381
|
-
|
|
1382
|
+
barriers.push(this.triggerInterrupt(queueKey, sessionKey, activeState?.baseagent, name, 'stop'));
|
|
1382
1383
|
}
|
|
1383
1384
|
}
|
|
1384
1385
|
}
|
|
1385
|
-
return
|
|
1386
|
+
return {
|
|
1387
|
+
count: interrupted,
|
|
1388
|
+
barrier: Promise.allSettled(barriers).then(() => undefined),
|
|
1389
|
+
};
|
|
1390
|
+
}
|
|
1391
|
+
/** 中断指定 agent 所有正在处理中的会话。 */
|
|
1392
|
+
interruptByAgent(agentName) {
|
|
1393
|
+
const interruption = this.beginAgentInterrupt(agentName);
|
|
1394
|
+
void interruption.barrier;
|
|
1395
|
+
return interruption.count;
|
|
1396
|
+
}
|
|
1397
|
+
/** 中断指定 agent 的会话,并等待 runner 中断屏障完成。 */
|
|
1398
|
+
async interruptByAgentAndWait(agentName) {
|
|
1399
|
+
const interruption = this.beginAgentInterrupt(agentName);
|
|
1400
|
+
await interruption.barrier;
|
|
1401
|
+
return interruption.count;
|
|
1386
1402
|
}
|
|
1387
1403
|
// ── Queue query/management methods ──
|
|
1388
1404
|
/**
|