evolcore 0.0.23 → 0.0.24

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 (84) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/README.md +3 -1
  3. package/bin/codex-managed-acl.ps1 +7 -2
  4. package/bin/codex-managed-hook.mjs +127 -10
  5. package/bin/ec-safe-output.js +85 -3
  6. package/bin/install-codex-managed-hooks.mjs +17 -6
  7. package/dist/agents/baseagent-availability.js +4 -4
  8. package/dist/agents/baseagent.js +29 -1
  9. package/dist/agents/claude-runner.js +148 -13
  10. package/dist/agents/codex/app-server-client.js +18 -8
  11. package/dist/agents/codex/catalog.js +1 -1
  12. package/dist/agents/codex/diagnostics.js +215 -0
  13. package/dist/agents/codex-runner.js +438 -59
  14. package/dist/agents/ecagent-runner.js +17 -3
  15. package/dist/agents/gemini-runner.js +60 -2
  16. package/dist/agents/runner-types.js +1 -1
  17. package/dist/aun/aid/managed-operation.js +28 -6
  18. package/dist/aun/aid/store.js +41 -0
  19. package/dist/aun/msg/managed-operation.js +6 -1
  20. package/dist/aun/outbox.js +56 -18
  21. package/dist/aun/rpc/client.js +4 -5
  22. package/dist/channels/aun.js +147 -76
  23. package/dist/channels/daemon.js +7 -1
  24. package/dist/channels/dingtalk.js +1 -0
  25. package/dist/channels/feishu.js +10 -4
  26. package/dist/channels/wecom.js +2 -1
  27. package/dist/cli/agent-command.js +29 -3
  28. package/dist/cli/agent-create.js +538 -0
  29. package/dist/cli/agent.js +26 -675
  30. package/dist/cli/aun-commands.js +60 -20
  31. package/dist/cli/ctl-command.js +2 -0
  32. package/dist/cli/daemon-commands.js +148 -17
  33. package/dist/cli/index.js +2 -2
  34. package/dist/cli/managed-command-guard.js +3 -0
  35. package/dist/cli/restart-monitor.js +432 -8
  36. package/dist/cli/stats.js +110 -0
  37. package/dist/config/builtin-roles.js +2 -0
  38. package/dist/core/auth/auth-gateway.js +3 -2
  39. package/dist/core/auth/authorization-audit.js +72 -19
  40. package/dist/core/auth/operation-authorizer.js +63 -3
  41. package/dist/core/auth/operation-catalog.js +10 -2
  42. package/dist/core/auth/remote-access-store.js +3 -2
  43. package/dist/core/baseagent-loader.js +4 -4
  44. package/dist/core/bootstrap-messages.js +39 -18
  45. package/dist/core/bootstrap-service.js +32 -26
  46. package/dist/core/capability/capability-manager.js +8 -4
  47. package/dist/core/capability/providers/codex-capability-provider.js +1 -1
  48. package/dist/core/command/agent-control.js +7 -2
  49. package/dist/core/command/command-handler.js +87 -25
  50. package/dist/core/command/menu-catalog.js +3 -1
  51. package/dist/core/command/menu-handler.js +274 -70
  52. package/dist/core/command/menu-protocol.js +6 -2
  53. package/dist/core/command/slash-gate.js +1 -3
  54. package/dist/core/command/slash-handler.js +157 -48
  55. package/dist/core/data-migration.js +5 -15
  56. package/dist/core/event-catalog.js +3 -0
  57. package/dist/core/handoff/runtime.js +3 -1
  58. package/dist/core/handoff/store.js +7 -1
  59. package/dist/core/message/im-renderer.js +13 -6
  60. package/dist/core/message/message-bridge.js +5 -36
  61. package/dist/core/message/message-utils.js +6 -3
  62. package/dist/core/message/response-engine.js +73 -21
  63. package/dist/core/model/catalog-service.js +3 -1
  64. package/dist/core/model/model-catalog.js +10 -0
  65. package/dist/core/model/model-permission.js +4 -1
  66. package/dist/core/permission/approval-gateway.js +169 -33
  67. package/dist/core/permission/tool-policy.js +13 -6
  68. package/dist/core/session/adapters/codex-session-file-adapter.js +71 -3
  69. package/dist/core/session/session-manager.js +49 -13
  70. package/dist/core/session/session-renew.js +1 -0
  71. package/dist/index.js +174 -28
  72. package/dist/ipc.js +68 -21
  73. package/dist/paths.js +23 -1
  74. package/dist/stats/billing.js +18 -4
  75. package/dist/utils/atomic-write.js +26 -2
  76. package/dist/utils/cross-platform.js +436 -1
  77. package/dist/utils/error-utils.js +11 -4
  78. package/dist/utils/model-prices.jsonl +23 -20
  79. package/dist/utils/npm-ops.js +24 -1
  80. package/dist/utils/windows-autostart.js +1 -1
  81. package/kits/templates/roles/admin.json +2 -0
  82. package/package.json +7 -3
  83. package/AGENTS.md +0 -62
  84. package/dist/utils/codex-cli.js +0 -43
