evolcore 0.0.10 → 0.0.12

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 (131) hide show
  1. package/CHANGELOG.md +44 -0
  2. package/README.md +3 -3
  3. package/bin/codex-managed-hook.mjs +80 -0
  4. package/dist/agents/baseagent.js +4 -0
  5. package/dist/agents/claude-runner.js +158 -44
  6. package/dist/agents/codex-app-server-client.js +153 -23
  7. package/dist/agents/codex-runner.js +331 -43
  8. package/dist/agents/ecagent-runner.js +53 -6
  9. package/dist/agents/gemini-runner.js +1 -0
  10. package/dist/agents/request-identity.js +55 -0
  11. package/dist/aun/aid/client.js +11 -21
  12. package/dist/aun/aid/managed-operation.js +107 -0
  13. package/dist/aun/msg/group.js +81 -12
  14. package/dist/aun/msg/managed-operation.js +804 -0
  15. package/dist/aun/msg/mention-schema.js +20 -0
  16. package/dist/aun/msg/p2p.js +7 -0
  17. package/dist/aun/outbox.js +28 -31
  18. package/dist/channels/aun.js +418 -235
  19. package/dist/channels/daemon.js +13 -0
  20. package/dist/cli/agent-command.js +72 -17
  21. package/dist/cli/agent.js +249 -22
  22. package/dist/cli/aun-commands.js +270 -69
  23. package/dist/cli/bench.js +12 -3
  24. package/dist/cli/daemon-commands.js +52 -33
  25. package/dist/cli/fs-command.js +60 -0
  26. package/dist/cli/handoff-command.js +3 -0
  27. package/dist/cli/index.js +106 -63
  28. package/dist/cli/init-cancel.js +208 -0
  29. package/dist/cli/init-channel.js +343 -195
  30. package/dist/cli/init.js +21 -9
  31. package/dist/cli/managed-command-guard.js +41 -0
  32. package/dist/cli/model.js +11 -4
  33. package/dist/cli/queue-command.js +11 -1
  34. package/dist/cli/response.js +71 -0
  35. package/dist/cli/restart-monitor.js +4 -18
  36. package/dist/cli/trigger-command.js +4 -0
  37. package/dist/config/builtin-roles.js +5 -0
  38. package/dist/config/contact-book-store.js +11 -3
  39. package/dist/config/contact-request-service.js +161 -15
  40. package/dist/config/gateway-config.js +26 -10
  41. package/dist/config/mention-mode.js +8 -24
  42. package/dist/core/agent-reload-coordinator.js +53 -0
  43. package/dist/core/auth/agent-delegation.js +0 -1
  44. package/dist/core/auth/operation-authorizer.js +62 -154
  45. package/dist/core/auth/operation-catalog.js +264 -22
  46. package/dist/core/bootstrap-messages.js +68 -0
  47. package/dist/core/bootstrap-service.js +144 -10
  48. package/dist/core/capability/providers/claude-capability-provider.js +9 -31
  49. package/dist/core/capability/providers/codex-capability-provider.js +4 -15
  50. package/dist/core/capability/skill-discovery.js +55 -0
  51. package/dist/core/channel-loader.js +24 -7
  52. package/dist/core/command/agent-control.js +14 -11
  53. package/dist/core/command/cli-intent-parser.js +9 -0
  54. package/dist/core/command/command-handler.js +467 -6
  55. package/dist/core/command/connect-menu.js +15 -0
  56. package/dist/core/command/group-menu.js +4 -4
  57. package/dist/core/command/menu-catalog.js +366 -0
  58. package/dist/core/command/menu-handler.js +213 -110
  59. package/dist/core/command/menu-protocol.js +71 -0
  60. package/dist/core/command/slash-gate.js +5 -2
  61. package/dist/core/command/slash-handler.js +45 -25
  62. package/dist/core/daemon-file-cache.js +2 -1
  63. package/dist/core/data-migration.js +9 -2
  64. package/dist/core/evolagent-registry.js +125 -35
  65. package/dist/core/evolagent.js +12 -5
  66. package/dist/core/handoff/dispatcher.js +5 -2
  67. package/dist/core/handoff/runtime.js +145 -21
  68. package/dist/core/handoff/store.js +24 -0
  69. package/dist/core/handoff/types.js +1 -0
  70. package/dist/core/inference/text-inference.js +38 -4
  71. package/dist/core/message/logical-queue-bridge.js +1 -0
  72. package/dist/core/message/mention-schema.js +126 -0
  73. package/dist/core/message/message-bridge.js +237 -79
  74. package/dist/core/message/message-log.js +23 -0
  75. package/dist/core/message/message-queue.js +20 -4
  76. package/dist/core/message/response-engine.js +54 -36
  77. package/dist/core/model/model-catalog.js +143 -24
  78. package/dist/core/model/model-diagnostics.js +28 -10
  79. package/dist/core/permission/ec-command-parser.js +156 -10
  80. package/dist/core/permission/index.js +1 -0
  81. package/dist/core/permission/readonly-shell-query.js +532 -0
  82. package/dist/core/permission/sandbox-runtime.js +22 -1
  83. package/dist/core/permission/shell-environment.js +46 -0
  84. package/dist/core/permission/tool-policy.js +292 -111
  85. package/dist/core/protected-paths.js +17 -9
  86. package/dist/core/runner-reload-transaction.js +57 -0
  87. package/dist/core/session/session-manager.js +21 -2
  88. package/dist/eck/manifest-engine.js +39 -13
  89. package/dist/index.js +644 -180
  90. package/dist/ipc.js +246 -19
  91. package/dist/response-system/coordinator.js +6 -4
  92. package/dist/response-system/engines/v1/proactive-flow.js +26 -8
  93. package/dist/response-system/modes/single-session/index.js +14 -1
  94. package/dist/response-system/selector.js +6 -5
  95. package/dist/trigger/anomaly-store.js +4 -0
  96. package/dist/trigger/script-executor.js +1 -0
  97. package/dist/utils/aid-bind.js +3 -8
  98. package/dist/utils/codex-app-server-registry.js +90 -0
  99. package/dist/utils/codex-cli.js +5 -1
  100. package/dist/utils/cross-platform.js +15 -0
  101. package/dist/utils/log-writer.js +6 -10
  102. package/dist/utils/logger.js +5 -5
  103. package/dist/utils/npm-ops.js +5 -12
  104. package/dist/utils/tool-summary.js +11 -0
  105. package/kits/docs/channels/aun.md +1 -1
  106. package/kits/docs/context-assembly.md +2 -2
  107. package/kits/docs/evolcore/agent.md +11 -6
  108. package/kits/docs/evolcore/aid.md +14 -0
  109. package/kits/docs/evolcore/msg.md +13 -0
  110. package/kits/docs/prompt-loading-architecture.md +1 -2
  111. package/kits/docs/venues/group.md +1 -1
  112. package/kits/eck_manifest.json +0 -12
  113. package/kits/rules/01-overview.md +9 -0
  114. package/kits/rules/04-relation.md +1 -1
  115. package/kits/rules/05-venue.md +2 -2
  116. package/kits/schemas/agent-config.schema.3.json +1 -1
  117. package/kits/schemas/agent-config.schema.4.json +1 -1
  118. package/kits/schemas/relation-config.schema.2.json +1 -1
  119. package/kits/schemas/role-config.schema.1.json +1 -1
  120. package/kits/schemas/single-session.schema.1.json +3 -3
  121. package/kits/schemas/single-session.schema.2.json +3 -3
  122. package/kits/templates/roles/admin.json +27 -0
  123. package/kits/templates/roles/member.json +44 -0
  124. package/kits/templates/roles/visitor.json +40 -2
  125. package/kits/templates/system-fragments/bootstrap.md +12 -6
  126. package/kits/templates/system-fragments/channel.md +6 -0
  127. package/kits/templates/system-fragments/session.md +2 -0
  128. package/package.json +3 -2
  129. package/skills/eclink/SKILL.md +15 -3
  130. package/skills/eclink/agents/openai.yaml +3 -3
  131. package/dist/core/command/evol-menu-version-gate.js +0 -39
