evolcore 0.0.24 → 0.0.26

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 (112) hide show
  1. package/CHANGELOG.md +59 -0
  2. package/bin/ec.js +12 -1
  3. package/bin/install-codex-managed-hooks.mjs +28 -1
  4. package/dist/agents/baseagent.js +96 -46
  5. package/dist/agents/claude-runner.js +54 -13
  6. package/dist/agents/codex/app-server-client.js +13 -5
  7. package/dist/agents/codex/catalog.js +1 -1
  8. package/dist/agents/codex/diagnostics.js +34 -1
  9. package/dist/agents/codex-runner.js +128 -42
  10. package/dist/agents/ecagent-runner.js +14 -18
  11. package/dist/agents/gemini-runner.js +24 -9
  12. package/dist/agents/runner-types.js +25 -17
  13. package/dist/aun/aid/agentmd.js +135 -10
  14. package/dist/aun/aid/client.js +1 -1
  15. package/dist/aun/aid/identity.js +1 -1
  16. package/dist/aun/aid/index.js +2 -1
  17. package/dist/aun/aid/managed-operation.js +53 -1
  18. package/dist/aun/aid/store.js +17 -13
  19. package/dist/aun/menu-client.js +74 -0
  20. package/dist/aun/msg/group.js +1 -1
  21. package/dist/aun/msg/managed-operation.js +1 -1
  22. package/dist/aun/msg/p2p.js +6 -1
  23. package/dist/aun/msg/upload.js +30 -22
  24. package/dist/aun/storage/client.js +4 -1
  25. package/dist/aun/storage/index.js +1 -1
  26. package/dist/channels/aun.js +329 -94
  27. package/dist/cli/agent-command.js +125 -5
  28. package/dist/cli/agent-create.js +151 -11
  29. package/dist/cli/agent.js +119 -6
  30. package/dist/cli/aun-commands.js +74 -7
  31. package/dist/cli/config.js +27 -8
  32. package/dist/cli/contact.js +63 -6
  33. package/dist/cli/daemon-commands.js +90 -25
  34. package/dist/cli/diag.js +31 -0
  35. package/dist/cli/handoff-command.js +35 -3
  36. package/dist/cli/index.js +7 -1
  37. package/dist/cli/managed-command-guard.js +3 -0
  38. package/dist/cli/menu-client.js +38 -23
  39. package/dist/cli/restart-monitor.js +57 -10
  40. package/dist/config/builtin-roles.js +2 -0
  41. package/dist/config/config-manager.js +15 -0
  42. package/dist/config/config-operation-service.js +165 -3
  43. package/dist/config/contact-operation-service.js +96 -7
  44. package/dist/config/resolved-config-op.js +66 -8
  45. package/dist/core/agent-application-service.js +2 -1
  46. package/dist/core/auth/authorization-audit.js +20 -12
  47. package/dist/core/auth/operation-authorizer.js +87 -8
  48. package/dist/core/auth/operation-catalog.js +51 -4
  49. package/dist/core/auth/remote-access-store.js +40 -1
  50. package/dist/core/bootstrap-service.js +1 -1
  51. package/dist/core/command/agent-control.js +54 -25
  52. package/dist/core/command/cli-intent-parser.js +18 -0
  53. package/dist/core/command/command-handler.js +152 -20
  54. package/dist/core/command/diag-service.js +118 -0
  55. package/dist/core/command/menu-catalog.js +13 -3
  56. package/dist/core/command/menu-handler.js +88 -31
  57. package/dist/core/command/menu-protocol.js +6 -0
  58. package/dist/core/command/menu-token-store.js +41 -8
  59. package/dist/core/command/slash-gate.js +34 -4
  60. package/dist/core/command/slash-handler.js +77 -12
  61. package/dist/core/event-catalog.js +5 -0
  62. package/dist/core/evolagent.js +3 -3
  63. package/dist/core/handoff/runtime.js +205 -27
  64. package/dist/core/handoff/store.js +193 -4
  65. package/dist/core/message/im-renderer.js +72 -21
  66. package/dist/core/message/message-bridge.js +69 -9
  67. package/dist/core/message/message-queue.js +26 -0
  68. package/dist/core/message/message-utils.js +1 -0
  69. package/dist/core/message/response-engine.js +48 -23
  70. package/dist/core/model/catalog-service.js +79 -49
  71. package/dist/core/model/context-window-suffix.js +33 -0
  72. package/dist/core/model/model-catalog.js +1 -1
  73. package/dist/core/model/model-permission.js +12 -3
  74. package/dist/core/permission/ec-command-parser.js +51 -12
  75. package/dist/core/permission/sandbox-runtime.js +36 -14
  76. package/dist/core/permission/tool-policy.js +47 -7
  77. package/dist/index.js +195 -31
  78. package/dist/ipc.js +54 -2
  79. package/dist/response-system/engines/v1/proactive-flow.js +59 -2
  80. package/dist/stats/billing.js +41 -15
  81. package/dist/stats/price-resolver.js +28 -10
  82. package/dist/utils/avatar-upload.js +282 -23
  83. package/dist/utils/cross-platform.js +30 -1
  84. package/dist/utils/error-utils.js +21 -0
  85. package/dist/utils/evolcore-version.js +129 -0
  86. package/dist/utils/logger.js +3 -1
  87. package/dist/utils/model-prices.jsonl +3 -3
  88. package/dist/utils/npm-ops.js +176 -35
  89. package/kits/docs/context-assembly.md +1 -0
  90. package/kits/docs/evolcore/INDEX.md +3 -1
  91. package/kits/docs/evolcore/agent-create.md +30 -11
  92. package/kits/docs/evolcore/agent.md +18 -8
  93. package/kits/docs/evolcore/aid.md +6 -4
  94. package/kits/docs/evolcore/codex-windows-sandbox.md +146 -0
  95. package/kits/docs/evolcore/config.md +7 -0
  96. package/kits/docs/evolcore/contact.md +12 -5
  97. package/kits/docs/evolcore/menu-client.md +11 -3
  98. package/kits/docs/evolcore/msg.md +1 -0
  99. package/kits/docs/prompt-loading-architecture.md +1 -0
  100. package/kits/docs/venues/group.md +12 -0
  101. package/kits/eck_manifest.json +12 -0
  102. package/kits/rules/03-identity.md +3 -1
  103. package/kits/templates/message-fragments/handoff-request-to-target.md +3 -3
  104. package/kits/templates/roles/admin.json +14 -2
  105. package/kits/templates/roles/member.json +3 -2
  106. package/kits/templates/roles/visitor.json +4 -3
  107. package/kits/templates/system-fragments/bootstrap.md +16 -5
  108. package/kits/templates/system-fragments/channel.md +1 -1
  109. package/kits/templates/system-fragments/session.md +1 -1
  110. package/package.json +6 -4
  111. package/skills/eclink/SKILL.md +0 -84
  112. package/skills/eclink/agents/openai.yaml +0 -4