@@ -22,7 +22,7 @@ import { splitConfigBatchGetArgv } from '../../cli/cli-argv.js';
22
22
  import { auditCommandAuthorization, auditFullAccessEvent, hashArgv } from '../auth/authorization-audit.js';
23
23
  import { executeResolvedContactCommand, resolveContactCommand } from '../../config/contact-operation-service.js';
24
24
  import { hasTrustedFullAccessDelegation, trustedExecutionIdentityInput } from '../auth/agent-delegation.js';
25
- import { executeManagedAidOperation, ManagedAidOperationError, resolveManagedAidOperation, } from '../../aun/aid/managed-operation.js';
25
+ import { executeManagedAidOperation, ManagedAidOperationError, managedAidShowViewForRole, resolveManagedAidOperation, } from '../../aun/aid/managed-operation.js';
26
26
  import { executeManagedGroupOperation, isManagedGroupRelationRead, ManagedGroupOperationError, resolveManagedGroupOperation, executeManagedMsgOperation, ManagedMsgOperationError, resolveManagedMsgOperation, executeManagedFsOperation, ManagedFsOperationError, resolveManagedFsOperation, } from '../../aun/msg/managed-operation.js';
27
27
  import { authorizeOperation, buildAuthSubject } from '../auth/auth-gateway.js';
28
28
  import { isHClassPath, isLClassPath, isSameOrDescendant } from '../permission/protected-paths.js';
