evolcore 0.0.19 → 0.0.21
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 +43 -1
- package/README.md +60 -9
- package/bin/install-codex-managed-hooks.mjs +61 -0
- package/dist/agents/baseagent.js +10 -6
- package/dist/agents/claude-runner.js +383 -111
- package/dist/agents/codex-app-server-client.js +10 -2
- package/dist/agents/codex-runner.js +405 -137
- package/dist/agents/ecagent-runner.js +174 -63
- package/dist/agents/gemini-runner.js +130 -30
- package/dist/agents/request-identity.js +25 -0
- package/dist/agents/runner-types.js +19 -0
- package/dist/aun/aid/agentmd.js +66 -2
- package/dist/aun/aid/identity.js +4 -1
- package/dist/aun/aid/index.js +1 -1
- package/dist/aun/msg/group.js +86 -9
- package/dist/aun/msg/history.js +213 -36
- package/dist/aun/msg/managed-operation.js +58 -9
- package/dist/aun/msg/p2p.js +26 -11
- package/dist/aun/outbox.js +295 -68
- package/dist/aun/service-proxy.js +43 -25
- package/dist/channels/aun.js +1004 -273
- package/dist/channels/daemon.js +6 -1
- package/dist/cli/agent-command.js +4 -3
- package/dist/cli/agent.js +66 -56
- package/dist/cli/aun-commands.js +177 -42
- package/dist/cli/command-log.js +10 -11
- package/dist/cli/contact.js +1 -0
- package/dist/cli/daemon-commands.js +81 -121
- package/dist/cli/index.js +1 -0
- package/dist/cli/init.js +76 -24
- package/dist/cli/restart-monitor.js +3 -3
- package/dist/cli/task-context.js +46 -0
- package/dist/cli/trigger-command.js +1 -1
- package/dist/cli/watch-logs.js +10 -3
- package/dist/config/builtin-roles.js +1 -0
- package/dist/config/config-field-policy.js +16 -5
- package/dist/config/config-manager.js +226 -24
- package/dist/config/config-operation-service.js +1 -2
- package/dist/config/contact-operation-service.js +32 -1
- package/dist/config/contact-request-service.js +44 -0
- package/dist/config/daemon-services.js +186 -0
- package/dist/config/gateway-config.js +29 -16
- package/dist/config/lifecycle.js +16 -5
- package/dist/config/role-service.js +54 -3
- package/dist/config/schema-migration.js +550 -0
- package/dist/config-store.js +161 -12
- package/dist/core/agent-application-service.js +279 -0
- package/dist/core/audit/log-integrity.js +102 -0
- package/dist/core/auth/agent-delegation.js +31 -1
- package/dist/core/auth/auth-gateway.js +33 -4
- package/dist/core/auth/authorization-audit.js +155 -4
- package/dist/core/auth/operation-authorizer.js +41 -1
- package/dist/core/auth/operation-catalog.js +9 -1
- package/dist/core/bootstrap-messages.js +2 -2
- package/dist/core/bootstrap-service.js +27 -38
- package/dist/core/causation/aun-association.js +7 -4
- package/dist/core/channel-loader.js +0 -2
- package/dist/core/command/agent-control.js +56 -16
- package/dist/core/command/command-handler.js +290 -44
- package/dist/core/command/connect-menu.js +3 -4
- package/dist/core/command/group-menu.js +5 -7
- package/dist/core/command/menu-handler.js +279 -80
- package/dist/core/command/role-menu.js +21 -11
- package/dist/core/command/slash-gate.js +85 -18
- package/dist/core/command/slash-handler.js +350 -32
- package/dist/core/event-catalog.js +5 -0
- package/dist/core/evolagent.js +9 -4
- package/dist/core/handoff/dispatcher.js +4 -0
- package/dist/core/handoff/runtime.js +10 -0
- package/dist/core/handoff/store.js +32 -9
- package/dist/core/inference/text-inference.js +7 -15
- package/dist/core/message/im-renderer.js +83 -84
- package/dist/core/message/{inbound-admission.js → message-admission.js} +51 -1
- package/dist/core/message/message-bridge.js +124 -10
- package/dist/core/message/message-log.js +14 -7
- package/dist/core/message/message-queue.js +206 -16
- package/dist/core/message/message-utils.js +12 -5
- package/dist/core/message/response-engine.js +495 -72
- package/dist/core/message/send-receipt.js +1 -0
- package/dist/core/message/stream-debouncer.js +9 -2
- package/dist/core/model/model-catalog.js +23 -15
- package/dist/core/model/model-diagnostics.js +28 -10
- package/dist/core/permission/approval-gateway.js +180 -6
- package/dist/core/permission/ec-command-parser.js +465 -56
- package/dist/core/permission/mode.js +18 -3
- package/dist/core/{protected-paths.js → permission/protected-paths.js} +17 -5
- package/dist/core/permission/readonly-shell-query.js +263 -9
- package/dist/core/permission/sandbox-runtime.js +205 -13
- package/dist/core/permission/tool-policy.js +673 -62
- package/dist/core/relation/peer-identity.js +18 -0
- package/dist/core/session/session-fs-store.js +154 -5
- package/dist/core/session/session-manager.js +299 -30
- package/dist/core/session/session-renew.js +19 -12
- package/dist/core/session/session-turn-coordinator.js +11 -4
- package/dist/eck/kit-renderer.js +18 -9
- package/dist/index.js +283 -65
- package/dist/ipc.js +374 -24
- package/dist/paths.js +64 -7
- package/dist/response-system/context-builder.js +1 -7
- package/dist/trigger/anomaly-store.js +1 -0
- package/dist/trigger/feedback.js +56 -5
- package/dist/trigger/history.js +79 -4
- package/dist/trigger/legacy-session-history.js +2 -2
- package/dist/trigger/parser.js +3 -2
- package/dist/trigger/validation.js +6 -1
- package/dist/utils/atomic-write.js +27 -0
- package/dist/utils/ecweb-utils.js +16 -2
- package/dist/utils/error-utils.js +4 -1
- package/dist/utils/logger.js +21 -2
- package/dist/utils/process-tree-stats.js +24 -4
- package/dist/utils/process-tree-worker.js +31 -0
- package/dist/utils/project-path.js +1 -2
- package/dist/utils/stats.js +52 -18
- package/dist/utils/welcome.js +2 -2
- package/kits/docs/INDEX.md +1 -1
- package/kits/docs/evolcore/INDEX.md +1 -1
- package/kits/docs/evolcore/contact.md +7 -1
- package/kits/docs/evolcore/msg.md +16 -0
- package/kits/rules/01-overview.md +1 -1
- package/kits/rules/03-identity.md +1 -1
- package/kits/rules/05-venue.md +1 -1
- package/kits/schemas/_meta.json +10 -5
- package/kits/schemas/agent-config.schema.10.json +2 -1
- package/kits/schemas/agent-config.schema.11.json +421 -0
- package/kits/schemas/daemon.schema.5.json +135 -0
- package/kits/schemas/daemon.schema.6.json +131 -0
- package/kits/schemas/defaults.schema.5.json +119 -0
- package/kits/schemas/migrations/README.md +3 -1
- package/kits/schemas/relation-config.schema.8.json +13 -0
- package/kits/schemas/role-config.schema.1.json +1 -2
- package/kits/schemas/single-session.schema.3.json +32 -0
- package/kits/templates/roles/admin.json +1 -0
- package/kits/templates/roles/member.json +1 -0
- package/kits/templates/roles/visitor.json +1 -0
- package/package.json +7 -3
- package/skills/eclink/SKILL.md +2 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { roleMenuQuery, roleMenuGroupCount, roleMenuUpdate, } from './role-menu.js';
|
|
2
2
|
import { getContactSnapshot, resolveContactView } from '../../config/contact-book.js';
|
|
3
3
|
import { mutateAccessPolicy, readAccessPolicySnapshot, } from '../../config/access-policy.js';
|
|
4
|
-
import { expirePendingContactRequests, mutateContactWithOperation, reviewContactRequest, } from '../../config/contact-request-service.js';
|
|
4
|
+
import { expirePendingContactRequests, addContact, mutateContactWithOperation, reviewContactRequest, } from '../../config/contact-request-service.js';
|
|
5
5
|
import { formatPeerKey, parsePeerKey } from '../relation/peer-identity.js';
|
|
6
6
|
/**
|
|
7
7
|
* Connect Menu — 联系人与关系管理协议
|
|
@@ -374,13 +374,12 @@ async function actionAdd(req, self, context) {
|
|
|
374
374
|
}
|
|
375
375
|
const current = getContactSnapshot(self).contacts.get(primaryId);
|
|
376
376
|
const displayName = hasDisplayName ? args.displayName : current?.displayName ?? null;
|
|
377
|
-
const result = await
|
|
377
|
+
const result = await addContact({
|
|
378
378
|
selfAid: self,
|
|
379
379
|
actorId: context.actorAid ?? 'connect-menu',
|
|
380
|
-
operation: 'manual-add',
|
|
381
380
|
expectedContactRevision,
|
|
382
|
-
mutation: { type: 'set-display-name', primaryId, displayName },
|
|
383
381
|
primaryId,
|
|
382
|
+
displayName,
|
|
384
383
|
});
|
|
385
384
|
const storedDisplayName = result.contact.contacts[primaryId]?.displayName ?? null;
|
|
386
385
|
return ok({
|
|
@@ -2,7 +2,7 @@ import crypto from 'crypto';
|
|
|
2
2
|
import { ConfigTarget, mutateConfig, read as readConfig, resolveEffectiveFieldWithSource, } from '../../config/config-manager.js';
|
|
3
3
|
import { normalizeMentionMode } from '../../config/mention-mode.js';
|
|
4
4
|
import { readRolesConfig } from '../../config/roles.js';
|
|
5
|
-
import { groupInfo, groupList } from '../../aun/msg/group.js';
|
|
5
|
+
import { groupInfo, groupList, groupDirectoryErrorCode } from '../../aun/msg/group.js';
|
|
6
6
|
import { formatPeerKey, parsePeerKey } from '../relation/peer-identity.js';
|
|
7
7
|
const DEFAULT_DIRECTORY = {
|
|
8
8
|
getGroup: groupInfo,
|
|
@@ -141,11 +141,10 @@ async function resolveTarget(args, context) {
|
|
|
141
141
|
required: ['member', 'state'],
|
|
142
142
|
});
|
|
143
143
|
if (!result.ok) {
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
throw new GroupMenuError('TEMPORARILY_UNAVAILABLE', result.error || 'Unable to verify the target group');
|
|
144
|
+
const code = groupDirectoryErrorCode(result.code, result.error);
|
|
145
|
+
throw new GroupMenuError(code, code === 'NOT_FOUND'
|
|
146
|
+
? 'The target group is not joined by this Agent'
|
|
147
|
+
: result.error || 'Unable to verify the target group');
|
|
149
148
|
}
|
|
150
149
|
const group = result.group;
|
|
151
150
|
if (!result.found || !group || !group.my_role || group.status === 'dissolved') {
|
|
@@ -209,7 +208,6 @@ function settingOptions(key, target) {
|
|
|
209
208
|
...values.map(option => ({
|
|
210
209
|
...option,
|
|
211
210
|
selected: state.override === option.value,
|
|
212
|
-
...(state.mode ? { effective: state.mode === option.value } : {}),
|
|
213
211
|
revision: state.revision,
|
|
214
212
|
})),
|
|
215
213
|
];
|
|
@@ -16,7 +16,7 @@ import { CronExpressionParser } from 'cron-parser';
|
|
|
16
16
|
import { parseDuration } from '../../trigger/parser.js';
|
|
17
17
|
import { checkLatestVersion, getLocalVersion, isLinkedInstall, compareVersions, resolveGlobalPkg } from '../../utils/npm-ops.js';
|
|
18
18
|
import { commandExists, spawnDetachedNode } from '../../utils/cross-platform.js';
|
|
19
|
-
import { loadDefaults, loadDaemonConfig } from '../../config-store.js';
|
|
19
|
+
import { isFullAccessEnabled, loadDefaults, loadDaemonConfig } from '../../config-store.js';
|
|
20
20
|
import { WEB_PACKAGE_NAME } from '../../product.js';
|
|
21
21
|
import { read as cfgRead, resolveEffective, resolveEffectiveFieldWithSource, routeFieldPath, write as cfgWrite, ConfigTarget, } from '../../config/config-manager.js';
|
|
22
22
|
import { execAgentAction, execAgentQuery, execAgentOptions, resolveProjectPath } from './agent-control.js';
|
|
@@ -25,7 +25,7 @@ import { displaySessionTitle } from '../session/session-title.js';
|
|
|
25
25
|
import { buildSessionTurnList } from '../session/session-turns.js';
|
|
26
26
|
import { isCapabilityType, listCapabilityOptions, queryCapabilityTypes, resolveCapabilityContext, updateCapabilityPolicy, } from '../capability/capability-manager.js';
|
|
27
27
|
import { normalizeCliArgv, parseCliIntent, parseLegacyCliCommand, validateCliArgv, withDefaultRelationContext } from './cli-intent-parser.js';
|
|
28
|
-
import { auditCommandAuthorization, hashArgv } from '../auth/authorization-audit.js';
|
|
28
|
+
import { auditCommandAuthorization, auditFullAccessEvent, hashArgv } from '../auth/authorization-audit.js';
|
|
29
29
|
import { authorizeOperation, buildAuthSubject } from '../auth/auth-gateway.js';
|
|
30
30
|
import { authorizationDenialData, formatAuthorizationDenial } from '../auth/authorization-denial.js';
|
|
31
31
|
import { evaluateRoleOperationCapability } from '../auth/operation-authorizer.js';
|
|
@@ -43,6 +43,7 @@ import { roleMenuAction, roleMenuOperation, roleMenuOptions, roleMenuQuery, role
|
|
|
43
43
|
import { handleConnectMenu, connectMenuOperation } from './connect-menu.js';
|
|
44
44
|
import { groupMenuAuthorizationArgs, groupMenuIntent, handleGroupMenu, } from './group-menu.js';
|
|
45
45
|
import { getMenuCatalog, execMenuValueBatch, resolveExplicitMenuExecutionContext, validateMenuCatalogArgs, validateMenuValueBatchArgs } from './menu-catalog.js';
|
|
46
|
+
import { probeSessionActivity } from './slash-gate.js';
|
|
46
47
|
/**
|
|
47
48
|
* 获取 baseagent CLI 的版本号(claude/gemini/codex)。
|
|
48
49
|
* 失败返回 null(命令不存在或执行失败)。
|
|
@@ -455,6 +456,24 @@ function menuConfigSource(fieldPath, sel) {
|
|
|
455
456
|
catch { }
|
|
456
457
|
return null;
|
|
457
458
|
}
|
|
459
|
+
/** Read the explicit value stored in the scope being queried, without resolving inheritance. */
|
|
460
|
+
function readMenuScopeOverride(scope, fieldPath, sel) {
|
|
461
|
+
const target = scope === 'agent' ? ConfigTarget.Agent : ConfigTarget.Relation;
|
|
462
|
+
try {
|
|
463
|
+
let current = cfgRead(target, sel, { cache: false });
|
|
464
|
+
for (const part of fieldPath.split('.')) {
|
|
465
|
+
if (!current || typeof current !== 'object'
|
|
466
|
+
|| !Object.prototype.hasOwnProperty.call(current, part)) {
|
|
467
|
+
return null;
|
|
468
|
+
}
|
|
469
|
+
current = current[part];
|
|
470
|
+
}
|
|
471
|
+
return current === undefined ? null : current;
|
|
472
|
+
}
|
|
473
|
+
catch {
|
|
474
|
+
return null;
|
|
475
|
+
}
|
|
476
|
+
}
|
|
458
477
|
function menuModelConfigFieldPath(target) {
|
|
459
478
|
return target.fieldPath;
|
|
460
479
|
}
|
|
@@ -1131,6 +1150,8 @@ function buildRoleMenuContext(owner, channel, subject) {
|
|
|
1131
1150
|
actorRoleSource: subject.roleSource,
|
|
1132
1151
|
actorAllowAccess: subject.allowAccess,
|
|
1133
1152
|
actorAid: subject.actorId,
|
|
1153
|
+
isDaemonOwner: subject.isDaemonOwner,
|
|
1154
|
+
fromControlChannel: subject.fromControlChannel,
|
|
1134
1155
|
chatType: subject.chatType,
|
|
1135
1156
|
aunAvailable,
|
|
1136
1157
|
...(contactAdapter?.send ? {
|
|
@@ -1549,7 +1570,9 @@ export async function getSubMenuItems(cmd, channel, channelId, userId, args, ove
|
|
|
1549
1570
|
// 控制 channel:返回全量;agent channel:仅返回自身单条。
|
|
1550
1571
|
if (cmd === '/agent') {
|
|
1551
1572
|
if (fromControlChannel) {
|
|
1552
|
-
const res =
|
|
1573
|
+
const res = this.agentApplicationService
|
|
1574
|
+
? await execAgentOptions(args, this.agentApplicationService)
|
|
1575
|
+
: await execAgentOptions(args);
|
|
1553
1576
|
if ('error' in res)
|
|
1554
1577
|
throw { code: res.code, message: res.error };
|
|
1555
1578
|
return res.data.agents.map(ag => ({ value: ag.aid, label: ag.name || ag.aid, desc: ag.status }));
|
|
@@ -1558,7 +1581,9 @@ export async function getSubMenuItems(cmd, channel, channelId, userId, args, ove
|
|
|
1558
1581
|
const selfAid = this.getOwningAgent?.(channel)?.aid;
|
|
1559
1582
|
if (!selfAid)
|
|
1560
1583
|
throw { code: 'FORBIDDEN', message: '当前 channel 无绑定 agent' };
|
|
1561
|
-
const res =
|
|
1584
|
+
const res = this.agentApplicationService
|
|
1585
|
+
? await execAgentQuery({ aid: selfAid }, this.agentApplicationService)
|
|
1586
|
+
: await execAgentQuery({ aid: selfAid });
|
|
1562
1587
|
if ('error' in res)
|
|
1563
1588
|
throw { code: res.code, message: res.error };
|
|
1564
1589
|
const ag = res.data;
|
|
@@ -1902,12 +1927,21 @@ export async function execMenuQuery(cmd, channel, channelId, userId, args, expli
|
|
|
1902
1927
|
// 授权由 agent.list/agent.show operation 决定;channel 闸门只负责目标范围。
|
|
1903
1928
|
if (cmdBase === '/agent') {
|
|
1904
1929
|
if (fromControlChannel) {
|
|
1905
|
-
|
|
1930
|
+
if (args?.aid) {
|
|
1931
|
+
return this.agentApplicationService
|
|
1932
|
+
? await execAgentQuery(args, this.agentApplicationService)
|
|
1933
|
+
: await execAgentQuery(args);
|
|
1934
|
+
}
|
|
1935
|
+
return this.agentApplicationService
|
|
1936
|
+
? await execAgentOptions(args, this.agentApplicationService)
|
|
1937
|
+
: await execAgentOptions(args);
|
|
1906
1938
|
}
|
|
1907
1939
|
const selfAid = this.getOwningAgent?.(channel)?.aid;
|
|
1908
1940
|
if (!selfAid)
|
|
1909
1941
|
return { error: '当前 channel 无绑定 agent', code: 'FORBIDDEN' };
|
|
1910
|
-
return
|
|
1942
|
+
return this.agentApplicationService
|
|
1943
|
+
? await execAgentQuery({ ...(args ?? {}), aid: selfAid }, this.agentApplicationService)
|
|
1944
|
+
: await execAgentQuery({ ...(args ?? {}), aid: selfAid });
|
|
1911
1945
|
}
|
|
1912
1946
|
// ── /gateway 查询(只读,列出全部作用域的网关配置;apiKey 已掩码) ──
|
|
1913
1947
|
if (cmdBase === '/gateway') {
|
|
@@ -2058,12 +2092,15 @@ export async function execMenuQuery(cmd, channel, channelId, userId, args, expli
|
|
|
2058
2092
|
: null;
|
|
2059
2093
|
const targetAgent = requestedAgent ?? evolagent;
|
|
2060
2094
|
const value = targetAgent?.baseagent ?? targetAgent?.config?.active_baseagent ?? null;
|
|
2095
|
+
const override = targetAgent?.aid
|
|
2096
|
+
? readMenuScopeOverride('agent', 'active_baseagent', { self: targetAgent.aid })
|
|
2097
|
+
: null;
|
|
2061
2098
|
const source = value == null
|
|
2062
2099
|
? null
|
|
2063
2100
|
: targetAgent?.aid
|
|
2064
2101
|
? menuConfigSource('active_baseagent', { self: targetAgent.aid }) ?? 'agent'
|
|
2065
2102
|
: 'agent';
|
|
2066
|
-
return { data: { baseagent: value, scope: 'agent', source } };
|
|
2103
|
+
return { data: { baseagent: value, scope: 'agent', override, source } };
|
|
2067
2104
|
}
|
|
2068
2105
|
if (cmdBase === '/model') {
|
|
2069
2106
|
const target = resolveMenuModelTarget.call(this, {
|
|
@@ -2087,6 +2124,7 @@ export async function execMenuQuery(cmd, channel, channelId, userId, args, expli
|
|
|
2087
2124
|
baseagent: target.baseagent,
|
|
2088
2125
|
source: current.source,
|
|
2089
2126
|
scope: target.scope,
|
|
2127
|
+
override: readMenuScopeOverride(target.scope, target.fieldPath, target.sel),
|
|
2090
2128
|
field: target.fieldPath,
|
|
2091
2129
|
self: target.sel.self,
|
|
2092
2130
|
...(target.sel.peerKey ? { peerKey: target.sel.peerKey } : {}),
|
|
@@ -2115,6 +2153,7 @@ export async function execMenuQuery(cmd, channel, channelId, userId, args, expli
|
|
|
2115
2153
|
baseagent: target.baseagent,
|
|
2116
2154
|
source: current.source,
|
|
2117
2155
|
scope: target.scope,
|
|
2156
|
+
override: readMenuScopeOverride(target.scope, target.fieldPath, target.sel),
|
|
2118
2157
|
field: target.fieldPath,
|
|
2119
2158
|
self: target.sel.self,
|
|
2120
2159
|
...(target.sel.peerKey ? { peerKey: target.sel.peerKey } : {}),
|
|
@@ -2140,6 +2179,7 @@ export async function execMenuQuery(cmd, channel, channelId, userId, args, expli
|
|
|
2140
2179
|
mode: current.value,
|
|
2141
2180
|
source: current.source,
|
|
2142
2181
|
scope: target.scope,
|
|
2182
|
+
override: readMenuScopeOverride(target.scope, target.fieldPath, target.sel),
|
|
2143
2183
|
field: target.fieldPath,
|
|
2144
2184
|
self: target.sel.self,
|
|
2145
2185
|
...(target.sel.peerKey ? { peerKey: target.sel.peerKey } : {}),
|
|
@@ -2170,6 +2210,7 @@ export async function execMenuQuery(cmd, channel, channelId, userId, args, expli
|
|
|
2170
2210
|
mode: current.value,
|
|
2171
2211
|
source: current.source,
|
|
2172
2212
|
scope: target.scope,
|
|
2213
|
+
override: readMenuScopeOverride(target.scope, target.fieldPath, target.sel),
|
|
2173
2214
|
field: target.fieldPath,
|
|
2174
2215
|
self: target.sel.self,
|
|
2175
2216
|
...(target.sel.peerKey ? { peerKey: target.sel.peerKey } : {}),
|
|
@@ -2180,10 +2221,13 @@ export async function execMenuQuery(cmd, channel, channelId, userId, args, expli
|
|
|
2180
2221
|
if (!evolagent)
|
|
2181
2222
|
return { error: '找不到通道所属 agent', code: 'MISSING_AID' };
|
|
2182
2223
|
const observable = evolagent?.getObservable() ?? false;
|
|
2224
|
+
const override = evolagent?.aid
|
|
2225
|
+
? readMenuScopeOverride('agent', 'observable', { self: evolagent.aid })
|
|
2226
|
+
: null;
|
|
2183
2227
|
const source = evolagent?.aid
|
|
2184
2228
|
? menuConfigSource('observable', { self: evolagent.aid }) ?? 'builtin'
|
|
2185
2229
|
: 'builtin';
|
|
2186
|
-
return { data: { observable, source } };
|
|
2230
|
+
return { data: { observable, scope: 'agent', override, source } };
|
|
2187
2231
|
}
|
|
2188
2232
|
if (cmdBase === '/perm') {
|
|
2189
2233
|
const target = resolveMenuPermissionTarget.call(this, {
|
|
@@ -2231,6 +2275,7 @@ export async function execMenuQuery(cmd, channel, channelId, userId, args, expli
|
|
|
2231
2275
|
mode: current.value,
|
|
2232
2276
|
source: current.source,
|
|
2233
2277
|
scope: target.scope,
|
|
2278
|
+
override: readMenuScopeOverride(target.scope, target.fieldPath, target.sel),
|
|
2234
2279
|
field: target.fieldPath,
|
|
2235
2280
|
self: target.sel.self,
|
|
2236
2281
|
...(target.sel.peerKey ? { peerKey: target.sel.peerKey } : {}),
|
|
@@ -2420,11 +2465,13 @@ export async function execMenuUpdate(cmd, value, channel, channelId, userId, ove
|
|
|
2420
2465
|
const triggerScheduler = this.getTriggerSchedulerForChannel?.(channel);
|
|
2421
2466
|
if (!triggerScheduler)
|
|
2422
2467
|
return { error: '触发器功能未启用', code: 'NOT_SUPPORTED' };
|
|
2423
|
-
const updated = await this.updateTriggerFromPatch(triggerScheduler, patch.nameOrId, patch, channel, channelId, userId ?? '', isAdmin);
|
|
2468
|
+
const updated = await this.updateTriggerFromPatch(triggerScheduler, patch.nameOrId, patch, channel, channelId, userId ?? '', isAdmin, undefined, undefined, subject.isDaemonOwner, subject.canApprovePersistentFullAccess);
|
|
2424
2469
|
if (!updated.ok) {
|
|
2425
2470
|
return {
|
|
2426
2471
|
error: updated.error,
|
|
2427
|
-
code: updated.code ?? (
|
|
2472
|
+
code: updated.code ?? (/不存在/.test(updated.error) ? 'NOT_FOUND'
|
|
2473
|
+
: /无权限|只有 daemon-owner|不能批准/.test(updated.error) ? 'FORBIDDEN'
|
|
2474
|
+
: 'INVALID_ARGS'),
|
|
2428
2475
|
...(updated.currentRevision ? { data: { currentRevision: updated.currentRevision } } : {}),
|
|
2429
2476
|
};
|
|
2430
2477
|
}
|
|
@@ -2474,7 +2521,12 @@ export async function execMenuUpdate(cmd, value, channel, channelId, userId, ove
|
|
|
2474
2521
|
return { error: `无效 baseagent: ${arg},可选: ${valid.join(' / ')}`, code: 'INVALID_VALUE' };
|
|
2475
2522
|
}
|
|
2476
2523
|
const previousBaseagent = evolagent.baseagent;
|
|
2477
|
-
|
|
2524
|
+
try {
|
|
2525
|
+
evolagent.setActiveBaseagent(arg);
|
|
2526
|
+
}
|
|
2527
|
+
catch (error) {
|
|
2528
|
+
return { error: error instanceof Error ? error.message : String(error), code: 'INVALID_VALUE' };
|
|
2529
|
+
}
|
|
2478
2530
|
this.eventBus.publish({
|
|
2479
2531
|
type: 'agent:baseagent-changed',
|
|
2480
2532
|
aid: evolagent.aid,
|
|
@@ -2890,23 +2942,31 @@ export async function execMenuAction(cmd, action, args, channel, channelId, user
|
|
|
2890
2942
|
if (!a.aid)
|
|
2891
2943
|
return { error: '缺少 aid', code: 'INVALID_ARGS' };
|
|
2892
2944
|
const hooks = globalThis.__evolcore_reloadHooks;
|
|
2893
|
-
if (!hooks)
|
|
2945
|
+
if (!this.agentApplicationService && !hooks)
|
|
2894
2946
|
return { error: 'Reload hooks 未初始化', code: 'INTERNAL' };
|
|
2895
2947
|
try {
|
|
2896
2948
|
if (action === 'stop') {
|
|
2897
|
-
|
|
2898
|
-
|
|
2899
|
-
|
|
2949
|
+
const result = this.agentApplicationService
|
|
2950
|
+
? await this.agentApplicationService.stop(a.aid)
|
|
2951
|
+
: this.agentRegistry?.stopAgent
|
|
2952
|
+
? await this.agentRegistry.stopAgent(a.aid, hooks).then(() => ({ ok: true, aid: a.aid }))
|
|
2953
|
+
: { ok: false, error: 'stopAgent 不可用', code: 'INTERNAL' };
|
|
2954
|
+
if (!result.ok)
|
|
2955
|
+
return { error: result.error, code: result.code || 'INTERNAL' };
|
|
2900
2956
|
this.eventBus.publish({ type: 'agent:stopped', aid: a.aid, timestamp: Date.now() });
|
|
2901
2957
|
// 中断该 agent 正在执行的大模型调用
|
|
2902
|
-
const handle = this.agentRegistry
|
|
2958
|
+
const handle = this.agentRegistry?.get(a.aid);
|
|
2903
2959
|
if (handle)
|
|
2904
2960
|
this.messageQueue.interruptByAgent(handle.name);
|
|
2905
2961
|
}
|
|
2906
2962
|
else {
|
|
2907
|
-
|
|
2908
|
-
|
|
2909
|
-
|
|
2963
|
+
const result = this.agentApplicationService
|
|
2964
|
+
? await this.agentApplicationService.start(a.aid)
|
|
2965
|
+
: this.agentRegistry?.startAgent
|
|
2966
|
+
? await this.agentRegistry.startAgent(a.aid, hooks).then(() => ({ ok: true, aid: a.aid }))
|
|
2967
|
+
: { ok: false, error: 'startAgent 不可用', code: 'INTERNAL' };
|
|
2968
|
+
if (!result.ok)
|
|
2969
|
+
return { error: result.error, code: result.code || 'INTERNAL' };
|
|
2910
2970
|
this.eventBus.publish({ type: 'agent:started', aid: a.aid, timestamp: Date.now() });
|
|
2911
2971
|
}
|
|
2912
2972
|
return { data: { aid: a.aid, action } };
|
|
@@ -2929,7 +2989,7 @@ export async function execMenuAction(cmd, action, args, channel, channelId, user
|
|
|
2929
2989
|
}
|
|
2930
2990
|
}
|
|
2931
2991
|
}
|
|
2932
|
-
return await execAgentAction(action, a, userId ?? '', this.eventBus);
|
|
2992
|
+
return await execAgentAction(action, a, userId ?? '', this.eventBus, this.agentApplicationService);
|
|
2933
2993
|
}
|
|
2934
2994
|
// ── 关系级 /trigger(不走 owners;复用 isAdmin + scoped 逻辑,D4 直调底层) ──
|
|
2935
2995
|
if (cmdBase === '/trigger') {
|
|
@@ -2979,9 +3039,15 @@ export async function execMenuAction(cmd, action, args, channel, channelId, user
|
|
|
2979
3039
|
triggerThread,
|
|
2980
3040
|
baseagent: args.baseagent,
|
|
2981
3041
|
};
|
|
2982
|
-
const r = await this.registerTriggerFromParsed(parsed, channel, channelId, userId ?? '', undefined, this.resolveMenuChatType(channel, channelId, explicitChatType), undefined, isAdmin);
|
|
2983
|
-
if (!r.ok)
|
|
2984
|
-
|
|
3042
|
+
const r = await this.registerTriggerFromParsed(parsed, channel, channelId, userId ?? '', undefined, this.resolveMenuChatType(channel, channelId, explicitChatType), undefined, isAdmin, subject.isDaemonOwner, subject.canApprovePersistentFullAccess);
|
|
3043
|
+
if (!r.ok) {
|
|
3044
|
+
const code = /已存在|exists|重复/.test(r.error)
|
|
3045
|
+
? 'CONFLICT'
|
|
3046
|
+
: /不能批准|只有 daemon-owner|无权限/.test(r.error)
|
|
3047
|
+
? 'FORBIDDEN'
|
|
3048
|
+
: 'INVALID_ARGS';
|
|
3049
|
+
return { error: r.error, code };
|
|
3050
|
+
}
|
|
2985
3051
|
return { data: { id: r.trigger.id, name: r.trigger.name, nextFireAt: r.trigger.nextFireAt } };
|
|
2986
3052
|
}
|
|
2987
3053
|
if (action === 'cancel') {
|
|
@@ -3006,7 +3072,35 @@ export async function execMenuAction(cmd, action, args, channel, channelId, user
|
|
|
3006
3072
|
const trigger = this.findTriggerDefinition(triggerScheduler, nameOrId, userId ?? '', channel, isAdmin);
|
|
3007
3073
|
if (!trigger)
|
|
3008
3074
|
return { error: '触发器不存在或无权限', code: 'NOT_FOUND' };
|
|
3075
|
+
if (action === 'enable' && trigger.execution.permissionMode === 'fullaccess') {
|
|
3076
|
+
if (!isFullAccessEnabled())
|
|
3077
|
+
return { error: 'fullaccess 功能未启用', code: 'NOT_SUPPORTED' };
|
|
3078
|
+
if (!subject.isDaemonOwner)
|
|
3079
|
+
return { error: '只有 daemon-owner 可以启用 fullaccess Trigger', code: 'FORBIDDEN' };
|
|
3080
|
+
if (subject.canApprovePersistentFullAccess === false) {
|
|
3081
|
+
return { error: '当前认证通道不能批准持久 fullaccess Trigger', code: 'FORBIDDEN' };
|
|
3082
|
+
}
|
|
3083
|
+
const authorizedBy = subject.principalId ?? userId;
|
|
3084
|
+
if (!authorizedBy)
|
|
3085
|
+
return { error: '无法确认 fullaccess Trigger 的批准者', code: 'FORBIDDEN' };
|
|
3086
|
+
// Enabling is itself a persistent fullaccess configuration action. Keep
|
|
3087
|
+
// the canonical owner provenance on definitions created before this
|
|
3088
|
+
// field existed, or after an owner explicitly re-enables the Trigger.
|
|
3089
|
+
triggerScheduler.update(trigger.id, { ...trigger, authorizedBy });
|
|
3090
|
+
}
|
|
3009
3091
|
const updated = triggerScheduler.setEnabled(trigger.id, action === 'enable');
|
|
3092
|
+
if (action === 'enable' && updated.execution.permissionMode === 'fullaccess') {
|
|
3093
|
+
auditFullAccessEvent({
|
|
3094
|
+
event: 'fullaccess.trigger.configured',
|
|
3095
|
+
source: 'trigger',
|
|
3096
|
+
actorId: userId,
|
|
3097
|
+
processRole: subject.isDaemonOwner ? 'daemon-owner' : 'none',
|
|
3098
|
+
agentAid: updated.agentAid,
|
|
3099
|
+
triggerId: updated.id,
|
|
3100
|
+
authorizedBy: updated.authorizedBy,
|
|
3101
|
+
reason: 'fullaccess Trigger enabled',
|
|
3102
|
+
});
|
|
3103
|
+
}
|
|
3010
3104
|
return { data: { id: updated.id, enabled: updated.enabled } };
|
|
3011
3105
|
}
|
|
3012
3106
|
if (action === 'delete') {
|
|
@@ -3021,6 +3115,18 @@ export async function execMenuAction(cmd, action, args, channel, channelId, user
|
|
|
3021
3115
|
if (trigger.enabled)
|
|
3022
3116
|
return { error: '请先禁用触发器再删除', code: 'INVALID_STATE' };
|
|
3023
3117
|
const deleted = triggerScheduler.delete(trigger.id);
|
|
3118
|
+
if (deleted.execution.permissionMode === 'fullaccess') {
|
|
3119
|
+
auditFullAccessEvent({
|
|
3120
|
+
event: 'fullaccess.trigger.deescalated',
|
|
3121
|
+
source: 'trigger',
|
|
3122
|
+
actorId: userId,
|
|
3123
|
+
processRole: subject.isDaemonOwner ? 'daemon-owner' : 'none',
|
|
3124
|
+
agentAid: deleted.agentAid,
|
|
3125
|
+
triggerId: deleted.id,
|
|
3126
|
+
authorizedBy: deleted.authorizedBy,
|
|
3127
|
+
reason: 'fullaccess Trigger deleted',
|
|
3128
|
+
});
|
|
3129
|
+
}
|
|
3024
3130
|
return { data: { id: deleted.id, deleted: true } };
|
|
3025
3131
|
}
|
|
3026
3132
|
if (action === 'run' || action === 'test') {
|
|
@@ -3124,72 +3230,146 @@ export async function execMenuAction(cmd, action, args, channel, channelId, user
|
|
|
3124
3230
|
return { error: `当前 Agent (${agent.name}) 不支持按轮次分叉`, code: 'NOT_SUPPORTED' };
|
|
3125
3231
|
}
|
|
3126
3232
|
const sourceKey = this.getQueueKey(source, channel, channelId);
|
|
3127
|
-
const
|
|
3233
|
+
const sourceIsBusy = (current, currentAgent) => !!current.processingState
|
|
3234
|
+
|| !!current.metadata?.turnState?.active
|
|
3128
3235
|
|| this.messageQueue.isProcessing?.(sourceKey)
|
|
3129
|
-
|| this.messageQueue.getQueueLength?.(sourceKey) > 0
|
|
3130
|
-
||
|
|
3131
|
-
|
|
3236
|
+
|| (this.messageQueue.getQueueLength?.(sourceKey) ?? 0) > 0
|
|
3237
|
+
|| currentAgent.hasActiveStream(sourceKey)
|
|
3238
|
+
|| (this.processor?.isPauseRequested?.(sourceKey) ?? false);
|
|
3239
|
+
if (!source.threadId && sourceIsBusy(source, agent)) {
|
|
3132
3240
|
return { error: '来源会话正在处理消息,请完成后重试', code: 'BUSY' };
|
|
3241
|
+
}
|
|
3133
3242
|
const targetKey = JSON.stringify([source.channelType || source.channel, source.selfAID || '', channelId, targetThreadId]);
|
|
3134
3243
|
if (topicForkTargetsInFlight.has(targetKey))
|
|
3135
3244
|
return { error: '目标话题正在创建', code: 'CONFLICT' };
|
|
3136
3245
|
topicForkTargetsInFlight.add(targetKey);
|
|
3137
|
-
const releaseLock = this.messageQueue.acquireLock?.(sourceKey) ?? (() => { });
|
|
3138
3246
|
try {
|
|
3139
|
-
|
|
3140
|
-
|
|
3141
|
-
|
|
3142
|
-
|
|
3143
|
-
|
|
3144
|
-
|
|
3145
|
-
|
|
3146
|
-
|
|
3147
|
-
|
|
3148
|
-
|
|
3149
|
-
|
|
3150
|
-
|
|
3151
|
-
|
|
3152
|
-
|
|
3153
|
-
|
|
3154
|
-
|
|
3155
|
-
|
|
3156
|
-
|
|
3157
|
-
|
|
3158
|
-
|
|
3159
|
-
|
|
3160
|
-
|
|
3161
|
-
|
|
3162
|
-
|
|
3163
|
-
|
|
3164
|
-
|
|
3165
|
-
|
|
3166
|
-
|
|
3167
|
-
|
|
3168
|
-
|
|
3169
|
-
|
|
3170
|
-
|
|
3171
|
-
|
|
3172
|
-
|
|
3173
|
-
|
|
3174
|
-
|
|
3175
|
-
|
|
3176
|
-
|
|
3177
|
-
|
|
3178
|
-
|
|
3179
|
-
|
|
3180
|
-
|
|
3181
|
-
|
|
3182
|
-
sourceTurn: forkTurn.index,
|
|
3247
|
+
const executeFork = async (current, currentAgent) => {
|
|
3248
|
+
const messages = await currentAgent.getSessionMessages(current.agentSessionId, current.projectPath);
|
|
3249
|
+
const turns = buildSessionTurnList(messages);
|
|
3250
|
+
const forkTurn = turns.find((turn) => turn.assistantUuid === sourceAssistantMessageId);
|
|
3251
|
+
if (!forkTurn)
|
|
3252
|
+
return { error: '分叉点已失效,请刷新历史后重试', code: 'NOT_FOUND' };
|
|
3253
|
+
const forkedAgentSessionId = await currentAgent.forkSessionAt(current.agentSessionId, current.projectPath, sourceAssistantMessageId, name || undefined);
|
|
3254
|
+
const topic = await this.sessionManager.createForkedThreadSession(current, forkedAgentSessionId, targetThreadId, {
|
|
3255
|
+
name: name || undefined,
|
|
3256
|
+
creatorPeerId: userId,
|
|
3257
|
+
sourceAssistantMessageId,
|
|
3258
|
+
sourceTurn: forkTurn.index,
|
|
3259
|
+
requestId,
|
|
3260
|
+
});
|
|
3261
|
+
await currentAgent.updateSessionMetadata?.(forkedAgentSessionId, {
|
|
3262
|
+
evolcoreSessionId: topic.id,
|
|
3263
|
+
sourceSessionId: current.id,
|
|
3264
|
+
sourceAssistantMessageId,
|
|
3265
|
+
sourceTurn: forkTurn.index,
|
|
3266
|
+
}).catch((error) => {
|
|
3267
|
+
logger.debug(`[MenuHandler] Topic fork metadata sync failed: ${error}`);
|
|
3268
|
+
});
|
|
3269
|
+
this.eventBus.publish({
|
|
3270
|
+
type: 'session:forked',
|
|
3271
|
+
sessionId: topic.id,
|
|
3272
|
+
sourceSessionId: current.id,
|
|
3273
|
+
name: topic.name,
|
|
3274
|
+
threadId: topic.threadId,
|
|
3275
|
+
sourceAssistantMessageId,
|
|
3276
|
+
sourceTurn: forkTurn.index,
|
|
3277
|
+
});
|
|
3278
|
+
return {
|
|
3279
|
+
data: {
|
|
3280
|
+
action: 'fork',
|
|
3281
|
+
success: true,
|
|
3282
|
+
topic: {
|
|
3283
|
+
...buildSessionPayload(topic, topic.name || ''),
|
|
3284
|
+
threadId: topic.threadId,
|
|
3285
|
+
sourceSessionId: current.id,
|
|
3286
|
+
sourceThreadId: current.threadId || null,
|
|
3287
|
+
sourceAssistantMessageId,
|
|
3288
|
+
sourceTurn: forkTurn.index,
|
|
3289
|
+
},
|
|
3183
3290
|
},
|
|
3184
|
-
}
|
|
3291
|
+
};
|
|
3185
3292
|
};
|
|
3293
|
+
const runForkUnderBarrier = async () => {
|
|
3294
|
+
if (source.threadId) {
|
|
3295
|
+
if (typeof this.messageQueue.withSessionBarrier !== 'function') {
|
|
3296
|
+
return { error: '当前运行环境不支持安全话题分叉', code: 'NOT_SUPPORTED' };
|
|
3297
|
+
}
|
|
3298
|
+
return this.messageQueue.withSessionBarrier(sourceKey, async (snapshot) => {
|
|
3299
|
+
const latest = await this.sessionManager.getSessionById?.(source.id);
|
|
3300
|
+
const current = latest;
|
|
3301
|
+
if (!current) {
|
|
3302
|
+
return { error: '来源会话不存在或归属已变化', code: 'NOT_FOUND' };
|
|
3303
|
+
}
|
|
3304
|
+
if (current.id !== source.id || (current.threadId || '') !== (source.threadId || '')) {
|
|
3305
|
+
return { error: '来源会话不存在或归属已变化', code: 'NOT_FOUND' };
|
|
3306
|
+
}
|
|
3307
|
+
if (!current.agentSessionId) {
|
|
3308
|
+
return { error: '来源会话暂无对话历史', code: 'INVALID_STATE' };
|
|
3309
|
+
}
|
|
3310
|
+
const currentAgent = this.getAgent(channel, current.baseagent);
|
|
3311
|
+
if (!currentAgent.capabilities?.forkAtTurn || !currentAgent.getSessionMessages || !currentAgent.forkSessionAt) {
|
|
3312
|
+
return { error: `当前 Agent (${currentAgent.name}) 不支持按轮次分叉`, code: 'NOT_SUPPORTED' };
|
|
3313
|
+
}
|
|
3314
|
+
const probes = {
|
|
3315
|
+
paused: () => this.processor?.isPauseRequested?.(current.id) ?? false,
|
|
3316
|
+
activeStream: () => currentAgent.hasActiveStream(current.id),
|
|
3317
|
+
};
|
|
3318
|
+
if (this.permissionGateway) {
|
|
3319
|
+
probes.pendingPermission = () => this.permissionGateway.getPendingRequests(current.id).length > 0;
|
|
3320
|
+
}
|
|
3321
|
+
if (this.interactionRouter) {
|
|
3322
|
+
probes.pendingInteraction = () => this.interactionRouter.getPending(current.id).length > 0;
|
|
3323
|
+
}
|
|
3324
|
+
const selfAid = current.selfAID || this.getOwningAgent?.(channel)?.aid;
|
|
3325
|
+
if (this.handoffRuntime) {
|
|
3326
|
+
probes.activeHandoff = () => {
|
|
3327
|
+
if (!selfAid)
|
|
3328
|
+
throw new Error('handoff activity owner unavailable');
|
|
3329
|
+
return this.handoffRuntime.hasActiveSessionWork(selfAid, current.id);
|
|
3330
|
+
};
|
|
3331
|
+
probes.openHandoffTarget = () => {
|
|
3332
|
+
if (!selfAid)
|
|
3333
|
+
throw new Error('handoff activity owner unavailable');
|
|
3334
|
+
return this.handoffRuntime.hasOpenTarget(selfAid, current.id);
|
|
3335
|
+
};
|
|
3336
|
+
}
|
|
3337
|
+
const activity = probeSessionActivity({
|
|
3338
|
+
prequeue: snapshot.prequeue,
|
|
3339
|
+
active: snapshot.active,
|
|
3340
|
+
queued: snapshot.queued,
|
|
3341
|
+
dispatching: snapshot.dispatching,
|
|
3342
|
+
processing: !!current.processingState,
|
|
3343
|
+
activeTurn: !!current.metadata?.turnState?.active,
|
|
3344
|
+
}, probes);
|
|
3345
|
+
if (activity === 'busy') {
|
|
3346
|
+
return { error: '来源会话正在处理消息,请完成后重试', code: 'BUSY' };
|
|
3347
|
+
}
|
|
3348
|
+
if (activity === 'unknown') {
|
|
3349
|
+
return { error: '无法可靠确认来源会话为空闲,请稍后重试', code: 'INVALID_STATE' };
|
|
3350
|
+
}
|
|
3351
|
+
return executeFork(current, currentAgent);
|
|
3352
|
+
});
|
|
3353
|
+
}
|
|
3354
|
+
// Main-session sources keep compatibility with older embeddings.
|
|
3355
|
+
const release = this.messageQueue.acquireLock?.(sourceKey) ?? (() => { });
|
|
3356
|
+
try {
|
|
3357
|
+
if (sourceIsBusy(source, agent)) {
|
|
3358
|
+
return { error: '来源会话正在处理消息,请完成后重试', code: 'BUSY' };
|
|
3359
|
+
}
|
|
3360
|
+
return await executeFork(source, agent);
|
|
3361
|
+
}
|
|
3362
|
+
finally {
|
|
3363
|
+
release();
|
|
3364
|
+
}
|
|
3365
|
+
};
|
|
3366
|
+
return await runForkUnderBarrier();
|
|
3186
3367
|
}
|
|
3187
3368
|
catch (error) {
|
|
3188
3369
|
logger.error('[MenuHandler] Topic history fork failed:', error);
|
|
3189
3370
|
return { error: error instanceof Error ? error.message : '话题分叉失败', code: 'EXEC_FAILED' };
|
|
3190
3371
|
}
|
|
3191
3372
|
finally {
|
|
3192
|
-
releaseLock();
|
|
3193
3373
|
topicForkTargetsInFlight.delete(targetKey);
|
|
3194
3374
|
}
|
|
3195
3375
|
}
|
|
@@ -3694,6 +3874,7 @@ async function execMenuForSystemControl(payload, context) {
|
|
|
3694
3874
|
conversationId: context.actorAid || SYSTEM_CONTROL_CHANNEL,
|
|
3695
3875
|
processOwners: context.owners,
|
|
3696
3876
|
fromControlChannel: true,
|
|
3877
|
+
canApprovePersistentFullAccess: context.canApprovePersistentFullAccess,
|
|
3697
3878
|
});
|
|
3698
3879
|
const trustedIdentity = trustedSubject.identity;
|
|
3699
3880
|
const cmd = name ? (menuCommandForName(name) ?? payload.cmd) : payload.cmd;
|
|
@@ -3820,20 +4001,37 @@ export async function execMenuForEcweb(payload, trusted) {
|
|
|
3820
4001
|
data: authorizationDenialData('DAEMON_OWNER_REQUIRED'),
|
|
3821
4002
|
});
|
|
3822
4003
|
}
|
|
3823
|
-
if (!trusted || (!trusted.localDirect &&
|
|
4004
|
+
if (!trusted || (!trusted.localDirect && trusted.authenticated !== true)) {
|
|
4005
|
+
return menuFailure({ id, ...(name ? { name } : {}) }, {
|
|
4006
|
+
code: 'PERMISSION_DENIED',
|
|
4007
|
+
message: 'ECWeb operation requires an authenticated ECWeb connection',
|
|
4008
|
+
data: { $schema_version: 1, kind: 'role_permission_denied', self: payload?.args?.self ?? null },
|
|
4009
|
+
});
|
|
4010
|
+
}
|
|
4011
|
+
// A verified ECWeb connection has daemon-owner management authority. If the
|
|
4012
|
+
// bearer token has no explicit owner provenance (the normal pairing path),
|
|
4013
|
+
// use the first currently configured owner as the canonical management
|
|
4014
|
+
// principal. This keeps audit/Trigger approval attribution deterministic
|
|
4015
|
+
// without requiring owner-bound pairing metadata.
|
|
4016
|
+
const userId = (trusted.actorAid && owners.includes(trusted.actorAid))
|
|
4017
|
+
? trusted.actorAid
|
|
4018
|
+
: owners[0] ?? (trusted.localDirect ? 'local-direct' : undefined);
|
|
4019
|
+
if (!userId) {
|
|
3824
4020
|
return menuFailure({ id, ...(name ? { name } : {}) }, {
|
|
3825
4021
|
code: 'PERMISSION_DENIED',
|
|
3826
|
-
message: 'ECWeb operation requires
|
|
4022
|
+
message: 'ECWeb operation requires configured daemon owners',
|
|
3827
4023
|
data: { $schema_version: 1, kind: 'role_permission_denied', self: payload?.args?.self ?? null },
|
|
3828
4024
|
});
|
|
3829
4025
|
}
|
|
3830
|
-
// A verified local ECWeb connection acts through the configured human
|
|
3831
|
-
// DaemonOwner identity; it is not an anonymous daemon service principal.
|
|
3832
|
-
const userId = trusted.actorAid ?? (trusted.localDirect ? (owners[0] || 'local-direct') : undefined);
|
|
3833
4026
|
return execMenuForSystemControl.call(this, payload, {
|
|
3834
4027
|
source: 'ecweb',
|
|
3835
|
-
actorAid: userId
|
|
4028
|
+
actorAid: userId,
|
|
3836
4029
|
localDirect: trusted.localDirect,
|
|
4030
|
+
// The ECWeb server/IPC control-token gate authenticates this management
|
|
4031
|
+
// request. All authenticated ECWeb connections are equally eligible to
|
|
4032
|
+
// approve persistent fullaccess; actorAid is used only when it identifies
|
|
4033
|
+
// a current configured owner.
|
|
4034
|
+
canApprovePersistentFullAccess: true,
|
|
3837
4035
|
owners,
|
|
3838
4036
|
});
|
|
3839
4037
|
}
|
|
@@ -3861,6 +4059,7 @@ export async function execMenuForControl(payload, peerId) {
|
|
|
3861
4059
|
source: 'control',
|
|
3862
4060
|
actorAid: peerId,
|
|
3863
4061
|
localDirect: false,
|
|
4062
|
+
canApprovePersistentFullAccess: false,
|
|
3864
4063
|
owners,
|
|
3865
4064
|
});
|
|
3866
4065
|
}
|