evolcore 0.0.23 → 0.0.25
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 +583 -516
- package/LICENSE +21 -21
- package/README.md +455 -453
- package/bin/codex-managed-acl.ps1 +160 -155
- package/bin/codex-managed-hook.mjs +209 -92
- package/bin/ec-safe-output.js +327 -245
- package/bin/ec.js +29 -29
- package/bin/install-codex-managed-hooks.mjs +555 -544
- package/dist/agents/baseagent-availability.js +4 -4
- package/dist/agents/baseagent.js +29 -1
- package/dist/agents/claude-runner.js +152 -14
- package/dist/agents/codex/app-server-client.js +18 -8
- package/dist/agents/codex/catalog.js +1 -1
- package/dist/agents/codex/diagnostics.js +215 -0
- package/dist/agents/codex-runner.js +445 -63
- package/dist/agents/ecagent-runner.js +31 -16
- package/dist/agents/gemini-runner.js +82 -4
- package/dist/agents/runner-types.js +1 -1
- package/dist/aun/aid/agentmd.js +56 -4
- package/dist/aun/aid/index.js +1 -0
- package/dist/aun/aid/managed-operation.js +80 -6
- package/dist/aun/aid/store.js +41 -0
- package/dist/aun/msg/managed-operation.js +6 -1
- package/dist/aun/outbox.js +56 -18
- package/dist/aun/rpc/client.js +4 -5
- package/dist/aun/storage/client.js +4 -1
- package/dist/aun/storage/index.js +1 -1
- package/dist/channels/aun.js +147 -76
- package/dist/channels/daemon.js +7 -1
- package/dist/channels/dingtalk.js +1 -0
- package/dist/channels/feishu.js +10 -4
- package/dist/channels/wecom.js +2 -1
- package/dist/cli/agent-command.js +214 -89
- package/dist/cli/agent-create.js +576 -0
- package/dist/cli/agent.js +129 -676
- package/dist/cli/aun-commands.js +419 -313
- package/dist/cli/bench.js +20 -20
- package/dist/cli/config.js +20 -20
- package/dist/cli/contact.js +10 -10
- package/dist/cli/ctl-command.js +22 -20
- package/dist/cli/daemon-commands.js +163 -32
- package/dist/cli/fs-command.js +67 -67
- package/dist/cli/handoff-command.js +7 -7
- package/dist/cli/index.js +79 -79
- package/dist/cli/link-rules.js +21 -21
- package/dist/cli/managed-command-guard.js +3 -0
- package/dist/cli/menu-client.js +11 -11
- package/dist/cli/net-check.js +22 -22
- package/dist/cli/queue-command.js +29 -29
- package/dist/cli/response.js +30 -30
- package/dist/cli/restart-monitor.js +462 -38
- package/dist/cli/stats.js +161 -51
- package/dist/cli/trigger-command.js +56 -56
- package/dist/config/builtin-roles.js +3 -0
- package/dist/config/config-manager.js +15 -0
- package/dist/config/config-operation-service.js +20 -3
- package/dist/config/resolved-config-op.js +39 -1
- package/dist/core/auth/auth-gateway.js +3 -2
- package/dist/core/auth/authorization-audit.js +72 -19
- package/dist/core/auth/operation-authorizer.js +125 -7
- package/dist/core/auth/operation-catalog.js +26 -2
- package/dist/core/auth/remote-access-store.js +3 -2
- 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 +60 -26
- package/dist/core/command/cli-intent-parser.js +18 -0
- package/dist/core/command/command-handler.js +173 -36
- package/dist/core/command/menu-catalog.js +4 -1
- package/dist/core/command/menu-handler.js +315 -75
- package/dist/core/command/menu-protocol.js +7 -2
- package/dist/core/command/slash-gate.js +1 -3
- package/dist/core/command/slash-handler.js +166 -57
- package/dist/core/data-migration.js +5 -15
- package/dist/core/event-catalog.js +8 -0
- package/dist/core/evolagent.js +3 -3
- 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-utils.js +6 -3
- package/dist/core/message/response-engine.js +85 -27
- package/dist/core/model/catalog-service.js +3 -1
- package/dist/core/model/model-catalog.js +10 -0
- package/dist/core/model/model-permission.js +4 -1
- package/dist/core/permission/approval-gateway.js +169 -33
- package/dist/core/permission/tool-policy.js +56 -10
- 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 +327 -134
- package/dist/ipc.js +68 -21
- package/dist/paths.js +23 -1
- package/dist/stats/billing.js +18 -4
- package/dist/stats/db.js +159 -159
- package/dist/stats/eck-vars.js +9 -9
- package/dist/stats/query.js +139 -139
- package/dist/stats/role-budget.js +6 -6
- package/dist/stats/writer.js +51 -51
- package/dist/utils/atomic-write.js +26 -2
- package/dist/utils/avatar-upload.js +282 -23
- package/dist/utils/cross-platform.js +436 -1
- package/dist/utils/error-dict.json +160 -160
- package/dist/utils/error-utils.js +11 -4
- package/dist/utils/macos-autostart.js +22 -22
- package/dist/utils/model-prices.jsonl +23 -20
- package/dist/utils/npm-ops.js +24 -1
- package/dist/utils/welcome.js +22 -22
- package/dist/utils/windows-autostart.js +1 -1
- package/ecagent/LICENSE +21 -21
- package/ecagent/NOTICE +11 -11
- package/ecagent/README.md +61 -61
- package/ecagent/dist/harness/compaction/branch-summarization.js +30 -30
- package/ecagent/dist/harness/compaction/compaction.js +83 -83
- package/ecagent/dist/harness/messages.js +7 -7
- package/kits/docs/GUIDE.md +20 -20
- package/kits/docs/INDEX.md +66 -66
- package/kits/docs/aun/CHEATSHEET.md +19 -19
- package/kits/docs/aun/SYNC_PROTOCOL.md +15 -15
- package/kits/docs/channels/aun.md +56 -56
- package/kits/docs/channels/feishu.md +56 -56
- package/kits/docs/context-assembly.md +365 -364
- package/kits/docs/eck_templates/GUIDE.template.md +22 -22
- package/kits/docs/eck_templates/INDEX.template.md +28 -28
- package/kits/docs/eck_templates/path-registry.template.md +33 -33
- package/kits/docs/eck_templates/runtime.template.md +19 -19
- package/kits/docs/evolcore/INDEX.md +71 -69
- package/kits/docs/evolcore/agent-create.md +170 -156
- package/kits/docs/evolcore/agent.md +83 -73
- package/kits/docs/evolcore/aid.md +64 -62
- package/kits/docs/evolcore/codex-windows-sandbox.md +146 -0
- package/kits/docs/evolcore/config.md +195 -195
- package/kits/docs/evolcore/contact.md +68 -68
- package/kits/docs/evolcore/ctl.md +46 -46
- package/kits/docs/evolcore/event.md +216 -216
- package/kits/docs/evolcore/fs-architecture.md +1215 -1215
- package/kits/docs/evolcore/fs.md +110 -110
- package/kits/docs/evolcore/group-collaboration.md +243 -243
- package/kits/docs/evolcore/group-fs.md +17 -17
- package/kits/docs/evolcore/group-rules.md +251 -251
- package/kits/docs/evolcore/group.md +152 -152
- package/kits/docs/evolcore/menu-client.md +34 -34
- package/kits/docs/evolcore/model.md +50 -50
- package/kits/docs/evolcore/msg.md +165 -165
- package/kits/docs/evolcore/response.md +75 -75
- package/kits/docs/evolcore/rpc.md +37 -37
- package/kits/docs/evolcore/self-summary.md +29 -29
- package/kits/docs/evolcore/stats.md +83 -83
- package/kits/docs/evolcore/storage.md +50 -50
- package/kits/docs/evolcore/trigger.md +575 -575
- package/kits/docs/identity/AID_PROFILE_SPEC.md +26 -26
- package/kits/docs/identity/PATH_OPS.md +16 -16
- package/kits/docs/identity/ROLE_DETAIL.md +25 -25
- package/kits/docs/identity/identity-tools.md +26 -26
- package/kits/docs/path-registry.md +62 -62
- package/kits/docs/prompt-loading-architecture.md +270 -269
- package/kits/docs/venues/aun-group.md +45 -45
- package/kits/docs/venues/aun-private.md +10 -10
- package/kits/docs/venues/client-desktop.md +10 -10
- package/kits/docs/venues/client-mobile.md +10 -10
- package/kits/docs/venues/feishu-group.md +13 -13
- package/kits/docs/venues/feishu-private.md +9 -9
- package/kits/docs/venues/group.md +25 -25
- package/kits/docs/venues/private.md +10 -10
- package/kits/eck_manifest.auxiliary.json +43 -43
- package/kits/eck_manifest.json +195 -183
- package/kits/eck_message_manifest.json +74 -74
- package/kits/migrations/README-role-config-v4.md +32 -32
- package/kits/migrations/migrate-contact-book-v2.mjs +748 -748
- package/kits/migrations/migrate-role-config-v4.mjs +614 -614
- package/kits/migrations/migrate-role-config-v5.mjs +365 -365
- package/kits/migrations/rename-config-file.mjs +99 -99
- package/kits/rules/01-overview.md +162 -162
- package/kits/rules/02-navigation.md +96 -96
- package/kits/rules/03-identity.md +39 -37
- package/kits/rules/04-relation.md +75 -75
- package/kits/rules/05-venue.md +60 -60
- package/kits/rules/06-channel.md +63 -63
- package/kits/schemas/_meta.json +53 -53
- package/kits/schemas/agent-config.schema.1.json +177 -177
- package/kits/schemas/agent-config.schema.10.json +395 -395
- package/kits/schemas/agent-config.schema.11.json +421 -421
- package/kits/schemas/agent-config.schema.12.json +427 -427
- package/kits/schemas/agent-config.schema.2.json +239 -239
- package/kits/schemas/agent-config.schema.3.json +119 -119
- package/kits/schemas/agent-config.schema.4.json +208 -208
- package/kits/schemas/agent-config.schema.5.json +326 -326
- package/kits/schemas/agent-config.schema.6.json +322 -322
- package/kits/schemas/agent-config.schema.7.json +303 -303
- package/kits/schemas/agent-config.schema.8.json +304 -304
- package/kits/schemas/agent-config.schema.9.json +364 -364
- package/kits/schemas/contact-book.schema.1.json +36 -36
- package/kits/schemas/contact-book.schema.2.json +43 -43
- package/kits/schemas/contact-book.schema.3.json +67 -67
- package/kits/schemas/daemon.schema.1.json +91 -91
- package/kits/schemas/daemon.schema.2.json +101 -101
- package/kits/schemas/daemon.schema.3.json +123 -123
- package/kits/schemas/daemon.schema.4.json +136 -136
- package/kits/schemas/daemon.schema.5.json +135 -135
- package/kits/schemas/daemon.schema.6.json +131 -131
- package/kits/schemas/defaults.schema.1.json +81 -81
- package/kits/schemas/defaults.schema.2.json +85 -85
- package/kits/schemas/defaults.schema.3.json +73 -73
- package/kits/schemas/defaults.schema.4.json +107 -107
- package/kits/schemas/defaults.schema.5.json +119 -119
- package/kits/schemas/menu-exec-schema-commands.md +208 -208
- package/kits/schemas/migrations/README.md +30 -30
- package/kits/schemas/relation-config.schema.1.json +158 -158
- package/kits/schemas/relation-config.schema.2.json +73 -73
- package/kits/schemas/relation-config.schema.3.json +50 -50
- package/kits/schemas/relation-config.schema.4.json +47 -47
- package/kits/schemas/relation-config.schema.5.json +47 -47
- package/kits/schemas/relation-config.schema.6.json +46 -46
- package/kits/schemas/relation-config.schema.7.json +59 -59
- package/kits/schemas/relation-config.schema.8.json +103 -103
- package/kits/schemas/role-config.schema.1.json +201 -201
- package/kits/schemas/role-registry.schema.1.json +35 -35
- package/kits/schemas/single-session.schema.1.json +31 -31
- package/kits/schemas/single-session.schema.2.json +57 -57
- package/kits/schemas/single-session.schema.3.json +32 -32
- package/kits/templates/bootstrap-welcome.md +17 -17
- package/kits/templates/message-fragments/handoff-context-to-target.md +9 -9
- package/kits/templates/message-fragments/handoff-request-to-target.md +19 -19
- package/kits/templates/message-fragments/handoff-response-to-origin.md +9 -9
- package/kits/templates/message-fragments/inject-default.md +2 -2
- package/kits/templates/message-fragments/item.md +2 -2
- package/kits/templates/roles/admin.json +96 -82
- package/kits/templates/roles/member.json +97 -96
- package/kits/templates/roles/owner.json +9 -9
- package/kits/templates/roles/visitor.json +86 -85
- package/kits/templates/system-fragments/baseagent.md +14 -14
- package/kits/templates/system-fragments/bootstrap.md +38 -29
- package/kits/templates/system-fragments/channel.md +64 -64
- package/kits/templates/system-fragments/commands.md +28 -28
- package/kits/templates/system-fragments/identity.md +13 -13
- package/kits/templates/system-fragments/relation.md +19 -19
- package/kits/templates/system-fragments/session.md +59 -59
- package/kits/templates/system-fragments/venue.md +31 -31
- package/package.json +98 -94
- package/AGENTS.md +0 -62
- package/dist/utils/codex-cli.js +0 -43
- package/skills/eclink/SKILL.md +0 -84
- package/skills/eclink/agents/openai.yaml +0 -4
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import { normalizePeer, writeScope } from '../model/config-scope.js';
|
|
2
2
|
import { formatPeerKey } from '../relation/peer-identity.js';
|
|
3
|
-
import { modelMatches, getCatalog, resolveCatalogModel, assertCatalogSelectable } from '../model/model-catalog.js';
|
|
3
|
+
import { modelMatches, getCatalog, getBaseagentCatalog, resolveCatalogModel, assertCatalogSelectable, TRANSPORT_EFFORTS } from '../model/model-catalog.js';
|
|
4
4
|
import { constrainResolvedModelForRole, filterModelsForRole, validateModelSelectionForRole } from '../model/model-permission.js';
|
|
5
5
|
import { hasModelSwitcher } from '../../agents/runner-types.js';
|
|
6
|
-
import { execCodexCliSync, resolveCodexCliPath } from '../../utils/codex-cli.js';
|
|
7
6
|
import { resolvePaths, getPackageRoot, daemonControlDir } from '../../paths.js';
|
|
8
7
|
import { inspectDataMigrationRequirement } from '../data-migration.js';
|
|
9
8
|
import { buildEnvelope, isDeliveryTarget, isDeliveryTargetForChannel, replyContextFromSession } from '../message/message-utils.js';
|
|
@@ -11,15 +10,16 @@ import path from 'path';
|
|
|
11
10
|
import fs from 'fs';
|
|
12
11
|
import os from 'os';
|
|
13
12
|
import crypto from 'crypto';
|
|
14
|
-
import { execFileSync } from 'child_process';
|
|
15
13
|
import { CronExpressionParser } from 'cron-parser';
|
|
16
14
|
import { parseDuration } from '../../trigger/parser.js';
|
|
17
15
|
import { checkLatestVersion, getLocalVersion, isLinkedInstall, compareVersions, resolveGlobalPkg } from '../../utils/npm-ops.js';
|
|
18
|
-
import { commandExists,
|
|
16
|
+
import { commandExists, resolveCliEntry, launchMonitorAndConfirm } from '../../utils/cross-platform.js';
|
|
17
|
+
import { cachedCliVersion } from '../../utils/cross-platform.js';
|
|
18
|
+
import { scanInstances } from '../../utils/instance-registry.js';
|
|
19
19
|
import { isFullAccessEnabled, loadDefaults, loadDaemonConfig } from '../../config-store.js';
|
|
20
20
|
import { WEB_PACKAGE_NAME } from '../../product.js';
|
|
21
21
|
import { read as cfgRead, resolveEffective, resolveEffectiveFieldWithSource, routeFieldPath, write as cfgWrite, ConfigTarget, } from '../../config/config-manager.js';
|
|
22
|
-
import { execAgentAction, execAgentQuery, execAgentOptions, resolveProjectPath } from './agent-control.js';
|
|
22
|
+
import { execAgentAction, execAgentQuery, execAgentOptions, execAidAvatar, resolveProjectPath } from './agent-control.js';
|
|
23
23
|
import { gatewayList, gatewayUpdate, gatewayDelete, gatewayTest, gatewayModels, gatewaySetPrice, gatewaySyncEnv } from '../../config/gateway-config.js';
|
|
24
24
|
import { displaySessionTitle } from '../session/session-title.js';
|
|
25
25
|
import { buildSessionTurnList } from '../session/session-turns.js';
|
|
@@ -52,32 +52,7 @@ import { probeSessionActivity } from './slash-gate.js';
|
|
|
52
52
|
* 失败返回 null(命令不存在或执行失败)。
|
|
53
53
|
*/
|
|
54
54
|
function getBaseagentVersion(cmd) {
|
|
55
|
-
|
|
56
|
-
const executable = cmd === 'codex' ? resolveCodexCliPath() : cmd;
|
|
57
|
-
if (!executable)
|
|
58
|
-
return null;
|
|
59
|
-
const output = cmd === 'codex'
|
|
60
|
-
? execCodexCliSync(['--version'], {
|
|
61
|
-
encoding: 'utf-8',
|
|
62
|
-
timeout: 3000,
|
|
63
|
-
stdio: ['ignore', 'pipe', 'pipe'],
|
|
64
|
-
})
|
|
65
|
-
: execFileSync(executable, ['--version'], {
|
|
66
|
-
encoding: 'utf-8',
|
|
67
|
-
timeout: 3000,
|
|
68
|
-
stdio: ['ignore', 'pipe', 'pipe'],
|
|
69
|
-
windowsHide: process.platform === 'win32',
|
|
70
|
-
});
|
|
71
|
-
const versionOutput = String(output).trim();
|
|
72
|
-
// claude: "2.1.187 (Claude Code)" → 提取 "2.1.187"
|
|
73
|
-
// gemini: "0.38.0" → 直接返回
|
|
74
|
-
// codex: "codex-cli 0.142.0" → 提取 "0.142.0"
|
|
75
|
-
const match = versionOutput.match(/(\d+\.\d+\.\d+)/);
|
|
76
|
-
return match ? match[1] : null;
|
|
77
|
-
}
|
|
78
|
-
catch {
|
|
79
|
-
return null;
|
|
80
|
-
}
|
|
55
|
+
return cachedCliVersion(cmd);
|
|
81
56
|
}
|
|
82
57
|
const allEfforts = ['none', 'minimal', 'low', 'medium', 'high', 'xhigh', 'max'];
|
|
83
58
|
const PERMISSION_MODE_KEYS = PUBLIC_PERMISSION_MODES;
|
|
@@ -397,7 +372,14 @@ function tokenizeArgv(line) {
|
|
|
397
372
|
return out;
|
|
398
373
|
}
|
|
399
374
|
async function getAvailableEfforts(agent, model) {
|
|
400
|
-
|
|
375
|
+
const dynamic = await agent.getAvailableEfforts?.(model);
|
|
376
|
+
if (Array.isArray(dynamic))
|
|
377
|
+
return dynamic;
|
|
378
|
+
// Keep Menu usable with legacy/embedded runner implementations that do not
|
|
379
|
+
// expose the optional catalog method. Full runners still take the dynamic
|
|
380
|
+
// path above, so this fallback cannot broaden their advertised capabilities.
|
|
381
|
+
const baseagent = agent?.baseagent || agent?.name;
|
|
382
|
+
return (TRANSPORT_EFFORTS[baseagent] ?? []);
|
|
401
383
|
}
|
|
402
384
|
function modelDisplayLabel(agent, model) {
|
|
403
385
|
const full = agent.resolveModelId?.(model);
|
|
@@ -410,6 +392,29 @@ function menuStringArg(args, key) {
|
|
|
410
392
|
const trimmed = value.trim();
|
|
411
393
|
return trimmed || undefined;
|
|
412
394
|
}
|
|
395
|
+
function hostCatalogArgs(args = {}) {
|
|
396
|
+
const invalid = (message) => { throw { code: 'INVALID_ARGUMENT', message }; };
|
|
397
|
+
if (!args || typeof args !== 'object' || Array.isArray(args))
|
|
398
|
+
invalid('Baseagent catalog args must be an object');
|
|
399
|
+
if (Object.keys(args).some(key => !['baseagent', 'ba', 'refresh', 'scope', 'context'].includes(key))) {
|
|
400
|
+
invalid('Baseagent catalog only accepts baseagent, ba, refresh and process context');
|
|
401
|
+
}
|
|
402
|
+
if (args.scope !== undefined && args.scope !== 'process')
|
|
403
|
+
invalid('Baseagent catalog requires process scope');
|
|
404
|
+
if (args.context !== undefined && (!args.context || typeof args.context !== 'object'
|
|
405
|
+
|| Array.isArray(args.context) || args.context.kind !== 'process'
|
|
406
|
+
|| Object.keys(args.context).some(key => key !== 'kind'))) {
|
|
407
|
+
invalid('Baseagent catalog requires process context');
|
|
408
|
+
}
|
|
409
|
+
for (const key of ['baseagent', 'ba']) {
|
|
410
|
+
if (args[key] !== undefined && (typeof args[key] !== 'string' || !args[key].trim())) {
|
|
411
|
+
invalid(`${key} must be a non-empty string`);
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
if (args.refresh !== undefined && typeof args.refresh !== 'boolean')
|
|
415
|
+
invalid('refresh must be a boolean');
|
|
416
|
+
return { baseagent: menuStringArg(args, 'baseagent') ?? menuStringArg(args, 'ba') ?? 'claude', refresh: args.refresh === true };
|
|
417
|
+
}
|
|
413
418
|
const LEGACY_SESSION_CONFIG_ARG_KEYS = [
|
|
414
419
|
'session',
|
|
415
420
|
'sessionId',
|
|
@@ -852,6 +857,8 @@ export const SELF_MANAGE_AGENT_ACTIONS = new Set(['update', 'reload']);
|
|
|
852
857
|
/** 判断 (cmdBase, action) 是否为进程级操作(仅控制 channel 可执行)。
|
|
853
858
|
* /system 全部进程级;/agent 仅 create/delete/enable/disable 进程级;其余关系级。 */
|
|
854
859
|
export function isProcessLevelAction(cmdBase, action) {
|
|
860
|
+
if (cmdBase === '/baseagent-models')
|
|
861
|
+
return true;
|
|
855
862
|
if (cmdBase === '/system')
|
|
856
863
|
return true;
|
|
857
864
|
if (cmdBase === '/gateway')
|
|
@@ -900,6 +907,8 @@ function buildMenuIntent(verb, cmdBase, args, action, value, fromControlChannel
|
|
|
900
907
|
});
|
|
901
908
|
const modelScope = args?.scope === 'agent' ? 'agent' : 'relation';
|
|
902
909
|
if (verb === 'query') {
|
|
910
|
+
if (cmdBase === '/baseagent-models')
|
|
911
|
+
return intent('baseagent.models.list', 'process');
|
|
903
912
|
if (cmdBase === '/pwd')
|
|
904
913
|
return intent('project.current', 'relation');
|
|
905
914
|
if (cmdBase === '/session' || cmdBase === '/s')
|
|
@@ -1019,6 +1028,8 @@ function buildMenuIntent(verb, cmdBase, args, action, value, fromControlChannel
|
|
|
1019
1028
|
if (action === 'disable')
|
|
1020
1029
|
return intent('agent.disable', 'control', { action });
|
|
1021
1030
|
}
|
|
1031
|
+
if (cmdBase === '/aid' && action === 'avatar')
|
|
1032
|
+
return intent('ec.aid.avatar', 'agent', { action });
|
|
1022
1033
|
if (cmdBase === '/trigger') {
|
|
1023
1034
|
if (action === 'create' || action === 'set')
|
|
1024
1035
|
return intent('trigger.create', 'relation', { action });
|
|
@@ -1107,6 +1118,7 @@ async function authorizeMenuIntent(params) {
|
|
|
1107
1118
|
intent.source = source;
|
|
1108
1119
|
const decision = await authorizeOperation({ source, intent, subject });
|
|
1109
1120
|
if (!decision.allow) {
|
|
1121
|
+
logger.warn(`[MenuAuth] denied operation=${intent.operation} scope=${intent.scope} source=${source} selfAid=${subject.selfAid ?? '<none>'} actor=${subject.actorId ?? '<none>'} role=${subject.role} processRole=${subject.processRole} dataScope=${subject.dataScope} daemonOwner=${subject.isDaemonOwner} control=${subject.fromControlChannel} reason=${decision.reason}`);
|
|
1110
1122
|
if (intent.operation.startsWith('role.') || intent.operation.startsWith('connect.')) {
|
|
1111
1123
|
const namespace = intent.operation.split('.', 1)[0];
|
|
1112
1124
|
return {
|
|
@@ -1277,7 +1289,11 @@ function menuResultFailure(error) {
|
|
|
1277
1289
|
return { error: normalized.message, code: normalized.code, ...(normalized.data !== undefined ? { data: normalized.data } : {}) };
|
|
1278
1290
|
}
|
|
1279
1291
|
function resolveExternalMenuAgentChannel(owner, payload, fallbackChannel, required) {
|
|
1280
|
-
const requestedAid = typeof payload?.agent === 'string'
|
|
1292
|
+
const requestedAid = typeof payload?.agent === 'string' && payload.agent.trim()
|
|
1293
|
+
? payload.agent.trim()
|
|
1294
|
+
: typeof payload?.args?.aid === 'string'
|
|
1295
|
+
? payload.args.aid.trim()
|
|
1296
|
+
: '';
|
|
1281
1297
|
if (!requestedAid) {
|
|
1282
1298
|
return required
|
|
1283
1299
|
? { error: '缺少顶层 agent 参数', code: 'MISSING_AID' }
|
|
@@ -1510,6 +1526,8 @@ export async function getSubMenuItems(cmd, channel, channelId, userId, args, ove
|
|
|
1510
1526
|
const gated0 = gateControlScope.call(this, { cmdBase: cmdBase0, args, channel, fromControlChannel });
|
|
1511
1527
|
if (gated0)
|
|
1512
1528
|
throw { code: gated0.code, message: gated0.error };
|
|
1529
|
+
if (cmdBase0 === '/baseagent-models')
|
|
1530
|
+
throw { code: 'NOT_SUPPORTED', message: 'Baseagent catalog only supports menu.query' };
|
|
1513
1531
|
if (cmdBase0 === '/role') {
|
|
1514
1532
|
const identity = overrideIdentity ?? this.sessionManager.resolveIdentity(channel, userId, ...menuIdentityArgs(session));
|
|
1515
1533
|
const authorized = await authorizeRoleMenu(this, {
|
|
@@ -1628,7 +1646,7 @@ export async function getSubMenuItems(cmd, channel, channelId, userId, args, ove
|
|
|
1628
1646
|
const triggerScheduler = this.getTriggerSchedulerForChannel?.(channel);
|
|
1629
1647
|
const scope = args?.options === 'all' ? 'all' : 'enabled';
|
|
1630
1648
|
const role = optionsIdentity.role;
|
|
1631
|
-
const isAdmin = isManagementRole(role) || subject.isDaemonOwner;
|
|
1649
|
+
const isAdmin = isManagementRole(role) || subject.isDaemonOwner || subject.processRole === 'daemon-service';
|
|
1632
1650
|
if (!triggerScheduler)
|
|
1633
1651
|
return [];
|
|
1634
1652
|
const list = triggerScheduler.list({ all: scope === 'all' });
|
|
@@ -1731,7 +1749,16 @@ export async function getSubMenuItems(cmd, channel, channelId, userId, args, ove
|
|
|
1731
1749
|
});
|
|
1732
1750
|
if ('error' in target)
|
|
1733
1751
|
throw { code: target.code, message: target.error };
|
|
1734
|
-
|
|
1752
|
+
let agent;
|
|
1753
|
+
try {
|
|
1754
|
+
agent = this.getAgent(channel, target.baseagent);
|
|
1755
|
+
}
|
|
1756
|
+
catch (error) {
|
|
1757
|
+
if (subject.processRole === 'daemon-service') {
|
|
1758
|
+
return [{ value: target.baseagent, label: target.baseagent, selected: true }];
|
|
1759
|
+
}
|
|
1760
|
+
throw error;
|
|
1761
|
+
}
|
|
1735
1762
|
if (hasModelSwitcher(agent)) {
|
|
1736
1763
|
const catalog = await getCatalog(target.sel.self, target.baseagent);
|
|
1737
1764
|
assertCatalogSelectable(catalog);
|
|
@@ -1793,7 +1820,16 @@ export async function getSubMenuItems(cmd, channel, channelId, userId, args, ove
|
|
|
1793
1820
|
});
|
|
1794
1821
|
if ('error' in target)
|
|
1795
1822
|
throw { code: target.code, message: target.error };
|
|
1796
|
-
|
|
1823
|
+
let agent;
|
|
1824
|
+
try {
|
|
1825
|
+
agent = this.getAgent(channel, target.baseagent);
|
|
1826
|
+
}
|
|
1827
|
+
catch (error) {
|
|
1828
|
+
if (subject.processRole === 'daemon-service') {
|
|
1829
|
+
return ['low', 'medium', 'high', 'auto'].map(e => ({ value: e, label: e === 'auto' ? 'auto (SDK默认)' : e }));
|
|
1830
|
+
}
|
|
1831
|
+
throw error;
|
|
1832
|
+
}
|
|
1797
1833
|
const requestedModel = menuStringArg(args, 'model') ?? menuStringArg(args, 'currentModel');
|
|
1798
1834
|
const modelTarget = { ...target, field: 'model', fieldPath: `baseagents.${target.baseagent}.model` };
|
|
1799
1835
|
const currentModel = requestedModel || (hasModelSwitcher(agent) ? (readMenuModel(modelTarget, agent).value || agent.getModel()) : agent.name);
|
|
@@ -1947,6 +1983,18 @@ export async function execMenuQuery(cmd, channel, channelId, userId, args, expli
|
|
|
1947
1983
|
});
|
|
1948
1984
|
if (authDenied)
|
|
1949
1985
|
return authDenied;
|
|
1986
|
+
if (cmdBase === '/baseagent-models') {
|
|
1987
|
+
try {
|
|
1988
|
+
const query = hostCatalogArgs(args);
|
|
1989
|
+
return { data: await getBaseagentCatalog(query.baseagent, { refresh: query.refresh }) };
|
|
1990
|
+
}
|
|
1991
|
+
catch (error) {
|
|
1992
|
+
if (error?.code === 'INVALID_ARGUMENT')
|
|
1993
|
+
return menuResultFailure(error);
|
|
1994
|
+
logger.error('Failed to query host baseagent catalog');
|
|
1995
|
+
return { error: 'Unable to query host baseagent catalog', code: 'INTERNAL_ERROR' };
|
|
1996
|
+
}
|
|
1997
|
+
}
|
|
1950
1998
|
// ── /agent 查询(只读) ──
|
|
1951
1999
|
// 授权由 agent.list/agent.show operation 决定;channel 闸门只负责目标范围。
|
|
1952
2000
|
if (cmdBase === '/agent') {
|
|
@@ -2408,7 +2456,7 @@ export async function execMenuQuery(cmd, channel, channelId, userId, args, expli
|
|
|
2408
2456
|
return { error: `不支持 query: ${cmdBase}`, code: 'NOT_SUPPORTED' };
|
|
2409
2457
|
}
|
|
2410
2458
|
/** menu.update — 写入新值。 */
|
|
2411
|
-
export async function execMenuUpdate(cmd, value, channel, channelId, userId, overrideIdentity, fromControlChannel = false, args, authSubject, source = 'menu') {
|
|
2459
|
+
export async function execMenuUpdate(cmd, value, channel, channelId, userId, overrideIdentity, fromControlChannel = false, args, authSubject, source = 'menu', transportReplyContext) {
|
|
2412
2460
|
const cmdBase = cmd.trim().split(' ')[0];
|
|
2413
2461
|
if (!cmdBase)
|
|
2414
2462
|
return { error: '缺少命令', code: 'MISSING_CMD' };
|
|
@@ -2424,10 +2472,27 @@ export async function execMenuUpdate(cmd, value, channel, channelId, userId, ove
|
|
|
2424
2472
|
const gated = gateControlScope.call(this, { cmdBase, args, channel, fromControlChannel });
|
|
2425
2473
|
if (gated)
|
|
2426
2474
|
return gated;
|
|
2475
|
+
const loadedContext = await this.loadMenuContext(channel, channelId);
|
|
2476
|
+
let session = loadedContext.session;
|
|
2477
|
+
const evolagent = loadedContext.evolagent;
|
|
2478
|
+
if (transportReplyContext?.threadId && typeof this.sessionManager.getThreadSession === 'function') {
|
|
2479
|
+
try {
|
|
2480
|
+
const topicSession = await this.sessionManager.getThreadSession(channel, channelId, transportReplyContext.threadId);
|
|
2481
|
+
if (!topicSession) {
|
|
2482
|
+
return { error: '话题会话不存在,拒绝回退到主会话', code: 'TOPIC_SESSION_NOT_FOUND' };
|
|
2483
|
+
}
|
|
2484
|
+
session = topicSession;
|
|
2485
|
+
}
|
|
2486
|
+
catch (error) {
|
|
2487
|
+
logger.debug('[MenuHandler] topic session lookup failed for menu update: thread=' + transportReplyContext.threadId + ': ' + (error instanceof Error ? error.message : String(error)));
|
|
2488
|
+
return { error: '无法读取话题会话,拒绝回退到主会话', code: 'TOPIC_SESSION_LOOKUP_FAILED' };
|
|
2489
|
+
}
|
|
2490
|
+
}
|
|
2491
|
+
if (cmdBase === '/baseagent-models')
|
|
2492
|
+
return { error: 'Baseagent catalog only supports menu.query', code: 'NOT_SUPPORTED' };
|
|
2427
2493
|
const arg = value.trim();
|
|
2428
2494
|
if (!arg)
|
|
2429
2495
|
return { error: '缺少 value 参数', code: 'MISSING_VALUE' };
|
|
2430
|
-
const { session, evolagent } = await this.loadMenuContext(channel, channelId);
|
|
2431
2496
|
const identity = overrideIdentity ?? this.sessionManager.resolveIdentity(channel, userId, ...menuIdentityArgs(session));
|
|
2432
2497
|
const isAdmin = isManagementRole(identity.role);
|
|
2433
2498
|
const subject = buildMenuAuthSubject(this, {
|
|
@@ -2516,7 +2581,7 @@ export async function execMenuUpdate(cmd, value, channel, channelId, userId, ove
|
|
|
2516
2581
|
}
|
|
2517
2582
|
if (!patch?.nameOrId)
|
|
2518
2583
|
return { error: '缺少 nameOrId', code: 'INVALID_ARGS' };
|
|
2519
|
-
const isAdmin = isManagementRole(identity.role) || subject.isDaemonOwner;
|
|
2584
|
+
const isAdmin = isManagementRole(identity.role) || subject.isDaemonOwner || subject.processRole === 'daemon-service';
|
|
2520
2585
|
if (!isAdmin && !userId)
|
|
2521
2586
|
return { error: '无法确认身份,请确保渠道提供发送者 ID', code: 'FORBIDDEN' };
|
|
2522
2587
|
const triggerScheduler = this.getTriggerSchedulerForChannel?.(channel);
|
|
@@ -2578,21 +2643,112 @@ export async function execMenuUpdate(cmd, value, channel, channelId, userId, ove
|
|
|
2578
2643
|
return { error: `无效 baseagent: ${arg},可选: ${valid.join(' / ')}`, code: 'INVALID_VALUE' };
|
|
2579
2644
|
}
|
|
2580
2645
|
const previousBaseagent = evolagent.baseagent;
|
|
2646
|
+
if (session?.threadId) {
|
|
2647
|
+
if (transportReplyContext?.threadId !== session.threadId) {
|
|
2648
|
+
return { error: '话题路由上下文不匹配,拒绝切换', code: 'THREAD_CONTEXT_MISMATCH' };
|
|
2649
|
+
}
|
|
2650
|
+
if (typeof this.messageQueue.withSessionBarrier !== 'function') {
|
|
2651
|
+
return { error: '当前运行环境不支持安全话题切换', code: 'NOT_SUPPORTED' };
|
|
2652
|
+
}
|
|
2653
|
+
const sessionKey = this.getQueueKey(session, channel, channelId);
|
|
2654
|
+
const currentAgent = this.getAgent(channel, session.baseagent);
|
|
2655
|
+
const outcome = await this.messageQueue.withSessionBarrier(sessionKey, async (snapshot) => {
|
|
2656
|
+
const latest = await this.sessionManager.getSessionById?.(session.id);
|
|
2657
|
+
if (!latest || latest.threadId !== session.threadId) {
|
|
2658
|
+
return { error: '话题会话不存在或归属已变化', code: 'NOT_FOUND' };
|
|
2659
|
+
}
|
|
2660
|
+
const busy = !!latest.processingState
|
|
2661
|
+
|| !!latest.metadata?.turnState?.active
|
|
2662
|
+
|| snapshot.active
|
|
2663
|
+
|| snapshot.queued > 0
|
|
2664
|
+
|| snapshot.dispatching
|
|
2665
|
+
|| this.messageQueue.isProcessing?.(sessionKey)
|
|
2666
|
+
|| (this.messageQueue.getQueueLength?.(sessionKey) ?? 0) > 0
|
|
2667
|
+
|| currentAgent.hasActiveStream(sessionKey);
|
|
2668
|
+
if (busy) {
|
|
2669
|
+
return { error: '当前话题会话忙碌,无法切换 baseagent', code: 'BUSY' };
|
|
2670
|
+
}
|
|
2671
|
+
try {
|
|
2672
|
+
evolagent.setActiveBaseagent(arg);
|
|
2673
|
+
await this.sessionManager.rotateTopicBackend({
|
|
2674
|
+
sessionId: latest.id,
|
|
2675
|
+
expectedThreadId: latest.threadId,
|
|
2676
|
+
baseagent: arg,
|
|
2677
|
+
});
|
|
2678
|
+
return { ok: true };
|
|
2679
|
+
}
|
|
2680
|
+
catch (error) {
|
|
2681
|
+
try {
|
|
2682
|
+
evolagent.setActiveBaseagent(previousBaseagent);
|
|
2683
|
+
}
|
|
2684
|
+
catch { }
|
|
2685
|
+
return { error: error instanceof Error ? error.message : String(error), code: 'EXEC_FAILED' };
|
|
2686
|
+
}
|
|
2687
|
+
});
|
|
2688
|
+
if ('error' in outcome)
|
|
2689
|
+
return outcome;
|
|
2690
|
+
this.eventBus.publish({
|
|
2691
|
+
type: 'agent:baseagent-changed',
|
|
2692
|
+
aid: evolagent.aid,
|
|
2693
|
+
baseagent: arg,
|
|
2694
|
+
previousBaseagent,
|
|
2695
|
+
scope: 'agent',
|
|
2696
|
+
timestamp: Date.now(),
|
|
2697
|
+
});
|
|
2698
|
+
return { data: {
|
|
2699
|
+
baseagent: arg,
|
|
2700
|
+
previousBaseagent,
|
|
2701
|
+
baseagentChanged: previousBaseagent !== arg,
|
|
2702
|
+
scope: 'agent',
|
|
2703
|
+
sessionId: session.id,
|
|
2704
|
+
threadId: session.threadId,
|
|
2705
|
+
sessionIdChanged: false,
|
|
2706
|
+
notice: '已切换 Baseagent: ' + previousBaseagent + ' → ' + arg + ',话题会话 ID 保持不变',
|
|
2707
|
+
} };
|
|
2708
|
+
}
|
|
2581
2709
|
try {
|
|
2582
2710
|
evolagent.setActiveBaseagent(arg);
|
|
2583
2711
|
}
|
|
2584
2712
|
catch (error) {
|
|
2585
2713
|
return { error: error instanceof Error ? error.message : String(error), code: 'INVALID_VALUE' };
|
|
2586
2714
|
}
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
|
|
2590
|
-
|
|
2591
|
-
|
|
2592
|
-
|
|
2593
|
-
|
|
2594
|
-
|
|
2595
|
-
|
|
2715
|
+
try {
|
|
2716
|
+
const newSession = await this.sessionManager.createNewSession(channel, channelId, evolagent.projectPath, undefined, arg, session?.metadata, { previousBaseagent, baseagentChanged: previousBaseagent !== arg });
|
|
2717
|
+
if (session) {
|
|
2718
|
+
try {
|
|
2719
|
+
const previousAgent = this.getAgent(channel, session.baseagent);
|
|
2720
|
+
await previousAgent.clearSession(session.id, session.agentSessionId || '', session.projectPath);
|
|
2721
|
+
await previousAgent.closeSession(session.id);
|
|
2722
|
+
}
|
|
2723
|
+
catch (error) {
|
|
2724
|
+
// The new Evolcore session is already active; backend cleanup is best effort.
|
|
2725
|
+
logger.warn(`[Menu] baseagent switch cleanup failed: session=${session.id}: ${error instanceof Error ? error.message : String(error)}`);
|
|
2726
|
+
}
|
|
2727
|
+
}
|
|
2728
|
+
this.eventBus.publish({
|
|
2729
|
+
type: 'agent:baseagent-changed',
|
|
2730
|
+
aid: evolagent.aid,
|
|
2731
|
+
baseagent: arg,
|
|
2732
|
+
previousBaseagent,
|
|
2733
|
+
scope: 'agent',
|
|
2734
|
+
timestamp: Date.now(),
|
|
2735
|
+
});
|
|
2736
|
+
return { data: {
|
|
2737
|
+
baseagent: arg,
|
|
2738
|
+
previousBaseagent,
|
|
2739
|
+
baseagentChanged: previousBaseagent !== arg,
|
|
2740
|
+
scope: 'agent',
|
|
2741
|
+
sessionId: newSession.id,
|
|
2742
|
+
notice: `已切换 Baseagent: ${previousBaseagent} → ${arg},已创建新的主会话`,
|
|
2743
|
+
} };
|
|
2744
|
+
}
|
|
2745
|
+
catch (error) {
|
|
2746
|
+
try {
|
|
2747
|
+
evolagent.setActiveBaseagent(previousBaseagent);
|
|
2748
|
+
}
|
|
2749
|
+
catch { }
|
|
2750
|
+
return { error: `创建新会话失败:${error instanceof Error ? error.message : String(error)}`, code: 'EXEC_FAILED' };
|
|
2751
|
+
}
|
|
2596
2752
|
}
|
|
2597
2753
|
if (cmdBase === '/model') {
|
|
2598
2754
|
const target = resolveMenuModelTarget.call(this, {
|
|
@@ -2816,8 +2972,9 @@ export async function execMenuUpdate(cmd, value, channel, channelId, userId, ove
|
|
|
2816
2972
|
};
|
|
2817
2973
|
}
|
|
2818
2974
|
if (cmdBase === '/observable') {
|
|
2819
|
-
if (identity.role !== 'owner')
|
|
2975
|
+
if (identity.role !== 'owner' && subject.processRole !== 'daemon-service' && !subject.isDaemonOwner) {
|
|
2820
2976
|
return { error: '观察者模式仅限 owner 开关', code: 'NO_PERMISSION' };
|
|
2977
|
+
}
|
|
2821
2978
|
if (arg !== 'true' && arg !== 'false')
|
|
2822
2979
|
return { error: `无效值: ${arg},可选: true / false`, code: 'INVALID_VALUE' };
|
|
2823
2980
|
if (!evolagent)
|
|
@@ -2837,8 +2994,25 @@ export async function execMenuAction(cmd, action, args, channel, channelId, user
|
|
|
2837
2994
|
const gated = gateControlScope.call(this, { cmdBase, action, args, channel, fromControlChannel });
|
|
2838
2995
|
if (gated)
|
|
2839
2996
|
return gated;
|
|
2840
|
-
const
|
|
2997
|
+
const loadedContext = await this.loadMenuContext(channel, channelId);
|
|
2998
|
+
// Menu callbacks can arrive from a topic while the channel's active
|
|
2999
|
+
// session still points at the main conversation. Prefer the explicitly
|
|
3000
|
+
// transported topic route for session-scoped actions and authorization.
|
|
3001
|
+
let authSession = loadedContext.session;
|
|
3002
|
+
if (transportReplyContext?.threadId && typeof this.sessionManager.getThreadSession === 'function') {
|
|
3003
|
+
try {
|
|
3004
|
+
authSession = await this.sessionManager.getThreadSession(channel, channelId, transportReplyContext.threadId) ?? authSession;
|
|
3005
|
+
}
|
|
3006
|
+
catch (error) {
|
|
3007
|
+
logger.debug(`[MenuHandler] topic session lookup failed for menu action: thread=${transportReplyContext.threadId}: ${error instanceof Error ? error.message : String(error)}`);
|
|
3008
|
+
}
|
|
3009
|
+
}
|
|
3010
|
+
const session = authSession;
|
|
3011
|
+
const evolagent = loadedContext.evolagent;
|
|
3012
|
+
if (cmdBase === '/baseagent-models')
|
|
3013
|
+
return { error: 'Baseagent catalog only supports menu.query', code: 'NOT_SUPPORTED' };
|
|
2841
3014
|
const authIdentity = overrideIdentity ?? this.sessionManager.resolveIdentity(channel, userId, ...menuIdentityArgs(authSession));
|
|
3015
|
+
const identity = authIdentity;
|
|
2842
3016
|
const subject = buildMenuAuthSubject(this, {
|
|
2843
3017
|
identity: authIdentity,
|
|
2844
3018
|
session: authSession,
|
|
@@ -2850,7 +3024,7 @@ export async function execMenuAction(cmd, action, args, channel, channelId, user
|
|
|
2850
3024
|
fromControlChannel,
|
|
2851
3025
|
subject: authSubject,
|
|
2852
3026
|
});
|
|
2853
|
-
if (cmdBase === '/system' && fromControlChannel && !subject.isDaemonOwner) {
|
|
3027
|
+
if (cmdBase === '/system' && fromControlChannel && !subject.isDaemonOwner && subject.processRole !== 'daemon-service') {
|
|
2854
3028
|
return {
|
|
2855
3029
|
error: formatAuthorizationDenial('This daemon-level operation requires DaemonOwner.', 'DAEMON_OWNER_REQUIRED'),
|
|
2856
3030
|
code: 'NOT_ALLOWED',
|
|
@@ -2893,7 +3067,7 @@ export async function execMenuAction(cmd, action, args, channel, channelId, user
|
|
|
2893
3067
|
return menuResultFailure(error);
|
|
2894
3068
|
}
|
|
2895
3069
|
}
|
|
2896
|
-
if (cmdBase === '/agent'
|
|
3070
|
+
if ((cmdBase === '/agent' || cmdBase === '/aid') && !args?.aid) {
|
|
2897
3071
|
const selfAid = this.getOwningAgent?.(channel)?.aid;
|
|
2898
3072
|
if (selfAid)
|
|
2899
3073
|
args = { ...(args ?? {}), aid: selfAid };
|
|
@@ -2927,15 +3101,37 @@ export async function execMenuAction(cmd, action, args, channel, channelId, user
|
|
|
2927
3101
|
return await gatewaySyncEnv(args);
|
|
2928
3102
|
return { error: `不支持 gateway action: ${action}`, code: 'NOT_SUPPORTED' };
|
|
2929
3103
|
}
|
|
2930
|
-
const
|
|
2931
|
-
|
|
2932
|
-
|
|
3104
|
+
const isMenuAdmin = isManagementRole(authIdentity.role);
|
|
3105
|
+
// ── /aid avatar ──
|
|
3106
|
+
// The AID namespace is the only avatar mutation surface.
|
|
3107
|
+
if (cmdBase === '/aid') {
|
|
3108
|
+
if (action !== 'avatar')
|
|
3109
|
+
return { error: `不支持 aid action: ${action}`, code: 'NOT_SUPPORTED' };
|
|
3110
|
+
const result = await execAidAvatar(args);
|
|
3111
|
+
if (!('error' in result)) {
|
|
3112
|
+
const data = result.data;
|
|
3113
|
+
if (data?.avatar) {
|
|
3114
|
+
this.eventBus.publish({
|
|
3115
|
+
type: 'agent:avatar_updated',
|
|
3116
|
+
aid: data.aid,
|
|
3117
|
+
avatar: data.avatar,
|
|
3118
|
+
...(data.sha256 !== undefined ? { sha256: data.sha256 } : {}),
|
|
3119
|
+
...(data.mime !== undefined ? { mime: data.mime } : {}),
|
|
3120
|
+
...(data.width !== undefined ? { width: data.width } : {}),
|
|
3121
|
+
...(data.height !== undefined ? { height: data.height } : {}),
|
|
3122
|
+
...(data.aspectRatio !== undefined ? { aspectRatio: data.aspectRatio } : {}),
|
|
3123
|
+
timestamp: Date.now(),
|
|
3124
|
+
});
|
|
3125
|
+
}
|
|
3126
|
+
}
|
|
3127
|
+
return result;
|
|
3128
|
+
}
|
|
2933
3129
|
// ── /agent action ──
|
|
2934
3130
|
// 控制 channel:验 evolcore.owners,可执行进程级(create/delete/enable/disable)+ 自管理(update/reload)。
|
|
2935
3131
|
// agent channel:闸门已挡掉进程级 + 跨 agent,仅 update/reload 能到此;reload 允许 owner/admin,update 仅 owner。
|
|
2936
3132
|
if (cmdBase === '/agent') {
|
|
2937
3133
|
if (fromControlChannel) {
|
|
2938
|
-
if (!subject.isDaemonOwner) {
|
|
3134
|
+
if (!subject.isDaemonOwner && subject.processRole !== 'daemon-service') {
|
|
2939
3135
|
return {
|
|
2940
3136
|
error: formatAuthorizationDenial('This Agent lifecycle operation requires DaemonOwner.', 'DAEMON_OWNER_REQUIRED'),
|
|
2941
3137
|
code: 'NOT_ALLOWED',
|
|
@@ -3054,7 +3250,7 @@ export async function execMenuAction(cmd, action, args, channel, channelId, user
|
|
|
3054
3250
|
// ── 关系级 /trigger(不走 owners;复用 isAdmin + scoped 逻辑,D4 直调底层) ──
|
|
3055
3251
|
if (cmdBase === '/trigger') {
|
|
3056
3252
|
const role = identity.role;
|
|
3057
|
-
const isAdmin = isManagementRole(role) || subject.isDaemonOwner;
|
|
3253
|
+
const isAdmin = isManagementRole(role) || subject.isDaemonOwner || subject.processRole === 'daemon-service';
|
|
3058
3254
|
const triggerScheduler = this.getTriggerSchedulerForChannel?.(channel);
|
|
3059
3255
|
if (!triggerScheduler)
|
|
3060
3256
|
return { error: '触发器功能未启用', code: 'NOT_SUPPORTED' };
|
|
@@ -3135,7 +3331,7 @@ export async function execMenuAction(cmd, action, args, channel, channelId, user
|
|
|
3135
3331
|
if (action === 'enable' && trigger.execution.permissionMode === 'fullaccess') {
|
|
3136
3332
|
if (!isFullAccessEnabled())
|
|
3137
3333
|
return { error: 'fullaccess 功能未启用', code: 'NOT_SUPPORTED' };
|
|
3138
|
-
if (!subject.isDaemonOwner)
|
|
3334
|
+
if (!subject.isDaemonOwner && subject.processRole !== 'daemon-service')
|
|
3139
3335
|
return { error: '只有 daemon-owner 可以启用 fullaccess Trigger', code: 'FORBIDDEN' };
|
|
3140
3336
|
if (subject.canApprovePersistentFullAccess === false) {
|
|
3141
3337
|
return { error: '当前认证通道不能批准持久 fullaccess Trigger', code: 'FORBIDDEN' };
|
|
@@ -3513,7 +3709,7 @@ export async function execMenuAction(cmd, action, args, channel, channelId, user
|
|
|
3513
3709
|
}
|
|
3514
3710
|
if (action === 'new') {
|
|
3515
3711
|
const name = (args?.name ?? '').toString().trim();
|
|
3516
|
-
return await this.delegateAsAction(action, name ? `/new ${name}` : '/new', channel, channelId, userId, { enrichSession: true, authSubject: subject });
|
|
3712
|
+
return await this.delegateAsAction(action, name ? `/new ${name}` : '/new', channel, channelId, userId, { enrichSession: true, authSubject: subject, threadId: transportReplyContext?.threadId, chatType: transportReplyContext?.delivery?.chatType, replyContext: transportReplyContext });
|
|
3517
3713
|
}
|
|
3518
3714
|
if (action === 'rename') {
|
|
3519
3715
|
const newName = getRenameName(args);
|
|
@@ -3556,33 +3752,33 @@ export async function execMenuAction(cmd, action, args, channel, channelId, user
|
|
|
3556
3752
|
const target = (args?.target ?? '').toString().trim();
|
|
3557
3753
|
if (!target)
|
|
3558
3754
|
return { error: '缺少 args.target', code: 'MISSING_VALUE' };
|
|
3559
|
-
return await this.delegateAsAction(action, `/del ${target}`, channel, channelId, userId, { authSubject: subject });
|
|
3755
|
+
return await this.delegateAsAction(action, `/del ${target}`, channel, channelId, userId, { authSubject: subject, threadId: transportReplyContext?.threadId, chatType: transportReplyContext?.delivery?.chatType, replyContext: transportReplyContext });
|
|
3560
3756
|
}
|
|
3561
3757
|
if (action === 'switch') {
|
|
3562
3758
|
const target = (args?.target ?? '').toString().trim();
|
|
3563
3759
|
if (!target)
|
|
3564
3760
|
return { error: '缺少 args.target', code: 'MISSING_VALUE' };
|
|
3565
|
-
return await this.delegateAsAction(action, `/s ${target}`, channel, channelId, userId, { enrichSession: true, authSubject: subject });
|
|
3761
|
+
return await this.delegateAsAction(action, `/s ${target}`, channel, channelId, userId, { enrichSession: true, authSubject: subject, threadId: transportReplyContext?.threadId, chatType: transportReplyContext?.delivery?.chatType, replyContext: transportReplyContext });
|
|
3566
3762
|
}
|
|
3567
3763
|
if (action === 'compact') {
|
|
3568
3764
|
const need = this.requireSession(session);
|
|
3569
3765
|
if (need)
|
|
3570
3766
|
return need;
|
|
3571
|
-
return await this.delegateAsAction(action, '/compact', channel, channelId, userId, { authSubject: subject });
|
|
3767
|
+
return await this.delegateAsAction(action, '/compact', channel, channelId, userId, { authSubject: subject, threadId: transportReplyContext?.threadId, chatType: transportReplyContext?.delivery?.chatType, replyContext: transportReplyContext });
|
|
3572
3768
|
}
|
|
3573
3769
|
if (action === 'fork') {
|
|
3574
3770
|
const need = this.requireSession(session);
|
|
3575
3771
|
if (need)
|
|
3576
3772
|
return need;
|
|
3577
3773
|
const name = (args?.name ?? '').toString().trim();
|
|
3578
|
-
return await this.delegateAsAction(action, name ? `/fork ${name}` : '/fork', channel, channelId, userId, { enrichSession: true, authSubject: subject });
|
|
3774
|
+
return await this.delegateAsAction(action, name ? `/fork ${name}` : '/fork', channel, channelId, userId, { enrichSession: true, authSubject: subject, threadId: transportReplyContext?.threadId, chatType: transportReplyContext?.delivery?.chatType, replyContext: transportReplyContext });
|
|
3579
3775
|
}
|
|
3580
3776
|
return { error: `不支持的 session action: ${action}`, code: 'NOT_SUPPORTED' };
|
|
3581
3777
|
}
|
|
3582
3778
|
// ── /system 系列 ──
|
|
3583
3779
|
if (cmdBase === '/system') {
|
|
3584
3780
|
// D1 迁移:进程级鉴权统一查 daemon.json owners,替代各 action 内联的 identity.role 判断
|
|
3585
|
-
if (!subject.isDaemonOwner) {
|
|
3781
|
+
if (!subject.isDaemonOwner && subject.processRole !== 'daemon-service') {
|
|
3586
3782
|
return {
|
|
3587
3783
|
error: formatAuthorizationDenial('This daemon-level operation requires DaemonOwner.', 'DAEMON_OWNER_REQUIRED'),
|
|
3588
3784
|
code: 'NOT_ALLOWED',
|
|
@@ -3626,17 +3822,28 @@ export async function execMenuAction(cmd, action, args, channel, channelId, user
|
|
|
3626
3822
|
const migrationRequirement = inspectDataMigrationRequirement(resolvePaths().root);
|
|
3627
3823
|
if (migrationRequirement.required) {
|
|
3628
3824
|
return {
|
|
3629
|
-
error: `待处理的用户数据迁移(${migrationRequirement.operationCount}
|
|
3825
|
+
error: `待处理的用户数据迁移(${migrationRequirement.operationCount} 项)阻止重启;本次仅检查,未停止当前服务。请先运行 ec data migrate --dry-run,然后运行 ec data migrate --apply。`,
|
|
3630
3826
|
code: 'DATA_MIGRATION_REQUIRED',
|
|
3631
3827
|
data: { reasonCode: 'DATA_MIGRATION_REQUIRED', operationCount: migrationRequirement.operationCount },
|
|
3632
3828
|
};
|
|
3633
3829
|
}
|
|
3634
3830
|
if (!suppressRealRestart) {
|
|
3831
|
+
// 这条路径同样以 daemon 自杀收尾,入口缺失必须在写 pending 和自杀之前拦下。
|
|
3832
|
+
const restartEntry = resolveCliEntry(getPackageRoot());
|
|
3833
|
+
if (!fs.existsSync(restartEntry.entry)) {
|
|
3834
|
+
return {
|
|
3835
|
+
error: `找不到重启入口:${restartEntry.entry}。请先执行 npm run build 重建产物。`,
|
|
3836
|
+
code: 'RESTART_ENTRY_MISSING',
|
|
3837
|
+
data: { reasonCode: 'RESTART_ENTRY_MISSING', entry: restartEntry.entry },
|
|
3838
|
+
};
|
|
3839
|
+
}
|
|
3635
3840
|
const restartInfo = {
|
|
3636
3841
|
channel,
|
|
3637
3842
|
channelId,
|
|
3638
3843
|
timestamp: Date.now(),
|
|
3844
|
+
...(restartReplyContext?.threadId ? { threadId: restartReplyContext.threadId } : {}),
|
|
3639
3845
|
...(restartDelivery ? { delivery: restartDelivery } : {}),
|
|
3846
|
+
...(restartReplyContext?.replyToMessageId ? { rootId: restartReplyContext.replyToMessageId } : {}),
|
|
3640
3847
|
};
|
|
3641
3848
|
const controlDir = daemonControlDir();
|
|
3642
3849
|
fs.mkdirSync(controlDir, { recursive: true });
|
|
@@ -3645,23 +3852,41 @@ export async function execMenuAction(cmd, action, args, channel, channelId, user
|
|
|
3645
3852
|
// the version check and automatic upgrades before replacing the daemon.
|
|
3646
3853
|
// Do not inherit managed-session credentials: otherwise cmdRestart can
|
|
3647
3854
|
// delegate back to this daemon and recursively enqueue another restart.
|
|
3648
|
-
|
|
3855
|
+
//
|
|
3856
|
+
// `ec restart` owns the monitor liveness check, but it can itself die on
|
|
3857
|
+
// load (tsx + a broken source tree), and this path kills the daemon
|
|
3858
|
+
// afterwards — so confirm the CLI process survived before proceeding.
|
|
3859
|
+
const restartLaunch = await launchMonitorAndConfirm([...restartEntry.args, 'restart'], {
|
|
3649
3860
|
EVOLCORE_HOME: resolvePaths().root,
|
|
3650
3861
|
EVOLCORE_SESSION_ID: '',
|
|
3651
3862
|
[AGENT_DELEGATION_TOKEN_ENV]: '',
|
|
3652
|
-
}
|
|
3863
|
+
},
|
|
3864
|
+
// `ec restart` does not register itself; staying alive is the signal.
|
|
3865
|
+
() => false, pid => scanInstances().restartMonitors.some(m => m.alive && m.record.pid !== pid), 8_000);
|
|
3866
|
+
if (!restartLaunch.ok) {
|
|
3867
|
+
try {
|
|
3868
|
+
fs.rmSync(path.join(controlDir, 'restart-pending.json'), { force: true });
|
|
3869
|
+
}
|
|
3870
|
+
catch { }
|
|
3871
|
+
logger.error(`[System] Refusing menu restart: launcher did not survive (${restartLaunch.reason})`);
|
|
3872
|
+
for (const line of (restartLaunch.stderrTail ?? '').split(/\r?\n/).filter(Boolean).slice(-8)) {
|
|
3873
|
+
logger.error(`[System] | ${line}`);
|
|
3874
|
+
}
|
|
3875
|
+
return {
|
|
3876
|
+
error: `重启已中止:启动器未能运行(${restartLaunch.reason})。服务未停止,仍在运行当前版本。`,
|
|
3877
|
+
code: 'RESTART_LAUNCH_FAILED',
|
|
3878
|
+
data: { reasonCode: 'RESTART_LAUNCH_FAILED', reason: restartLaunch.reason },
|
|
3879
|
+
};
|
|
3880
|
+
}
|
|
3653
3881
|
}
|
|
3654
3882
|
else {
|
|
3655
3883
|
logger.info('[System] Suppressed real menu restart in test runtime');
|
|
3656
3884
|
}
|
|
3657
3885
|
this.eventBus.publish({ type: 'system:restart', channel, channelId });
|
|
3658
|
-
if (!suppressRealRestart) {
|
|
3659
|
-
setTimeout(() => { process.kill(process.pid, 'SIGTERM'); }, 1000);
|
|
3660
|
-
}
|
|
3661
3886
|
return { data: { action: 'restart', success: true } };
|
|
3662
3887
|
}
|
|
3663
3888
|
if (action === 'check') {
|
|
3664
|
-
const r = await this.delegateAsAction(action, '/check', channel, channelId, userId, { overrideIdentity, authSubject: subject });
|
|
3889
|
+
const r = await this.delegateAsAction(action, '/check', channel, channelId, userId, { overrideIdentity, authSubject: subject, threadId: transportReplyContext?.threadId, chatType: transportReplyContext?.delivery?.chatType, replyContext: transportReplyContext });
|
|
3665
3890
|
// delegateAsAction 会把 structured 展开到 data 顶层,并保留 data.structured 兼容旧客户端。
|
|
3666
3891
|
return r;
|
|
3667
3892
|
}
|
|
@@ -3927,8 +4152,8 @@ function menuCommandToken(cmd) {
|
|
|
3927
4152
|
}
|
|
3928
4153
|
function isProcessLevelMenu(name, cmd) {
|
|
3929
4154
|
const cmdToken = menuCommandToken(cmd);
|
|
3930
|
-
return name === 'system' || name === 'agent' || name === 'gateway' || name === 'config'
|
|
3931
|
-
|| cmdToken === '/system' || cmdToken === '/agent' || cmdToken === '/gateway' || cmdToken === '/config';
|
|
4155
|
+
return name === 'system' || name === 'agent' || name === 'gateway' || name === 'config' || name === 'baseagent-models'
|
|
4156
|
+
|| cmdToken === '/system' || cmdToken === '/agent' || cmdToken === '/gateway' || cmdToken === '/config' || cmdToken === '/baseagent-models';
|
|
3932
4157
|
}
|
|
3933
4158
|
function isProcessContextMenu(payload) {
|
|
3934
4159
|
const isCatalog = payload?.type === 'menu.list' && payload?.args?.view === 'catalog';
|
|
@@ -3962,9 +4187,9 @@ async function execMenuForSystemControl(payload, context) {
|
|
|
3962
4187
|
const isRoleRequest = name === 'role' || compatCmd === '/role';
|
|
3963
4188
|
const isConnectRequest = name === 'connect' || compatCmd === '/connect';
|
|
3964
4189
|
const requiresAgentTarget = isRoleRequest || isConnectRequest || [
|
|
3965
|
-
'observable', 'baseagent', 'model', 'effort', 'trigger', 'capability', 'group',
|
|
4190
|
+
'observable', 'baseagent', 'model', 'effort', 'trigger', 'capability', 'group', 'aid',
|
|
3966
4191
|
].includes(name)
|
|
3967
|
-
|| ['/observable', '/baseagent', '/model', '/effort', '/trigger', '/capability', '/group'].includes(compatCmd ?? '')
|
|
4192
|
+
|| ['/observable', '/baseagent', '/model', '/effort', '/trigger', '/capability', '/group', '/aid'].includes(compatCmd ?? '')
|
|
3968
4193
|
|| (!isProcessLevel && requestedContextKind !== undefined && requestedContextKind !== 'process');
|
|
3969
4194
|
const target = isProcessLevel
|
|
3970
4195
|
? { channel: SYSTEM_CONTROL_CHANNEL }
|
|
@@ -3974,6 +4199,12 @@ async function execMenuForSystemControl(payload, context) {
|
|
|
3974
4199
|
const targetChannel = target.channel;
|
|
3975
4200
|
const targetAid = this.getOwningAgent?.(targetChannel)?.aid
|
|
3976
4201
|
?? this.getDaemonTriggerOwnerAid?.(targetChannel);
|
|
4202
|
+
if (name === 'aid' || compatCmd === '/aid') {
|
|
4203
|
+
const requestedAid = typeof payload?.args?.aid === 'string' ? payload.args.aid.trim().replace(/^@/, '') : '';
|
|
4204
|
+
if (requestedAid && targetAid && requestedAid !== targetAid) {
|
|
4205
|
+
return ecwebErr(id, name, 'INVALID_ARGUMENT', 'args.aid 必须与目标 Agent 一致');
|
|
4206
|
+
}
|
|
4207
|
+
}
|
|
3977
4208
|
const trustedSubject = buildAuthSubject({
|
|
3978
4209
|
selfAid: targetAid,
|
|
3979
4210
|
actorId: context.actorAid,
|
|
@@ -3986,10 +4217,18 @@ async function execMenuForSystemControl(payload, context) {
|
|
|
3986
4217
|
processOwners: context.owners,
|
|
3987
4218
|
fromControlChannel: true,
|
|
3988
4219
|
canApprovePersistentFullAccess: context.canApprovePersistentFullAccess,
|
|
4220
|
+
trustedProcessRole: context.source === 'ecweb' ? 'daemon-owner' : undefined,
|
|
3989
4221
|
});
|
|
3990
4222
|
const trustedIdentity = trustedSubject.identity;
|
|
3991
4223
|
const cmd = name ? (menuCommandForName(name) ?? payload.cmd) : payload.cmd;
|
|
3992
4224
|
try {
|
|
4225
|
+
if (menuCommandToken(cmd) === '/baseagent-models') {
|
|
4226
|
+
if (payload.type !== 'menu.query')
|
|
4227
|
+
return ecwebErr(id, name, 'NOT_SUPPORTED', 'Baseagent catalog only supports menu.query');
|
|
4228
|
+
if (payload.agent !== undefined)
|
|
4229
|
+
return ecwebErr(id, name, 'INVALID_ARGUMENT', 'Baseagent catalog does not accept an Agent target');
|
|
4230
|
+
hostCatalogArgs(payload.args);
|
|
4231
|
+
}
|
|
3993
4232
|
switch (payload?.type) {
|
|
3994
4233
|
case 'menu.list':
|
|
3995
4234
|
if (payload?.args?.view === 'catalog') {
|
|
@@ -4093,6 +4332,7 @@ async function execMenuForSystemControl(payload, context) {
|
|
|
4093
4332
|
}
|
|
4094
4333
|
}
|
|
4095
4334
|
catch (e) {
|
|
4335
|
+
logger.error(`[ECWebMenu] failed type=${payload?.type ?? '<none>'} name=${name ?? '<none>'} cmd=${cmd ?? '<none>'} agent=${payload?.agent ?? '<none>'} actor=${context.actorAid ?? '<none>'}: ${e?.stack || e?.message || String(e)}`);
|
|
4096
4336
|
return menuFailure({ id, ...(name ? { name } : {}) }, normalizeMenuError(e));
|
|
4097
4337
|
}
|
|
4098
4338
|
}
|