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
package/dist/cli/agent.js CHANGED
@@ -1,32 +1,18 @@
1
1
  import fs from 'fs';
2
2
  import path from 'path';
3
- import readline from 'readline';
4
3
  import { resolvePaths, agentMdPath as getAgentMdPathFromPaths, aunPath as defaultAunPath } from '../paths.js';
5
- import { loadDefaults, loadAllAgents, loadAgent, saveAgent, ensureAgentDirSkeleton, loadDaemonConfig } from '../config-store.js';
6
- import { ConfigTarget, write as cfgWrite, ensureFile as cfgEnsure, read as cfgRead, initConfigManager } from '../config/config-manager.js';
4
+ import { loadDefaults, loadAllAgents, loadAgent, saveAgent, ensureAgentDirSkeleton } from '../config-store.js';
5
+ import { initConfigManager } from '../config/config-manager.js';
7
6
  import { resolveConfigCommand } from '../config/resolved-config-op.js';
8
7
  import { executeResolvedConfigCommand } from '../config/config-operation-service.js';
9
- import { ipcQuery } from '../ipc.js';
8
+ import { ipcQuery, IpcTransportError } from '../ipc.js';
10
9
  import { CONFIG_SCHEMA_VERSION } from '../types.js';
11
10
  import { ensureInitialRoleRegistry } from '../config/role-service.js';
12
- import { isValidChannelName } from '../core/channel-loader.js';
13
- import { commandExists } from '../utils/cross-platform.js';
14
- import { getCodexAppServerAvailability, isCodexAppServerAvailable } from '../agents/codex-runner.js';
15
- import { resolveEcagentConfig } from '../agents/baseagent.js';
16
11
  import { agentProjectRootFromDefaults, deriveAgentProjectPath } from '../utils/project-path.js';
12
+ import { isValidChannelName } from '../core/channel-loader.js';
17
13
  import { AGENT_DELEGATION_TOKEN_ENV } from '../core/auth/agent-delegation.js';
18
14
  import { readTaskRuntimeContextFromEnv } from './task-context.js';
19
- import { isValidAid } from '../aun/aid/validation.js';
20
- import { DEFAULT_AGENT_DESCRIPTION } from '../aun/aid/agentmd.js';
21
- function withStaticOwner(config, owner) {
22
- const value = owner?.trim();
23
- if (!value)
24
- return config;
25
- const owners = config.owners ?? [];
26
- if (owners.includes(value))
27
- return config;
28
- return { ...config, owners: [...owners, value] };
29
- }
15
+ export { agentCreateInteractive, agentCreateNonInteractive, parseAgentOwnerArgs, resolveAgentCreatePlan, validateAgentCreatePlan, validateAgentOwnerOptions, } from './agent-create.js';
30
16
  /** Route agent lifecycle commands from a managed task through daemon IPC. */
31
17
  export async function runManagedAgentOperation(argv) {
32
18
  const sessionId = process.env.EVOLCORE_SESSION_ID;
@@ -53,174 +39,32 @@ export async function runManagedAgentOperation(argv) {
53
39
  return { ok: false, code: 'DAEMON_UNAVAILABLE', error: error instanceof Error ? error.message : String(error) };
54
40
  }
55
41
  }
