evolcore 0.0.12 → 0.0.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +22 -0
- package/bin/ec-safe-output.js +26 -7
- package/dist/agents/claude-runner.js +53 -7
- package/dist/agents/codex-app-server-client.js +53 -5
- package/dist/agents/codex-runner.js +104 -59
- package/dist/agents/ecagent-runner.js +4 -1
- package/dist/aun/aid/control-aid.js +36 -13
- package/dist/aun/aid/store.js +13 -7
- package/dist/aun/group-fs-download.js +56 -0
- package/dist/aun/group-identity.js +1 -2
- package/dist/aun/msg/group.js +11 -33
- package/dist/aun/msg/index.js +1 -2
- package/dist/aun/msg/managed-operation.js +116 -55
- package/dist/aun/msg/upload.js +16 -1
- package/dist/aun/outbox.js +31 -2
- package/dist/aun/rpc/client.js +66 -0
- package/dist/aun/rpc/index.js +1 -2
- package/dist/aun/storage/{upload.js → client.js} +11 -2
- package/dist/aun/storage/index.js +1 -2
- package/dist/channels/aun.js +371 -136
- package/dist/cli/agent.js +9 -96
- package/dist/cli/cli-argv.js +10 -0
- package/dist/cli/config.js +9 -17
- package/dist/cli/ctl-command.js +1 -1
- package/dist/cli/daemon-commands.js +39 -24
- package/dist/cli/fs-command.js +19 -10
- package/dist/cli/init.js +77 -8
- package/dist/cli/queue-command.js +42 -0
- package/dist/cli/restart-monitor.js +3 -14
- package/dist/cli/trigger-command.js +7 -0
- package/dist/config/access-policy.js +1 -2
- package/dist/config/builtin-roles.js +5 -0
- package/dist/config/config-manager.js +21 -1
- package/dist/config/peer-role-resolver.js +6 -2
- package/dist/config/role-migration-startup.js +53 -0
- package/dist/config/role-store.js +1 -1
- package/dist/core/auth/agent-delegation.js +1 -1
- package/dist/core/auth/auth-gateway.js +29 -1
- package/dist/core/auth/authorization-audit.js +15 -0
- package/dist/core/auth/operation-authorizer.js +1 -0
- package/dist/core/auth/operation-catalog.js +9 -0
- package/dist/core/bootstrap-messages.js +9 -2
- package/dist/core/bootstrap-service.js +1 -0
- package/dist/core/command/command-handler.js +198 -51
- package/dist/core/command/menu-catalog.js +4 -0
- package/dist/core/command/menu-handler.js +85 -18
- package/dist/core/command/menu-protocol.js +23 -2
- package/dist/core/command/slash-handler.js +58 -10
- package/dist/core/event-catalog.js +6 -0
- package/dist/core/handoff/runtime.js +12 -1
- package/dist/core/handoff/store.js +5 -1
- package/dist/core/interaction-router.js +10 -0
- package/dist/core/message/im-renderer.js +19 -0
- package/dist/core/message/message-bridge.js +63 -11
- package/dist/core/message/message-log.js +2 -0
- package/dist/core/message/message-queue.js +267 -39
- package/dist/core/message/message-utils.js +81 -4
- package/dist/core/message/response-engine.js +107 -41
- package/dist/core/message/stream-debouncer.js +10 -2
- package/dist/core/permission/approval-gateway.js +1 -0
- package/dist/core/permission/ec-command-parser.js +93 -14
- package/dist/core/permission/index.js +1 -1
- package/dist/core/permission/mode.js +15 -0
- package/dist/core/permission/readonly-shell-query.js +148 -8
- package/dist/core/permission/sandbox-runtime.js +21 -0
- package/dist/core/permission/tool-policy.js +249 -27
- package/dist/core/protected-paths.js +107 -2
- package/dist/core/session/session-manager.js +21 -2
- package/dist/core/session/session-mapper.js +20 -1
- package/dist/eck/detect.js +18 -1
- package/dist/eck/group-rules-sync.js +15 -44
- package/dist/index.js +161 -86
- package/dist/ipc.js +16 -3
- package/dist/paths.js +3 -0
- package/dist/trigger/feedback.js +64 -11
- package/dist/trigger/manager.js +21 -1
- package/dist/trigger/parser.js +14 -2
- package/dist/trigger/scheduler.js +1 -0
- package/dist/trigger/validation.js +67 -6
- package/dist/utils/ecweb-supervisor.js +332 -0
- package/dist/utils/error-utils.js +10 -0
- package/kits/docs/evolcore/trigger.md +17 -1
- package/kits/migrations/migrate-contact-book-v2.mjs +1 -2
- package/kits/schemas/role-config.schema.1.json +1 -0
- package/kits/templates/roles/admin.json +5 -0
- package/kits/templates/roles/member.json +7 -1
- package/kits/templates/roles/visitor.json +2 -0
- package/package.json +1 -1
- package/dist/aun/msg/payload-type.js +0 -27
- package/dist/aun/rpc/caller.js +0 -42
- package/dist/aun/rpc/connection.js +0 -25
- package/dist/aun/storage/manage.js +0 -10
- package/dist/config/access-policy-domain.js +0 -18
- package/dist/config/config-batch-get.js +0 -11
- package/dist/config/owner-policy.js +0 -4
- package/dist/config/role-config-v4-startup.js +0 -32
- package/dist/config/role-config-v5-startup.js +0 -27
- package/dist/core/auth/trigger-authorization.js +0 -15
- package/dist/core/interaction-registration.js +0 -10
- package/dist/core/permission/execution-sandbox.js +0 -16
- package/dist/core/relation/peer-key.js +0 -1
- package/dist/core/session/session-key.js +0 -24
- package/dist/eck/baseagent-caps.js +0 -18
- package/dist/eck/rules-loader.js +0 -28
package/dist/eck/detect.js
CHANGED
|
@@ -1,6 +1,23 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
2
|
import path from 'path';
|
|
3
|
-
|
|
3
|
+
export const BASEAGENT_CAPS = {
|
|
4
|
+
'claude-code': {
|
|
5
|
+
autoLoadsRules: true,
|
|
6
|
+
supportsSystemPrompt: true,
|
|
7
|
+
},
|
|
8
|
+
'claude': {
|
|
9
|
+
autoLoadsRules: true,
|
|
10
|
+
supportsSystemPrompt: true,
|
|
11
|
+
},
|
|
12
|
+
'codex': {
|
|
13
|
+
autoLoadsRules: false,
|
|
14
|
+
supportsSystemPrompt: true,
|
|
15
|
+
},
|
|
16
|
+
'gemini': {
|
|
17
|
+
autoLoadsRules: false,
|
|
18
|
+
supportsSystemPrompt: true,
|
|
19
|
+
},
|
|
20
|
+
};
|
|
4
21
|
const MAX_DEPTH = 5;
|
|
5
22
|
export function resolveEckInjection(agentConfig, projectPath, kitsRulesPath) {
|
|
6
23
|
const caps = BASEAGENT_CAPS[agentConfig.baseAgent]
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
2
|
import path from 'path';
|
|
3
|
-
import { createHash } from 'crypto';
|
|
4
3
|
import { TextDecoder } from 'util';
|
|
5
4
|
import { getAidStore, isValidAid, loadClient, SLOT } from '../aun/aid/index.js';
|
|
5
|
+
import { downloadGroupFsBytes, GroupFsDownloadError } from '../aun/group-fs-download.js';
|
|
6
6
|
import { checkGroupIndex as checkGroupIndexRpc, getGroupIndex as getGroupIndexRpc } from '../aun/msg/group-index.js';
|
|
7
7
|
import { agentRelationsDir } from '../paths.js';
|
|
8
8
|
import { formatPeerKey } from '../core/relation/peer-identity.js';
|
|
@@ -220,7 +220,9 @@ function lazyGroupClient(selfAid) {
|
|
|
220
220
|
client: async () => {
|
|
221
221
|
if (client)
|
|
222
222
|
return client;
|
|
223
|
-
|
|
223
|
+
// This short-lived RPC client must never share the daemon's gateway
|
|
224
|
+
// cursor. A short connection can perform a background pull on connect.
|
|
225
|
+
store = await getAidStore({ slotId: SLOT.groupRules });
|
|
224
226
|
client = await loadClient(store, selfAid);
|
|
225
227
|
await client.connect({ connection_kind: 'short', short_ttl_ms: 30000, auto_reconnect: false });
|
|
226
228
|
return client;
|
|
@@ -242,39 +244,15 @@ function lazyGroupClient(selfAid) {
|
|
|
242
244
|
};
|
|
243
245
|
}
|
|
244
246
|
async function downloadGroupFileBytes(client, remoteRef) {
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
throw statusError('unreadable', 'group.fs.create_download_ticket returned invalid response');
|
|
248
|
-
}
|
|
249
|
-
const downloadUrl = stringField(ticket, ['download_url', 'url']);
|
|
250
|
-
if (!downloadUrl) {
|
|
251
|
-
throw statusError('unreadable', 'group.fs.create_download_ticket did not return download_url');
|
|
252
|
-
}
|
|
253
|
-
const bytes = await client.group.fs.lowlevel.httpGet(downloadUrl, bearerHeaders(client));
|
|
254
|
-
const expectedSha = stringField(ticket, ['sha256']);
|
|
255
|
-
if (expectedSha && sha256Hex(bytes).toLowerCase() !== expectedSha.toLowerCase()) {
|
|
256
|
-
throw statusError('unreadable', 'download hash verification failed');
|
|
247
|
+
try {
|
|
248
|
+
return (await downloadGroupFsBytes(client, remoteRef)).bytes;
|
|
257
249
|
}
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
function bearerHeaders(client) {
|
|
264
|
-
const token = accessTokenFromClient(client);
|
|
265
|
-
return token ? { Authorization: `Bearer ${token}` } : undefined;
|
|
266
|
-
}
|
|
267
|
-
function accessTokenFromClient(client) {
|
|
268
|
-
const anyClient = client;
|
|
269
|
-
if (typeof anyClient.getAccessToken === 'function') {
|
|
270
|
-
const token = stringValue(anyClient.getAccessToken());
|
|
271
|
-
if (token)
|
|
272
|
-
return token;
|
|
250
|
+
catch (error) {
|
|
251
|
+
if (error instanceof GroupFsDownloadError) {
|
|
252
|
+
throw statusError('unreadable', error.message);
|
|
253
|
+
}
|
|
254
|
+
throw error;
|
|
273
255
|
}
|
|
274
|
-
return stringValue(anyClient.accessToken)
|
|
275
|
-
|| stringValue(anyClient.access_token)
|
|
276
|
-
|| stringValue(anyClient._identity?.access_token)
|
|
277
|
-
|| stringValue(anyClient._sessionParams?.access_token);
|
|
278
256
|
}
|
|
279
257
|
function assertUtf8(bytes) {
|
|
280
258
|
new TextDecoder('utf-8', { fatal: true }).decode(bytes);
|
|
@@ -302,6 +280,10 @@ function classifyError(e) {
|
|
|
302
280
|
if (e instanceof StatusError)
|
|
303
281
|
return e.status;
|
|
304
282
|
const msg = errorMessage(e).toLowerCase();
|
|
283
|
+
// A peer cache miss is a signer/identity lookup failure, not a missing
|
|
284
|
+
// rules file.
|
|
285
|
+
if (msg.includes('peer not found in cache'))
|
|
286
|
+
return 'unreadable';
|
|
305
287
|
if (msg.includes('forbidden') || msg.includes('unauthorized') || msg.includes('permission'))
|
|
306
288
|
return 'forbidden';
|
|
307
289
|
if (msg.includes('not found') || msg.includes('does not exist'))
|
|
@@ -314,17 +296,6 @@ function errorMessage(e) {
|
|
|
314
296
|
function isRecord(x) {
|
|
315
297
|
return typeof x === 'object' && x !== null && !Array.isArray(x);
|
|
316
298
|
}
|
|
317
|
-
function stringValue(x) {
|
|
318
|
-
return typeof x === 'string' ? x : '';
|
|
319
|
-
}
|
|
320
|
-
function stringField(obj, keys) {
|
|
321
|
-
for (const k of keys) {
|
|
322
|
-
const v = obj[k];
|
|
323
|
-
if (typeof v === 'string')
|
|
324
|
-
return v;
|
|
325
|
-
}
|
|
326
|
-
return undefined;
|
|
327
|
-
}
|
|
328
299
|
function numberField(obj, keys) {
|
|
329
300
|
for (const k of keys) {
|
|
330
301
|
const v = obj[k];
|
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,7 +38,7 @@ 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';
|
|
@@ -58,10 +55,9 @@ 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';
|
|
65
61
|
import { parsePeerKey } from './core/relation/peer-identity.js';
|
|
66
62
|
import { isHClassPath } from './core/protected-paths.js';
|
|
67
63
|
import { ChannelLoader, tryParseChannelKey } from './core/channel-loader.js';
|
|
@@ -72,7 +68,8 @@ import { AgentReloadBusyError, AgentReloadCoordinator } from './core/agent-reloa
|
|
|
72
68
|
import { buildReloadHooks } from './core/channel-loader.js';
|
|
73
69
|
import { IpcServer } from './ipc.js';
|
|
74
70
|
import { logger, setLogLevel } from './utils/logger.js';
|
|
75
|
-
import {
|
|
71
|
+
import { fetchEcwebPairCode } from './utils/ecweb-utils.js';
|
|
72
|
+
import { createEcwebSupervisor } from './utils/ecweb-supervisor.js';
|
|
76
73
|
import { writeMain, removeAll, isMainWinner, scanInstances } from './utils/instance-registry.js';
|
|
77
74
|
import { detectDuplicates } from './core/evolagent-registry.js';
|
|
78
75
|
import { loadKitManifest, cleanEckDebug, invalidateKitCache } from './eck/kit-renderer.js';
|
|
@@ -96,8 +93,6 @@ import { MAIN_PACKAGE_NAME } from './product.js';
|
|
|
96
93
|
import fs from 'fs';
|
|
97
94
|
import crypto from 'crypto';
|
|
98
95
|
import { fileURLToPath } from 'url';
|
|
99
|
-
import { spawn } from 'child_process';
|
|
100
|
-
import * as platform from './utils/cross-platform.js';
|
|
101
96
|
const controlMenuDeduper = new MenuRequestDeduper();
|
|
102
97
|
/** 出站 payload 摘要(用于 channel-out.log) */
|
|
103
98
|
function summarizeOutboundPayload(payload) {
|
|
@@ -176,6 +171,61 @@ function normalizeMessageSource(value) {
|
|
|
176
171
|
? value
|
|
177
172
|
: 'daemon';
|
|
178
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
|
+
}
|
|
179
229
|
function daemonConversationWatchdogMs(settings) {
|
|
180
230
|
const idleTimeoutSec = settings.idleMonitor?.timeout;
|
|
181
231
|
const idleMs = typeof idleTimeoutSec === 'number' && Number.isFinite(idleTimeoutSec) && idleTimeoutSec > 0
|
|
@@ -205,6 +255,10 @@ function originFromActorSession(session, authenticatedPeerId, authenticatedChann
|
|
|
205
255
|
threadId: sameConversation && !isGroup ? (session.threadId || undefined) : undefined,
|
|
206
256
|
peerId,
|
|
207
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' },
|
|
208
262
|
};
|
|
209
263
|
}
|
|
210
264
|
function seedUpgradeCheckTrigger(manager, owner) {
|
|
@@ -437,7 +491,7 @@ async function runBindBootstrapDaemon(daemonCfg) {
|
|
|
437
491
|
}
|
|
438
492
|
const response = parsed ? await bindService.handleRequest(parsed, opts.peerId) : null;
|
|
439
493
|
if (response) {
|
|
440
|
-
await controlChannel.sendMessage(opts.channelId, JSON.stringify(response));
|
|
494
|
+
await controlChannel.sendMessage(opts.channelId, JSON.stringify(response), controlReplyContext(opts));
|
|
441
495
|
}
|
|
442
496
|
});
|
|
443
497
|
const controlToken = crypto.randomBytes(32).toString('base64url');
|
|
@@ -789,6 +843,23 @@ async function main() {
|
|
|
789
843
|
'如需远程管理,请在 daemon.json 配置 owners: [<你的 AID>]');
|
|
790
844
|
}
|
|
791
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
|
+
}
|
|
792
863
|
if (bindBootstrapMode && daemonCfg.aid && loadAllAgents().agents.length === 0) {
|
|
793
864
|
await runBindBootstrapDaemon(daemonCfg);
|
|
794
865
|
return;
|
|
@@ -1091,7 +1162,7 @@ async function main() {
|
|
|
1091
1162
|
if (!adapter)
|
|
1092
1163
|
return;
|
|
1093
1164
|
if (text) {
|
|
1094
|
-
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' });
|
|
1095
1166
|
}
|
|
1096
1167
|
};
|
|
1097
1168
|
return cmdHandler.handle(content, channel, channelId, sendFn, userId, threadId);
|
|
@@ -1120,6 +1191,7 @@ async function main() {
|
|
|
1120
1191
|
await processor.processMessage(message);
|
|
1121
1192
|
}, {
|
|
1122
1193
|
persistencePath: daemonQueuePath(),
|
|
1194
|
+
dedupPath: daemonMessageDedupPath(),
|
|
1123
1195
|
});
|
|
1124
1196
|
// 设置中断回调(精确中断正在处理的 agent)
|
|
1125
1197
|
messageQueue.setInterruptCallback(async (sessionKey, baseagent, evolagentName, reason = 'new_message') => {
|
|
@@ -1160,12 +1232,20 @@ async function main() {
|
|
|
1160
1232
|
source: 'msg',
|
|
1161
1233
|
handoffTrace: { version: 2, handoff_id: handoff.handoff_id },
|
|
1162
1234
|
};
|
|
1163
|
-
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'
|
|
1164
1243
|
? typeof channel.sendDaemonGroupMsg === 'function'
|
|
1165
1244
|
? await channel.sendDaemonGroupMsg({
|
|
1166
|
-
groupId:
|
|
1245
|
+
groupId: delivery.groupId,
|
|
1167
1246
|
payload: { ...handoff.request.payload, [AUN_HANDOFF_MARKER_FIELD]: handoff.handoff_id },
|
|
1168
1247
|
encrypt: handoff.request.encrypt,
|
|
1248
|
+
causation: handoff.causation,
|
|
1169
1249
|
log,
|
|
1170
1250
|
})
|
|
1171
1251
|
: { ok: false, error: 'AUN channel does not support daemon group sends' }
|
|
@@ -1279,6 +1359,14 @@ async function main() {
|
|
|
1279
1359
|
if (target && !getTriggerChannel(definition.agentAid, target.channelKey)) {
|
|
1280
1360
|
throw new Error(`agent ${definition.agentAid} has no configured channel ${target.channelKey}`);
|
|
1281
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
|
+
}
|
|
1282
1370
|
};
|
|
1283
1371
|
const validateTriggerDefinitionForActor = (definition, actor) => {
|
|
1284
1372
|
if (definition.execution.type === 'script' && !actor.management) {
|
|
@@ -1520,9 +1608,13 @@ async function main() {
|
|
|
1520
1608
|
&& (candidate.adapter.channelKey === target.channelKey || candidate.adapter.channelName === target.channelKey)
|
|
1521
1609
|
&& tryParseChannelKey(candidate.adapter.channelKey)?.selfAID === input.trigger.agentAid));
|
|
1522
1610
|
const channel = inst?.channel;
|
|
1523
|
-
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)
|
|
1524
1617
|
return null;
|
|
1525
|
-
const targetChatType = channel.isGroupId(target.channelId) ? 'group' : 'private';
|
|
1526
1618
|
if (targetChatType === 'group' && typeof channel.sendDaemonGroupMsg !== 'function')
|
|
1527
1619
|
return null;
|
|
1528
1620
|
if (targetChatType === 'private' && typeof channel.sendDaemonMsg !== 'function')
|
|
@@ -1748,8 +1840,8 @@ async function main() {
|
|
|
1748
1840
|
});
|
|
1749
1841
|
}
|
|
1750
1842
|
// 5. 撤回消息 → 中断执行中任务
|
|
1751
|
-
inst.channel.onRecall?.((messageId) => {
|
|
1752
|
-
msgBridge.cancel(messageId);
|
|
1843
|
+
inst.channel.onRecall?.((messageId, selfAID) => {
|
|
1844
|
+
msgBridge.cancel(messageId, selfAID);
|
|
1753
1845
|
});
|
|
1754
1846
|
}
|
|
1755
1847
|
// ── 注册所有渠道实例 ──
|
|
@@ -1858,6 +1950,7 @@ async function main() {
|
|
|
1858
1950
|
channel: adapter.channelName,
|
|
1859
1951
|
channelId: ownerAid,
|
|
1860
1952
|
agentName,
|
|
1953
|
+
delivery: { chatType: 'private' },
|
|
1861
1954
|
});
|
|
1862
1955
|
sendSystemPayload(adapter, envelope, {
|
|
1863
1956
|
kind: 'system.notice',
|
|
@@ -1896,16 +1989,31 @@ async function main() {
|
|
|
1896
1989
|
logger.info(`[Restart] Pending notification waits for channel connection: ${pending.channel}`);
|
|
1897
1990
|
return;
|
|
1898
1991
|
}
|
|
1899
|
-
const
|
|
1900
|
-
|
|
1901
|
-
:
|
|
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
|
+
};
|
|
1902
2009
|
const owningAgent = agentRegistry.resolveByChannel(adapter.channelKey);
|
|
1903
2010
|
const envelope = buildEnvelope({
|
|
1904
2011
|
taskId: `system-restart-${process.pid}`,
|
|
1905
2012
|
channel: adapter.channelKey,
|
|
1906
2013
|
channelId: pending.channelId,
|
|
1907
2014
|
agentName: owningAgent?.aid || 'evolcore',
|
|
1908
|
-
|
|
2015
|
+
...(restartDelivery.delivery ? { delivery: restartDelivery.delivery } : {}),
|
|
2016
|
+
replyContext: Object.keys(replyContext).length > 0 ? replyContext : undefined,
|
|
1909
2017
|
});
|
|
1910
2018
|
await sendSystemPayload(adapter, envelope, {
|
|
1911
2019
|
kind: 'system.notice',
|
|
@@ -2115,53 +2223,6 @@ async function main() {
|
|
|
2115
2223
|
catch (e) {
|
|
2116
2224
|
logger.warn(`控制 AID 首连失败(后台自动重连,不影响 daemon 主流程): ${e?.message || e}`);
|
|
2117
2225
|
}
|
|
2118
|
-
// ── ECWeb 自动启动 ──
|
|
2119
|
-
// 如果 config.ecweb.enabled,自动拉起 ecweb 服务
|
|
2120
|
-
if (daemonCfg.ecweb?.enabled) {
|
|
2121
|
-
const ecwebPath = path.join(resolvePaths().root, 'ecweb');
|
|
2122
|
-
const ecwebEntry = path.join(ecwebPath, 'dist', 'index.js');
|
|
2123
|
-
if (fs.existsSync(ecwebEntry)) {
|
|
2124
|
-
let ecwebVersion;
|
|
2125
|
-
try {
|
|
2126
|
-
const pkg = JSON.parse(fs.readFileSync(path.join(ecwebPath, 'package.json'), 'utf-8'));
|
|
2127
|
-
ecwebVersion = pkg.version;
|
|
2128
|
-
}
|
|
2129
|
-
catch { }
|
|
2130
|
-
const versionError = ecwebVersionRequirementError(ecwebVersion);
|
|
2131
|
-
if (versionError) {
|
|
2132
|
-
logger.error(`ECWeb 未启动: ${versionError}`);
|
|
2133
|
-
}
|
|
2134
|
-
else {
|
|
2135
|
-
try {
|
|
2136
|
-
const ecwebProc = spawn(process.execPath, [ecwebEntry], {
|
|
2137
|
-
cwd: ecwebPath,
|
|
2138
|
-
detached: true,
|
|
2139
|
-
stdio: 'ignore',
|
|
2140
|
-
windowsHide: platform.isWindows,
|
|
2141
|
-
env: { ...process.env, EVOLCORE_HOME: resolvePaths().root }
|
|
2142
|
-
});
|
|
2143
|
-
ecwebProc.unref();
|
|
2144
|
-
logger.info(`✓ ECWeb 已启动 (PID: ${ecwebProc.pid})`);
|
|
2145
|
-
onShutdown(() => {
|
|
2146
|
-
try {
|
|
2147
|
-
if (ecwebProc.pid)
|
|
2148
|
-
platform.killProcess(ecwebProc.pid, true);
|
|
2149
|
-
logger.info(`✓ ECWeb 已停止`);
|
|
2150
|
-
}
|
|
2151
|
-
catch { }
|
|
2152
|
-
});
|
|
2153
|
-
}
|
|
2154
|
-
catch (e) {
|
|
2155
|
-
logger.warn(`ECWeb 启动失败: ${e?.message || e}`);
|
|
2156
|
-
}
|
|
2157
|
-
}
|
|
2158
|
-
}
|
|
2159
|
-
else {
|
|
2160
|
-
// 正式安装时 ECWeb 是独立的全局 ec-web 包,由 ec start/restart 的
|
|
2161
|
-
// 启动链路管理;runtime 目录没有内嵌构建产物是正常状态。
|
|
2162
|
-
logger.debug(`ECWeb runtime entry absent (managed externally): ${ecwebEntry}`);
|
|
2163
|
-
}
|
|
2164
|
-
}
|
|
2165
2226
|
// 控制 AID 接收 owner 指令:
|
|
2166
2227
|
// 1. /pair — ECWeb 配对码(文本快路径)
|
|
2167
2228
|
// 2. menu.* JSON — 路由到 cmdHandler.execMenuForControl(进程级 + 全量权限)
|
|
@@ -2176,22 +2237,20 @@ async function main() {
|
|
|
2176
2237
|
catch {
|
|
2177
2238
|
parsed = null;
|
|
2178
2239
|
}
|
|
2240
|
+
const menuResponseContext = () => controlReplyContext(opts, evolMenuResponseTransportMetadata());
|
|
2179
2241
|
if (bindService && parsed?.type === 'bind.request') {
|
|
2180
2242
|
const response = await bindService.handleRequest(parsed, opts.peerId);
|
|
2181
2243
|
if (response) {
|
|
2182
2244
|
// 用 sendStructured 直发 typed payload(payload.type='bind.response'),
|
|
2183
2245
|
// 不能用 sendMessage——它会把内容包成 {type:'text', text:...},App 无法识别。
|
|
2184
2246
|
// encrypted 跟随入站请求:bind.response 与 bind.request 的加密/明文对称。
|
|
2185
|
-
await controlChannel.sendStructured(opts.channelId, response,
|
|
2247
|
+
await controlChannel.sendStructured(opts.channelId, response, controlReplyContext(opts));
|
|
2186
2248
|
}
|
|
2187
2249
|
return;
|
|
2188
2250
|
}
|
|
2189
2251
|
const menuControl = parseMenuControl(text);
|
|
2190
2252
|
const isRoleMenu = menuControl.isMenu
|
|
2191
2253
|
&& (menuControl.name === 'role' || menuControl.request.cmd === '/role');
|
|
2192
|
-
const menuResponseContext = () => ({
|
|
2193
|
-
metadata: evolMenuResponseTransportMetadata(),
|
|
2194
|
-
});
|
|
2195
2254
|
if (menuControl.isMenu && !hasValidMenuId(menuControl)) {
|
|
2196
2255
|
logger.warn(`[ControlMenu] dropped malformed request without id type=${menuControl.type}`);
|
|
2197
2256
|
return;
|
|
@@ -2216,7 +2275,7 @@ async function main() {
|
|
|
2216
2275
|
else {
|
|
2217
2276
|
reply = 'ECWeb 未运行或暂不可达。请在主机运行 ec watch web 启动后重试。';
|
|
2218
2277
|
}
|
|
2219
|
-
await controlChannel.sendMessage(opts.channelId, reply);
|
|
2278
|
+
await controlChannel.sendMessage(opts.channelId, reply, controlReplyContext(opts));
|
|
2220
2279
|
return;
|
|
2221
2280
|
}
|
|
2222
2281
|
// menu.* JSON 路由:owner 已在上方校验,转交 execMenuForControl(fromControlChannel=true)
|
|
@@ -2235,6 +2294,16 @@ async function main() {
|
|
|
2235
2294
|
return;
|
|
2236
2295
|
}
|
|
2237
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
|
+
}
|
|
2238
2307
|
const response = 'conflict' in deduped
|
|
2239
2308
|
? menuFailure({ id: menuControl.id, ...(menuControl.name?.trim() ? { name: menuControl.name } : {}) }, { code: 'CONFLICT', message: 'Request ID was already used with a different payload' })
|
|
2240
2309
|
: deduped.value;
|
|
@@ -2244,7 +2313,7 @@ async function main() {
|
|
|
2244
2313
|
return;
|
|
2245
2314
|
}
|
|
2246
2315
|
// owner 发的其他内容:提示可用指令
|
|
2247
|
-
await controlChannel.sendMessage(opts.channelId, '可用指令:/pair(获取 ECWeb 登录配对码)');
|
|
2316
|
+
await controlChannel.sendMessage(opts.channelId, '可用指令:/pair(获取 ECWeb 登录配对码)', controlReplyContext(opts));
|
|
2248
2317
|
}
|
|
2249
2318
|
catch (e) {
|
|
2250
2319
|
logger.warn(`控制 AID 消息处理失败: ${e?.message || e}`);
|
|
@@ -2296,6 +2365,7 @@ async function main() {
|
|
|
2296
2365
|
channel: other.adapter.channelKey,
|
|
2297
2366
|
channelId: ownerId,
|
|
2298
2367
|
agentName: owningAgent?.aid || 'evolcore',
|
|
2368
|
+
delivery: { chatType: 'private' },
|
|
2299
2369
|
});
|
|
2300
2370
|
sendSystemPayload(other.adapter, envelope, {
|
|
2301
2371
|
kind: 'system.error',
|
|
@@ -2385,11 +2455,13 @@ async function main() {
|
|
|
2385
2455
|
channelType: session.channelType || parsedResumeKey?.type,
|
|
2386
2456
|
selfAID: resumeSelfAID,
|
|
2387
2457
|
channelId: session.channelId,
|
|
2458
|
+
groupId: session.chatType === 'group' ? (session.metadata?.groupId || session.channelId) : undefined,
|
|
2459
|
+
chatType: session.chatType === 'group' ? 'group' : 'private',
|
|
2388
2460
|
content: '服务已重启,请继续之前未完成的任务。',
|
|
2389
2461
|
timestamp: Date.now(),
|
|
2390
2462
|
peerId: '',
|
|
2391
2463
|
threadId: session.threadId || undefined,
|
|
2392
|
-
replyContext: session
|
|
2464
|
+
replyContext: replyContextFromSession(session),
|
|
2393
2465
|
};
|
|
2394
2466
|
// 清除状态后入队(processMessage 会重新标记)
|
|
2395
2467
|
sessionManager.clearProcessing(session.id);
|
|
@@ -2721,10 +2793,9 @@ async function main() {
|
|
|
2721
2793
|
if (!ch) {
|
|
2722
2794
|
return { ok: false, error: `AUN channel not found for ${params.aid}`, code: 'AUN_CHANNEL_NOT_FOUND' };
|
|
2723
2795
|
}
|
|
2724
|
-
|
|
2725
|
-
|
|
2726
|
-
|
|
2727
|
-
}
|
|
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.
|
|
2728
2799
|
const encrypt = typeof params.encrypt === 'boolean'
|
|
2729
2800
|
? params.encrypt
|
|
2730
2801
|
: (typeof ch.getDefaultEncrypt === 'function' ? ch.getDefaultEncrypt() : true);
|
|
@@ -3180,7 +3251,7 @@ async function main() {
|
|
|
3180
3251
|
const agentName = handle?.name;
|
|
3181
3252
|
if (!agentName)
|
|
3182
3253
|
return [];
|
|
3183
|
-
return messageQueue.getQueueItemsByAgent(agentName);
|
|
3254
|
+
return messageQueue.getQueueItemsByAgent(agentName, { after: params.after, before: params.before });
|
|
3184
3255
|
});
|
|
3185
3256
|
ipcServer.setQueueActionExecutor(async (params) => {
|
|
3186
3257
|
const handle = agentRegistry.get(params.agent);
|
|
@@ -3261,8 +3332,12 @@ async function main() {
|
|
|
3261
3332
|
const handle = agentRegistry.get(authorization.aid);
|
|
3262
3333
|
if (!handle?.name)
|
|
3263
3334
|
return { ok: false, code: 'AGENT_NOT_FOUND', error: 'current Agent not found' };
|
|
3264
|
-
if (!params.action)
|
|
3265
|
-
return { ok: true, items: messageQueue.getQueueItemsByAgent(handle.name
|
|
3335
|
+
if (!params.action) {
|
|
3336
|
+
return { ok: true, items: messageQueue.getQueueItemsByAgent(handle.name, {
|
|
3337
|
+
after: params.after,
|
|
3338
|
+
before: params.before,
|
|
3339
|
+
}) };
|
|
3340
|
+
}
|
|
3266
3341
|
if (params.action === 'clear')
|
|
3267
3342
|
return { ok: true, cleared: messageQueue.clearByAgent(handle.name) };
|
|
3268
3343
|
if (params.action === 'cancel') {
|
package/dist/ipc.js
CHANGED
|
@@ -15,6 +15,7 @@ import { hashCurrentDelegatedCommand } from './core/auth/agent-delegation.js';
|
|
|
15
15
|
import { normalizeAunMentionEntries } from './aun/msg/mention-schema.js';
|
|
16
16
|
const isWindows = process.platform === 'win32';
|
|
17
17
|
const isNamedPipe = (p) => isWindows && p.startsWith('\\\\.\\pipe\\');
|
|
18
|
+
const finiteTimestamp = (value) => (typeof value === 'number' && Number.isFinite(value) ? value : undefined);
|
|
18
19
|
const LOCAL_CONTROL_COMMANDS = new Set([
|
|
19
20
|
'bind.begin',
|
|
20
21
|
'bind.status',
|
|
@@ -32,6 +33,12 @@ const LOCAL_CONTROL_COMMANDS = new Set([
|
|
|
32
33
|
'menu.exec',
|
|
33
34
|
'monitor-snapshot',
|
|
34
35
|
]);
|
|
36
|
+
function requiresLocalControlToken(cmd) {
|
|
37
|
+
// Managed bootstrap completion is authenticated by the session delegation
|
|
38
|
+
// ticket. A standalone CLI invocation still needs the daemon control token.
|
|
39
|
+
return LOCAL_CONTROL_COMMANDS.has(String(cmd.type ?? ''))
|
|
40
|
+
|| (cmd.type === 'evolagent.bootstrapComplete' && typeof cmd.sessionId !== 'string');
|
|
41
|
+
}
|
|
35
42
|
export function summarizeMonitorAgents(agents, aids) {
|
|
36
43
|
const connectedAgents = new Set(aids.filter(aid => aid.status === 'connected').map(aid => aid.agentName || aid.aid));
|
|
37
44
|
const enabledAgents = agents.filter(agent => agent.status !== 'disabled');
|
|
@@ -379,7 +386,7 @@ export class IpcServer {
|
|
|
379
386
|
}
|
|
380
387
|
}
|
|
381
388
|
async handleCommand(cmd) {
|
|
382
|
-
if (
|
|
389
|
+
if (requiresLocalControlToken(cmd) && !this.hasValidControlToken(cmd.controlToken)) {
|
|
383
390
|
return { ok: false, code: 'INVALID_CONTROL_TOKEN', error: 'valid local control token is required' };
|
|
384
391
|
}
|
|
385
392
|
switch (cmd.type) {
|
|
@@ -520,7 +527,11 @@ export class IpcServer {
|
|
|
520
527
|
// 纯查询
|
|
521
528
|
if (!this.queueSnapshotProvider)
|
|
522
529
|
return { ok: false, error: 'queue-snapshot not configured' };
|
|
523
|
-
return { ok: true, items: this.queueSnapshotProvider({
|
|
530
|
+
return { ok: true, items: this.queueSnapshotProvider({
|
|
531
|
+
agent: cmd.agent,
|
|
532
|
+
after: finiteTimestamp(cmd.after),
|
|
533
|
+
before: finiteTimestamp(cmd.before),
|
|
534
|
+
}) };
|
|
524
535
|
}
|
|
525
536
|
// 操作:clear / cancel / interrupt
|
|
526
537
|
if (!this.queueActionExecutor)
|
|
@@ -546,6 +557,8 @@ export class IpcServer {
|
|
|
546
557
|
action: cmd.action,
|
|
547
558
|
messageId: typeof cmd.messageId === 'string' ? cmd.messageId : undefined,
|
|
548
559
|
sessionKey: typeof cmd.sessionKey === 'string' ? cmd.sessionKey : undefined,
|
|
560
|
+
after: finiteTimestamp(cmd.after),
|
|
561
|
+
before: finiteTimestamp(cmd.before),
|
|
549
562
|
sessionId: cmd.sessionId,
|
|
550
563
|
delegationToken: typeof cmd.delegationToken === 'string' ? cmd.delegationToken : undefined,
|
|
551
564
|
delegationCommandHash: typeof cmd.delegationCommandHash === 'string' ? cmd.delegationCommandHash : undefined,
|
|
@@ -1037,7 +1050,7 @@ export class IpcServer {
|
|
|
1037
1050
|
export function ipcQuery(socketPath, cmd, timeoutMs = 3000) {
|
|
1038
1051
|
let authenticatedCmd = cmd;
|
|
1039
1052
|
if (!process.env.EVOLCORE_SESSION_ID
|
|
1040
|
-
&&
|
|
1053
|
+
&& requiresLocalControlToken(cmd)
|
|
1041
1054
|
&& typeof cmd.controlToken !== 'string') {
|
|
1042
1055
|
try {
|
|
1043
1056
|
const controlToken = fs.readFileSync(resolvePaths().controlToken, 'utf8').trim();
|