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
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// 【重要】最先加载环境变量,确保后续模块初始化时可用
|
|
2
2
|
import dotenv from 'dotenv';
|
|
3
3
|
import path from 'path';
|
|
4
|
-
import { ensureDataDirs, resolvePaths, getPackageRoot, agentMdPath, eckDebugDir, daemonControlDir, daemonQueuePath } from './paths.js';
|
|
4
|
+
import { ensureDataDirs, resolvePaths, getPackageRoot, agentMdPath, eckDebugDir, daemonControlDir, daemonQueuePath, daemonMessageDedupPath } from './paths.js';
|
|
5
5
|
// 立即加载 .env 文件(在其他模块导入之前)
|
|
6
6
|
try {
|
|
7
7
|
dotenv.config({ path: path.join(resolvePaths().root, '.env') });
|
|
@@ -14,16 +14,13 @@ import { CodexSessionFileAdapter } from './core/session/adapters/codex-session-f
|
|
|
14
14
|
import { GeminiSessionFileAdapter } from './core/session/adapters/gemini-session-file-adapter.js';
|
|
15
15
|
import { EcagentSessionFileAdapter } from './core/session/adapters/ecagent-session-file-adapter.js';
|
|
16
16
|
import { loadDefaults, loadAllAgents, migrateIdentitiesIfNeeded, loadDaemonConfig, initializeEckSnapshotsConfig } from './config-store.js';
|
|
17
|
-
import { ConfigTarget, initConfigManager, onConfigWrite, read as readConfig } from './config/config-manager.js';
|
|
18
|
-
import { ensureRoleConfigV4OnStartup } from './config/role-
|
|
19
|
-
import { ensureRoleConfigV5OnStartup } from './config/role-config-v5-startup.js';
|
|
17
|
+
import { ConfigTarget, initConfigManager, onConfigWrite, read as readConfig, shouldFailFastForMissingOwners } from './config/config-manager.js';
|
|
18
|
+
import { ensureRoleConfigV4OnStartup, ensureRoleConfigV5OnStartup } from './config/role-migration-startup.js';
|
|
20
19
|
import { ensureContactBookV2OnStartup } from './config/contact-book-v2-startup.js';
|
|
21
20
|
import { resolvePeerDisplayLabel } from './config/contact-book.js';
|
|
22
21
|
import { expirePendingContactRequests } from './config/contact-request-service.js';
|
|
23
|
-
import { shouldFailFastForMissingOwners } from './config/owner-policy.js';
|
|
24
22
|
import { isManagementRole } from './config/builtin-roles.js';
|
|
25
23
|
import { checkRoleAccess, getFirstStaticAgentOwner, resolvePeerRoleDetail, roleToSessionIdentity } from './config/peer-role-resolver.js';
|
|
26
|
-
import { isCrossAgentTriggerOperationAllowed, isCrossAgentTriggerReadOperation } from './core/auth/trigger-authorization.js';
|
|
27
24
|
import { ensureStartupConfigSnapshot, isConfigSnapshotsEnabled, retentionCleanup, readCurrent, readWVersion, diffWorkingVsVersion, paramDiff, incrementSuccessCount } from './config/snapshot.js';
|
|
28
25
|
import { appendBootLog, selfDiagnose } from './config/boot-log.js';
|
|
29
26
|
import { CONFIG_SCHEMA_VERSION } from './types.js';
|
|
@@ -41,16 +38,16 @@ import { BindService } from './utils/aid-bind.js';
|
|
|
41
38
|
import { DingtalkChannelPlugin, registerPendingDingtalkContactBind } from './channels/dingtalk.js';
|
|
42
39
|
import { QQBotChannelPlugin, registerPendingQQBotContactBind } from './channels/qqbot.js';
|
|
43
40
|
import { WecomChannelPlugin, registerPendingWecomContactBind } from './channels/wecom.js';
|
|
44
|
-
import { buildEnvelope } from './core/message/message-utils.js';
|
|
41
|
+
import { buildEnvelope, isDeliveryTargetForChannel, replyContextFromSession } from './core/message/message-utils.js';
|
|
45
42
|
import { ResponseEngine } from './core/message/response-engine.js';
|
|
46
43
|
import { MessageQueue } from './core/message/message-queue.js';
|
|
47
44
|
import { MessageBridge } from './core/message/message-bridge.js';
|
|
48
|
-
import { MenuRequestDeduper, hasValidMenuId, menuFailure, menuPayloadFingerprint, parseMenuControl, validateMenuRequest } from './core/command/menu-protocol.js';
|
|
49
|
-
import { evaluateEvolMenuVersionGate, evolMenuResponseTransportMetadata } from './core/command/evol-menu-version-gate.js';
|
|
45
|
+
import { evaluateEvolMenuVersionGate, evolMenuResponseTransportMetadata, MenuRequestDeduper, hasValidMenuId, menuFailure, menuPayloadFingerprint, parseMenuControl, validateMenuRequest } from './core/command/menu-protocol.js';
|
|
50
46
|
import { readInstalledEvolcoreVersion } from './utils/evolcore-version.js';
|
|
51
47
|
import { recoverAllRoleMutationsSync } from './core/command/role-menu.js';
|
|
52
48
|
import { HandoffRuntime } from './core/handoff/runtime.js';
|
|
53
49
|
import { BootstrapService, completeBootstrapWithWelcome } from './core/bootstrap-service.js';
|
|
50
|
+
import { postBootstrapWelcomeOperationId } from './core/bootstrap-messages.js';
|
|
54
51
|
import { MessageCache } from './core/message/message-cache.js';
|
|
55
52
|
import { CommandHandler, isProcessLevelOwner } from './core/command/command-handler.js';
|
|
56
53
|
import { EventBus } from './core/event-bus.js';
|
|
@@ -58,10 +55,10 @@ import { getEventCatalog } from './core/event-catalog.js';
|
|
|
58
55
|
import { StatsCollector } from './utils/stats.js';
|
|
59
56
|
import { AidStatsCollector } from './utils/stats.js';
|
|
60
57
|
import { PermissionGateway } from './core/permission/approval-gateway.js';
|
|
61
|
-
import { InteractionRouter } from './core/interaction-router.js';
|
|
62
|
-
import { registerAdapterInteractions } from './core/interaction-registration.js';
|
|
58
|
+
import { InteractionRouter, registerAdapterInteractions } from './core/interaction-router.js';
|
|
63
59
|
import { AgentDelegationRegistry, authorizeDelegatedAunMsgSend } from './core/auth/agent-delegation.js';
|
|
64
|
-
import { authorizeOperation, buildAuthSubject } from './core/auth/auth-gateway.js';
|
|
60
|
+
import { authorizeOperation, buildAuthSubject, isCrossAgentTriggerOperationAllowed, isCrossAgentTriggerReadOperation, } from './core/auth/auth-gateway.js';
|
|
61
|
+
import { parsePeerKey } from './core/relation/peer-identity.js';
|
|
65
62
|
import { isHClassPath } from './core/protected-paths.js';
|
|
66
63
|
import { ChannelLoader, tryParseChannelKey } from './core/channel-loader.js';
|
|
67
64
|
import { AgentLoader } from './core/baseagent-loader.js';
|
|
@@ -71,7 +68,8 @@ import { AgentReloadBusyError, AgentReloadCoordinator } from './core/agent-reloa
|
|
|
71
68
|
import { buildReloadHooks } from './core/channel-loader.js';
|
|
72
69
|
import { IpcServer } from './ipc.js';
|
|
73
70
|
import { logger, setLogLevel } from './utils/logger.js';
|
|
74
|
-
import {
|
|
71
|
+
import { fetchEcwebPairCode } from './utils/ecweb-utils.js';
|
|
72
|
+
import { createEcwebSupervisor } from './utils/ecweb-supervisor.js';
|
|
75
73
|
import { writeMain, removeAll, isMainWinner, scanInstances } from './utils/instance-registry.js';
|
|
76
74
|
import { detectDuplicates } from './core/evolagent-registry.js';
|
|
77
75
|
import { loadKitManifest, cleanEckDebug, invalidateKitCache } from './eck/kit-renderer.js';
|
|
@@ -90,12 +88,11 @@ import { validateModelSelectionForRole } from './core/model/model-permission.js'
|
|
|
90
88
|
import { constrainRuntimePermissionMode, validateRuntimeStringFieldOverride, } from './core/role/runtime-policy.js';
|
|
91
89
|
import { atomicWriteJson } from './core/session/session-fs-store.js';
|
|
92
90
|
import { appendMessageLog, buildOutboundEntry, classifyAunPayloadForLog } from './core/message/message-log.js';
|
|
91
|
+
import { normalizeAunMentionEntries } from './aun/msg/mention-schema.js';
|
|
93
92
|
import { MAIN_PACKAGE_NAME } from './product.js';
|
|
94
93
|
import fs from 'fs';
|
|
95
94
|
import crypto from 'crypto';
|
|
96
95
|
import { fileURLToPath } from 'url';
|
|
97
|
-
import { spawn } from 'child_process';
|
|
98
|
-
import * as platform from './utils/cross-platform.js';
|
|
99
96
|
const controlMenuDeduper = new MenuRequestDeduper();
|
|
100
97
|
/** 出站 payload 摘要(用于 channel-out.log) */
|
|
101
98
|
function summarizeOutboundPayload(payload) {
|
|
@@ -174,6 +171,61 @@ function normalizeMessageSource(value) {
|
|
|
174
171
|
? value
|
|
175
172
|
: 'daemon';
|
|
176
173
|
}
|
|
174
|
+
/**
|
|
175
|
+
* Preserve the authenticated AUN inbound route for control-plane replies.
|
|
176
|
+
* Control callbacks must not reconstruct group/private semantics from an AID.
|
|
177
|
+
*/
|
|
178
|
+
function controlReplyContext(opts, metadata) {
|
|
179
|
+
const supplied = opts.replyContext?.delivery;
|
|
180
|
+
if (supplied) {
|
|
181
|
+
const matchesInbound = opts.chatType === 'group'
|
|
182
|
+
? supplied.chatType === 'group' && supplied.groupId === opts.groupId
|
|
183
|
+
: supplied.chatType === 'private';
|
|
184
|
+
if (!matchesInbound) {
|
|
185
|
+
throw Object.assign(new Error('AUN control reply route does not match its authenticated inbound context'), {
|
|
186
|
+
code: 'AUN_OUTBOUND_ROUTE_MISMATCH',
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
const delivery = supplied
|
|
191
|
+
?? (opts.chatType === 'group'
|
|
192
|
+
? (opts.groupId ? { chatType: 'group', groupId: opts.groupId } : undefined)
|
|
193
|
+
: { chatType: 'private' });
|
|
194
|
+
if (!delivery)
|
|
195
|
+
throw Object.assign(new Error('AUN control reply is missing a trusted groupId'), { code: 'AUN_OUTBOUND_ROUTE_REQUIRED' });
|
|
196
|
+
return {
|
|
197
|
+
...(opts.replyContext ?? {}),
|
|
198
|
+
delivery,
|
|
199
|
+
metadata: {
|
|
200
|
+
...(opts.replyContext?.metadata ?? {}),
|
|
201
|
+
...(opts.encrypted === undefined ? {} : { encrypted: opts.encrypted }),
|
|
202
|
+
...(opts.protectedHeaders ? { protectedHeaders: opts.protectedHeaders } : {}),
|
|
203
|
+
...(metadata ?? {}),
|
|
204
|
+
},
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
function isValidAunDeliveryForChannel(delivery, channelId) {
|
|
208
|
+
return isDeliveryTargetForChannel(delivery, channelId);
|
|
209
|
+
}
|
|
210
|
+
function restartChannelType(channel) {
|
|
211
|
+
return channel?.split('#', 1)[0].toLowerCase();
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Restore a persisted restart route without inferring AUN group semantics from
|
|
215
|
+
* an address. Legacy non-AUN entries may remain route-less; AUN entries do not.
|
|
216
|
+
*/
|
|
217
|
+
export function resolvePersistedRestartDelivery(opts) {
|
|
218
|
+
const requiresAunRoute = restartChannelType(opts.channel) === 'aun'
|
|
219
|
+
|| restartChannelType(opts.adapterChannel) === 'aun';
|
|
220
|
+
if (!requiresAunRoute) {
|
|
221
|
+
return isDeliveryTargetForChannel(opts.delivery, opts.channelId)
|
|
222
|
+
? { valid: true, delivery: opts.delivery }
|
|
223
|
+
: { valid: true };
|
|
224
|
+
}
|
|
225
|
+
return isDeliveryTargetForChannel(opts.delivery, opts.channelId)
|
|
226
|
+
? { valid: true, delivery: opts.delivery }
|
|
227
|
+
: { valid: false };
|
|
228
|
+
}
|
|
177
229
|
function daemonConversationWatchdogMs(settings) {
|
|
178
230
|
const idleTimeoutSec = settings.idleMonitor?.timeout;
|
|
179
231
|
const idleMs = typeof idleTimeoutSec === 'number' && Number.isFinite(idleTimeoutSec) && idleTimeoutSec > 0
|
|
@@ -203,11 +255,15 @@ function originFromActorSession(session, authenticatedPeerId, authenticatedChann
|
|
|
203
255
|
threadId: sameConversation && !isGroup ? (session.threadId || undefined) : undefined,
|
|
204
256
|
peerId,
|
|
205
257
|
sessionKey: sameConversation && !isGroup ? session.sessionKey : undefined,
|
|
258
|
+
// A group-created trigger intentionally feeds back to the authenticated
|
|
259
|
+
// actor's private conversation (the origin channelId is peerId), so the
|
|
260
|
+
// route remains explicit even though the source session was a group.
|
|
261
|
+
delivery: { chatType: 'private' },
|
|
206
262
|
};
|
|
207
263
|
}
|
|
208
264
|
function seedUpgradeCheckTrigger(manager, owner) {
|
|
209
265
|
const now = Date.now();
|
|
210
|
-
const scriptName = 'upgrade-check.
|
|
266
|
+
const scriptName = 'upgrade-check.cjs';
|
|
211
267
|
const definition = normalizeTriggerDefinition({
|
|
212
268
|
$schema_version: 3.1,
|
|
213
269
|
id: '__upgrade-check',
|
|
@@ -234,7 +290,7 @@ function seedUpgradeCheckTrigger(manager, owner) {
|
|
|
234
290
|
type: 'script',
|
|
235
291
|
script: {
|
|
236
292
|
path: scriptName,
|
|
237
|
-
runtime: '
|
|
293
|
+
runtime: 'node',
|
|
238
294
|
timeoutMs: 60_000,
|
|
239
295
|
},
|
|
240
296
|
permissionMode: 'bypass',
|
|
@@ -260,67 +316,107 @@ function seedUpgradeCheckTrigger(manager, owner) {
|
|
|
260
316
|
const cliPath = path.join(packageRoot, 'dist', 'cli', 'index.js');
|
|
261
317
|
const packageJsonPath = path.join(packageRoot, 'package.json');
|
|
262
318
|
const devMode = fs.existsSync(path.join(packageRoot, 'src', 'index.ts'));
|
|
263
|
-
fs.writeFileSync(scriptPath,
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
319
|
+
fs.writeFileSync(scriptPath, `#!/usr/bin/env node
|
|
320
|
+
const { existsSync, readFileSync } = require('node:fs');
|
|
321
|
+
const { spawn } = require('node:child_process');
|
|
322
|
+
|
|
323
|
+
const CLI_PATH = ${JSON.stringify(cliPath)};
|
|
324
|
+
const PACKAGE_JSON = ${JSON.stringify(packageJsonPath)};
|
|
325
|
+
const PACKAGE_NAME = ${JSON.stringify(MAIN_PACKAGE_NAME)};
|
|
326
|
+
const DEV_MODE = ${devMode ? 'true' : 'false'};
|
|
327
|
+
|
|
328
|
+
function emit(outcome, text) {
|
|
329
|
+
process.stdout.write(JSON.stringify({ outcome, text, files: [] }));
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
function compareVersions(a, b) {
|
|
333
|
+
const pa = a.split('-')[0].split('.').map(Number);
|
|
334
|
+
const pb = b.split('-')[0].split('.').map(Number);
|
|
335
|
+
const count = Math.max(pa.length, pb.length);
|
|
336
|
+
for (let i = 0; i < count; i += 1) {
|
|
337
|
+
const av = Number.isFinite(pa[i]) ? pa[i] : 0;
|
|
338
|
+
const bv = Number.isFinite(pb[i]) ? pb[i] : 0;
|
|
339
|
+
if (av < bv) return -1;
|
|
340
|
+
if (av > bv) return 1;
|
|
341
|
+
}
|
|
342
|
+
return 0;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
async function latestVersion() {
|
|
346
|
+
const controller = new AbortController();
|
|
347
|
+
const timer = setTimeout(() => controller.abort(), 10_000);
|
|
348
|
+
try {
|
|
349
|
+
const response = await fetch('https://registry.npmjs.org/' + encodeURIComponent(PACKAGE_NAME) + '/latest', {
|
|
350
|
+
signal: controller.signal,
|
|
351
|
+
headers: { Accept: 'application/json' },
|
|
352
|
+
});
|
|
353
|
+
if (!response.ok) return null;
|
|
354
|
+
const data = await response.json();
|
|
355
|
+
return typeof data.version === 'string' ? data.version : null;
|
|
356
|
+
} finally {
|
|
357
|
+
clearTimeout(timer);
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
async function main() {
|
|
362
|
+
if (DEV_MODE) {
|
|
363
|
+
emit('noop', 'upgrade check skipped in dev mode');
|
|
364
|
+
return;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
let localVersion;
|
|
368
|
+
try {
|
|
369
|
+
localVersion = JSON.parse(readFileSync(PACKAGE_JSON, 'utf8')).version;
|
|
370
|
+
} catch {
|
|
371
|
+
emit('error', 'failed to read local ec version');
|
|
372
|
+
return;
|
|
373
|
+
}
|
|
374
|
+
if (typeof localVersion !== 'string' || !localVersion) {
|
|
375
|
+
emit('error', 'failed to read local ec version');
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
let remoteVersion;
|
|
380
|
+
try {
|
|
381
|
+
remoteVersion = await latestVersion();
|
|
382
|
+
} catch {
|
|
383
|
+
remoteVersion = null;
|
|
384
|
+
}
|
|
385
|
+
if (!remoteVersion) {
|
|
386
|
+
emit('error', 'failed to check evolcore latest version');
|
|
387
|
+
return;
|
|
388
|
+
}
|
|
389
|
+
if (compareVersions(localVersion, remoteVersion) >= 0) {
|
|
390
|
+
emit('noop', 'evolcore is already up to date (' + localVersion + '; latest ' + remoteVersion + ')');
|
|
391
|
+
return;
|
|
392
|
+
}
|
|
393
|
+
if (!existsSync(CLI_PATH)) {
|
|
394
|
+
emit('error', 'restart-monitor entry not found: ' + CLI_PATH);
|
|
395
|
+
return;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
try {
|
|
399
|
+
const child = spawn(process.execPath, [CLI_PATH, 'restart-monitor'], {
|
|
400
|
+
detached: true,
|
|
401
|
+
stdio: 'ignore',
|
|
402
|
+
windowsHide: process.platform === 'win32',
|
|
403
|
+
env: { ...process.env },
|
|
404
|
+
});
|
|
405
|
+
child.unref();
|
|
406
|
+
emit('success', 'evolcore upgrade available: ' + localVersion + ' -> ' + remoteVersion + '; restart-monitor started');
|
|
407
|
+
} catch (error) {
|
|
408
|
+
emit('error', 'failed to start restart-monitor: ' + (error && error.message ? error.message : String(error)));
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
main().catch((error) => {
|
|
413
|
+
emit('error', 'upgrade check failed: ' + (error && error.message ? error.message : String(error)));
|
|
414
|
+
});
|
|
415
|
+
`);
|
|
416
|
+
// Remove the pre-Node script left by older installations so it cannot be
|
|
417
|
+
// selected by a stale trigger definition or run manually by accident.
|
|
418
|
+
fs.rmSync(path.join(dir, 'upgrade-check.sh'), { force: true });
|
|
419
|
+
fs.rmSync(path.join(dir, 'upgrade-check.js'), { force: true });
|
|
324
420
|
fs.chmodSync(scriptPath, 0o755);
|
|
325
421
|
atomicWriteJson(manager.definitionPath(definition.id), definition);
|
|
326
422
|
fs.rmSync(manager.activePath(definition.id), { force: true });
|
|
@@ -338,7 +434,9 @@ function removeLegacyAgentUpgradeCheck(manager) {
|
|
|
338
434
|
return;
|
|
339
435
|
const isLegacySystemOrigin = existing.origin?.channelKey === '__system__' || existing.origin?.channelKey === 'daemon';
|
|
340
436
|
const isUpgradeScript = existing.execution.type === 'script'
|
|
341
|
-
&& existing.execution.script?.path === 'upgrade-check.sh'
|
|
437
|
+
&& (existing.execution.script?.path === 'upgrade-check.sh'
|
|
438
|
+
|| existing.execution.script?.path === 'upgrade-check.js'
|
|
439
|
+
|| existing.execution.script?.path === 'upgrade-check.cjs');
|
|
342
440
|
if (!isLegacySystemOrigin || !isUpgradeScript)
|
|
343
441
|
return;
|
|
344
442
|
fs.rmSync(manager.triggerDir('__upgrade-check'), { recursive: true, force: true });
|
|
@@ -393,9 +491,16 @@ async function runBindBootstrapDaemon(daemonCfg) {
|
|
|
393
491
|
}
|
|
394
492
|
const response = parsed ? await bindService.handleRequest(parsed, opts.peerId) : null;
|
|
395
493
|
if (response) {
|
|
396
|
-
await controlChannel.sendMessage(opts.channelId, JSON.stringify(response));
|
|
494
|
+
await controlChannel.sendMessage(opts.channelId, JSON.stringify(response), controlReplyContext(opts));
|
|
397
495
|
}
|
|
398
496
|
});
|
|
497
|
+
const controlToken = crypto.randomBytes(32).toString('base64url');
|
|
498
|
+
fs.mkdirSync(path.dirname(resolvePaths().controlToken), { recursive: true });
|
|
499
|
+
fs.writeFileSync(resolvePaths().controlToken, controlToken, { mode: 0o600 });
|
|
500
|
+
try {
|
|
501
|
+
fs.chmodSync(resolvePaths().controlToken, 0o600);
|
|
502
|
+
}
|
|
503
|
+
catch { }
|
|
399
504
|
const ipcServer = new IpcServer(resolvePaths().socket, () => ({
|
|
400
505
|
pid: process.pid,
|
|
401
506
|
uptime: Math.round(process.uptime() * 1000),
|
|
@@ -417,6 +522,7 @@ async function runBindBootstrapDaemon(daemonCfg) {
|
|
|
417
522
|
};
|
|
418
523
|
})(),
|
|
419
524
|
}));
|
|
525
|
+
ipcServer.setControlToken(controlToken);
|
|
420
526
|
ipcServer.setBindExecutor({
|
|
421
527
|
begin: (cmd) => bindService.begin(cmd),
|
|
422
528
|
status: (taskId) => bindService.status(taskId),
|
|
@@ -737,6 +843,23 @@ async function main() {
|
|
|
737
843
|
'如需远程管理,请在 daemon.json 配置 owners: [<你的 AID>]');
|
|
738
844
|
}
|
|
739
845
|
const paths = resolvePaths();
|
|
846
|
+
// ECWeb has one lifecycle owner: the daemon. The CLI may have started an
|
|
847
|
+
// instance just before spawning us; the supervisor adopts it when healthy,
|
|
848
|
+
// otherwise starts one and keeps it alive for the daemon's lifetime.
|
|
849
|
+
let ecwebSupervisor = null;
|
|
850
|
+
if (daemonCfg.ecweb?.enabled) {
|
|
851
|
+
ecwebSupervisor = createEcwebSupervisor({
|
|
852
|
+
port: daemonCfg.ecweb.port ?? 42705,
|
|
853
|
+
logger,
|
|
854
|
+
});
|
|
855
|
+
try {
|
|
856
|
+
await ecwebSupervisor.start();
|
|
857
|
+
onShutdown(() => ecwebSupervisor?.stop());
|
|
858
|
+
}
|
|
859
|
+
catch (error) {
|
|
860
|
+
logger.warn(`ECWeb supervisor 启动失败(不影响 daemon 主流程): ${error?.message || error}`);
|
|
861
|
+
}
|
|
862
|
+
}
|
|
740
863
|
if (bindBootstrapMode && daemonCfg.aid && loadAllAgents().agents.length === 0) {
|
|
741
864
|
await runBindBootstrapDaemon(daemonCfg);
|
|
742
865
|
return;
|
|
@@ -1039,7 +1162,7 @@ async function main() {
|
|
|
1039
1162
|
if (!adapter)
|
|
1040
1163
|
return;
|
|
1041
1164
|
if (text) {
|
|
1042
|
-
await adapter.send(buildEnvelope({ channel: adapter.channelName, channelId: id, replyContext: opts }), { kind: 'system.notice', text, subtype: 'health' });
|
|
1165
|
+
await adapter.send(buildEnvelope({ channel: adapter.channelName, channelId: id, delivery: opts?.delivery, replyContext: opts }), { kind: 'system.notice', text, subtype: 'health' });
|
|
1043
1166
|
}
|
|
1044
1167
|
};
|
|
1045
1168
|
return cmdHandler.handle(content, channel, channelId, sendFn, userId, threadId);
|
|
@@ -1068,6 +1191,7 @@ async function main() {
|
|
|
1068
1191
|
await processor.processMessage(message);
|
|
1069
1192
|
}, {
|
|
1070
1193
|
persistencePath: daemonQueuePath(),
|
|
1194
|
+
dedupPath: daemonMessageDedupPath(),
|
|
1071
1195
|
});
|
|
1072
1196
|
// 设置中断回调(精确中断正在处理的 agent)
|
|
1073
1197
|
messageQueue.setInterruptCallback(async (sessionKey, baseagent, evolagentName, reason = 'new_message') => {
|
|
@@ -1108,12 +1232,20 @@ async function main() {
|
|
|
1108
1232
|
source: 'msg',
|
|
1109
1233
|
handoffTrace: { version: 2, handoff_id: handoff.handoff_id },
|
|
1110
1234
|
};
|
|
1111
|
-
const
|
|
1235
|
+
const delivery = handoff.request.delivery;
|
|
1236
|
+
if (!isValidAunDeliveryForChannel(delivery, targetSession.channelId)) {
|
|
1237
|
+
return {
|
|
1238
|
+
ok: false,
|
|
1239
|
+
error: 'handoff is missing a valid immutable AUN delivery route',
|
|
1240
|
+
};
|
|
1241
|
+
}
|
|
1242
|
+
const result = delivery.chatType === 'group'
|
|
1112
1243
|
? typeof channel.sendDaemonGroupMsg === 'function'
|
|
1113
1244
|
? await channel.sendDaemonGroupMsg({
|
|
1114
|
-
groupId:
|
|
1245
|
+
groupId: delivery.groupId,
|
|
1115
1246
|
payload: { ...handoff.request.payload, [AUN_HANDOFF_MARKER_FIELD]: handoff.handoff_id },
|
|
1116
1247
|
encrypt: handoff.request.encrypt,
|
|
1248
|
+
causation: handoff.causation,
|
|
1117
1249
|
log,
|
|
1118
1250
|
})
|
|
1119
1251
|
: { ok: false, error: 'AUN channel does not support daemon group sends' }
|
|
@@ -1129,6 +1261,7 @@ async function main() {
|
|
|
1129
1261
|
return { ok: result.ok, message_id: result.message_id, error: result.error };
|
|
1130
1262
|
}, undefined, { queueTtlMs: taskExecutionTtlMs });
|
|
1131
1263
|
responseEngine.setHandoffRuntime(handoffRuntime);
|
|
1264
|
+
cmdHandler.setHandoffRuntime(handoffRuntime);
|
|
1132
1265
|
// Trigger runtime: daemon-level script + feedback scheduler.
|
|
1133
1266
|
const triggerSchedulers = new Map();
|
|
1134
1267
|
const targetSessionLocks = new TargetSessionLockRegistry();
|
|
@@ -1226,6 +1359,14 @@ async function main() {
|
|
|
1226
1359
|
if (target && !getTriggerChannel(definition.agentAid, target.channelKey)) {
|
|
1227
1360
|
throw new Error(`agent ${definition.agentAid} has no configured channel ${target.channelKey}`);
|
|
1228
1361
|
}
|
|
1362
|
+
const channelType = tryParseChannelKey(target?.channelKey ?? '')?.type
|
|
1363
|
+
?? (target?.channelKey?.split('#', 1)[0] || undefined);
|
|
1364
|
+
const validAunRoute = definition.feedback.strategy === 'target'
|
|
1365
|
+
? definition.feedback.target?.chatType === 'private' || definition.feedback.target?.chatType === 'group'
|
|
1366
|
+
: !!definition.origin && isValidAunDeliveryForChannel(definition.origin.delivery, definition.origin.channelId);
|
|
1367
|
+
if (target && channelType === 'aun' && !validAunRoute) {
|
|
1368
|
+
throw Object.assign(new Error('AUN trigger feedback target requires a valid explicit delivery route'), { code: 'AUN_OUTBOUND_ROUTE_REQUIRED' });
|
|
1369
|
+
}
|
|
1229
1370
|
};
|
|
1230
1371
|
const validateTriggerDefinitionForActor = (definition, actor) => {
|
|
1231
1372
|
if (definition.execution.type === 'script' && !actor.management) {
|
|
@@ -1467,9 +1608,13 @@ async function main() {
|
|
|
1467
1608
|
&& (candidate.adapter.channelKey === target.channelKey || candidate.adapter.channelName === target.channelKey)
|
|
1468
1609
|
&& tryParseChannelKey(candidate.adapter.channelKey)?.selfAID === input.trigger.agentAid));
|
|
1469
1610
|
const channel = inst?.channel;
|
|
1470
|
-
if (!channel
|
|
1611
|
+
if (!channel)
|
|
1612
|
+
return null;
|
|
1613
|
+
// Trigger targets carry their transport classification explicitly. A
|
|
1614
|
+
// channelId is only an address and is never parsed to choose the RPC.
|
|
1615
|
+
const targetChatType = target.chatType;
|
|
1616
|
+
if (!targetChatType)
|
|
1471
1617
|
return null;
|
|
1472
|
-
const targetChatType = channel.isGroupId(target.channelId) ? 'group' : 'private';
|
|
1473
1618
|
if (targetChatType === 'group' && typeof channel.sendDaemonGroupMsg !== 'function')
|
|
1474
1619
|
return null;
|
|
1475
1620
|
if (targetChatType === 'private' && typeof channel.sendDaemonMsg !== 'function')
|
|
@@ -1695,8 +1840,8 @@ async function main() {
|
|
|
1695
1840
|
});
|
|
1696
1841
|
}
|
|
1697
1842
|
// 5. 撤回消息 → 中断执行中任务
|
|
1698
|
-
inst.channel.onRecall?.((messageId) => {
|
|
1699
|
-
msgBridge.cancel(messageId);
|
|
1843
|
+
inst.channel.onRecall?.((messageId, selfAID) => {
|
|
1844
|
+
msgBridge.cancel(messageId, selfAID);
|
|
1700
1845
|
});
|
|
1701
1846
|
}
|
|
1702
1847
|
// ── 注册所有渠道实例 ──
|
|
@@ -1805,6 +1950,7 @@ async function main() {
|
|
|
1805
1950
|
channel: adapter.channelName,
|
|
1806
1951
|
channelId: ownerAid,
|
|
1807
1952
|
agentName,
|
|
1953
|
+
delivery: { chatType: 'private' },
|
|
1808
1954
|
});
|
|
1809
1955
|
sendSystemPayload(adapter, envelope, {
|
|
1810
1956
|
kind: 'system.notice',
|
|
@@ -1843,16 +1989,31 @@ async function main() {
|
|
|
1843
1989
|
logger.info(`[Restart] Pending notification waits for channel connection: ${pending.channel}`);
|
|
1844
1990
|
return;
|
|
1845
1991
|
}
|
|
1846
|
-
const
|
|
1847
|
-
|
|
1848
|
-
:
|
|
1992
|
+
const pendingDelivery = pending.delivery;
|
|
1993
|
+
const restartDelivery = resolvePersistedRestartDelivery({
|
|
1994
|
+
channel: pending.channel,
|
|
1995
|
+
adapterChannel: adapter.channelName,
|
|
1996
|
+
channelId: pending.channelId,
|
|
1997
|
+
delivery: pendingDelivery,
|
|
1998
|
+
});
|
|
1999
|
+
if (!restartDelivery.valid) {
|
|
2000
|
+
logger.warn(`[Restart] Discarding pending notification without a valid persisted delivery route: channel=${pending.channel} channelId=${pending.channelId}`);
|
|
2001
|
+
fs.rmSync(pendingFile, { force: true });
|
|
2002
|
+
return;
|
|
2003
|
+
}
|
|
2004
|
+
const pendingRouteContext = restartDelivery.delivery ? { delivery: restartDelivery.delivery } : {};
|
|
2005
|
+
const replyContext = {
|
|
2006
|
+
...(pendingRouteContext ?? {}),
|
|
2007
|
+
...(pending.rootId ? { replyToMessageId: pending.rootId, replyInThread: !!pending.threadId } : {}),
|
|
2008
|
+
};
|
|
1849
2009
|
const owningAgent = agentRegistry.resolveByChannel(adapter.channelKey);
|
|
1850
2010
|
const envelope = buildEnvelope({
|
|
1851
2011
|
taskId: `system-restart-${process.pid}`,
|
|
1852
2012
|
channel: adapter.channelKey,
|
|
1853
2013
|
channelId: pending.channelId,
|
|
1854
2014
|
agentName: owningAgent?.aid || 'evolcore',
|
|
1855
|
-
|
|
2015
|
+
...(restartDelivery.delivery ? { delivery: restartDelivery.delivery } : {}),
|
|
2016
|
+
replyContext: Object.keys(replyContext).length > 0 ? replyContext : undefined,
|
|
1856
2017
|
});
|
|
1857
2018
|
await sendSystemPayload(adapter, envelope, {
|
|
1858
2019
|
kind: 'system.notice',
|
|
@@ -1993,7 +2154,7 @@ async function main() {
|
|
|
1993
2154
|
const delegation = agentDelegationRegistry.validate(delegationToken, sessionId, delegationCommandHash);
|
|
1994
2155
|
if (!delegation.ok)
|
|
1995
2156
|
return { ok: false, code: delegation.code, error: delegation.reason };
|
|
1996
|
-
return cmdHandler.handleCtl(cmd, sessionId);
|
|
2157
|
+
return cmdHandler.handleCtl(cmd, sessionId, delegation.grant);
|
|
1997
2158
|
});
|
|
1998
2159
|
// Register every IPC executor/provider before exposing the endpoint. The
|
|
1999
2160
|
// function declaration is hoisted, while its invocation remains here so a
|
|
@@ -2062,52 +2223,6 @@ async function main() {
|
|
|
2062
2223
|
catch (e) {
|
|
2063
2224
|
logger.warn(`控制 AID 首连失败(后台自动重连,不影响 daemon 主流程): ${e?.message || e}`);
|
|
2064
2225
|
}
|
|
2065
|
-
// ── ECWeb 自动启动 ──
|
|
2066
|
-
// 如果 config.ecweb.enabled,自动拉起 ecweb 服务
|
|
2067
|
-
if (daemonCfg.ecweb?.enabled) {
|
|
2068
|
-
const ecwebPath = path.join(resolvePaths().root, 'ecweb');
|
|
2069
|
-
const ecwebEntry = path.join(ecwebPath, 'dist', 'index.js');
|
|
2070
|
-
if (fs.existsSync(ecwebEntry)) {
|
|
2071
|
-
let ecwebVersion;
|
|
2072
|
-
try {
|
|
2073
|
-
const pkg = JSON.parse(fs.readFileSync(path.join(ecwebPath, 'package.json'), 'utf-8'));
|
|
2074
|
-
ecwebVersion = pkg.version;
|
|
2075
|
-
}
|
|
2076
|
-
catch { }
|
|
2077
|
-
const versionError = ecwebVersionRequirementError(ecwebVersion);
|
|
2078
|
-
if (versionError) {
|
|
2079
|
-
logger.error(`ECWeb 未启动: ${versionError}`);
|
|
2080
|
-
}
|
|
2081
|
-
else {
|
|
2082
|
-
try {
|
|
2083
|
-
const ecwebProc = spawn('node', [ecwebEntry], {
|
|
2084
|
-
cwd: ecwebPath,
|
|
2085
|
-
detached: true,
|
|
2086
|
-
stdio: 'ignore',
|
|
2087
|
-
env: { ...process.env, EVOLCORE_HOME: resolvePaths().root }
|
|
2088
|
-
});
|
|
2089
|
-
ecwebProc.unref();
|
|
2090
|
-
logger.info(`✓ ECWeb 已启动 (PID: ${ecwebProc.pid})`);
|
|
2091
|
-
onShutdown(() => {
|
|
2092
|
-
try {
|
|
2093
|
-
if (ecwebProc.pid)
|
|
2094
|
-
platform.killProcess(ecwebProc.pid, true);
|
|
2095
|
-
logger.info(`✓ ECWeb 已停止`);
|
|
2096
|
-
}
|
|
2097
|
-
catch { }
|
|
2098
|
-
});
|
|
2099
|
-
}
|
|
2100
|
-
catch (e) {
|
|
2101
|
-
logger.warn(`ECWeb 启动失败: ${e?.message || e}`);
|
|
2102
|
-
}
|
|
2103
|
-
}
|
|
2104
|
-
}
|
|
2105
|
-
else {
|
|
2106
|
-
// 正式安装时 ECWeb 是独立的全局 ec-web 包,由 ec start/restart 的
|
|
2107
|
-
// 启动链路管理;runtime 目录没有内嵌构建产物是正常状态。
|
|
2108
|
-
logger.debug(`ECWeb runtime entry absent (managed externally): ${ecwebEntry}`);
|
|
2109
|
-
}
|
|
2110
|
-
}
|
|
2111
2226
|
// 控制 AID 接收 owner 指令:
|
|
2112
2227
|
// 1. /pair — ECWeb 配对码(文本快路径)
|
|
2113
2228
|
// 2. menu.* JSON — 路由到 cmdHandler.execMenuForControl(进程级 + 全量权限)
|
|
@@ -2122,22 +2237,20 @@ async function main() {
|
|
|
2122
2237
|
catch {
|
|
2123
2238
|
parsed = null;
|
|
2124
2239
|
}
|
|
2240
|
+
const menuResponseContext = () => controlReplyContext(opts, evolMenuResponseTransportMetadata());
|
|
2125
2241
|
if (bindService && parsed?.type === 'bind.request') {
|
|
2126
2242
|
const response = await bindService.handleRequest(parsed, opts.peerId);
|
|
2127
2243
|
if (response) {
|
|
2128
2244
|
// 用 sendStructured 直发 typed payload(payload.type='bind.response'),
|
|
2129
2245
|
// 不能用 sendMessage——它会把内容包成 {type:'text', text:...},App 无法识别。
|
|
2130
2246
|
// encrypted 跟随入站请求:bind.response 与 bind.request 的加密/明文对称。
|
|
2131
|
-
await controlChannel.sendStructured(opts.channelId, response,
|
|
2247
|
+
await controlChannel.sendStructured(opts.channelId, response, controlReplyContext(opts));
|
|
2132
2248
|
}
|
|
2133
2249
|
return;
|
|
2134
2250
|
}
|
|
2135
2251
|
const menuControl = parseMenuControl(text);
|
|
2136
2252
|
const isRoleMenu = menuControl.isMenu
|
|
2137
2253
|
&& (menuControl.name === 'role' || menuControl.request.cmd === '/role');
|
|
2138
|
-
const menuResponseContext = () => ({
|
|
2139
|
-
metadata: evolMenuResponseTransportMetadata(),
|
|
2140
|
-
});
|
|
2141
2254
|
if (menuControl.isMenu && !hasValidMenuId(menuControl)) {
|
|
2142
2255
|
logger.warn(`[ControlMenu] dropped malformed request without id type=${menuControl.type}`);
|
|
2143
2256
|
return;
|
|
@@ -2162,7 +2275,7 @@ async function main() {
|
|
|
2162
2275
|
else {
|
|
2163
2276
|
reply = 'ECWeb 未运行或暂不可达。请在主机运行 ec watch web 启动后重试。';
|
|
2164
2277
|
}
|
|
2165
|
-
await controlChannel.sendMessage(opts.channelId, reply);
|
|
2278
|
+
await controlChannel.sendMessage(opts.channelId, reply, controlReplyContext(opts));
|
|
2166
2279
|
return;
|
|
2167
2280
|
}
|
|
2168
2281
|
// menu.* JSON 路由:owner 已在上方校验,转交 execMenuForControl(fromControlChannel=true)
|
|
@@ -2181,6 +2294,16 @@ async function main() {
|
|
|
2181
2294
|
return;
|
|
2182
2295
|
}
|
|
2183
2296
|
const deduped = await controlMenuDeduper.execute([opts.channelId, opts.peerId, menuControl.id].join('\u001f'), menuPayloadFingerprint(menuControl.raw), () => cmdHandler.execMenuForControl(parsed, opts.peerId));
|
|
2297
|
+
if ('retry' in deduped) {
|
|
2298
|
+
if (deduped.replayed) {
|
|
2299
|
+
logger.warn(`[MenuControl] category=request-replay request=${menuControl.id}`
|
|
2300
|
+
+ ` sender=${opts.peerId} replay=true`);
|
|
2301
|
+
}
|
|
2302
|
+
if (deduped.retry) {
|
|
2303
|
+
logger.warn(`[MenuControl] category=request-retry request=${menuControl.id}`
|
|
2304
|
+
+ ` sender=${opts.peerId} retry=true`);
|
|
2305
|
+
}
|
|
2306
|
+
}
|
|
2184
2307
|
const response = 'conflict' in deduped
|
|
2185
2308
|
? menuFailure({ id: menuControl.id, ...(menuControl.name?.trim() ? { name: menuControl.name } : {}) }, { code: 'CONFLICT', message: 'Request ID was already used with a different payload' })
|
|
2186
2309
|
: deduped.value;
|
|
@@ -2190,7 +2313,7 @@ async function main() {
|
|
|
2190
2313
|
return;
|
|
2191
2314
|
}
|
|
2192
2315
|
// owner 发的其他内容:提示可用指令
|
|
2193
|
-
await controlChannel.sendMessage(opts.channelId, '可用指令:/pair(获取 ECWeb 登录配对码)');
|
|
2316
|
+
await controlChannel.sendMessage(opts.channelId, '可用指令:/pair(获取 ECWeb 登录配对码)', controlReplyContext(opts));
|
|
2194
2317
|
}
|
|
2195
2318
|
catch (e) {
|
|
2196
2319
|
logger.warn(`控制 AID 消息处理失败: ${e?.message || e}`);
|
|
@@ -2242,6 +2365,7 @@ async function main() {
|
|
|
2242
2365
|
channel: other.adapter.channelKey,
|
|
2243
2366
|
channelId: ownerId,
|
|
2244
2367
|
agentName: owningAgent?.aid || 'evolcore',
|
|
2368
|
+
delivery: { chatType: 'private' },
|
|
2245
2369
|
});
|
|
2246
2370
|
sendSystemPayload(other.adapter, envelope, {
|
|
2247
2371
|
kind: 'system.error',
|
|
@@ -2259,6 +2383,9 @@ async function main() {
|
|
|
2259
2383
|
const recoveryAids = triggerStartupAgents.map(agent => agent.aid);
|
|
2260
2384
|
for (const aid of recoveryAids) {
|
|
2261
2385
|
try {
|
|
2386
|
+
const discarded = await handoffRuntime.discardExpired([aid]);
|
|
2387
|
+
if (discarded > 0)
|
|
2388
|
+
logger.info(`[Handoff] discarded ${discarded} expired handoff(s) for ${aid}`);
|
|
2262
2389
|
const expired = handoffRuntime.expireQueued([aid]);
|
|
2263
2390
|
if (expired > 0)
|
|
2264
2391
|
logger.info(`[Handoff] expired ${expired} stale queued delivery item(s) for ${aid}`);
|
|
@@ -2328,11 +2455,13 @@ async function main() {
|
|
|
2328
2455
|
channelType: session.channelType || parsedResumeKey?.type,
|
|
2329
2456
|
selfAID: resumeSelfAID,
|
|
2330
2457
|
channelId: session.channelId,
|
|
2458
|
+
groupId: session.chatType === 'group' ? (session.metadata?.groupId || session.channelId) : undefined,
|
|
2459
|
+
chatType: session.chatType === 'group' ? 'group' : 'private',
|
|
2331
2460
|
content: '服务已重启,请继续之前未完成的任务。',
|
|
2332
2461
|
timestamp: Date.now(),
|
|
2333
2462
|
peerId: '',
|
|
2334
2463
|
threadId: session.threadId || undefined,
|
|
2335
|
-
replyContext: session
|
|
2464
|
+
replyContext: replyContextFromSession(session),
|
|
2336
2465
|
};
|
|
2337
2466
|
// 清除状态后入队(processMessage 会重新标记)
|
|
2338
2467
|
sessionManager.clearProcessing(session.id);
|
|
@@ -2344,7 +2473,27 @@ async function main() {
|
|
|
2344
2473
|
function configureIpc() {
|
|
2345
2474
|
// M3: direct call (not cast) — wire EvolAgentRegistry into IPC for evolagent.* handlers
|
|
2346
2475
|
ipcServer.setAgentRegistry(agentRegistry);
|
|
2347
|
-
ipcServer.
|
|
2476
|
+
ipcServer.setControlToken(triggerControlToken);
|
|
2477
|
+
ipcServer.setBootstrapCompleteExecutor(async (aid, auth) => {
|
|
2478
|
+
if (auth?.sessionId) {
|
|
2479
|
+
const authorization = await authorizeManagedAgentOperation({
|
|
2480
|
+
sessionId: auth.sessionId,
|
|
2481
|
+
delegationToken: auth.delegationToken,
|
|
2482
|
+
delegationCommandHash: auth.delegationCommandHash,
|
|
2483
|
+
targetAid: aid,
|
|
2484
|
+
}, 'agent.bootstrapComplete');
|
|
2485
|
+
if (!authorization.ok)
|
|
2486
|
+
return authorization;
|
|
2487
|
+
}
|
|
2488
|
+
else {
|
|
2489
|
+
const providedToken = typeof auth?.controlToken === 'string' ? Buffer.from(auth.controlToken) : undefined;
|
|
2490
|
+
const expectedToken = Buffer.from(triggerControlToken);
|
|
2491
|
+
if (!providedToken
|
|
2492
|
+
|| providedToken.length !== expectedToken.length
|
|
2493
|
+
|| !crypto.timingSafeEqual(providedToken, expectedToken)) {
|
|
2494
|
+
return { ok: false, code: 'INVALID_CONTROL_TOKEN', error: 'valid local control token is required' };
|
|
2495
|
+
}
|
|
2496
|
+
}
|
|
2348
2497
|
const aunInstance = channelInstances.find(candidate => {
|
|
2349
2498
|
if (candidate.channelType !== 'aun')
|
|
2350
2499
|
return false;
|
|
@@ -2353,7 +2502,30 @@ async function main() {
|
|
|
2353
2502
|
return owner?.aid === aid;
|
|
2354
2503
|
});
|
|
2355
2504
|
const welcome = aunInstance?.channel;
|
|
2356
|
-
|
|
2505
|
+
const ownerAid = getFirstStaticAgentOwner(aid);
|
|
2506
|
+
const owningAgent = agentRegistry.get(aid);
|
|
2507
|
+
const sessions = aunInstance && ownerAid && owningAgent
|
|
2508
|
+
? {
|
|
2509
|
+
ensurePostBootstrapMainSession: async () => {
|
|
2510
|
+
const channel = aunInstance.adapter.channelName;
|
|
2511
|
+
const active = await sessionManager.getActiveSession(channel, ownerAid);
|
|
2512
|
+
const ensured = await sessionManager.ensureNewMainSessionForOperation({
|
|
2513
|
+
channel,
|
|
2514
|
+
channelId: ownerAid,
|
|
2515
|
+
projectPath: active?.projectPath || owningAgent.projectPath || defaultProjectPath,
|
|
2516
|
+
baseagent: owningAgent.baseagent || active?.baseagent || primaryBaseagent,
|
|
2517
|
+
operationId: postBootstrapWelcomeOperationId(aid),
|
|
2518
|
+
identityMetadata: {
|
|
2519
|
+
...(active?.metadata ?? {}),
|
|
2520
|
+
channelKey: aunInstance.adapter.channelKey,
|
|
2521
|
+
peerId: ownerAid,
|
|
2522
|
+
},
|
|
2523
|
+
});
|
|
2524
|
+
return { sessionId: ensured.session.id, created: ensured.created };
|
|
2525
|
+
},
|
|
2526
|
+
}
|
|
2527
|
+
: undefined;
|
|
2528
|
+
return completeBootstrapWithWelcome(bootstrapService, aid, welcome, sessions);
|
|
2357
2529
|
});
|
|
2358
2530
|
ipcServer.setDingtalkContactBindExecutor({
|
|
2359
2531
|
register: (cmd) => registerPendingDingtalkContactBind(cmd),
|
|
@@ -2378,6 +2550,10 @@ async function main() {
|
|
|
2378
2550
|
ipcServer.setMenuExecutor((payload, auth) => cmdHandler.execMenuForEcweb(payload, auth));
|
|
2379
2551
|
ipcServer.setConfigOperationExecutor((argv, sessionId, delegationToken, delegationCommandHash) => cmdHandler.handleConfigOperation(argv, sessionId, delegationToken, delegationCommandHash));
|
|
2380
2552
|
ipcServer.setContactOperationExecutor((argv, sessionId, delegationToken, delegationCommandHash) => cmdHandler.handleContactOperation(argv, sessionId, delegationToken, delegationCommandHash));
|
|
2553
|
+
ipcServer.setAidOperationExecutor((argv, sessionId, delegationToken, delegationCommandHash) => cmdHandler.handleAidOperation(argv, sessionId, delegationToken, delegationCommandHash));
|
|
2554
|
+
ipcServer.setGroupOperationExecutor((argv, sessionId, delegationToken, delegationCommandHash) => cmdHandler.handleGroupOperation(argv, sessionId, delegationToken, delegationCommandHash));
|
|
2555
|
+
ipcServer.setMsgOperationExecutor((argv, sessionId, delegationToken, delegationCommandHash) => cmdHandler.handleMsgOperation(argv, sessionId, delegationToken, delegationCommandHash));
|
|
2556
|
+
ipcServer.setFsOperationExecutor((argv, sessionId, delegationToken, delegationCommandHash) => cmdHandler.handleFsOperation(argv, sessionId, delegationToken, delegationCommandHash));
|
|
2381
2557
|
cmdHandler.setDaemonStatusProvider(() => {
|
|
2382
2558
|
const aidState = controlChannel?.getAidState?.();
|
|
2383
2559
|
return {
|
|
@@ -2446,30 +2622,82 @@ async function main() {
|
|
|
2446
2622
|
aidStatsCollector.recordOutbound(params.aid, params.toPeer, Buffer.byteLength(params.text || '', 'utf-8'), params.text, false, params.encrypt, params.chatmode, 'send');
|
|
2447
2623
|
});
|
|
2448
2624
|
ipcServer.setTaskRuntimeContextProvider(({ sessionId }) => responseEngine.getTaskRuntimeContext(sessionId));
|
|
2449
|
-
ipcServer.
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
2625
|
+
ipcServer.setCodexPreToolUseExecutor(async (params) => {
|
|
2626
|
+
for (const runner of new Set(agentMap.values())) {
|
|
2627
|
+
if (typeof runner?.evaluatePreToolUse !== 'function')
|
|
2628
|
+
continue;
|
|
2629
|
+
const result = await runner.evaluatePreToolUse(params.threadId, params.toolName, params.toolInput);
|
|
2630
|
+
if (result.applicable)
|
|
2631
|
+
return result;
|
|
2632
|
+
}
|
|
2633
|
+
return {
|
|
2634
|
+
ok: false,
|
|
2635
|
+
applicable: true,
|
|
2636
|
+
decision: 'deny',
|
|
2637
|
+
reason: 'No active EvolCore Codex runner accepted this managed hook request',
|
|
2638
|
+
};
|
|
2639
|
+
});
|
|
2640
|
+
const resolveManagedHandoffScope = async (params, operation) => {
|
|
2641
|
+
const delegation = agentDelegationRegistry.validate(params.delegationToken, params.sessionId, params.delegationCommandHash);
|
|
2642
|
+
if (!delegation.ok)
|
|
2643
|
+
return { ok: false, code: delegation.code, error: delegation.reason };
|
|
2453
2644
|
const runtime = responseEngine.getTaskRuntimeContext(params.sessionId);
|
|
2454
2645
|
const session = await sessionManager.getSessionById(params.sessionId);
|
|
2455
2646
|
const selfAid = runtime?.selfAid || session?.selfAID;
|
|
2456
|
-
if (!selfAid)
|
|
2457
|
-
return { ok: false, code: '
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2647
|
+
if (!selfAid || !session) {
|
|
2648
|
+
return { ok: false, code: 'HANDOFF_CALL_SESSION_INVALID', error: 'current session is invalid' };
|
|
2649
|
+
}
|
|
2650
|
+
if (delegation.grant.selfAid !== selfAid) {
|
|
2651
|
+
return { ok: false, code: 'INVALID_DELEGATION', error: 'delegation self agent does not match current session' };
|
|
2652
|
+
}
|
|
2653
|
+
if (params.agent && params.agent !== selfAid) {
|
|
2654
|
+
return { ok: false, code: 'HANDOFF_AGENT_SCOPE_MISMATCH', error: 'agent does not match the current session' };
|
|
2655
|
+
}
|
|
2656
|
+
let conversationId;
|
|
2657
|
+
try {
|
|
2658
|
+
conversationId = parsePeerKey(delegation.grant.peerKey).channelId;
|
|
2659
|
+
}
|
|
2660
|
+
catch {
|
|
2661
|
+
return { ok: false, code: 'INVALID_DELEGATION', error: 'delegation contains an invalid relation key' };
|
|
2662
|
+
}
|
|
2663
|
+
const subject = buildAuthSubject({
|
|
2664
|
+
selfAid,
|
|
2665
|
+
actorId: delegation.grant.actorId,
|
|
2666
|
+
channel: delegation.grant.channel,
|
|
2667
|
+
channelType: delegation.grant.channelType,
|
|
2668
|
+
channelId: conversationId,
|
|
2669
|
+
chatType: delegation.grant.chatType,
|
|
2670
|
+
conversationId,
|
|
2671
|
+
processOwners: daemonCfg.owners ?? [],
|
|
2672
|
+
});
|
|
2673
|
+
const managementRole = isManagementRole(subject.role);
|
|
2674
|
+
const decision = authorizeOperation({
|
|
2675
|
+
source: 'agent-tool',
|
|
2676
|
+
subject,
|
|
2677
|
+
intent: {
|
|
2678
|
+
operation,
|
|
2679
|
+
scope: managementRole ? 'agent' : 'relation',
|
|
2680
|
+
source: 'agent-tool',
|
|
2681
|
+
args: {
|
|
2682
|
+
self: selfAid,
|
|
2683
|
+
peer: conversationId,
|
|
2684
|
+
peerKey: delegation.grant.peerKey,
|
|
2685
|
+
},
|
|
2686
|
+
},
|
|
2687
|
+
auditMetadata: { taskId: delegation.grant.taskId, messageId: delegation.grant.messageId },
|
|
2688
|
+
});
|
|
2689
|
+
if (!decision.allow) {
|
|
2690
|
+
return {
|
|
2691
|
+
ok: false,
|
|
2692
|
+
code: decision.command?.code ?? 'ROLE_ACCESS_DENIED',
|
|
2693
|
+
error: decision.reason,
|
|
2694
|
+
};
|
|
2695
|
+
}
|
|
2696
|
+
return { ok: true, aid: selfAid, currentSessionId: params.sessionId, managementRole };
|
|
2697
|
+
};
|
|
2698
|
+
const resolveHandoffQueryScope = async (params, operation) => {
|
|
2462
2699
|
if (params.sessionId) {
|
|
2463
|
-
|
|
2464
|
-
const session = await sessionManager.getSessionById(params.sessionId);
|
|
2465
|
-
const selfAid = runtime?.selfAid || session?.selfAID;
|
|
2466
|
-
if (!selfAid) {
|
|
2467
|
-
return { ok: false, code: 'HANDOFF_CALL_SESSION_INVALID', error: 'current session is invalid' };
|
|
2468
|
-
}
|
|
2469
|
-
if (params.agent && params.agent !== selfAid) {
|
|
2470
|
-
return { ok: false, code: 'HANDOFF_AGENT_SCOPE_MISMATCH', error: 'agent does not match the current session' };
|
|
2471
|
-
}
|
|
2472
|
-
return { ok: true, aid: selfAid };
|
|
2700
|
+
return resolveManagedHandoffScope({ ...params, sessionId: params.sessionId }, operation);
|
|
2473
2701
|
}
|
|
2474
2702
|
if (!params.agent) {
|
|
2475
2703
|
return { ok: false, code: 'HANDOFF_AGENT_REQUIRED', error: '--agent is required outside a task context' };
|
|
@@ -2477,23 +2705,56 @@ async function main() {
|
|
|
2477
2705
|
if (!agentRegistry.get(params.agent)) {
|
|
2478
2706
|
return { ok: false, code: 'HANDOFF_AGENT_NOT_FOUND', error: `agent not found: ${params.agent}` };
|
|
2479
2707
|
}
|
|
2480
|
-
return { ok: true, aid: params.agent };
|
|
2708
|
+
return { ok: true, aid: params.agent, currentSessionId: undefined, managementRole: true };
|
|
2481
2709
|
};
|
|
2710
|
+
ipcServer.setHandoffReturnExecutor(async (params) => {
|
|
2711
|
+
if (!params.sessionId)
|
|
2712
|
+
return responseEngine.returnHandoffResult(params);
|
|
2713
|
+
const scope = await resolveManagedHandoffScope({ ...params, sessionId: params.sessionId }, 'ec.handoff.return');
|
|
2714
|
+
if (!scope.ok)
|
|
2715
|
+
return scope;
|
|
2716
|
+
return responseEngine.returnAuthorizedHandoffResult({ sessionId: params.sessionId, handoffId: params.handoffId, content: params.content }, scope.aid);
|
|
2717
|
+
});
|
|
2718
|
+
ipcServer.setHandoffStatusExecutor(async (params) => {
|
|
2719
|
+
const scope = await resolveHandoffQueryScope(params, 'ec.handoff.status');
|
|
2720
|
+
if (!scope.ok)
|
|
2721
|
+
return scope;
|
|
2722
|
+
const status = handoffRuntime.status(scope.aid, params.handoffId);
|
|
2723
|
+
if (!status)
|
|
2724
|
+
return { ok: false, code: 'HANDOFF_NOT_FOUND', error: 'handoff not found' };
|
|
2725
|
+
if (!scope.managementRole
|
|
2726
|
+
&& status.origin_session_id !== scope.currentSessionId
|
|
2727
|
+
&& status.target_session_id !== scope.currentSessionId) {
|
|
2728
|
+
return { ok: false, code: 'HANDOFF_NOT_FOUND', error: 'handoff not found in the current session' };
|
|
2729
|
+
}
|
|
2730
|
+
return status;
|
|
2731
|
+
});
|
|
2482
2732
|
ipcServer.setHandoffListExecutor(async (params) => {
|
|
2483
|
-
const scope = await
|
|
2733
|
+
const scope = await resolveHandoffQueryScope(params, 'ec.handoff.list');
|
|
2484
2734
|
if (!scope.ok)
|
|
2485
2735
|
return scope;
|
|
2736
|
+
if (!scope.managementRole && params.filterSessionId && params.filterSessionId !== scope.currentSessionId) {
|
|
2737
|
+
return { ok: false, code: 'HANDOFF_SESSION_SCOPE_MISMATCH', error: 'only the current session can be queried' };
|
|
2738
|
+
}
|
|
2486
2739
|
return handoffRuntime.listHandoffs({
|
|
2487
2740
|
selfAid: scope.aid,
|
|
2488
2741
|
state: params.state,
|
|
2489
|
-
sessionId: params.filterSessionId,
|
|
2742
|
+
sessionId: scope.managementRole ? params.filterSessionId : scope.currentSessionId,
|
|
2490
2743
|
limit: params.limit,
|
|
2491
2744
|
});
|
|
2492
2745
|
});
|
|
2493
2746
|
ipcServer.setHandoffTraceExecutor(async (params) => {
|
|
2494
|
-
const scope = await
|
|
2747
|
+
const scope = await resolveHandoffQueryScope(params, 'ec.handoff.trace');
|
|
2495
2748
|
if (!scope.ok)
|
|
2496
2749
|
return scope;
|
|
2750
|
+
if (!scope.managementRole) {
|
|
2751
|
+
const status = handoffRuntime.status(scope.aid, params.handoffId);
|
|
2752
|
+
if (!status
|
|
2753
|
+
|| (status.origin_session_id !== scope.currentSessionId
|
|
2754
|
+
&& status.target_session_id !== scope.currentSessionId)) {
|
|
2755
|
+
return { ok: false, code: 'HANDOFF_NOT_FOUND', error: 'handoff not found in the current session' };
|
|
2756
|
+
}
|
|
2757
|
+
}
|
|
2497
2758
|
return handoffRuntime.traceHandoff(scope.aid, params.handoffId, params.limit)
|
|
2498
2759
|
?? { ok: false, code: 'HANDOFF_NOT_FOUND', error: 'handoff not found' };
|
|
2499
2760
|
});
|
|
@@ -2532,10 +2793,9 @@ async function main() {
|
|
|
2532
2793
|
if (!ch) {
|
|
2533
2794
|
return { ok: false, error: `AUN channel not found for ${params.aid}`, code: 'AUN_CHANNEL_NOT_FOUND' };
|
|
2534
2795
|
}
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
|
|
2538
|
-
}
|
|
2796
|
+
// The IPC command already carries the caller-selected transport scope.
|
|
2797
|
+
// Never reclassify the target by parsing its AID string: a private AID may
|
|
2798
|
+
// look group-like and a group identifier may use a future format.
|
|
2539
2799
|
const encrypt = typeof params.encrypt === 'boolean'
|
|
2540
2800
|
? params.encrypt
|
|
2541
2801
|
: (typeof ch.getDefaultEncrypt === 'function' ? ch.getDefaultEncrypt() : true);
|
|
@@ -2565,12 +2825,27 @@ async function main() {
|
|
|
2565
2825
|
if (!payload) {
|
|
2566
2826
|
return { ok: false, error: 'message payload is required', code: 'INVALID_PAYLOAD' };
|
|
2567
2827
|
}
|
|
2828
|
+
try {
|
|
2829
|
+
if (Object.prototype.hasOwnProperty.call(payload, 'mentions')) {
|
|
2830
|
+
payload = { ...payload, mentions: normalizeAunMentionEntries(payload.mentions) };
|
|
2831
|
+
}
|
|
2832
|
+
if (params.scope === 'group' && params.mentions !== undefined) {
|
|
2833
|
+
const normalizedMentions = normalizeAunMentionEntries(params.mentions);
|
|
2834
|
+
if (normalizedMentions.length > 0) {
|
|
2835
|
+
payload = { ...payload, mentions: normalizedMentions };
|
|
2836
|
+
}
|
|
2837
|
+
}
|
|
2838
|
+
}
|
|
2839
|
+
catch (error) {
|
|
2840
|
+
return {
|
|
2841
|
+
ok: false,
|
|
2842
|
+
error: error instanceof Error ? error.message : String(error),
|
|
2843
|
+
code: error?.code ?? 'INVALID_MENTION_SCHEMA',
|
|
2844
|
+
};
|
|
2845
|
+
}
|
|
2568
2846
|
if (params.thread && typeof payload.thread_id !== 'string') {
|
|
2569
2847
|
payload = { ...payload, thread_id: params.thread };
|
|
2570
2848
|
}
|
|
2571
|
-
if (params.scope === 'group' && params.mentions?.length) {
|
|
2572
|
-
payload = { ...payload, mentions: params.mentions };
|
|
2573
|
-
}
|
|
2574
2849
|
let targetSessionId;
|
|
2575
2850
|
if (params.originSessionId && params.originMessageId) {
|
|
2576
2851
|
try {
|
|
@@ -2824,7 +3099,7 @@ async function main() {
|
|
|
2824
3099
|
agent.status = 'running';
|
|
2825
3100
|
// 连接
|
|
2826
3101
|
await channelLoader.connectAll(instances, { onConnected: markChannelConnected });
|
|
2827
|
-
await handoffRuntime.recover([aid]);
|
|
3102
|
+
await handoffRuntime.recover([aid], { discardExpired: false });
|
|
2828
3103
|
await ensureTriggerSchedulerStarted(agent);
|
|
2829
3104
|
handoffRuntime.resumeAgent(aid);
|
|
2830
3105
|
resumed = true;
|
|
@@ -2976,7 +3251,7 @@ async function main() {
|
|
|
2976
3251
|
const agentName = handle?.name;
|
|
2977
3252
|
if (!agentName)
|
|
2978
3253
|
return [];
|
|
2979
|
-
return messageQueue.getQueueItemsByAgent(agentName);
|
|
3254
|
+
return messageQueue.getQueueItemsByAgent(agentName, { after: params.after, before: params.before });
|
|
2980
3255
|
});
|
|
2981
3256
|
ipcServer.setQueueActionExecutor(async (params) => {
|
|
2982
3257
|
const handle = agentRegistry.get(params.agent);
|
|
@@ -3003,6 +3278,92 @@ async function main() {
|
|
|
3003
3278
|
return { ok: false, error: `unknown action: ${params.action}` };
|
|
3004
3279
|
}
|
|
3005
3280
|
});
|
|
3281
|
+
async function authorizeManagedAgentOperation(params, operation) {
|
|
3282
|
+
const delegation = agentDelegationRegistry.validate(params.delegationToken, params.sessionId, params.delegationCommandHash);
|
|
3283
|
+
if (!delegation.ok)
|
|
3284
|
+
return { ok: false, code: delegation.code, error: delegation.reason };
|
|
3285
|
+
const session = await sessionManager.getSessionById(params.sessionId);
|
|
3286
|
+
if (!session || session.selfAID !== delegation.grant.selfAid) {
|
|
3287
|
+
return { ok: false, code: 'INVALID_DELEGATION', error: 'delegation does not match the current session' };
|
|
3288
|
+
}
|
|
3289
|
+
if (params.targetAid && params.targetAid !== delegation.grant.selfAid) {
|
|
3290
|
+
return { ok: false, code: 'SCOPE_MISMATCH', error: 'only the current Agent can be targeted' };
|
|
3291
|
+
}
|
|
3292
|
+
let conversationId;
|
|
3293
|
+
try {
|
|
3294
|
+
conversationId = parsePeerKey(delegation.grant.peerKey).channelId;
|
|
3295
|
+
}
|
|
3296
|
+
catch {
|
|
3297
|
+
return { ok: false, code: 'INVALID_DELEGATION', error: 'delegation contains an invalid relation key' };
|
|
3298
|
+
}
|
|
3299
|
+
const subject = buildAuthSubject({
|
|
3300
|
+
selfAid: delegation.grant.selfAid,
|
|
3301
|
+
actorId: delegation.grant.actorId,
|
|
3302
|
+
channel: delegation.grant.channel,
|
|
3303
|
+
channelType: delegation.grant.channelType,
|
|
3304
|
+
channelId: conversationId,
|
|
3305
|
+
chatType: delegation.grant.chatType,
|
|
3306
|
+
conversationId,
|
|
3307
|
+
processOwners: daemonCfg.owners ?? [],
|
|
3308
|
+
});
|
|
3309
|
+
if (!isManagementRole(subject.role)) {
|
|
3310
|
+
return { ok: false, code: 'ROLE_ACCESS_DENIED', error: `${operation} requires Agent admin permission` };
|
|
3311
|
+
}
|
|
3312
|
+
const decision = authorizeOperation({
|
|
3313
|
+
source: 'agent-tool',
|
|
3314
|
+
subject,
|
|
3315
|
+
intent: {
|
|
3316
|
+
operation,
|
|
3317
|
+
scope: operation === 'system.status' ? 'process' : 'agent',
|
|
3318
|
+
source: 'agent-tool',
|
|
3319
|
+
args: { self: delegation.grant.selfAid, aid: params.targetAid ?? delegation.grant.selfAid },
|
|
3320
|
+
},
|
|
3321
|
+
auditMetadata: { taskId: delegation.grant.taskId, messageId: delegation.grant.messageId },
|
|
3322
|
+
});
|
|
3323
|
+
return decision.allow
|
|
3324
|
+
? { ok: true, aid: delegation.grant.selfAid }
|
|
3325
|
+
: { ok: false, code: decision.code, error: decision.reason };
|
|
3326
|
+
}
|
|
3327
|
+
ipcServer.setQueueOperationExecutor(async (params) => {
|
|
3328
|
+
const operation = `ec.queue.${params.action ?? 'read'}`;
|
|
3329
|
+
const authorization = await authorizeManagedAgentOperation(params, operation);
|
|
3330
|
+
if (!authorization.ok)
|
|
3331
|
+
return authorization;
|
|
3332
|
+
const handle = agentRegistry.get(authorization.aid);
|
|
3333
|
+
if (!handle?.name)
|
|
3334
|
+
return { ok: false, code: 'AGENT_NOT_FOUND', error: 'current Agent not found' };
|
|
3335
|
+
if (!params.action) {
|
|
3336
|
+
return { ok: true, items: messageQueue.getQueueItemsByAgent(handle.name, {
|
|
3337
|
+
after: params.after,
|
|
3338
|
+
before: params.before,
|
|
3339
|
+
}) };
|
|
3340
|
+
}
|
|
3341
|
+
if (params.action === 'clear')
|
|
3342
|
+
return { ok: true, cleared: messageQueue.clearByAgent(handle.name) };
|
|
3343
|
+
if (params.action === 'cancel') {
|
|
3344
|
+
if (!params.messageId)
|
|
3345
|
+
return { ok: false, code: 'INVALID_ARGUMENT', error: 'missing messageId' };
|
|
3346
|
+
return { ok: true, cancelled: messageQueue.cancelMessageById(handle.name, params.messageId) };
|
|
3347
|
+
}
|
|
3348
|
+
if (!params.sessionKey)
|
|
3349
|
+
return { ok: false, code: 'INVALID_ARGUMENT', error: 'missing sessionKey' };
|
|
3350
|
+
const targetSessionId = messageQueue.findSessionIdBySessionKey(params.sessionKey);
|
|
3351
|
+
if (!targetSessionId)
|
|
3352
|
+
return { ok: false, code: 'SESSION_NOT_FOUND', error: `session not found: ${params.sessionKey}` };
|
|
3353
|
+
const targetSession = await sessionManager.getSessionById(targetSessionId);
|
|
3354
|
+
if (!targetSession || targetSession.selfAID !== authorization.aid) {
|
|
3355
|
+
return { ok: false, code: 'SCOPE_MISMATCH', error: 'session does not belong to the current Agent' };
|
|
3356
|
+
}
|
|
3357
|
+
return { ok: true, interrupted: await messageQueue.interruptBySession(targetSessionId) };
|
|
3358
|
+
});
|
|
3359
|
+
ipcServer.setStatusAuthorizationExecutor(async (params) => {
|
|
3360
|
+
const authorization = await authorizeManagedAgentOperation(params, 'system.status');
|
|
3361
|
+
return authorization.ok ? { ok: true } : authorization;
|
|
3362
|
+
});
|
|
3363
|
+
ipcServer.setStorageAuthorizationExecutor(async (params) => {
|
|
3364
|
+
const authorization = await authorizeManagedAgentOperation({ ...params, targetAid: params.aid }, params.operation);
|
|
3365
|
+
return authorization.ok ? { ok: true } : authorization;
|
|
3366
|
+
});
|
|
3006
3367
|
ipcServer.setTriggerExecutor(async (cmd) => {
|
|
3007
3368
|
const schedulerFor = (agentAid) => {
|
|
3008
3369
|
const scheduler = triggerSchedulers.get(agentAid);
|