@@ -1,6 +1,7 @@
1
1
  import path from 'path';
2
2
  import { getArgValue, isHelpFlag, wantsHelp } from './help.js';
3
3
  import { isHostChinese } from '../utils/locale.js';
4
+ import { readTaskRuntimeContextFromEnv } from './task-context.js';
4
5
  function formatTimeAgo(ms) {
5
6
  const sec = Math.floor(ms / 1000);
6
7
  if (sec < 60)
@@ -25,7 +26,7 @@ function formatBytes(bytes) {
25
26
  export async function cmdAgent(args) {
26
27
  const sub = args[0];
27
28
  const formatJson = getArgValue(args, '--format') === 'json';
28
- const commands = new Set(['list', 'show', 'new', 'enable', 'disable', 'get', 'set', 'rename', 'ready', 'reload', 'delete']);
29
+ const commands = new Set(['list', 'show', 'new', 'enable', 'disable', 'get', 'set', 'persona', 'rename', 'ready', 'reload', 'delete']);
29
30
  if (sub === 'create') {
30
31
  const error = { ok: false, code: 'INVALID_COMMAND', error: '`ec agent create` is not a command; use `ec agent new`.' };
31
32
  if (formatJson)
@@ -47,6 +48,9 @@ Commands:
47
48
  disable <aid> 停用 agent
48
49
  get <aid> <key> 读取单个配置字段(支持点路径)
49
50
  set <aid> <key> <val> 修改单个配置字段(支持点路径)
51
+ persona show 查看当前 Agent 的 persona.md
52
+ persona --text <text> 用文本设置当前 Agent 的 persona.md
53
+ persona --file <path> 用 .md/.txt 文件设置当前 Agent 的 persona.md
50
54
  rename <aid> <name> 修改 agent.md 中的显示名称
51
55
  ready <aid> 标记 bootstrap 完成,进入 active 状态
52
56
  reload [aid] 热重载配置(无参数=全量 resync)
@@ -63,14 +67,15 @@ Options:
63
67
  ec agent enable mybot.agentid.pub
64
68
  ec agent get mybot.agentid.pub active_baseagent
65
69
  ec agent set mybot.agentid.pub active_baseagent codex
70
+ ec agent persona --text "你是一名严谨的诊断工程师。"
66
71
  ec agent rename mybot.agentid.pub "New Name"
67
72
  ec agent ready mybot.agentid.pub
68
73
  ec agent delete mybot.agentid.pub --purge`);
69
74
  return;
70
75
  }
71
- const { agentList, agentShow, agentCreateInteractive, agentCreateNonInteractive, agentReload, agentEnable, agentDisable, agentGet, agentSet, agentRename, agentDelete, agentReady, runManagedAgentOperation, } = await import('./agent.js');
72
- const { parseAgentOwnerArgs, resolveAgentCreatePlan, validateAgentCreatePlan } = await import('./agent-create.js');
73
- if (process.env.EVOLCORE_SESSION_ID && !wantsHelp(args) && !new Set(['get', 'set', 'ready']).has(sub)) {
76
+ const { agentList, agentShow, agentCreateInteractive, agentCreateNonInteractive, agentReload, agentEnable, agentDisable, agentGet, agentSet, agentPersona, agentRename, agentDelete, agentReady, runManagedAgentOperation, } = await import('./agent.js');
77
+ const { parseAgentOwnerArgs, parseAgentTags, resolveAgentCreatePlan, validateAgentCreatePlan } = await import('./agent-create.js');
78
+ if (process.env.EVOLCORE_SESSION_ID && !wantsHelp(args) && !new Set(['get', 'set', 'persona', 'ready']).has(sub)) {
74
79
  if (sub === 'new' && !args.includes('--non-interactive')) {
75
80
  const error = {
76
81
  ok: false,
@@ -215,6 +220,8 @@ Options:
215
220
  --handoff <context> 当前任务的 Bootstrap 上下文
216
221
  --name <display-name> 默认: package.json name,否则目录名
217
222
  --description <text> 默认: package.json description,否则使用 EvolCore 默认描述
223
+ --type <type> 主体类型: human|agent|group|service|device|org(默认 agent)
224
+ --tags <json-array> 3 到 6 个标签,例如 '["coding","diagnostics","logs"]'
218
225
  --dry-run 只预览最终参数,不注册 AID、不写文件
219
226
  --force 覆盖已有 config.json
220
227
  --format json 输出 JSON
@@ -259,6 +266,19 @@ Options:
259
266
  process.exitCode = 1;
260
267
  return;
261
268
  }
269
+ let tags;
270
+ try {
271
+ tags = parseAgentTags(getArg('--tags'));
272
+ }
273
+ catch (error) {
274
+ const result = { ok: false, error: error instanceof Error ? error.message : String(error) };
275
+ if (formatJson)
276
+ console.log(JSON.stringify(result));
277
+ else
278
+ console.error(`❌ ${result.error}`);
279
+ process.exitCode = 1;
280
+ return;
281
+ }
262
282
  const plan = resolveAgentCreatePlan({
263
283
  aid: name,
264
284
  project: getArg('--project'),
@@ -267,6 +287,8 @@ Options:
267
287
  handoff: getArg('--handoff'),
268
288
  name: getArg('--name'),
269
289
  description: getArg('--description'),
290
+ type: getArg('--type'),
291
+ tags,
270
292
  force: args.includes('--force'),
271
293
  });
272
294
  const validationError = validateAgentCreatePlan(plan);
@@ -304,6 +326,8 @@ Options:
304
326
  owners: plan.owners,
305
327
  name: plan.name,
306
328
  description: plan.description,
329
+ type: plan.type,
330
+ tags: plan.tags,
307
331
  force: args.includes('--force'),
308
332
  });
309
333
  if (!result.ok) {
@@ -330,6 +354,15 @@ Options:
330
354
  : result.hotLoadError
331
355
  ? ` ✗ 热重载失败:${result.hotLoadError}`
332
356
  : ' ⚠ 服务未运行,下次 ec start 时生效');
357
+ if (result.ownerContactsSynced?.length) {
358
+ console.log(` ✓ 已同步 Owner 联系人: ${result.ownerContactsSynced.join(', ')}`);
359
+ }
360
+ if (result.ownerContactsSkipped?.length) {
361
+ console.log(` ⚠ 无本地联系人簿,跳过 Owner: ${result.ownerContactsSkipped.join(', ')}`);
362
+ }
363
+ if (result.ownerContactSyncErrors?.length) {
364
+ console.log(` ⚠ Owner 联系人同步失败: ${result.ownerContactSyncErrors.join('; ')}`);
365
+ }
333
366
  if (result.ownerBoundAid) {
334
367
  console.log(` ✓ agent owner 已绑定: ${result.ownerBoundAid}`);
335
368
  }
@@ -364,6 +397,15 @@ Options:
364
397
  : result.hotLoadError
365
398
  ? ` ✗ 热重载失败:${result.hotLoadError}`
366
399
  : ' ⚠ 服务未运行,下次 ec start 时生效');
400
+ if (result.ownerContactsSynced?.length) {
401
+ console.log(` ✓ 已同步 Owner 联系人: ${result.ownerContactsSynced.join(', ')}`);
402
+ }
403
+ if (result.ownerContactsSkipped?.length) {
404
+ console.log(` ⚠ 无本地联系人簿,跳过 Owner: ${result.ownerContactsSkipped.join(', ')}`);
405
+ }
406
+ if (result.ownerContactSyncErrors?.length) {
407
+ console.log(` ⚠ Owner 联系人同步失败: ${result.ownerContactSyncErrors.join('; ')}`);
408
+ }
367
409
  if (result.ownerBoundAid) {
368
410
  console.log(` ✓ agent owner 已绑定: ${result.ownerBoundAid}`);
369
411
  }
@@ -497,6 +539,84 @@ Options:
497
539
  return;
498
540
  }
499
541
  // --- get ---
542
+ if (sub === 'persona') {
543
+ if (wantsHelp(args)) {
544
+ console.log(`用法: ec agent persona show [--format json]
545
+ ec agent persona --text <text> [--format json]
546
+ ec agent persona --file <path> [--format json]
547
+
548
+ 读取或设置当前 Agent 的 persona.md。--file 只接受 .md/.txt 文本文件。`);
549
+ return;
550
+ }
551
+ const formatSuffix = args.length >= 3
552
+ && args.at(-2) === '--format'
553
+ && args.at(-1) === 'json';
554
+ const commandArgs = formatSuffix ? args.slice(0, -2) : args;
555
+ const personaArgs = commandArgs.slice(1);
556
+ const invalid = (error) => {
557
+ const result = { ok: false, code: 'INVALID_ARGS', error };
558
+ if (formatJson)
559
+ console.log(JSON.stringify(result));
560
+ else
561
+ console.error(`❌ ${result.error}`);
562
+ process.exitCode = 1;
563
+ return;
564
+ };
565
+ if (!formatSuffix && args.includes('--format')) {
566
+ invalid('persona 仅支持末尾的 --format json');
567
+ return;
568
+ }
569
+ let text;
570
+ let file;
571
+ if (personaArgs.length === 1 && personaArgs[0] === 'show') {
572
+ // show is the only read form; no payload is accepted.
573
+ }
574
+ else if (personaArgs.length === 2 && personaArgs[0] === '--text') {
575
+ text = personaArgs[1];
576
+ if (text === undefined) {
577
+ invalid('persona --text 缺少参数');
578
+ return;
579
+ }
580
+ }
581
+ else if (personaArgs.length === 2 && personaArgs[0] === '--file') {
582
+ file = personaArgs[1];
583
+ if (!file || file.startsWith('-')) {
584
+ invalid('persona --file 缺少有效路径');
585
+ return;
586
+ }
587
+ }
588
+ else {
589
+ invalid('用法: ec agent persona show | --text <text> | --file <path>');
590
+ return;
591
+ }
592
+ const taskAid = process.env.EVOLCORE_SESSION_ID ? (readTaskRuntimeContextFromEnv()?.selfAid || '') : '';
593
+ const aid = taskAid || process.env.EVOLCORE_SELF_AID || '';
594
+ if (!aid) {
595
+ const result = { ok: false, code: 'INVALID_ARGS', error: '无法确定当前 Agent AID' };
596
+ if (formatJson)
597
+ console.log(JSON.stringify(result));
598
+ else
599
+ console.error(`❌ ${result.error}`);
600
+ process.exitCode = 1;
601
+ return;
602
+ }
603
+ const result = await agentPersona(aid, { text, file });
604
+ if (!result.ok) {
605
+ if (formatJson)
606
+ console.log(JSON.stringify(result));
607
+ else
608
+ console.error(`❌ ${result.error}`);
609
+ process.exitCode = 1;
610
+ return;
611
+ }
612
+ if (formatJson)
613
+ console.log(JSON.stringify(result, null, 2));
614
+ else if (result.saved)
615
+ console.log(`✓ ${aid} persona.md 已保存`);
616
+ else
617
+ console.log(result.content ?? '(persona.md 不存在)');
618
+ return;
619
+ }
500
620
  if (sub === 'get') {
501
621
  if (wantsHelp(args)) {
502
622
  console.log(`用法: ec agent get <aid> <key> [--format json]
@@ -720,7 +840,7 @@ Options:
720
840
  printAgentShowHuman(result);
721
841
  }
722
842
  function printAgentShowHuman(result) {
723
- console.log(`${result.aid} (${result.status})\n`);
843
+ console.log(`${result.aid} (${result.status}, lifecycle=${result.lifecycle || 'unknown'})\n`);
724
844
  if (result.identity.name || result.identity.description) {
725
845
  console.log(' Identity');
726
846
  if (result.identity.name)
@@ -7,12 +7,33 @@ import { ConfigTarget, write as cfgWrite, ensureFile as cfgEnsure, read as cfgRe
7
7
  import { ipcQuery } from '../ipc.js';
8
8
  import { CONFIG_SCHEMA_VERSION } from '../types.js';
9
9
  import { ensureInitialRoleRegistry } from '../config/role-service.js';
10
+ import { addContact } from '../config/contact-request-service.js';
10
11
  import { getCodexAppServerAvailability } from '../agents/codex-runner.js';
11
12
  import { resolveEcagentConfig } from '../agents/baseagent.js';
12
13
  import { AUTO_BASEAGENT_CANDIDATES, BASEAGENT_CANDIDATES, detectAvailableBaseagents, } from '../agents/baseagent-availability.js';
13
14
  import { agentProjectRootFromDefaults, deriveAgentProjectPath } from '../utils/project-path.js';
14
15
  import { isValidAid } from '../aun/aid/validation.js';
15
- import { DEFAULT_AGENT_DESCRIPTION } from '../aun/aid/agentmd.js';
16
+ import { AGENT_DESCRIPTION_MAX_LENGTH, AGENT_TAG_MAX_COUNT, AGENT_TAG_MIN_COUNT, DEFAULT_AGENT_DESCRIPTION, DEFAULT_AGENT_TYPE, normalizeAgentType, validateAgentTags, validateAgentType, } from '../aun/aid/agentmd.js';
17
+ export function parseAgentTags(raw) {
18
+ if (raw === undefined)
19
+ return undefined;
20
+ let parsed;
21
+ try {
22
+ parsed = JSON.parse(raw);
23
+ }
24
+ catch {
25
+ throw new Error('--tags must be a JSON array of 3 to 6 valid tags');
26
+ }
27
+ if (!Array.isArray(parsed))
28
+ throw new Error('--tags must be a JSON array of 3 to 6 valid tags');
29
+ const tags = parsed.map(value => typeof value === 'string' ? value.trim() : value);
30
+ const tagErrors = validateAgentTags(tags, { minCount: AGENT_TAG_MIN_COUNT, maxCount: AGENT_TAG_MAX_COUNT });
31
+ if (tagErrors.length > 0)
32
+ throw new Error(`--tags ${tagErrors.join('; ')}`);
33
+ // Duplicate labels are harmless at the wire level, but should not consume
34
+ // the advertised 3-6 tag budget or produce duplicate frontmatter entries.
35
+ return [...new Set(tags)];
36
+ }
16
37
  export function validateAgentOwnerOptions(options) {
17
38
  if (options.owner !== undefined && options.owners !== undefined) {
18
39
  return 'Options --owner and --owners are mutually exclusive';
@@ -69,6 +90,42 @@ function withStaticOwners(config, ownersInput) {
69
90
  function withStaticOwner(config, owner) {
70
91
  return withStaticOwners(config, owner ? [owner] : undefined);
71
92
  }
93
+ /**
94
+ * An Agent owner must be able to receive the new Agent's Bootstrap reply.
95
+ * Contact admission is per local Agent, so materialize the new AID in each
96
+ * locally managed owner's contact book immediately after creation. Human or
97
+ * remote owners have no local contact book and are reported as skipped.
98
+ */
99
+ export async function syncOwnerContactsAfterCreate(createdAid, owners) {
100
+ const synced = [];
101
+ const skipped = [];
102
+ const errors = [];
103
+ for (const ownerAid of [...new Set((owners ?? []).map(value => value.trim()).filter(Boolean))]) {
104
+ if (ownerAid === createdAid)
105
+ continue;
106
+ const ownerConfig = path.join(resolvePaths().agentsDir, ownerAid, 'config.json');
107
+ if (!fs.existsSync(ownerConfig)) {
108
+ skipped.push(ownerAid);
109
+ continue;
110
+ }
111
+ try {
112
+ await addContact({
113
+ selfAid: ownerAid,
114
+ primaryId: createdAid,
115
+ actorId: 'agent-create-owner-sync',
116
+ });
117
+ synced.push(ownerAid);
118
+ }
119
+ catch (error) {
120
+ errors.push(`${ownerAid}: ${error instanceof Error ? error.message : String(error)}`);
121
+ }
122
+ }
123
+ return {
124
+ ...(synced.length > 0 ? { ownerContactsSynced: synced } : {}),
125
+ ...(skipped.length > 0 ? { ownerContactsSkipped: skipped } : {}),
126
+ ...(errors.length > 0 ? { ownerContactSyncErrors: errors } : {}),
127
+ };
128
+ }
72
129
  function pickDefaultBaseagent(available) {
73
130
  return AUTO_BASEAGENT_CANDIDATES.find(candidate => available.includes(candidate)) ?? null;
74
131
  }
@@ -117,13 +174,25 @@ export function resolveAgentCreatePlan(options) {
117
174
  const owner = options.owner?.trim() || (owners === undefined ? daemon.owners?.[0] : undefined);
118
175
  const ownerSource = owners !== undefined || options.owner?.trim() ? 'argument' : owner ? 'daemon' : 'none';
119
176
  const name = options.name?.trim() || metadata.name || projectSlug(project);
120
- const description = options.description?.trim() || metadata.description || DEFAULT_AGENT_DESCRIPTION;
177
+ // description 是可选字段:用户显式传入的必须合规(超长会在校验阶段被拒);
178
+ // 从 package.json 推导出来的超长值直接弃用、回落默认短描述——用户没设过它,
179
+ // 不该因此让创建命令失败。
180
+ const explicitDescription = options.description?.trim();
181
+ const packageDescription = metadata.description
182
+ && Array.from(metadata.description).length <= AGENT_DESCRIPTION_MAX_LENGTH
183
+ ? metadata.description
184
+ : undefined;
185
+ const description = explicitDescription || packageDescription || DEFAULT_AGENT_DESCRIPTION;
186
+ // 大小写不敏感:能识别就收敛成小写;无法识别时保留原值,交给 validateAgentCreatePlan 报错。
187
+ const requestedType = options.type?.trim();
188
+ const type = requestedType ? (normalizeAgentType(requestedType) ?? requestedType) : DEFAULT_AGENT_TYPE;
121
189
  const configExists = isValidAid(aid) && fs.existsSync(path.join(resolvePaths().agentsDir, aid, 'config.json'));
122
190
  return {
123
- aid, project, baseagent, owner, owners, handoff: options.handoff, name, description,
191
+ aid, project, baseagent, owner, owners, handoff: options.handoff, name, description, type, tags: options.tags,
124
192
  force: options.force === true, configExists, baseagentSource, ownerSource,
125
193
  nameSource: options.name?.trim() ? 'argument' : metadata.name ? 'package' : 'directory',
126
- descriptionSource: options.description?.trim() ? 'argument' : metadata.description ? 'package' : 'default',
194
+ descriptionSource: explicitDescription ? 'argument' : packageDescription ? 'package' : 'default',
195
+ typeSource: options.type?.trim() ? 'argument' : 'default',
127
196
  };
128
197
  }
129
198
  export function validateAgentCreatePlan(plan) {
@@ -134,6 +203,21 @@ export function validateAgentCreatePlan(plan) {
134
203
  return `Invalid AID "${plan.aid}": must be a valid multi-level domain (e.g. mybot.agentid.pub)`;
135
204
  if (plan.configExists && !plan.force)
136
205
  return `Agent "${plan.aid}" already exists (use --force to overwrite)`;
206
+ if (plan.tags !== undefined) {
207
+ const tagErrors = validateAgentTags(plan.tags, { minCount: AGENT_TAG_MIN_COUNT, maxCount: AGENT_TAG_MAX_COUNT });
208
+ if (tagErrors.length > 0)
209
+ return `Invalid tags: ${tagErrors.join('; ')}`;
210
+ }
211
+ const typeError = validateAgentType(plan.type);
212
+ if (typeError)
213
+ return `Invalid --type: ${typeError}`;
214
+ // 只有显式传入的 --description 才会超限(package.json 的超长值在 resolve 阶段
215
+ // 已被弃用)。必须在这里就拒绝:否则 plan 通过、直到 agentmdPut 上传才失败,
216
+ // 而那处是 warn 级不中断创建,最终留下一张 SDK 兜底名片(--type/--name 全部丢失)。
217
+ const descriptionLength = Array.from(plan.description).length;
218
+ if (descriptionLength > AGENT_DESCRIPTION_MAX_LENGTH) {
219
+ return `Invalid --description: ${descriptionLength} characters exceeds the agent.md limit of ${AGENT_DESCRIPTION_MAX_LENGTH}. Pass a shorter --description.`;
220
+ }
137
221
  if (!plan.baseagent)
138
222
  return 'No usable baseagent detected. Pass --baseagent or configure a baseagent in agents/defaults.json.';
139
223
  if (!BASEAGENT_CANDIDATES.includes(plan.baseagent)) {
@@ -305,15 +389,25 @@ export async function agentCreateInteractive(opts = {}) {
305
389
  const defaultName = aid.split('.')[0];
306
390
  const agentName = (await ask(`Display name [${defaultName}]: `)).trim() || defaultName;
307
391
  const agentDescription = (await ask('Description (optional): ')).trim() || '';
392
+ const tagInput = (await ask('Tags (3-6, comma separated; optional): ')).trim();
393
+ let agentTags;
394
+ if (tagInput) {
395
+ agentTags = [...new Set(tagInput.split(/[,,\s]+/).map(tag => tag.trim()).filter(Boolean))];
396
+ const tagErrors = validateAgentTags(agentTags, { minCount: AGENT_TAG_MIN_COUNT, maxCount: AGENT_TAG_MAX_COUNT });
397
+ if (tagErrors.length > 0)
398
+ return { ok: false, error: `Invalid tags: ${tagErrors.join('; ')}` };
399
+ }
308
400
  if (ownRl)
309
401
  rl.close();
310
402
  saveAgent(withStaticOwner({ $schema_version: CONFIG_SCHEMA_VERSION, aid, enabled: true, lifecycle: 'created', channels: [], projects: { defaultPath: projectPath } }, owner));
311
403
  ensureInitialRoleRegistry(aid);
312
404
  saveInitialBehavior(aid, baseagent);
313
405
  ensureAgentDirSkeleton(aid);
406
+ if (owner)
407
+ await syncOwnerContactsAfterCreate(aid, [owner]);
314
408
  let agentmdUploaded = false;
315
409
  try {
316
- const content = buildInitialAgentMd({ aid, name: agentName, description: agentDescription });
410
+ const content = buildInitialAgentMd({ aid, name: agentName, description: agentDescription, tags: agentTags });
317
411
  const aunPath = process.env.AUN_HOME || defaultAunPath();
318
412
  let lastError;
319
413
  for (let attempt = 1; attempt <= 3; attempt++) {
@@ -366,7 +460,21 @@ export async function agentCreateInteractive(opts = {}) {
366
460
  ownerBoundAid = await promptAgentOwnerManually(aid);
367
461
  }
368
462
  }
369
- return { ok: true, aid, configPath: toPosix(path.join(agentDirPath, 'config.json')), aidCreated, agentmdUploaded, hotLoaded, hotLoadError, ownerBoundAid, ownerBindSkipped, ownerBindInterrupted };
463
+ const finalOwnerAid = owner ?? ownerBoundAid;
464
+ const ownerContactSync = await syncOwnerContactsAfterCreate(aid, finalOwnerAid ? [finalOwnerAid] : undefined);
465
+ return {
466
+ ok: true,
467
+ aid,
468
+ configPath: toPosix(path.join(agentDirPath, 'config.json')),
469
+ aidCreated,
470
+ agentmdUploaded,
471
+ hotLoaded,
472
+ hotLoadError,
473
+ ownerBoundAid,
474
+ ownerBindSkipped,
475
+ ownerBindInterrupted,
476
+ ...ownerContactSync,
477
+ };
370
478
  }
371
479
  finally {
372
480
  if (ownRl) {
@@ -411,6 +519,22 @@ export async function agentCreateNonInteractive(opts, runtime = {}) {
411
519
  const ownerError = validateAgentOwnerOptions(opts);
412
520
  if (ownerError)
413
521
  return failValidating(ownerError);
522
+ if (opts.tags !== undefined) {
523
+ const tagErrors = validateAgentTags(opts.tags, { minCount: AGENT_TAG_MIN_COUNT, maxCount: AGENT_TAG_MAX_COUNT });
524
+ if (tagErrors.length > 0)
525
+ return failValidating(`Invalid tags: ${tagErrors.join('; ')}`);
526
+ }
527
+ if (opts.type !== undefined) {
528
+ const typeError = validateAgentType(opts.type);
529
+ if (typeError)
530
+ return failValidating(`Invalid --type: ${typeError}`);
531
+ }
532
+ if (opts.description !== undefined) {
533
+ const descriptionLength = Array.from(opts.description).length;
534
+ if (descriptionLength > AGENT_DESCRIPTION_MAX_LENGTH) {
535
+ return failValidating(`Invalid description: ${descriptionLength} characters exceeds the agent.md limit of ${AGENT_DESCRIPTION_MAX_LENGTH}. Pass a shorter --description.`);
536
+ }
537
+ }
414
538
  if (!isValidAid(opts.aid))
415
539
  return failValidating(`Invalid AID "${opts.aid}": must be a valid multi-level domain (e.g. mybot.agentid.pub)`);
416
540
  const agentDirPath = path.join(p.agentsDir, opts.aid);
@@ -480,11 +604,13 @@ export async function agentCreateNonInteractive(opts, runtime = {}) {
480
604
  ensureInitialRoleRegistry(opts.aid);
481
605
  saveInitialBehavior(opts.aid, baseagent);
482
606
  ensureAgentDirSkeleton(opts.aid);
607
+ const ownerContactSync = await syncOwnerContactsAfterCreate(opts.aid, opts.owners ?? (opts.owner ? [opts.owner] : undefined));
483
608
  opts.onPhase?.('config_saved', 'done');
484
609
  opts.onPhase?.('uploading_agentmd', 'begin');
485
610
  let agentmdUploaded = false;
611
+ let agentmdError;
486
612
  try {
487
- const content = buildInitialAgentMd({ aid: opts.aid, name: opts.name, description: opts.description });
613
+ const content = buildInitialAgentMd({ aid: opts.aid, name: opts.name, description: opts.description, type: opts.type, tags: opts.tags });
488
614
  const aunPath = process.env.AUN_HOME || defaultAunPath();
489
615
  let lastError;
490
616
  for (let attempt = 1; attempt <= 3; attempt++) {
@@ -500,12 +626,15 @@ export async function agentCreateNonInteractive(opts, runtime = {}) {
500
626
  lastError = e;
501
627
  }
502
628
  }
503
- if (!agentmdUploaded)
504
- opts.onPhase?.('uploading_agentmd', 'warn', `upload failed: ${lastError?.message || lastError}`);
629
+ if (!agentmdUploaded) {
630
+ agentmdError = `upload failed: ${lastError?.message || lastError}`;
631
+ opts.onPhase?.('uploading_agentmd', 'warn', agentmdError);
632
+ }
505
633
  await new Promise(r => setTimeout(r, 0));
506
634
  }
507
635
  catch (e) {
508
- opts.onPhase?.('uploading_agentmd', 'warn', `generation failed: ${e?.message || e}`);
636
+ agentmdError = `generation failed: ${e?.message || e}`;
637
+ opts.onPhase?.('uploading_agentmd', 'warn', agentmdError);
509
638
  }
510
639
  let hotLoaded = false;
511
640
  let hotLoadError;
@@ -534,5 +663,16 @@ export async function agentCreateNonInteractive(opts, runtime = {}) {
534
663
  hotLoadError = error instanceof Error ? error.message : String(error);
535
664
  opts.onPhase?.('hot_loading', 'warn', hotLoadError);
536
665
  }
537
- return { ok: true, aid: opts.aid, ownerAid: (opts.owners ?? (opts.owner ? [opts.owner] : undefined))?.[0], configPath: toPosix(path.join(agentDirPath, 'config.json')), aidCreated, agentmdUploaded, hotLoaded, hotLoadError };
666
+ return {
667
+ ok: true,
668
+ aid: opts.aid,
669
+ ownerAid: (opts.owners ?? (opts.owner ? [opts.owner] : undefined))?.[0],
670
+ configPath: toPosix(path.join(agentDirPath, 'config.json')),
671
+ aidCreated,
672
+ agentmdUploaded,
673
+ agentmdError,
674
+ hotLoaded,
675
+ hotLoadError,
676
+ ...ownerContactSync,
677
+ };
538
678
  }
package/dist/cli/agent.js CHANGED
@@ -1,10 +1,10 @@
1
1
  import fs from 'fs';
2
2
  import path from 'path';
3
- import { resolvePaths, agentMdPath as getAgentMdPathFromPaths, aunPath as defaultAunPath } from '../paths.js';
3
+ import { resolvePaths, agentMdPath as getAgentMdPathFromPaths, agentPersonalDir, aunPath as defaultAunPath } from '../paths.js';
4
4
  import { loadDefaults, loadAllAgents, loadAgent, saveAgent, ensureAgentDirSkeleton } from '../config-store.js';
5
5
  import { initConfigManager } from '../config/config-manager.js';
6
6
  import { resolveConfigCommand } from '../config/resolved-config-op.js';
7
- import { executeResolvedConfigCommand } from '../config/config-operation-service.js';
7
+ import { executeResolvedConfigCommandAsync } from '../config/config-operation-service.js';
8
8
  import { ipcQuery, IpcTransportError } from '../ipc.js';
9
9
  import { CONFIG_SCHEMA_VERSION } from '../types.js';
10
10
  import { ensureInitialRoleRegistry } from '../config/role-service.js';
@@ -12,6 +12,7 @@ import { agentProjectRootFromDefaults, deriveAgentProjectPath } from '../utils/p
12
12
  import { isValidChannelName } from '../core/channel-loader.js';
13
13
  import { AGENT_DELEGATION_TOKEN_ENV } from '../core/auth/agent-delegation.js';
14
14
  import { readTaskRuntimeContextFromEnv } from './task-context.js';
15
+ import { isHClassPath, isLClassPath, isSameOrDescendant } from '../core/permission/protected-paths.js';
15
16
  export { agentCreateInteractive, agentCreateNonInteractive, parseAgentOwnerArgs, resolveAgentCreatePlan, validateAgentCreatePlan, validateAgentOwnerOptions, } from './agent-create.js';
16
17
  /** Route agent lifecycle commands from a managed task through daemon IPC. */
17
18
  export async function runManagedAgentOperation(argv) {
@@ -182,6 +183,7 @@ export async function agentShow(aid) {
182
183
  ok: true,
183
184
  aid,
184
185
  status: agentInfo.status || 'unknown',
186
+ lifecycle: loadAgent(aid)?.lifecycle ?? null,
185
187
  identity,
186
188
  config: {
187
189
  baseagent: agentInfo.baseagent || null,
@@ -389,12 +391,22 @@ async function runManagedAgentConfig(args, options) {
389
391
  return { managed: true, error: { ok: false, code: 'DAEMON_UNAVAILABLE', error: 'Unable to connect to the evolcore daemon' } };
390
392
  }
391
393
  }
392
- function executeLocalAgentConfig(args) {
394
+ async function executeLocalAgentConfig(args) {
393
395
  initConfigManager();
394
396
  const resolved = resolveConfigCommand(['config', ...args]);
395
397
  if (!resolved.ok)
396
398
  return { ok: false, code: resolved.code, error: resolved.reason };
397
- return executeResolvedConfigCommand(resolved.command);
399
+ return executeResolvedConfigCommandAsync(resolved.command, {
400
+ role: 'owner',
401
+ roleContext: {
402
+ self: resolved.command.self || '',
403
+ actorRole: 'owner',
404
+ actorRoleSource: 'local-cli',
405
+ isDaemonOwner: false,
406
+ fromControlChannel: false,
407
+ aunAvailable: true,
408
+ },
409
+ });
398
410
  }
399
411
  export async function agentGet(aid, key, options = {}) {
400
412
  const managed = await runManagedAgentConfig(['get', key, '--self', aid], options);
@@ -411,7 +423,7 @@ export async function agentGet(aid, key, options = {}) {
411
423
  return { ok: false, error: `Agent "${aid}" not found` };
412
424
  }
413
425
  try {
414
- const result = executeLocalAgentConfig(['get', key, '--self', aid]);
426
+ const result = await executeLocalAgentConfig(['get', key, '--self', aid]);
415
427
  return result.ok && result.subcommand === 'get'
416
428
  ? { ok: true, aid, key, value: result.value }
417
429
  : { ok: false, code: result.ok ? 'UNEXPECTED_RESULT' : result.code, error: result.ok ? 'config get returned an unexpected result' : result.error };
@@ -437,7 +449,7 @@ export async function agentSet(aid, key, rawValue, options = {}) {
437
449
  }
438
450
  let value;
439
451
  try {
440
- const result = executeLocalAgentConfig(['set', key, rawValue, '--self', aid]);
452
+ const result = await executeLocalAgentConfig(['set', key, rawValue, '--self', aid]);
441
453
  if (!result.ok)
442
454
  return { ok: false, code: result.code, error: result.error };
443
455
  if (result.subcommand !== 'set')
@@ -456,6 +468,107 @@ export async function agentSet(aid, key, rawValue, options = {}) {
456
468
  catch { }
457
469
  return { ok: true, aid, key, value, reloaded };
458
470
  }
471
+ export const AGENT_PERSONA_MAX_BYTES = 64 * 1024;
472
+ function validatePersonaContent(content) {
473
+ if (content.includes('\0'))
474
+ return 'persona 只能包含文本内容';
475
+ if (Buffer.byteLength(content, 'utf8') > AGENT_PERSONA_MAX_BYTES) {
476
+ return `persona 不能超过 ${AGENT_PERSONA_MAX_BYTES} bytes`;
477
+ }
478
+ return null;
479
+ }
480
+ function readPersonaSource(file) {
481
+ const resolved = path.resolve(file);
482
+ if (!/\.(?:md|txt)$/i.test(resolved)) {
483
+ return { ok: false, code: 'INVALID_ARGS', error: '--file 只接受 .md 或 .txt 文本文件' };
484
+ }
485
+ try {
486
+ const stat = fs.statSync(resolved);
487
+ if (!stat.isFile())
488
+ return { ok: false, code: 'INVALID_ARGS', error: `--file 必须指向普通文件: ${resolved}` };
489
+ const content = fs.readFileSync(resolved, 'utf8');
490
+ const error = validatePersonaContent(content);
491
+ return error ? { ok: false, code: 'INVALID_ARGS', error } : { ok: true, content };
492
+ }
493
+ catch (e) {
494
+ return { ok: false, code: 'READ_FAILED', error: `无法读取 --file: ${e?.message || e}` };
495
+ }
496
+ }
497
+ /** Read or replace the current Agent's personal/persona.md. */
498
+ export async function agentPersona(aid, options = {}) {
499
+ if (options.text !== undefined && options.file !== undefined)
500
+ return { ok: false, code: 'INVALID_ARGS', error: '--text 与 --file 不能同时使用' };
501
+ const managedSession = !!process.env.EVOLCORE_SESSION_ID && !options.trustedLocal;
502
+ let text = options.text;
503
+ if (options.file !== undefined) {
504
+ if (managedSession) {
505
+ const resolved = path.resolve(options.file);
506
+ const projectRoot = path.resolve(process.cwd());
507
+ const tempRoot = process.env.TMPDIR?.trim() && path.isAbsolute(process.env.TMPDIR)
508
+ ? path.resolve(process.env.TMPDIR)
509
+ : undefined;
510
+ const allowed = isSameOrDescendant(resolved, projectRoot)
511
+ || (!!tempRoot && isSameOrDescendant(resolved, tempRoot));
512
+ if (!allowed || isHClassPath(resolved) || isLClassPath(resolved)) {
513
+ return { ok: false, code: 'INVALID_ARGS', error: '--file 必须位于当前项目或会话 $TMPDIR,且不能读取 EvolCore 受保护路径' };
514
+ }
515
+ try {
516
+ const stat = fs.lstatSync(resolved);
517
+ if (stat.isSymbolicLink() || !stat.isFile()) {
518
+ return { ok: false, code: 'INVALID_ARGS', error: '--file 必须指向当前项目或会话 $TMPDIR 内的普通文件' };
519
+ }
520
+ const realPath = fs.realpathSync(resolved);
521
+ const realProjectRoot = fs.realpathSync(projectRoot);
522
+ const realTempRoot = tempRoot ? fs.realpathSync(tempRoot) : undefined;
523
+ const realAllowed = isSameOrDescendant(realPath, realProjectRoot)
524
+ || (!!realTempRoot && isSameOrDescendant(realPath, realTempRoot));
525
+ if (!realAllowed || isHClassPath(realPath) || isLClassPath(realPath)) {
526
+ return { ok: false, code: 'INVALID_ARGS', error: '--file 解析后越出允许目录或命中 EvolCore 受保护路径' };
527
+ }
528
+ }
529
+ catch (e) {
530
+ return { ok: false, code: 'READ_FAILED', error: `无法校验 --file: ${e?.message || e}` };
531
+ }
532
+ }
533
+ const source = readPersonaSource(options.file);
534
+ if (!source.ok)
535
+ return source;
536
+ text = source.content;
537
+ }
538
+ if (text !== undefined) {
539
+ const error = validatePersonaContent(text);
540
+ if (error)
541
+ return { ok: false, code: 'INVALID_ARGS', error };
542
+ }
543
+ const managedArgs = options.text !== undefined
544
+ ? ['persona', '--text', text]
545
+ : options.file !== undefined
546
+ ? ['persona', '--text', text]
547
+ : ['persona', 'show'];
548
+ if (managedSession) {
549
+ const managed = await runManagedAgentOperation(managedArgs);
550
+ if (!managed.ok)
551
+ return managed;
552
+ return { ok: true, aid, content: managed.content ?? null, ...(managed.path ? { path: managed.path } : {}), ...(managed.saved ? { saved: true } : {}) };
553
+ }
554
+ const personaPath = path.join(agentPersonalDir(aid), 'persona.md');
555
+ if (text === undefined) {
556
+ try {
557
+ return { ok: true, aid, content: fs.existsSync(personaPath) ? fs.readFileSync(personaPath, 'utf8') : null, path: personaPath };
558
+ }
559
+ catch (e) {
560
+ return { ok: false, code: 'READ_FAILED', error: e?.message || String(e) };
561
+ }
562
+ }
563
+ try {
564
+ fs.mkdirSync(path.dirname(personaPath), { recursive: true });
565
+ fs.writeFileSync(personaPath, text, 'utf8');
566
+ return { ok: true, aid, content: text, path: personaPath, saved: true };
567
+ }
568
+ catch (e) {
569
+ return { ok: false, code: 'WRITE_FAILED', error: e?.message || String(e) };
570
+ }
571
+ }
459
572
  /**
460
573
  * Add or overwrite a channel instance on a per-agent config.
461
574
  *