@@ -415,7 +415,11 @@ function normalizeExecutionAnomaly(value) {
415
415
  : Date.now();
416
416
  const toolName = nonEmptyString(value.toolName);
417
417
  const requestId = nonEmptyString(value.requestId);
418
+ const policyCode = nonEmptyString(value.policyCode);
418
419
  const policy = normalizeApproverPolicy(value.policy);
420
+ const agentAid = nonEmptyString(value.agentAid);
421
+ const sessionId = nonEmptyString(value.sessionId);
422
+ const permissionMode = normalizePermissionMode(value.permissionMode);
419
423
  const summary = nonEmptyString(value.summary);
420
424
  return {
421
425
  code: code,
@@ -425,7 +429,11 @@ function normalizeExecutionAnomaly(value) {
425
429
  effect,
426
430
  ...(toolName ? { toolName } : {}),
427
431
  ...(requestId ? { requestId } : {}),
432
+ ...(policyCode ? { policyCode } : {}),
428
433
  ...(policy ? { policy } : {}),
434
+ ...(agentAid ? { agentAid } : {}),
435
+ ...(sessionId ? { sessionId } : {}),
436
+ ...(permissionMode ? { permissionMode } : {}),
429
437
  ...(summary ? { summary: summary.slice(0, 512) } : {}),
430
438
  };
431
439
  }
@@ -437,3 +445,8 @@ function normalizeApproverPolicy(value) {
437
445
  ? value
438
446
  : undefined;
439
447
  }
448
+ function normalizePermissionMode(value) {
449
+ return value === 'readonly' || value === 'auto' || value === 'request' || value === 'bypass'
450
+ ? value
451
+ : undefined;
452
+ }
@@ -59,6 +59,13 @@ Options:
59
59
  ec agent delete mybot.agentid.pub --purge`);
60
60
  return;
61
61
  }
62
+ if (process.env.EVOLCORE_SESSION_ID && !wantsHelp(args) && !new Set(['get', 'set', 'ready']).has(sub)) {
63
+ if (formatJson)
64
+ console.log(JSON.stringify({ ok: false, code: 'NOT_ALLOWED', error: `agent ${sub} is not available in a managed task` }));
65
+ else
66
+ console.error(`❌ [NOT_ALLOWED] agent ${sub} is not available in a managed task`);
67
+ process.exit(1);
68
+ }
62
69
  const { agentList, agentShow, agentCreateInteractive, agentCreateNonInteractive, agentReload, agentEnable, agentDisable, agentGet, agentSet, agentRename, agentDelete, agentReady, } = await import('./agent.js');
63
70
  // --- list ---
64
71
  if (sub === 'list') {
@@ -137,17 +144,21 @@ Options:
137
144
  ec agent new <aid> --non-interactive [选项]
138
145
 
139
146
  非交互模式选项:
140
- --baseagent <claude|codex|gemini|ecagent> 默认: PATH 中第一个可用;ecagent 需显式选择
141
- --project <absolute path> 必填
142
- --owner <aid>
143
- --name <display-name>
144
- --description <text>
147
+ --project <path> 默认: 当前目录
148
+ --baseagent <claude|codex|gemini|ecagent>
149
+ 默认: defaults 配置,否则检测本机可用基座
150
+ --owner <aid> 默认: daemon.json 第一个 owner
151
+ --name <display-name> 默认: package.json name,否则目录名
152
+ --description <text> 默认: package.json description,否则留空
153
+ --dry-run 只预览最终参数,不注册 AID、不写文件
145
154
  --force 覆盖已有 config.json
146
155
  --format json 输出 JSON
147
156
 
148
157
  示例:
149
158
  ec agent new mybot.agentid.pub
150
- ec agent new mybot.agentid.pub --non-interactive --project /abs/path --baseagent claude`);
159
+ ec agent new mybot.agentid.pub --non-interactive
160
+ ec agent new mybot.agentid.pub --non-interactive --dry-run --format json
161
+ ec agent new mybot.agentid.pub --non-interactive --project ../project --baseagent claude`);
151
162
  return;
152
163
  }
153
164
  const name = args[1];
@@ -161,15 +172,52 @@ Options:
161
172
  const idx = args.indexOf(flag);
162
173
  return idx !== -1 && idx + 1 < args.length ? args[idx + 1] : undefined;
163
174
  };
164
- const result = await agentCreateNonInteractive({
175
+ const { resolveAgentCreatePlan, validateAgentCreatePlan } = await import('./agent.js');
176
+ const plan = resolveAgentCreatePlan({
165
177
  aid: name,
178
+ project: getArg('--project'),
166
179
  baseagent: getArg('--baseagent'),
167
- project: getArg('--project') || '',
168
180
  owner: getArg('--owner'),
169
181
  name: getArg('--name'),
170
182
  description: getArg('--description'),
171
183
  force: args.includes('--force'),
172
184
  });
185
+ const validationError = validateAgentCreatePlan(plan);
186
+ if (validationError) {
187
+ const error = { ok: false, error: validationError };
188
+ if (formatJson)
189
+ console.log(JSON.stringify(error));
190
+ else
191
+ console.error(`❌ ${error.error}`);
192
+ process.exitCode = 1;
193
+ return;
194
+ }
195
+ if (args.includes('--dry-run')) {
196
+ const preview = { ok: true, dryRun: true, plan };
197
+ if (formatJson)
198
+ console.log(JSON.stringify(preview, null, 2));
199
+ else {
200
+ console.log('EvolAgent 创建计划(dry-run)');
201
+ console.log(` AID: ${plan.aid}`);
202
+ console.log(` Project: ${plan.project}`);
203
+ console.log(` Baseagent: ${plan.baseagent} (${plan.baseagentSource})`);
204
+ console.log(` Owner: ${plan.owner || '未设置'} (${plan.ownerSource})`);
205
+ console.log(` Name: ${plan.name} (${plan.nameSource})`);
206
+ if (plan.description)
207
+ console.log(` Description: ${plan.description} (${plan.descriptionSource})`);
208
+ console.log(` Existing config: ${plan.configExists ? (plan.force ? '将覆盖' : '是') : '否'}`);
209
+ }
210
+ return;
211
+ }
212
+ const result = await agentCreateNonInteractive({
213
+ aid: plan.aid,
214
+ baseagent: plan.baseagent,
215
+ project: plan.project,
216
+ owner: plan.owner,
217
+ name: plan.name,
218
+ description: plan.description,
219
+ force: args.includes('--force'),
220
+ });
173
221
  if (!result.ok) {
174
222
  if (formatJson) {
175
223
  console.log(JSON.stringify(result));
@@ -179,6 +227,8 @@ Options:
179
227
  }
180
228
  process.exit(1);
181
229
  }
230
+ if (result.ownerBindInterrupted)
231
+ return;
182
232
  if (formatJson) {
183
233
  console.log(JSON.stringify(result, null, 2));
184
234
  }
@@ -211,6 +261,8 @@ Options:
211
261
  }
212
262
  process.exit(1);
213
263
  }
264
+ if (result.ownerBindInterrupted)
265
+ return;
214
266
  if (formatJson) {
215
267
  console.log(JSON.stringify(result, null, 2));
216
268
  }
@@ -259,15 +311,16 @@ Options:
259
311
  // --- reload ---
260
312
  if (sub === 'reload') {
261
313
  if (wantsHelp(args)) {
262
- console.log(`用法: ec agent reload [aid] [--format json]
314
+ console.log(`用法: ec agent reload [aid] [--force] [--format json]
263
315
 
264
316
  热重载 agent 配置。
265
317
  无参数 全量 resync(扫磁盘,新增上线、删除下线、修改热更新)
266
- <aid> 仅热重载指定 agent`);
318
+ <aid> 仅热重载指定 agent
319
+ --force 中断目标 Agent 的运行中任务并清空排队任务后重载(仅与 <aid> 同用)`);
267
320
  return;
268
321
  }
269
322
  const target = args[1] && !args[1].startsWith('--') ? args[1] : undefined;
270
- const result = await agentReload(target);
323
+ const result = await agentReload(target, { force: args.includes('--force') });
271
324
  if (!result.ok) {
272
325
  if (formatJson) {
273
326
  console.log(JSON.stringify(result));
@@ -294,9 +347,10 @@ Options:
294
347
  // --- enable ---
295
348
  if (sub === 'enable') {
296
349
  if (wantsHelp(args)) {
297
- console.log(`用法: ec agent enable <aid> [--format json]
350
+ console.log(`用法: ec agent enable <aid> [--force] [--format json]
298
351
 
299
- 启用 agent。若服务运行中会热重载,否则下次 ec start 时生效。`);
352
+ 启用 agent。若服务运行中会热重载,否则下次 ec start 时生效。
353
+ --force 中断目标 Agent 的运行中任务并清空排队任务后重载`);
300
354
  return;
301
355
  }
302
356
  const aid = args[1];
@@ -304,7 +358,7 @@ Options:
304
358
  console.error('用法: ec agent enable <aid>');
305
359
  process.exit(1);
306
360
  }
307
- const result = await agentEnable(aid);
361
+ const result = await agentEnable(aid, { force: args.includes('--force') });
308
362
  if (!result.ok) {
309
363
  if (formatJson) {
310
364
  console.log(JSON.stringify(result));
@@ -325,9 +379,10 @@ Options:
325
379
  // --- disable ---
326
380
  if (sub === 'disable') {
327
381
  if (wantsHelp(args)) {
328
- console.log(`用法: ec agent disable <aid> [--format json]
382
+ console.log(`用法: ec agent disable <aid> [--force] [--format json]
329
383
 
330
- 停用 agent。若服务运行中会热重载离线,否则在配置中标记为禁用。`);
384
+ 停用 agent。若服务运行中会热重载离线,否则在配置中标记为禁用。
385
+ --force 中断目标 Agent 的运行中任务并清空排队任务后重载`);
331
386
  return;
332
387
  }
333
388
  const aid = args[1];
@@ -335,7 +390,7 @@ Options:
335
390
  console.error('用法: ec agent disable <aid>');
336
391
  process.exit(1);
337
392
  }
338
- const result = await agentDisable(aid);
393
+ const result = await agentDisable(aid, { force: args.includes('--force') });
339
394
  if (!result.ok) {
340
395
  if (formatJson) {
341
396
  console.log(JSON.stringify(result));
package/dist/cli/agent.js CHANGED
@@ -2,20 +2,25 @@ import fs from 'fs';
2
2
  import path from 'path';
3
3
  import readline from 'readline';
4
4
  import { resolvePaths, agentMdPath as getAgentMdPathFromPaths, aunPath as defaultAunPath } from '../paths.js';
5
- import { loadDefaults, loadAllAgents, loadAgent, saveAgent, ensureAgentDirSkeleton } from '../config-store.js';
5
+ import { loadDefaults, loadAllAgents, loadAgent, saveAgent, ensureAgentDirSkeleton, loadDaemonConfig } from '../config-store.js';
6
6
  import { ConfigTarget, write as cfgWrite, ensureFile as cfgEnsure, read as cfgRead, initConfigManager } from '../config/config-manager.js';
7
7
  import { resolveConfigCommand } from '../config/resolved-config-op.js';
8
8
  import { executeResolvedConfigCommand } from '../config/config-operation-service.js';
9
9
  import { ipcQuery } from '../ipc.js';
10
10
  import { CONFIG_SCHEMA_VERSION } from '../types.js';
11
- import { withLifecycleForWrite } from '../config/lifecycle.js';
11
+ import { normalizeAgentLifecycle, withLifecycleForWrite } from '../config/lifecycle.js';
12
+ import { bindPostBootstrapWelcomeOutboxSession, hasPendingPostBootstrapWelcomeOutbox, postBootstrapWelcomeOperationId, preparePostBootstrapWelcomeOutbox, } from '../core/bootstrap-messages.js';
12
13
  import { ensureInitialRoleRegistry } from '../config/role-service.js';
13
- import { isValidChannelName } from '../core/channel-loader.js';
14
+ import { formatChannelKey, isValidChannelName } from '../core/channel-loader.js';
14
15
  import { commandExists } from '../utils/cross-platform.js';
15
16
  import { getCodexAppServerAvailability, isCodexAppServerAvailable } from '../agents/codex-runner.js';
16
17
  import { resolveEcagentConfig } from '../agents/baseagent.js';
17
18
  import { agentProjectRootFromDefaults, deriveAgentProjectPath } from '../utils/project-path.js';
18
19
  import { AGENT_DELEGATION_TOKEN_ENV } from '../core/auth/agent-delegation.js';
20
+ import { readTaskRuntimeContextFromEnv } from './task-context.js';
21
+ import { EventBus } from '../core/event-bus.js';
22
+ import { SessionManager } from '../core/session/session-manager.js';
23
+ import { isValidAid } from '../aun/aid/validation.js';
19
24
  function withStaticOwner(config, owner) {
20
25
  const value = owner?.trim();
21
26
  if (!value)
@@ -41,6 +46,100 @@ function detectAvailableBaseagents() {
41
46
  function pickDefaultBaseagent(available) {
42
47
  return AUTO_BASEAGENT_CANDIDATES.find(candidate => available.includes(candidate)) ?? null;
43
48
  }
49
+ function readProjectMetadata(project) {
50
+ try {
51
+ const packageJsonPath = path.join(project, 'package.json');
52
+ if (!fs.existsSync(packageJsonPath))
53
+ return {};
54
+ const parsed = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
55
+ return {
56
+ name: typeof parsed.name === 'string' ? parsed.name.trim() : undefined,
57
+ description: typeof parsed.description === 'string' ? parsed.description.trim() : undefined,
58
+ };
59
+ }
60
+ catch {
61
+ return {};
62
+ }
63
+ }
64
+ function projectSlug(project) {
65
+ const basename = path.basename(project) || 'agent';
66
+ const slug = basename
67
+ .normalize('NFKD')
68
+ .replace(/[^a-zA-Z0-9-]+/g, '-')
69
+ .replace(/^-+|-+$/g, '')
70
+ .toLowerCase()
71
+ .slice(0, 63)
72
+ .replace(/-+$/g, '');
73
+ return slug || 'agent';
74
+ }
75
+ /**
76
+ * Resolve non-interactive creation defaults without performing any network or
77
+ * filesystem mutation. The CLI uses this for both execution and --dry-run.
78
+ */
79
+ export function resolveAgentCreatePlan(options) {
80
+ const aid = options.aid.trim();
81
+ const project = path.resolve(options.project || process.cwd());
82
+ const metadata = readProjectMetadata(project);
83
+ const defaults = loadDefaults();
84
+ const daemon = loadDaemonConfig();
85
+ const configuredBaseagent = defaults?.active_baseagent;
86
+ const available = detectAvailableBaseagents();
87
+ const defaultBaseagent = configuredBaseagent && available.includes(configuredBaseagent)
88
+ ? configuredBaseagent
89
+ : pickDefaultBaseagent(available) ?? undefined;
90
+ const baseagent = options.baseagent || defaultBaseagent;
91
+ const baseagentSource = options.baseagent
92
+ ? 'argument'
93
+ : configuredBaseagent && baseagent === configuredBaseagent
94
+ ? 'defaults'
95
+ : 'detected';
96
+ const owner = options.owner?.trim() || daemon.owners?.[0];
97
+ const ownerSource = options.owner?.trim() ? 'argument' : owner ? 'daemon' : 'none';
98
+ const name = options.name?.trim() || metadata.name || projectSlug(project);
99
+ const description = options.description?.trim() || metadata.description || '';
100
+ const configExists = isValidAid(aid)
101
+ && fs.existsSync(path.join(resolvePaths().agentsDir, aid, 'config.json'));
102
+ return {
103
+ aid,
104
+ project,
105
+ baseagent,
106
+ owner,
107
+ name,
108
+ description,
109
+ force: options.force === true,
110
+ configExists,
111
+ baseagentSource,
112
+ ownerSource,
113
+ nameSource: options.name?.trim() ? 'argument' : metadata.name ? 'package' : 'directory',
114
+ descriptionSource: options.description?.trim() ? 'argument' : metadata.description ? 'package' : 'none',
115
+ };
116
+ }
117
+ export function validateAgentCreatePlan(plan) {
118
+ if (!isValidAid(plan.aid)) {
119
+ return `Invalid AID "${plan.aid}": must be a valid multi-level domain (e.g. mybot.agentid.pub)`;
120
+ }
121
+ if (plan.configExists && !plan.force) {
122
+ return `Agent "${plan.aid}" already exists (use --force to overwrite)`;
123
+ }
124
+ if (!plan.baseagent) {
125
+ return 'No usable baseagent detected. Pass --baseagent or configure a baseagent in agents/defaults.json.';
126
+ }
127
+ if (!BASEAGENT_CANDIDATES.includes(plan.baseagent)) {
128
+ return `Invalid baseagent: ${plan.baseagent} (options: ${BASEAGENT_CANDIDATES.join('/')})`;
129
+ }
130
+ if (!isBaseagentAvailable(plan.baseagent)) {
131
+ return `${plan.baseagent} is not available in the current environment`;
132
+ }
133
+ if (plan.baseagent === 'ecagent') {
134
+ const reason = ecagentUnavailableReason();
135
+ if (reason)
136
+ return reason;
137
+ }
138
+ if (plan.owner && !isValidAid(plan.owner)) {
139
+ return `Invalid owner: ${plan.owner}`;
140
+ }
141
+ return null;
142
+ }
44
143
  function ecagentUnavailableReason() {
45
144
  try {
46
145
  resolveEcagentConfig({ agents: { ecagent: {} } });
@@ -458,6 +557,7 @@ export async function agentCreateInteractive(opts = {}) {
458
557
  catch { /* daemon not running */ }
459
558
  let ownerBoundAid;
460
559
  let ownerBindSkipped = false;
560
+ let ownerBindInterrupted = false;
461
561
  // Only prompt for agent owner binding if:
462
562
  // 1. User didn't manually input owner during setup (owner is undefined)
463
563
  // 2. User didn't skip QR binding explicitly (skipQrBinding is false)
@@ -467,15 +567,18 @@ export async function agentCreateInteractive(opts = {}) {
467
567
  console.log('\n📡 Agent owner 绑定 — 请用 evol app 扫描二维码,10 分钟内有效\n');
468
568
  try {
469
569
  const { runAgentOwnerQrBindFlow } = await import('./init-channel.js');
470
- const bound = await runAgentOwnerQrBindFlow(aid, agentName, 'append');
471
- if (bound?.boundAid) {
570
+ const bound = await runAgentOwnerQrBindFlow(aid, agentName);
571
+ if (bound.action === 'bound') {
472
572
  ownerBoundAid = bound.boundAid;
473
573
  console.log(` ✓ 已配置 agent owner: ${bound.boundAid}`);
474
574
  }
475
- else {
575
+ else if (bound.action === 'manual') {
476
576
  ownerBindSkipped = true;
477
577
  ownerBoundAid = await promptAgentOwnerManually(aid);
478
578
  }
579
+ else {
580
+ ownerBindInterrupted = true;
581
+ }
479
582
  }
480
583
  catch (e) {
481
584
  ownerBindSkipped = true;
@@ -493,6 +596,7 @@ export async function agentCreateInteractive(opts = {}) {
493
596
  hotLoadError,
494
597
  ownerBoundAid,
495
598
  ownerBindSkipped,
599
+ ownerBindInterrupted,
496
600
  };
497
601
  }
498
602
  finally {
@@ -784,12 +888,17 @@ export async function agentSyncAids() {
784
888
  return { ok: true, created, template: templateAgent.aid, hotReloaded };
785
889
  }
786
890
  // ==================== agentReload ====================
787
- export async function agentReload(aid) {
891
+ const AGENT_RELOAD_TIMEOUT_MS = 45_000;
892
+ const AGENT_RESYNC_TIMEOUT_MS = 5 * 60_000;
893
+ export async function agentReload(aid, options = {}) {
788
894
  const p = resolvePaths();
789
895
  if (!aid) {
896
+ if (options.force) {
897
+ return { ok: false, code: 'INVALID_ARGS', error: '--force requires a target Agent AID' };
898
+ }
790
899
  // Full resync
791
900
  try {
792
- const result = await ipcQuery(p.socket, { type: 'evolagent.resync' });
901
+ const result = await ipcQuery(p.socket, { type: 'evolagent.resync' }, AGENT_RESYNC_TIMEOUT_MS);
793
902
  if (result === null) {
794
903
  return { ok: false, error: 'evolcore 未运行,请先 ec start' };
795
904
  }
@@ -804,27 +913,27 @@ export async function agentReload(aid) {
804
913
  }
805
914
  // Single agent reload
806
915
  try {
807
- const result = await ipcQuery(p.socket, { type: 'evolagent.reload', name: aid });
916
+ const result = await ipcQuery(p.socket, { type: 'evolagent.reload', name: aid, force: options.force === true }, AGENT_RELOAD_TIMEOUT_MS);
808
917
  if (result === null) {
809
918
  return { ok: false, error: 'evolcore 未运行,请先 ec start 后再 reload' };
810
919
  }
811
920
  if (result?.ok) {
812
921
  return { ok: true };
813
922
  }
814
- return { ok: false, error: result?.error || 'unknown error' };
923
+ return { ok: false, code: result?.code, error: result?.error || 'unknown error' };
815
924
  }
816
925
  catch {
817
926
  return { ok: false, error: 'evolcore 未运行,请先 ec start 后再 reload' };
818
927
  }
819
928
  }
820
929
  // ==================== agentEnable / agentDisable ====================
821
- export async function agentEnable(aid) {
822
- return agentSetEnabled(aid, true);
930
+ export async function agentEnable(aid, options = {}) {
931
+ return agentSetEnabled(aid, true, options);
823
932
  }
824
- export async function agentDisable(aid) {
825
- return agentSetEnabled(aid, false);
933
+ export async function agentDisable(aid, options = {}) {
934
+ return agentSetEnabled(aid, false, options);
826
935
  }
827
- async function agentSetEnabled(aid, enabled) {
936
+ async function agentSetEnabled(aid, enabled, options = {}) {
828
937
  const p = resolvePaths();
829
938
  let config;
830
939
  try {
@@ -836,15 +945,28 @@ async function agentSetEnabled(aid, enabled) {
836
945
  if (!config) {
837
946
  return { ok: false, error: `Agent "${aid}" not found` };
838
947
  }
948
+ const previousEnabled = config.enabled;
839
949
  config.enabled = enabled;
840
950
  saveAgent(config);
841
951
  // Try to hot-reload if daemon is running, but don't fail if it's not
842
952
  let reloaded = false;
843
953
  try {
844
- const result = await ipcQuery(p.socket, { type: 'evolagent.reload', name: aid }, 1000);
954
+ const result = await ipcQuery(p.socket, {
955
+ type: 'evolagent.reload',
956
+ name: aid,
957
+ force: options.force === true,
958
+ }, AGENT_RELOAD_TIMEOUT_MS);
845
959
  if (result?.ok) {
846
960
  reloaded = true;
847
961
  }
962
+ else if (result?.code === 'AGENT_BUSY') {
963
+ if (previousEnabled === undefined)
964
+ delete config.enabled;
965
+ else
966
+ config.enabled = previousEnabled;
967
+ saveAgent(config);
968
+ return { ok: false, code: result.code, error: result.error || 'Agent is busy' };
969
+ }
848
970
  }
849
971
  catch {
850
972
  // Daemon not running or unreachable — that's ok, config is already saved
@@ -1074,17 +1196,122 @@ export async function agentRename(aid, name) {
1074
1196
  return { ok: true, aid, name };
1075
1197
  }
1076
1198
  // ==================== agentReady ====================
1199
+ async function ensureOfflinePostBootstrapMainSession(aid, config) {
1200
+ const owner = config.owners?.[0];
1201
+ if (!owner)
1202
+ throw new Error(`Agent "${aid}" has no owner for the post-bootstrap main session`);
1203
+ const p = resolvePaths();
1204
+ const aunName = config.channels?.find(channel => channel.type === 'aun')?.name || 'main';
1205
+ const channel = formatChannelKey({ type: 'aun', selfAID: aid, name: aunName });
1206
+ const manager = new SessionManager(p.sessionsDir, new EventBus());
1207
+ const active = await manager.getActiveSession(channel, owner);
1208
+ const ensured = await manager.ensureNewMainSessionForOperation({
1209
+ channel,
1210
+ channelId: owner,
1211
+ projectPath: active?.projectPath
1212
+ || config.projects?.defaultPath
1213
+ || path.join(p.root, 'projects', aid.split('.')[0]),
1214
+ baseagent: config.active_baseagent || active?.baseagent || 'claude',
1215
+ operationId: postBootstrapWelcomeOperationId(aid),
1216
+ identityMetadata: {
1217
+ ...(active?.metadata ?? {}),
1218
+ channelKey: channel,
1219
+ peerId: owner,
1220
+ },
1221
+ });
1222
+ return ensured.session.id;
1223
+ }
1077
1224
  export async function agentReady(aid) {
1225
+ const taskSelfAid = readTaskRuntimeContextFromEnv()?.selfAid;
1226
+ if (taskSelfAid && taskSelfAid.replace(/^@/, '') !== aid.replace(/^@/, '')) {
1227
+ return {
1228
+ ok: false,
1229
+ code: 'SELF_AGENT_REQUIRED',
1230
+ error: `A managed task can only complete bootstrap for itself (${taskSelfAid})`,
1231
+ };
1232
+ }
1078
1233
  const p = resolvePaths();
1234
+ const managedSessionId = process.env.EVOLCORE_SESSION_ID;
1235
+ if (managedSessionId) {
1236
+ const delegationToken = process.env[AGENT_DELEGATION_TOKEN_ENV];
1237
+ if (!delegationToken)
1238
+ return { ok: false, code: 'DELEGATION_REQUIRED', error: `${AGENT_DELEGATION_TOKEN_ENV} is not set for the active task` };
1239
+ const result = await ipcQuery(p.socket, {
1240
+ type: 'evolagent.bootstrapComplete',
1241
+ aid,
1242
+ sessionId: managedSessionId,
1243
+ delegationToken,
1244
+ }, 30_000);
1245
+ if (!result)
1246
+ return { ok: false, code: 'DAEMON_UNAVAILABLE', error: 'Unable to connect to the evolcore daemon' };
1247
+ return result.ok
1248
+ ? { ok: true, aid, reloaded: result.reloaded === true }
1249
+ : { ok: false, code: result.code, error: result.error || 'Failed to complete bootstrap' };
1250
+ }
1079
1251
  const config = loadAgent(aid);
1080
1252
  if (!config)
1081
1253
  return { ok: false, error: `Agent "${aid}" not found` };
1082
- saveAgent(withLifecycleForWrite(config, 'active'));
1083
- let reloaded = false;
1084
1254
  try {
1085
- const result = await ipcQuery(p.socket, { type: 'evolagent.bootstrapComplete', aid }, 30_000);
1086
- reloaded = !!result?.ok;
1255
+ let controlToken;
1256
+ try {
1257
+ controlToken = fs.readFileSync(p.controlToken, 'utf8').trim() || undefined;
1258
+ }
1259
+ catch { }
1260
+ const result = await ipcQuery(p.socket, { type: 'evolagent.bootstrapComplete', aid, controlToken }, 30_000);
1261
+ if (!result) {
1262
+ const offlineConfig = loadAgent(aid) || config;
1263
+ const lifecycle = normalizeAgentLifecycle(offlineConfig).lifecycle;
1264
+ if (lifecycle !== 'bootstrapping') {
1265
+ if (lifecycle === 'active') {
1266
+ if (hasPendingPostBootstrapWelcomeOutbox(aid)) {
1267
+ try {
1268
+ const sessionId = await ensureOfflinePostBootstrapMainSession(aid, offlineConfig);
1269
+ if (!bindPostBootstrapWelcomeOutboxSession(aid, sessionId)) {
1270
+ throw new Error(`Failed to bind the completion welcome to session ${sessionId}`);
1271
+ }
1272
+ }
1273
+ catch (e) {
1274
+ return {
1275
+ ok: false,
1276
+ code: 'MAIN_SESSION_CREATE_FAILED',
1277
+ error: e instanceof Error ? e.message : 'Failed to recover the post-bootstrap main session',
1278
+ };
1279
+ }
1280
+ }
1281
+ return { ok: true, aid, reloaded: false };
1282
+ }
1283
+ return { ok: false, code: 'INVALID_LIFECYCLE', error: `Agent "${aid}" is ${lifecycle}; only a bootstrapping agent can become ready` };
1284
+ }
1285
+ const owner = offlineConfig.owners?.[0];
1286
+ if (!owner)
1287
+ return { ok: false, code: 'OWNER_REQUIRED', error: `Agent "${aid}" has no owner for the completion welcome` };
1288
+ try {
1289
+ preparePostBootstrapWelcomeOutbox(aid, owner);
1290
+ const sessionId = await ensureOfflinePostBootstrapMainSession(aid, offlineConfig);
1291
+ if (!bindPostBootstrapWelcomeOutboxSession(aid, sessionId)) {
1292
+ throw new Error(`Failed to bind the completion welcome to session ${sessionId}`);
1293
+ }
1294
+ saveAgent(withLifecycleForWrite(offlineConfig, 'active'));
1295
+ }
1296
+ catch (e) {
1297
+ return {
1298
+ ok: false,
1299
+ code: 'READY_PERSIST_FAILED',
1300
+ error: e instanceof Error ? e.message : 'Failed to persist bootstrap completion',
1301
+ };
1302
+ }
1303
+ return { ok: true, aid, reloaded: false };
1304
+ }
1305
+ if (!result.ok) {
1306
+ return { ok: false, code: result.code, error: result.error || 'Failed to complete bootstrap' };
1307
+ }
1308
+ return { ok: true, aid, reloaded: true };
1309
+ }
1310
+ catch (e) {
1311
+ return {
1312
+ ok: false,
1313
+ code: 'DAEMON_UNAVAILABLE',
1314
+ error: e instanceof Error ? e.message : 'Unable to connect to the EvolCore daemon',
1315
+ };
1087
1316
  }
1088
- catch { /* daemon not running */ }
1089
- return { ok: true, aid, reloaded };
1090
1317
  }