evolcore 0.0.17 → 0.0.19

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.
Files changed (66) hide show
  1. package/CHANGELOG.md +51 -0
  2. package/bin/codex-managed-hook.mjs +16 -7
  3. package/bin/install-codex-managed-hooks.mjs +4 -2
  4. package/dist/agents/claude-runner.js +113 -24
  5. package/dist/agents/codex-app-server-client.js +6 -1
  6. package/dist/agents/codex-runner.js +43 -24
  7. package/dist/agents/ecagent-runner.js +39 -10
  8. package/dist/agents/gemini-runner.js +90 -19
  9. package/dist/aun/aid/store.js +36 -0
  10. package/dist/aun/msg/p2p.js +20 -8
  11. package/dist/channels/aun.js +159 -21
  12. package/dist/cli/agent-command.js +67 -6
  13. package/dist/cli/agent.js +26 -0
  14. package/dist/cli/command-log.js +23 -4
  15. package/dist/cli/daemon-commands.js +82 -14
  16. package/dist/cli/init.js +21 -5
  17. package/dist/cli/restart-monitor.js +13 -6
  18. package/dist/cli/watch-logs.js +2 -2
  19. package/dist/config/builtin-roles.js +5 -1
  20. package/dist/config/role-ranks.js +4 -0
  21. package/dist/core/audit/event-key.js +29 -0
  22. package/dist/core/audit/log-integrity.js +13 -3
  23. package/dist/core/auth/auth-gateway.js +14 -18
  24. package/dist/core/auth/authorization-audit.js +110 -3
  25. package/dist/core/auth/authorization-denial.js +17 -0
  26. package/dist/core/auth/operation-authorizer.js +143 -18
  27. package/dist/core/auth/operation-catalog.js +21 -5
  28. package/dist/core/bootstrap-messages.js +11 -6
  29. package/dist/core/bootstrap-service.js +26 -4
  30. package/dist/core/causation/aun-association.js +7 -4
  31. package/dist/core/command/agent-control.js +25 -16
  32. package/dist/core/command/command-handler.js +50 -4
  33. package/dist/core/command/group-menu.js +1 -1
  34. package/dist/core/command/menu-catalog.js +32 -7
  35. package/dist/core/command/menu-handler.js +59 -23
  36. package/dist/core/command/menu-protocol.js +196 -0
  37. package/dist/core/command/slash-gate.js +14 -5
  38. package/dist/core/command/slash-handler.js +81 -99
  39. package/dist/core/data-migration.js +10 -4
  40. package/dist/core/event-catalog.js +18 -0
  41. package/dist/core/message/message-bridge.js +66 -8
  42. package/dist/core/message/response-engine.js +147 -10
  43. package/dist/core/permission/ec-command-parser.js +148 -22
  44. package/dist/core/permission/sandbox-runtime.js +79 -13
  45. package/dist/core/permission/tool-policy.js +19 -7
  46. package/dist/index.js +357 -48
  47. package/dist/ipc.js +81 -5
  48. package/dist/paths.js +0 -3
  49. package/dist/utils/atomic-write.js +45 -11
  50. package/dist/utils/logger.js +27 -0
  51. package/dist/utils/windows-autostart.js +740 -83
  52. package/ecagent/dist/harness/agent-harness.d.ts +1 -1
  53. package/ecagent/dist/harness/agent-harness.js +6 -4
  54. package/kits/docs/evolcore/config.md +1 -1
  55. package/kits/docs/evolcore/group-rules.md +2 -1
  56. package/kits/docs/identity/ROLE_DETAIL.md +3 -1
  57. package/kits/docs/path-registry.md +1 -1
  58. package/kits/eck_manifest.json +25 -16
  59. package/kits/rules/01-overview.md +5 -5
  60. package/kits/rules/02-navigation.md +2 -2
  61. package/kits/rules/03-identity.md +1 -1
  62. package/kits/rules/04-relation.md +4 -4
  63. package/kits/rules/05-venue.md +5 -5
  64. package/kits/templates/bootstrap-welcome.md +3 -1
  65. package/kits/templates/system-fragments/bootstrap.md +17 -9
  66. package/package.json +1 -1
@@ -11,22 +11,18 @@ import { isValidAid } from '../../aun/aid/validation.js';
11
11
  const SUPPORTED_AGENT_PATCH_FIELDS = new Set(['aid', 'name', 'avatar', 'active_baseagent', 'baseagents', 'projects', 'owners', 'chatmode', 'channels', 'channelOwners']);