@@ -108,6 +108,7 @@ export class CommandHandler {
108
108
  interactionRouter;
109
109
  statsCollector;
110
110
  agentMap;
111
+ legacySingleRunnerMode;
111
112
  primaryRunnerKey;
112
113
  agentRegistry;
113
114
  agentApplicationService;
@@ -131,23 +132,42 @@ export class CommandHandler {
131
132
  * different backend; that would corrupt session metadata.
132
133
  */
133
134
  getAgent(channel, baseagent) {
134
- if (channel && baseagent) {
135
+ const requestedBaseagent = typeof baseagent === 'string' ? baseagent.trim() : '';
136
+ // Support callers that intentionally provide a single-runner map keyed by
137
+ // the backend name. This exact lookup does not select another agent.
138
+ if (requestedBaseagent && this.agentMap.has(requestedBaseagent)) {
139
+ return this.agentMap.get(requestedBaseagent);
140
+ }
141
+ // A caller that passed one runner explicitly has already selected the
142
+ // backend; this compatibility path does not exist for multi-agent maps.
143
+ if (this.legacySingleRunnerMode && this.agentMap.size === 1
144
+ && this.agentMap.values().next().value?.name === requestedBaseagent) {
145
+ return this.agentMap.values().next().value;
146
+ }
147
+ if (channel && requestedBaseagent) {
135
148
  const owner = this.agentRegistry?.resolveByChannel(channel);
136
149
  const evolName = owner?.name || '<unknown>';
137
- const key = `${evolName}::${baseagent}`;
150
+ const key = `${evolName}::${requestedBaseagent}`;
138
151
  if (this.agentMap.has(key))
139
152
  return this.agentMap.get(key);
140
153
  if (owner) {
141
- throw new BaseagentRunnerUnavailableError(evolName, baseagent, this.getAvailableBaseagentsForOwner(evolName));
154
+ throw new BaseagentRunnerUnavailableError(evolName, requestedBaseagent, this.getAvailableBaseagentsForOwner(evolName));
142
155
  }
143
- const globalRunner = [...this.agentMap.entries()]
144
- .find(([runnerKey]) => runnerKey === baseagent || runnerKey.endsWith(`::${baseagent}`));
145
- if (globalRunner)
146
- return globalRunner[1];
147
- }
148
- if (this.agentMap.has(this.primaryRunnerKey))
149
- return this.agentMap.get(this.primaryRunnerKey);
150
- return this.agentMap.values().next().value;
156
+ const matches = [...this.agentMap.entries()]
157
+ .filter(([runnerKey]) => runnerKey.endsWith(`::${requestedBaseagent}`));
158
+ if (matches.length === 1)
159
+ return matches[0][1];
160
+ logger.error(`[RunnerLookup] unavailable channel=${channel ?? '<none>'} baseagent=${requestedBaseagent} owner=${evolName} keys=${[...this.agentMap.keys()].join(',')}`);
161
+ throw new BaseagentRunnerUnavailableError('<unknown>', requestedBaseagent, []);
162
+ }
163
+ if (requestedBaseagent) {
164
+ const matches = [...this.agentMap.entries()]
165
+ .filter(([runnerKey]) => runnerKey.endsWith(`::${requestedBaseagent}`));
166
+ if (matches.length === 1)
167
+ return matches[0][1];
168
+ }
169
+ logger.error(`[RunnerLookup] unavailable channel=${channel ?? '<none>'} baseagent=${requestedBaseagent || '<missing>'} keys=${[...this.agentMap.keys()].join(',')}`);
170
+ throw new BaseagentRunnerUnavailableError('<unknown>', requestedBaseagent || '<missing>', []);
151
171
  }
152
172
  getAvailableBaseagentsForOwner(evolName) {
153
173
  const prefix = `${evolName}::`;
@@ -205,11 +225,13 @@ export class CommandHandler {
205
225
  this.eventBus = eventBus;
206
226
  if (agentRunnerOrMap instanceof Map) {
207
227
  this.agentMap = agentRunnerOrMap;
228
+ this.legacySingleRunnerMode = false;
208
229
  this.primaryRunnerKey = primaryRunnerKey || '<unknown>::claude';
209
230
  }
210
231
  else {
211
232
  // 测试 / 单 runner 路径:占位 agent name 用 '<unknown>'
212
233
  this.agentMap = new Map([[`<unknown>::${agentRunnerOrMap.name}`, agentRunnerOrMap]]);
234
+ this.legacySingleRunnerMode = true;
213
235
  this.primaryRunnerKey = `<unknown>::${agentRunnerOrMap.name}`;
214
236
  }
215
237
  }
@@ -424,13 +446,13 @@ export class CommandHandler {
424
446
  return fallback;
425
447
  }
426
448
  }
427
- validateTriggerPermissionModeForRole(mode, isAdmin, role, selfAid, isDaemonOwner = false) {
449
+ validateTriggerPermissionModeForRole(mode, isAdmin, role, selfAid, isDaemonOwner = false, canApprovePersistentFullAccess = isDaemonOwner) {
428
450
  if (!mode)
429
451
  return undefined;
430
452
  if (mode === 'fullaccess') {
431
453
  if (!isFullAccessEnabled())
432
454
  return 'fullaccess 功能未启用';
433
- return isDaemonOwner ? undefined : '无权限:只有 daemon-owner 可以配置 fullaccess Trigger';
455
+ return canApprovePersistentFullAccess ? undefined : '无权限:只有 daemon-owner 可以配置 fullaccess Trigger';
434
456
  }
435
457
  if (!role) {
436
458
  if (mode === 'bypass' && !isAdmin)
@@ -486,7 +508,9 @@ export class CommandHandler {
486
508
  return modelDecision.message || '当前角色不允许使用该 Trigger 模型';
487
509
  }
488
510
  if (definition.execution.effort) {
489
- const base = executionBaseagent || 'claude';
511
+ if (!executionBaseagent)
512
+ return '无法确定 Trigger 会话的 baseagent';
513
+ const base = executionBaseagent;
490
514
  const effortDecision = validateRuntimeStringFieldOverride({
491
515
  selfAid: definition.agentAid,
492
516
  role,
@@ -575,7 +599,7 @@ export class CommandHandler {
575
599
  } : undefined;
576
600
  const permissionMode = this.triggerPermissionFromParsed(parsed);
577
601
  const triggerRole = this.triggerRoleContext(schedulerAid, channel, peerId, chatType);
578
- const permissionError = this.validateTriggerPermissionModeForRole(permissionMode, isAdmin, triggerRole.role, schedulerAid, isDaemonOwner);
602
+ const permissionError = this.validateTriggerPermissionModeForRole(permissionMode, isAdmin, triggerRole.role, schedulerAid, isDaemonOwner, canApprovePersistentFullAccess);
579
603
  if (permissionError)
580
604
  return { error: permissionError };
581
605
  if (permissionMode === 'fullaccess' && !canApprovePersistentFullAccess) {
@@ -604,7 +628,7 @@ export class CommandHandler {
604
628
  };
605
629
  }
606
630
  }
607
- const authorizedBy = permissionMode === 'fullaccess' && isDaemonOwner
631
+ const authorizedBy = permissionMode === 'fullaccess' && canApprovePersistentFullAccess
608
632
  ? this.canonicalDaemonOwnerId(schedulerAid, channel, peerId, chatType)
609
633
  : undefined;
610
634
  const definition = {
@@ -1000,8 +1024,8 @@ export class CommandHandler {
1000
1024
  return await menuExecMenuQuery.call(this, cmd, channel, channelId, userId, args, explicitChatType, fromControlChannel, overrideIdentity, authSubject, source);
1001
1025
  }
1002
1026
  /** menu.update — 写入新值。 */
1003
- async execMenuUpdate(cmd, value, channel, channelId, userId, overrideIdentity, fromControlChannel = false, args, authSubject, source = 'menu') {
1004
- return await menuExecMenuUpdate.call(this, cmd, value, channel, channelId, userId, overrideIdentity, fromControlChannel, args, authSubject, source);
1027
+ async execMenuUpdate(cmd, value, channel, channelId, userId, overrideIdentity, fromControlChannel = false, args, authSubject, source = 'menu', transportReplyContext) {
1028
+ return await menuExecMenuUpdate.call(this, cmd, value, channel, channelId, userId, overrideIdentity, fromControlChannel, args, authSubject, source, transportReplyContext);
1005
1029
  }
1006
1030
  /** menu.action — 触发动词。 */
1007
1031
  async execMenuAction(cmd, action, args, channel, channelId, userId, overrideIdentity, explicitChatType, requestId, fromControlChannel = false, authSubject, source = 'menu', transportReplyContext, deferSideEffect) {
@@ -1103,7 +1127,7 @@ export class CommandHandler {
1103
1127
  /** 把 menu.action 委派给已有 slash 命令处理逻辑,把 OutboundPayload 包成结构化结果。 */
1104
1128
  async delegateAsAction(action, slashCmd, channel, channelId, userId, opts = {}) {
1105
1129
  try {
1106
- const result = await this._handleInternal(slashCmd, channel, channelId, undefined, userId, undefined, undefined, undefined, undefined, undefined, opts.overrideIdentity, opts.authSubject);
1130
+ const result = await this._handleInternal(slashCmd, channel, channelId, undefined, userId, opts.threadId, opts.chatType, undefined, undefined, undefined, opts.overrideIdentity, opts.authSubject, opts.replyContext);
1107
1131
  if (result == null) {
1108
1132
  // null / undefined: 命令未识别或前置守卫拦截(如 idle 检查),视为失败
1109
1133
  return { error: '命令未执行(可能被前置守卫拦截)', code: 'EXEC_FAILED' };
@@ -1432,7 +1456,7 @@ export class CommandHandler {
1432
1456
  return { ok: false, error: err?.message || String(err) };
1433
1457
  }
1434
1458
  if (updated.execution.permissionMode === 'fullaccess') {
1435
- if (!isDaemonOwner) {
1459
+ if (!isDaemonOwner && !canApprovePersistentFullAccess) {
1436
1460
  return { ok: false, code: 'FORBIDDEN', error: '无权限:只有 daemon-owner 可以配置 fullaccess Trigger' };
1437
1461
  }
1438
1462
  if (!canApprovePersistentFullAccess) {
@@ -1455,7 +1479,7 @@ export class CommandHandler {
1455
1479
  return { ok: false, error: '无权限:只有 owner/admin 可以修改 script trigger' };
1456
1480
  }
1457
1481
  const triggerRole = this.triggerRoleContext(definition.agentAid, definition.origin?.channelKey || channel, definition.origin?.peerId || peerId);
1458
- const permissionError = this.validateTriggerPermissionModeForRole(updated.execution.permissionMode, isAdmin, triggerRole.role, definition.agentAid, isDaemonOwner);
1482
+ const permissionError = this.validateTriggerPermissionModeForRole(updated.execution.permissionMode, isAdmin, triggerRole.role, definition.agentAid, isDaemonOwner, canApprovePersistentFullAccess);
1459
1483
  if (permissionError) {
1460
1484
  return {
1461
1485
  ok: false,
@@ -1922,7 +1946,10 @@ export class CommandHandler {
1922
1946
  if (sessionSelfAid && sessionSelfAid !== grantSelfAid) {
1923
1947
  return { ok: false, code: 'INVALID_DELEGATION', error: 'Delegation self agent does not match the active session' };
1924
1948
  }
1925
- if (parsed.command.aid !== grantSelfAid && !hasTrustedFullAccessDelegation(delegation.grant)) {
1949
+ const requiresSelfAid = parsed.command.kind === 'show' || parsed.command.kind === 'agentmd.put';
1950
+ if (requiresSelfAid
1951
+ && parsed.command.aid !== grantSelfAid
1952
+ && !hasTrustedFullAccessDelegation(delegation.grant)) {
1926
1953
  return { ok: false, code: 'SCOPE_MISMATCH', error: 'Only the current Agent AID can be targeted' };
1927
1954
  }
1928
1955
  let conversationId;
@@ -1962,7 +1989,12 @@ export class CommandHandler {
1962
1989
  if (!decision.allow)
1963
1990
  return { ok: false, code: decision.code, error: decision.reason };
1964
1991
  try {
1965
- return { ok: true, result: await executeManagedAidOperation(parsed.command) };
1992
+ return {
1993
+ ok: true,
1994
+ result: await executeManagedAidOperation(parsed.command, {
1995
+ showView: managedAidShowViewForRole(subject.role),
1996
+ }),
1997
+ };
1966
1998
  }
1967
1999
  catch (error) {
1968
2000
  const message = error instanceof Error ? error.message : String(error);
@@ -2188,7 +2220,14 @@ export class CommandHandler {
2188
2220
  if (!hasTrustedFullAccessDelegation(grant) && (grant.channelType !== 'aun' || grant.chatType !== 'private')) {
2189
2221
  return { ok: false, code: 'SCOPE_MISMATCH', error: 'Managed msg reads require the current AUN private relation' };
2190
2222
  }
2191
- if (!hasTrustedFullAccessDelegation(grant) && parsed.command.target !== conversationId) {
2223
+ // A management role may read history for any peer, one peer per query.
2224
+ // `issuedRole` is resolved daemon-side from the persisted relation
2225
+ // config, so it cannot be spoofed by the calling task. Narrower roles
2226
+ // stay pinned here, and authorizeOperation below remains the authority
2227
+ // for every role (member carries currentRelationOnly; visitor is denied).
2228
+ const mayReadCrossPeer = hasTrustedFullAccessDelegation(grant)
2229
+ || (parsed.command.kind === 'history' && isAdminRole(grant.issuedRole));
2230
+ if (!mayReadCrossPeer && parsed.command.target !== conversationId) {
2192
2231
  return { ok: false, code: 'SCOPE_MISMATCH', error: 'Only the current AUN private relation can be targeted' };
2193
2232
  }
2194
2233
  }
@@ -2458,6 +2497,29 @@ export class CommandHandler {
2458
2497
  });
2459
2498
  if (!ctlDecision.allow)
2460
2499
  return { ok: false, code: ctlDecision.code, error: ctlDecision.reason };
2500
+ if (cmd.startsWith('/status ') && cmd !== '/status --format json') {
2501
+ return { ok: false, code: 'INVALID_ARGUMENT', error: 'status 仅支持可选参数 --format json' };
2502
+ }
2503
+ if (cmd === '/status --format json') {
2504
+ const context = {
2505
+ selfAid: grantSelfAid,
2506
+ sessionId,
2507
+ taskId: grant.taskId,
2508
+ ...(grant.messageId ? { messageId: grant.messageId } : {}),
2509
+ baseagent: session.baseagent,
2510
+ channel: grant.channelType,
2511
+ channelKey: grant.channel,
2512
+ channelId: conversationId,
2513
+ chatType: grant.chatType,
2514
+ peerKey: grant.peerKey,
2515
+ peerId: conversationId,
2516
+ peerRole: ctlSubject.role,
2517
+ permissionMode: grant.permissionMode ?? ctlSubject.permissionMode,
2518
+ projectPath: path.resolve(session.projectPath),
2519
+ ...(grant.sessionRuntimeDir ? { tmpDir: grant.sessionRuntimeDir } : {}),
2520
+ };
2521
+ return { ok: true, result: JSON.stringify(context, null, 2), data: context };
2522
+ }
2461
2523
  if (cmd.startsWith('/model info ')) {
2462
2524
  const modelId = cmd.slice('/model info '.length).trim();
2463
2525
  if (!modelId || /\s/.test(modelId))
@@ -83,6 +83,7 @@ const ENTRIES = [
83
83
  { id: 'system.monitor', type: 'list', contexts: ['process'], name: 'system', label: '运行监控快照', section: 'process', order: 11, query: { cmd: '/system', args: { view: 'monitor' }, operation: 'system.status', scope: 'process' }, control: 'list' },
84
84
  { id: 'system.logs', type: 'list', contexts: ['process'], name: 'system', label: 'Daemon 日志', section: 'process', order: 12, query: { cmd: '/system', args: { view: 'logs' }, operation: 'system.status', scope: 'process' }, options: { cmd: '/system', args: { option: 'logs' }, operation: 'system.status', scope: 'process', mode: 'dynamic' }, control: 'list' },
85
85
  { id: 'config.rawFile', type: 'list', contexts: ['process'], name: 'config', label: '原始配置文件', section: 'process', order: 13, query: { cmd: '/config', args: { view: 'rawFile' }, operation: 'config.read', scope: 'process' }, options: { cmd: '/config', args: { option: 'rawFile' }, operation: 'config.read', scope: 'process', mode: 'dynamic' }, control: 'list' },
86
+ { id: 'baseagent.models', type: 'list', contexts: ['process'], name: 'baseagent-models', label: 'Baseagent 模型目录', section: 'process', order: 41, query: { cmd: '/baseagent-models', args: { baseagent: 'claude' }, operation: 'baseagent.models.list', scope: 'process' }, control: 'list' },
86
87
  { id: 'system.restart', type: 'action', contexts: ['process'], name: 'system', label: '重启服务', section: 'process', order: 20, action: { cmd: '/system', action: 'restart', operation: 'system.restart', scope: 'process' } },
87
88
  { id: 'system.upgrade', type: 'action', contexts: ['process'], name: 'system', label: '升级服务', section: 'process', order: 30, action: { cmd: '/system', action: 'upgrade', operation: 'system.upgrade', scope: 'process' } },
88
89
  { id: 'agent.list', type: 'list', contexts: ['process'], name: 'agent', label: 'Agent 列表', section: 'process', order: 40, options: { cmd: '/agent', args: { options: 'all' }, operation: 'agent.list', scope: 'control', mode: 'dynamic' }, control: 'list' },
@@ -110,7 +111,8 @@ export async function resolveMenuCatalogContext(input) {
110
111
  throw { code: 'FORBIDDEN', message: 'control catalog context requires a control subject' };
111
112
  }
112
113
  if (requested === 'process') {
113
- if (input.channelScope !== 'control' || !subject.fromControlChannel || !subject.isDaemonOwner) {
114
+ if (input.channelScope !== 'control' || !subject.fromControlChannel
115
+ || (!subject.isDaemonOwner && subject.processRole !== 'daemon-service')) {
114
116
  throw {
115
117
  code: 'NOT_ALLOWED',
116
118
  message: formatAuthorizationDenial('Process context requires a DaemonOwner control subject.', 'DAEMON_OWNER_REQUIRED'),