evolcore 0.0.20 → 0.0.22
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 +65 -0
- package/README.md +58 -9
- package/bin/codex-managed-hook.mjs +3 -0
- package/bin/install-codex-managed-hooks.mjs +3 -1
- package/dist/agents/baseagent.js +10 -6
- package/dist/agents/claude-runner.js +393 -108
- package/dist/agents/codex-app-server-client.js +41 -7
- package/dist/agents/codex-runner.js +1292 -220
- package/dist/agents/ecagent-runner.js +171 -61
- 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 +59 -2
- package/dist/aun/aid/identity.js +4 -1
- package/dist/aun/aid/index.js +1 -1
- package/dist/aun/msg/group.js +72 -6
- package/dist/aun/msg/history.js +213 -36
- package/dist/aun/msg/managed-operation.js +58 -9
- package/dist/aun/msg/p2p.js +5 -0
- package/dist/aun/outbox.js +189 -80
- package/dist/aun/service-proxy.js +43 -25
- package/dist/channels/aun.js +618 -123
- 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 +98 -123
- package/dist/cli/init.js +27 -15
- package/dist/cli/task-context.js +50 -0
- package/dist/cli/trigger-command.js +14 -5
- package/dist/cli/watch-logs.js +10 -3
- package/dist/config/builtin-roles.js +1 -0
- package/dist/config/config-field-policy.js +19 -5
- package/dist/config/config-manager.js +167 -22
- package/dist/config/contact-book-store.js +25 -3
- 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 +20 -9
- package/dist/config/role-service.js +54 -3
- package/dist/config/schema-migration.js +550 -0
- package/dist/config-store.js +151 -9
- 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 +43 -1
- package/dist/core/auth/auth-gateway.js +41 -4
- package/dist/core/auth/authorization-audit.js +216 -8
- package/dist/core/auth/operation-authorizer.js +41 -1
- package/dist/core/auth/operation-catalog.js +9 -1
- package/dist/core/bootstrap-messages.js +8 -0
- package/dist/core/bootstrap-service.js +99 -27
- package/dist/core/causation/aun-association.js +7 -4
- package/dist/core/command/agent-control.js +56 -16
- package/dist/core/command/command-handler.js +311 -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 +288 -80
- package/dist/core/command/menu-protocol.js +1 -1
- 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 +377 -36
- package/dist/core/data-migration.js +11 -1
- package/dist/core/event-catalog.js +37 -0
- package/dist/core/evolagent.js +4 -0
- package/dist/core/handoff/dispatcher.js +4 -0
- package/dist/core/handoff/runtime.js +33 -3
- package/dist/core/handoff/store.js +32 -9
- package/dist/core/inference/text-inference.js +7 -15
- package/dist/core/message/im-renderer.js +90 -87
- package/dist/core/message/{inbound-admission.js → message-admission.js} +51 -1
- package/dist/core/message/message-bridge.js +184 -12
- package/dist/core/message/message-log.js +47 -7
- package/dist/core/message/message-queue.js +227 -16
- package/dist/core/message/message-utils.js +12 -5
- package/dist/core/message/response-engine.js +658 -109
- 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 +627 -69
- package/dist/core/permission/mode.js +18 -3
- package/dist/core/{protected-paths.js → permission/protected-paths.js} +27 -14
- package/dist/core/permission/readonly-shell-query.js +263 -9
- package/dist/core/permission/sandbox-runtime.js +159 -1
- package/dist/core/permission/tool-error-code.js +12 -0
- package/dist/core/permission/tool-policy.js +618 -23
- package/dist/core/session/session-fs-store.js +154 -5
- package/dist/core/session/session-manager.js +329 -30
- package/dist/core/session/session-renew.js +37 -13
- package/dist/core/session/session-turn-coordinator.js +16 -5
- package/dist/eck/kit-renderer.js +1 -1
- package/dist/index.js +316 -50
- package/dist/ipc.js +459 -29
- package/dist/paths.js +82 -7
- package/dist/response-system/context-builder.js +1 -7
- package/dist/response-system/engines/v1/proactive-flow.js +7 -2
- package/dist/stats/price-resolver.js +4 -0
- package/dist/trigger/anomaly-store.js +1 -0
- package/dist/trigger/feedback.js +70 -7
- package/dist/trigger/history.js +79 -4
- package/dist/trigger/legacy-session-history.js +2 -2
- package/dist/trigger/parser.js +13 -3
- package/dist/trigger/scheduler.js +20 -3
- 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 +30 -6
- 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/tool-summary.js +59 -0
- package/dist/utils/windows-shell-trust.js +201 -0
- package/kits/docs/INDEX.md +1 -1
- package/kits/docs/evolcore/INDEX.md +3 -3
- package/kits/docs/evolcore/agent-create.md +146 -0
- package/kits/docs/evolcore/agent.md +6 -0
- package/kits/docs/evolcore/contact.md +7 -1
- package/kits/docs/evolcore/group-collaboration.md +251 -0
- package/kits/docs/evolcore/group-rules.md +1 -19
- package/kits/docs/evolcore/group.md +3 -1
- package/kits/docs/evolcore/msg.md +16 -0
- package/kits/docs/evolcore/trigger.md +6 -3
- package/kits/docs/prompt-loading-architecture.md +6 -0
- package/kits/eck_message_manifest.json +6 -6
- package/kits/schemas/_meta.json +7 -4
- package/kits/schemas/agent-config.schema.11.json +13 -0
- package/kits/schemas/agent-config.schema.12.json +427 -0
- package/kits/schemas/daemon.schema.5.json +0 -1
- package/kits/schemas/daemon.schema.6.json +131 -0
- package/kits/schemas/defaults.schema.5.json +15 -3
- 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/message-fragments/item.md +1 -1
- 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/kits/templates/system-fragments/bootstrap.md +2 -1
- package/kits/templates/system-fragments/commands.md +2 -2
- package/package.json +6 -3
- package/skills/eclink/SKILL.md +2 -0
- package/dist/config/aun-gateway-config.js +0 -2
|
@@ -6,6 +6,7 @@ import { hasModelSwitcher } from '../../agents/runner-types.js';
|
|
|
6
6
|
import { getCodexEfforts } from '../../agents/codex-runner.js';
|
|
7
7
|
import { execCodexCliSync, resolveCodexCliPath } from '../../utils/codex-cli.js';
|
|
8
8
|
import { resolvePaths, getPackageRoot, daemonControlDir } from '../../paths.js';
|
|
9
|
+
import { inspectDataMigrationRequirement } from '../data-migration.js';
|
|
9
10
|
import { buildEnvelope, isDeliveryTarget, isDeliveryTargetForChannel, replyContextFromSession } from '../message/message-utils.js';
|
|
10
11
|
import path from 'path';
|
|
11
12
|
import fs from 'fs';
|
|
@@ -16,7 +17,7 @@ import { CronExpressionParser } from 'cron-parser';
|
|
|
16
17
|
import { parseDuration } from '../../trigger/parser.js';
|
|
17
18
|
import { checkLatestVersion, getLocalVersion, isLinkedInstall, compareVersions, resolveGlobalPkg } from '../../utils/npm-ops.js';
|
|
18
19
|
import { commandExists, spawnDetachedNode } from '../../utils/cross-platform.js';
|
|
19
|
-
import { loadDefaults, loadDaemonConfig } from '../../config-store.js';
|
|
20
|
+
import { isFullAccessEnabled, loadDefaults, loadDaemonConfig } from '../../config-store.js';
|
|
20
21
|
import { WEB_PACKAGE_NAME } from '../../product.js';
|
|
21
22
|
import { read as cfgRead, resolveEffective, resolveEffectiveFieldWithSource, routeFieldPath, write as cfgWrite, ConfigTarget, } from '../../config/config-manager.js';
|
|
22
23
|
import { execAgentAction, execAgentQuery, execAgentOptions, resolveProjectPath } from './agent-control.js';
|
|
@@ -25,7 +26,7 @@ import { displaySessionTitle } from '../session/session-title.js';
|
|
|
25
26
|
import { buildSessionTurnList } from '../session/session-turns.js';
|
|
26
27
|
import { isCapabilityType, listCapabilityOptions, queryCapabilityTypes, resolveCapabilityContext, updateCapabilityPolicy, } from '../capability/capability-manager.js';
|
|
27
28
|
import { normalizeCliArgv, parseCliIntent, parseLegacyCliCommand, validateCliArgv, withDefaultRelationContext } from './cli-intent-parser.js';
|
|
28
|
-
import { auditCommandAuthorization, hashArgv } from '../auth/authorization-audit.js';
|
|
29
|
+
import { auditCommandAuthorization, auditFullAccessEvent, hashArgv } from '../auth/authorization-audit.js';
|
|
29
30
|
import { authorizeOperation, buildAuthSubject } from '../auth/auth-gateway.js';
|
|
30
31
|
import { authorizationDenialData, formatAuthorizationDenial } from '../auth/authorization-denial.js';
|
|
31
32
|
import { evaluateRoleOperationCapability } from '../auth/operation-authorizer.js';
|
|
@@ -43,6 +44,7 @@ import { roleMenuAction, roleMenuOperation, roleMenuOptions, roleMenuQuery, role
|
|
|
43
44
|
import { handleConnectMenu, connectMenuOperation } from './connect-menu.js';
|
|
44
45
|
import { groupMenuAuthorizationArgs, groupMenuIntent, handleGroupMenu, } from './group-menu.js';
|
|
45
46
|
import { getMenuCatalog, execMenuValueBatch, resolveExplicitMenuExecutionContext, validateMenuCatalogArgs, validateMenuValueBatchArgs } from './menu-catalog.js';
|
|
47
|
+
import { probeSessionActivity } from './slash-gate.js';
|
|
46
48
|
/**
|
|
47
49
|
* 获取 baseagent CLI 的版本号(claude/gemini/codex)。
|
|
48
50
|
* 失败返回 null(命令不存在或执行失败)。
|
|
@@ -455,6 +457,24 @@ function menuConfigSource(fieldPath, sel) {
|
|
|
455
457
|
catch { }
|
|
456
458
|
return null;
|
|
457
459
|
}
|
|
460
|
+
/** Read the explicit value stored in the scope being queried, without resolving inheritance. */
|
|
461
|
+
function readMenuScopeOverride(scope, fieldPath, sel) {
|
|
462
|
+
const target = scope === 'agent' ? ConfigTarget.Agent : ConfigTarget.Relation;
|
|
463
|
+
try {
|
|
464
|
+
let current = cfgRead(target, sel, { cache: false });
|
|
465
|
+
for (const part of fieldPath.split('.')) {
|
|
466
|
+
if (!current || typeof current !== 'object'
|
|
467
|
+
|| !Object.prototype.hasOwnProperty.call(current, part)) {
|
|
468
|
+
return null;
|
|
469
|
+
}
|
|
470
|
+
current = current[part];
|
|
471
|
+
}
|
|
472
|
+
return current === undefined ? null : current;
|
|
473
|
+
}
|
|
474
|
+
catch {
|
|
475
|
+
return null;
|
|
476
|
+
}
|
|
477
|
+
}
|
|
458
478
|
function menuModelConfigFieldPath(target) {
|
|
459
479
|
return target.fieldPath;
|
|
460
480
|
}
|
|
@@ -1131,6 +1151,8 @@ function buildRoleMenuContext(owner, channel, subject) {
|
|
|
1131
1151
|
actorRoleSource: subject.roleSource,
|
|
1132
1152
|
actorAllowAccess: subject.allowAccess,
|
|
1133
1153
|
actorAid: subject.actorId,
|
|
1154
|
+
isDaemonOwner: subject.isDaemonOwner,
|
|
1155
|
+
fromControlChannel: subject.fromControlChannel,
|
|
1134
1156
|
chatType: subject.chatType,
|
|
1135
1157
|
aunAvailable,
|
|
1136
1158
|
...(contactAdapter?.send ? {
|
|
@@ -1549,7 +1571,9 @@ export async function getSubMenuItems(cmd, channel, channelId, userId, args, ove
|
|
|
1549
1571
|
// 控制 channel:返回全量;agent channel:仅返回自身单条。
|
|
1550
1572
|
if (cmd === '/agent') {
|
|
1551
1573
|
if (fromControlChannel) {
|
|
1552
|
-
const res =
|
|
1574
|
+
const res = this.agentApplicationService
|
|
1575
|
+
? await execAgentOptions(args, this.agentApplicationService)
|
|
1576
|
+
: await execAgentOptions(args);
|
|
1553
1577
|
if ('error' in res)
|
|
1554
1578
|
throw { code: res.code, message: res.error };
|
|
1555
1579
|
return res.data.agents.map(ag => ({ value: ag.aid, label: ag.name || ag.aid, desc: ag.status }));
|
|
@@ -1558,7 +1582,9 @@ export async function getSubMenuItems(cmd, channel, channelId, userId, args, ove
|
|
|
1558
1582
|
const selfAid = this.getOwningAgent?.(channel)?.aid;
|
|
1559
1583
|
if (!selfAid)
|
|
1560
1584
|
throw { code: 'FORBIDDEN', message: '当前 channel 无绑定 agent' };
|
|
1561
|
-
const res =
|
|
1585
|
+
const res = this.agentApplicationService
|
|
1586
|
+
? await execAgentQuery({ aid: selfAid }, this.agentApplicationService)
|
|
1587
|
+
: await execAgentQuery({ aid: selfAid });
|
|
1562
1588
|
if ('error' in res)
|
|
1563
1589
|
throw { code: res.code, message: res.error };
|
|
1564
1590
|
const ag = res.data;
|
|
@@ -1902,12 +1928,21 @@ export async function execMenuQuery(cmd, channel, channelId, userId, args, expli
|
|
|
1902
1928
|
// 授权由 agent.list/agent.show operation 决定;channel 闸门只负责目标范围。
|
|
1903
1929
|
if (cmdBase === '/agent') {
|
|
1904
1930
|
if (fromControlChannel) {
|
|
1905
|
-
|
|
1931
|
+
if (args?.aid) {
|
|
1932
|
+
return this.agentApplicationService
|
|
1933
|
+
? await execAgentQuery(args, this.agentApplicationService)
|
|
1934
|
+
: await execAgentQuery(args);
|
|
1935
|
+
}
|
|
1936
|
+
return this.agentApplicationService
|
|
1937
|
+
? await execAgentOptions(args, this.agentApplicationService)
|
|
1938
|
+
: await execAgentOptions(args);
|
|
1906
1939
|
}
|
|
1907
1940
|
const selfAid = this.getOwningAgent?.(channel)?.aid;
|
|
1908
1941
|
if (!selfAid)
|
|
1909
1942
|
return { error: '当前 channel 无绑定 agent', code: 'FORBIDDEN' };
|
|
1910
|
-
return
|
|
1943
|
+
return this.agentApplicationService
|
|
1944
|
+
? await execAgentQuery({ ...(args ?? {}), aid: selfAid }, this.agentApplicationService)
|
|
1945
|
+
: await execAgentQuery({ ...(args ?? {}), aid: selfAid });
|
|
1911
1946
|
}
|
|
1912
1947
|
// ── /gateway 查询(只读,列出全部作用域的网关配置;apiKey 已掩码) ──
|
|
1913
1948
|
if (cmdBase === '/gateway') {
|
|
@@ -2058,12 +2093,15 @@ export async function execMenuQuery(cmd, channel, channelId, userId, args, expli
|
|
|
2058
2093
|
: null;
|
|
2059
2094
|
const targetAgent = requestedAgent ?? evolagent;
|
|
2060
2095
|
const value = targetAgent?.baseagent ?? targetAgent?.config?.active_baseagent ?? null;
|
|
2096
|
+
const override = targetAgent?.aid
|
|
2097
|
+
? readMenuScopeOverride('agent', 'active_baseagent', { self: targetAgent.aid })
|
|
2098
|
+
: null;
|
|
2061
2099
|
const source = value == null
|
|
2062
2100
|
? null
|
|
2063
2101
|
: targetAgent?.aid
|
|
2064
2102
|
? menuConfigSource('active_baseagent', { self: targetAgent.aid }) ?? 'agent'
|
|
2065
2103
|
: 'agent';
|
|
2066
|
-
return { data: { baseagent: value, scope: 'agent', source } };
|
|
2104
|
+
return { data: { baseagent: value, scope: 'agent', override, source } };
|
|
2067
2105
|
}
|
|
2068
2106
|
if (cmdBase === '/model') {
|
|
2069
2107
|
const target = resolveMenuModelTarget.call(this, {
|
|
@@ -2087,6 +2125,7 @@ export async function execMenuQuery(cmd, channel, channelId, userId, args, expli
|
|
|
2087
2125
|
baseagent: target.baseagent,
|
|
2088
2126
|
source: current.source,
|
|
2089
2127
|
scope: target.scope,
|
|
2128
|
+
override: readMenuScopeOverride(target.scope, target.fieldPath, target.sel),
|
|
2090
2129
|
field: target.fieldPath,
|
|
2091
2130
|
self: target.sel.self,
|
|
2092
2131
|
...(target.sel.peerKey ? { peerKey: target.sel.peerKey } : {}),
|
|
@@ -2115,6 +2154,7 @@ export async function execMenuQuery(cmd, channel, channelId, userId, args, expli
|
|
|
2115
2154
|
baseagent: target.baseagent,
|
|
2116
2155
|
source: current.source,
|
|
2117
2156
|
scope: target.scope,
|
|
2157
|
+
override: readMenuScopeOverride(target.scope, target.fieldPath, target.sel),
|
|
2118
2158
|
field: target.fieldPath,
|
|
2119
2159
|
self: target.sel.self,
|
|
2120
2160
|
...(target.sel.peerKey ? { peerKey: target.sel.peerKey } : {}),
|
|
@@ -2140,6 +2180,7 @@ export async function execMenuQuery(cmd, channel, channelId, userId, args, expli
|
|
|
2140
2180
|
mode: current.value,
|
|
2141
2181
|
source: current.source,
|
|
2142
2182
|
scope: target.scope,
|
|
2183
|
+
override: readMenuScopeOverride(target.scope, target.fieldPath, target.sel),
|
|
2143
2184
|
field: target.fieldPath,
|
|
2144
2185
|
self: target.sel.self,
|
|
2145
2186
|
...(target.sel.peerKey ? { peerKey: target.sel.peerKey } : {}),
|
|
@@ -2170,6 +2211,7 @@ export async function execMenuQuery(cmd, channel, channelId, userId, args, expli
|
|
|
2170
2211
|
mode: current.value,
|
|
2171
2212
|
source: current.source,
|
|
2172
2213
|
scope: target.scope,
|
|
2214
|
+
override: readMenuScopeOverride(target.scope, target.fieldPath, target.sel),
|
|
2173
2215
|
field: target.fieldPath,
|
|
2174
2216
|
self: target.sel.self,
|
|
2175
2217
|
...(target.sel.peerKey ? { peerKey: target.sel.peerKey } : {}),
|
|
@@ -2180,10 +2222,13 @@ export async function execMenuQuery(cmd, channel, channelId, userId, args, expli
|
|
|
2180
2222
|
if (!evolagent)
|
|
2181
2223
|
return { error: '找不到通道所属 agent', code: 'MISSING_AID' };
|
|
2182
2224
|
const observable = evolagent?.getObservable() ?? false;
|
|
2225
|
+
const override = evolagent?.aid
|
|
2226
|
+
? readMenuScopeOverride('agent', 'observable', { self: evolagent.aid })
|
|
2227
|
+
: null;
|
|
2183
2228
|
const source = evolagent?.aid
|
|
2184
2229
|
? menuConfigSource('observable', { self: evolagent.aid }) ?? 'builtin'
|
|
2185
2230
|
: 'builtin';
|
|
2186
|
-
return { data: { observable, source } };
|
|
2231
|
+
return { data: { observable, scope: 'agent', override, source } };
|
|
2187
2232
|
}
|
|
2188
2233
|
if (cmdBase === '/perm') {
|
|
2189
2234
|
const target = resolveMenuPermissionTarget.call(this, {
|
|
@@ -2231,6 +2276,7 @@ export async function execMenuQuery(cmd, channel, channelId, userId, args, expli
|
|
|
2231
2276
|
mode: current.value,
|
|
2232
2277
|
source: current.source,
|
|
2233
2278
|
scope: target.scope,
|
|
2279
|
+
override: readMenuScopeOverride(target.scope, target.fieldPath, target.sel),
|
|
2234
2280
|
field: target.fieldPath,
|
|
2235
2281
|
self: target.sel.self,
|
|
2236
2282
|
...(target.sel.peerKey ? { peerKey: target.sel.peerKey } : {}),
|
|
@@ -2420,11 +2466,13 @@ export async function execMenuUpdate(cmd, value, channel, channelId, userId, ove
|
|
|
2420
2466
|
const triggerScheduler = this.getTriggerSchedulerForChannel?.(channel);
|
|
2421
2467
|
if (!triggerScheduler)
|
|
2422
2468
|
return { error: '触发器功能未启用', code: 'NOT_SUPPORTED' };
|
|
2423
|
-
const updated = await this.updateTriggerFromPatch(triggerScheduler, patch.nameOrId, patch, channel, channelId, userId ?? '', isAdmin);
|
|
2469
|
+
const updated = await this.updateTriggerFromPatch(triggerScheduler, patch.nameOrId, patch, channel, channelId, userId ?? '', isAdmin, undefined, undefined, subject.isDaemonOwner, subject.canApprovePersistentFullAccess);
|
|
2424
2470
|
if (!updated.ok) {
|
|
2425
2471
|
return {
|
|
2426
2472
|
error: updated.error,
|
|
2427
|
-
code: updated.code ?? (
|
|
2473
|
+
code: updated.code ?? (/不存在/.test(updated.error) ? 'NOT_FOUND'
|
|
2474
|
+
: /无权限|只有 daemon-owner|不能批准/.test(updated.error) ? 'FORBIDDEN'
|
|
2475
|
+
: 'INVALID_ARGS'),
|
|
2428
2476
|
...(updated.currentRevision ? { data: { currentRevision: updated.currentRevision } } : {}),
|
|
2429
2477
|
};
|
|
2430
2478
|
}
|
|
@@ -2474,7 +2522,12 @@ export async function execMenuUpdate(cmd, value, channel, channelId, userId, ove
|
|
|
2474
2522
|
return { error: `无效 baseagent: ${arg},可选: ${valid.join(' / ')}`, code: 'INVALID_VALUE' };
|
|
2475
2523
|
}
|
|
2476
2524
|
const previousBaseagent = evolagent.baseagent;
|
|
2477
|
-
|
|
2525
|
+
try {
|
|
2526
|
+
evolagent.setActiveBaseagent(arg);
|
|
2527
|
+
}
|
|
2528
|
+
catch (error) {
|
|
2529
|
+
return { error: error instanceof Error ? error.message : String(error), code: 'INVALID_VALUE' };
|
|
2530
|
+
}
|
|
2478
2531
|
this.eventBus.publish({
|
|
2479
2532
|
type: 'agent:baseagent-changed',
|
|
2480
2533
|
aid: evolagent.aid,
|
|
@@ -2890,23 +2943,31 @@ export async function execMenuAction(cmd, action, args, channel, channelId, user
|
|
|
2890
2943
|
if (!a.aid)
|
|
2891
2944
|
return { error: '缺少 aid', code: 'INVALID_ARGS' };
|
|
2892
2945
|
const hooks = globalThis.__evolcore_reloadHooks;
|
|
2893
|
-
if (!hooks)
|
|
2946
|
+
if (!this.agentApplicationService && !hooks)
|
|
2894
2947
|
return { error: 'Reload hooks 未初始化', code: 'INTERNAL' };
|
|
2895
2948
|
try {
|
|
2896
2949
|
if (action === 'stop') {
|
|
2897
|
-
|
|
2898
|
-
|
|
2899
|
-
|
|
2950
|
+
const result = this.agentApplicationService
|
|
2951
|
+
? await this.agentApplicationService.stop(a.aid)
|
|
2952
|
+
: this.agentRegistry?.stopAgent
|
|
2953
|
+
? await this.agentRegistry.stopAgent(a.aid, hooks).then(() => ({ ok: true, aid: a.aid }))
|
|
2954
|
+
: { ok: false, error: 'stopAgent 不可用', code: 'INTERNAL' };
|
|
2955
|
+
if (!result.ok)
|
|
2956
|
+
return { error: result.error, code: result.code || 'INTERNAL' };
|
|
2900
2957
|
this.eventBus.publish({ type: 'agent:stopped', aid: a.aid, timestamp: Date.now() });
|
|
2901
2958
|
// 中断该 agent 正在执行的大模型调用
|
|
2902
|
-
const handle = this.agentRegistry
|
|
2959
|
+
const handle = this.agentRegistry?.get(a.aid);
|
|
2903
2960
|
if (handle)
|
|
2904
2961
|
this.messageQueue.interruptByAgent(handle.name);
|
|
2905
2962
|
}
|
|
2906
2963
|
else {
|
|
2907
|
-
|
|
2908
|
-
|
|
2909
|
-
|
|
2964
|
+
const result = this.agentApplicationService
|
|
2965
|
+
? await this.agentApplicationService.start(a.aid)
|
|
2966
|
+
: this.agentRegistry?.startAgent
|
|
2967
|
+
? await this.agentRegistry.startAgent(a.aid, hooks).then(() => ({ ok: true, aid: a.aid }))
|
|
2968
|
+
: { ok: false, error: 'startAgent 不可用', code: 'INTERNAL' };
|
|
2969
|
+
if (!result.ok)
|
|
2970
|
+
return { error: result.error, code: result.code || 'INTERNAL' };
|
|
2910
2971
|
this.eventBus.publish({ type: 'agent:started', aid: a.aid, timestamp: Date.now() });
|
|
2911
2972
|
}
|
|
2912
2973
|
return { data: { aid: a.aid, action } };
|
|
@@ -2929,7 +2990,7 @@ export async function execMenuAction(cmd, action, args, channel, channelId, user
|
|
|
2929
2990
|
}
|
|
2930
2991
|
}
|
|
2931
2992
|
}
|
|
2932
|
-
return await execAgentAction(action, a, userId ?? '', this.eventBus);
|
|
2993
|
+
return await execAgentAction(action, a, userId ?? '', this.eventBus, this.agentApplicationService);
|
|
2933
2994
|
}
|
|
2934
2995
|
// ── 关系级 /trigger(不走 owners;复用 isAdmin + scoped 逻辑,D4 直调底层) ──
|
|
2935
2996
|
if (cmdBase === '/trigger') {
|
|
@@ -2979,9 +3040,15 @@ export async function execMenuAction(cmd, action, args, channel, channelId, user
|
|
|
2979
3040
|
triggerThread,
|
|
2980
3041
|
baseagent: args.baseagent,
|
|
2981
3042
|
};
|
|
2982
|
-
const r = await this.registerTriggerFromParsed(parsed, channel, channelId, userId ?? '', undefined, this.resolveMenuChatType(channel, channelId, explicitChatType), undefined, isAdmin);
|
|
2983
|
-
if (!r.ok)
|
|
2984
|
-
|
|
3043
|
+
const r = await this.registerTriggerFromParsed(parsed, channel, channelId, userId ?? '', undefined, this.resolveMenuChatType(channel, channelId, explicitChatType), undefined, isAdmin, subject.isDaemonOwner, subject.canApprovePersistentFullAccess);
|
|
3044
|
+
if (!r.ok) {
|
|
3045
|
+
const code = /已存在|exists|重复/.test(r.error)
|
|
3046
|
+
? 'CONFLICT'
|
|
3047
|
+
: /不能批准|只有 daemon-owner|无权限/.test(r.error)
|
|
3048
|
+
? 'FORBIDDEN'
|
|
3049
|
+
: 'INVALID_ARGS';
|
|
3050
|
+
return { error: r.error, code };
|
|
3051
|
+
}
|
|
2985
3052
|
return { data: { id: r.trigger.id, name: r.trigger.name, nextFireAt: r.trigger.nextFireAt } };
|
|
2986
3053
|
}
|
|
2987
3054
|
if (action === 'cancel') {
|
|
@@ -3006,7 +3073,35 @@ export async function execMenuAction(cmd, action, args, channel, channelId, user
|
|
|
3006
3073
|
const trigger = this.findTriggerDefinition(triggerScheduler, nameOrId, userId ?? '', channel, isAdmin);
|
|
3007
3074
|
if (!trigger)
|
|
3008
3075
|
return { error: '触发器不存在或无权限', code: 'NOT_FOUND' };
|
|
3076
|
+
if (action === 'enable' && trigger.execution.permissionMode === 'fullaccess') {
|
|
3077
|
+
if (!isFullAccessEnabled())
|
|
3078
|
+
return { error: 'fullaccess 功能未启用', code: 'NOT_SUPPORTED' };
|
|
3079
|
+
if (!subject.isDaemonOwner)
|
|
3080
|
+
return { error: '只有 daemon-owner 可以启用 fullaccess Trigger', code: 'FORBIDDEN' };
|
|
3081
|
+
if (subject.canApprovePersistentFullAccess === false) {
|
|
3082
|
+
return { error: '当前认证通道不能批准持久 fullaccess Trigger', code: 'FORBIDDEN' };
|
|
3083
|
+
}
|
|
3084
|
+
const authorizedBy = subject.principalId ?? userId;
|
|
3085
|
+
if (!authorizedBy)
|
|
3086
|
+
return { error: '无法确认 fullaccess Trigger 的批准者', code: 'FORBIDDEN' };
|
|
3087
|
+
// Enabling is itself a persistent fullaccess configuration action. Keep
|
|
3088
|
+
// the canonical owner provenance on definitions created before this
|
|
3089
|
+
// field existed, or after an owner explicitly re-enables the Trigger.
|
|
3090
|
+
triggerScheduler.update(trigger.id, { ...trigger, authorizedBy });
|
|
3091
|
+
}
|
|
3009
3092
|
const updated = triggerScheduler.setEnabled(trigger.id, action === 'enable');
|
|
3093
|
+
if (action === 'enable' && updated.execution.permissionMode === 'fullaccess') {
|
|
3094
|
+
auditFullAccessEvent({
|
|
3095
|
+
event: 'fullaccess.trigger.configured',
|
|
3096
|
+
source: 'trigger',
|
|
3097
|
+
actorId: userId,
|
|
3098
|
+
processRole: subject.isDaemonOwner ? 'daemon-owner' : 'none',
|
|
3099
|
+
agentAid: updated.agentAid,
|
|
3100
|
+
triggerId: updated.id,
|
|
3101
|
+
authorizedBy: updated.authorizedBy,
|
|
3102
|
+
reason: 'fullaccess Trigger enabled',
|
|
3103
|
+
});
|
|
3104
|
+
}
|
|
3010
3105
|
return { data: { id: updated.id, enabled: updated.enabled } };
|
|
3011
3106
|
}
|
|
3012
3107
|
if (action === 'delete') {
|
|
@@ -3021,6 +3116,18 @@ export async function execMenuAction(cmd, action, args, channel, channelId, user
|
|
|
3021
3116
|
if (trigger.enabled)
|
|
3022
3117
|
return { error: '请先禁用触发器再删除', code: 'INVALID_STATE' };
|
|
3023
3118
|
const deleted = triggerScheduler.delete(trigger.id);
|
|
3119
|
+
if (deleted.execution.permissionMode === 'fullaccess') {
|
|
3120
|
+
auditFullAccessEvent({
|
|
3121
|
+
event: 'fullaccess.trigger.deescalated',
|
|
3122
|
+
source: 'trigger',
|
|
3123
|
+
actorId: userId,
|
|
3124
|
+
processRole: subject.isDaemonOwner ? 'daemon-owner' : 'none',
|
|
3125
|
+
agentAid: deleted.agentAid,
|
|
3126
|
+
triggerId: deleted.id,
|
|
3127
|
+
authorizedBy: deleted.authorizedBy,
|
|
3128
|
+
reason: 'fullaccess Trigger deleted',
|
|
3129
|
+
});
|
|
3130
|
+
}
|
|
3024
3131
|
return { data: { id: deleted.id, deleted: true } };
|
|
3025
3132
|
}
|
|
3026
3133
|
if (action === 'run' || action === 'test') {
|
|
@@ -3124,72 +3231,146 @@ export async function execMenuAction(cmd, action, args, channel, channelId, user
|
|
|
3124
3231
|
return { error: `当前 Agent (${agent.name}) 不支持按轮次分叉`, code: 'NOT_SUPPORTED' };
|
|
3125
3232
|
}
|
|
3126
3233
|
const sourceKey = this.getQueueKey(source, channel, channelId);
|
|
3127
|
-
const
|
|
3234
|
+
const sourceIsBusy = (current, currentAgent) => !!current.processingState
|
|
3235
|
+
|| !!current.metadata?.turnState?.active
|
|
3128
3236
|
|| this.messageQueue.isProcessing?.(sourceKey)
|
|
3129
|
-
|| this.messageQueue.getQueueLength?.(sourceKey) > 0
|
|
3130
|
-
||
|
|
3131
|
-
|
|
3237
|
+
|| (this.messageQueue.getQueueLength?.(sourceKey) ?? 0) > 0
|
|
3238
|
+
|| currentAgent.hasActiveStream(sourceKey)
|
|
3239
|
+
|| (this.processor?.isPauseRequested?.(sourceKey) ?? false);
|
|
3240
|
+
if (!source.threadId && sourceIsBusy(source, agent)) {
|
|
3132
3241
|
return { error: '来源会话正在处理消息,请完成后重试', code: 'BUSY' };
|
|
3242
|
+
}
|
|
3133
3243
|
const targetKey = JSON.stringify([source.channelType || source.channel, source.selfAID || '', channelId, targetThreadId]);
|
|
3134
3244
|
if (topicForkTargetsInFlight.has(targetKey))
|
|
3135
3245
|
return { error: '目标话题正在创建', code: 'CONFLICT' };
|
|
3136
3246
|
topicForkTargetsInFlight.add(targetKey);
|
|
3137
|
-
const releaseLock = this.messageQueue.acquireLock?.(sourceKey) ?? (() => { });
|
|
3138
3247
|
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,
|
|
3248
|
+
const executeFork = async (current, currentAgent) => {
|
|
3249
|
+
const messages = await currentAgent.getSessionMessages(current.agentSessionId, current.projectPath);
|
|
3250
|
+
const turns = buildSessionTurnList(messages);
|
|
3251
|
+
const forkTurn = turns.find((turn) => turn.assistantUuid === sourceAssistantMessageId);
|
|
3252
|
+
if (!forkTurn)
|
|
3253
|
+
return { error: '分叉点已失效,请刷新历史后重试', code: 'NOT_FOUND' };
|
|
3254
|
+
const forkedAgentSessionId = await currentAgent.forkSessionAt(current.agentSessionId, current.projectPath, sourceAssistantMessageId, name || undefined);
|
|
3255
|
+
const topic = await this.sessionManager.createForkedThreadSession(current, forkedAgentSessionId, targetThreadId, {
|
|
3256
|
+
name: name || undefined,
|
|
3257
|
+
creatorPeerId: userId,
|
|
3258
|
+
sourceAssistantMessageId,
|
|
3259
|
+
sourceTurn: forkTurn.index,
|
|
3260
|
+
requestId,
|
|
3261
|
+
});
|
|
3262
|
+
await currentAgent.updateSessionMetadata?.(forkedAgentSessionId, {
|
|
3263
|
+
evolcoreSessionId: topic.id,
|
|
3264
|
+
sourceSessionId: current.id,
|
|
3265
|
+
sourceAssistantMessageId,
|
|
3266
|
+
sourceTurn: forkTurn.index,
|
|
3267
|
+
}).catch((error) => {
|
|
3268
|
+
logger.debug(`[MenuHandler] Topic fork metadata sync failed: ${error}`);
|
|
3269
|
+
});
|
|
3270
|
+
this.eventBus.publish({
|
|
3271
|
+
type: 'session:forked',
|
|
3272
|
+
sessionId: topic.id,
|
|
3273
|
+
sourceSessionId: current.id,
|
|
3274
|
+
name: topic.name,
|
|
3275
|
+
threadId: topic.threadId,
|
|
3276
|
+
sourceAssistantMessageId,
|
|
3277
|
+
sourceTurn: forkTurn.index,
|
|
3278
|
+
});
|
|
3279
|
+
return {
|
|
3280
|
+
data: {
|
|
3281
|
+
action: 'fork',
|
|
3282
|
+
success: true,
|
|
3283
|
+
topic: {
|
|
3284
|
+
...buildSessionPayload(topic, topic.name || ''),
|
|
3285
|
+
threadId: topic.threadId,
|
|
3286
|
+
sourceSessionId: current.id,
|
|
3287
|
+
sourceThreadId: current.threadId || null,
|
|
3288
|
+
sourceAssistantMessageId,
|
|
3289
|
+
sourceTurn: forkTurn.index,
|
|
3290
|
+
},
|
|
3183
3291
|
},
|
|
3184
|
-
}
|
|
3292
|
+
};
|
|
3185
3293
|
};
|
|
3294
|
+
const runForkUnderBarrier = async () => {
|
|
3295
|
+
if (source.threadId) {
|
|
3296
|
+
if (typeof this.messageQueue.withSessionBarrier !== 'function') {
|
|
3297
|
+
return { error: '当前运行环境不支持安全话题分叉', code: 'NOT_SUPPORTED' };
|
|
3298
|
+
}
|
|
3299
|
+
return this.messageQueue.withSessionBarrier(sourceKey, async (snapshot) => {
|
|
3300
|
+
const latest = await this.sessionManager.getSessionById?.(source.id);
|
|
3301
|
+
const current = latest;
|
|
3302
|
+
if (!current) {
|
|
3303
|
+
return { error: '来源会话不存在或归属已变化', code: 'NOT_FOUND' };
|
|
3304
|
+
}
|
|
3305
|
+
if (current.id !== source.id || (current.threadId || '') !== (source.threadId || '')) {
|
|
3306
|
+
return { error: '来源会话不存在或归属已变化', code: 'NOT_FOUND' };
|
|
3307
|
+
}
|
|
3308
|
+
if (!current.agentSessionId) {
|
|
3309
|
+
return { error: '来源会话暂无对话历史', code: 'INVALID_STATE' };
|
|
3310
|
+
}
|
|
3311
|
+
const currentAgent = this.getAgent(channel, current.baseagent);
|
|
3312
|
+
if (!currentAgent.capabilities?.forkAtTurn || !currentAgent.getSessionMessages || !currentAgent.forkSessionAt) {
|
|
3313
|
+
return { error: `当前 Agent (${currentAgent.name}) 不支持按轮次分叉`, code: 'NOT_SUPPORTED' };
|
|
3314
|
+
}
|
|
3315
|
+
const probes = {
|
|
3316
|
+
paused: () => this.processor?.isPauseRequested?.(current.id) ?? false,
|
|
3317
|
+
activeStream: () => currentAgent.hasActiveStream(current.id),
|
|
3318
|
+
};
|
|
3319
|
+
if (this.permissionGateway) {
|
|
3320
|
+
probes.pendingPermission = () => this.permissionGateway.getPendingRequests(current.id).length > 0;
|
|
3321
|
+
}
|
|
3322
|
+
if (this.interactionRouter) {
|
|
3323
|
+
probes.pendingInteraction = () => this.interactionRouter.getPending(current.id).length > 0;
|
|
3324
|
+
}
|
|
3325
|
+
const selfAid = current.selfAID || this.getOwningAgent?.(channel)?.aid;
|
|
3326
|
+
if (this.handoffRuntime) {
|
|
3327
|
+
probes.activeHandoff = () => {
|
|
3328
|
+
if (!selfAid)
|
|
3329
|
+
throw new Error('handoff activity owner unavailable');
|
|
3330
|
+
return this.handoffRuntime.hasActiveSessionWork(selfAid, current.id);
|
|
3331
|
+
};
|
|
3332
|
+
probes.openHandoffTarget = () => {
|
|
3333
|
+
if (!selfAid)
|
|
3334
|
+
throw new Error('handoff activity owner unavailable');
|
|
3335
|
+
return this.handoffRuntime.hasOpenTarget(selfAid, current.id);
|
|
3336
|
+
};
|
|
3337
|
+
}
|
|
3338
|
+
const activity = probeSessionActivity({
|
|
3339
|
+
prequeue: snapshot.prequeue,
|
|
3340
|
+
active: snapshot.active,
|
|
3341
|
+
queued: snapshot.queued,
|
|
3342
|
+
dispatching: snapshot.dispatching,
|
|
3343
|
+
processing: !!current.processingState,
|
|
3344
|
+
activeTurn: !!current.metadata?.turnState?.active,
|
|
3345
|
+
}, probes);
|
|
3346
|
+
if (activity === 'busy') {
|
|
3347
|
+
return { error: '来源会话正在处理消息,请完成后重试', code: 'BUSY' };
|
|
3348
|
+
}
|
|
3349
|
+
if (activity === 'unknown') {
|
|
3350
|
+
return { error: '无法可靠确认来源会话为空闲,请稍后重试', code: 'INVALID_STATE' };
|
|
3351
|
+
}
|
|
3352
|
+
return executeFork(current, currentAgent);
|
|
3353
|
+
});
|
|
3354
|
+
}
|
|
3355
|
+
// Main-session sources keep compatibility with older embeddings.
|
|
3356
|
+
const release = this.messageQueue.acquireLock?.(sourceKey) ?? (() => { });
|
|
3357
|
+
try {
|
|
3358
|
+
if (sourceIsBusy(source, agent)) {
|
|
3359
|
+
return { error: '来源会话正在处理消息,请完成后重试', code: 'BUSY' };
|
|
3360
|
+
}
|
|
3361
|
+
return await executeFork(source, agent);
|
|
3362
|
+
}
|
|
3363
|
+
finally {
|
|
3364
|
+
release();
|
|
3365
|
+
}
|
|
3366
|
+
};
|
|
3367
|
+
return await runForkUnderBarrier();
|
|
3186
3368
|
}
|
|
3187
3369
|
catch (error) {
|
|
3188
3370
|
logger.error('[MenuHandler] Topic history fork failed:', error);
|
|
3189
3371
|
return { error: error instanceof Error ? error.message : '话题分叉失败', code: 'EXEC_FAILED' };
|
|
3190
3372
|
}
|
|
3191
3373
|
finally {
|
|
3192
|
-
releaseLock();
|
|
3193
3374
|
topicForkTargetsInFlight.delete(targetKey);
|
|
3194
3375
|
}
|
|
3195
3376
|
}
|
|
@@ -3383,6 +3564,14 @@ export async function execMenuAction(cmd, action, args, channel, channelId, user
|
|
|
3383
3564
|
: 'AUN_OUTBOUND_ROUTE_REQUIRED',
|
|
3384
3565
|
};
|
|
3385
3566
|
}
|
|
3567
|
+
const migrationRequirement = inspectDataMigrationRequirement(resolvePaths().root);
|
|
3568
|
+
if (migrationRequirement.required) {
|
|
3569
|
+
return {
|
|
3570
|
+
error: `待处理的用户数据迁移(${migrationRequirement.operationCount} 项)阻止重启。请先运行 ec data migrate --dry-run,然后运行 ec data migrate --apply。`,
|
|
3571
|
+
code: 'DATA_MIGRATION_REQUIRED',
|
|
3572
|
+
data: { reasonCode: 'DATA_MIGRATION_REQUIRED', operationCount: migrationRequirement.operationCount },
|
|
3573
|
+
};
|
|
3574
|
+
}
|
|
3386
3575
|
if (!suppressRealRestart) {
|
|
3387
3576
|
const restartInfo = {
|
|
3388
3577
|
channel,
|
|
@@ -3694,6 +3883,7 @@ async function execMenuForSystemControl(payload, context) {
|
|
|
3694
3883
|
conversationId: context.actorAid || SYSTEM_CONTROL_CHANNEL,
|
|
3695
3884
|
processOwners: context.owners,
|
|
3696
3885
|
fromControlChannel: true,
|
|
3886
|
+
canApprovePersistentFullAccess: context.canApprovePersistentFullAccess,
|
|
3697
3887
|
});
|
|
3698
3888
|
const trustedIdentity = trustedSubject.identity;
|
|
3699
3889
|
const cmd = name ? (menuCommandForName(name) ?? payload.cmd) : payload.cmd;
|
|
@@ -3820,20 +4010,37 @@ export async function execMenuForEcweb(payload, trusted) {
|
|
|
3820
4010
|
data: authorizationDenialData('DAEMON_OWNER_REQUIRED'),
|
|
3821
4011
|
});
|
|
3822
4012
|
}
|
|
3823
|
-
if (!trusted || (!trusted.localDirect &&
|
|
4013
|
+
if (!trusted || (!trusted.localDirect && trusted.authenticated !== true)) {
|
|
4014
|
+
return menuFailure({ id, ...(name ? { name } : {}) }, {
|
|
4015
|
+
code: 'PERMISSION_DENIED',
|
|
4016
|
+
message: 'ECWeb operation requires an authenticated ECWeb connection',
|
|
4017
|
+
data: { $schema_version: 1, kind: 'role_permission_denied', self: payload?.args?.self ?? null },
|
|
4018
|
+
});
|
|
4019
|
+
}
|
|
4020
|
+
// A verified ECWeb connection has daemon-owner management authority. If the
|
|
4021
|
+
// bearer token has no explicit owner provenance (the normal pairing path),
|
|
4022
|
+
// use the first currently configured owner as the canonical management
|
|
4023
|
+
// principal. This keeps audit/Trigger approval attribution deterministic
|
|
4024
|
+
// without requiring owner-bound pairing metadata.
|
|
4025
|
+
const userId = (trusted.actorAid && owners.includes(trusted.actorAid))
|
|
4026
|
+
? trusted.actorAid
|
|
4027
|
+
: owners[0] ?? (trusted.localDirect ? 'local-direct' : undefined);
|
|
4028
|
+
if (!userId) {
|
|
3824
4029
|
return menuFailure({ id, ...(name ? { name } : {}) }, {
|
|
3825
4030
|
code: 'PERMISSION_DENIED',
|
|
3826
|
-
message: 'ECWeb operation requires
|
|
4031
|
+
message: 'ECWeb operation requires configured daemon owners',
|
|
3827
4032
|
data: { $schema_version: 1, kind: 'role_permission_denied', self: payload?.args?.self ?? null },
|
|
3828
4033
|
});
|
|
3829
4034
|
}
|
|
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
4035
|
return execMenuForSystemControl.call(this, payload, {
|
|
3834
4036
|
source: 'ecweb',
|
|
3835
|
-
actorAid: userId
|
|
4037
|
+
actorAid: userId,
|
|
3836
4038
|
localDirect: trusted.localDirect,
|
|
4039
|
+
// The ECWeb server/IPC control-token gate authenticates this management
|
|
4040
|
+
// request. All authenticated ECWeb connections are equally eligible to
|
|
4041
|
+
// approve persistent fullaccess; actorAid is used only when it identifies
|
|
4042
|
+
// a current configured owner.
|
|
4043
|
+
canApprovePersistentFullAccess: true,
|
|
3837
4044
|
owners,
|
|
3838
4045
|
});
|
|
3839
4046
|
}
|
|
@@ -3861,6 +4068,7 @@ export async function execMenuForControl(payload, peerId) {
|
|
|
3861
4068
|
source: 'control',
|
|
3862
4069
|
actorAid: peerId,
|
|
3863
4070
|
localDirect: false,
|
|
4071
|
+
canApprovePersistentFullAccess: false,
|
|
3864
4072
|
owners,
|
|
3865
4073
|
});
|
|
3866
4074
|
}
|
|
@@ -272,7 +272,7 @@ const STABLE_CODES = new Set([
|
|
|
272
272
|
'TEMPORARILY_UNAVAILABLE', 'INTERNAL_ERROR', 'MISSING_SCOPE', 'UPGRADE_REQUIRED',
|
|
273
273
|
'MENU_TOKEN_REQUIRED', 'MENU_TOKEN_REJECTED', 'MENU_TOKEN_ENCRYPTION_REQUIRED',
|
|
274
274
|
'INVALID_CONTEXT', 'SCHEMA_VERSION_UNSUPPORTED', 'DEPENDENCY_UNAVAILABLE',
|
|
275
|
-
'RATE_LIMITED', 'CATALOG_STALE',
|
|
275
|
+
'RATE_LIMITED', 'CATALOG_STALE', 'DATA_MIGRATION_REQUIRED',
|
|
276
276
|
]);
|
|
277
277
|
export function normalizeMenuError(error) {
|
|
278
278
|
const source = error && typeof error === 'object' ? error : {};
|