12
12
  const HIDDEN_VALUE = '[hidden]';
13
13
  const SENSITIVE_CONFIG_KEYS = new Set([
14
- 'apiKey',
15
14
  'apikey',
16
- 'appId',
17
- 'appSecret',
18
- 'botId',
19
- 'clientId',
20
- 'clientSecret',
21
- 'secret',
22
- 'token',
23
- 'accessToken',
15
+ 'appid',
16
+ 'appsecret',
17
+ 'botid',
24
18
  'authorization',
19
+ 'clientid',
25
20
  'credential',
26
21
  'credentials',
27
22
  'password',
28
- 'privateKey',
29
- 'encryptionSeed',
23
+ 'privatekey',
24
+ 'encryptionseed',
25
+ 'aeskey',
30
26
  ]);
31
27
  const HIDDEN_SETTINGS_KEYS = new Set([
32
28
  'pathToClaudeCodeExecutable',
@@ -225,7 +221,7 @@ function sanitizeConfigValue(value) {
225
221
  if (out.effort === undefined)
226
222
  out.effort = sanitizeConfigValue(raw);
227
223
  }
228
- else if (SENSITIVE_CONFIG_KEYS.has(key)) {
224
+ else if (isSensitiveConfigKey(key)) {
229
225
  out[key] = raw == null || raw === '' ? raw : HIDDEN_VALUE;
230
226
  }
231
227
  else if (key === 'projects') {
@@ -237,6 +233,18 @@ function sanitizeConfigValue(value) {
237
233
  }
238
234
  return out;
239
235
  }
236
+ function isSensitiveConfigKey(key) {
237
+ const normalized = key.replace(/[-_\s]/g, '').toLowerCase();
238
+ return SENSITIVE_CONFIG_KEYS.has(normalized)
239
+ || normalized.endsWith('token')
240
+ || normalized.endsWith('secret')
241
+ || normalized.endsWith('password')
242
+ || normalized.endsWith('credential')
243
+ || normalized.endsWith('credentials')
244
+ || normalized.endsWith('privatekey')
245
+ || normalized.endsWith('apikey')
246
+ || normalized.endsWith('authorization');
247
+ }
240
248
  function buildSafeAgentConfig(config) {
241
249
  if (!config)
242
250
  return null;
@@ -592,14 +600,15 @@ export async function execAgentQuery(args) {
592
600
  }
593
601
  const config = cfgRead(ConfigTarget.Agent, { self: aid });
594
602
  const safeConfig = buildSafeAgentConfig(config);
603
+ const safeAgentConfig = safeConfig ?? {};
595
604
  const data = {
596
605
  ...res,
597
606
  config: {
598
607
  ...(res.config ?? {}),
599
- active_baseagent: config?.active_baseagent ?? res.config?.baseagent ?? null,
600
- projects: stripUnsupportedProjectFields(config?.projects) ?? {},
601
- baseagents: config?.baseagents ?? {},
602
- owners: config?.owners ?? res.config?.owners ?? [],
608
+ active_baseagent: safeAgentConfig.active_baseagent ?? res.config?.baseagent ?? null,
609
+ projects: safeAgentConfig.projects ?? {},
610
+ baseagents: safeAgentConfig.baseagents ?? {},
611
+ owners: safeAgentConfig.owners ?? res.config?.owners ?? [],
603
612
  },
604
613
  safeConfig,
605
614
  };
@@ -19,7 +19,7 @@ import { buildSessionTurnList } from '../session/session-turns.js';
19
19
  import { resolveConfigCommand } from '../../config/resolved-config-op.js';
20
20
  import { AUTHENTICATED_CONFIG_ROLE_ENV, executeResolvedConfigCommand } from '../../config/config-operation-service.js';
21
21
  import { splitConfigBatchGetArgv } from '../../cli/cli-argv.js';
22
- import { hashArgv } from '../auth/authorization-audit.js';
22
+ import { auditCommandAuthorization, hashArgv } from '../auth/authorization-audit.js';
23
23
  import { executeResolvedContactCommand, resolveContactCommand } from '../../config/contact-operation-service.js';
24
24
  import { executeManagedAidOperation, ManagedAidOperationError, resolveManagedAidOperation, } from '../../aun/aid/managed-operation.js';
25
25
  import { executeManagedGroupOperation, isManagedGroupRelationRead, ManagedGroupOperationError, resolveManagedGroupOperation, executeManagedMsgOperation, ManagedMsgOperationError, resolveManagedMsgOperation, executeManagedFsOperation, ManagedFsOperationError, resolveManagedFsOperation, } from '../../aun/msg/managed-operation.js';
@@ -27,7 +27,7 @@ import { authorizeOperation, buildAuthSubject } from '../auth/auth-gateway.js';
27
27
  import { isHClassPath, isLClassPath, isSameOrDescendant } from '../protected-paths.js';
28
28
  import { validateModelSelectionForRole } from '../model/model-permission.js';
29
29
  import { getModelInfo } from '../model/model-catalog.js';
30
- import { constrainRuntimePermissionMode, validateRuntimeStringFieldOverride } from '../role/runtime-policy.js';
30
+ import { constrainRuntimePermissionMode, resolveRuntimePermissionMode, validateRuntimeStringFieldOverride } from '../role/runtime-policy.js';
31
31
  import { parsePeerKey } from '../relation/peer-identity.js';
32
32
  import { isQuickCommand } from './slash-gate.js';
33
33
  import { handleSlashCommand } from './slash-handler.js';
@@ -1002,7 +1002,13 @@ export class CommandHandler {
1002
1002
  }
1003
1003
  const payload = result;
1004
1004
  if (payload.kind === 'command.error') {
1005
- return { error: payload.text || '执行失败', code: 'EXEC_FAILED' };
1005
+ return {
1006
+ error: payload.text || '执行失败',
1007
+ code: payload.reason || 'EXEC_FAILED',
1008
+ ...(payload.reasonCode ? {
1009
+ data: { reasonCode: payload.reasonCode, nextStep: payload.nextStep },
1010
+ } : {}),
1011
+ };
1006
1012
  }
1007
1013
  const data = payload.structured && typeof payload.structured === 'object'
1008
1014
  ? { ...payload.structured }
@@ -1771,6 +1777,7 @@ export class CommandHandler {
1771
1777
  },
1772
1778
  },
1773
1779
  auditMetadata: {
1780
+ correlationId: grant.taskId,
1774
1781
  argvHash: hashArgv(parsed.command.canonicalArgv),
1775
1782
  taskId: grant.taskId,
1776
1783
  messageId: grant.messageId,
@@ -1854,8 +1861,41 @@ export class CommandHandler {
1854
1861
  if (!session)
1855
1862
  return { ok: false, code: 'INVALID_SESSION', error: 'Invalid session' };
1856
1863
  const parsed = resolveManagedMsgOperation(argv);
1857
- if (!parsed.ok)
1864
+ if (!parsed.ok) {
1865
+ const grant = delegation.grant;
1866
+ auditCommandAuthorization({
1867
+ ts: Date.now(),
1868
+ source: 'agent-tool',
1869
+ operation: 'ec.msg.managed.parse',
1870
+ scope: 'relation',
1871
+ dangerous: false,
1872
+ decision: 'deny',
1873
+ executed: false,
1874
+ executionState: 'blocked',
1875
+ decisionSource: 'policy',
1876
+ policyCode: parsed.code,
1877
+ code: parsed.code,
1878
+ reason: parsed.reason,
1879
+ correlationId: grant.taskId,
1880
+ requestId: grant.taskId,
1881
+ sessionId,
1882
+ agentAid: grant.selfAid,
1883
+ permissionMode: resolveRuntimePermissionMode({ selfAid: grant.selfAid, role: grant.issuedRole }).effectiveValue,
1884
+ actorId: grant.actorId,
1885
+ selfAid: grant.selfAid,
1886
+ peerKey: grant.peerKey,
1887
+ channel: grant.channel,
1888
+ role: grant.issuedRole,
1889
+ taskId: grant.taskId,
1890
+ messageId: grant.messageId,
1891
+ argvHash: hashArgv(argv),
1892
+ argsSummary: {
1893
+ subcommand: String(argv[1] ?? argv[0] ?? '<missing>'),
1894
+ providedOptions: argv.filter(value => typeof value === 'string' && value.startsWith('--')).join(','),
1895
+ },
1896
+ });
1858
1897
  return { ok: false, code: parsed.code, error: parsed.reason };
1898
+ }
1859
1899
  const grant = delegation.grant;
1860
1900
  const grantSelfAid = grant.selfAid.replace(/^@/, '');
1861
1901
  const sessionSelfAid = session.selfAID?.replace(/^@/, '');
@@ -1897,9 +1937,15 @@ export class CommandHandler {
1897
1937
  targetId: parsed.command.target,
1898
1938
  peer: parsed.command.target,
1899
1939
  peerKey: formatPeerKey('aun', parsed.command.target),
1940
+ ...(parsed.command.session ? { session: parsed.command.session } : {}),
1941
+ ...(parsed.command.limit !== undefined ? { limit: parsed.command.limit } : {}),
1942
+ ...(parsed.command.before !== undefined ? { before: parsed.command.before } : {}),
1943
+ ...(parsed.command.after !== undefined ? { after: parsed.command.after } : {}),
1944
+ ...(parsed.command.direction ? { direction: parsed.command.direction } : {}),
1900
1945
  },
1901
1946
  },
1902
1947
  auditMetadata: {
1948
+ correlationId: grant.taskId,
1903
1949
  argvHash: hashArgv(parsed.command.canonicalArgv),
1904
1950
  taskId: grant.taskId,
1905
1951
  messageId: grant.messageId,
@@ -225,7 +225,7 @@ function updateSetting(key, target, value, args, context) {
225
225
  if (!allowed.has(raw)) {
226
226
  throw new GroupMenuError('INVALID_ARGUMENT', `Invalid ${key} value: ${raw || '<empty>'}`);
227
227
  }
228
- if (key === 'rulesPolicy' && context.role !== 'owner' && !context.isDaemonOwner) {
228
+ if (key === 'rulesPolicy' && context.role !== 'owner') {
229
229
  throw new GroupMenuError('PERMISSION_DENIED', 'rulesPolicy can only be changed by an Agent owner');
230
230
  }
231
231
  const expectedRevision = stringArg(args?.expectedRevision);
@@ -2,6 +2,7 @@ import { createHash } from 'node:crypto';
2
2
  import { groupInfo, groupMembers } from '../../aun/msg/group.js';
3
3
  import { formatPeerKey } from '../relation/peer-identity.js';
4
4
  import { authorizeAccess, authorizeOperation, buildAuthSubject } from '../auth/auth-gateway.js';
5
+ import { authorizationDenialData, formatAuthorizationDenial } from '../auth/authorization-denial.js';
5
6
  import { ConfigTarget, read as readConfig } from '../../config/config-manager.js';
6
7
  import { readRolesConfig } from '../../config/roles.js';
7
8
  import { normalizeMenuError } from './menu-protocol.js';
@@ -106,8 +107,13 @@ export async function resolveMenuCatalogContext(input) {
106
107
  throw { code: 'FORBIDDEN', message: 'control catalog context requires a control subject' };
107
108
  }
108
109
  if (requested === 'process') {
109
- if (input.channelScope !== 'control' || !subject.fromControlChannel || !subject.isDaemonOwner)
110
- throw { code: 'FORBIDDEN', message: 'process context requires a daemon owner control subject' };
110
+ if (input.channelScope !== 'control' || !subject.fromControlChannel || !subject.isDaemonOwner) {
111
+ throw {
112
+ code: 'NOT_ALLOWED',
113
+ message: formatAuthorizationDenial('Process context requires a DaemonOwner control subject.', 'DAEMON_OWNER_REQUIRED'),
114
+ data: authorizationDenialData('DAEMON_OWNER_REQUIRED'),
115
+ };
116
+ }
111
117
  return makeContext(input, requested, subject, input.channelId);
112
118
  }
113
119
  if (requested === 'agent') {
@@ -139,9 +145,18 @@ export async function resolveMenuCatalogContext(input) {
139
145
  chatType: 'private',
140
146
  conversationId: subject.actorId,
141
147
  fromControlChannel: subject.fromControlChannel,
142
- identity: subject.isDaemonOwner ? subject.identity : undefined,
143
148
  });
144
- const bound = { ...rebuilt, channel: subject.channel, channelId: subject.actorId, peerKey: formatPeerKey('aun', subject.actorId), isDaemonOwner: subject.isDaemonOwner || rebuilt.isDaemonOwner, fromControlChannel: subject.fromControlChannel };
149
+ const processRole = subject.processRole ?? (subject.isDaemonOwner ? 'daemon-owner' : 'none');
150
+ const bound = {
151
+ ...rebuilt,
152
+ channel: subject.channel,
153
+ channelId: subject.actorId,
154
+ peerKey: formatPeerKey('aun', subject.actorId),
155
+ processRole,
156
+ isDaemonOwner: processRole === 'daemon-owner',
157
+ fromControlChannel: subject.fromControlChannel,
158
+ allowAccess: processRole !== 'none' || rebuilt.allowAccess,
159
+ };
145
160
  const access = authorizeAccess(bound);
146
161
  if (!access.allow)
147
162
  throw { code: access.code, message: access.reason };
@@ -201,9 +216,19 @@ export async function resolveMenuCatalogContext(input) {
201
216
  chatType: 'group',
202
217
  conversationId: canonicalGroupId,
203
218
  fromControlChannel: subject.fromControlChannel,
204
- identity: subject.isDaemonOwner ? subject.identity : undefined,
205
219
  });
206
- const bound = { ...rebuilt, channel: subject.channel, channelId: canonicalGroupId, conversationId: canonicalGroupId, peerKey, isDaemonOwner: subject.isDaemonOwner || rebuilt.isDaemonOwner, fromControlChannel: subject.fromControlChannel };
220
+ const processRole = subject.processRole ?? (subject.isDaemonOwner ? 'daemon-owner' : 'none');
221
+ const bound = {
222
+ ...rebuilt,
223
+ channel: subject.channel,
224
+ channelId: canonicalGroupId,
225
+ conversationId: canonicalGroupId,
226
+ peerKey,
227
+ processRole,
228
+ isDaemonOwner: processRole === 'daemon-owner',
229
+ fromControlChannel: subject.fromControlChannel,
230
+ allowAccess: processRole !== 'none' || rebuilt.allowAccess,
231
+ };
207
232
  const access = authorizeAccess(bound);
208
233
  if (!access.allow)
209
234
  throw { code: access.code, message: access.reason };
@@ -278,7 +303,7 @@ async function operationView(binding, context) {
278
303
  return undefined;
279
304
  if (binding.supported === false)
280
305
  return { supported: false, allowed: false, reason: { code: 'NOT_SUPPORTED', message: 'operation is not supported' } };
281
- if (binding.operation === 'group.rulespolicy.update' && context.subject.role !== 'owner' && !context.subject.isDaemonOwner) {
306
+ if (binding.operation === 'group.rulespolicy.update' && context.subject.role !== 'owner') {
282
307
  return { supported: true, allowed: false, reason: { code: 'PERMISSION_DENIED', message: 'rulesPolicy can only be changed by an Agent owner' } };
283
308
  }
284
309
  const decision = authorizeOperation({ source: 'menu', intent: intentFor(binding, context), subject: context.subject, audit: false });
@@ -27,6 +27,7 @@ import { isCapabilityType, listCapabilityOptions, queryCapabilityTypes, resolveC
27
27
  import { normalizeCliArgv, parseCliIntent, parseLegacyCliCommand, validateCliArgv, withDefaultRelationContext } from './cli-intent-parser.js';
28
28
  import { auditCommandAuthorization, hashArgv } from '../auth/authorization-audit.js';
29
29
  import { authorizeOperation, buildAuthSubject } from '../auth/auth-gateway.js';
30
+ import { authorizationDenialData, formatAuthorizationDenial } from '../auth/authorization-denial.js';
30
31
  import { evaluateRoleOperationCapability } from '../auth/operation-authorizer.js';
31
32
  import { splitConfigBatchGetArgv } from '../../cli/cli-argv.js';
32
33
  import { chatmodeFieldForPeer, resolveChatModeForField } from '../message/peer-mode.js';
@@ -854,13 +855,21 @@ function gateControlScope(opts) {
854
855
  if (fromControlChannel)
855
856
  return null;
856
857
  if (isProcessLevelAction(cmdBase, action)) {
857
- return { error: '此操作仅允许通过控制 AID channel 执行', code: 'FORBIDDEN' };
858
+ return {
859
+ error: formatAuthorizationDenial('This daemon-level operation is not available from an Agent channel.', 'DAEMON_OWNER_REQUIRED'),
860
+ code: 'NOT_ALLOWED',
861
+ data: authorizationDenialData('DAEMON_OWNER_REQUIRED'),
862
+ };
858
863
  }
859
864
  const targetAid = args?.aid;
860
865
  if (targetAid) {
861
866
  const currentAgentAid = this.getOwningAgent?.(channel)?.aid;
862
867
  if (targetAid !== currentAgentAid) {
863
- return { error: '跨 agent 操作仅允许通过控制 AID channel 执行', code: 'FORBIDDEN' };
868
+ return {
869
+ error: formatAuthorizationDenial('An Agent channel may only target its current Agent.', 'TARGET_SELF_ONLY'),
870
+ code: 'NOT_ALLOWED',
871
+ data: authorizationDenialData('TARGET_SELF_ONLY'),
872
+ };
864
873
  }
865
874
  }
866
875
  return null;
@@ -983,8 +992,12 @@ function buildMenuIntent(verb, cmdBase, args, action, value, fromControlChannel
983
992
  return intent('agent.reload', fromControlChannel ? 'control' : 'agent', { action });
984
993
  if (action === 'create')
985
994
  return intent('agent.create', 'control', { action });
986
- if (action === 'delete' || action === 'disable')
995
+ if (action === 'delete')
987
996
  return intent('agent.delete', 'control', { action });
997
+ if (action === 'enable')
998
+ return intent('agent.enable', 'control', { action });
999
+ if (action === 'disable')
1000
+ return intent('agent.disable', 'control', { action });
988
1001
  }
989
1002
  if (cmdBase === '/trigger') {
990
1003
  if (action === 'create' || action === 'set')
@@ -1088,7 +1101,12 @@ async function authorizeMenuIntent(params) {
1088
1101
  },
1089
1102
  };
1090
1103
  }
1091
- return { error: decision.reason, code: decision.code };
1104
+ const reasonCode = decision.command?.reasonCode;
1105
+ return {
1106
+ error: formatAuthorizationDenial(decision.reason, reasonCode),
1107
+ code: decision.code,
1108
+ ...(reasonCode ? { data: authorizationDenialData(reasonCode) } : {}),
1109
+ };
1092
1110
  }
1093
1111
  return null;
1094
1112
  }
@@ -1136,8 +1154,6 @@ function buildRoleMenuContext(owner, channel, subject) {
1136
1154
  },
1137
1155
  } : {}),
1138
1156
  availableBaseagents: Array.from(new Set([
1139
- ...Object.keys(owningAgent?.config?.baseagents ?? {}),
1140
- ...(owningAgent?.baseagent ? [owningAgent.baseagent] : []),
1141
1157
  ...(owningAgent?.name ? (owner.getAvailableBaseagentsForOwner?.(owningAgent.name) ?? []) : []),
1142
1158
  ])),
1143
1159
  listModels: async (baseagent) => {
@@ -1187,9 +1203,6 @@ async function executeGroupMenu(owner, params) {
1187
1203
  return menuResultFailure(error);
1188
1204
  }
1189
1205
  const authorizationArgs = groupMenuAuthorizationArgs(params.args, params.subject.selfAid, menuIntent.scope === 'relation' ? params.subject.peerKey : undefined);
1190
- const authorizationSubject = params.subject.isDaemonOwner && params.subject.role !== 'owner'
1191
- ? { ...params.subject, role: 'owner' }
1192
- : params.subject;
1193
1206
  const denied = await authorizeMenuIntent.call(owner, {
1194
1207
  intent: {
1195
1208
  operation: menuIntent.operation,
@@ -1198,7 +1211,7 @@ async function executeGroupMenu(owner, params) {
1198
1211
  args: authorizationArgs,
1199
1212
  },
1200
1213
  identity: params.identity,
1201
- subject: authorizationSubject,
1214
+ subject: params.subject,
1202
1215
  session: params.session,
1203
1216
  explicitChatType: params.explicitChatType,
1204
1217
  channel: params.channel,
@@ -1541,16 +1554,15 @@ export async function getSubMenuItems(cmd, channel, channelId, userId, args, ove
1541
1554
  throw { code: res.code, message: res.error };
1542
1555
  return res.data.agents.map(ag => ({ value: ag.aid, label: ag.name || ag.aid, desc: ag.status }));
1543
1556
  }
1544
- // agent channel:作用域绑定自身,仅返回自身单条
1557
+ // agent channel:直接读取自身,避免先枚举全部 Agent 再过滤。
1545
1558
  const selfAid = this.getOwningAgent?.(channel)?.aid;
1546
1559
  if (!selfAid)
1547
1560
  throw { code: 'FORBIDDEN', message: '当前 channel 无绑定 agent' };
1548
- const res = await execAgentOptions(args);
1561
+ const res = await execAgentQuery({ aid: selfAid });
1549
1562
  if ('error' in res)
1550
1563
  throw { code: res.code, message: res.error };
1551
- return res.data.agents
1552
- .filter(ag => ag.aid === selfAid)
1553
- .map(ag => ({ value: ag.aid, label: ag.name || ag.aid, desc: ag.status }));
1564
+ const ag = res.data;
1565
+ return [{ value: ag.aid, label: ag.identity?.name || ag.name || ag.aid, desc: ag.status }];
1554
1566
  }
1555
1567
  if (cmd === '/capability') {
1556
1568
  const type = args?.type;
@@ -1890,7 +1902,7 @@ export async function execMenuQuery(cmd, channel, channelId, userId, args, expli
1890
1902
  // 授权由 agent.list/agent.show operation 决定;channel 闸门只负责目标范围。
1891
1903
  if (cmdBase === '/agent') {
1892
1904
  if (fromControlChannel) {
1893
- return await execAgentQuery(args);
1905
+ return args?.aid ? await execAgentQuery(args) : await execAgentOptions(args);
1894
1906
  }
1895
1907
  const selfAid = this.getOwningAgent?.(channel)?.aid;
1896
1908
  if (!selfAid)
@@ -2727,7 +2739,11 @@ export async function execMenuAction(cmd, action, args, channel, channelId, user
2727
2739
  subject: authSubject,
2728
2740
  });
2729
2741
  if (cmdBase === '/system' && fromControlChannel && !subject.isDaemonOwner) {
2730
- return { error: '操作需要 owner 权限', code: 'FORBIDDEN' };
2742
+ return {
2743
+ error: formatAuthorizationDenial('This daemon-level operation requires DaemonOwner.', 'DAEMON_OWNER_REQUIRED'),
2744
+ code: 'NOT_ALLOWED',
2745
+ data: authorizationDenialData('DAEMON_OWNER_REQUIRED'),
2746
+ };
2731
2747
  }
2732
2748
  if (cmdBase === '/role') {
2733
2749
  const authorized = await authorizeRoleMenu(this, {
@@ -2808,7 +2824,11 @@ export async function execMenuAction(cmd, action, args, channel, channelId, user
2808
2824
  if (cmdBase === '/agent') {
2809
2825
  if (fromControlChannel) {
2810
2826
  if (!subject.isDaemonOwner) {
2811
- return { error: '操作需要 owner 权限', code: 'FORBIDDEN' };
2827
+ return {
2828
+ error: formatAuthorizationDenial('This Agent lifecycle operation requires DaemonOwner.', 'DAEMON_OWNER_REQUIRED'),
2829
+ code: 'NOT_ALLOWED',
2830
+ data: authorizationDenialData('DAEMON_OWNER_REQUIRED'),
2831
+ };
2812
2832
  }
2813
2833
  }
2814
2834
  else {
@@ -3323,7 +3343,11 @@ export async function execMenuAction(cmd, action, args, channel, channelId, user
3323
3343
  if (cmdBase === '/system') {
3324
3344
  // D1 迁移:进程级鉴权统一查 daemon.json owners,替代各 action 内联的 identity.role 判断
3325
3345
  if (!subject.isDaemonOwner) {
3326
- return { error: '操作需要 owner 权限', code: 'FORBIDDEN' };
3346
+ return {
3347
+ error: formatAuthorizationDenial('This daemon-level operation requires DaemonOwner.', 'DAEMON_OWNER_REQUIRED'),
3348
+ code: 'NOT_ALLOWED',
3349
+ data: authorizationDenialData('DAEMON_OWNER_REQUIRED'),
3350
+ };
3327
3351
  }
3328
3352
  if (action === 'restart') {
3329
3353
  const suppressRealRestart = shouldSuppressRealRestart();
@@ -3475,6 +3499,7 @@ export async function execMenuAction(cmd, action, args, channel, channelId, user
3475
3499
  requestId, sessionId: subject.sessionId, agentAid: subject.selfAid,
3476
3500
  permissionMode: subject.permissionMode,
3477
3501
  channel, channelId, role: identity.role, decision: 'deny', code: 'NOT_ALLOWED',
3502
+ executed: false, executionState: 'blocked',
3478
3503
  reason: 'Unrecognized CLI command', taskId: requestId, argvHash: hashArgv(intentArgv),
3479
3504
  name: 'cli', action: 'exec', args: { argv: [...requestedArgv] },
3480
3505
  });
@@ -3534,6 +3559,8 @@ export async function execMenuAction(cmd, action, args, channel, channelId, user
3534
3559
  permissionMode: subject.permissionMode,
3535
3560
  channel, channelId, role: identity.role, isDaemonOwner,
3536
3561
  fromControlChannel: fromControlChannel ?? false, decision: 'allow',
3562
+ executed: true,
3563
+ executionState: executionData?.exitCode === undefined || executionData.exitCode === 0 ? 'completed' : 'failed',
3537
3564
  matchedRule: decision.command?.matchedRule, taskId: requestId, argvHash: hashArgv(argv),
3538
3565
  name: 'cli', action: 'exec',
3539
3566
  args: { argv: [...requestedArgv] },
@@ -3665,7 +3692,6 @@ async function execMenuForSystemControl(payload, context) {
3665
3692
  channelId: context.actorAid || SYSTEM_CONTROL_CHANNEL,
3666
3693
  chatType: 'private',
3667
3694
  conversationId: context.actorAid || SYSTEM_CONTROL_CHANNEL,
3668
- identity: context.localDirect ? { role: 'owner', mode: 'interactive' } : undefined,
3669
3695
  processOwners: context.owners,
3670
3696
  fromControlChannel: true,
3671
3697
  });
@@ -3788,7 +3814,11 @@ export async function execMenuForEcweb(payload, trusted) {
3788
3814
  const isProcessLevel = isProcessLevelMenu(name, payload?.cmd) || isProcessContextMenu(payload);
3789
3815
  const owners = loadDaemonConfig().owners ?? [];
3790
3816
  if (isProcessLevel && owners.length === 0) {
3791
- return { type: 'menu.response', id, name, error: { code: 'FORBIDDEN', message: '请在 daemon.json 配置 owners 后使用进程级操作' } };
3817
+ return menuFailure({ id, ...(name ? { name } : {}) }, {
3818
+ code: 'NOT_ALLOWED',
3819
+ message: formatAuthorizationDenial('Configure daemon.json.owners before using process-level operations.', 'DAEMON_OWNER_REQUIRED'),
3820
+ data: authorizationDenialData('DAEMON_OWNER_REQUIRED'),
3821
+ });
3792
3822
  }
3793
3823
  if (!trusted || (!trusted.localDirect && !trusted.actorAid)) {
3794
3824
  return menuFailure({ id, ...(name ? { name } : {}) }, {
@@ -3797,7 +3827,9 @@ export async function execMenuForEcweb(payload, trusted) {
3797
3827
  data: { $schema_version: 1, kind: 'role_permission_denied', self: payload?.args?.self ?? null },
3798
3828
  });
3799
3829
  }
3800
- const userId = trusted.actorAid ?? (trusted.localDirect ? (isProcessLevel ? owners[0] : 'local-direct') : undefined);
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);
3801
3833
  return execMenuForSystemControl.call(this, payload, {
3802
3834
  source: 'ecweb',
3803
3835
  actorAid: userId || 'local-direct',
@@ -3819,7 +3851,11 @@ export async function execMenuForControl(payload, peerId) {
3819
3851
  const isRoleRequest = name === 'role' || compatCmd === '/role';
3820
3852
  const isConnectRequest = name === 'connect' || compatCmd === '/connect';
3821
3853
  if (!isRoleRequest && !isConnectRequest && !isProcessLevelOwner(peerId, owners)) {
3822
- return menuFailure({ id, ...(name ? { name } : {}) }, { code: 'ROLE_ACCESS_DENIED', message: '控制 channel 操作需要 owner 权限' });
3854
+ return menuFailure({ id, ...(name ? { name } : {}) }, {
3855
+ code: 'NOT_ALLOWED',
3856
+ message: formatAuthorizationDenial('Control-channel process operations require DaemonOwner.', 'DAEMON_OWNER_REQUIRED'),
3857
+ data: authorizationDenialData('DAEMON_OWNER_REQUIRED'),
3858
+ });
3823
3859
  }
3824
3860
  return execMenuForSystemControl.call(this, payload, {
3825
3861
  source: 'control',