evolcore 0.0.35 → 0.0.37
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 +42 -0
- package/README.md +6 -0
- package/bin/install-codex-managed-hooks.mjs +20 -3
- package/dist/agents/baseagent-availability.js +1 -1
- package/dist/agents/baseagent.js +88 -0
- package/dist/agents/claude-runner.js +59 -12
- package/dist/agents/codex/app-server-client.js +18 -1
- package/dist/agents/codex-runner.js +262 -21
- package/dist/agents/ecagent-runner.js +47 -11
- package/dist/agents/gemini-runner.js +36 -12
- package/dist/aun/aid/client.js +4 -3
- package/dist/aun/aid/control-aid.js +270 -8
- package/dist/aun/aid/identity.js +6 -3
- package/dist/aun/aid/ownership-operation.js +58 -0
- package/dist/aun/aid/store.js +1 -0
- package/dist/aun/group/fs-download.js +67 -2
- package/dist/aun/group/history.js +20 -5
- package/dist/aun/group/operations.js +8 -2
- package/dist/aun/menu/client.js +39 -2
- package/dist/aun/menu/transport.js +1 -1
- package/dist/aun/msg/history.js +43 -4
- package/dist/aun/msg/managed-operation.js +30 -53
- package/dist/aun/msg/p2p.js +51 -15
- package/dist/aun/outbox.js +4 -0
- package/dist/channels/aun.js +260 -73
- package/dist/channels/daemon.js +6 -4
- package/dist/channels/feishu.js +3 -0
- package/dist/cli/agent-command.js +161 -223
- package/dist/cli/agent-create.js +190 -223
- package/dist/cli/agent.js +3 -1
- package/dist/cli/aid-bind-command.js +53 -0
- package/dist/cli/aun-commands.js +384 -91
- package/dist/cli/bench.js +1 -1
- package/dist/cli/cli-argv.js +74 -0
- package/dist/cli/daemon-commands.js +105 -74
- package/dist/cli/diag.js +1 -1
- package/dist/cli/fs-command.js +18 -19
- package/dist/cli/getmeta-command.js +159 -6
- package/dist/cli/index.js +6 -12
- package/dist/cli/init-channel.js +8 -55
- package/dist/cli/init.js +125 -218
- package/dist/cli/queue-command.js +11 -8
- package/dist/cli/restart-monitor.js +177 -85
- package/dist/cli/session-command.js +1 -1
- package/dist/cli/stats.js +7 -5
- package/dist/cli/thread-command.js +103 -30
- package/dist/cli/trigger-command.js +1 -1
- package/dist/cli/watch-msg-diag.js +1 -0
- package/dist/config/access-policy.js +2 -2
- package/dist/config/boot-log.js +14 -11
- package/dist/config/config-field-policy.js +9 -1
- package/dist/config/config-manager.js +176 -90
- package/dist/config/config-operation-service.js +7 -14
- package/dist/config/contact-book-store.js +142 -26
- package/dist/config/contact-book.js +4 -2
- package/dist/config/lifecycle.js +1 -1
- package/dist/config/owner-config.js +96 -0
- package/dist/config/peer-role-resolver.js +12 -18
- package/dist/config/providers-config.js +382 -0
- package/dist/config/providers-import.js +74 -0
- package/dist/config/providers-management.js +328 -0
- package/dist/config/providers.js +22 -0
- package/dist/config/resolved-config-op.js +17 -14
- package/dist/config/snapshot.js +28 -16
- package/dist/config-store.js +18 -14
- package/dist/core/agent-ipc-registration.js +21 -63
- package/dist/core/agent-runtime.js +16 -5
- package/dist/core/auth/agent-delegation.js +199 -25
- package/dist/core/auth/auth-gateway.js +44 -2
- package/dist/core/auth/authorization-denial.js +1 -1
- package/dist/core/auth/debug-context.js +2 -1
- package/dist/core/auth/delegation-authorization.js +2 -2
- package/dist/core/auth/delegation-diagnostics.js +4 -1
- package/dist/core/auth/operation-authorizer.js +10 -1
- package/dist/core/auth/operation-catalog.js +19 -1
- package/dist/core/bootstrap-service.js +43 -20
- package/dist/core/channel-loader.js +0 -1
- package/dist/core/channel-runtime.js +30 -4
- package/dist/core/command/agent-control.js +196 -37
- package/dist/core/command/cli-intent-parser.js +60 -4
- package/dist/core/command/command-handler.js +212 -60
- package/dist/core/command/connect-menu.js +1 -0
- package/dist/core/command/diag-service.js +5 -4
- package/dist/core/command/file-search.js +368 -0
- package/dist/core/command/menu-catalog.js +2 -1
- package/dist/core/command/menu-handler.js +292 -107
- package/dist/core/command/menu-protocol.js +23 -0
- package/dist/core/command/role-menu.js +7 -19
- package/dist/core/command/skill-slash.js +130 -25
- package/dist/core/command/slash-handler.js +101 -53
- package/dist/core/command/topic-slash.js +16 -5
- package/dist/core/daemon-bootstrap.js +7 -1
- package/dist/core/daemon-control.js +19 -1
- package/dist/core/daemon-lifecycle.js +2 -1
- package/dist/core/event-catalog.js +2 -0
- package/dist/core/evolagent-registry.js +13 -2
- package/dist/core/evolagent.js +3 -6
- package/dist/core/handoff/ipc-registration.js +2 -2
- package/dist/core/handoff/runtime.js +40 -6
- package/dist/core/handoff/store.js +1 -0
- package/dist/core/inference/text-inference.js +95 -6
- package/dist/core/ipc-registration.js +43 -24
- package/dist/core/memory/consolidation-context.js +4 -1
- package/dist/core/memory/consolidation.js +25 -14
- package/dist/core/message/aun-ipc-sender.js +296 -88
- package/dist/core/message/aun-request-registry.js +226 -0
- package/dist/core/message/create-status.js +33 -4
- package/dist/core/message/message-bridge.js +62 -13
- package/dist/core/message/message-log.js +0 -1
- package/dist/core/message/message-queue.js +4 -1
- package/dist/core/message/message-utils.js +8 -1
- package/dist/core/message/peer-mode.js +17 -9
- package/dist/core/message/response-engine.js +266 -124
- package/dist/core/message/retry-scheduler.js +11 -8
- package/dist/core/model/catalog-service.js +60 -18
- package/dist/core/model/model-catalog.js +112 -9
- package/dist/core/model/model-fallback.js +94 -26
- package/dist/core/ownership/owner-resolver.js +687 -0
- package/dist/core/permission/approval-gateway.js +196 -62
- package/dist/core/permission/ec-command-parser.js +138 -25
- package/dist/core/permission/filesystem-approval-scope.js +141 -0
- package/dist/core/permission/protected-paths.js +14 -1
- package/dist/core/permission/sandbox-runtime.js +171 -18
- package/dist/core/permission/tool-policy.js +13 -2
- package/dist/core/role/runtime-policy.js +3 -4
- package/dist/core/session/session-idle-monitor.js +10 -8
- package/dist/core/session/session-manager.js +72 -45
- package/dist/core/session/session-mapper.js +18 -7
- package/dist/core/session/session-renew.js +5 -4
- package/dist/core/session/session-title.js +2 -1
- package/dist/core/session/session-turn-coordinator.js +4 -2
- package/dist/core/skill/index-generator.js +2 -16
- package/dist/core/task/task-recovery-service.js +15 -2
- package/dist/core/thread/command-parser.js +28 -13
- package/dist/core/thread/index.js +1 -0
- package/dist/core/thread/ipc-operation.js +11 -8
- package/dist/core/thread/thread-operation.js +79 -44
- package/dist/core/thread/thread-route.js +54 -6
- package/dist/core/thread/thread-service.js +236 -45
- package/dist/core/thread/topic-id.js +248 -0
- package/dist/eck/kit-renderer.js +1 -1
- package/dist/eck/message-renderer.js +12 -2
- package/dist/index.js +50 -27
- package/dist/ipc.js +59 -6
- package/dist/paths.js +1 -0
- package/dist/product.js +1 -0
- package/dist/response-system/engines/v1/proactive-flow.js +10 -3
- package/dist/stats/db.js +8 -0
- package/dist/stats/writer.js +26 -4
- package/dist/trigger/authorization.js +48 -3
- package/dist/trigger/feedback.js +28 -10
- package/dist/trigger/ipc-service.js +86 -10
- package/dist/trigger/local-files.js +55 -23
- package/dist/trigger/scheduler.js +57 -11
- package/dist/trigger/system-definitions.js +1 -0
- package/dist/trigger/validation.js +44 -15
- package/dist/utils/aid-bind.js +17 -297
- package/dist/utils/avatar-upload.js +16 -4
- package/dist/utils/cross-platform.js +115 -36
- package/dist/utils/ecweb-supervisor.js +1 -1
- package/dist/utils/ecweb-utils.js +8 -1
- package/dist/utils/error-utils.js +44 -6
- package/dist/utils/instance-registry.js +12 -2
- package/dist/utils/npm-ops.js +102 -27
- package/dist/utils/package-version-info.js +72 -27
- package/dist/utils/restart-safety.js +50 -0
- package/dist/utils/upgrade-jobs.js +304 -0
- package/dist/utils/windows-autostart.js +17 -10
- package/kits/docs/INDEX.md +5 -0
- package/kits/docs/aun/ABILITY_CALLING.md +239 -0
- package/kits/docs/evolcore/INDEX.md +14 -3
- package/kits/docs/evolcore/agent-create.md +55 -141
- package/kits/docs/evolcore/agent.md +38 -16
- package/kits/docs/evolcore/aid.md +34 -0
- package/kits/docs/evolcore/config.md +20 -1
- package/kits/docs/evolcore/event.md +2 -2
- package/kits/docs/evolcore/fs.md +18 -2
- package/kits/docs/evolcore/group-collaboration.md +13 -246
- package/kits/docs/evolcore/group.md +2 -1
- package/kits/docs/evolcore/memory-consolidation-batches.md +1 -1
- package/kits/docs/evolcore/menu-protocol.md +65 -1
- package/kits/docs/evolcore/msg.md +64 -2
- package/kits/docs/evolcore/skill.md +86 -0
- package/kits/docs/evolcore/stats.md +6 -0
- package/kits/docs/evolcore/task.md +2 -0
- package/kits/docs/evolcore/thread-chain.md +145 -51
- package/kits/docs/evolcore/trigger.md +48 -3
- package/kits/eck_manifest.json +21 -0
- package/kits/eck_message_manifest.json +4 -4
- package/kits/migrations/migrate-role-config-v5.mjs +20 -7
- package/kits/rules/01-overview.md +2 -1
- package/kits/rules/03-identity.md +3 -1
- package/kits/rules/04-relation.md +1 -1
- package/kits/rules/07-memory-and-thread.md +1 -1
- package/kits/schemas/_meta.json +16 -1
- package/kits/schemas/agent-config.schema.13.json +44 -2
- package/kits/schemas/contact-book.schema.3.json +5 -0
- package/kits/schemas/daemon.schema.6.json +3 -1
- package/kits/schemas/daemon.schema.8.json +329 -0
- package/kits/schemas/defaults.schema.5.json +39 -3
- package/kits/schemas/providers.schema.1.json +76 -0
- package/kits/schemas/relation-config.schema.9.json +5 -1
- package/kits/skills/.switches.json +28 -0
- package/kits/skills/aid-ability/SKILL.md +214 -0
- package/kits/skills/ec-agent-create/SKILL.md +162 -0
- package/kits/skills/ec-group/SKILL.md +2 -1
- package/kits/skills/ec-group-collaboration/SKILL.md +228 -0
- package/kits/skills/ec-identity/SKILL.md +143 -0
- package/kits/skills/ec-skill/scripts/cli.mjs +36 -3
- package/kits/skills/ec-skill/scripts/skill.mjs +202 -15
- package/kits/skills/ec-trigger/SKILL.md +167 -0
- package/kits/skills/ec-trigger/references/examples.md +153 -0
- package/kits/skills/thread-ops/SKILL.md +108 -420
- package/kits/templates/message-fragments/thread-context.md +1 -2
- package/kits/templates/message-fragments/trigger-thread-check.md +1 -1
- package/kits/templates/roles/member.json +1 -1
- package/kits/templates/roles/visitor.json +1 -0
- package/kits/templates/system-fragments/bootstrap.md +1 -0
- package/kits/templates/system-fragments/channel.md +2 -1
- package/kits/templates/system-fragments/commands.md +5 -4
- package/kits/templates/system-fragments/identity.md +2 -0
- package/kits/templates/system-fragments/session.md +1 -1
- package/kits/templates/system-fragments/venue.md +1 -1
- package/package.json +4 -3
|
@@ -7,19 +7,23 @@ import { constrainResolvedModelForRole, filterModelsForRole, validateModelSelect
|
|
|
7
7
|
import { hasModelSwitcher } from '../../agents/runner-types.js';
|
|
8
8
|
import { resolvePaths, getPackageRoot, daemonControlDir } from '../../paths.js';
|
|
9
9
|
import { buildEnvelope, isDeliveryTarget, isDeliveryTargetForChannel, replyContextFromSession } from '../message/message-utils.js';
|
|
10
|
+
import { isMainSession, isTopicSession, sessionLocalTopicId, sessionTopicThreadId } from '../thread/topic-id.js';
|
|
10
11
|
import path from 'path';
|
|
11
12
|
import fs from 'fs';
|
|
13
|
+
import { parseFileSearchArgs, searchProjectFiles } from './file-search.js';
|
|
12
14
|
import os from 'os';
|
|
13
15
|
import crypto from 'crypto';
|
|
14
16
|
import { CronExpressionParser } from 'cron-parser';
|
|
15
17
|
import { parseDuration } from '../../trigger/parser.js';
|
|
16
|
-
import {
|
|
18
|
+
import { resolveGlobalPkg } from '../../utils/npm-ops.js';
|
|
17
19
|
import { refreshManagedPackageVersions, VERSION_INFO_PACKAGES } from '../../utils/package-version-info.js';
|
|
18
|
-
import {
|
|
20
|
+
import { resolveUpgradeDistTag } from '../../utils/package-version-info.js';
|
|
21
|
+
import { readUpgradeJob, startUpgradeJob, structuredUpgradeCheck, upgradeJobView } from '../../utils/upgrade-jobs.js';
|
|
22
|
+
import { launchMonitorAndConfirm, resolveCliEntry, isProcessRunning } from '../../utils/cross-platform.js';
|
|
19
23
|
import { cachedCliVersion } from '../../utils/cross-platform.js';
|
|
20
24
|
import { scanInstances } from '../../utils/instance-registry.js';
|
|
21
25
|
import { loadDefaults, loadDaemonConfig } from '../../config-store.js';
|
|
22
|
-
import { WEB_PACKAGE_NAME } from '../../product.js';
|
|
26
|
+
import { WEB_PACKAGE_NAME, RESTART_REQUEST_ID_ENV, RUNTIME_HOME_ENV, SESSION_ID_ENV } from '../../product.js';
|
|
23
27
|
import { read as cfgRead, resolveEffective, resolveEffectiveFieldWithSource, routeFieldPath, write as cfgWrite, ConfigTarget, } from '../../config/config-manager.js';
|
|
24
28
|
import { execAgentAction, execAgentQuery, execAgentOptions, execAidAvatar, resolveProjectPath } from './agent-control.js';
|
|
25
29
|
import { gatewayList, gatewayUpdate, gatewayDelete, gatewayTest, gatewayModels, gatewaySetPrice, gatewaySyncEnv } from '../../config/gateway-config.js';
|
|
@@ -29,18 +33,20 @@ import { buildForkHistorySnapshot, pageForkHistory } from '../session/session-ma
|
|
|
29
33
|
import { isCapabilityType, listCapabilityOptions, queryCapabilityTypes, resolveCapabilityContext, updateCapabilityPolicy, } from '../capability/capability-manager.js';
|
|
30
34
|
import { normalizeCliArgv, parseCliIntent, parseLegacyCliCommand, validateCliArgv, withDefaultRelationContext } from './cli-intent-parser.js';
|
|
31
35
|
import { auditCommandAuthorization, auditFullAccessEvent, hashArgv } from '../auth/authorization-audit.js';
|
|
32
|
-
import { authorizeOperation, buildAuthSubject } from '../auth/auth-gateway.js';
|
|
36
|
+
import { authorizeOperation, authorizeFreshOperation, buildAuthSubject } from '../auth/auth-gateway.js';
|
|
37
|
+
import { ownerResolver, getDaemonOwners, refreshOwnershipForAuthorization, OWNERSHIP_CACHE_MAX_AGE_MS } from '../ownership/owner-resolver.js';
|
|
38
|
+
import { parseOwnershipCommand, executeOwnershipCommand, ownershipFailure } from '../../aun/aid/ownership-operation.js';
|
|
33
39
|
import { authorizationDenialData, formatAuthorizationDenial } from '../auth/authorization-denial.js';
|
|
34
40
|
import { evaluateRoleOperationCapability } from '../auth/operation-authorizer.js';
|
|
35
41
|
import { isUnsupportedBaseagentConfigField } from '../../config/config-field-policy.js';
|
|
36
42
|
import { splitConfigBatchGetArgv } from '../../cli/cli-argv.js';
|
|
37
|
-
import { chatmodeFieldForPeer, resolveChatModeForField } from '../message/peer-mode.js';
|
|
43
|
+
import { chatmodeFieldForPeer, isPrivateChatmodeRoleLocked, resolveChatModeForField } from '../message/peer-mode.js';
|
|
38
44
|
import { PUBLIC_PERMISSION_MODES } from '../permission/mode.js';
|
|
39
45
|
import { resolveRuntimePermissionMode, resolveRuntimeStringField, validateRuntimeStringFieldOverride, } from '../role/runtime-policy.js';
|
|
40
46
|
import { isManagementRole } from '../../config/builtin-roles.js';
|
|
41
47
|
import { SYSTEM_CONTROL_CHANNEL } from '../system-channels.js';
|
|
42
48
|
import { logger } from '../../utils/logger.js';
|
|
43
|
-
import { shouldSuppressRealRestart } from '../../utils/restart-safety.js';
|
|
49
|
+
import { shouldSuppressRealRestart, createRestartRequest, readRestartRequest, updateRestartRequest, removeRestartRequest } from '../../utils/restart-safety.js';
|
|
44
50
|
import { AGENT_DELEGATION_TOKEN_ENV } from '../auth/agent-delegation.js';
|
|
45
51
|
import { evolMenuResponseTransportMetadata, menuFailure, menuCommandForName, menuSuccess, normalizeMenuError, validateTaskActivityQueryArgs, validateConfigWriteScope, validateMenuRequest, } from './menu-protocol.js';
|
|
46
52
|
import { parseMenuClientProtectedHeaders } from './menu-protocol.js';
|
|
@@ -57,6 +63,7 @@ import { dataRootFailure, dataRootResolver } from './data-root-resolver.js';
|
|
|
57
63
|
import { agentStorageMenuService, isAgentStorageMenuRequest } from './agent-storage-menu.js';
|
|
58
64
|
import { MAX_AUN_ATTACHMENT_SIZE } from '../../aun/msg/upload.js';
|
|
59
65
|
import { normalizeTopicLabel, normalizeTopicStatus, resolveTopicArchiveFilter, } from '../session/session-mapper.js';
|
|
66
|
+
import { notifyThreadRename } from '../thread/thread-operation.js';
|
|
60
67
|
/**
|
|
61
68
|
* 获取 baseagent CLI 的版本号(claude/gemini/codex)。
|
|
62
69
|
* 失败返回 null(命令不存在或执行失败)。
|
|
@@ -206,7 +213,7 @@ function buildSessionPayload(session, name) {
|
|
|
206
213
|
const payload = { id: session.id, name };
|
|
207
214
|
if (session.label)
|
|
208
215
|
payload.label = session.label;
|
|
209
|
-
if (session
|
|
216
|
+
if (isTopicSession(session))
|
|
210
217
|
payload.topicStatus = session.topicStatus ?? 'pending';
|
|
211
218
|
if (session.agentSessionId)
|
|
212
219
|
payload.agentSessionId = session.agentSessionId;
|
|
@@ -217,10 +224,10 @@ function buildSessionPayload(session, name) {
|
|
|
217
224
|
}
|
|
218
225
|
async function findMainSessionTarget(sessionManager, channel, channelId, target, activeSession) {
|
|
219
226
|
if (!target) {
|
|
220
|
-
return activeSession &&
|
|
227
|
+
return activeSession && isMainSession(activeSession) ? activeSession : undefined;
|
|
221
228
|
}
|
|
222
229
|
const sessions = (await sessionManager.listSessions(channel, channelId))
|
|
223
|
-
.filter((s) =>
|
|
230
|
+
.filter((s) => isMainSession(s));
|
|
224
231
|
return sessions.find((s) => s.name === target ||
|
|
225
232
|
s.id === target ||
|
|
226
233
|
(target.length >= 8 && s.id.startsWith(target)) ||
|
|
@@ -232,7 +239,7 @@ async function resolveTopicForkSource(sessionManager, channel, channelId, source
|
|
|
232
239
|
if (threadId)
|
|
233
240
|
return sessionManager.getThreadSession(channel, channelId, threadId);
|
|
234
241
|
const active = await sessionManager.getActiveSession(channel, channelId);
|
|
235
|
-
return active &&
|
|
242
|
+
return active && isMainSession(active) ? active : undefined;
|
|
236
243
|
}
|
|
237
244
|
/**
|
|
238
245
|
* 解析并校验 menu `name=file` 的目标路径(query/fetch 共用)。
|
|
@@ -392,6 +399,7 @@ const CLI_EXEC_WHITELIST = {
|
|
|
392
399
|
agent: new Set(['list', 'show', 'get']),
|
|
393
400
|
aid: new Set(['list', 'show', 'lookup']),
|
|
394
401
|
storage: new Set(['ls', 'quota']),
|
|
402
|
+
thread: new Set(['summary']),
|
|
395
403
|
};
|
|
396
404
|
function tokenizeArgv(line) {
|
|
397
405
|
const out = [];
|
|
@@ -588,9 +596,18 @@ function resolveMenuChatmodeTarget(params) {
|
|
|
588
596
|
}
|
|
589
597
|
if (!peerKey)
|
|
590
598
|
return { error: 'relation scope 需要 peer/peerKey,或可推导的当前对端', code: 'MISSING_PEER' };
|
|
591
|
-
return {
|
|
599
|
+
return {
|
|
600
|
+
scope,
|
|
601
|
+
sel: { ...sel, peerKey },
|
|
602
|
+
role: params.role,
|
|
603
|
+
roleLocked: !params.fromControlChannel && isPrivateChatmodeRoleLocked(field, params.role),
|
|
604
|
+
field,
|
|
605
|
+
fieldPath: `chatmode.${field}`,
|
|
606
|
+
};
|
|
592
607
|
}
|
|
593
608
|
function readMenuChatmode(target) {
|
|
609
|
+
if (target.roleLocked)
|
|
610
|
+
return { value: 'proactive', source: 'role' };
|
|
594
611
|
try {
|
|
595
612
|
const resolved = resolveEffectiveFieldWithSource(target.fieldPath, target.sel, { cache: true });
|
|
596
613
|
const decision = resolveRuntimeStringField({
|
|
@@ -774,7 +791,7 @@ function resolveMenuModelTarget(params) {
|
|
|
774
791
|
return { error: 'relation scope 需要 peer/peerKey,或可推导的当前对端', code: 'MISSING_PEER' };
|
|
775
792
|
return { scope, sel: { ...sel, peerKey }, role: params.role, baseagent, field: params.field, fieldPath: `baseagents.${baseagent}.${params.field}` };
|
|
776
793
|
}
|
|
777
|
-
function readMenuModel(target, agent) {
|
|
794
|
+
function readMenuModel(target, agent, agentSessionId) {
|
|
778
795
|
try {
|
|
779
796
|
const fieldPath = menuModelConfigFieldPath(target);
|
|
780
797
|
const resolved = resolveEffectiveFieldWithSource(fieldPath, target.sel, { cache: true });
|
|
@@ -1050,6 +1067,8 @@ function buildMenuIntent(verb, cmdBase, args, action, value, fromControlChannel
|
|
|
1050
1067
|
}
|
|
1051
1068
|
if (verb === 'action') {
|
|
1052
1069
|
if (cmdBase === '/file') {
|
|
1070
|
+
if (action === 'search')
|
|
1071
|
+
return intent('file.search', 'filesystem', { filePath: args?.path ?? '.' });
|
|
1053
1072
|
if (action === 'list')
|
|
1054
1073
|
return intent('file.list', 'filesystem', { filePath: args?.path });
|
|
1055
1074
|
if (action === 'fetch')
|
|
@@ -1140,7 +1159,7 @@ function buildMenuAuthSubject(owner, params) {
|
|
|
1140
1159
|
chatType,
|
|
1141
1160
|
conversationId: peerKeyId,
|
|
1142
1161
|
identity: params.identity,
|
|
1143
|
-
processOwners:
|
|
1162
|
+
processOwners: getDaemonOwners(),
|
|
1144
1163
|
fromControlChannel: params.fromControlChannel,
|
|
1145
1164
|
});
|
|
1146
1165
|
}
|
|
@@ -1173,6 +1192,14 @@ async function authorizeMenuIntent(params) {
|
|
|
1173
1192
|
if (intent.operation.startsWith('trigger.') && !subject.selfAid) {
|
|
1174
1193
|
return { error: 'Trigger operation requires a channel bound to an Agent', code: 'ROLE_ACCESS_DENIED' };
|
|
1175
1194
|
}
|
|
1195
|
+
if (intent.operation.startsWith('trigger.') && intent.args.triggerScope !== undefined) {
|
|
1196
|
+
if (intent.args.triggerScope !== 'daemon') {
|
|
1197
|
+
return { error: 'triggerScope 仅支持 daemon', code: 'INVALID_ARGUMENT' };
|
|
1198
|
+
}
|
|
1199
|
+
if (!fromControlChannel || subject.selfAid !== this.getDaemonTriggerOwnerAid?.(channel)) {
|
|
1200
|
+
return { error: '主机级 Trigger 必须通过控制 AID 管理', code: 'NOT_SUPPORTED' };
|
|
1201
|
+
}
|
|
1202
|
+
}
|
|
1176
1203
|
if (intent.scope === 'relation') {
|
|
1177
1204
|
intent.args = buildRelationIntentArgs({
|
|
1178
1205
|
args: intent.args,
|
|
@@ -1185,7 +1212,7 @@ async function authorizeMenuIntent(params) {
|
|
|
1185
1212
|
}
|
|
1186
1213
|
const source = params.source ?? 'menu';
|
|
1187
1214
|
intent.source = source;
|
|
1188
|
-
const decision = await
|
|
1215
|
+
const decision = await authorizeFreshOperation({
|
|
1189
1216
|
source,
|
|
1190
1217
|
intent,
|
|
1191
1218
|
subject,
|
|
@@ -1363,6 +1390,22 @@ function menuResultFailure(error) {
|
|
|
1363
1390
|
return { error: normalized.message, code: normalized.code, ...(normalized.data !== undefined ? { data: normalized.data } : {}) };
|
|
1364
1391
|
}
|
|
1365
1392
|
function resolveExternalMenuAgentChannel(owner, payload, fallbackChannel, required) {
|
|
1393
|
+
const requestCmd = payload?.name ? menuCommandForName(payload.name) ?? payload.cmd : payload?.cmd;
|
|
1394
|
+
const isTrigger = menuCommandToken(requestCmd) === '/trigger';
|
|
1395
|
+
if (isTrigger && payload?.args?.triggerScope !== undefined) {
|
|
1396
|
+
if (payload.args.triggerScope !== 'daemon') {
|
|
1397
|
+
return { error: 'triggerScope 仅支持 daemon', code: 'INVALID_ARGUMENT' };
|
|
1398
|
+
}
|
|
1399
|
+
if (payload.agent !== undefined
|
|
1400
|
+
|| ['aid', 'agentId', 'self', 'context'].some(key => payload.args[key] !== undefined)) {
|
|
1401
|
+
return { error: '主机级 Trigger 不接受 Agent 选择参数', code: 'INVALID_ARGUMENT' };
|
|
1402
|
+
}
|
|
1403
|
+
const channel = owner.getDaemonTriggerChannel?.() ?? 'daemon';
|
|
1404
|
+
if (!owner.getDaemonTriggerOwnerAid?.(channel)) {
|
|
1405
|
+
return { error: '主机调度器不可用', code: 'NOT_SUPPORTED' };
|
|
1406
|
+
}
|
|
1407
|
+
return { channel };
|
|
1408
|
+
}
|
|
1366
1409
|
const requestedAid = typeof payload?.agent === 'string' && payload.agent.trim()
|
|
1367
1410
|
? payload.agent.trim()
|
|
1368
1411
|
: typeof payload?.args?.aid === 'string'
|
|
@@ -1541,7 +1584,7 @@ export function getMenuItems(role, chatType = 'private', scope = 'agent', authSu
|
|
|
1541
1584
|
}
|
|
1542
1585
|
// 记忆管理(紧跟权限管理):条目与 evolhelp 同源,无参数的一点即执行,需要 slug 的弹输入框
|
|
1543
1586
|
const memoryOwnerAid = authSubject?.selfAid
|
|
1544
|
-
?
|
|
1587
|
+
? ownerResolver.primaryOwner(authSubject.selfAid)
|
|
1545
1588
|
: undefined;
|
|
1546
1589
|
const memoryEntries = isControlScope ? [] : memoryHelpEntries({
|
|
1547
1590
|
chatType: normalizedChatType,
|
|
@@ -1723,7 +1766,12 @@ export async function getSubMenuItems(cmd, channel, channelId, userId, args, ove
|
|
|
1723
1766
|
if ('error' in res)
|
|
1724
1767
|
throw { code: res.code, message: res.error };
|
|
1725
1768
|
return res.data.agents.map(ag => ({
|
|
1726
|
-
value: ag.aid,
|
|
1769
|
+
value: ag.aid,
|
|
1770
|
+
label: ag.name || ag.aid,
|
|
1771
|
+
desc: ag.status,
|
|
1772
|
+
lifecycle: ag.lifecycle ?? null,
|
|
1773
|
+
baseagent: ag.baseagent ?? null,
|
|
1774
|
+
projectPath: ag.projectPath ?? null,
|
|
1727
1775
|
}));
|
|
1728
1776
|
}
|
|
1729
1777
|
// agent channel:直接读取自身,避免先枚举全部 Agent 再过滤。
|
|
@@ -1737,7 +1785,12 @@ export async function getSubMenuItems(cmd, channel, channelId, userId, args, ove
|
|
|
1737
1785
|
throw { code: res.code, message: res.error };
|
|
1738
1786
|
const ag = res.data;
|
|
1739
1787
|
return [{
|
|
1740
|
-
value: ag.aid,
|
|
1788
|
+
value: ag.aid,
|
|
1789
|
+
label: ag.identity?.name || ag.name || ag.aid,
|
|
1790
|
+
desc: ag.status,
|
|
1791
|
+
lifecycle: ag.lifecycle ?? null,
|
|
1792
|
+
baseagent: ag.baseagent ?? null,
|
|
1793
|
+
projectPath: ag.projectPath ?? null,
|
|
1741
1794
|
}];
|
|
1742
1795
|
}
|
|
1743
1796
|
if (cmd === '/capability') {
|
|
@@ -1833,7 +1886,7 @@ export async function getSubMenuItems(cmd, channel, channelId, userId, args, ove
|
|
|
1833
1886
|
}
|
|
1834
1887
|
}
|
|
1835
1888
|
return sessions
|
|
1836
|
-
.filter((s) =>
|
|
1889
|
+
.filter((s) => isTopicSession(s) && (!hasLabel || s.label === label) && (!topicStatus || s.topicStatus === topicStatus))
|
|
1837
1890
|
.map((s) => this.buildTopicMenuItem(s));
|
|
1838
1891
|
}
|
|
1839
1892
|
if (cmd === '/s' || cmd === '/session' || cmd === '/del') {
|
|
@@ -1841,7 +1894,7 @@ export async function getSubMenuItems(cmd, channel, channelId, userId, args, ove
|
|
|
1841
1894
|
const active = cmd === '/del' ? await this.sessionManager.getActiveSession(channel, channelId) : null;
|
|
1842
1895
|
const currentSession = session;
|
|
1843
1896
|
const items = sessions
|
|
1844
|
-
.filter((s) =>
|
|
1897
|
+
.filter((s) => isMainSession(s))
|
|
1845
1898
|
.filter((s) => !active || s.id !== active.id)
|
|
1846
1899
|
.map((s) => {
|
|
1847
1900
|
const displayName = displaySessionTitle(s.name, s.id.slice(0, 8));
|
|
@@ -1915,7 +1968,7 @@ export async function getSubMenuItems(cmd, channel, channelId, userId, args, ove
|
|
|
1915
1968
|
const rawModels = catalog.models.map(m => m.id);
|
|
1916
1969
|
const models = filterModelsForRole(optionsIdentity.role, target.baseagent, rawModels, model => resolveCatalogModel(catalog, model), target.sel.self);
|
|
1917
1970
|
const requestedModel = menuStringArg(args, 'model') ?? menuStringArg(args, 'current');
|
|
1918
|
-
const currentModel = requestedModel || readMenuModel(target, agent).value || agent.getModel();
|
|
1971
|
+
const currentModel = requestedModel || readMenuModel(target, agent, session?.agentSessionId).value || agent.getModel();
|
|
1919
1972
|
if (models.length > 0)
|
|
1920
1973
|
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) }));
|
|
1921
1974
|
}
|
|
@@ -1982,9 +2035,9 @@ export async function getSubMenuItems(cmd, channel, channelId, userId, args, ove
|
|
|
1982
2035
|
}
|
|
1983
2036
|
const requestedModel = menuStringArg(args, 'model') ?? menuStringArg(args, 'currentModel');
|
|
1984
2037
|
const modelTarget = { ...target, field: 'model', fieldPath: `baseagents.${target.baseagent}.model` };
|
|
1985
|
-
const currentModel = requestedModel || (hasModelSwitcher(agent) ? (readMenuModel(modelTarget, agent).value || agent.getModel()) : agent.name);
|
|
2038
|
+
const currentModel = requestedModel || (hasModelSwitcher(agent) ? (readMenuModel(modelTarget, agent, session?.agentSessionId).value || agent.getModel()) : agent.name);
|
|
1986
2039
|
const efforts = await getAvailableEfforts(agent, currentModel);
|
|
1987
|
-
const currentEffort = menuStringArg(args, 'effort') ?? menuStringArg(args, 'current') ?? readMenuModel(target, agent).value ?? 'auto';
|
|
2040
|
+
const currentEffort = menuStringArg(args, 'effort') ?? menuStringArg(args, 'current') ?? readMenuModel(target, agent, session?.agentSessionId).value ?? 'auto';
|
|
1988
2041
|
const allItems = [...efforts, 'auto'];
|
|
1989
2042
|
return allItems.map(e => ({ value: e, label: e === 'auto' ? 'auto (SDK默认)' : e, selected: e === currentEffort }));
|
|
1990
2043
|
}
|
|
@@ -2506,7 +2559,7 @@ export async function execMenuQuery(cmd, channel, channelId, userId, args, expli
|
|
|
2506
2559
|
const page = pageForkHistory(snapshot, before, limit);
|
|
2507
2560
|
const forkSource = current.metadata?.forkSource;
|
|
2508
2561
|
return { data: {
|
|
2509
|
-
threadId: current
|
|
2562
|
+
threadId: sessionLocalTopicId(current) ?? '',
|
|
2510
2563
|
sourceAssistantMessageId: source.assistantMessageId,
|
|
2511
2564
|
historyVersion: 1,
|
|
2512
2565
|
historyDigest: forkSource?.historyDigest,
|
|
@@ -2538,7 +2591,7 @@ export async function execMenuQuery(cmd, channel, channelId, userId, args, expli
|
|
|
2538
2591
|
turns = this.sessionManager.getSessionFileInfo(topic.projectPath, topic.agentSessionId, topic.baseagent, topic.selfAID).turns;
|
|
2539
2592
|
}
|
|
2540
2593
|
const data = {
|
|
2541
|
-
threadId: topic
|
|
2594
|
+
threadId: sessionLocalTopicId(topic) ?? '',
|
|
2542
2595
|
name: topic.name || null,
|
|
2543
2596
|
label: topic.label || null,
|
|
2544
2597
|
topicStatus: topic.topicStatus ?? 'pending',
|
|
@@ -2594,7 +2647,7 @@ export async function execMenuQuery(cmd, channel, channelId, userId, args, expli
|
|
|
2594
2647
|
if ('error' in target)
|
|
2595
2648
|
return target;
|
|
2596
2649
|
const agent = this.getAgent(channel, target.baseagent);
|
|
2597
|
-
const current = readMenuModel(target, agent);
|
|
2650
|
+
const current = readMenuModel(target, agent, session?.agentSessionId);
|
|
2598
2651
|
return {
|
|
2599
2652
|
data: {
|
|
2600
2653
|
model: current.value,
|
|
@@ -2623,7 +2676,7 @@ export async function execMenuQuery(cmd, channel, channelId, userId, args, expli
|
|
|
2623
2676
|
if ('error' in target)
|
|
2624
2677
|
return target;
|
|
2625
2678
|
const agent = this.getAgent(channel, target.baseagent);
|
|
2626
|
-
const current = readMenuModel(target, agent);
|
|
2679
|
+
const current = readMenuModel(target, agent, session?.agentSessionId);
|
|
2627
2680
|
return {
|
|
2628
2681
|
data: {
|
|
2629
2682
|
effort: current.value,
|
|
@@ -2777,6 +2830,7 @@ export async function execMenuQuery(cmd, channel, channelId, userId, args, expli
|
|
|
2777
2830
|
data.fastaunVersion = fp2.version;
|
|
2778
2831
|
}
|
|
2779
2832
|
catch { }
|
|
2833
|
+
data.avatarCreateSupported = true;
|
|
2780
2834
|
return { data };
|
|
2781
2835
|
}
|
|
2782
2836
|
if (view === 'monitor') {
|
|
@@ -3052,7 +3106,7 @@ export async function execMenuUpdate(cmd, value, channel, channelId, userId, ove
|
|
|
3052
3106
|
const triggerScheduler = this.getTriggerSchedulerForChannel?.(channel);
|
|
3053
3107
|
if (!triggerScheduler)
|
|
3054
3108
|
return { error: '触发器功能未启用', code: 'NOT_SUPPORTED' };
|
|
3055
|
-
const updated = await this.updateTriggerFromPatch(triggerScheduler, patch.nameOrId, patch, channel, channelId, userId ?? '', isAdmin, undefined, undefined, subject.isDaemonOwner, subject.canApprovePersistentFullAccess);
|
|
3109
|
+
const updated = await this.updateTriggerFromPatch(triggerScheduler, patch.nameOrId, patch, channel, channelId, userId ?? '', isAdmin, undefined, undefined, subject.isDaemonOwner, subject.canApprovePersistentFullAccess, session?.chatType);
|
|
3056
3110
|
if (!updated.ok) {
|
|
3057
3111
|
return {
|
|
3058
3112
|
error: updated.error,
|
|
@@ -3108,8 +3162,9 @@ export async function execMenuUpdate(cmd, value, channel, channelId, userId, ove
|
|
|
3108
3162
|
return { error: `无效 baseagent: ${arg},可选: ${valid.join(' / ')}`, code: 'INVALID_VALUE' };
|
|
3109
3163
|
}
|
|
3110
3164
|
const previousBaseagent = evolagent.baseagent;
|
|
3111
|
-
|
|
3112
|
-
|
|
3165
|
+
const sessionTopicId = sessionTopicThreadId(session);
|
|
3166
|
+
if (session && sessionTopicId) {
|
|
3167
|
+
if (transportReplyContext?.threadId !== sessionTopicId) {
|
|
3113
3168
|
return { error: '话题路由上下文不匹配,拒绝切换', code: 'THREAD_CONTEXT_MISMATCH' };
|
|
3114
3169
|
}
|
|
3115
3170
|
if (typeof this.messageQueue.withSessionBarrier !== 'function') {
|
|
@@ -3119,7 +3174,7 @@ export async function execMenuUpdate(cmd, value, channel, channelId, userId, ove
|
|
|
3119
3174
|
const currentAgent = this.getAgent(channel, session.baseagent);
|
|
3120
3175
|
const outcome = await this.messageQueue.withSessionBarrier(sessionKey, async (snapshot) => {
|
|
3121
3176
|
const latest = await this.sessionManager.getSessionById?.(session.id);
|
|
3122
|
-
if (!latest || latest
|
|
3177
|
+
if (!latest || sessionTopicThreadId(latest) !== sessionTopicId) {
|
|
3123
3178
|
return { error: '话题会话不存在或归属已变化', code: 'NOT_FOUND' };
|
|
3124
3179
|
}
|
|
3125
3180
|
const busy = !!latest.processingState
|
|
@@ -3137,7 +3192,7 @@ export async function execMenuUpdate(cmd, value, channel, channelId, userId, ove
|
|
|
3137
3192
|
evolagent.setActiveBaseagent(arg);
|
|
3138
3193
|
await this.sessionManager.rotateTopicBackend({
|
|
3139
3194
|
sessionId: latest.id,
|
|
3140
|
-
expectedThreadId:
|
|
3195
|
+
expectedThreadId: sessionTopicId,
|
|
3141
3196
|
baseagent: arg,
|
|
3142
3197
|
});
|
|
3143
3198
|
return { ok: true };
|
|
@@ -3166,7 +3221,7 @@ export async function execMenuUpdate(cmd, value, channel, channelId, userId, ove
|
|
|
3166
3221
|
baseagentChanged: previousBaseagent !== arg,
|
|
3167
3222
|
scope: 'agent',
|
|
3168
3223
|
sessionId: session.id,
|
|
3169
|
-
threadId:
|
|
3224
|
+
threadId: sessionTopicId,
|
|
3170
3225
|
sessionIdChanged: false,
|
|
3171
3226
|
notice: '已切换 Baseagent: ' + previousBaseagent + ' → ' + arg + ',话题会话 ID 保持不变',
|
|
3172
3227
|
} };
|
|
@@ -3291,7 +3346,7 @@ export async function execMenuUpdate(cmd, value, channel, channelId, userId, ove
|
|
|
3291
3346
|
return target;
|
|
3292
3347
|
const agent = this.getAgent(channel, target.baseagent);
|
|
3293
3348
|
const modelTarget = { ...target, field: 'model', fieldPath: `baseagents.${target.baseagent}.model` };
|
|
3294
|
-
const currentModel = hasModelSwitcher(agent) ? (readMenuModel(modelTarget, agent).value || agent.getModel()) : agent.name;
|
|
3349
|
+
const currentModel = hasModelSwitcher(agent) ? (readMenuModel(modelTarget, agent, session?.agentSessionId).value || agent.getModel()) : agent.name;
|
|
3295
3350
|
const validEfforts = await getAvailableEfforts(agent, currentModel);
|
|
3296
3351
|
const allValid = [...validEfforts, 'auto'];
|
|
3297
3352
|
if (!allValid.includes(arg)) {
|
|
@@ -3346,6 +3401,9 @@ export async function execMenuUpdate(cmd, value, channel, channelId, userId, ove
|
|
|
3346
3401
|
});
|
|
3347
3402
|
if ('error' in target)
|
|
3348
3403
|
return target;
|
|
3404
|
+
if (arg === 'interactive' && target.roleLocked) {
|
|
3405
|
+
return { error: '私聊交互模式仅限 owner/admin,当前角色固定为 proactive', code: 'ROLE_VALUE_NOT_ALLOWED' };
|
|
3406
|
+
}
|
|
3349
3407
|
const roleDecision = validateRuntimeStringFieldOverride({
|
|
3350
3408
|
selfAid: target.sel.self,
|
|
3351
3409
|
role: target.role,
|
|
@@ -3458,6 +3516,17 @@ export async function execMenuAction(cmd, action, args, channel, channelId, user
|
|
|
3458
3516
|
return { error: '缺少命令', code: 'MISSING_CMD' };
|
|
3459
3517
|
if (!action)
|
|
3460
3518
|
return { error: '缺少 action', code: 'MISSING_VALUE' };
|
|
3519
|
+
if (cmdBase === '/file' && action === 'search') {
|
|
3520
|
+
try {
|
|
3521
|
+
parseFileSearchArgs(args);
|
|
3522
|
+
}
|
|
3523
|
+
catch (error) {
|
|
3524
|
+
return { error: error.message, code: 'INVALID_ARGUMENT' };
|
|
3525
|
+
}
|
|
3526
|
+
if (fromControlChannel || authSubject?.fromControlChannel) {
|
|
3527
|
+
return { error: 'File search must be addressed to the current Agent AID', code: 'NOT_SUPPORTED' };
|
|
3528
|
+
}
|
|
3529
|
+
}
|
|
3461
3530
|
const gated = gateControlScope.call(this, { cmdBase, action, args, channel, fromControlChannel });
|
|
3462
3531
|
if (gated)
|
|
3463
3532
|
return gated;
|
|
@@ -3943,7 +4012,7 @@ export async function execMenuAction(cmd, action, args, channel, channelId, user
|
|
|
3943
4012
|
success: true,
|
|
3944
4013
|
topic: {
|
|
3945
4014
|
...buildSessionPayload(existingTopic, existingTopic.name || ''),
|
|
3946
|
-
threadId: existingTopic
|
|
4015
|
+
threadId: sessionLocalTopicId(existingTopic) ?? '',
|
|
3947
4016
|
sourceSessionId: forkSource.sessionId,
|
|
3948
4017
|
sourceThreadId: forkSource.threadId,
|
|
3949
4018
|
sourceAssistantMessageId: forkSource.assistantMessageId,
|
|
@@ -3977,7 +4046,7 @@ export async function execMenuAction(cmd, action, args, channel, channelId, user
|
|
|
3977
4046
|
|| (this.messageQueue.getQueueLength?.(sourceKey) ?? 0) > 0
|
|
3978
4047
|
|| currentAgent.hasActiveStream(sourceKey)
|
|
3979
4048
|
|| (this.processor?.isPauseRequested?.(sourceKey) ?? false);
|
|
3980
|
-
if (
|
|
4049
|
+
if (isMainSession(source) && sourceIsBusy(source, agent)) {
|
|
3981
4050
|
return { error: '来源会话正在处理消息,请完成后重试', code: 'BUSY' };
|
|
3982
4051
|
}
|
|
3983
4052
|
const targetKey = JSON.stringify([source.channelType || source.channel, source.selfAID || '', channelId, targetThreadId]);
|
|
@@ -4023,7 +4092,7 @@ export async function execMenuAction(cmd, action, args, channel, channelId, user
|
|
|
4023
4092
|
sessionId: topic.id,
|
|
4024
4093
|
sourceSessionId: current.id,
|
|
4025
4094
|
name: topic.name,
|
|
4026
|
-
threadId: topic
|
|
4095
|
+
threadId: sessionTopicThreadId(topic),
|
|
4027
4096
|
sourceAssistantMessageId: actualSourceMessageId,
|
|
4028
4097
|
sourceTurn: forkTurn.index,
|
|
4029
4098
|
});
|
|
@@ -4033,9 +4102,9 @@ export async function execMenuAction(cmd, action, args, channel, channelId, user
|
|
|
4033
4102
|
success: true,
|
|
4034
4103
|
topic: {
|
|
4035
4104
|
...buildSessionPayload(topic, topic.name || ''),
|
|
4036
|
-
threadId: topic
|
|
4105
|
+
threadId: sessionTopicThreadId(topic) ?? '',
|
|
4037
4106
|
sourceSessionId: current.id,
|
|
4038
|
-
sourceThreadId: current
|
|
4107
|
+
sourceThreadId: sessionTopicThreadId(current) ?? null,
|
|
4039
4108
|
sourceAssistantMessageId: actualSourceMessageId,
|
|
4040
4109
|
sourceTurn: forkTurn.index,
|
|
4041
4110
|
},
|
|
@@ -4043,7 +4112,7 @@ export async function execMenuAction(cmd, action, args, channel, channelId, user
|
|
|
4043
4112
|
};
|
|
4044
4113
|
};
|
|
4045
4114
|
const runForkUnderBarrier = async () => {
|
|
4046
|
-
if (source
|
|
4115
|
+
if (isTopicSession(source)) {
|
|
4047
4116
|
if (typeof this.messageQueue.withSessionBarrier !== 'function') {
|
|
4048
4117
|
return { error: '当前运行环境不支持安全话题分叉', code: 'NOT_SUPPORTED' };
|
|
4049
4118
|
}
|
|
@@ -4053,7 +4122,7 @@ export async function execMenuAction(cmd, action, args, channel, channelId, user
|
|
|
4053
4122
|
if (!current) {
|
|
4054
4123
|
return { error: '来源会话不存在或归属已变化', code: 'NOT_FOUND' };
|
|
4055
4124
|
}
|
|
4056
|
-
if (current.id !== source.id || (current
|
|
4125
|
+
if (current.id !== source.id || sessionTopicThreadId(current) !== sessionTopicThreadId(source)) {
|
|
4057
4126
|
return { error: '来源会话不存在或归属已变化', code: 'NOT_FOUND' };
|
|
4058
4127
|
}
|
|
4059
4128
|
if (!current.agentSessionId) {
|
|
@@ -4148,6 +4217,9 @@ export async function execMenuAction(cmd, action, args, channel, channelId, user
|
|
|
4148
4217
|
const topic = await this.sessionManager.getThreadSession(channel, channelId, target);
|
|
4149
4218
|
if (!topic)
|
|
4150
4219
|
return { error: '话题不存在', code: 'NOT_FOUND' };
|
|
4220
|
+
// target 可能是主话题规范 id,getThreadSession 会解析到主会话;这些动作只针对话题。
|
|
4221
|
+
if (!isTopicSession(topic))
|
|
4222
|
+
return { error: '主会话不支持此操作,请指定话题', code: 'NOT_SUPPORTED' };
|
|
4151
4223
|
const chatType = topic.chatType === 'group'
|
|
4152
4224
|
? 'group'
|
|
4153
4225
|
: topic.chatType === 'private'
|
|
@@ -4163,7 +4235,7 @@ export async function execMenuAction(cmd, action, args, channel, channelId, user
|
|
|
4163
4235
|
return {
|
|
4164
4236
|
data: {
|
|
4165
4237
|
action: 'label', success: true,
|
|
4166
|
-
topic: { ...buildSessionPayload(topic, topic.name || ''), threadId: topic
|
|
4238
|
+
topic: { ...buildSessionPayload(topic, topic.name || ''), threadId: sessionLocalTopicId(topic) ?? '', label: label ?? null },
|
|
4167
4239
|
},
|
|
4168
4240
|
};
|
|
4169
4241
|
}
|
|
@@ -4171,7 +4243,7 @@ export async function execMenuAction(cmd, action, args, channel, channelId, user
|
|
|
4171
4243
|
const success = await this.sessionManager.setTopicStatus(topic.id, topicStatus);
|
|
4172
4244
|
if (!success)
|
|
4173
4245
|
return { error: '话题不存在', code: 'NOT_FOUND' };
|
|
4174
|
-
return { data: { action: 'status', success: true, topic: { ...buildSessionPayload(topic, topic.name || ''), threadId: topic
|
|
4246
|
+
return { data: { action: 'status', success: true, topic: { ...buildSessionPayload(topic, topic.name || ''), threadId: sessionLocalTopicId(topic) ?? '', topicStatus } } };
|
|
4175
4247
|
}
|
|
4176
4248
|
if (action === 'rename') {
|
|
4177
4249
|
const newName = renameName;
|
|
@@ -4179,7 +4251,7 @@ export async function execMenuAction(cmd, action, args, channel, channelId, user
|
|
|
4179
4251
|
if (existing && existing.id !== topic.id) {
|
|
4180
4252
|
return { error: `名称 "${newName}" 已存在`, code: 'CONFLICT' };
|
|
4181
4253
|
}
|
|
4182
|
-
const oldName = displaySessionTitle(topic.name, topic
|
|
4254
|
+
const oldName = displaySessionTitle(topic.name, sessionTopicThreadId(topic) || '(未命名)');
|
|
4183
4255
|
const success = await this.sessionManager.renameSession(topic.id, newName);
|
|
4184
4256
|
if (!success)
|
|
4185
4257
|
return { error: '重命名失败', code: 'EXEC_FAILED' };
|
|
@@ -4191,14 +4263,25 @@ export async function execMenuAction(cmd, action, args, channel, channelId, user
|
|
|
4191
4263
|
catch { }
|
|
4192
4264
|
}
|
|
4193
4265
|
// P1a.5.3: 若为话题会话,同步更新话题索引
|
|
4194
|
-
|
|
4266
|
+
const renamedTopicThreadId = sessionTopicThreadId(topic);
|
|
4267
|
+
if (renamedTopicThreadId) {
|
|
4195
4268
|
try {
|
|
4196
4269
|
const { ThreadService } = await import('../../core/thread/index.js');
|
|
4197
4270
|
const selfAid = topic.selfAID;
|
|
4198
4271
|
const peer = topic.channelId;
|
|
4199
4272
|
if (selfAid && peer) {
|
|
4200
4273
|
const svc = new ThreadService(this.sessionManager);
|
|
4201
|
-
await svc.renameThread({ selfAid, peer, threadId:
|
|
4274
|
+
const result = await svc.renameThread({ selfAid, peer, threadId: renamedTopicThreadId, name: newName });
|
|
4275
|
+
await notifyThreadRename({
|
|
4276
|
+
selfAid,
|
|
4277
|
+
peer,
|
|
4278
|
+
threadId: renamedTopicThreadId,
|
|
4279
|
+
result,
|
|
4280
|
+
threadService: svc,
|
|
4281
|
+
sendSystemMsg: (topic.channelType || channel.split('#', 1)[0]) === 'aun'
|
|
4282
|
+
? this.getThreadSystemMsgSender?.()
|
|
4283
|
+
: undefined,
|
|
4284
|
+
});
|
|
4202
4285
|
}
|
|
4203
4286
|
}
|
|
4204
4287
|
catch (err) {
|
|
@@ -4212,7 +4295,7 @@ export async function execMenuAction(cmd, action, args, channel, channelId, user
|
|
|
4212
4295
|
success: true,
|
|
4213
4296
|
topic: {
|
|
4214
4297
|
...buildSessionPayload(topic, newName),
|
|
4215
|
-
threadId: topic
|
|
4298
|
+
threadId: sessionLocalTopicId(topic) ?? '',
|
|
4216
4299
|
},
|
|
4217
4300
|
},
|
|
4218
4301
|
};
|
|
@@ -4316,7 +4399,7 @@ export async function execMenuAction(cmd, action, args, channel, channelId, user
|
|
|
4316
4399
|
}
|
|
4317
4400
|
// ── /system 系列 ──
|
|
4318
4401
|
if (cmdBase === '/system') {
|
|
4319
|
-
//
|
|
4402
|
+
// 进程级鉴权统一使用 Control AID 的协议 Owner,替代各 action 内联的 identity.role 判断。
|
|
4320
4403
|
if (!subject.isDaemonOwner && subject.processRole !== 'daemon-service') {
|
|
4321
4404
|
return {
|
|
4322
4405
|
error: formatAuthorizationDenial('This daemon-level operation requires DaemonOwner.', 'DAEMON_OWNER_REQUIRED'),
|
|
@@ -4358,63 +4441,97 @@ export async function execMenuAction(cmd, action, args, channel, channelId, user
|
|
|
4358
4441
|
: 'AUN_OUTBOUND_ROUTE_REQUIRED',
|
|
4359
4442
|
};
|
|
4360
4443
|
}
|
|
4444
|
+
let restartRequestId;
|
|
4361
4445
|
if (!suppressRealRestart) {
|
|
4362
|
-
|
|
4363
|
-
const restartEntry = resolveCliEntry(getPackageRoot());
|
|
4364
|
-
if (!fs.existsSync(restartEntry.entry)) {
|
|
4365
|
-
return {
|
|
4366
|
-
error: `找不到重启入口:${restartEntry.entry}。请先执行 npm run build 重建产物。`,
|
|
4367
|
-
code: 'RESTART_ENTRY_MISSING',
|
|
4368
|
-
data: { reasonCode: 'RESTART_ENTRY_MISSING', entry: restartEntry.entry },
|
|
4369
|
-
};
|
|
4370
|
-
}
|
|
4446
|
+
restartRequestId = crypto.randomUUID();
|
|
4371
4447
|
const restartInfo = {
|
|
4372
4448
|
channel,
|
|
4373
4449
|
channelId,
|
|
4374
4450
|
timestamp: Date.now(),
|
|
4451
|
+
requestId: restartRequestId,
|
|
4452
|
+
requesterPid: process.pid,
|
|
4453
|
+
status: 'requested',
|
|
4375
4454
|
...(restartReplyContext?.threadId ? { threadId: restartReplyContext.threadId } : {}),
|
|
4376
4455
|
...(restartDelivery ? { delivery: restartDelivery } : {}),
|
|
4377
4456
|
...(restartReplyContext?.replyToMessageId ? { rootId: restartReplyContext.replyToMessageId } : {}),
|
|
4378
4457
|
};
|
|
4379
4458
|
const controlDir = daemonControlDir();
|
|
4380
4459
|
fs.mkdirSync(controlDir, { recursive: true });
|
|
4381
|
-
|
|
4382
|
-
|
|
4383
|
-
|
|
4384
|
-
|
|
4385
|
-
|
|
4386
|
-
|
|
4387
|
-
|
|
4388
|
-
|
|
4389
|
-
|
|
4390
|
-
|
|
4391
|
-
|
|
4392
|
-
|
|
4460
|
+
const pendingFile = path.join(controlDir, 'restart-pending.json');
|
|
4461
|
+
const monitorEntry = resolveCliEntry(getPackageRoot());
|
|
4462
|
+
if (!fs.existsSync(monitorEntry.entry)) {
|
|
4463
|
+
return {
|
|
4464
|
+
error: `找不到重启监控入口:${monitorEntry.entry}。请先执行 npm run build 重建产物。`,
|
|
4465
|
+
code: 'RESTART_ENTRY_MISSING',
|
|
4466
|
+
data: { reasonCode: 'RESTART_ENTRY_MISSING', entry: monitorEntry.entry },
|
|
4467
|
+
};
|
|
4468
|
+
}
|
|
4469
|
+
if (scanInstances().restartMonitors.some(item => item.alive)
|
|
4470
|
+
|| !createRestartRequest(pendingFile, restartInfo, isProcessRunning)) {
|
|
4471
|
+
return { error: '已有重启请求正在处理,请等待其结果,不要重复发起。', code: 'RESTART_ALREADY_PENDING' };
|
|
4472
|
+
}
|
|
4473
|
+
const requestId = restartRequestId;
|
|
4474
|
+
const accepted = () => {
|
|
4475
|
+
const request = readRestartRequest(pendingFile, requestId);
|
|
4476
|
+
return request?.status === 'accepted' || request?.status === 'ready';
|
|
4477
|
+
};
|
|
4478
|
+
const logFilePath = path.join(resolvePaths().logs, `restart-launch-${requestId}.log`);
|
|
4479
|
+
const restartLaunch = await launchMonitorAndConfirm([...monitorEntry.args, 'restart'], {
|
|
4480
|
+
[RUNTIME_HOME_ENV]: resolvePaths().root,
|
|
4481
|
+
[SESSION_ID_ENV]: '',
|
|
4393
4482
|
[AGENT_DELEGATION_TOKEN_ENV]: '',
|
|
4394
|
-
|
|
4395
|
-
|
|
4396
|
-
|
|
4483
|
+
[RESTART_REQUEST_ID_ENV]: requestId,
|
|
4484
|
+
}, () => accepted(), () => scanInstances().restartMonitors.some(item => item.alive && item.record.requestId === requestId && item.record.phase === 'accepted'), {
|
|
4485
|
+
requestId,
|
|
4486
|
+
timeoutMs: 8_000,
|
|
4487
|
+
logFilePath,
|
|
4488
|
+
onSpawn: pid => { updateRestartRequest(pendingFile, requestId, { launcherPid: pid }); },
|
|
4489
|
+
isPendingInRegistry: () => scanInstances().restartMonitors.some(item => item.alive && item.record.requestId === requestId),
|
|
4490
|
+
});
|
|
4397
4491
|
if (!restartLaunch.ok) {
|
|
4398
|
-
|
|
4399
|
-
|
|
4492
|
+
if (restartLaunch.pending) {
|
|
4493
|
+
logger.error(`[System] Restart ownership is still unconfirmed (requestId=${restartRequestId}, PID=${restartLaunch.pid ?? 'unknown'})`);
|
|
4494
|
+
return {
|
|
4495
|
+
error: `重启请求状态暂不可确认(requestId=${restartRequestId})。启动链仍在运行,可能继续执行;请勿立即重复发起。诊断日志:${logFilePath},monitor 记录见 restart.log。`,
|
|
4496
|
+
code: 'RESTART_PENDING_CONFIRMATION',
|
|
4497
|
+
data: {
|
|
4498
|
+
action: 'restart',
|
|
4499
|
+
success: false,
|
|
4500
|
+
status: 'pending-confirmation',
|
|
4501
|
+
requestId: restartRequestId,
|
|
4502
|
+
...(restartLaunch.pid ? { pid: restartLaunch.pid } : {}),
|
|
4503
|
+
},
|
|
4504
|
+
};
|
|
4400
4505
|
}
|
|
4401
|
-
|
|
4402
|
-
logger.error(`[System] Refusing menu restart:
|
|
4506
|
+
removeRestartRequest(pendingFile, requestId);
|
|
4507
|
+
logger.error(`[System] Refusing menu restart: restart-monitor did not take ownership (${restartLaunch.reason})`);
|
|
4403
4508
|
for (const line of (restartLaunch.stderrTail ?? '').split(/\r?\n/).filter(Boolean).slice(-8)) {
|
|
4404
4509
|
logger.error(`[System] | ${line}`);
|
|
4405
4510
|
}
|
|
4406
4511
|
return {
|
|
4407
|
-
error:
|
|
4408
|
-
code: '
|
|
4409
|
-
data: {
|
|
4512
|
+
error: `重启已中止:重启监控未接管(${restartLaunch.reason})。服务未停止,仍在运行当前版本。`,
|
|
4513
|
+
code: 'RESTART_MONITOR_DEAD',
|
|
4514
|
+
data: {
|
|
4515
|
+
reasonCode: 'RESTART_MONITOR_DEAD',
|
|
4516
|
+
reason: restartLaunch.reason,
|
|
4517
|
+
requestId: restartRequestId,
|
|
4518
|
+
...(restartLaunch.pid ? { pid: restartLaunch.pid } : {}),
|
|
4519
|
+
},
|
|
4410
4520
|
};
|
|
4411
4521
|
}
|
|
4522
|
+
logger.info(`[System] Restart monitor accepted request ${restartRequestId} (PID ${restartLaunch.pid ?? 'unknown'})`);
|
|
4412
4523
|
}
|
|
4413
4524
|
else {
|
|
4414
4525
|
logger.info('[System] Suppressed real menu restart in test runtime');
|
|
4415
4526
|
}
|
|
4416
4527
|
this.eventBus.publish({ type: 'system:restart', channel, channelId });
|
|
4417
|
-
return {
|
|
4528
|
+
return {
|
|
4529
|
+
data: {
|
|
4530
|
+
action: 'restart',
|
|
4531
|
+
success: true,
|
|
4532
|
+
...(restartRequestId ? { status: 'accepted', requestId: restartRequestId } : {}),
|
|
4533
|
+
},
|
|
4534
|
+
};
|
|
4418
4535
|
}
|
|
4419
4536
|
if (action === 'paircode') {
|
|
4420
4537
|
// Keep Menu pairing on the same daemon-owned store as /pair, ECWeb, and
|
|
@@ -4428,26 +4545,28 @@ export async function execMenuAction(cmd, action, args, channel, channelId, user
|
|
|
4428
4545
|
return r;
|
|
4429
4546
|
}
|
|
4430
4547
|
if (action === 'upgrade') {
|
|
4431
|
-
const
|
|
4432
|
-
|
|
4433
|
-
|
|
4434
|
-
|
|
4435
|
-
|
|
4436
|
-
|
|
4437
|
-
|
|
4438
|
-
|
|
4439
|
-
|
|
4440
|
-
|
|
4441
|
-
|
|
4442
|
-
|
|
4443
|
-
|
|
4444
|
-
|
|
4445
|
-
|
|
4446
|
-
|
|
4447
|
-
|
|
4448
|
-
|
|
4449
|
-
|
|
4450
|
-
|
|
4548
|
+
const mode = args?.mode ?? 'check';
|
|
4549
|
+
if (!['check', 'update', 'status'].includes(mode)
|
|
4550
|
+
|| Object.keys(args ?? {}).some(key => !['mode', 'jobId', 'context'].includes(key))
|
|
4551
|
+
|| (mode !== 'status' && args?.jobId !== undefined))
|
|
4552
|
+
return { error: '无效的更新参数', code: 'INVALID_ARGUMENT' };
|
|
4553
|
+
if (mode === 'status') {
|
|
4554
|
+
if (typeof args?.jobId !== 'string' || !/^job_[a-f0-9-]{36}$/.test(args.jobId))
|
|
4555
|
+
return { error: '缺少或无效的 jobId', code: 'INVALID_ARGUMENT' };
|
|
4556
|
+
const job = readUpgradeJob(args.jobId);
|
|
4557
|
+
const scope = JSON.stringify([source, channel, channelId, subject.actorId, transportReplyContext?.toDeviceId, transportReplyContext?.threadId]);
|
|
4558
|
+
const expectedKey = job ? crypto.createHash('sha256').update(JSON.stringify([scope, job.requestId])).digest('hex') : undefined;
|
|
4559
|
+
return job && job.requestKey === expectedKey
|
|
4560
|
+
? { data: upgradeJobView(job) }
|
|
4561
|
+
: { error: '更新任务不存在或不可访问', code: 'NOT_FOUND' };
|
|
4562
|
+
}
|
|
4563
|
+
if (mode === 'update') {
|
|
4564
|
+
const result = await startUpgradeJob(requestId || crypto.randomUUID(), JSON.stringify([source, channel, channelId, subject.actorId, transportReplyContext?.toDeviceId, transportReplyContext?.threadId]));
|
|
4565
|
+
return { data: { ...upgradeJobView(result.job), reused: result.reused } };
|
|
4566
|
+
}
|
|
4567
|
+
const distTag = resolveUpgradeDistTag();
|
|
4568
|
+
const results = await refreshManagedPackageVersions(VERSION_INFO_PACKAGES, { source: 'menu-upgrade', distTag, includeUninstalled: true });
|
|
4569
|
+
return { data: structuredUpgradeCheck(results, distTag) };
|
|
4451
4570
|
}
|
|
4452
4571
|
return { error: `不支持的 system action: ${action}`, code: 'NOT_SUPPORTED' };
|
|
4453
4572
|
}
|
|
@@ -4466,7 +4585,8 @@ export async function execMenuAction(cmd, action, args, channel, channelId, user
|
|
|
4466
4585
|
const requestedArgv = [...argvValidation.argv];
|
|
4467
4586
|
argv = normalizeCliArgv(argv);
|
|
4468
4587
|
const cliAgent = this.getOwningAgent?.(channel);
|
|
4469
|
-
const cliSelfAid = cliAgent?.aid
|
|
4588
|
+
const cliSelfAid = cliAgent?.aid ?? (argv[0] === 'aid' && argv[1] === 'bind'
|
|
4589
|
+
? session?.selfAID ?? authSubject?.selfAid ?? ownerResolver.getDaemonAid() : undefined);
|
|
4470
4590
|
const cliChannelType = this.resolveChannelType?.(channel);
|
|
4471
4591
|
const cliChatType = session?.chatType ?? explicitChatType;
|
|
4472
4592
|
const cliPeerKeyId = cliChatType === 'group'
|
|
@@ -4529,7 +4649,7 @@ export async function execMenuAction(cmd, action, args, channel, channelId, user
|
|
|
4529
4649
|
for (let i = 0; i < parsedCommands.length; i++) {
|
|
4530
4650
|
const parsed = parsedCommands[i];
|
|
4531
4651
|
const authArgv = parsed.resolvedConfigCommand?.canonicalArgv ?? intentArgvs[i] ?? argv;
|
|
4532
|
-
const decision = await
|
|
4652
|
+
const decision = await authorizeFreshOperation({
|
|
4533
4653
|
source: 'menu.cli',
|
|
4534
4654
|
intent: parsed.intent,
|
|
4535
4655
|
subject,
|
|
@@ -4562,7 +4682,35 @@ export async function execMenuAction(cmd, action, args, channel, channelId, user
|
|
|
4562
4682
|
}
|
|
4563
4683
|
authorizedCommands.push({ parsed, decision });
|
|
4564
4684
|
}
|
|
4565
|
-
const
|
|
4685
|
+
const executionIntent = parsedCommands[0]?.intent;
|
|
4686
|
+
const ownershipOperation = executionIntent?.operation;
|
|
4687
|
+
let rawExecution;
|
|
4688
|
+
if (ownershipOperation === 'ec.aid.bind' || ownershipOperation === 'ec.aid.bind.status') {
|
|
4689
|
+
try {
|
|
4690
|
+
rawExecution = { data: { ok: true, state: await executeOwnershipCommand(parseOwnershipCommand(argv), cliSelfAid) } };
|
|
4691
|
+
}
|
|
4692
|
+
catch (error) {
|
|
4693
|
+
const failure = ownershipFailure(error);
|
|
4694
|
+
const code = ['INVALID_ARGUMENT', 'INVALID_AID', 'INVALID_OWNER'].includes(failure.code) ? 'INVALID_ARGUMENT'
|
|
4695
|
+
: ['SUBJECT_REQUIRED', 'SUBJECT_UNAVAILABLE'].includes(failure.code) ? 'NOT_FOUND'
|
|
4696
|
+
: ['OWNER_ALREADY_BOUND', 'BIND_CONFLICT', 'BIND_UNCERTAIN', 'CONSENSUS_REQUIRED', 'CLIENT_REPLACED', 'conflict', 'owner_locked'].includes(failure.code)
|
|
4697
|
+
? 'CONFLICT' : 'INTERNAL_ERROR';
|
|
4698
|
+
rawExecution = { error: failure.error, code, data: failure };
|
|
4699
|
+
}
|
|
4700
|
+
}
|
|
4701
|
+
else if (executionIntent?.operation === 'ec.thread.summary-show'
|
|
4702
|
+
|| executionIntent?.operation === 'ec.thread.summary-log') {
|
|
4703
|
+
rawExecution = await this.executeThreadSummaryCli({
|
|
4704
|
+
action: String(executionIntent.args.action),
|
|
4705
|
+
selfAid: String(executionIntent.args.self),
|
|
4706
|
+
peer: String(executionIntent.args.peer),
|
|
4707
|
+
threadId: String(executionIntent.args.threadId),
|
|
4708
|
+
authenticatedSelfAid: subject.selfAid ?? '',
|
|
4709
|
+
});
|
|
4710
|
+
}
|
|
4711
|
+
else {
|
|
4712
|
+
rawExecution = await this.execCliPassthrough(argv, subject.role);
|
|
4713
|
+
}
|
|
4566
4714
|
const execution = appendGroupRulesCompatibilityNote(rawExecution, parsedCommands[0]?.resolvedConfigCommand, session);
|
|
4567
4715
|
const executionData = 'data' in execution ? execution.data : undefined;
|
|
4568
4716
|
for (const { parsed, decision } of authorizedCommands) {
|
|
@@ -4575,7 +4723,7 @@ export async function execMenuAction(cmd, action, args, channel, channelId, user
|
|
|
4575
4723
|
channel, channelId, role: identity.role, isDaemonOwner,
|
|
4576
4724
|
fromControlChannel: fromControlChannel ?? false, decision: 'allow',
|
|
4577
4725
|
executed: true,
|
|
4578
|
-
executionState: executionData?.
|
|
4726
|
+
executionState: 'error' in execution || executionData?.ok === false || (executionData?.exitCode !== undefined && executionData.exitCode !== 0) ? 'failed' : 'completed',
|
|
4579
4727
|
matchedRule: decision.command?.matchedRule, taskId: requestId, argvHash: hashArgv(argv),
|
|
4580
4728
|
name: 'cli', action: 'exec',
|
|
4581
4729
|
args: { argv: [...requestedArgv] },
|
|
@@ -4589,6 +4737,41 @@ export async function execMenuAction(cmd, action, args, channel, channelId, user
|
|
|
4589
4737
|
// fetch 文件作为独立 result.file 消息异步发回;把请求 id 作为 correlationId 透传,
|
|
4590
4738
|
// 客户端用它把异步到达的文件消息对回这次 fetch 点击。
|
|
4591
4739
|
if (cmdBase === '/file') {
|
|
4740
|
+
if (action === 'search') {
|
|
4741
|
+
for (const operation of ['file.list', ...(args.content !== undefined ? ['file.fetch'] : [])]) {
|
|
4742
|
+
const denied = await authorizeMenuIntent.call(this, {
|
|
4743
|
+
intent: { operation, scope: 'filesystem', source, args: { ...args, filePath: args.path ?? '.' } },
|
|
4744
|
+
identity: authIdentity, subject, session: authSession, explicitChatType,
|
|
4745
|
+
channel, channelId, userId, fromControlChannel, source,
|
|
4746
|
+
});
|
|
4747
|
+
if (denied)
|
|
4748
|
+
return denied;
|
|
4749
|
+
}
|
|
4750
|
+
const projectPath = session?.projectPath ?? evolagent?.projectPath ?? this.getOwningAgent?.(channel)?.projectPath;
|
|
4751
|
+
try {
|
|
4752
|
+
const data = await searchProjectFiles(projectPath, args, {
|
|
4753
|
+
canAccessFile: async (relative, absolute, isDirectory) => {
|
|
4754
|
+
const filePath = path.isAbsolute(args.path ?? '.') ? absolute : relative;
|
|
4755
|
+
const operations = [
|
|
4756
|
+
'file.search', 'file.list', ...(!isDirectory && args.content !== undefined ? ['file.fetch'] : []),
|
|
4757
|
+
];
|
|
4758
|
+
for (const operation of operations) {
|
|
4759
|
+
const decision = await authorizeOperation({
|
|
4760
|
+
source, subject, audit: false,
|
|
4761
|
+
intent: { operation, scope: 'filesystem', source, args: { ...args, path: filePath, filePath } },
|
|
4762
|
+
});
|
|
4763
|
+
if (!decision.allow)
|
|
4764
|
+
return false;
|
|
4765
|
+
}
|
|
4766
|
+
return true;
|
|
4767
|
+
},
|
|
4768
|
+
});
|
|
4769
|
+
return { data };
|
|
4770
|
+
}
|
|
4771
|
+
catch (error) {
|
|
4772
|
+
return menuResultFailure(error);
|
|
4773
|
+
}
|
|
4774
|
+
}
|
|
4592
4775
|
if (action === 'list') {
|
|
4593
4776
|
const dirArg = (args?.path ?? '.').toString().trim() || '.';
|
|
4594
4777
|
const offset = parseFileListOffset(args?.offset);
|
|
@@ -4739,7 +4922,7 @@ export function resolveControlMenuAuthorization(input) {
|
|
|
4739
4922
|
return {
|
|
4740
4923
|
allowed: false,
|
|
4741
4924
|
code: 'NOT_ALLOWED',
|
|
4742
|
-
message: formatAuthorizationDenial('
|
|
4925
|
+
message: formatAuthorizationDenial('Verify the protocol Owner of the daemon Control AID before using Control Menu operations.', 'DAEMON_OWNER_REQUIRED'),
|
|
4743
4926
|
data: authorizationDenialData('DAEMON_OWNER_REQUIRED'),
|
|
4744
4927
|
};
|
|
4745
4928
|
}
|
|
@@ -4771,7 +4954,7 @@ async function execMenuForSystemControl(payload, context) {
|
|
|
4771
4954
|
// operation is available through Host/control: file operations must be
|
|
4772
4955
|
// addressed to the current Agent AID so the Agent project boundary applies.
|
|
4773
4956
|
const isFileFetch = (payload?.type === 'menu.query'
|
|
4774
|
-
|| (payload?.type === 'menu.action' && payload?.action === 'fetch'))
|
|
4957
|
+
|| (payload?.type === 'menu.action' && (payload?.action === 'fetch' || payload?.action === 'search')))
|
|
4775
4958
|
&& (name === 'file' || compatCmd === '/file');
|
|
4776
4959
|
const isDataRootQuery = payload?.type === 'menu.query'
|
|
4777
4960
|
&& (name === 'agent-data-root'
|
|
@@ -4973,6 +5156,7 @@ async function execMenuForSystemControl(payload, context) {
|
|
|
4973
5156
|
}
|
|
4974
5157
|
/** ECWeb 专用入口:只信任 IPC 信封中的服务端认证上下文,不信任 Menu payload 自报身份。 */
|
|
4975
5158
|
export async function execMenuForEcweb(payload, trusted) {
|
|
5159
|
+
await refreshOwnershipForAuthorization(undefined, { maxAgeMs: OWNERSHIP_CACHE_MAX_AGE_MS });
|
|
4976
5160
|
const id = payload?.id ?? '';
|
|
4977
5161
|
const name = payload?.name;
|
|
4978
5162
|
const validationError = validateMenuRequest(payload ?? {});
|
|
@@ -4988,7 +5172,7 @@ export async function execMenuForEcweb(payload, trusted) {
|
|
|
4988
5172
|
// ECWeb authentication grants daemon-owner authority independently of the
|
|
4989
5173
|
// configured owners. Prefer an owner for audit/Trigger attribution when
|
|
4990
5174
|
// available; otherwise keep local and remote management actors distinct.
|
|
4991
|
-
const owners =
|
|
5175
|
+
const owners = getDaemonOwners();
|
|
4992
5176
|
const userId = (trusted.actorAid && owners.includes(trusted.actorAid))
|
|
4993
5177
|
? trusted.actorAid
|
|
4994
5178
|
: owners[0] ?? (trusted.localDirect ? 'local-direct' : 'ecweb-remote');
|
|
@@ -5016,12 +5200,13 @@ export async function execMenuForEcweb(payload, trusted) {
|
|
|
5016
5200
|
* process authorization path. The authenticated envelope peerId remains the
|
|
5017
5201
|
* requester; token mode maps to a configured owner as its management actor. */
|
|
5018
5202
|
export async function execMenuForControl(payload, peerId, auth = {}) {
|
|
5203
|
+
await refreshOwnershipForAuthorization(undefined, { maxAgeMs: OWNERSHIP_CACHE_MAX_AGE_MS });
|
|
5019
5204
|
const id = payload?.id ?? '';
|
|
5020
5205
|
const name = payload?.name;
|
|
5021
5206
|
const validationError = validateMenuRequest(payload ?? {});
|
|
5022
5207
|
if (validationError)
|
|
5023
5208
|
return menuFailure({ id, ...(name ? { name } : {}) }, validationError);
|
|
5024
|
-
const owners =
|
|
5209
|
+
const owners = getDaemonOwners();
|
|
5025
5210
|
const compatCmd = menuCommandToken(payload?.cmd);
|
|
5026
5211
|
const isRoleRequest = name === 'role' || compatCmd === '/role';
|
|
5027
5212
|
const isConnectRequest = name === 'connect' || compatCmd === '/connect';
|