evolcore 0.0.22 → 0.0.24
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 +74 -14
- package/README.md +4 -0
- package/bin/codex-managed-acl.ps1 +160 -0
- package/bin/codex-managed-hook.mjs +127 -10
- package/bin/ec-safe-output.js +85 -3
- package/bin/install-codex-managed-hooks.mjs +384 -95
- package/dist/agents/baseagent-availability.js +48 -0
- package/dist/agents/baseagent.js +30 -2
- package/dist/agents/claude-runner.js +179 -240
- package/dist/agents/{codex-app-server-client.js → codex/app-server-client.js} +90 -20
- 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 +369 -0
- package/dist/agents/codex-runner.js +839 -192
- package/dist/agents/ecagent-runner.js +27 -34
- package/dist/agents/gemini-runner.js +62 -8
- package/dist/agents/runner-types.js +1 -1
- package/dist/aun/aid/managed-operation.js +28 -6
- package/dist/aun/aid/store.js +43 -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 +57 -5
- package/dist/aun/outbox.js +56 -18
- package/dist/aun/rpc/client.js +7 -1
- package/dist/channels/aun.js +178 -77
- package/dist/channels/daemon.js +7 -1
- package/dist/channels/dingtalk.js +1 -0
- package/dist/channels/feishu.js +10 -4
- package/dist/channels/wechat.js +81 -10
- package/dist/channels/wecom.js +2 -1
- package/dist/cli/agent-command.js +29 -3
- package/dist/cli/agent-create.js +538 -0
- package/dist/cli/agent.js +26 -675
- package/dist/cli/aun-commands.js +108 -24
- package/dist/cli/command-log.js +21 -2
- package/dist/cli/ctl-command.js +2 -0
- package/dist/cli/daemon-commands.js +289 -61
- package/dist/cli/index.js +22 -2
- package/dist/cli/init.js +2 -10
- package/dist/cli/managed-command-guard.js +3 -0
- package/dist/cli/menu-client.js +95 -0
- package/dist/cli/model.js +23 -8
- package/dist/cli/restart-monitor.js +432 -8
- package/dist/cli/stats.js +110 -0
- package/dist/cli/task-context.js +12 -2
- package/dist/cli/trigger-command.js +9 -1
- package/dist/config/builtin-roles.js +2 -0
- package/dist/config/schema-migration.js +14 -2
- package/dist/core/auth/agent-delegation.js +10 -7
- package/dist/core/auth/auth-gateway.js +3 -2
- package/dist/core/auth/authorization-audit.js +75 -18
- package/dist/core/auth/operation-authorizer.js +63 -3
- package/dist/core/auth/operation-catalog.js +14 -2
- package/dist/core/auth/remote-access-store.js +136 -0
- package/dist/core/baseagent-loader.js +4 -4
- package/dist/core/bootstrap-messages.js +39 -18
- package/dist/core/bootstrap-service.js +32 -26
- package/dist/core/capability/capability-manager.js +8 -4
- package/dist/core/capability/providers/codex-capability-provider.js +1 -1
- package/dist/core/command/agent-control.js +7 -2
- package/dist/core/command/command-handler.js +128 -30
- package/dist/core/command/daemon-diagnostics.js +448 -0
- package/dist/core/command/menu-catalog.js +6 -1
- package/dist/core/command/menu-handler.js +410 -98
- package/dist/core/command/menu-protocol.js +123 -0
- package/dist/core/command/slash-gate.js +4 -6
- package/dist/core/command/slash-handler.js +199 -70
- package/dist/core/data-migration.js +5 -15
- package/dist/core/event-catalog.js +3 -0
- package/dist/core/handoff/runtime.js +3 -1
- package/dist/core/handoff/store.js +7 -1
- package/dist/core/message/im-renderer.js +13 -6
- package/dist/core/message/message-bridge.js +5 -36
- package/dist/core/message/message-queue.js +20 -15
- package/dist/core/message/message-utils.js +6 -3
- package/dist/core/message/response-engine.js +147 -94
- package/dist/core/model/catalog-service.js +310 -0
- package/dist/core/model/model-catalog.js +134 -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 +5 -2
- package/dist/core/permission/approval-gateway.js +183 -32
- package/dist/core/permission/protected-paths.js +63 -4
- package/dist/core/permission/sandbox-runtime.js +4 -2
- package/dist/core/permission/tool-policy.js +257 -31
- package/dist/core/runtime-lock.js +13 -8
- package/dist/core/session/adapters/codex-session-file-adapter.js +71 -3
- package/dist/core/session/session-manager.js +49 -13
- package/dist/core/session/session-renew.js +1 -0
- package/dist/index.js +214 -57
- package/dist/ipc.js +191 -102
- package/dist/paths.js +23 -1
- package/dist/stats/billing.js +18 -4
- package/dist/trigger/manager.js +3 -1
- package/dist/trigger/parser.js +77 -13
- package/dist/trigger/patch.js +104 -1
- package/dist/utils/atomic-write.js +26 -2
- package/dist/utils/cross-platform.js +436 -1
- package/dist/utils/ecweb-supervisor.js +11 -2
- package/dist/utils/ecweb-utils.js +8 -26
- package/dist/utils/error-utils.js +16 -2
- package/dist/utils/model-prices.jsonl +23 -20
- package/dist/utils/npm-ops.js +70 -15
- package/dist/utils/windows-autostart.js +1 -1
- package/kits/docs/evolcore/INDEX.md +1 -0
- package/kits/docs/evolcore/agent-create.md +13 -0
- package/kits/docs/evolcore/group-collaboration.md +7 -2
- package/kits/docs/evolcore/menu-client.md +34 -0
- 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/templates/roles/admin.json +2 -0
- package/kits/templates/roles/visitor.json +1 -0
- package/kits/templates/system-fragments/bootstrap.md +8 -1
- package/package.json +16 -4
- package/dist/utils/codex-cli.js +0 -43
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';
|
|
@@ -100,6 +100,7 @@ import { MAIN_PACKAGE_NAME } from './product.js';
|
|
|
100
100
|
import fs from 'fs';
|
|
101
101
|
import crypto from 'crypto';
|
|
102
102
|
import { fileURLToPath } from 'url';
|
|
103
|
+
import * as platform from './utils/cross-platform.js';
|
|
103
104
|
const controlMenuDeduper = new MenuRequestDeduper();
|
|
104
105
|
/** 出站 payload 摘要(用于 channel-out.log) */
|
|
105
106
|
function summarizeOutboundPayload(payload) {
|
|
@@ -134,6 +135,7 @@ function summarizeOutboundPayload(payload) {
|
|
|
134
135
|
break;
|
|
135
136
|
case 'status.started':
|
|
136
137
|
case 'status.progress':
|
|
138
|
+
case 'status.notice':
|
|
137
139
|
case 'status.requires_action':
|
|
138
140
|
case 'status.queued':
|
|
139
141
|
case 'status.completed':
|
|
@@ -326,11 +328,21 @@ function seedUpgradeCheckTrigger(manager, owner) {
|
|
|
326
328
|
const cliPath = path.join(packageRoot, 'dist', 'cli', 'index.js');
|
|
327
329
|
const packageJsonPath = path.join(packageRoot, 'package.json');
|
|
328
330
|
const devMode = fs.existsSync(path.join(packageRoot, 'src', 'index.ts'));
|
|
331
|
+
// Resolve the launch argv at generation time: in a checkout this points the
|
|
332
|
+
// monitor at src/ through tsx, so a `npm run build` that wipes dist/ cannot
|
|
333
|
+
// take the watchdog down with it.
|
|
334
|
+
const monitorEntry = platform.resolveCliEntry(packageRoot);
|
|
335
|
+
const stageParent = path.join(resolvePaths().root, 'data', 'monitor-stage');
|
|
329
336
|
fs.writeFileSync(scriptPath, `#!/usr/bin/env node
|
|
330
|
-
const
|
|
337
|
+
const fs = require('node:fs');
|
|
338
|
+
const path = require('node:path');
|
|
339
|
+
const { existsSync, readFileSync } = fs;
|
|
331
340
|
const { spawn } = require('node:child_process');
|
|
332
341
|
|
|
333
|
-
const CLI_PATH = ${JSON.stringify(
|
|
342
|
+
const CLI_PATH = ${JSON.stringify(monitorEntry.entry)};
|
|
343
|
+
const CLI_ARGS = ${JSON.stringify(monitorEntry.args)};
|
|
344
|
+
const PACKAGE_ROOT = ${JSON.stringify(packageRoot)};
|
|
345
|
+
const STAGE_PARENT = ${JSON.stringify(stageParent)};
|
|
334
346
|
const PACKAGE_JSON = ${JSON.stringify(packageJsonPath)};
|
|
335
347
|
const PACKAGE_NAME = ${JSON.stringify(MAIN_PACKAGE_NAME)};
|
|
336
348
|
const DEV_MODE = ${devMode ? 'true' : 'false'};
|
|
@@ -405,14 +417,60 @@ async function main() {
|
|
|
405
417
|
return;
|
|
406
418
|
}
|
|
407
419
|
|
|
420
|
+
// Installed mode: copy the monitor out of the package before it upgrades that
|
|
421
|
+
// same package. Mirrors prepareMonitorStage(); kept inline because this file
|
|
422
|
+
// is generated standalone CJS. Any failure degrades to launching in place —
|
|
423
|
+
// losing upgrade isolation beats losing the upgrade.
|
|
424
|
+
let launchArgs = CLI_ARGS;
|
|
425
|
+
const launchEnv = { ...process.env };
|
|
426
|
+
if (!DEV_MODE && STAGE_PARENT) {
|
|
427
|
+
try {
|
|
428
|
+
const stageDir = path.join(STAGE_PARENT, 'upgrade-' + process.pid + '-' + Date.now());
|
|
429
|
+
fs.mkdirSync(stageDir, { recursive: true });
|
|
430
|
+
fs.cpSync(path.join(PACKAGE_ROOT, 'dist'), path.join(stageDir, 'dist'), { recursive: true });
|
|
431
|
+
if (existsSync(PACKAGE_JSON)) fs.copyFileSync(PACKAGE_JSON, path.join(stageDir, 'package.json'));
|
|
432
|
+
const modulesSource = path.join(PACKAGE_ROOT, 'node_modules');
|
|
433
|
+
if (existsSync(modulesSource)) {
|
|
434
|
+
const staged = path.join(stageDir, 'node_modules');
|
|
435
|
+
try {
|
|
436
|
+
// junction works unelevated on Windows; dir symlink elsewhere.
|
|
437
|
+
fs.symlinkSync(modulesSource, staged, process.platform === 'win32' ? 'junction' : 'dir');
|
|
438
|
+
fs.readdirSync(staged);
|
|
439
|
+
} catch (linkError) {
|
|
440
|
+
fs.rmSync(staged, { recursive: true, force: true });
|
|
441
|
+
fs.cpSync(modulesSource, staged, { recursive: true });
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
const stagedEntry = path.join(stageDir, 'dist', 'cli', 'index.js');
|
|
445
|
+
if (existsSync(stagedEntry)) {
|
|
446
|
+
launchArgs = [stagedEntry];
|
|
447
|
+
launchEnv.EVOLCORE_TARGET_PACKAGE_ROOT = PACKAGE_ROOT;
|
|
448
|
+
launchEnv.EVOLCORE_MONITOR_STAGE = stageDir;
|
|
449
|
+
}
|
|
450
|
+
} catch (stageError) {
|
|
451
|
+
// Fall through with the in-place entry.
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
|
|
408
455
|
try {
|
|
409
|
-
const child = spawn(process.execPath, [
|
|
456
|
+
const child = spawn(process.execPath, [...launchArgs, 'restart-monitor'], {
|
|
410
457
|
detached: true,
|
|
411
458
|
stdio: 'ignore',
|
|
412
459
|
windowsHide: process.platform === 'win32',
|
|
413
|
-
env:
|
|
460
|
+
env: launchEnv,
|
|
414
461
|
});
|
|
415
462
|
child.unref();
|
|
463
|
+
const pid = child.pid;
|
|
464
|
+
// The daemon is still running here — the monitor is what stops it. So a
|
|
465
|
+
// monitor that dies on load costs the upgrade, not the service; still,
|
|
466
|
+
// report it accurately instead of claiming success.
|
|
467
|
+
await new Promise(resolve => setTimeout(resolve, 3000));
|
|
468
|
+
let alive = false;
|
|
469
|
+
try { process.kill(pid, 0); alive = true; } catch (e) { alive = false; }
|
|
470
|
+
if (!alive) {
|
|
471
|
+
emit('error', 'restart-monitor exited during startup; upgrade to ' + remoteVersion + ' not applied');
|
|
472
|
+
return;
|
|
473
|
+
}
|
|
416
474
|
emit('success', 'evolcore upgrade available: ' + localVersion + ' -> ' + remoteVersion + '; restart-monitor started');
|
|
417
475
|
} catch (error) {
|
|
418
476
|
emit('error', 'failed to start restart-monitor: ' + (error && error.message ? error.message : String(error)));
|
|
@@ -551,6 +609,7 @@ async function runBindBootstrapDaemon(daemonCfg) {
|
|
|
551
609
|
})(),
|
|
552
610
|
}));
|
|
553
611
|
ipcServer.setControlToken(controlToken);
|
|
612
|
+
ipcServer.setRemoteAccessService(remoteAccessService);
|
|
554
613
|
ipcServer.setShutdownExecutor(async (reason) => {
|
|
555
614
|
if (!shutdown) {
|
|
556
615
|
pendingShutdownReason = reason;
|
|
@@ -1065,7 +1124,7 @@ async function main() {
|
|
|
1065
1124
|
agentLoader.register(new GeminiAgentPlugin());
|
|
1066
1125
|
agentLoader.register(new EcagentAgentPlugin());
|
|
1067
1126
|
const creationErrors = [];
|
|
1068
|
-
const agentInstances = agentLoader.createAll(agentRegistry, {
|
|
1127
|
+
const agentInstances = await agentLoader.createAll(agentRegistry, {
|
|
1069
1128
|
onSessionIdUpdate: async (sessionId, agentSessionId, context) => {
|
|
1070
1129
|
return await sessionManager.updateAgentSessionIdBySessionId(sessionId, agentSessionId, context?.turn);
|
|
1071
1130
|
},
|
|
@@ -1354,6 +1413,7 @@ async function main() {
|
|
|
1354
1413
|
baseagent: primaryAgent?.baseagent || 'codex',
|
|
1355
1414
|
projectPath: primaryAgent?.projectPath || process.cwd(),
|
|
1356
1415
|
};
|
|
1416
|
+
cmdHandler.setDaemonTriggerOwner(daemonTriggerOwner.aid, daemonChannel.channelName);
|
|
1357
1417
|
const triggerBaseagentsForAgent = (agentAid) => {
|
|
1358
1418
|
const ownerName = agentRegistry.get(agentAid)?.name
|
|
1359
1419
|
?? (agentAid === daemonTriggerOwner.aid ? primaryAgent?.name : undefined);
|
|
@@ -1491,8 +1551,9 @@ async function main() {
|
|
|
1491
1551
|
}
|
|
1492
1552
|
if (definition.execution.effort && !actor.daemonPrivileged) {
|
|
1493
1553
|
const baseagent = definition.execution.baseagent
|
|
1494
|
-
?? agentRegistry.get(definition.agentAid)?.baseagent
|
|
1495
|
-
|
|
1554
|
+
?? agentRegistry.get(definition.agentAid)?.baseagent;
|
|
1555
|
+
if (!baseagent)
|
|
1556
|
+
throw new Error('cannot determine Trigger baseagent');
|
|
1496
1557
|
const effortDecision = validateRuntimeStringFieldOverride({
|
|
1497
1558
|
selfAid: definition.agentAid,
|
|
1498
1559
|
role: actor.role,
|
|
@@ -2152,6 +2213,7 @@ async function main() {
|
|
|
2152
2213
|
const pendingRouteContext = restartDelivery.delivery ? { delivery: restartDelivery.delivery } : {};
|
|
2153
2214
|
const replyContext = {
|
|
2154
2215
|
...(pendingRouteContext ?? {}),
|
|
2216
|
+
...(pending.threadId ? { threadId: pending.threadId } : {}),
|
|
2155
2217
|
...(pending.rootId ? { replyToMessageId: pending.rootId, replyInThread: !!pending.threadId } : {}),
|
|
2156
2218
|
};
|
|
2157
2219
|
const owningAgent = agentRegistry.resolveByChannel(adapter.channelKey);
|
|
@@ -2163,10 +2225,23 @@ async function main() {
|
|
|
2163
2225
|
...(restartDelivery.delivery ? { delivery: restartDelivery.delivery } : {}),
|
|
2164
2226
|
replyContext: Object.keys(replyContext).length > 0 ? replyContext : undefined,
|
|
2165
2227
|
});
|
|
2228
|
+
// restart-monitor 会在回滚/升级时写入 outcome:必须如实告诉发起方这次跑的是
|
|
2229
|
+
// 哪个版本,否则 agent 会以为自己的改动或升级已经生效。
|
|
2230
|
+
const restartNotice = pending.outcome === 'rolled-back'
|
|
2231
|
+
? {
|
|
2232
|
+
text: `⚠️ 服务已重启,但运行的是上一个版本。${pending.outcomeReason ? `原因:${pending.outcomeReason}。` : ''}本次改动未生效,请检查后重试。`,
|
|
2233
|
+
subtype: 'restarted-rolled-back',
|
|
2234
|
+
}
|
|
2235
|
+
: pending.outcome === 'upgraded'
|
|
2236
|
+
? {
|
|
2237
|
+
text: `✅ 服务重启成功!${pending.outcomeReason ? `${pending.outcomeReason}` : ''}`,
|
|
2238
|
+
subtype: 'restarted-upgraded',
|
|
2239
|
+
}
|
|
2240
|
+
: { text: '✅ 服务重启成功!', subtype: 'restarted' };
|
|
2166
2241
|
await sendSystemPayload(adapter, envelope, {
|
|
2167
2242
|
kind: 'system.notice',
|
|
2168
|
-
text:
|
|
2169
|
-
subtype:
|
|
2243
|
+
text: restartNotice.text,
|
|
2244
|
+
subtype: restartNotice.subtype,
|
|
2170
2245
|
});
|
|
2171
2246
|
pendingRestartNoticeSent = true;
|
|
2172
2247
|
fs.rmSync(pendingFile, { force: true });
|
|
@@ -2309,6 +2384,7 @@ async function main() {
|
|
|
2309
2384
|
return cmdHandler.handleCtl(cmd, sessionId, delegation.grant);
|
|
2310
2385
|
});
|
|
2311
2386
|
ipcServer.setDaemonRuntimeEpoch(agentDelegationRegistry.getRuntimeEpoch());
|
|
2387
|
+
cmdHandler.setDaemonMonitorProvider(() => ipcServer.getMonitorSnapshot().snapshot);
|
|
2312
2388
|
// Register every IPC executor/provider before exposing the endpoint. The
|
|
2313
2389
|
// function declaration is hoisted, while its invocation remains here so a
|
|
2314
2390
|
// failed bind still aborts before any AUN connection is attempted.
|
|
@@ -2378,10 +2454,10 @@ async function main() {
|
|
|
2378
2454
|
catch (e) {
|
|
2379
2455
|
logger.warn(`控制 AID 首连失败(后台自动重连,不影响 daemon 主流程): ${e?.message || e}`);
|
|
2380
2456
|
}
|
|
2381
|
-
// 控制 AID 接收 owner
|
|
2382
|
-
// 1. /pair —
|
|
2457
|
+
// 控制 AID 接收 daemon-owner 或有效远程访问 token 授权的指令:
|
|
2458
|
+
// 1. /pair — daemon 远程控制配对码(文本快路径)
|
|
2383
2459
|
// 2. menu.* JSON — 路由到 cmdHandler.execMenuForControl(进程级 + 全量权限)
|
|
2384
|
-
// 发送方身份由 AUN X.509
|
|
2460
|
+
// 发送方身份由 AUN X.509 证书链验证;非 Menu 文本仍只接受 daemon-owner。
|
|
2385
2461
|
controlChannel.onMessage(async (opts) => {
|
|
2386
2462
|
let activeMenuRequest;
|
|
2387
2463
|
let menuCompletionLogged = false;
|
|
@@ -2389,6 +2465,11 @@ async function main() {
|
|
|
2389
2465
|
source: 'control-aun',
|
|
2390
2466
|
selfAid: daemonCfg.aid,
|
|
2391
2467
|
messageId: opts.messageId,
|
|
2468
|
+
requesterAid: opts.peerId,
|
|
2469
|
+
authMethod: opts.protectedHeaders?.auth_method === 'remote-access-token' || opts.protectedHeaders?.auth_method === 'ecweb-token'
|
|
2470
|
+
? opts.protectedHeaders.auth_method
|
|
2471
|
+
: 'daemon-owner',
|
|
2472
|
+
clientName: opts.protectedHeaders?.client_name ?? 'evol',
|
|
2392
2473
|
};
|
|
2393
2474
|
try {
|
|
2394
2475
|
const menuTiming = normalizeMenuResponseTiming(opts);
|
|
@@ -2402,7 +2483,7 @@ async function main() {
|
|
|
2402
2483
|
}
|
|
2403
2484
|
const menuResponseContext = () => controlReplyContext(opts, evolMenuResponseTransportMetadata());
|
|
2404
2485
|
const sendControlMenuResponse = async (response) => {
|
|
2405
|
-
const timedResponse = withMenuProcessingTime(response, menuTiming);
|
|
2486
|
+
const timedResponse = withMenuProcessingTime(enforceMenuResponseSize(response, 128 * 1024 - 1024), menuTiming);
|
|
2406
2487
|
const request = activeMenuRequest ?? {
|
|
2407
2488
|
type: 'menu.unknown',
|
|
2408
2489
|
id: response.id,
|
|
@@ -2450,7 +2531,21 @@ async function main() {
|
|
|
2450
2531
|
menuCompletionLogged = true;
|
|
2451
2532
|
return;
|
|
2452
2533
|
}
|
|
2453
|
-
|
|
2534
|
+
const menuClientCompatibilityError = menuControl.isMenu
|
|
2535
|
+
? evaluateMenuClientCompatibility({ encrypted: opts.encrypted, protectedHeaders: opts.protectedHeaders })
|
|
2536
|
+
: null;
|
|
2537
|
+
if (menuControl.isMenu && menuClientCompatibilityError) {
|
|
2538
|
+
await sendControlMenuResponse(menuFailure({ id: menuControl.id, ...(menuControl.name?.trim() ? { name: menuControl.name } : {}) }, menuClientCompatibilityError));
|
|
2539
|
+
return;
|
|
2540
|
+
}
|
|
2541
|
+
const protectedMenuHeaders = opts.protectedHeaders;
|
|
2542
|
+
const hasRemoteAccessToken = protectedMenuHeaders?.auth_method === 'remote-access-token'
|
|
2543
|
+
|| protectedMenuHeaders?.auth_method === 'ecweb-token';
|
|
2544
|
+
if (text.toLowerCase() === '/pair' && !isProcessLevelOwner(opts.peerId, currentProcessOwners)) {
|
|
2545
|
+
logger.debug(`控制 AID 收到非 owner 配对请求,忽略: from=${opts.peerId}`);
|
|
2546
|
+
return;
|
|
2547
|
+
}
|
|
2548
|
+
if (menuControl.isMenu && !isRoleMenu && !hasRemoteAccessToken && !isProcessLevelOwner(opts.peerId, currentProcessOwners)) {
|
|
2454
2549
|
const response = menuFailure({ id: menuControl.id, ...(menuControl.name?.trim() ? { name: menuControl.name } : {}) }, {
|
|
2455
2550
|
code: 'NOT_ALLOWED',
|
|
2456
2551
|
message: formatAuthorizationDenial('Control-channel process operations require DaemonOwner.', 'DAEMON_OWNER_REQUIRED'),
|
|
@@ -2459,40 +2554,28 @@ async function main() {
|
|
|
2459
2554
|
await sendControlMenuResponse(response);
|
|
2460
2555
|
return;
|
|
2461
2556
|
}
|
|
2462
|
-
if (!isRoleMenu && !isProcessLevelOwner(opts.peerId, currentProcessOwners)) {
|
|
2557
|
+
if (!isRoleMenu && !hasRemoteAccessToken && !isProcessLevelOwner(opts.peerId, currentProcessOwners)) {
|
|
2463
2558
|
logger.debug(`控制 AID 收到非 owner 消息,忽略: from=${opts.peerId}`);
|
|
2464
2559
|
return;
|
|
2465
2560
|
}
|
|
2466
2561
|
if (text.toLowerCase() === '/pair') {
|
|
2467
|
-
const
|
|
2468
|
-
const
|
|
2469
|
-
|
|
2470
|
-
if (pair) {
|
|
2471
|
-
const mins = Math.max(0, Math.round((pair.expiresAt - Date.now()) / 60000));
|
|
2472
|
-
reply = `ECWeb 配对码:${pair.code}(约 ${mins} 分钟内有效)\n在浏览器打开 ECWeb 后输入此码登录`;
|
|
2473
|
-
}
|
|
2474
|
-
else {
|
|
2475
|
-
reply = 'ECWeb 未运行或暂不可达。请在主机运行 ec watch web 启动后重试。';
|
|
2476
|
-
}
|
|
2562
|
+
const pair = remoteAccessService.getPairingCode(opts.peerId);
|
|
2563
|
+
const mins = Math.max(0, Math.round((pair.expiresAt - Date.now()) / 60000));
|
|
2564
|
+
const reply = `远程控制配对码:${pair.code}(约 ${mins} 分钟内有效)`;
|
|
2477
2565
|
await controlChannel.sendMessage(opts.channelId, reply, controlReplyContext(opts));
|
|
2478
2566
|
return;
|
|
2479
2567
|
}
|
|
2480
|
-
// menu.* JSON
|
|
2568
|
+
// menu.* JSON 路由:准入来源在上方分流,最终 token/owner 校验由 execMenuForControl 完成。
|
|
2481
2569
|
if (menuControl.isMenu) {
|
|
2482
|
-
const versionError = evaluateEvolMenuVersionGate({
|
|
2483
|
-
encrypted: opts.encrypted,
|
|
2484
|
-
protectedHeaders: opts.protectedHeaders,
|
|
2485
|
-
});
|
|
2486
|
-
if (versionError) {
|
|
2487
|
-
await sendControlMenuResponse(menuFailure({ id: menuControl.id, ...(menuControl.name?.trim() ? { name: menuControl.name } : {}) }, versionError));
|
|
2488
|
-
return;
|
|
2489
|
-
}
|
|
2490
2570
|
const validationError = validateMenuRequest(menuControl.request);
|
|
2491
2571
|
if (validationError) {
|
|
2492
2572
|
await sendControlMenuResponse(menuFailure({ id: menuControl.id, ...(menuControl.name?.trim() ? { name: menuControl.name } : {}) }, validationError));
|
|
2493
2573
|
return;
|
|
2494
2574
|
}
|
|
2495
|
-
const deduped = await controlMenuDeduper.execute([opts.channelId, opts.peerId, menuControl.id].join('\u001f'), menuPayloadFingerprint(menuControl.raw), () => cmdHandler.execMenuForControl(parsed, opts.peerId
|
|
2575
|
+
const deduped = await controlMenuDeduper.execute([opts.channelId, opts.peerId, menuControl.id].join('\u001f'), menuPayloadFingerprint(menuControl.raw), () => cmdHandler.execMenuForControl(parsed, opts.peerId, {
|
|
2576
|
+
encrypted: opts.encrypted,
|
|
2577
|
+
protectedHeaders: opts.protectedHeaders,
|
|
2578
|
+
}));
|
|
2496
2579
|
if ('retry' in deduped) {
|
|
2497
2580
|
if (deduped.replayed) {
|
|
2498
2581
|
logger.warn(`[MenuControl] category=request-replay request=${menuControl.id}`
|
|
@@ -2512,7 +2595,7 @@ async function main() {
|
|
|
2512
2595
|
return;
|
|
2513
2596
|
}
|
|
2514
2597
|
// owner 发的其他内容:提示可用指令
|
|
2515
|
-
await controlChannel.sendMessage(opts.channelId, '可用指令:/pair
|
|
2598
|
+
await controlChannel.sendMessage(opts.channelId, '可用指令:/pair(获取远程控制配对码)', controlReplyContext(opts));
|
|
2516
2599
|
}
|
|
2517
2600
|
catch (e) {
|
|
2518
2601
|
if (activeMenuRequest && !menuCompletionLogged) {
|
|
@@ -2648,9 +2731,18 @@ async function main() {
|
|
|
2648
2731
|
continue;
|
|
2649
2732
|
}
|
|
2650
2733
|
const evolName = owningAgent.aid;
|
|
2651
|
-
const baseagentName = session.baseagent
|
|
2652
|
-
|
|
2734
|
+
const baseagentName = typeof session.baseagent === 'string' ? session.baseagent.trim() : '';
|
|
2735
|
+
if (!baseagentName) {
|
|
2736
|
+
logger.error(`[Resume] session ${session.id}: missing baseagent binding, refusing implicit runner fallback`);
|
|
2737
|
+
sessionManager.clearProcessing(session.id);
|
|
2738
|
+
continue;
|
|
2739
|
+
}
|
|
2740
|
+
// Recovery must resume on the exact Agent × baseagent runner recorded by
|
|
2741
|
+
// the Session. Never fall back to the primary runner: doing so can bind
|
|
2742
|
+
// an in-flight session to a different backend.
|
|
2743
|
+
const agent = agentMap.get(`${evolName}::${baseagentName}`);
|
|
2653
2744
|
if (!agent) {
|
|
2745
|
+
logger.error(`[Resume] session ${session.id}: runner unavailable for ${evolName}::${baseagentName}; refusing fallback`);
|
|
2654
2746
|
sessionManager.clearProcessing(session.id);
|
|
2655
2747
|
continue;
|
|
2656
2748
|
}
|
|
@@ -2688,6 +2780,7 @@ async function main() {
|
|
|
2688
2780
|
// M3: direct call (not cast) — wire EvolAgentRegistry into IPC for evolagent.* handlers
|
|
2689
2781
|
ipcServer.setAgentRegistry(agentRegistry);
|
|
2690
2782
|
ipcServer.setControlToken(triggerControlToken);
|
|
2783
|
+
ipcServer.setRemoteAccessService(remoteAccessService);
|
|
2691
2784
|
ipcServer.setBootstrapCompleteExecutor(async (aid, auth) => {
|
|
2692
2785
|
if (auth?.sessionId) {
|
|
2693
2786
|
const authorization = await authorizeManagedAgentOperation({
|
|
@@ -2716,11 +2809,12 @@ async function main() {
|
|
|
2716
2809
|
return owner?.aid === aid;
|
|
2717
2810
|
});
|
|
2718
2811
|
const welcome = aunInstance?.channel;
|
|
2719
|
-
const ownerAid = getFirstStaticAgentOwner(aid);
|
|
2720
2812
|
const owningAgent = agentRegistry.get(aid);
|
|
2721
|
-
const
|
|
2813
|
+
const firstOwnerAid = getFirstStaticAgentOwner(aid);
|
|
2814
|
+
const sessions = aunInstance && firstOwnerAid && owningAgent
|
|
2722
2815
|
? {
|
|
2723
|
-
ensurePostBootstrapMainSession: async () => {
|
|
2816
|
+
ensurePostBootstrapMainSession: async (owner) => {
|
|
2817
|
+
const ownerAid = owner || firstOwnerAid;
|
|
2724
2818
|
const channel = aunInstance.adapter.channelName;
|
|
2725
2819
|
const active = await sessionManager.getActiveSession(channel, ownerAid);
|
|
2726
2820
|
const ensured = await sessionManager.ensureNewMainSessionForOperation({
|
|
@@ -2728,7 +2822,7 @@ async function main() {
|
|
|
2728
2822
|
channelId: ownerAid,
|
|
2729
2823
|
projectPath: active?.projectPath || owningAgent.projectPath || defaultProjectPath,
|
|
2730
2824
|
baseagent: owningAgent.baseagent || active?.baseagent || primaryBaseagent,
|
|
2731
|
-
operationId: postBootstrapWelcomeOperationId(aid),
|
|
2825
|
+
operationId: postBootstrapWelcomeOperationId(aid, ownerAid),
|
|
2732
2826
|
identityMetadata: {
|
|
2733
2827
|
...(active?.metadata ?? {}),
|
|
2734
2828
|
channelKey: aunInstance.adapter.channelKey,
|
|
@@ -2840,15 +2934,18 @@ async function main() {
|
|
|
2840
2934
|
for (const runner of new Set(agentMap.values())) {
|
|
2841
2935
|
if (typeof runner?.evaluatePreToolUse !== 'function')
|
|
2842
2936
|
continue;
|
|
2843
|
-
const result = await runner.evaluatePreToolUse(params.threadId, params.toolName, params.toolInput, params.signal, params.callId);
|
|
2937
|
+
const result = await runner.evaluatePreToolUse(params.threadId, params.toolName, params.toolInput, params.signal, params.callId, params.sessionToken);
|
|
2844
2938
|
if (result.applicable)
|
|
2845
2939
|
return result;
|
|
2846
2940
|
}
|
|
2941
|
+
// A credential that no runner issued is forged or belongs to a thread
|
|
2942
|
+
// whose rollout is gone. Never allow it; uncredentialed calls were
|
|
2943
|
+
// already allowed by the IPC layer before reaching this executor.
|
|
2847
2944
|
return {
|
|
2848
2945
|
ok: false,
|
|
2849
2946
|
applicable: true,
|
|
2850
2947
|
decision: 'deny',
|
|
2851
|
-
reason: 'No active EvolCore Codex runner accepted this managed hook
|
|
2948
|
+
reason: 'No active EvolCore Codex runner accepted this managed hook credential',
|
|
2852
2949
|
};
|
|
2853
2950
|
});
|
|
2854
2951
|
const resolveManagedHandoffScope = async (params, operation) => {
|
|
@@ -3181,7 +3278,7 @@ async function main() {
|
|
|
3181
3278
|
const creationErrors = [];
|
|
3182
3279
|
const staged = candidate.config.enabled === false
|
|
3183
3280
|
? []
|
|
3184
|
-
: agentLoader.createForAgent(candidate, {
|
|
3281
|
+
: await agentLoader.createForAgent(candidate, {
|
|
3185
3282
|
onSessionIdUpdate: async (sessionId, agentSessionId, context) => {
|
|
3186
3283
|
return await sessionManager.updateAgentSessionIdBySessionId(sessionId, agentSessionId, context?.turn);
|
|
3187
3284
|
},
|
|
@@ -3281,7 +3378,7 @@ async function main() {
|
|
|
3281
3378
|
throw new Error(agentRuntimeError);
|
|
3282
3379
|
}
|
|
3283
3380
|
const creationErrors = [];
|
|
3284
|
-
const newAgentInstances = agentLoader.createForAgent(agent, {
|
|
3381
|
+
const newAgentInstances = await agentLoader.createForAgent(agent, {
|
|
3285
3382
|
onSessionIdUpdate: async (sessionId, agentSessionId, context) => {
|
|
3286
3383
|
return await sessionManager.updateAgentSessionIdBySessionId(sessionId, agentSessionId, context?.turn);
|
|
3287
3384
|
},
|
|
@@ -3472,7 +3569,7 @@ async function main() {
|
|
|
3472
3569
|
hotLoad: coreHotLoadAgent,
|
|
3473
3570
|
resync: coreResyncAgents,
|
|
3474
3571
|
create: async (options) => {
|
|
3475
|
-
const { agentCreateNonInteractive } = await import('./cli/agent.js');
|
|
3572
|
+
const { agentCreateNonInteractive } = await import('./cli/agent-create.js');
|
|
3476
3573
|
return await agentCreateNonInteractive(options, { hotLoadAgent: coreHotLoadAgent });
|
|
3477
3574
|
},
|
|
3478
3575
|
aunAids: () => channelInstances.flatMap((inst) => {
|
|
@@ -3721,12 +3818,20 @@ async function main() {
|
|
|
3721
3818
|
const aid = argv[2] && !argv[2].startsWith('--') ? argv[2] : undefined;
|
|
3722
3819
|
if (!aid)
|
|
3723
3820
|
return fail('INVALID_ARGS', 'ec agent new requires an AID');
|
|
3724
|
-
const { agentCreateNonInteractive, resolveAgentCreatePlan, validateAgentCreatePlan } = await import('./cli/agent.js');
|
|
3821
|
+
const { agentCreateNonInteractive, resolveAgentCreatePlan, validateAgentCreatePlan, parseAgentOwnerArgs } = await import('./cli/agent-create.js');
|
|
3822
|
+
let ownerOptions;
|
|
3823
|
+
try {
|
|
3824
|
+
ownerOptions = parseAgentOwnerArgs(argv);
|
|
3825
|
+
}
|
|
3826
|
+
catch (error) {
|
|
3827
|
+
return fail('INVALID_ARGS', error instanceof Error ? error.message : String(error));
|
|
3828
|
+
}
|
|
3725
3829
|
const plan = resolveAgentCreatePlan({
|
|
3726
3830
|
aid,
|
|
3727
3831
|
project: value('--project'),
|
|
3728
3832
|
baseagent: value('--baseagent'),
|
|
3729
|
-
|
|
3833
|
+
...ownerOptions,
|
|
3834
|
+
handoff: value('--handoff'),
|
|
3730
3835
|
name: value('--name'),
|
|
3731
3836
|
description: value('--description'),
|
|
3732
3837
|
force: argv.includes('--force'),
|
|
@@ -3734,23 +3839,75 @@ async function main() {
|
|
|
3734
3839
|
const validationError = validateAgentCreatePlan(plan);
|
|
3735
3840
|
if (validationError)
|
|
3736
3841
|
return fail('INVALID_ARGS', validationError);
|
|
3842
|
+
if (argv.includes('--handoff')) {
|
|
3843
|
+
if (!delegation.grant.messageId)
|
|
3844
|
+
return fail('INVALID_ARGS', '--handoff requires an originating message');
|
|
3845
|
+
if (!plan.handoff?.trim())
|
|
3846
|
+
return fail('INVALID_ARGS', '--handoff requires non-empty context');
|
|
3847
|
+
if ((plan.owners?.[0] ?? plan.owner) !== taskAid) {
|
|
3848
|
+
return fail('INVALID_ARGS', '--handoff requires the creating Agent to be the first Owner');
|
|
3849
|
+
}
|
|
3850
|
+
if (plan.configExists)
|
|
3851
|
+
return fail('INVALID_ARGS', '--handoff requires a new Agent');
|
|
3852
|
+
}
|
|
3737
3853
|
if (argv.includes('--dry-run'))
|
|
3738
3854
|
return { ok: true, result: { ok: true, dryRun: true, plan } };
|
|
3855
|
+
let handoffId;
|
|
3856
|
+
if (plan.handoff) {
|
|
3857
|
+
try {
|
|
3858
|
+
const created = await handoffRuntime.createOutbound({
|
|
3859
|
+
selfAid: taskAid,
|
|
3860
|
+
to: aid,
|
|
3861
|
+
originSessionId: sessionId,
|
|
3862
|
+
originMessageId: delegation.grant.messageId,
|
|
3863
|
+
payload: { type: 'text', text: plan.handoff },
|
|
3864
|
+
encrypt: true,
|
|
3865
|
+
targetChatType: 'private',
|
|
3866
|
+
explicitReturnPolicy: 'required',
|
|
3867
|
+
contextOnly: true,
|
|
3868
|
+
originAuthorization: {
|
|
3869
|
+
actorId: delegation.grant.actorId,
|
|
3870
|
+
channelKey: delegation.grant.channel,
|
|
3871
|
+
channelType: delegation.grant.channelType,
|
|
3872
|
+
chatType: delegation.grant.chatType,
|
|
3873
|
+
peerKey: delegation.grant.peerKey,
|
|
3874
|
+
},
|
|
3875
|
+
causation: responseEngine.getTaskRuntimeContext(sessionId)?.causation,
|
|
3876
|
+
});
|
|
3877
|
+
handoffId = created.handoff?.handoff_id;
|
|
3878
|
+
if (!handoffId)
|
|
3879
|
+
return fail('INVALID_ARGS', '--handoff requires a different target session');
|
|
3880
|
+
}
|
|
3881
|
+
catch (error) {
|
|
3882
|
+
return fail('HANDOFF_PREPARE_FAILED', error instanceof Error ? error.message : String(error));
|
|
3883
|
+
}
|
|
3884
|
+
}
|
|
3739
3885
|
const createOptions = {
|
|
3740
3886
|
aid: plan.aid,
|
|
3741
3887
|
project: plan.project,
|
|
3742
3888
|
baseagent: plan.baseagent,
|
|
3743
3889
|
owner: plan.owner,
|
|
3890
|
+
owners: plan.owners,
|
|
3744
3891
|
name: plan.name,
|
|
3745
3892
|
description: plan.description,
|
|
3746
3893
|
force: argv.includes('--force'),
|
|
3747
3894
|
};
|
|
3748
|
-
|
|
3749
|
-
|
|
3750
|
-
|
|
3751
|
-
|
|
3752
|
-
|
|
3753
|
-
|
|
3895
|
+
try {
|
|
3896
|
+
const result = agentApplicationService
|
|
3897
|
+
? await agentApplicationService.create(createOptions)
|
|
3898
|
+
: await agentCreateNonInteractive(createOptions);
|
|
3899
|
+
if (!result.ok) {
|
|
3900
|
+
if (handoffId)
|
|
3901
|
+
handoffRuntime.store.discard(taskAid, handoffId, 'operation_failed');
|
|
3902
|
+
return fail(result.code ?? 'INTERNAL', result.error);
|
|
3903
|
+
}
|
|
3904
|
+
return { ok: true, result: { ...result, configPath: '[redacted]', ...(handoffId ? { handoff_id: handoffId } : {}) } };
|
|
3905
|
+
}
|
|
3906
|
+
catch (error) {
|
|
3907
|
+
if (handoffId)
|
|
3908
|
+
handoffRuntime.store.discard(taskAid, handoffId, 'operation_failed');
|
|
3909
|
+
return fail('INTERNAL', error instanceof Error ? error.message : String(error));
|
|
3910
|
+
}
|
|
3754
3911
|
}
|
|
3755
3912
|
if (operation === 'agent.delete') {
|
|
3756
3913
|
const result = agentApplicationService
|