56
- // ==================== Helpers ====================
57
- const BASEAGENT_CANDIDATES = ['claude', 'codex', 'gemini', 'ecagent'];
58
- const AUTO_BASEAGENT_CANDIDATES = ['claude', 'codex', 'gemini'];
59
- function isBaseagentAvailable(baseagent) {
60
- if (baseagent === 'codex')
61
- return isCodexAppServerAvailable();
62
- if (baseagent === 'ecagent')
63
- return true;
64
- return commandExists(baseagent);
65
- }
66
- function detectAvailableBaseagents() {
67
- return BASEAGENT_CANDIDATES.filter(isBaseagentAvailable);
68
- }
69
- function pickDefaultBaseagent(available) {
70
- return AUTO_BASEAGENT_CANDIDATES.find(candidate => available.includes(candidate)) ?? null;
71
- }
72
- function readProjectMetadata(project) {
73
- try {
74
- const packageJsonPath = path.join(project, 'package.json');
75
- if (!fs.existsSync(packageJsonPath))
76
- return {};
77
- const parsed = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
78
- return {
79
- name: typeof parsed.name === 'string' ? parsed.name.trim() : undefined,
80
- description: typeof parsed.description === 'string' ? parsed.description.trim() : undefined,
81
- };
82
- }
83
- catch {
84
- return {};
85
- }
86
- }
87
- function projectSlug(project) {
88
- const basename = path.basename(project) || 'agent';
89
- const slug = basename
90
- .normalize('NFKD')
91
- .replace(/[^a-zA-Z0-9-]+/g, '-')
92
- .replace(/^-+|-+$/g, '')
93
- .toLowerCase()
94
- .slice(0, 63)
95
- .replace(/-+$/g, '');
96
- return slug || 'agent';
97
- }
98
- /**
99
- * Resolve non-interactive creation defaults without performing any network or
100
- * filesystem mutation. The CLI uses this for both execution and --dry-run.
101
- */
102
- export function resolveAgentCreatePlan(options) {
103
- const aid = options.aid.trim();
104
- const project = path.resolve(options.project || process.cwd());
105
- const metadata = readProjectMetadata(project);
106
- const defaults = loadDefaults();
107
- const daemon = loadDaemonConfig();
108
- const configuredBaseagent = defaults?.active_baseagent;
109
- const available = detectAvailableBaseagents();
110
- const defaultBaseagent = configuredBaseagent && available.includes(configuredBaseagent)
111
- ? configuredBaseagent
112
- : pickDefaultBaseagent(available) ?? undefined;
113
- const baseagent = options.baseagent || defaultBaseagent;
114
- const baseagentSource = options.baseagent
115
- ? 'argument'
116
- : configuredBaseagent && baseagent === configuredBaseagent
117
- ? 'defaults'
118
- : 'detected';
119
- const owner = options.owner?.trim() || daemon.owners?.[0];
120
- const ownerSource = options.owner?.trim() ? 'argument' : owner ? 'daemon' : 'none';
121
- const name = options.name?.trim() || metadata.name || projectSlug(project);
122
- const description = options.description?.trim() || metadata.description || DEFAULT_AGENT_DESCRIPTION;
123
- const configExists = isValidAid(aid)
124
- && fs.existsSync(path.join(resolvePaths().agentsDir, aid, 'config.json'));
125
- return {
126
- aid,
127
- project,
128
- baseagent,
129
- owner,
130
- name,
131
- description,
132
- force: options.force === true,
133
- configExists,
134
- baseagentSource,
135
- ownerSource,
136
- nameSource: options.name?.trim() ? 'argument' : metadata.name ? 'package' : 'directory',
137
- descriptionSource: options.description?.trim() ? 'argument' : metadata.description ? 'package' : 'default',
138
- };
139
- }
140
- export function validateAgentCreatePlan(plan) {
141
- if (!isValidAid(plan.aid)) {
142
- return `Invalid AID "${plan.aid}": must be a valid multi-level domain (e.g. mybot.agentid.pub)`;
143
- }
144
- if (plan.configExists && !plan.force) {
145
- return `Agent "${plan.aid}" already exists (use --force to overwrite)`;
146
- }
147
- if (!plan.baseagent) {
148
- return 'No usable baseagent detected. Pass --baseagent or configure a baseagent in agents/defaults.json.';
149
- }
150
- if (!BASEAGENT_CANDIDATES.includes(plan.baseagent)) {
151
- return `Invalid baseagent: ${plan.baseagent} (options: ${BASEAGENT_CANDIDATES.join('/')})`;
152
- }
153
- if (!isBaseagentAvailable(plan.baseagent)) {
154
- return `${plan.baseagent} is not available in the current environment`;
155
- }
156
- if (plan.baseagent === 'ecagent') {
157
- const reason = ecagentUnavailableReason();
158
- if (reason)
159
- return reason;
160
- }
161
- if (plan.owner && !isValidAid(plan.owner)) {
162
- return `Invalid owner: ${plan.owner}`;
163
- }
164
- return null;
165
- }
166
- function ecagentUnavailableReason() {
167
- try {
168
- resolveEcagentConfig({ agents: { ecagent: {} } });
169
- return undefined;
170
- }
171
- catch (error) {
172
- return error instanceof Error ? error.message : String(error);
173
- }
174
- }
175
- function buildBaseagentsBlock(chosen) {
176
- return { [chosen]: {} };
42
+ function getAgentMdPath(aid) {
43
+ return getAgentMdPathFromPaths(aid);
177
44
  }
178
- const DEFAULT_CHATMODE = { private: 'interactive', group: 'proactive', nothuman: 'proactive' };
179
- /**
180
- * 写入新 agent 的初始配置(所有字段统一在 config.json)。
181
- * mentionMode 不写初始值,继承 schema 出厂默认(disabled)。
182
- */
183
- function saveInitialBehavior(aid, baseagent) {
184
- const behavior = {
185
- active_baseagent: baseagent,
186
- baseagents: buildBaseagentsBlock(baseagent),
187
- chatmode: { ...DEFAULT_CHATMODE },
188
- };
189
- const sel = { self: aid };
190
- const existingBehavior = cfgRead(ConfigTarget.Agent, sel) || {};
191
- cfgEnsure(ConfigTarget.Agent, sel);
192
- cfgWrite(ConfigTarget.Agent, { ...existingBehavior, ...behavior }, sel);
45
+ function toPosix(value) {
46
+ return value ? value.replace(/\\/g, '/') : null;
193
47
  }
194
48
  function readAgentMdIdentity(aid) {
195
- const agentMdFilePath = getAgentMdPathFromPaths(aid);
49
+ const file = getAgentMdPath(aid);
196
50
  try {
197
- if (!fs.existsSync(agentMdFilePath))
51
+ if (!fs.existsSync(file))
198
52
  return { name: null, description: null };
199
- const content = fs.readFileSync(agentMdFilePath, 'utf-8');
200
- const fmMatch = content.match(/^---\n([\s\S]*?)\n---/);
201
- if (!fmMatch)
53
+ const content = fs.readFileSync(file, 'utf8');
54
+ const frontmatter = content.match(/^---\n([\s\S]*?)\n---/);
55
+ if (!frontmatter)
202
56
  return { name: null, description: null };
203
- const fm = fmMatch[1];
204
- const nameMatch = fm.match(/^name:\s*["']?(.+?)["']?\s*$/m);
205
- const descMatch = fm.match(/^description:\s*["']?(.+?)["']?\s*$/m);
57
+ const body = frontmatter[1];
206
58
  return {
207
- name: nameMatch?.[1]?.trim() || null,
208
- description: descMatch?.[1]?.trim() || null,
59
+ name: body.match(/^name:\s*["']?(.+?)["']?\s*$/m)?.[1]?.trim() || null,
60
+ description: body.match(/^description:\s*["']?(.+?)["']?\s*$/m)?.[1]?.trim() || null,
209
61
  };
210
62
  }
211
63
  catch {
212
64
  return { name: null, description: null };
213
65
  }
214
66
  }
215
- function getAgentMdPath(aid) {
216
- return getAgentMdPathFromPaths(aid);
217
- }
218
- /** Normalize path separators to forward slashes for cross-platform consistent display. */
219
- function toPosix(p) {
220
- if (!p)
221
- return null;
222
- return p.replace(/\\/g, '/');
223
- }
67
+ // ==================== Helpers ====================
224
68
  // ==================== agentList ====================
225
69
  function toAgentListItem(info, stale = false) {
226
70
  const storedStatus = info.status || 'unknown';
@@ -360,499 +204,6 @@ export async function agentShow(aid) {
360
204
  ...(stale ? { stale: true } : {}),
361
205
  };
362
206
  }
363
- export async function agentCreateInteractive(opts = {}) {
364
- const p = resolvePaths();
365
- const ownRl = !opts.rl;
366
- const rl = opts.rl ?? readline.createInterface({
367
- input: opts.stdin || process.stdin,
368
- output: opts.stdout || process.stdout,
369
- });
370
- const ask = (q) => new Promise(r => rl.question(q, r));
371
- try {
372
- const { isValidAid, aidCreate } = await import('../aun/aid/index.js');
373
- const aidPrompt = opts.suggestedName
374
- ? `AID [${opts.suggestedName}]: `
375
- : 'AID (e.g. mybot.agentid.pub): ';
376
- let aid = '';
377
- while (!aid) {
378
- const aidInput = (await ask(aidPrompt)).trim();
379
- const candidate = aidInput || opts.suggestedName;
380
- if (!candidate) {
381
- console.log(' ⚠ AID is required.');
382
- continue;
383
- }
384
- if (!isValidAid(candidate)) {
385
- console.log(` ⚠ Invalid AID "${candidate}": must be a valid multi-level domain (e.g. mybot.agentid.pub)`);
386
- continue;
387
- }
388
- aid = candidate;
389
- }
390
- const agentDirPath = path.join(p.agentsDir, aid);
391
- const configExists = fs.existsSync(path.join(agentDirPath, 'config.json'));
392
- if (configExists) {
393
- const ans = (await ask(`Agent "${aid}" already exists. Overwrite config.json? (AID 与 agent.md 保留) [y/N]: `)).trim().toLowerCase();
394
- if (ans !== 'y' && ans !== 'yes') {
395
- return { ok: false, error: 'Aborted.' };
396
- }
397
- }
398
- console.log(`\nCreating agent: ${aid}\n`);
399
- let aidCreated = false;
400
- try {
401
- const result = await aidCreate(aid);
402
- try {
403
- await result.client.close();
404
- }
405
- catch { /* ignore */ }
406
- aidCreated = !result.alreadyExisted;
407
- console.log(` ✓ AID ${result.alreadyExisted ? 'reused' : 'created'}: ${aid}`);
408
- }
409
- catch (e) {
410
- return { ok: false, error: `AID creation failed: ${e?.message || e}` };
411
- }
412
- // Project path
413
- let suggestedProjectPath = '';
414
- try {
415
- const defaults = loadDefaults();
416
- const rootPath = agentProjectRootFromDefaults(defaults, p.root);
417
- suggestedProjectPath = deriveAgentProjectPath(rootPath, aid);
418
- }
419
- catch {
420
- suggestedProjectPath = deriveAgentProjectPath(agentProjectRootFromDefaults(null, p.root), aid);
421
- }
422
- const projectInput = (await ask(`Project path [${suggestedProjectPath}]: `)).trim();
423
- const projectPath = projectInput || suggestedProjectPath;
424
- if (!path.isAbsolute(projectPath)) {
425
- return { ok: false, error: 'Project path must be an absolute path.' };
426
- }
427
- if (!fs.existsSync(projectPath)) {
428
- const create = (await ask(`Project path does not exist. Create? [Y/n]: `)).trim().toLowerCase();
429
- if (create === '' || create === 'y' || create === 'yes') {
430
- try {
431
- fs.mkdirSync(projectPath, { recursive: true });
432
- }
433
- catch (e) {
434
- return { ok: false, error: `Failed to create project path "${projectPath}": ${e?.message || e}` };
435
- }
436
- console.log(` ✓ Created ${projectPath}`);
437
- }
438
- else {
439
- return { ok: false, error: 'Aborted.' };
440
- }
441
- }
442
- // Baseagent
443
- const available = detectAvailableBaseagents();
444
- if (available.length === 0 || (available.length === 1 && available[0] === 'ecagent' && ecagentUnavailableReason())) {
445
- return { ok: false, error: `No usable baseagent detected. Install claude/gemini CLI or codex CLI with app-server, or configure bundled ecagent.` };
446
- }
447
- const defaultBa = pickDefaultBaseagent(available);
448
- let baseagent;
449
- if (available.length === 1 && defaultBa) {
450
- console.log(` Baseagent: ${defaultBa}`);
451
- baseagent = defaultBa;
452
- }
453
- else {
454
- let chosen = null;
455
- while (chosen === null) {
456
- const defaultHint = defaultBa ? ` [${defaultBa}]` : '';
457
- const input = (await ask(`Baseagent (${available.join('/')})${defaultHint}: `)).trim() || defaultBa;
458
- if (!input) {
459
- console.log(' ecagent is optional; enter ecagent to select it explicitly.');
460
- continue;
461
- }
462
- if (!BASEAGENT_CANDIDATES.includes(input)) {
463
- console.log(` Invalid choice. Options: ${BASEAGENT_CANDIDATES.join('/')}`);
464
- continue;
465
- }
466
- if (!available.includes(input)) {
467
- console.log(` ${input} is not available in the current environment. Available: ${available.join('/')}`);
468
- continue;
469
- }
470
- if (input === 'ecagent') {
471
- const reason = ecagentUnavailableReason();
472
- if (reason) {
473
- console.log(` ecagent is unavailable: ${reason}`);
474
- continue;
475
- }
476
- }
477
- chosen = input;
478
- }
479
- baseagent = chosen;
480
- }
481
- // Owner: only prompt if daemon (daemon.json) has owners configured.
482
- // If daemon has no owner, agent owner is meaningless and should be skipped entirely.
483
- const { loadDaemonConfig } = await import('../config-store.js');
484
- const daemonCfg = loadDaemonConfig();
485
- const daemonHasOwner = daemonCfg.owners && daemonCfg.owners.length > 0;
486
- let owner;
487
- let skipQrBinding = false;
488
- if (daemonHasOwner) {
489
- const defaultOwner = daemonCfg.owners[0];
490
- while (true) {
491
- const prompt = `Owner AID [${defaultOwner}]: `;
492
- const ownerInput = (await ask(prompt)).trim();
493
- // User pressed Enter - use default owner
494
- if (!ownerInput) {
495
- owner = defaultOwner;
496
- break;
497
- }
498
- // User entered "new" or "qr" - trigger QR binding flow
499
- if (ownerInput.toLowerCase() === 'new' || ownerInput.toLowerCase() === 'qr') {
500
- owner = undefined;
501
- skipQrBinding = false;
502
- break;
503
- }
504
- // User entered a specific AID - validate and use it
505
- if (!isValidAid(ownerInput)) {
506
- console.log(` ⚠ Invalid Owner AID "${ownerInput}": must be a valid multi-level domain (e.g. alice.agentid.pub)`);
507
- console.log(` 提示: 直接回车使用 ${defaultOwner},输入 "new" 扫码绑定新 AID`);
508
- continue;
509
- }
510
- owner = ownerInput;
511
- skipQrBinding = true; // Skip QR if user manually entered an AID
512
- break;
513
- }
514
- }
515
- // Name + description for agent.md
516
- const defaultName = aid.split('.')[0];
517
- const agentName = (await ask(`Display name [${defaultName}]: `)).trim() || defaultName;
518
- const agentDescription = (await ask('Description (optional): ')).trim() || '';
519
- if (ownRl)
520
- rl.close();
521
- const agentConfig = withStaticOwner({
522
- $schema_version: CONFIG_SCHEMA_VERSION,
523
- aid,
524
- enabled: true,
525
- lifecycle: 'created',
526
- channels: [],
527
- projects: { defaultPath: projectPath },
528
- }, owner);
529
- saveAgent(agentConfig);
530
- ensureInitialRoleRegistry(aid);
531
- saveInitialBehavior(aid, baseagent);
532
- ensureAgentDirSkeleton(aid);
533
- // Generate and upload agent.md
534
- let agentmdUploaded = false;
535
- try {
536
- const { buildInitialAgentMd, agentmdPut } = await import('../aun/aid/index.js');
537
- const content = buildInitialAgentMd({ aid, name: agentName, description: agentDescription });
538
- const aunPath = process.env.AUN_HOME || defaultAunPath();
539
- // agentmdPut 会写本地文件到 agentMdPath(aid) 并调用 publishAgentMd
540
- // Upload with retry (3 attempts, 2s delay between retries)
541
- const MAX_ATTEMPTS = 3;
542
- const RETRY_DELAY_MS = 2000;
543
- let lastError;
544
- for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {
545
- try {
546
- if (attempt > 1) {
547
- process.stdout.write(` ↻ agent.md 上传重试 (${attempt}/${MAX_ATTEMPTS})...\n`);
548
- await new Promise(r => setTimeout(r, RETRY_DELAY_MS));
549
- }
550
- await agentmdPut(content, { aid, aunPath });
551
- agentmdUploaded = true;
552
- break;
553
- }
554
- catch (e) {
555
- lastError = e;
556
- }
557
- }
558
- if (!agentmdUploaded) {
559
- console.warn(` ⚠ agent.md upload failed: ${lastError?.message || lastError}`);
560
- console.warn(` → Retry later with: ec aid agentmd put ${aid}`);
561
- }
562
- // Yield to allow the SDK WebSocket to fully close before IPC
563
- await new Promise(r => setTimeout(r, 0));
564
- }
565
- catch (e) {
566
- console.warn(` ⚠ agent.md generation failed: ${e?.message || e}`);
567
- }
568
- // Attempt hot-load via IPC (if daemon is running).
569
- // Cold-starting a new agent (connecting AUN WebSocket) routinely takes
570
- // >3s, so use a generous timeout to avoid a false "service not running"
571
- // report while the daemon actually finishes bringing the agent online.
572
- let hotLoaded = false;
573
- let hotLoadError;
574
- try {
575
- const ipcResult = await ipcQuery(p.socket, { type: 'evolagent.load', aid }, 30_000);
576
- if (ipcResult?.ok) {
577
- hotLoaded = true;
578
- }
579
- else if (ipcResult) {
580
- hotLoadError = ipcResult.error;
581
- }
582
- }
583
- catch { /* daemon not running */ }
584
- let ownerBoundAid;
585
- let ownerBindSkipped = false;
586
- let ownerBindInterrupted = false;
587
- // Only prompt for agent owner binding if:
588
- // 1. User didn't manually input owner during setup (owner is undefined)
589
- // 2. User didn't skip QR binding explicitly (skipQrBinding is false)
590
- // 3. We're in an interactive terminal
591
- // 4. The daemon (daemon.json) has at least one owner configured
592
- if (!owner && !skipQrBinding && process.stdin.isTTY && daemonHasOwner) {
593
- console.log('\n📡 Agent owner 绑定 — 请用 evol app 扫描二维码,10 分钟内有效\n');
594
- try {
595
- const { runAgentOwnerQrBindFlow } = await import('./init-channel.js');
596
- const bound = await runAgentOwnerQrBindFlow(aid, agentName);
597
- if (bound.action === 'bound') {
598
- ownerBoundAid = bound.boundAid;
599
- console.log(` ✓ 已配置 agent owner: ${bound.boundAid}`);
600
- }
601
- else if (bound.action === 'manual') {
602
- ownerBindSkipped = true;
603
- ownerBoundAid = await promptAgentOwnerManually(aid);
604
- }
605
- else {
606
- ownerBindInterrupted = true;
607
- }
608
- }
609
- catch (e) {
610
- ownerBindSkipped = true;
611
- console.log(` ⚠ 扫码绑定不可用: ${e?.message || e}`);
612
- ownerBoundAid = await promptAgentOwnerManually(aid);
613
- }
614
- }
615
- return {
616
- ok: true,
617
- aid,
618
- configPath: toPosix(path.join(agentDirPath, 'config.json')),
619
- aidCreated,
620
- agentmdUploaded,
621
- hotLoaded,
622
- hotLoadError,
623
- ownerBoundAid,
624
- ownerBindSkipped,
625
- ownerBindInterrupted,
626
- };
627
- }
628
- finally {
629
- if (ownRl) {
630
- try {
631
- rl.close();
632
- }
633
- catch { /* ignore */ }
634
- }
635
- }
636
- }
637
- async function promptAgentOwnerManually(aid) {
638
- const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
639
- try {
640
- while (true) {
641
- const raw = await new Promise(resolve => rl.question('\n扫码未完成。手动输入 agent owner AID(留空跳过): ', resolve));
642
- const owner = raw.trim();
643
- if (!owner) {
644
- console.log(' 已跳过 agent owner 配置');
645
- return undefined;
646
- }
647
- const { isValidAid } = await import('../aun/aid/index.js');
648
- if (!isValidAid(owner)) {
649
- console.log(' ⚠ Owner AID 格式无效(需合法多级域名,如 alice.agentid.pub)');
650
- continue;
651
- }
652
- const agent = loadAgent(aid);
653
- if (!agent) {
654
- console.log(` ⚠ 无法加载 agent 配置: ${aid}`);
655
- return undefined;
656
- }
657
- saveAgent(withStaticOwner(agent, owner));
658
- try {
659
- const result = await ipcQuery(resolvePaths().socket, { type: 'evolagent.reload', name: aid }, 30_000);
660
- if (result?.ok)
661
- console.log(' ✓ agent owner 已热重载');
662
- }
663
- catch { /* daemon not running */ }
664
- console.log(` ✓ 已配置 agent owner: ${owner}`);
665
- return owner;
666
- }
667
- }
668
- finally {
669
- rl.close();
670
- }
671
- }
672
- export async function agentCreateNonInteractive(opts, runtime = {}) {
673
- const p = resolvePaths();
674
- const { isValidAid, aidCreate } = await import('../aun/aid/index.js');
675
- opts.onPhase?.('validating', 'begin');
676
- /** 校验失败:透出 failed 进度并返回原结构(控制流不变)。 */
677
- const failValidating = (error) => {
678
- opts.onPhase?.('validating', 'failed', error);
679
- return { ok: false, error };
680
- };
681
- if (!isValidAid(opts.aid)) {
682
- return failValidating(`Invalid AID "${opts.aid}": must be a valid multi-level domain (e.g. mybot.agentid.pub)`);
683
- }
684
- const agentDirPath = path.join(p.agentsDir, opts.aid);
685
- const configExists = fs.existsSync(path.join(agentDirPath, 'config.json'));
686
- if (configExists && !opts.force) {
687
- return failValidating(`Agent "${opts.aid}" already exists: ${agentDirPath}/config.json (use --force to overwrite)`);
688
- }
689
- // Baseagent
690
- const available = detectAvailableBaseagents();
691
- if (available.length === 0 || (available.length === 1 && available[0] === 'ecagent' && ecagentUnavailableReason())) {
692
- return failValidating(`No usable baseagent detected. Install claude/gemini CLI or codex CLI with app-server, or configure bundled ecagent.`);
693
- }
694
- let baseagent;
695
- if (opts.baseagent) {
696
- if (!BASEAGENT_CANDIDATES.includes(opts.baseagent)) {
697
- return failValidating(`Invalid baseagent: ${opts.baseagent} (options: ${BASEAGENT_CANDIDATES.join('/')})`);
698
- }
699
- if (!available.includes(opts.baseagent)) {
700
- const reason = opts.baseagent === 'codex'
701
- ? getCodexAppServerAvailability().reason
702
- : undefined;
703
- return failValidating(reason || `${opts.baseagent} is not available in the current environment (available: ${available.join('/')})`);
704
- }
705
- if (opts.baseagent === 'ecagent') {
706
- const reason = ecagentUnavailableReason();
707
- if (reason)
708
- return failValidating(reason);
709
- }
710
- baseagent = opts.baseagent;
711
- }
712
- else {
713
- const defaultBaseagent = pickDefaultBaseagent(available);
714
- if (!defaultBaseagent)
715
- return failValidating('ecagent is optional; pass --baseagent ecagent after configuring its API key');
716
- baseagent = defaultBaseagent;
717
- }
718
- if (!path.isAbsolute(opts.project)) {
719
- return failValidating(`--project must be absolute: ${opts.project}`);
720
- }
721
- if (!fs.existsSync(opts.project)) {
722
- try {
723
- fs.mkdirSync(opts.project, { recursive: true });
724
- }
725
- catch (e) {
726
- return failValidating(`Failed to create ${opts.project}: ${e?.message || e}`);
727
- }
728
- }
729
- if (opts.owner && !isValidAid(opts.owner)) {
730
- return failValidating(`Invalid owner: ${opts.owner}`);
731
- }
732
- opts.onPhase?.('validating', 'done');
733
- // Register AID
734
- opts.onPhase?.('registering_aid', 'begin');
735
- let aidCreated = false;
736
- try {
737
- const result = await aidCreate(opts.aid);
738
- try {
739
- await result.client.close();
740
- }
741
- catch { /* ignore */ }
742
- aidCreated = !result.alreadyExisted;
743
- opts.onPhase?.('registering_aid', 'done', aidCreated ? 'created' : 'existed');
744
- }
745
- catch (e) {
746
- const error = `AID creation failed: ${e?.message || e}`;
747
- opts.onPhase?.('registering_aid', 'failed', error);
748
- return { ok: false, error };
749
- }
750
- // Force 模式下若 agent 已存在,保留生命周期(避免重复 bootstrap/welcome)。
751
- let preservedLifecycle = 'created';
752
- if (configExists) {
753
- try {
754
- const existing = loadAgent(opts.aid);
755
- if (existing?.lifecycle)
756
- preservedLifecycle = existing.lifecycle;
757
- }
758
- catch { /* ignore */ }
759
- }
760
- const agentConfig = withStaticOwner({
761
- $schema_version: CONFIG_SCHEMA_VERSION,
762
- aid: opts.aid,
763
- enabled: true,
764
- lifecycle: preservedLifecycle,
765
- channels: [],
766
- projects: { defaultPath: opts.project },
767
- }, opts.owner);
768
- opts.onPhase?.('config_saved', 'begin');
769
- saveAgent(agentConfig);
770
- ensureInitialRoleRegistry(opts.aid);
771
- saveInitialBehavior(opts.aid, baseagent);
772
- ensureAgentDirSkeleton(opts.aid);
773
- opts.onPhase?.('config_saved', 'done');
774
- // Generate and upload agent.md
775
- opts.onPhase?.('uploading_agentmd', 'begin');
776
- let agentmdUploaded = false;
777
- try {
778
- const { buildInitialAgentMd, agentmdPut } = await import('../aun/aid/index.js');
779
- const content = buildInitialAgentMd({
780
- aid: opts.aid,
781
- name: opts.name,
782
- description: opts.description,
783
- });
784
- const aunPath = process.env.AUN_HOME || defaultAunPath();
785
- // agentmdPut 会写本地文件到 agentMdPath(aid) 并调用 publishAgentMd
786
- const MAX_ATTEMPTS = 3;
787
- const RETRY_DELAY_MS = 2000;
788
- let lastError;
789
- for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {
790
- try {
791
- if (attempt > 1)
792
- await new Promise(r => setTimeout(r, RETRY_DELAY_MS));
793
- await agentmdPut(content, { aid: opts.aid, aunPath });
794
- agentmdUploaded = true;
795
- opts.onPhase?.('uploading_agentmd', 'done');
796
- break;
797
- }
798
- catch (e) {
799
- lastError = e;
800
- }
801
- }
802
- if (!agentmdUploaded) {
803
- console.warn(`⚠ agent.md upload failed: ${lastError?.message || lastError}`);
804
- console.warn(` Retry later with: ec aid agentmd put ${opts.aid}`);
805
- opts.onPhase?.('uploading_agentmd', 'warn', `upload failed: ${lastError?.message || lastError}`);
806
- }
807
- await new Promise(r => setTimeout(r, 0));
808
- }
809
- catch (e) {
810
- console.warn(`⚠ agent.md generation failed: ${e?.message || e}`);
811
- opts.onPhase?.('uploading_agentmd', 'warn', `generation failed: ${e?.message || e}`);
812
- }
813
- // Core-owned callers invoke the already-loaded runtime directly. Standalone
814
- // CLI callers retain the IPC path so this module remains usable without a
815
- // daemon instance in the current process.
816
- let hotLoaded = false;
817
- let hotLoadError;
818
- opts.onPhase?.('hot_loading', 'begin');
819
- try {
820
- if (runtime.hotLoadAgent) {
821
- await runtime.hotLoadAgent(opts.aid);
822
- hotLoaded = true;
823
- opts.onPhase?.('hot_loading', 'done');
824
- }
825
- else {
826
- const ipcResult = await ipcQuery(p.socket, { type: 'evolagent.load', aid: opts.aid }, 30_000);
827
- if (ipcResult?.ok) {
828
- hotLoaded = true;
829
- opts.onPhase?.('hot_loading', 'done');
830
- }
831
- else if (ipcResult) {
832
- hotLoadError = ipcResult.error;
833
- opts.onPhase?.('hot_loading', 'warn', hotLoadError);
834
- }
835
- else {
836
- opts.onPhase?.('hot_loading', 'warn', 'daemon not running');
837
- }
838
- }
839
- }
840
- catch (error) {
841
- const message = error instanceof Error ? error.message : String(error);
842
- hotLoadError = message;
843
- opts.onPhase?.('hot_loading', 'warn', message);
844
- }
845
- return {
846
- ok: true,
847
- aid: opts.aid,
848
- ownerAid: opts.owner,
849
- configPath: toPosix(path.join(agentDirPath, 'config.json')),
850
- aidCreated,
851
- agentmdUploaded,
852
- hotLoaded,
853
- hotLoadError,
854
- };
855
- }
856
207
  // ==================== agentSyncAids (deprecated) ====================
857
208
  /** @deprecated sync-aids 已废弃,不再从 CLI 调用 */
858
209
  export async function agentSyncAids() {
@@ -930,32 +281,32 @@ export async function agentReload(aid, options = {}) {
930
281
  }
931
282
  // Full resync
932
283
  try {
933
- const result = await ipcQuery(p.socket, { type: 'evolagent.resync' }, AGENT_RESYNC_TIMEOUT_MS);
284
+ const result = await ipcQuery(p.socket, { type: 'evolagent.resync' }, AGENT_RESYNC_TIMEOUT_MS, { throwOnError: true });
934
285
  if (result === null) {
935
- return { ok: false, error: 'evolcore 未运行,请先 ec start' };
286
+ return { ok: false, code: 'IPC_INVALID_RESPONSE', error: 'daemon IPC 未返回有效响应,请检查 ec status' };
936
287
  }
937
288
  if (result?.ok) {
938
289
  return { ok: true, results: result.results || [] };
939
290
  }
940
291
  return { ok: false, error: result?.error || 'unknown error' };
941
292
  }
942
- catch {
943
- return { ok: false, error: 'evolcore 未运行,请先 ec start' };
293
+ catch (error) {
294
+ return { ok: false, code: error instanceof IpcTransportError ? `IPC_${error.code}` : 'IPC_FAILED', error: String(error instanceof Error ? error.message : error) };
944
295
  }
945
296
  }
946
297
  // Single agent reload
947
298
  try {
948
- const result = await ipcQuery(p.socket, { type: 'evolagent.reload', name: aid, force: options.force === true }, AGENT_RELOAD_TIMEOUT_MS);
299
+ const result = await ipcQuery(p.socket, { type: 'evolagent.reload', name: aid, force: options.force === true }, AGENT_RELOAD_TIMEOUT_MS, { throwOnError: true });
949
300
  if (result === null) {
950
- return { ok: false, error: 'evolcore 未运行,请先 ec start 后再 reload' };
301
+ return { ok: false, code: 'IPC_INVALID_RESPONSE', error: 'daemon IPC 未返回有效响应,请检查 ec status' };
951
302
  }
952
303
  if (result?.ok) {
953
304
  return { ok: true };
954
305
  }
955
306
  return { ok: false, code: result?.code, error: result?.error || 'unknown error' };
956
307
  }
957
- catch {
958
- return { ok: false, error: 'evolcore 未运行,请先 ec start 后再 reload' };
308
+ catch (error) {
309
+ return { ok: false, code: error instanceof IpcTransportError ? `IPC_${error.code}` : 'IPC_FAILED', error: String(error instanceof Error ? error.message : error) };
959
310
  }
960
311
  }
961
312
  // ==================== agentEnable / agentDisable ====================