evolcore 0.0.25 → 0.0.27
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 +632 -565
- package/LICENSE +21 -21
- package/README.md +455 -455
- package/bin/codex-managed-acl.ps1 +203 -160
- package/bin/codex-managed-hook.mjs +208 -208
- package/bin/ec-safe-output.js +327 -327
- package/bin/ec.js +40 -29
- package/bin/install-codex-managed-hooks.mjs +582 -555
- package/dist/agents/baseagent-availability.js +68 -0
- package/dist/agents/baseagent.js +96 -46
- package/dist/agents/claude-runner.js +172 -33
- package/dist/agents/codex/app-server-client.js +76 -31
- package/dist/agents/codex/catalog.js +1 -1
- package/dist/agents/codex/diagnostics.js +34 -1
- package/dist/agents/codex-runner.js +434 -446
- package/dist/agents/ecagent-runner.js +174 -34
- package/dist/agents/gemini-runner.js +8 -7
- package/dist/agents/runner-types.js +75 -19
- package/dist/aun/aid/agentmd.js +151 -36
- package/dist/aun/aid/client.js +1 -1
- package/dist/aun/aid/control-aid.js +180 -1
- package/dist/aun/aid/identity.js +4 -4
- package/dist/aun/aid/index.js +1 -1
- package/dist/aun/aid/store.js +17 -13
- package/dist/aun/group/history.js +303 -0
- package/dist/aun/group/identity.js +43 -0
- package/dist/aun/group/index.js +5 -0
- package/dist/aun/{msg/group.js → group/operations.js} +162 -39
- package/dist/aun/{menu-client.js → menu/client.js} +85 -6
- package/dist/aun/menu/index.js +2 -0
- package/dist/aun/menu/transport.js +49 -0
- package/dist/aun/msg/index.js +2 -1
- package/dist/aun/msg/managed-operation.js +85 -19
- package/dist/aun/msg/mention-schema.js +51 -0
- package/dist/aun/msg/p2p.js +19 -9
- package/dist/aun/msg/upload.js +43 -80
- package/dist/aun/outbox.js +29 -7
- package/dist/aun/rpc/client.js +1 -0
- package/dist/aun/storage/client.js +39 -50
- package/dist/channels/aun.js +842 -318
- package/dist/channels/daemon.js +10 -6
- package/dist/cli/agent-command.js +212 -89
- package/dist/cli/agent-create.js +626 -11
- package/dist/cli/agent.js +125 -13
- package/dist/cli/aun-commands.js +487 -337
- package/dist/cli/bench.js +20 -20
- package/dist/cli/config.js +55 -33
- package/dist/cli/contact.js +72 -15
- package/dist/cli/ctl-command.js +24 -24
- package/dist/cli/daemon-commands.js +140 -63
- package/dist/cli/diag.js +31 -0
- package/dist/cli/fs-command.js +70 -70
- package/dist/cli/handoff-command.js +46 -14
- package/dist/cli/index.js +91 -79
- package/dist/cli/init.js +34 -1
- package/dist/cli/link-rules.js +21 -21
- package/dist/cli/managed-command-guard.js +5 -1
- package/dist/cli/menu-client.js +51 -32
- package/dist/cli/model.js +3 -3
- package/dist/cli/net-check.js +22 -22
- package/dist/cli/queue-command.js +31 -31
- package/dist/cli/response.js +32 -32
- package/dist/cli/restart-monitor.js +103 -42
- package/dist/cli/stats.js +51 -51
- package/dist/cli/task-command.js +59 -0
- package/dist/cli/task-context.js +97 -49
- package/dist/cli/trigger-command.js +74 -64
- package/dist/config/builtin-roles.js +11 -0
- package/dist/config/config-field-policy.js +11 -0
- package/dist/config/config-manager.js +32 -4
- package/dist/config/config-operation-service.js +160 -0
- package/dist/config/contact-book-store.js +1 -1
- package/dist/config/contact-book.js +1 -1
- package/dist/config/contact-operation-service.js +97 -8
- package/dist/config/peer-role-resolver.js +1 -1
- package/dist/config/resolved-config-op.js +30 -7
- package/dist/config-store.js +1 -1
- package/dist/core/agent-application-service.js +7 -1
- package/dist/core/audit/event-key.js +17 -2
- package/dist/core/audit/log-integrity.js +34 -8
- package/dist/core/auth/agent-delegation.js +48 -14
- package/dist/core/auth/auth-gateway.js +2 -0
- package/dist/core/auth/authorization-audit.js +53 -49
- package/dist/core/auth/operation-authorizer.js +90 -7
- package/dist/core/auth/operation-catalog.js +145 -4
- package/dist/core/auth/remote-access-store.js +155 -10
- package/dist/core/bootstrap-messages.js +66 -7
- package/dist/core/bootstrap-service.js +111 -20
- package/dist/core/command/agent-control.js +88 -7
- package/dist/core/command/agent-storage-menu.js +443 -0
- package/dist/core/command/cli-intent-parser.js +19 -0
- package/dist/core/command/command-handler.js +159 -65
- package/dist/core/command/data-root-resolver.js +273 -0
- package/dist/core/command/diag-service.js +118 -0
- package/dist/core/command/group-menu.js +7 -2
- package/dist/core/command/menu-catalog.js +46 -7
- package/dist/core/command/menu-handler.js +392 -100
- package/dist/core/command/menu-protocol.js +45 -6
- package/dist/core/command/menu-token-store.js +41 -8
- package/dist/core/command/role-menu.js +2 -2
- package/dist/core/command/slash-gate.js +34 -4
- package/dist/core/command/slash-handler.js +123 -43
- package/dist/core/event-catalog.js +4 -0
- package/dist/core/evolagent-registry.js +28 -2
- package/dist/core/group/invite-service.js +924 -0
- package/dist/core/handoff/runtime.js +205 -27
- package/dist/core/handoff/store.js +193 -4
- package/dist/core/interaction-router.js +22 -8
- package/dist/core/message/im-renderer.js +146 -34
- package/dist/core/message/message-bridge.js +163 -20
- package/dist/core/message/message-log.js +5 -3
- package/dist/core/message/message-queue.js +99 -41
- package/dist/core/message/message-utils.js +1 -0
- package/dist/core/message/response-engine.js +755 -144
- package/dist/core/model/catalog-service.js +79 -49
- package/dist/core/model/context-window-suffix.js +33 -0
- package/dist/core/model/model-catalog.js +1 -1
- package/dist/core/model/model-permission.js +12 -3
- package/dist/core/permission/approval-gateway.js +36 -3
- package/dist/core/permission/ec-command-parser.js +112 -41
- package/dist/core/permission/protected-paths.js +134 -16
- package/dist/core/permission/sandbox-runtime.js +108 -17
- package/dist/core/permission/tool-error-code.js +15 -0
- package/dist/core/permission/tool-policy.js +9 -8
- package/dist/core/relation/peer-identity.js +57 -6
- package/dist/core/relation/shared-data.js +106 -0
- package/dist/core/role/runtime-policy.js +64 -0
- package/dist/core/runtime-lock.js +10 -26
- package/dist/core/session/runner-task-history.js +170 -0
- package/dist/core/session/session-manager.js +34 -6
- package/dist/core/session/session-mapper.js +41 -0
- package/dist/core/session/session-turn-coordinator.js +37 -7
- package/dist/core/session/session-turns.js +48 -7
- package/dist/core/task/task-recovery-service.js +176 -0
- package/dist/core/task/task-resume-operation.js +32 -0
- package/dist/eck/group-rules-sync.js +5 -3
- package/dist/eck/kit-renderer.js +1 -0
- package/dist/index.js +588 -217
- package/dist/ipc.js +193 -15
- package/dist/paths.js +17 -4
- package/dist/product.js +2 -0
- package/dist/response-system/engines/v1/proactive-flow.js +59 -2
- package/dist/stats/billing.js +41 -15
- package/dist/stats/db.js +159 -159
- package/dist/stats/eck-vars.js +9 -9
- package/dist/stats/price-resolver.js +28 -10
- package/dist/stats/query.js +139 -139
- package/dist/stats/role-budget.js +6 -6
- package/dist/stats/writer.js +51 -51
- package/dist/trigger/event-source.js +19 -0
- package/dist/trigger/feedback.js +233 -40
- package/dist/trigger/parser.js +55 -5
- package/dist/trigger/patch.js +9 -0
- package/dist/trigger/scheduler.js +30 -4
- package/dist/trigger/validation.js +20 -3
- package/dist/utils/cross-platform.js +30 -1
- package/dist/utils/ecweb-utils.js +1 -0
- package/dist/utils/error-dict.json +160 -160
- package/dist/utils/error-utils.js +61 -2
- package/dist/utils/evolcore-version.js +129 -0
- package/dist/utils/logger.js +26 -14
- package/dist/utils/macos-autostart.js +22 -22
- package/dist/utils/model-prices.jsonl +23 -23
- package/dist/utils/npm-ops.js +176 -35
- package/dist/utils/tool-summary.js +59 -3
- package/dist/utils/welcome.js +196 -23
- package/ecagent/LICENSE +21 -21
- package/ecagent/NOTICE +11 -11
- package/ecagent/README.md +61 -61
- package/ecagent/dist/harness/agent-harness.js +3 -0
- 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/ecagent/dist/harness/session/jsonl-storage.js +21 -2
- package/kits/docs/GUIDE.md +20 -20
- package/kits/docs/INDEX.md +60 -66
- package/kits/docs/aun/CHEATSHEET.md +19 -19
- package/kits/docs/aun/SYNC_PROTOCOL.md +15 -15
- package/kits/docs/channels/aun.md +69 -56
- package/kits/docs/channels/feishu.md +56 -56
- package/kits/docs/context-assembly.md +379 -367
- 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 +76 -71
- package/kits/docs/evolcore/agent-create.md +202 -165
- package/kits/docs/evolcore/agent.md +99 -67
- package/kits/docs/evolcore/aid.md +68 -60
- package/kits/docs/evolcore/codex-windows-sandbox.md +146 -146
- package/kits/docs/evolcore/config.md +241 -195
- package/kits/docs/evolcore/contact.md +75 -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 +244 -244
- package/kits/docs/evolcore/group-fs.md +17 -17
- package/kits/docs/evolcore/group-rules.md +251 -251
- package/kits/docs/evolcore/group.md +267 -152
- package/kits/docs/evolcore/menu-client.md +58 -34
- package/kits/docs/evolcore/model.md +50 -50
- package/kits/docs/evolcore/msg.md +169 -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 +53 -50
- package/kits/docs/evolcore/task.md +14 -0
- package/kits/docs/evolcore/trigger.md +623 -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 +269 -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 +42 -25
- package/kits/docs/venues/private.md +10 -10
- package/kits/eck_manifest.auxiliary.json +43 -43
- package/kits/eck_manifest.json +217 -190
- 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 +166 -162
- package/kits/rules/02-navigation.md +96 -96
- package/kits/rules/03-identity.md +36 -36
- package/kits/rules/04-relation.md +76 -75
- package/kits/rules/05-venue.md +60 -60
- package/kits/rules/06-channel.md +65 -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 +451 -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 +215 -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 +92 -92
- package/kits/templates/roles/member.json +101 -96
- package/kits/templates/roles/owner.json +9 -9
- package/kits/templates/roles/visitor.json +90 -85
- package/kits/templates/system-fragments/baseagent.md +14 -14
- package/kits/templates/system-fragments/bootstrap.md +53 -34
- package/kits/templates/system-fragments/channel.md +28 -64
- package/kits/templates/system-fragments/commands.md +36 -28
- package/kits/templates/system-fragments/group-unread-history.md +7 -0
- package/kits/templates/system-fragments/identity.md +13 -13
- package/kits/templates/system-fragments/relation.md +19 -19
- package/kits/templates/system-fragments/reply.md +56 -0
- package/kits/templates/system-fragments/session.md +59 -59
- package/kits/templates/system-fragments/venue.md +31 -31
- package/package.json +104 -98
- package/dist/aun/group-identity.js +0 -9
- /package/dist/aun/{group-fs-download.js → group/fs-download.js} +0 -0
- /package/dist/aun/{msg/group-index.js → group/index-rpc.js} +0 -0
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { getBaseagentSnapshot, loadedBaseagentIds } from '../../agents/baseagent-availability.js';
|
|
1
2
|
import { normalizePeer, writeScope } from '../model/config-scope.js';
|
|
2
3
|
import { formatPeerKey } from '../relation/peer-identity.js';
|
|
3
4
|
import { modelMatches, getCatalog, getBaseagentCatalog, resolveCatalogModel, assertCatalogSelectable, TRANSPORT_EFFORTS } from '../model/model-catalog.js';
|
|
@@ -13,7 +14,7 @@ import crypto from 'crypto';
|
|
|
13
14
|
import { CronExpressionParser } from 'cron-parser';
|
|
14
15
|
import { parseDuration } from '../../trigger/parser.js';
|
|
15
16
|
import { checkLatestVersion, getLocalVersion, isLinkedInstall, compareVersions, resolveGlobalPkg } from '../../utils/npm-ops.js';
|
|
16
|
-
import {
|
|
17
|
+
import { resolveCliEntry, launchMonitorAndConfirm } from '../../utils/cross-platform.js';
|
|
17
18
|
import { cachedCliVersion } from '../../utils/cross-platform.js';
|
|
18
19
|
import { scanInstances } from '../../utils/instance-registry.js';
|
|
19
20
|
import { isFullAccessEnabled, loadDefaults, loadDaemonConfig } from '../../config-store.js';
|
|
@@ -29,6 +30,7 @@ import { auditCommandAuthorization, auditFullAccessEvent, hashArgv } from '../au
|
|
|
29
30
|
import { authorizeOperation, buildAuthSubject } from '../auth/auth-gateway.js';
|
|
30
31
|
import { authorizationDenialData, formatAuthorizationDenial } from '../auth/authorization-denial.js';
|
|
31
32
|
import { evaluateRoleOperationCapability } from '../auth/operation-authorizer.js';
|
|
33
|
+
import { isUnsupportedBaseagentConfigField } from '../../config/config-field-policy.js';
|
|
32
34
|
import { splitConfigBatchGetArgv } from '../../cli/cli-argv.js';
|
|
33
35
|
import { chatmodeFieldForPeer, resolveChatModeForField } from '../message/peer-mode.js';
|
|
34
36
|
import { PUBLIC_PERMISSION_MODES } from '../permission/mode.js';
|
|
@@ -40,13 +42,16 @@ import { shouldSuppressRealRestart } from '../../utils/restart-safety.js';
|
|
|
40
42
|
import { AGENT_DELEGATION_TOKEN_ENV } from '../auth/agent-delegation.js';
|
|
41
43
|
import { evolMenuResponseTransportMetadata, menuFailure, menuCommandForName, menuSuccess, normalizeMenuError, validateConfigWriteScope, validateMenuRequest, } from './menu-protocol.js';
|
|
42
44
|
import { parseMenuClientProtectedHeaders } from './menu-protocol.js';
|
|
43
|
-
import { validateAndRenewRemoteAccessToken } from '../auth/remote-access-store.js';
|
|
45
|
+
import { remoteAccessService, validateAndRenewRemoteAccessToken } from '../auth/remote-access-store.js';
|
|
44
46
|
import { listDiagnosticLogOptions, listRawConfigResources, queryDiagnosticLogs, readRawConfigResource, } from './daemon-diagnostics.js';
|
|
45
47
|
import { roleMenuAction, roleMenuOperation, roleMenuOptions, roleMenuQuery, roleMenuUpdate, } from './role-menu.js';
|
|
46
48
|
import { handleConnectMenu, connectMenuOperation } from './connect-menu.js';
|
|
47
49
|
import { groupMenuAuthorizationArgs, groupMenuIntent, handleGroupMenu, } from './group-menu.js';
|
|
48
50
|
import { getMenuCatalog, execMenuValueBatch, resolveExplicitMenuExecutionContext, validateMenuCatalogArgs, validateMenuValueBatchArgs } from './menu-catalog.js';
|
|
49
51
|
import { probeSessionActivity } from './slash-gate.js';
|
|
52
|
+
import { dataRootFailure, dataRootResolver } from './data-root-resolver.js';
|
|
53
|
+
import { agentStorageMenuService, isAgentStorageMenuRequest } from './agent-storage-menu.js';
|
|
54
|
+
import { isArchivedTopicLable, normalizeTopicLable, resolveTopicArchiveFilter, } from '../session/session-mapper.js';
|
|
50
55
|
/**
|
|
51
56
|
* 获取 baseagent CLI 的版本号(claude/gemini/codex)。
|
|
52
57
|
* 失败返回 null(命令不存在或执行失败)。
|
|
@@ -180,6 +185,8 @@ function getRenameName(args) {
|
|
|
180
185
|
}
|
|
181
186
|
function buildSessionPayload(session, name) {
|
|
182
187
|
const payload = { id: session.id, name };
|
|
188
|
+
if (session.lable)
|
|
189
|
+
payload.lable = session.lable;
|
|
183
190
|
if (session.agentSessionId)
|
|
184
191
|
payload.agentSessionId = session.agentSessionId;
|
|
185
192
|
return payload;
|
|
@@ -413,7 +420,10 @@ function hostCatalogArgs(args = {}) {
|
|
|
413
420
|
}
|
|
414
421
|
if (args.refresh !== undefined && typeof args.refresh !== 'boolean')
|
|
415
422
|
invalid('refresh must be a boolean');
|
|
416
|
-
|
|
423
|
+
const baseagent = menuStringArg(args, 'baseagent') ?? menuStringArg(args, 'ba');
|
|
424
|
+
if (!baseagent)
|
|
425
|
+
return invalid('baseagent is required (claude / codex / ecagent / gemini)');
|
|
426
|
+
return { baseagent, refresh: args.refresh === true };
|
|
417
427
|
}
|
|
418
428
|
const LEGACY_SESSION_CONFIG_ARG_KEYS = [
|
|
419
429
|
'session',
|
|
@@ -851,7 +861,7 @@ export function isProcessLevelOwner(peerId, owners) {
|
|
|
851
861
|
// ── 控制面双轨鉴权(见 docs/.../2026-06-10-control-channel-auth-design.md)──
|
|
852
862
|
// 白名单而非黑名单:默认安全,新增进程级 action 需显式加入。
|
|
853
863
|
/** /agent 的进程级 action:仅控制 channel 可执行。 */
|
|
854
|
-
export const PROCESS_LEVEL_AGENT_ACTIONS = new Set(['create', 'delete', 'enable', 'disable']);
|
|
864
|
+
export const PROCESS_LEVEL_AGENT_ACTIONS = new Set(['create', 'initialize', 'delete', 'enable', 'disable']);
|
|
855
865
|
/** /agent 的「本 agent 自管理」action:agent channel 仅 owner/admin 可对自身 aid 执行;update 另行收紧为 owner。 */
|
|
856
866
|
export const SELF_MANAGE_AGENT_ACTIONS = new Set(['update', 'reload']);
|
|
857
867
|
/** 判断 (cmdBase, action) 是否为进程级操作(仅控制 channel 可执行)。
|
|
@@ -898,6 +908,10 @@ function gateControlScope(opts) {
|
|
|
898
908
|
return null;
|
|
899
909
|
}
|
|
900
910
|
function buildMenuIntent(verb, cmdBase, args, action, value, fromControlChannel = false) {
|
|
911
|
+
if (cmdBase === '/agent-data-root' || cmdBase === '/relation-data-root')
|
|
912
|
+
return null;
|
|
913
|
+
if (cmdBase === '/file' && args?.rootId !== undefined)
|
|
914
|
+
return null;
|
|
901
915
|
const payload = { ...(args ?? {}), ...(value !== undefined ? { value } : {}) };
|
|
902
916
|
const intent = (operation, scope = 'relation', extra) => ({
|
|
903
917
|
operation,
|
|
@@ -909,6 +923,9 @@ function buildMenuIntent(verb, cmdBase, args, action, value, fromControlChannel
|
|
|
909
923
|
if (verb === 'query') {
|
|
910
924
|
if (cmdBase === '/baseagent-models')
|
|
911
925
|
return intent('baseagent.models.list', 'process');
|
|
926
|
+
if (cmdBase === '/baseagents') {
|
|
927
|
+
return intent(fromControlChannel ? 'system.status' : 'agent.baseagent.list', fromControlChannel ? 'process' : 'agent');
|
|
928
|
+
}
|
|
912
929
|
if (cmdBase === '/pwd')
|
|
913
930
|
return intent('project.current', 'relation');
|
|
914
931
|
if (cmdBase === '/session' || cmdBase === '/s')
|
|
@@ -1015,12 +1032,19 @@ function buildMenuIntent(verb, cmdBase, args, action, value, fromControlChannel
|
|
|
1015
1032
|
return intent('system.upgrade', 'process', { action });
|
|
1016
1033
|
if (action === 'check')
|
|
1017
1034
|
return intent('system.status', 'process', { action });
|
|
1035
|
+
if (action === 'paircode')
|
|
1036
|
+
return intent('system.paircode', 'process', { action });
|
|
1018
1037
|
}
|
|
1019
1038
|
if (cmdBase === '/agent') {
|
|
1039
|
+
if (action === 'update' && args?.patch && Object.hasOwn(args.patch, 'avatar')) {
|
|
1040
|
+
return intent('ec.aid.avatar', 'agent', { action });
|
|
1041
|
+
}
|
|
1020
1042
|
if (action === 'reload')
|
|
1021
1043
|
return intent('agent.reload', fromControlChannel ? 'control' : 'agent', { action });
|
|
1022
1044
|
if (action === 'create')
|
|
1023
1045
|
return intent('agent.create', 'control', { action });
|
|
1046
|
+
if (action === 'initialize')
|
|
1047
|
+
return intent('agent.initialize', 'control', { action });
|
|
1024
1048
|
if (action === 'delete')
|
|
1025
1049
|
return intent('agent.delete', 'control', { action });
|
|
1026
1050
|
if (action === 'enable')
|
|
@@ -1061,6 +1085,7 @@ function buildMenuAuthSubject(owner, params) {
|
|
|
1061
1085
|
if (params.subject) {
|
|
1062
1086
|
return {
|
|
1063
1087
|
...params.subject,
|
|
1088
|
+
fromControlChannel: params.fromControlChannel || params.subject.fromControlChannel,
|
|
1064
1089
|
...(params.requestId && !params.subject.requestId ? { requestId: params.requestId } : {}),
|
|
1065
1090
|
...(params.session?.id && !params.subject.sessionId ? { sessionId: params.session.id } : {}),
|
|
1066
1091
|
};
|
|
@@ -1509,9 +1534,27 @@ export function getMenuItems(role, chatType = 'private', scope = 'agent', authSu
|
|
|
1509
1534
|
});
|
|
1510
1535
|
return items;
|
|
1511
1536
|
}
|
|
1537
|
+
/** All Menu reads and writes must resolve the same transport-scoped topic. */
|
|
1538
|
+
async function loadMenuTopicSession(sessionManager, channel, channelId, threadId) {
|
|
1539
|
+
let session;
|
|
1540
|
+
try {
|
|
1541
|
+
if (typeof sessionManager.getThreadSession !== 'function')
|
|
1542
|
+
throw new Error('topic lookup unavailable');
|
|
1543
|
+
session = await sessionManager.getThreadSession(channel, channelId, threadId);
|
|
1544
|
+
}
|
|
1545
|
+
catch (error) {
|
|
1546
|
+
logger.debug(`[MenuHandler] topic session lookup failed: thread=${threadId}: ${error instanceof Error ? error.message : String(error)}`);
|
|
1547
|
+
throw { code: 'TOPIC_SESSION_LOOKUP_FAILED', message: '无法读取话题会话,拒绝回退到主会话' };
|
|
1548
|
+
}
|
|
1549
|
+
if (!session)
|
|
1550
|
+
throw { code: 'TOPIC_SESSION_NOT_FOUND', message: '话题会话不存在,拒绝回退到主会话' };
|
|
1551
|
+
return session;
|
|
1552
|
+
}
|
|
1512
1553
|
/** 动态子菜单:根据 cmd 路径返回选项列表(供 menu.query + cmd 使用) */
|
|
1513
|
-
export async function getSubMenuItems(cmd, channel, channelId, userId, args, overrideIdentity, explicitChatType, fromControlChannel = false, authSubject, source = 'menu') {
|
|
1514
|
-
const session =
|
|
1554
|
+
export async function getSubMenuItems(cmd, channel, channelId, userId, args, overrideIdentity, explicitChatType, fromControlChannel = false, authSubject, source = 'menu', transportReplyContext) {
|
|
1555
|
+
const session = transportReplyContext?.threadId
|
|
1556
|
+
? await loadMenuTopicSession(this.sessionManager, channel, channelId, transportReplyContext.threadId)
|
|
1557
|
+
: await this.sessionManager.getActiveSession(channel, channelId);
|
|
1515
1558
|
const subject = buildMenuAuthSubject(this, {
|
|
1516
1559
|
identity: overrideIdentity ?? this.sessionManager.resolveIdentity(channel, userId, ...menuIdentityArgs(session)),
|
|
1517
1560
|
session,
|
|
@@ -1615,19 +1658,23 @@ export async function getSubMenuItems(cmd, channel, channelId, userId, args, ove
|
|
|
1615
1658
|
: await execAgentOptions(args);
|
|
1616
1659
|
if ('error' in res)
|
|
1617
1660
|
throw { code: res.code, message: res.error };
|
|
1618
|
-
return res.data.agents.map(ag => ({
|
|
1661
|
+
return res.data.agents.map(ag => ({
|
|
1662
|
+
value: ag.aid, label: ag.name || ag.aid, desc: ag.status, lifecycle: ag.lifecycle ?? null,
|
|
1663
|
+
}));
|
|
1619
1664
|
}
|
|
1620
1665
|
// agent channel:直接读取自身,避免先枚举全部 Agent 再过滤。
|
|
1621
1666
|
const selfAid = this.getOwningAgent?.(channel)?.aid;
|
|
1622
1667
|
if (!selfAid)
|
|
1623
1668
|
throw { code: 'FORBIDDEN', message: '当前 channel 无绑定 agent' };
|
|
1624
1669
|
const res = this.agentApplicationService
|
|
1625
|
-
? await execAgentQuery({ aid: selfAid }, this.agentApplicationService)
|
|
1626
|
-
: await execAgentQuery({ aid: selfAid });
|
|
1670
|
+
? await execAgentQuery({ aid: selfAid }, this.agentApplicationService, subject)
|
|
1671
|
+
: await execAgentQuery({ aid: selfAid }, undefined, subject);
|
|
1627
1672
|
if ('error' in res)
|
|
1628
1673
|
throw { code: res.code, message: res.error };
|
|
1629
1674
|
const ag = res.data;
|
|
1630
|
-
return [{
|
|
1675
|
+
return [{
|
|
1676
|
+
value: ag.aid, label: ag.identity?.name || ag.name || ag.aid, desc: ag.status, lifecycle: ag.lifecycle ?? null,
|
|
1677
|
+
}];
|
|
1631
1678
|
}
|
|
1632
1679
|
if (cmd === '/capability') {
|
|
1633
1680
|
const type = args?.type;
|
|
@@ -1683,12 +1730,33 @@ export async function getSubMenuItems(cmd, channel, channelId, userId, args, ove
|
|
|
1683
1730
|
preview: turn.userContent,
|
|
1684
1731
|
turn: turn.index,
|
|
1685
1732
|
assistantMessageId: turn.assistantUuid,
|
|
1733
|
+
assistantMessageIds: turn.assistantUuids,
|
|
1686
1734
|
userMessageId: turn.userUuid,
|
|
1735
|
+
userTextHash: turn.userTextHash,
|
|
1736
|
+
assistantTextHashes: turn.assistantTextHashes,
|
|
1687
1737
|
}));
|
|
1688
1738
|
}
|
|
1689
|
-
const
|
|
1739
|
+
const hasLable = args && (Object.hasOwn(args, 'lable') || Object.hasOwn(args, 'label'));
|
|
1740
|
+
let lable;
|
|
1741
|
+
if (hasLable) {
|
|
1742
|
+
try {
|
|
1743
|
+
lable = normalizeTopicLable(Object.hasOwn(args, 'lable') ? args.lable : args.label);
|
|
1744
|
+
}
|
|
1745
|
+
catch (error) {
|
|
1746
|
+
throw { code: 'INVALID_ARGS', message: error.message };
|
|
1747
|
+
}
|
|
1748
|
+
}
|
|
1749
|
+
let archiveFilter;
|
|
1750
|
+
try {
|
|
1751
|
+
const archived = args?.archived === undefined && isArchivedTopicLable(lable) ? true : args?.archived;
|
|
1752
|
+
archiveFilter = resolveTopicArchiveFilter(archived);
|
|
1753
|
+
}
|
|
1754
|
+
catch (error) {
|
|
1755
|
+
throw { code: 'INVALID_ARGS', message: error.message };
|
|
1756
|
+
}
|
|
1757
|
+
const sessions = await this.sessionManager.listSessions(channel, channelId, { archiveFilter });
|
|
1690
1758
|
return sessions
|
|
1691
|
-
.filter((s) => !!s.threadId)
|
|
1759
|
+
.filter((s) => !!s.threadId && (!hasLable || s.lable === lable))
|
|
1692
1760
|
.map((s) => this.buildTopicMenuItem(s));
|
|
1693
1761
|
}
|
|
1694
1762
|
if (cmd === '/s' || cmd === '/session' || cmd === '/del') {
|
|
@@ -1733,7 +1801,12 @@ export async function getSubMenuItems(cmd, channel, channelId, userId, args, ove
|
|
|
1733
1801
|
const available = requestedAgent?.name
|
|
1734
1802
|
? (this.getAvailableBaseagentsForOwner?.(requestedAgent.name) ?? [])
|
|
1735
1803
|
: this.getAvailableBaseagents(channel);
|
|
1736
|
-
|
|
1804
|
+
const inventory = await getBaseagentSnapshot({
|
|
1805
|
+
config: { active_baseagent: currentAgent }, loaded: available, probe: false,
|
|
1806
|
+
});
|
|
1807
|
+
return inventory.baseagents
|
|
1808
|
+
.filter(item => item.loaded === true)
|
|
1809
|
+
.map(item => ({ value: item.id, label: item.id, selected: item.id === currentAgent }));
|
|
1737
1810
|
}
|
|
1738
1811
|
if (cmd === '/model') {
|
|
1739
1812
|
const target = resolveMenuModelTarget.call(this, {
|
|
@@ -1767,7 +1840,7 @@ export async function getSubMenuItems(cmd, channel, channelId, userId, args, ove
|
|
|
1767
1840
|
const requestedModel = menuStringArg(args, 'model') ?? menuStringArg(args, 'current');
|
|
1768
1841
|
const currentModel = requestedModel || readMenuModel(target, agent).value || agent.getModel();
|
|
1769
1842
|
if (models.length > 0)
|
|
1770
|
-
return models.map((m) => ({ value: m, label: modelDisplayLabel(agent, m) + (catalog.models.find(e => e.id === m)?.verification === 'unverified' ? ' [unverified]' : '')
|
|
1843
|
+
return models.map((m) => ({ value: m, label: modelDisplayLabel(agent, m) + (catalog.models.find(e => e.id === m)?.verification === 'unverified' ? ' [unverified]' : ''), selected: modelMatches(agent, m, currentModel) }));
|
|
1771
1844
|
}
|
|
1772
1845
|
return null;
|
|
1773
1846
|
}
|
|
@@ -1892,7 +1965,7 @@ export async function getSubMenuItems(cmd, channel, channelId, userId, args, ove
|
|
|
1892
1965
|
// 所有方法返回 { data } 或 { error, code? }。code 是结构化错误码(NO_ACTIVE_SESSION 等),
|
|
1893
1966
|
// 客户端可据此决定降级策略。message-bridge 把 code 透传到 menu.response。
|
|
1894
1967
|
/** menu.query — 查询当前值。 */
|
|
1895
|
-
export async function execMenuQuery(cmd, channel, channelId, userId, args, explicitChatType, fromControlChannel = false, overrideIdentity, authSubject, source = 'menu') {
|
|
1968
|
+
export async function execMenuQuery(cmd, channel, channelId, userId, args, explicitChatType, fromControlChannel = false, overrideIdentity, authSubject, source = 'menu', transportReplyContext) {
|
|
1896
1969
|
const cmdBase = cmd.trim().split(' ')[0];
|
|
1897
1970
|
if (!cmdBase)
|
|
1898
1971
|
return { error: '缺少命令', code: 'MISSING_CMD' };
|
|
@@ -1903,7 +1976,17 @@ export async function execMenuQuery(cmd, channel, channelId, userId, args, expli
|
|
|
1903
1976
|
const gated = gateControlScope.call(this, { cmdBase, args, channel, fromControlChannel });
|
|
1904
1977
|
if (gated)
|
|
1905
1978
|
return gated;
|
|
1906
|
-
const
|
|
1979
|
+
const loadedContext = await this.loadMenuContext(channel, channelId);
|
|
1980
|
+
const evolagent = loadedContext.evolagent;
|
|
1981
|
+
let session = loadedContext.session;
|
|
1982
|
+
if (transportReplyContext?.threadId) {
|
|
1983
|
+
try {
|
|
1984
|
+
session = await loadMenuTopicSession(this.sessionManager, channel, channelId, transportReplyContext.threadId);
|
|
1985
|
+
}
|
|
1986
|
+
catch (error) {
|
|
1987
|
+
return menuResultFailure(error);
|
|
1988
|
+
}
|
|
1989
|
+
}
|
|
1907
1990
|
const identity = overrideIdentity ?? this.sessionManager.resolveIdentity(channel, userId, ...menuIdentityArgs(session));
|
|
1908
1991
|
const subject = buildMenuAuthSubject(this, {
|
|
1909
1992
|
identity,
|
|
@@ -1915,6 +1998,15 @@ export async function execMenuQuery(cmd, channel, channelId, userId, args, expli
|
|
|
1915
1998
|
fromControlChannel,
|
|
1916
1999
|
subject: authSubject,
|
|
1917
2000
|
});
|
|
2001
|
+
const storageRequest = { type: 'menu.query', cmd, args };
|
|
2002
|
+
if (isAgentStorageMenuRequest(storageRequest)) {
|
|
2003
|
+
try {
|
|
2004
|
+
return { data: await agentStorageMenuService.execute(storageRequest, subject, this.agentStorageClientResolver) };
|
|
2005
|
+
}
|
|
2006
|
+
catch (error) {
|
|
2007
|
+
return dataRootFailure(error);
|
|
2008
|
+
}
|
|
2009
|
+
}
|
|
1918
2010
|
if (isRequesterRoleQuery) {
|
|
1919
2011
|
if (!subject.allowAccess) {
|
|
1920
2012
|
return { error: `Role ${subject.role} is not allowed to access this agent`, code: 'ROLE_ACCESS_DENIED' };
|
|
@@ -1983,6 +2075,28 @@ export async function execMenuQuery(cmd, channel, channelId, userId, args, expli
|
|
|
1983
2075
|
});
|
|
1984
2076
|
if (authDenied)
|
|
1985
2077
|
return authDenied;
|
|
2078
|
+
if (cmdBase === '/agent-data-root') {
|
|
2079
|
+
if (args && Object.keys(args).length > 0) {
|
|
2080
|
+
return { error: 'agent-data-root does not accept args', code: 'INVALID_ARGUMENT' };
|
|
2081
|
+
}
|
|
2082
|
+
try {
|
|
2083
|
+
return { data: dataRootResolver.issueAgentRoot(subject) };
|
|
2084
|
+
}
|
|
2085
|
+
catch (error) {
|
|
2086
|
+
return dataRootFailure(error);
|
|
2087
|
+
}
|
|
2088
|
+
}
|
|
2089
|
+
if (cmdBase === '/relation-data-root') {
|
|
2090
|
+
if (args && Object.keys(args).some(key => key !== 'peerAid')) {
|
|
2091
|
+
return { error: 'relation-data-root only accepts args.peerAid', code: 'INVALID_ARGUMENT' };
|
|
2092
|
+
}
|
|
2093
|
+
try {
|
|
2094
|
+
return { data: dataRootResolver.issueRelationRoot(subject, args?.peerAid) };
|
|
2095
|
+
}
|
|
2096
|
+
catch (error) {
|
|
2097
|
+
return dataRootFailure(error);
|
|
2098
|
+
}
|
|
2099
|
+
}
|
|
1986
2100
|
if (cmdBase === '/baseagent-models') {
|
|
1987
2101
|
try {
|
|
1988
2102
|
const query = hostCatalogArgs(args);
|
|
@@ -2001,8 +2115,8 @@ export async function execMenuQuery(cmd, channel, channelId, userId, args, expli
|
|
|
2001
2115
|
if (fromControlChannel) {
|
|
2002
2116
|
if (args?.aid) {
|
|
2003
2117
|
return this.agentApplicationService
|
|
2004
|
-
? await execAgentQuery(args, this.agentApplicationService)
|
|
2005
|
-
: await execAgentQuery(args);
|
|
2118
|
+
? await execAgentQuery(args, this.agentApplicationService, subject)
|
|
2119
|
+
: await execAgentQuery(args, undefined, subject);
|
|
2006
2120
|
}
|
|
2007
2121
|
return this.agentApplicationService
|
|
2008
2122
|
? await execAgentOptions(args, this.agentApplicationService)
|
|
@@ -2012,8 +2126,8 @@ export async function execMenuQuery(cmd, channel, channelId, userId, args, expli
|
|
|
2012
2126
|
if (!selfAid)
|
|
2013
2127
|
return { error: '当前 channel 无绑定 agent', code: 'FORBIDDEN' };
|
|
2014
2128
|
return this.agentApplicationService
|
|
2015
|
-
? await execAgentQuery({ ...(args ?? {}), aid: selfAid }, this.agentApplicationService)
|
|
2016
|
-
: await execAgentQuery({ ...(args ?? {}), aid: selfAid });
|
|
2129
|
+
? await execAgentQuery({ ...(args ?? {}), aid: selfAid }, this.agentApplicationService, subject)
|
|
2130
|
+
: await execAgentQuery({ ...(args ?? {}), aid: selfAid }, undefined, subject);
|
|
2017
2131
|
}
|
|
2018
2132
|
// ── /gateway 查询(只读,列出全部作用域的网关配置;apiKey 已掩码) ──
|
|
2019
2133
|
if (cmdBase === '/gateway') {
|
|
@@ -2093,6 +2207,9 @@ export async function execMenuQuery(cmd, channel, channelId, userId, args, expli
|
|
|
2093
2207
|
if (!session) {
|
|
2094
2208
|
return { data: { status: 'no-session' } };
|
|
2095
2209
|
}
|
|
2210
|
+
if (args?.view === 'subtasks') {
|
|
2211
|
+
return { data: { scope: 'retained-history', tasks: this.sessionManager.getRunnerTaskHistory(session.id) } };
|
|
2212
|
+
}
|
|
2096
2213
|
const sessionKey = this.getQueueKey(session, channel, channelId);
|
|
2097
2214
|
const sessionAgent = this.getAgent(channel, session.baseagent);
|
|
2098
2215
|
const isProcessing = this.messageQueue.isProcessing(sessionKey) || sessionAgent.hasActiveStream(sessionKey);
|
|
@@ -2115,6 +2232,8 @@ export async function execMenuQuery(cmd, channel, channelId, userId, args, expli
|
|
|
2115
2232
|
status: isProcessing ? 'processing' : 'idle',
|
|
2116
2233
|
createdAt: session.createdAt,
|
|
2117
2234
|
updatedAt: session.updatedAt,
|
|
2235
|
+
subtasks: this.sessionManager.getRunnerTaskHistory(session.id).length,
|
|
2236
|
+
subtasksScope: 'retained-history',
|
|
2118
2237
|
};
|
|
2119
2238
|
if (processingDuration !== undefined)
|
|
2120
2239
|
data.processingDuration = processingDuration;
|
|
@@ -2130,6 +2249,21 @@ export async function execMenuQuery(cmd, channel, channelId, userId, args, expli
|
|
|
2130
2249
|
data.lastError = { type: health.lastErrorType || 'unknown', message: health.lastError.substring(0, 100) };
|
|
2131
2250
|
return { data };
|
|
2132
2251
|
}
|
|
2252
|
+
if (cmdBase === '/baseagents') {
|
|
2253
|
+
if (args && Object.keys(args).length > 0) {
|
|
2254
|
+
return { error: 'baseagents does not accept query arguments', code: 'INVALID_ARGUMENT' };
|
|
2255
|
+
}
|
|
2256
|
+
if (fromControlChannel) {
|
|
2257
|
+
return { data: await getBaseagentSnapshot({ loaded: loadedBaseagentIds(this.agentMap.keys()) }) };
|
|
2258
|
+
}
|
|
2259
|
+
if (!evolagent)
|
|
2260
|
+
return { error: '当前 channel 无绑定 agent', code: 'FORBIDDEN' };
|
|
2261
|
+
return { data: await getBaseagentSnapshot({
|
|
2262
|
+
config: evolagent.config ?? {},
|
|
2263
|
+
loaded: this.getAvailableBaseagents(channel),
|
|
2264
|
+
probe: false,
|
|
2265
|
+
}) };
|
|
2266
|
+
}
|
|
2133
2267
|
if (cmdBase === '/topic') {
|
|
2134
2268
|
const target = (args?.target ?? '').toString().trim();
|
|
2135
2269
|
if (!target)
|
|
@@ -2137,6 +2271,9 @@ export async function execMenuQuery(cmd, channel, channelId, userId, args, expli
|
|
|
2137
2271
|
const topic = await this.sessionManager.getThreadSession(channel, channelId, target);
|
|
2138
2272
|
if (!topic)
|
|
2139
2273
|
return { error: '话题不存在', code: 'NOT_FOUND' };
|
|
2274
|
+
if (args?.view === 'subtasks') {
|
|
2275
|
+
return { data: { scope: 'retained-history', tasks: this.sessionManager.getRunnerTaskHistory(topic.id) } };
|
|
2276
|
+
}
|
|
2140
2277
|
const sessionKey = this.getQueueKey(topic, channel, channelId);
|
|
2141
2278
|
const sessionAgent = this.getAgent(channel, topic.baseagent);
|
|
2142
2279
|
const isProcessing = this.messageQueue.isProcessing(sessionKey) || sessionAgent.hasActiveStream(sessionKey);
|
|
@@ -2155,10 +2292,13 @@ export async function execMenuQuery(cmd, channel, channelId, userId, args, expli
|
|
|
2155
2292
|
const data = {
|
|
2156
2293
|
threadId: topic.threadId,
|
|
2157
2294
|
name: topic.name || null,
|
|
2295
|
+
lable: topic.lable || null,
|
|
2158
2296
|
agentSessionId: topic.agentSessionId || null,
|
|
2159
2297
|
status: isProcessing ? 'processing' : 'idle',
|
|
2160
2298
|
createdAt: topic.createdAt,
|
|
2161
2299
|
updatedAt: topic.updatedAt,
|
|
2300
|
+
subtasks: this.sessionManager.getRunnerTaskHistory(topic.id).length,
|
|
2301
|
+
subtasksScope: 'retained-history',
|
|
2162
2302
|
};
|
|
2163
2303
|
if (processingDuration !== undefined)
|
|
2164
2304
|
data.processingDuration = processingDuration;
|
|
@@ -2372,6 +2512,24 @@ export async function execMenuQuery(cmd, channel, channelId, userId, args, expli
|
|
|
2372
2512
|
}
|
|
2373
2513
|
if (cmdBase === '/system') {
|
|
2374
2514
|
const view = args?.view ?? 'status';
|
|
2515
|
+
if (view === 'version') {
|
|
2516
|
+
const data = {};
|
|
2517
|
+
try {
|
|
2518
|
+
const pkgPath = path.join(getPackageRoot(), 'package.json');
|
|
2519
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
|
|
2520
|
+
if (pkg?.version)
|
|
2521
|
+
data.version = pkg.version;
|
|
2522
|
+
}
|
|
2523
|
+
catch { }
|
|
2524
|
+
try {
|
|
2525
|
+
const fp = path.join(getPackageRoot(), 'node_modules', '@agentunion', 'fastaun', 'package.json');
|
|
2526
|
+
const fp2 = JSON.parse(fs.readFileSync(fp, 'utf-8'));
|
|
2527
|
+
if (fp2?.version)
|
|
2528
|
+
data.fastaunVersion = fp2.version;
|
|
2529
|
+
}
|
|
2530
|
+
catch { }
|
|
2531
|
+
return { data };
|
|
2532
|
+
}
|
|
2375
2533
|
if (view === 'monitor') {
|
|
2376
2534
|
if (typeof this.daemonMonitorProvider !== 'function') {
|
|
2377
2535
|
return { error: 'Daemon monitor snapshot is unavailable', code: 'DEPENDENCY_UNAVAILABLE' };
|
|
@@ -2416,24 +2574,41 @@ export async function execMenuQuery(cmd, channel, channelId, userId, args, expli
|
|
|
2416
2574
|
if (ecwebPkg?.version)
|
|
2417
2575
|
data.ecwebVersion = ecwebPkg.version;
|
|
2418
2576
|
// 收集主机级可用的所有 baseagent 类型(检测已安装的 CLI + 版本)
|
|
2419
|
-
const
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
2577
|
+
const inventory = await getBaseagentSnapshot({
|
|
2578
|
+
loaded: loadedBaseagentIds(this.agentMap.keys()),
|
|
2579
|
+
});
|
|
2580
|
+
data.baseagents = inventory.baseagents.map(item => ({
|
|
2581
|
+
...item, name: item.id,
|
|
2582
|
+
version: item.version ?? (item.detected && item.id !== 'ecagent' ? getBaseagentVersion(item.id) : null),
|
|
2583
|
+
}));
|
|
2426
2584
|
return { data };
|
|
2427
2585
|
}
|
|
2428
2586
|
// ── name=file:文件元信息(§5.1) ──
|
|
2429
2587
|
// operation 权限已在入口统一判定;项目外文件仍仅 owner 可取。
|
|
2430
2588
|
if (cmdBase === '/file') {
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
|
|
2589
|
+
let realPath;
|
|
2590
|
+
let stat;
|
|
2591
|
+
let responsePath = (args?.path ?? '').toString();
|
|
2592
|
+
let dataRoot;
|
|
2593
|
+
if (args?.rootId !== undefined) {
|
|
2594
|
+
try {
|
|
2595
|
+
const resolved = dataRootResolver.resolvePath(args.rootId, subject, args?.path, 'file');
|
|
2596
|
+
({ realPath, stat } = resolved);
|
|
2597
|
+
responsePath = resolved.relativePath;
|
|
2598
|
+
dataRoot = resolved;
|
|
2599
|
+
}
|
|
2600
|
+
catch (error) {
|
|
2601
|
+
return dataRootFailure(error);
|
|
2602
|
+
}
|
|
2603
|
+
}
|
|
2604
|
+
else {
|
|
2605
|
+
const role = identity.role;
|
|
2606
|
+
const projectPath = session?.projectPath ?? evolagent?.projectPath ?? this.getOwningAgent?.(channel)?.projectPath;
|
|
2607
|
+
const resolved = resolveMenuFilePath(args?.path, projectPath, role, 'file');
|
|
2608
|
+
if ('error' in resolved)
|
|
2609
|
+
return resolved;
|
|
2610
|
+
({ realPath, stat } = resolved);
|
|
2611
|
+
}
|
|
2437
2612
|
// sha256 仅对 ≤ 2 MB 文件计算,超过返回 null,客户端降级到 size+mtime(§4、§7 决策 2)
|
|
2438
2613
|
let sha256 = null;
|
|
2439
2614
|
if (stat.size <= FILE_HASH_MAX_SIZE) {
|
|
@@ -2446,7 +2621,16 @@ export async function execMenuQuery(cmd, channel, channelId, userId, args, expli
|
|
|
2446
2621
|
}
|
|
2447
2622
|
return {
|
|
2448
2623
|
data: {
|
|
2449
|
-
path:
|
|
2624
|
+
path: responsePath,
|
|
2625
|
+
...(dataRoot ? {
|
|
2626
|
+
root: {
|
|
2627
|
+
scope: dataRoot.scope,
|
|
2628
|
+
targetAid: dataRoot.targetAid,
|
|
2629
|
+
...(dataRoot.peerAid ? { peerAid: dataRoot.peerAid } : {}),
|
|
2630
|
+
...(dataRoot.peerKey ? { peerKey: dataRoot.peerKey } : {}),
|
|
2631
|
+
expiresAt: dataRoot.expiresAt,
|
|
2632
|
+
},
|
|
2633
|
+
} : {}),
|
|
2450
2634
|
sha256,
|
|
2451
2635
|
size: stat.size,
|
|
2452
2636
|
mtime: stat.mtimeMs,
|
|
@@ -2475,17 +2659,12 @@ export async function execMenuUpdate(cmd, value, channel, channelId, userId, ove
|
|
|
2475
2659
|
const loadedContext = await this.loadMenuContext(channel, channelId);
|
|
2476
2660
|
let session = loadedContext.session;
|
|
2477
2661
|
const evolagent = loadedContext.evolagent;
|
|
2478
|
-
if (transportReplyContext?.threadId
|
|
2662
|
+
if (transportReplyContext?.threadId) {
|
|
2479
2663
|
try {
|
|
2480
|
-
|
|
2481
|
-
if (!topicSession) {
|
|
2482
|
-
return { error: '话题会话不存在,拒绝回退到主会话', code: 'TOPIC_SESSION_NOT_FOUND' };
|
|
2483
|
-
}
|
|
2484
|
-
session = topicSession;
|
|
2664
|
+
session = await loadMenuTopicSession(this.sessionManager, channel, channelId, transportReplyContext.threadId);
|
|
2485
2665
|
}
|
|
2486
2666
|
catch (error) {
|
|
2487
|
-
|
|
2488
|
-
return { error: '无法读取话题会话,拒绝回退到主会话', code: 'TOPIC_SESSION_LOOKUP_FAILED' };
|
|
2667
|
+
return menuResultFailure(error);
|
|
2489
2668
|
}
|
|
2490
2669
|
}
|
|
2491
2670
|
if (cmdBase === '/baseagent-models')
|
|
@@ -2764,7 +2943,7 @@ export async function execMenuUpdate(cmd, value, channel, channelId, userId, ove
|
|
|
2764
2943
|
if ('error' in target)
|
|
2765
2944
|
return target;
|
|
2766
2945
|
const agent = this.getAgent(channel, target.baseagent);
|
|
2767
|
-
|
|
2946
|
+
const requestedModel = arg;
|
|
2768
2947
|
if (hasModelSwitcher(agent)) {
|
|
2769
2948
|
const catalog = await getCatalog(target.sel.self, target.baseagent);
|
|
2770
2949
|
assertCatalogSelectable(catalog);
|
|
@@ -2772,20 +2951,22 @@ export async function execMenuUpdate(cmd, value, channel, channelId, userId, ove
|
|
|
2772
2951
|
const decision = validateModelSelectionForRole({
|
|
2773
2952
|
role: identity.role,
|
|
2774
2953
|
baseagent: target.baseagent,
|
|
2775
|
-
requestedModel
|
|
2954
|
+
requestedModel,
|
|
2776
2955
|
models,
|
|
2777
2956
|
selfAid: target.sel.self,
|
|
2778
2957
|
resolveModelId: model => resolveCatalogModel(catalog, model),
|
|
2779
2958
|
});
|
|
2780
2959
|
if (!decision.ok)
|
|
2781
2960
|
return { error: decision.message || `invalid model: ${arg}`, code: decision.code || 'INVALID_VALUE' };
|
|
2782
|
-
|
|
2783
|
-
|
|
2961
|
+
// Validate the resolved ID, but persist/echo the selected value so aliases
|
|
2962
|
+
// such as `sonnet` keep following the catalog instead of pinning a version.
|
|
2963
|
+
const resolvedModel = decision.model || requestedModel;
|
|
2964
|
+
if (models.length && !models.includes(resolvedModel)) {
|
|
2784
2965
|
return { error: `无效模型: ${arg}`, code: 'INVALID_VALUE' };
|
|
2785
2966
|
}
|
|
2786
2967
|
}
|
|
2787
2968
|
try {
|
|
2788
|
-
writeMenuModel(target,
|
|
2969
|
+
writeMenuModel(target, requestedModel);
|
|
2789
2970
|
}
|
|
2790
2971
|
catch (e) {
|
|
2791
2972
|
return { error: e?.message || String(e), code: e?.code || 'CONFIG_WRITE_FAILED', ...(e?.data !== undefined ? { data: e.data } : {}) };
|
|
@@ -2795,12 +2976,12 @@ export async function execMenuUpdate(cmd, value, channel, channelId, userId, ove
|
|
|
2795
2976
|
sessionId: session?.id,
|
|
2796
2977
|
agentName: evolagent?.name,
|
|
2797
2978
|
baseagent: target.baseagent,
|
|
2798
|
-
model:
|
|
2979
|
+
model: requestedModel,
|
|
2799
2980
|
timestamp: Date.now(),
|
|
2800
2981
|
});
|
|
2801
2982
|
return {
|
|
2802
2983
|
data: {
|
|
2803
|
-
model:
|
|
2984
|
+
model: requestedModel,
|
|
2804
2985
|
baseagent: target.baseagent,
|
|
2805
2986
|
scope: target.scope,
|
|
2806
2987
|
field: target.fieldPath,
|
|
@@ -3024,6 +3205,15 @@ export async function execMenuAction(cmd, action, args, channel, channelId, user
|
|
|
3024
3205
|
fromControlChannel,
|
|
3025
3206
|
subject: authSubject,
|
|
3026
3207
|
});
|
|
3208
|
+
const storageRequest = { type: 'menu.action', cmd, action, args };
|
|
3209
|
+
if (isAgentStorageMenuRequest(storageRequest)) {
|
|
3210
|
+
try {
|
|
3211
|
+
return { data: await agentStorageMenuService.execute(storageRequest, subject, this.agentStorageClientResolver) };
|
|
3212
|
+
}
|
|
3213
|
+
catch (error) {
|
|
3214
|
+
return dataRootFailure(error);
|
|
3215
|
+
}
|
|
3216
|
+
}
|
|
3027
3217
|
if (cmdBase === '/system' && fromControlChannel && !subject.isDaemonOwner && subject.processRole !== 'daemon-service') {
|
|
3028
3218
|
return {
|
|
3029
3219
|
error: formatAuthorizationDenial('This daemon-level operation requires DaemonOwner.', 'DAEMON_OWNER_REQUIRED'),
|
|
@@ -3103,7 +3293,7 @@ export async function execMenuAction(cmd, action, args, channel, channelId, user
|
|
|
3103
3293
|
}
|
|
3104
3294
|
const isMenuAdmin = isManagementRole(authIdentity.role);
|
|
3105
3295
|
// ── /aid avatar ──
|
|
3106
|
-
//
|
|
3296
|
+
// Canonical avatar action; legacy agent/update patch.avatar reuses the same uploader.
|
|
3107
3297
|
if (cmdBase === '/aid') {
|
|
3108
3298
|
if (action !== 'avatar')
|
|
3109
3299
|
return { error: `不支持 aid action: ${action}`, code: 'NOT_SUPPORTED' };
|
|
@@ -3245,7 +3435,7 @@ export async function execMenuAction(cmd, action, args, channel, channelId, user
|
|
|
3245
3435
|
}
|
|
3246
3436
|
}
|
|
3247
3437
|
}
|
|
3248
|
-
return await execAgentAction(action, a, userId ?? '', this.eventBus, this.agentApplicationService);
|
|
3438
|
+
return await execAgentAction(action, a, userId ?? '', this.eventBus, this.agentApplicationService, subject);
|
|
3249
3439
|
}
|
|
3250
3440
|
// ── 关系级 /trigger(不走 owners;复用 isAdmin + scoped 逻辑,D4 直调底层) ──
|
|
3251
3441
|
if (cmdBase === '/trigger') {
|
|
@@ -3290,6 +3480,7 @@ export async function execMenuAction(cmd, action, args, channel, channelId, user
|
|
|
3290
3480
|
targetThreadId: args.targetThreadId,
|
|
3291
3481
|
agentId: args.agentId,
|
|
3292
3482
|
model: args.model,
|
|
3483
|
+
executionTimeoutMs: args.executionTimeoutMs,
|
|
3293
3484
|
effort: args.effort,
|
|
3294
3485
|
permissionMode: args.permissionMode,
|
|
3295
3486
|
triggerThread,
|
|
@@ -3427,6 +3618,16 @@ export async function execMenuAction(cmd, action, args, channel, channelId, user
|
|
|
3427
3618
|
return { error: `不支持的 trigger action: ${action}`, code: 'INVALID_ARGS' };
|
|
3428
3619
|
}
|
|
3429
3620
|
if (cmdBase === '/topic') {
|
|
3621
|
+
const hasLable = args && (Object.hasOwn(args, 'lable') || Object.hasOwn(args, 'label'));
|
|
3622
|
+
let lable;
|
|
3623
|
+
if (hasLable && (action === 'fork' || action === 'label')) {
|
|
3624
|
+
try {
|
|
3625
|
+
lable = normalizeTopicLable(Object.hasOwn(args, 'lable') ? args.lable : args.label);
|
|
3626
|
+
}
|
|
3627
|
+
catch (error) {
|
|
3628
|
+
return { error: error.message, code: 'INVALID_ARGS' };
|
|
3629
|
+
}
|
|
3630
|
+
}
|
|
3430
3631
|
if (action === 'fork') {
|
|
3431
3632
|
if (!this.canReadTopics(identity.role))
|
|
3432
3633
|
return { error: '无权限创建话题分支', code: 'FORBIDDEN' };
|
|
@@ -3503,21 +3704,24 @@ export async function execMenuAction(cmd, action, args, channel, channelId, user
|
|
|
3503
3704
|
const executeFork = async (current, currentAgent) => {
|
|
3504
3705
|
const messages = await currentAgent.getSessionMessages(current.agentSessionId, current.projectPath);
|
|
3505
3706
|
const turns = buildSessionTurnList(messages);
|
|
3506
|
-
|
|
3707
|
+
// fork-turns resolves bubble aliases to this canonical, complete-turn
|
|
3708
|
+
// anchor. Never fall back to a runner iteration or a guessed index.
|
|
3709
|
+
const forkTurn = turns.find(turn => turn.assistantUuid === sourceAssistantMessageId);
|
|
3507
3710
|
if (!forkTurn)
|
|
3508
3711
|
return { error: '分叉点已失效,请刷新历史后重试', code: 'NOT_FOUND' };
|
|
3509
|
-
const forkedAgentSessionId = await currentAgent.forkSessionAt(current.agentSessionId, current.projectPath,
|
|
3712
|
+
const forkedAgentSessionId = await currentAgent.forkSessionAt(current.agentSessionId, current.projectPath, forkTurn.assistantUuid, name || undefined, { sessionId: current.id, permissionMode: subject.permissionMode, role: subject.role });
|
|
3510
3713
|
const topic = await this.sessionManager.createForkedThreadSession(current, forkedAgentSessionId, targetThreadId, {
|
|
3511
3714
|
name: name || undefined,
|
|
3715
|
+
...(hasLable ? { lable: lable ?? null } : {}),
|
|
3512
3716
|
creatorPeerId: userId,
|
|
3513
|
-
sourceAssistantMessageId,
|
|
3717
|
+
sourceAssistantMessageId: forkTurn.assistantUuid,
|
|
3514
3718
|
sourceTurn: forkTurn.index,
|
|
3515
3719
|
requestId,
|
|
3516
3720
|
});
|
|
3517
3721
|
await currentAgent.updateSessionMetadata?.(forkedAgentSessionId, {
|
|
3518
3722
|
evolcoreSessionId: topic.id,
|
|
3519
3723
|
sourceSessionId: current.id,
|
|
3520
|
-
sourceAssistantMessageId,
|
|
3724
|
+
sourceAssistantMessageId: forkTurn.assistantUuid,
|
|
3521
3725
|
sourceTurn: forkTurn.index,
|
|
3522
3726
|
}).catch((error) => {
|
|
3523
3727
|
logger.debug(`[MenuHandler] Topic fork metadata sync failed: ${error}`);
|
|
@@ -3528,7 +3732,7 @@ export async function execMenuAction(cmd, action, args, channel, channelId, user
|
|
|
3528
3732
|
sourceSessionId: current.id,
|
|
3529
3733
|
name: topic.name,
|
|
3530
3734
|
threadId: topic.threadId,
|
|
3531
|
-
sourceAssistantMessageId,
|
|
3735
|
+
sourceAssistantMessageId: forkTurn.assistantUuid,
|
|
3532
3736
|
sourceTurn: forkTurn.index,
|
|
3533
3737
|
});
|
|
3534
3738
|
return {
|
|
@@ -3540,7 +3744,7 @@ export async function execMenuAction(cmd, action, args, channel, channelId, user
|
|
|
3540
3744
|
threadId: topic.threadId,
|
|
3541
3745
|
sourceSessionId: current.id,
|
|
3542
3746
|
sourceThreadId: current.threadId || null,
|
|
3543
|
-
sourceAssistantMessageId,
|
|
3747
|
+
sourceAssistantMessageId: forkTurn.assistantUuid,
|
|
3544
3748
|
sourceTurn: forkTurn.index,
|
|
3545
3749
|
},
|
|
3546
3750
|
},
|
|
@@ -3629,7 +3833,7 @@ export async function execMenuAction(cmd, action, args, channel, channelId, user
|
|
|
3629
3833
|
topicForkTargetsInFlight.delete(targetKey);
|
|
3630
3834
|
}
|
|
3631
3835
|
}
|
|
3632
|
-
if (action !== 'delete' && action !== 'rename') {
|
|
3836
|
+
if (action !== 'delete' && action !== 'rename' && action !== 'label') {
|
|
3633
3837
|
return { error: `不支持的 topic action: ${action}`, code: 'NOT_SUPPORTED' };
|
|
3634
3838
|
}
|
|
3635
3839
|
const target = (args?.target ?? '').toString().trim();
|
|
@@ -3638,6 +3842,8 @@ export async function execMenuAction(cmd, action, args, channel, channelId, user
|
|
|
3638
3842
|
const renameName = action === 'rename' ? getRenameName(args) : '';
|
|
3639
3843
|
if (action === 'rename' && !renameName)
|
|
3640
3844
|
return { error: '缺少 args.name', code: 'MISSING_VALUE' };
|
|
3845
|
+
if (action === 'label' && !hasLable)
|
|
3846
|
+
return { error: '缺少 args.lable', code: 'MISSING_VALUE' };
|
|
3641
3847
|
const topic = await this.sessionManager.getThreadSession(channel, channelId, target);
|
|
3642
3848
|
if (!topic)
|
|
3643
3849
|
return { error: '话题不存在', code: 'NOT_FOUND' };
|
|
@@ -3647,7 +3853,18 @@ export async function execMenuAction(cmd, action, args, channel, channelId, user
|
|
|
3647
3853
|
? 'private'
|
|
3648
3854
|
: this.resolveMenuChatType(channel, channelId, explicitChatType);
|
|
3649
3855
|
if (!this.canDeleteTopic(identity.role, chatType, topic, userId)) {
|
|
3650
|
-
return { error: action === 'rename' ? '无权限重命名话题' : '无权限删除话题', code: 'FORBIDDEN' };
|
|
3856
|
+
return { error: action === 'label' ? '无权限修改话题分类' : action === 'rename' ? '无权限重命名话题' : '无权限删除话题', code: 'FORBIDDEN' };
|
|
3857
|
+
}
|
|
3858
|
+
if (action === 'label') {
|
|
3859
|
+
const success = await this.sessionManager.setTopicLable(topic.id, lable ?? null);
|
|
3860
|
+
if (!success)
|
|
3861
|
+
return { error: '话题不存在', code: 'NOT_FOUND' };
|
|
3862
|
+
return {
|
|
3863
|
+
data: {
|
|
3864
|
+
action: 'label', success: true,
|
|
3865
|
+
topic: { ...buildSessionPayload(topic, topic.name || ''), threadId: topic.threadId, lable: lable ?? null },
|
|
3866
|
+
},
|
|
3867
|
+
};
|
|
3651
3868
|
}
|
|
3652
3869
|
if (action === 'rename') {
|
|
3653
3870
|
const newName = renameName;
|
|
@@ -3885,6 +4102,12 @@ export async function execMenuAction(cmd, action, args, channel, channelId, user
|
|
|
3885
4102
|
this.eventBus.publish({ type: 'system:restart', channel, channelId });
|
|
3886
4103
|
return { data: { action: 'restart', success: true } };
|
|
3887
4104
|
}
|
|
4105
|
+
if (action === 'paircode') {
|
|
4106
|
+
// Keep Menu pairing on the same daemon-owned store as /pair, ECWeb, and
|
|
4107
|
+
// the local IPC endpoint. Each successful redemption consumes the code.
|
|
4108
|
+
const pair = remoteAccessService.getPairingCode(subject.actorId, Date.now(), source === 'ecweb' ? 'ecweb-local' : 'menu');
|
|
4109
|
+
return { data: { code: pair.code, expiresAt: pair.expiresAt } };
|
|
4110
|
+
}
|
|
3888
4111
|
if (action === 'check') {
|
|
3889
4112
|
const r = await this.delegateAsAction(action, '/check', channel, channelId, userId, { overrideIdentity, authSubject: subject, threadId: transportReplyContext?.threadId, chatType: transportReplyContext?.delivery?.chatType, replyContext: transportReplyContext });
|
|
3890
4113
|
// delegateAsAction 会把 structured 展开到 data 顶层,并保留 data.structured 兼容旧客户端。
|
|
@@ -4016,6 +4239,15 @@ export async function execMenuAction(cmd, action, args, channel, channelId, user
|
|
|
4016
4239
|
},
|
|
4017
4240
|
});
|
|
4018
4241
|
if (!decision.allow) {
|
|
4242
|
+
// Keep unsupported runner fields in the aggregate so the CLI can
|
|
4243
|
+
// return a per-field error while still returning supported values.
|
|
4244
|
+
if (isBatchGet
|
|
4245
|
+
&& decision.code === 'UNSUPPORTED_FIELD'
|
|
4246
|
+
&& parsed.resolvedConfigCommand?.kind === 'field'
|
|
4247
|
+
&& parsed.resolvedConfigCommand.subcommand === 'get'
|
|
4248
|
+
&& isUnsupportedBaseagentConfigField(parsed.resolvedConfigCommand.field)) {
|
|
4249
|
+
continue;
|
|
4250
|
+
}
|
|
4019
4251
|
return { error: decision.reason, code: decision.code };
|
|
4020
4252
|
}
|
|
4021
4253
|
authorizedCommands.push({ parsed, decision });
|
|
@@ -4047,26 +4279,78 @@ export async function execMenuAction(cmd, action, args, channel, channelId, user
|
|
|
4047
4279
|
// fetch 文件作为独立 result.file 消息异步发回;把请求 id 作为 correlationId 透传,
|
|
4048
4280
|
// 客户端用它把异步到达的文件消息对回这次 fetch 点击。
|
|
4049
4281
|
if (cmdBase === '/file') {
|
|
4050
|
-
const projectPath = session?.projectPath ?? evolagent?.projectPath ?? this.getOwningAgent?.(channel)?.projectPath;
|
|
4051
4282
|
if (action === 'list') {
|
|
4052
4283
|
const dirArg = (args?.path ?? '.').toString().trim() || '.';
|
|
4053
|
-
const resolved = resolveMenuFilePath(dirArg, projectPath, identity.role, 'directory');
|
|
4054
|
-
if ('error' in resolved)
|
|
4055
|
-
return resolved;
|
|
4056
4284
|
const offset = parseFileListOffset(args?.offset);
|
|
4057
4285
|
const limit = parseFileListLimit(args?.limit);
|
|
4058
4286
|
const includeHidden = args?.includeHidden === true;
|
|
4059
|
-
|
|
4287
|
+
let listed;
|
|
4288
|
+
let responsePath = dirArg;
|
|
4289
|
+
let dataRoot;
|
|
4290
|
+
if (args?.rootId !== undefined) {
|
|
4291
|
+
try {
|
|
4292
|
+
const resolved = dataRootResolver.resolvePath(args.rootId, subject, dirArg, 'directory');
|
|
4293
|
+
listed = listDirectory(resolved.realPath, {
|
|
4294
|
+
offset,
|
|
4295
|
+
limit,
|
|
4296
|
+
includeHidden,
|
|
4297
|
+
projectPath: resolved.rootPath,
|
|
4298
|
+
role: undefined,
|
|
4299
|
+
});
|
|
4300
|
+
responsePath = resolved.relativePath;
|
|
4301
|
+
dataRoot = resolved;
|
|
4302
|
+
}
|
|
4303
|
+
catch (error) {
|
|
4304
|
+
return dataRootFailure(error);
|
|
4305
|
+
}
|
|
4306
|
+
}
|
|
4307
|
+
else {
|
|
4308
|
+
const projectPath = session?.projectPath ?? evolagent?.projectPath ?? this.getOwningAgent?.(channel)?.projectPath;
|
|
4309
|
+
const resolved = resolveMenuFilePath(dirArg, projectPath, identity.role, 'directory');
|
|
4310
|
+
if ('error' in resolved)
|
|
4311
|
+
return resolved;
|
|
4312
|
+
listed = listDirectory(resolved.realPath, { offset, limit, includeHidden, projectPath: resolved.projectPath, role: identity.role });
|
|
4313
|
+
}
|
|
4060
4314
|
if ('error' in listed)
|
|
4061
4315
|
return listed;
|
|
4062
|
-
return {
|
|
4316
|
+
return {
|
|
4317
|
+
data: {
|
|
4318
|
+
path: responsePath,
|
|
4319
|
+
...(dataRoot ? {
|
|
4320
|
+
root: {
|
|
4321
|
+
scope: dataRoot.scope,
|
|
4322
|
+
targetAid: dataRoot.targetAid,
|
|
4323
|
+
...(dataRoot.peerAid ? { peerAid: dataRoot.peerAid } : {}),
|
|
4324
|
+
...(dataRoot.peerKey ? { peerKey: dataRoot.peerKey } : {}),
|
|
4325
|
+
expiresAt: dataRoot.expiresAt,
|
|
4326
|
+
},
|
|
4327
|
+
} : {}),
|
|
4328
|
+
...listed.data,
|
|
4329
|
+
},
|
|
4330
|
+
};
|
|
4063
4331
|
}
|
|
4064
4332
|
if (action !== 'fetch')
|
|
4065
4333
|
return { error: `不支持的 file action: ${action}`, code: 'NOT_SUPPORTED' };
|
|
4066
|
-
|
|
4067
|
-
|
|
4068
|
-
|
|
4069
|
-
|
|
4334
|
+
let realPath;
|
|
4335
|
+
let stat;
|
|
4336
|
+
let dataRoot;
|
|
4337
|
+
if (args?.rootId !== undefined) {
|
|
4338
|
+
try {
|
|
4339
|
+
const resolved = dataRootResolver.resolvePath(args.rootId, subject, args?.path, 'file');
|
|
4340
|
+
({ realPath, stat } = resolved);
|
|
4341
|
+
dataRoot = resolved;
|
|
4342
|
+
}
|
|
4343
|
+
catch (error) {
|
|
4344
|
+
return dataRootFailure(error);
|
|
4345
|
+
}
|
|
4346
|
+
}
|
|
4347
|
+
else {
|
|
4348
|
+
const projectPath = session?.projectPath ?? evolagent?.projectPath ?? this.getOwningAgent?.(channel)?.projectPath;
|
|
4349
|
+
const resolved = resolveMenuFilePath(args?.path, projectPath, identity.role, 'file');
|
|
4350
|
+
if ('error' in resolved)
|
|
4351
|
+
return resolved;
|
|
4352
|
+
({ realPath, stat } = resolved);
|
|
4353
|
+
}
|
|
4070
4354
|
if (stat.size > FILE_FETCH_MAX_SIZE) {
|
|
4071
4355
|
return { error: `文件过大: ${(stat.size / 1024 / 1024).toFixed(1)} MB (限制 ${FILE_FETCH_MAX_SIZE / 1024 / 1024} MB)`, code: 'FILE_TOO_LARGE' };
|
|
4072
4356
|
}
|
|
@@ -4087,15 +4371,21 @@ export async function execMenuAction(cmd, action, args, channel, channelId, user
|
|
|
4087
4371
|
}
|
|
4088
4372
|
: sessionReplyContext;
|
|
4089
4373
|
const sendFile = async () => {
|
|
4090
|
-
await adapter.send(buildEnvelope({ channel: adapter.channelName, channelId, replyContext: replyCtx }), { kind: 'result.file', filePath: realPath, correlationId: requestId });
|
|
4374
|
+
const receipt = await adapter.send(buildEnvelope({ channel: adapter.channelName, channelId, replyContext: replyCtx }), { kind: 'result.file', filePath: realPath, correlationId: requestId });
|
|
4375
|
+
if (receipt && typeof receipt === 'object' && receipt.status === 'failed') {
|
|
4376
|
+
throw Object.assign(new Error(receipt.error || '文件发送失败'), {
|
|
4377
|
+
code: receipt.code ?? 'EXEC_FAILED',
|
|
4378
|
+
outboxId: receipt.outboxId,
|
|
4379
|
+
});
|
|
4380
|
+
}
|
|
4091
4381
|
};
|
|
4092
4382
|
if (deferSideEffect) {
|
|
4093
4383
|
deferSideEffect(sendFile);
|
|
4094
|
-
return { data: { accepted: true } };
|
|
4384
|
+
return { data: { accepted: true, ...(dataRoot ? { scope: dataRoot.scope, expiresAt: dataRoot.expiresAt } : {}) } };
|
|
4095
4385
|
}
|
|
4096
4386
|
try {
|
|
4097
4387
|
await sendFile();
|
|
4098
|
-
return { data: { accepted: true } };
|
|
4388
|
+
return { data: { accepted: true, ...(dataRoot ? { scope: dataRoot.scope, expiresAt: dataRoot.expiresAt } : {}) } };
|
|
4099
4389
|
}
|
|
4100
4390
|
catch (e) {
|
|
4101
4391
|
return { error: `文件发送失败: ${e?.message ?? e}`, code: 'EXEC_FAILED' };
|
|
@@ -4152,8 +4442,8 @@ function menuCommandToken(cmd) {
|
|
|
4152
4442
|
}
|
|
4153
4443
|
function isProcessLevelMenu(name, cmd) {
|
|
4154
4444
|
const cmdToken = menuCommandToken(cmd);
|
|
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';
|
|
4445
|
+
return name === 'system' || name === 'agent' || name === 'gateway' || name === 'config' || name === 'baseagent-models' || name === 'baseagents'
|
|
4446
|
+
|| cmdToken === '/system' || cmdToken === '/agent' || cmdToken === '/gateway' || cmdToken === '/config' || cmdToken === '/baseagent-models' || cmdToken === '/baseagents';
|
|
4157
4447
|
}
|
|
4158
4448
|
function isProcessContextMenu(payload) {
|
|
4159
4449
|
const isCatalog = payload?.type === 'menu.list' && payload?.args?.view === 'catalog';
|
|
@@ -4173,8 +4463,15 @@ async function execMenuForSystemControl(payload, context) {
|
|
|
4173
4463
|
const isFileFetch = (payload?.type === 'menu.query'
|
|
4174
4464
|
|| (payload?.type === 'menu.action' && payload?.action === 'fetch'))
|
|
4175
4465
|
&& (name === 'file' || compatCmd === '/file');
|
|
4176
|
-
|
|
4177
|
-
|
|
4466
|
+
const isDataRootQuery = payload?.type === 'menu.query'
|
|
4467
|
+
&& (name === 'agent-data-root'
|
|
4468
|
+
|| name === 'relation-data-root'
|
|
4469
|
+
|| compatCmd === '/agent-data-root'
|
|
4470
|
+
|| compatCmd === '/relation-data-root');
|
|
4471
|
+
const isRootedFile = (name === 'file' || compatCmd === '/file')
|
|
4472
|
+
&& typeof payload?.args?.rootId === 'string';
|
|
4473
|
+
if (isFileFetch || isDataRootQuery || isRootedFile || isAgentStorageMenuRequest(payload ?? {})) {
|
|
4474
|
+
return ecwebErr(id, name, 'NOT_SUPPORTED', 'Host/control 不支持数据根文件访问,请通过目标 Agent AID 请求');
|
|
4178
4475
|
}
|
|
4179
4476
|
if (name === 'cli' || compatCmd === '/cli') {
|
|
4180
4477
|
const message = context.source === 'ecweb'
|
|
@@ -4216,11 +4513,18 @@ async function execMenuForSystemControl(payload, context) {
|
|
|
4216
4513
|
conversationId: context.actorAid || SYSTEM_CONTROL_CHANNEL,
|
|
4217
4514
|
processOwners: context.owners,
|
|
4218
4515
|
fromControlChannel: true,
|
|
4516
|
+
ecwebManagement: context.source === 'ecweb',
|
|
4219
4517
|
canApprovePersistentFullAccess: context.canApprovePersistentFullAccess,
|
|
4220
4518
|
trustedProcessRole: context.source === 'ecweb' ? 'daemon-owner' : undefined,
|
|
4221
4519
|
});
|
|
4222
4520
|
const trustedIdentity = trustedSubject.identity;
|
|
4223
4521
|
const cmd = name ? (menuCommandForName(name) ?? payload.cmd) : payload.cmd;
|
|
4522
|
+
if (payload?.type === 'menu.action'
|
|
4523
|
+
&& menuCommandToken(cmd) === '/system'
|
|
4524
|
+
&& payload.action === 'paircode'
|
|
4525
|
+
&& (context.authMethod !== 'daemon-owner' || (context.source === 'control' && !context.encrypted))) {
|
|
4526
|
+
return ecwebErr(id, name, 'NOT_ALLOWED', 'Pairing code issuance requires encrypted DaemonOwner authentication');
|
|
4527
|
+
}
|
|
4224
4528
|
try {
|
|
4225
4529
|
if (menuCommandToken(cmd) === '/baseagent-models') {
|
|
4226
4530
|
if (payload.type !== 'menu.query')
|
|
@@ -4239,6 +4543,8 @@ async function execMenuForSystemControl(payload, context) {
|
|
|
4239
4543
|
channelScope: 'control',
|
|
4240
4544
|
channelId: targetChannel,
|
|
4241
4545
|
request: payload.args.context,
|
|
4546
|
+
paircodeAllowed: context.authMethod === 'daemon-owner'
|
|
4547
|
+
&& (context.source !== 'control' || context.encrypted),
|
|
4242
4548
|
});
|
|
4243
4549
|
return menuSuccess({ id, ...(name ? { name } : {}) }, data);
|
|
4244
4550
|
}
|
|
@@ -4343,15 +4649,6 @@ export async function execMenuForEcweb(payload, trusted) {
|
|
|
4343
4649
|
const validationError = validateMenuRequest(payload ?? {});
|
|
4344
4650
|
if (validationError)
|
|
4345
4651
|
return menuFailure({ id, ...(name ? { name } : {}) }, validationError);
|
|
4346
|
-
const isProcessLevel = isProcessLevelMenu(name, payload?.cmd) || isProcessContextMenu(payload);
|
|
4347
|
-
const owners = loadDaemonConfig().owners ?? [];
|
|
4348
|
-
if (isProcessLevel && owners.length === 0) {
|
|
4349
|
-
return menuFailure({ id, ...(name ? { name } : {}) }, {
|
|
4350
|
-
code: 'NOT_ALLOWED',
|
|
4351
|
-
message: formatAuthorizationDenial('Configure daemon.json.owners before using process-level operations.', 'DAEMON_OWNER_REQUIRED'),
|
|
4352
|
-
data: authorizationDenialData('DAEMON_OWNER_REQUIRED'),
|
|
4353
|
-
});
|
|
4354
|
-
}
|
|
4355
4652
|
if (!trusted || (!trusted.localDirect && trusted.authenticated !== true)) {
|
|
4356
4653
|
return menuFailure({ id, ...(name ? { name } : {}) }, {
|
|
4357
4654
|
code: 'PERMISSION_DENIED',
|
|
@@ -4359,29 +4656,22 @@ export async function execMenuForEcweb(payload, trusted) {
|
|
|
4359
4656
|
data: { $schema_version: 1, kind: 'role_permission_denied', self: payload?.args?.self ?? null },
|
|
4360
4657
|
});
|
|
4361
4658
|
}
|
|
4362
|
-
//
|
|
4363
|
-
//
|
|
4364
|
-
//
|
|
4365
|
-
|
|
4366
|
-
// without requiring owner-bound pairing metadata.
|
|
4659
|
+
// ECWeb authentication grants daemon-owner authority independently of the
|
|
4660
|
+
// configured owners. Prefer an owner for audit/Trigger attribution when
|
|
4661
|
+
// available; otherwise keep local and remote management actors distinct.
|
|
4662
|
+
const owners = loadDaemonConfig().owners ?? [];
|
|
4367
4663
|
const userId = (trusted.actorAid && owners.includes(trusted.actorAid))
|
|
4368
4664
|
? trusted.actorAid
|
|
4369
|
-
: owners[0] ?? (trusted.localDirect ? 'local-direct' :
|
|
4370
|
-
if (!userId) {
|
|
4371
|
-
return menuFailure({ id, ...(name ? { name } : {}) }, {
|
|
4372
|
-
code: 'PERMISSION_DENIED',
|
|
4373
|
-
message: 'ECWeb operation requires configured daemon owners',
|
|
4374
|
-
data: { $schema_version: 1, kind: 'role_permission_denied', self: payload?.args?.self ?? null },
|
|
4375
|
-
});
|
|
4376
|
-
}
|
|
4665
|
+
: owners[0] ?? (trusted.localDirect ? 'local-direct' : 'ecweb-remote');
|
|
4377
4666
|
return execMenuForSystemControl.call(this, payload, {
|
|
4378
4667
|
source: 'ecweb',
|
|
4379
4668
|
actorAid: userId,
|
|
4669
|
+
authMethod: trusted.localDirect ? 'daemon-owner' : 'remote-access-token',
|
|
4670
|
+
encrypted: trusted.localDirect,
|
|
4380
4671
|
localDirect: trusted.localDirect,
|
|
4381
4672
|
// The ECWeb server/IPC control-token gate authenticates this management
|
|
4382
4673
|
// request. All authenticated ECWeb connections are equally eligible to
|
|
4383
|
-
// approve persistent fullaccess
|
|
4384
|
-
// a current configured owner.
|
|
4674
|
+
// approve persistent fullaccess without owner-bound token metadata.
|
|
4385
4675
|
canApprovePersistentFullAccess: true,
|
|
4386
4676
|
owners,
|
|
4387
4677
|
});
|
|
@@ -4415,6 +4705,8 @@ export async function execMenuForControl(payload, peerId, auth = {}) {
|
|
|
4415
4705
|
return execMenuForSystemControl.call(this, payload, {
|
|
4416
4706
|
source: 'control',
|
|
4417
4707
|
actorAid: managementActor,
|
|
4708
|
+
authMethod: authorization.allowed ? authorization.authMethod : 'daemon-owner',
|
|
4709
|
+
encrypted: auth.encrypted === true,
|
|
4418
4710
|
localDirect: false,
|
|
4419
4711
|
canApprovePersistentFullAccess: false,
|
|
4420
4712
|
owners,
|