evolcore 0.0.21 → 0.0.23
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/AGENTS.md +62 -0
- package/CHANGELOG.md +53 -0
- package/README.md +2 -0
- package/bin/codex-managed-acl.ps1 +155 -0
- package/bin/codex-managed-hook.mjs +3 -0
- package/bin/install-codex-managed-hooks.mjs +376 -96
- package/dist/agents/baseagent-availability.js +48 -0
- package/dist/agents/baseagent.js +1 -1
- package/dist/agents/claude-runner.js +44 -226
- package/dist/agents/{codex-app-server-client.js → codex/app-server-client.js} +103 -17
- package/dist/{utils/codex-app-server-registry.js → agents/codex/app-server-registry.js} +2 -2
- package/dist/agents/codex/catalog.js +70 -0
- package/dist/agents/codex/diagnostics.js +154 -0
- package/dist/agents/codex-runner.js +1292 -219
- package/dist/agents/ecagent-runner.js +10 -31
- package/dist/agents/gemini-runner.js +2 -6
- package/dist/aun/aid/store.js +2 -0
- package/dist/aun/menu-client.js +272 -0
- package/dist/aun/msg/group.js +65 -4
- package/dist/aun/msg/index.js +1 -1
- package/dist/aun/msg/managed-operation.js +51 -4
- package/dist/aun/outbox.js +7 -0
- package/dist/aun/rpc/client.js +8 -1
- package/dist/channels/aun.js +240 -36
- package/dist/channels/wechat.js +81 -10
- package/dist/cli/aun-commands.js +48 -4
- package/dist/cli/command-log.js +21 -2
- package/dist/cli/daemon-commands.js +169 -51
- package/dist/cli/index.js +20 -0
- package/dist/cli/init.js +2 -10
- package/dist/cli/menu-client.js +95 -0
- package/dist/cli/model.js +23 -8
- package/dist/cli/task-context.js +16 -2
- package/dist/cli/trigger-command.js +22 -5
- package/dist/config/config-field-policy.js +3 -0
- package/dist/config/config-manager.js +32 -5
- package/dist/config/contact-book-store.js +25 -3
- package/dist/config/schema-migration.js +14 -2
- package/dist/core/auth/agent-delegation.js +15 -0
- package/dist/core/auth/auth-gateway.js +8 -0
- package/dist/core/auth/authorization-audit.js +70 -6
- package/dist/core/auth/operation-catalog.js +4 -0
- package/dist/core/auth/remote-access-store.js +135 -0
- package/dist/core/bootstrap-messages.js +8 -0
- package/dist/core/bootstrap-service.js +93 -25
- package/dist/core/command/command-handler.js +62 -5
- package/dist/core/command/daemon-diagnostics.js +448 -0
- package/dist/core/command/menu-catalog.js +3 -0
- package/dist/core/command/menu-handler.js +150 -33
- package/dist/core/command/menu-protocol.js +120 -1
- package/dist/core/command/slash-gate.js +3 -3
- package/dist/core/command/slash-handler.js +83 -40
- package/dist/core/data-migration.js +11 -1
- package/dist/core/event-catalog.js +32 -0
- package/dist/core/handoff/runtime.js +23 -3
- package/dist/core/message/im-renderer.js +7 -3
- package/dist/core/message/message-bridge.js +60 -2
- package/dist/core/message/message-log.js +33 -0
- package/dist/core/message/message-queue.js +41 -15
- package/dist/core/message/response-engine.js +246 -114
- package/dist/core/model/catalog-service.js +308 -0
- package/dist/core/model/model-catalog.js +124 -334
- package/dist/core/model/model-diagnostics.js +4 -5
- package/dist/core/model/model-fallback.js +9 -16
- package/dist/core/model/model-permission.js +1 -1
- package/dist/core/permission/approval-gateway.js +16 -1
- package/dist/core/permission/ec-command-parser.js +272 -70
- package/dist/core/permission/protected-paths.js +74 -14
- package/dist/core/permission/sandbox-runtime.js +4 -2
- package/dist/core/permission/tool-error-code.js +12 -0
- package/dist/core/permission/tool-policy.js +290 -30
- package/dist/core/runtime-lock.js +13 -8
- package/dist/core/session/session-manager.js +30 -0
- package/dist/core/session/session-renew.js +18 -1
- package/dist/core/session/session-turn-coordinator.js +5 -1
- package/dist/index.js +104 -34
- package/dist/ipc.js +218 -96
- package/dist/paths.js +18 -0
- package/dist/response-system/engines/v1/proactive-flow.js +7 -2
- package/dist/stats/price-resolver.js +4 -0
- package/dist/trigger/feedback.js +14 -2
- package/dist/trigger/manager.js +3 -1
- package/dist/trigger/parser.js +86 -13
- package/dist/trigger/patch.js +104 -1
- package/dist/trigger/scheduler.js +20 -3
- package/dist/utils/ecweb-supervisor.js +11 -2
- package/dist/utils/ecweb-utils.js +8 -26
- package/dist/utils/error-utils.js +7 -0
- package/dist/utils/logger.js +9 -4
- package/dist/utils/npm-ops.js +47 -15
- package/dist/utils/tool-summary.js +59 -0
- package/dist/utils/windows-shell-trust.js +201 -0
- package/kits/docs/evolcore/INDEX.md +3 -2
- package/kits/docs/evolcore/agent-create.md +159 -0
- package/kits/docs/evolcore/agent.md +6 -0
- package/kits/docs/evolcore/group-collaboration.md +256 -0
- package/kits/docs/evolcore/group-rules.md +1 -19
- package/kits/docs/evolcore/group.md +3 -1
- package/kits/docs/evolcore/menu-client.md +34 -0
- package/kits/docs/evolcore/trigger.md +6 -3
- package/kits/docs/prompt-loading-architecture.md +6 -0
- package/kits/eck_message_manifest.json +6 -6
- package/kits/migrations/migrate-contact-book-v2.mjs +9 -1
- package/kits/migrations/migrate-role-config-v4.mjs +11 -5
- package/kits/migrations/migrate-role-config-v5.mjs +8 -2
- package/kits/schemas/_meta.json +3 -2
- package/kits/schemas/agent-config.schema.12.json +427 -0
- package/kits/templates/message-fragments/item.md +1 -1
- package/kits/templates/roles/visitor.json +1 -0
- package/kits/templates/system-fragments/bootstrap.md +10 -2
- package/kits/templates/system-fragments/commands.md +2 -2
- package/package.json +11 -3
|
@@ -828,6 +828,7 @@ export class SessionManager {
|
|
|
828
828
|
}
|
|
829
829
|
// Create new session
|
|
830
830
|
const sessionMetadata = { ...(metadata || {}) };
|
|
831
|
+
sessionMetadata.__sessionCreatedAt = Date.now();
|
|
831
832
|
const newIdentity = identity ?? this.resolveIdentity(channel, userId, chatType, identityConversationId);
|
|
832
833
|
const resolvedBaseagent = baseagent || 'claude';
|
|
833
834
|
const session = {
|
|
@@ -1111,6 +1112,33 @@ export class SessionManager {
|
|
|
1111
1112
|
}
|
|
1112
1113
|
return 'legacy_updated';
|
|
1113
1114
|
}
|
|
1115
|
+
/**
|
|
1116
|
+
* Clear a main-session backend only when the durable latest snapshot still
|
|
1117
|
+
* points at the caller's expected ID. This is the recovery counterpart to
|
|
1118
|
+
* provider-side "session not found" errors and must never rotate a topic.
|
|
1119
|
+
*/
|
|
1120
|
+
async clearMainSessionBackendIfMatches(sessionId, expectedAgentSessionId, metadataKeys = []) {
|
|
1121
|
+
const loaded = this.loadSessionForUpdate(sessionId);
|
|
1122
|
+
if (!loaded)
|
|
1123
|
+
return 'missing';
|
|
1124
|
+
const { current } = loaded;
|
|
1125
|
+
if (current.threadId)
|
|
1126
|
+
return 'topic';
|
|
1127
|
+
const currentId = normalizeBackendSessionId(current.agentSessionId);
|
|
1128
|
+
if (!currentId)
|
|
1129
|
+
return 'already_clear';
|
|
1130
|
+
if (currentId !== normalizeBackendSessionId(expectedAgentSessionId))
|
|
1131
|
+
return 'mismatch';
|
|
1132
|
+
current.agentSessionId = undefined;
|
|
1133
|
+
if (metadataKeys.length > 0 && current.metadata) {
|
|
1134
|
+
const metadata = { ...current.metadata };
|
|
1135
|
+
for (const key of metadataKeys)
|
|
1136
|
+
delete metadata[key];
|
|
1137
|
+
current.metadata = metadata;
|
|
1138
|
+
}
|
|
1139
|
+
this.persistSession(current, 'sync');
|
|
1140
|
+
return 'cleared';
|
|
1141
|
+
}
|
|
1114
1142
|
async switchAgent(channel, channelId, projectPath, newBaseagent) {
|
|
1115
1143
|
const inheritedChatType = this.getActiveChatType(channel, channelId);
|
|
1116
1144
|
const identity = this.deriveChannelIdentity(channel, channelId);
|
|
@@ -1424,6 +1452,7 @@ export class SessionManager {
|
|
|
1424
1452
|
throw new Error(`[SessionManager] createNewSession: baseagent is empty for channel=${channel} channelId=${channelId}`);
|
|
1425
1453
|
}
|
|
1426
1454
|
const metadata = newSessionStableMetadata(inheritedChatType, activeMetadata, identityMetadata);
|
|
1455
|
+
metadata.__sessionCreatedAt = Date.now();
|
|
1427
1456
|
if (identityMetadata?.sessionBoundaryOperationId) {
|
|
1428
1457
|
metadata.sessionBoundaryOperationId = identityMetadata.sessionBoundaryOperationId;
|
|
1429
1458
|
}
|
|
@@ -1586,6 +1615,7 @@ export class SessionManager {
|
|
|
1586
1615
|
delete metadata.agentSessions;
|
|
1587
1616
|
delete metadata.resumeAt;
|
|
1588
1617
|
delete metadata.replyContext;
|
|
1618
|
+
metadata.__sessionRenewedAt = Date.now();
|
|
1589
1619
|
const session = {
|
|
1590
1620
|
id: generateSessionId(),
|
|
1591
1621
|
channel: sourceSession.channel,
|
|
@@ -13,6 +13,9 @@ const MAX_CONTEXT_CHARS = 16_000;
|
|
|
13
13
|
const MAX_MESSAGE_CHARS = 2_000;
|
|
14
14
|
const MODEL_TIMEOUT_MS = 10_000;
|
|
15
15
|
const RECENT_DECISION_TTL_MS = 30_000;
|
|
16
|
+
const FRESH_EMPTY_SESSION_TTL_MS = 30_000;
|
|
17
|
+
const RENEWED_AT_METADATA_KEY = '__sessionRenewedAt';
|
|
18
|
+
const CREATED_AT_METADATA_KEY = '__sessionCreatedAt';
|
|
16
19
|
const NEW_CONFIDENCE_THRESHOLD = 0.85;
|
|
17
20
|
const AUXILIARY_EFFORTS = new Set(['low', 'medium', 'high', 'xhigh', 'max']);
|
|
18
21
|
const EXPLICIT_NEW_RE = /^(?:新话题|换个话题|重新开始|新开会话|新会话)[::,,;;。!!\n]/;
|
|
@@ -91,7 +94,9 @@ export class SessionRenewService {
|
|
|
91
94
|
const routeKey = this.routeKey(request);
|
|
92
95
|
return await this.withLock(routeKey, async () => {
|
|
93
96
|
const cached = this.recentDecisions.get(routeKey);
|
|
94
|
-
if (cached
|
|
97
|
+
if (cached
|
|
98
|
+
&& cached.expiresAt > this.now()
|
|
99
|
+
&& (cached.candidateSessionId === request.session.id || cached.selectedSessionId === request.session.id)) {
|
|
95
100
|
const selected = cached.selectedSessionId === request.session.id
|
|
96
101
|
? request.session
|
|
97
102
|
: await this.sessionManager.getSessionById(cached.selectedSessionId);
|
|
@@ -121,6 +126,18 @@ export class SessionRenewService {
|
|
|
121
126
|
if (!last) {
|
|
122
127
|
if (request.isNewSession)
|
|
123
128
|
return { session: request.session, renewed: false };
|
|
129
|
+
// A renewed session is intentionally created before its first inbound
|
|
130
|
+
// message is appended. A concurrent message can therefore observe an
|
|
131
|
+
// empty, freshly-created session. Treat that state as already renewed;
|
|
132
|
+
// do not create a second logical session for the same route.
|
|
133
|
+
const metadata = request.session.metadata;
|
|
134
|
+
const renewedAt = Number(metadata?.[RENEWED_AT_METADATA_KEY] ?? metadata?.[CREATED_AT_METADATA_KEY]);
|
|
135
|
+
const sessionAgeMs = Number.isFinite(renewedAt)
|
|
136
|
+
? Math.max(0, this.now() - renewedAt)
|
|
137
|
+
: Number.POSITIVE_INFINITY;
|
|
138
|
+
if (Number.isFinite(renewedAt) && renewedAt <= this.now() && sessionAgeMs <= FRESH_EMPTY_SESSION_TTL_MS) {
|
|
139
|
+
return { session: request.session, renewed: false, decision: 'continue', source: 'missing_history' };
|
|
140
|
+
}
|
|
124
141
|
const idleMs = this.now() - request.session.updatedAt;
|
|
125
142
|
return await this.finishDecision(routeKey, request, 'new', 'missing_history', idleMs, evaluationStartedAt);
|
|
126
143
|
}
|
|
@@ -48,10 +48,14 @@ export class SessionTurnCoordinator {
|
|
|
48
48
|
&& state.lastTerminal.taskId === lease.taskId
|
|
49
49
|
&& state.lastTerminal.generation === lease.generation;
|
|
50
50
|
}
|
|
51
|
-
invalidate(sessionId, reason) {
|
|
51
|
+
invalidate(sessionId, reason, expected) {
|
|
52
52
|
const state = this.states.get(sessionId);
|
|
53
53
|
if (!state)
|
|
54
54
|
return undefined;
|
|
55
|
+
if (expected?.taskId && state.active?.taskId !== expected.taskId)
|
|
56
|
+
return undefined;
|
|
57
|
+
if (expected?.generation !== undefined && state.active?.generation !== expected.generation)
|
|
58
|
+
return undefined;
|
|
55
59
|
if (state.active?.status === 'interrupting' && state.active.cancelReason === reason) {
|
|
56
60
|
return cloneTurnMetadata(state);
|
|
57
61
|
}
|
package/dist/index.js
CHANGED
|
@@ -45,7 +45,7 @@ import { ResponseEngine } from './core/message/response-engine.js';
|
|
|
45
45
|
import { MessageQueue } from './core/message/message-queue.js';
|
|
46
46
|
import { MessageBridge } from './core/message/message-bridge.js';
|
|
47
47
|
import { evaluateOutboundContactAdmission } from './core/message/message-admission.js';
|
|
48
|
-
import {
|
|
48
|
+
import { enforceMenuResponseSize, evaluateMenuClientCompatibility, evolMenuResponseTransportMetadata, MenuRequestDeduper, hasValidMenuId, menuFailure, menuPayloadFingerprint, normalizeMenuResponseTiming, parseMenuControl, validateMenuRequest, withMenuProcessingTime, logMenuRequestCompleted, logMenuRequestReceived } from './core/command/menu-protocol.js';
|
|
49
49
|
import { readInstalledEvolcoreVersion } from './utils/evolcore-version.js';
|
|
50
50
|
import { recoverAllRoleMutationsSync } from './core/command/role-menu.js';
|
|
51
51
|
import { HandoffRuntime } from './core/handoff/runtime.js';
|
|
@@ -74,7 +74,7 @@ import { AgentReloadBusyError, AgentReloadCoordinator } from './core/agent-reloa
|
|
|
74
74
|
import { buildReloadHooks } from './core/channel-loader.js';
|
|
75
75
|
import { IpcServer } from './ipc.js';
|
|
76
76
|
import { logger, setLogLevel } from './utils/logger.js';
|
|
77
|
-
import {
|
|
77
|
+
import { remoteAccessService } from './core/auth/remote-access-store.js';
|
|
78
78
|
import { createEcwebSupervisor } from './utils/ecweb-supervisor.js';
|
|
79
79
|
import { writeMain, removeAll, isMainWinner, scanInstances } from './utils/instance-registry.js';
|
|
80
80
|
import { detectDuplicates } from './core/evolagent-registry.js';
|
|
@@ -88,12 +88,12 @@ import { TriggerFeedbackDispatcher } from './trigger/feedback.js';
|
|
|
88
88
|
import { TriggerRuntimeScheduler } from './trigger/scheduler.js';
|
|
89
89
|
import { TargetSessionLockRegistry } from './trigger/session-lock.js';
|
|
90
90
|
import { DaemonChannel } from './channels/daemon.js';
|
|
91
|
-
import { definitionRevision, normalizeTriggerDefinition } from './trigger/validation.js';
|
|
91
|
+
import { definitionRevision, normalizeTriggerDefinition, splitScriptCommand } from './trigger/validation.js';
|
|
92
92
|
import { applyTriggerPatch } from './trigger/patch.js';
|
|
93
93
|
import { validateModelSelectionForRole } from './core/model/model-permission.js';
|
|
94
94
|
import { constrainRuntimePermissionMode, validateRuntimeStringFieldOverride, } from './core/role/runtime-policy.js';
|
|
95
95
|
import { atomicWriteJson } from './core/session/session-fs-store.js';
|
|
96
|
-
import { ensureProcessManagedTempDir } from './cli/task-context.js';
|
|
96
|
+
import { DAEMON_RUNTIME_EPOCH_ENV, ensureProcessManagedTempDir } from './cli/task-context.js';
|
|
97
97
|
import { appendMessageLog, buildOutboundEntry, classifyAunPayloadForLog } from './core/message/message-log.js';
|
|
98
98
|
import { normalizeAunMentionEntries } from './aun/msg/mention-schema.js';
|
|
99
99
|
import { MAIN_PACKAGE_NAME } from './product.js';
|
|
@@ -551,6 +551,7 @@ async function runBindBootstrapDaemon(daemonCfg) {
|
|
|
551
551
|
})(),
|
|
552
552
|
}));
|
|
553
553
|
ipcServer.setControlToken(controlToken);
|
|
554
|
+
ipcServer.setRemoteAccessService(remoteAccessService);
|
|
554
555
|
ipcServer.setShutdownExecutor(async (reason) => {
|
|
555
556
|
if (!shutdown) {
|
|
556
557
|
pendingShutdownReason = reason;
|
|
@@ -1211,6 +1212,9 @@ async function main() {
|
|
|
1211
1212
|
: null;
|
|
1212
1213
|
bindService?.startCleanup();
|
|
1213
1214
|
const agentDelegationRegistry = new AgentDelegationRegistry();
|
|
1215
|
+
// Replace any inherited marker from a parent/previous daemon before child
|
|
1216
|
+
// runners or in-process IPC clients are created.
|
|
1217
|
+
process.env[DAEMON_RUNTIME_EPOCH_ENV] = agentDelegationRegistry.getRuntimeEpoch();
|
|
1214
1218
|
// 创建命令处理器
|
|
1215
1219
|
const cmdHandler = new CommandHandler(sessionManager, agentMap, messageCache, eventBus, primaryRunnerKey);
|
|
1216
1220
|
let agentApplicationService;
|
|
@@ -1351,6 +1355,7 @@ async function main() {
|
|
|
1351
1355
|
baseagent: primaryAgent?.baseagent || 'codex',
|
|
1352
1356
|
projectPath: primaryAgent?.projectPath || process.cwd(),
|
|
1353
1357
|
};
|
|
1358
|
+
cmdHandler.setDaemonTriggerOwner(daemonTriggerOwner.aid, daemonChannel.channelName);
|
|
1354
1359
|
const triggerBaseagentsForAgent = (agentAid) => {
|
|
1355
1360
|
const ownerName = agentRegistry.get(agentAid)?.name
|
|
1356
1361
|
?? (agentAid === daemonTriggerOwner.aid ? primaryAgent?.name : undefined);
|
|
@@ -2305,6 +2310,8 @@ async function main() {
|
|
|
2305
2310
|
return { ok: false, code: delegation.code, error: delegation.reason };
|
|
2306
2311
|
return cmdHandler.handleCtl(cmd, sessionId, delegation.grant);
|
|
2307
2312
|
});
|
|
2313
|
+
ipcServer.setDaemonRuntimeEpoch(agentDelegationRegistry.getRuntimeEpoch());
|
|
2314
|
+
cmdHandler.setDaemonMonitorProvider(() => ipcServer.getMonitorSnapshot().snapshot);
|
|
2308
2315
|
// Register every IPC executor/provider before exposing the endpoint. The
|
|
2309
2316
|
// function declaration is hoisted, while its invocation remains here so a
|
|
2310
2317
|
// failed bind still aborts before any AUN connection is attempted.
|
|
@@ -2374,10 +2381,10 @@ async function main() {
|
|
|
2374
2381
|
catch (e) {
|
|
2375
2382
|
logger.warn(`控制 AID 首连失败(后台自动重连,不影响 daemon 主流程): ${e?.message || e}`);
|
|
2376
2383
|
}
|
|
2377
|
-
// 控制 AID 接收 owner
|
|
2378
|
-
// 1. /pair —
|
|
2384
|
+
// 控制 AID 接收 daemon-owner 或有效远程访问 token 授权的指令:
|
|
2385
|
+
// 1. /pair — daemon 远程控制配对码(文本快路径)
|
|
2379
2386
|
// 2. menu.* JSON — 路由到 cmdHandler.execMenuForControl(进程级 + 全量权限)
|
|
2380
|
-
// 发送方身份由 AUN X.509
|
|
2387
|
+
// 发送方身份由 AUN X.509 证书链验证;非 Menu 文本仍只接受 daemon-owner。
|
|
2381
2388
|
controlChannel.onMessage(async (opts) => {
|
|
2382
2389
|
let activeMenuRequest;
|
|
2383
2390
|
let menuCompletionLogged = false;
|
|
@@ -2385,6 +2392,11 @@ async function main() {
|
|
|
2385
2392
|
source: 'control-aun',
|
|
2386
2393
|
selfAid: daemonCfg.aid,
|
|
2387
2394
|
messageId: opts.messageId,
|
|
2395
|
+
requesterAid: opts.peerId,
|
|
2396
|
+
authMethod: opts.protectedHeaders?.auth_method === 'remote-access-token' || opts.protectedHeaders?.auth_method === 'ecweb-token'
|
|
2397
|
+
? opts.protectedHeaders.auth_method
|
|
2398
|
+
: 'daemon-owner',
|
|
2399
|
+
clientName: opts.protectedHeaders?.client_name ?? 'evol',
|
|
2388
2400
|
};
|
|
2389
2401
|
try {
|
|
2390
2402
|
const menuTiming = normalizeMenuResponseTiming(opts);
|
|
@@ -2398,7 +2410,7 @@ async function main() {
|
|
|
2398
2410
|
}
|
|
2399
2411
|
const menuResponseContext = () => controlReplyContext(opts, evolMenuResponseTransportMetadata());
|
|
2400
2412
|
const sendControlMenuResponse = async (response) => {
|
|
2401
|
-
const timedResponse = withMenuProcessingTime(response, menuTiming);
|
|
2413
|
+
const timedResponse = withMenuProcessingTime(enforceMenuResponseSize(response, 128 * 1024 - 1024), menuTiming);
|
|
2402
2414
|
const request = activeMenuRequest ?? {
|
|
2403
2415
|
type: 'menu.unknown',
|
|
2404
2416
|
id: response.id,
|
|
@@ -2446,7 +2458,21 @@ async function main() {
|
|
|
2446
2458
|
menuCompletionLogged = true;
|
|
2447
2459
|
return;
|
|
2448
2460
|
}
|
|
2449
|
-
|
|
2461
|
+
const menuClientCompatibilityError = menuControl.isMenu
|
|
2462
|
+
? evaluateMenuClientCompatibility({ encrypted: opts.encrypted, protectedHeaders: opts.protectedHeaders })
|
|
2463
|
+
: null;
|
|
2464
|
+
if (menuControl.isMenu && menuClientCompatibilityError) {
|
|
2465
|
+
await sendControlMenuResponse(menuFailure({ id: menuControl.id, ...(menuControl.name?.trim() ? { name: menuControl.name } : {}) }, menuClientCompatibilityError));
|
|
2466
|
+
return;
|
|
2467
|
+
}
|
|
2468
|
+
const protectedMenuHeaders = opts.protectedHeaders;
|
|
2469
|
+
const hasRemoteAccessToken = protectedMenuHeaders?.auth_method === 'remote-access-token'
|
|
2470
|
+
|| protectedMenuHeaders?.auth_method === 'ecweb-token';
|
|
2471
|
+
if (text.toLowerCase() === '/pair' && !isProcessLevelOwner(opts.peerId, currentProcessOwners)) {
|
|
2472
|
+
logger.debug(`控制 AID 收到非 owner 配对请求,忽略: from=${opts.peerId}`);
|
|
2473
|
+
return;
|
|
2474
|
+
}
|
|
2475
|
+
if (menuControl.isMenu && !isRoleMenu && !hasRemoteAccessToken && !isProcessLevelOwner(opts.peerId, currentProcessOwners)) {
|
|
2450
2476
|
const response = menuFailure({ id: menuControl.id, ...(menuControl.name?.trim() ? { name: menuControl.name } : {}) }, {
|
|
2451
2477
|
code: 'NOT_ALLOWED',
|
|
2452
2478
|
message: formatAuthorizationDenial('Control-channel process operations require DaemonOwner.', 'DAEMON_OWNER_REQUIRED'),
|
|
@@ -2455,40 +2481,28 @@ async function main() {
|
|
|
2455
2481
|
await sendControlMenuResponse(response);
|
|
2456
2482
|
return;
|
|
2457
2483
|
}
|
|
2458
|
-
if (!isRoleMenu && !isProcessLevelOwner(opts.peerId, currentProcessOwners)) {
|
|
2484
|
+
if (!isRoleMenu && !hasRemoteAccessToken && !isProcessLevelOwner(opts.peerId, currentProcessOwners)) {
|
|
2459
2485
|
logger.debug(`控制 AID 收到非 owner 消息,忽略: from=${opts.peerId}`);
|
|
2460
2486
|
return;
|
|
2461
2487
|
}
|
|
2462
2488
|
if (text.toLowerCase() === '/pair') {
|
|
2463
|
-
const
|
|
2464
|
-
const
|
|
2465
|
-
|
|
2466
|
-
if (pair) {
|
|
2467
|
-
const mins = Math.max(0, Math.round((pair.expiresAt - Date.now()) / 60000));
|
|
2468
|
-
reply = `ECWeb 配对码:${pair.code}(约 ${mins} 分钟内有效)\n在浏览器打开 ECWeb 后输入此码登录`;
|
|
2469
|
-
}
|
|
2470
|
-
else {
|
|
2471
|
-
reply = 'ECWeb 未运行或暂不可达。请在主机运行 ec watch web 启动后重试。';
|
|
2472
|
-
}
|
|
2489
|
+
const pair = remoteAccessService.getPairingCode(opts.peerId);
|
|
2490
|
+
const mins = Math.max(0, Math.round((pair.expiresAt - Date.now()) / 60000));
|
|
2491
|
+
const reply = `远程控制配对码:${pair.code}(约 ${mins} 分钟内有效)`;
|
|
2473
2492
|
await controlChannel.sendMessage(opts.channelId, reply, controlReplyContext(opts));
|
|
2474
2493
|
return;
|
|
2475
2494
|
}
|
|
2476
|
-
// menu.* JSON
|
|
2495
|
+
// menu.* JSON 路由:准入来源在上方分流,最终 token/owner 校验由 execMenuForControl 完成。
|
|
2477
2496
|
if (menuControl.isMenu) {
|
|
2478
|
-
const versionError = evaluateEvolMenuVersionGate({
|
|
2479
|
-
encrypted: opts.encrypted,
|
|
2480
|
-
protectedHeaders: opts.protectedHeaders,
|
|
2481
|
-
});
|
|
2482
|
-
if (versionError) {
|
|
2483
|
-
await sendControlMenuResponse(menuFailure({ id: menuControl.id, ...(menuControl.name?.trim() ? { name: menuControl.name } : {}) }, versionError));
|
|
2484
|
-
return;
|
|
2485
|
-
}
|
|
2486
2497
|
const validationError = validateMenuRequest(menuControl.request);
|
|
2487
2498
|
if (validationError) {
|
|
2488
2499
|
await sendControlMenuResponse(menuFailure({ id: menuControl.id, ...(menuControl.name?.trim() ? { name: menuControl.name } : {}) }, validationError));
|
|
2489
2500
|
return;
|
|
2490
2501
|
}
|
|
2491
|
-
const deduped = await controlMenuDeduper.execute([opts.channelId, opts.peerId, menuControl.id].join('\u001f'), menuPayloadFingerprint(menuControl.raw), () => cmdHandler.execMenuForControl(parsed, opts.peerId
|
|
2502
|
+
const deduped = await controlMenuDeduper.execute([opts.channelId, opts.peerId, menuControl.id].join('\u001f'), menuPayloadFingerprint(menuControl.raw), () => cmdHandler.execMenuForControl(parsed, opts.peerId, {
|
|
2503
|
+
encrypted: opts.encrypted,
|
|
2504
|
+
protectedHeaders: opts.protectedHeaders,
|
|
2505
|
+
}));
|
|
2492
2506
|
if ('retry' in deduped) {
|
|
2493
2507
|
if (deduped.replayed) {
|
|
2494
2508
|
logger.warn(`[MenuControl] category=request-replay request=${menuControl.id}`
|
|
@@ -2508,7 +2522,7 @@ async function main() {
|
|
|
2508
2522
|
return;
|
|
2509
2523
|
}
|
|
2510
2524
|
// owner 发的其他内容:提示可用指令
|
|
2511
|
-
await controlChannel.sendMessage(opts.channelId, '可用指令:/pair
|
|
2525
|
+
await controlChannel.sendMessage(opts.channelId, '可用指令:/pair(获取远程控制配对码)', controlReplyContext(opts));
|
|
2512
2526
|
}
|
|
2513
2527
|
catch (e) {
|
|
2514
2528
|
if (activeMenuRequest && !menuCompletionLogged) {
|
|
@@ -2684,6 +2698,7 @@ async function main() {
|
|
|
2684
2698
|
// M3: direct call (not cast) — wire EvolAgentRegistry into IPC for evolagent.* handlers
|
|
2685
2699
|
ipcServer.setAgentRegistry(agentRegistry);
|
|
2686
2700
|
ipcServer.setControlToken(triggerControlToken);
|
|
2701
|
+
ipcServer.setRemoteAccessService(remoteAccessService);
|
|
2687
2702
|
ipcServer.setBootstrapCompleteExecutor(async (aid, auth) => {
|
|
2688
2703
|
if (auth?.sessionId) {
|
|
2689
2704
|
const authorization = await authorizeManagedAgentOperation({
|
|
@@ -2836,7 +2851,7 @@ async function main() {
|
|
|
2836
2851
|
for (const runner of new Set(agentMap.values())) {
|
|
2837
2852
|
if (typeof runner?.evaluatePreToolUse !== 'function')
|
|
2838
2853
|
continue;
|
|
2839
|
-
const result = await runner.evaluatePreToolUse(params.threadId, params.toolName, params.toolInput, params.signal);
|
|
2854
|
+
const result = await runner.evaluatePreToolUse(params.threadId, params.toolName, params.toolInput, params.signal, params.callId);
|
|
2840
2855
|
if (result.applicable)
|
|
2841
2856
|
return result;
|
|
2842
2857
|
}
|
|
@@ -4003,17 +4018,59 @@ async function main() {
|
|
|
4003
4018
|
const existing = schedulerFor(agentAid).list({ all: true }).find(trigger => trigger.id === cmd.triggerId);
|
|
4004
4019
|
if (!existing)
|
|
4005
4020
|
throw new Error(`trigger not found: ${cmd.triggerId}`);
|
|
4021
|
+
const rawPatch = cmd.patch && typeof cmd.patch === 'object' && !Array.isArray(cmd.patch)
|
|
4022
|
+
? cmd.patch
|
|
4023
|
+
: {};
|
|
4024
|
+
const rawScriptFile = rawPatch.scriptFile;
|
|
4025
|
+
if (rawScriptFile !== undefined && rawScriptFile !== true) {
|
|
4026
|
+
throw new Error('scriptFile must be true');
|
|
4027
|
+
}
|
|
4028
|
+
const patchScriptFile = rawScriptFile === true;
|
|
4029
|
+
const { scriptFile: _scriptFile, ...definitionPatch } = rawPatch;
|
|
4030
|
+
const scriptFileBase64 = cmd.scriptFileBase64;
|
|
4031
|
+
if (patchScriptFile && scriptFileBase64 === undefined) {
|
|
4032
|
+
throw new Error('--script-file requires scriptFileBase64');
|
|
4033
|
+
}
|
|
4034
|
+
if (!patchScriptFile && scriptFileBase64 !== undefined) {
|
|
4035
|
+
throw new Error('scriptFileBase64 requires --script-file');
|
|
4036
|
+
}
|
|
4037
|
+
if (scriptFileBase64 !== undefined) {
|
|
4038
|
+
if (typeof scriptFileBase64 !== 'string') {
|
|
4039
|
+
throw new Error('scriptFileBase64 must be a base64 string');
|
|
4040
|
+
}
|
|
4041
|
+
if (!actor.daemonPrivileged) {
|
|
4042
|
+
throw new Error('updating a script Trigger requires DaemonOwner');
|
|
4043
|
+
}
|
|
4044
|
+
if (existing.execution.type !== 'script' || !existing.execution.script) {
|
|
4045
|
+
throw new Error('--script-file only applies to script Triggers');
|
|
4046
|
+
}
|
|
4047
|
+
if (scriptFileBase64.length > 6 * 1024 * 1024
|
|
4048
|
+
|| !/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(scriptFileBase64)) {
|
|
4049
|
+
throw new Error('scriptFileBase64 is invalid or exceeds the 4 MiB limit');
|
|
4050
|
+
}
|
|
4051
|
+
const scriptBytes = Buffer.from(scriptFileBase64, 'base64');
|
|
4052
|
+
if (scriptBytes.length > 4 * 1024 * 1024)
|
|
4053
|
+
throw new Error('script file exceeds the 4 MiB limit');
|
|
4054
|
+
}
|
|
4006
4055
|
const currentRevision = definitionRevision(existing);
|
|
4007
4056
|
if (cmd.expectedRevision !== undefined && cmd.expectedRevision !== currentRevision) {
|
|
4008
4057
|
throw new Error(`trigger revision conflict: expected ${cmd.expectedRevision}, current ${currentRevision}`);
|
|
4009
4058
|
}
|
|
4010
|
-
let definition =
|
|
4059
|
+
let definition = Object.keys(definitionPatch).length > 0
|
|
4060
|
+
? applyTriggerPatch(existing, definitionPatch, { fromPromptFile: cmd.promptFile === true })
|
|
4061
|
+
: existing;
|
|
4011
4062
|
rejectUnapprovedFullAccessConfiguration(definition, actor);
|
|
4012
4063
|
definition = withFullAccessConfigurationProvenance(definition, actor);
|
|
4013
4064
|
validateTriggerDefinitionForActor(definition, actor);
|
|
4014
4065
|
validateTriggerFeedbackChannels(definition);
|
|
4015
4066
|
const scheduler = schedulerFor(agentAid);
|
|
4016
|
-
const
|
|
4067
|
+
const files = scriptFileBase64 !== undefined && existing.execution.script
|
|
4068
|
+
? [{
|
|
4069
|
+
relativePath: splitScriptCommand(existing.execution.script.path).path,
|
|
4070
|
+
contentBase64: scriptFileBase64,
|
|
4071
|
+
}]
|
|
4072
|
+
: [];
|
|
4073
|
+
const trigger = scheduler.update(cmd.triggerId, definition, files);
|
|
4017
4074
|
if (trigger.execution.permissionMode === 'fullaccess') {
|
|
4018
4075
|
auditFullAccessTriggerConfiguration('fullaccess.trigger.configured', trigger, actor, existing.execution.permissionMode === 'fullaccess'
|
|
4019
4076
|
? 'fullaccess Trigger updated through IPC'
|
|
@@ -4152,6 +4209,19 @@ async function main() {
|
|
|
4152
4209
|
await Promise.all([...triggerSchedulers.values()].map(scheduler => scheduler.suspendForDaemonExit()));
|
|
4153
4210
|
});
|
|
4154
4211
|
await shutdownStep('interrupt active message queue tasks', async () => { await queueShutdown; });
|
|
4212
|
+
await shutdownStep('dispose agent runners', async () => {
|
|
4213
|
+
const currentInstances = [...agentMap.entries()].map(([key, agent]) => {
|
|
4214
|
+
const split = key.lastIndexOf('::');
|
|
4215
|
+
return {
|
|
4216
|
+
evolagentName: split > 0 ? key.slice(0, split) : '<unknown>',
|
|
4217
|
+
baseagent: split > 0 ? key.slice(split + 2) : 'unknown',
|
|
4218
|
+
agent,
|
|
4219
|
+
};
|
|
4220
|
+
});
|
|
4221
|
+
await disposeAgentInstances(currentInstances, (instance, error) => {
|
|
4222
|
+
logger.warn(`[Shutdown] Failed to dispose runner ${instance.evolagentName}::${instance.baseagent}: ${error instanceof Error ? error.message : String(error)}`);
|
|
4223
|
+
});
|
|
4224
|
+
});
|
|
4155
4225
|
eventBus.publish({
|
|
4156
4226
|
type: 'system:shutdown',
|
|
4157
4227
|
timestamp: Date.now()
|