evolcore 0.0.38 → 0.0.40

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 (125) hide show
  1. package/CHANGELOG.md +64 -0
  2. package/README.md +31 -20
  3. package/bin/ec-safe-output.js +14 -2
  4. package/dist/agents/baseagent-availability.js +1 -0
  5. package/dist/agents/claude-runner.js +32 -46
  6. package/dist/agents/codex-runner.js +54 -30
  7. package/dist/agents/ecagent-runner.js +18 -7
  8. package/dist/agents/gemini-runner.js +1 -2
  9. package/dist/agents/runner-types.js +28 -6
  10. package/dist/aun/aid/agentmd.js +21 -0
  11. package/dist/aun/aid/client.js +1 -1
  12. package/dist/aun/aid/control-aid.js +23 -98
  13. package/dist/aun/aid/ownership-operation.js +35 -12
  14. package/dist/aun/group/invite-service.js +6 -0
  15. package/dist/aun/msg/p2p.js +8 -12
  16. package/dist/channels/aun.js +66 -48
  17. package/dist/channels/dingtalk.js +8 -2
  18. package/dist/channels/feishu.js +6 -1
  19. package/dist/channels/wecom.js +7 -3
  20. package/dist/cli/agent-create.js +30 -6
  21. package/dist/cli/agent.js +30 -37
  22. package/dist/cli/aid-bind-command.js +16 -2
  23. package/dist/cli/aun-commands.js +31 -23
  24. package/dist/cli/config.js +15 -18
  25. package/dist/cli/contact.js +9 -12
  26. package/dist/cli/ctl-command.js +3 -3
  27. package/dist/cli/daemon-commands.js +242 -17
  28. package/dist/cli/diag.js +7 -3
  29. package/dist/cli/fs-command.js +5 -4
  30. package/dist/cli/handoff-command.js +11 -11
  31. package/dist/cli/help.js +18 -0
  32. package/dist/cli/index.js +25 -13
  33. package/dist/cli/init-channel.js +2 -1
  34. package/dist/cli/memory-command.js +47 -27
  35. package/dist/cli/model.js +5 -4
  36. package/dist/cli/queue-command.js +3 -3
  37. package/dist/cli/response.js +5 -4
  38. package/dist/cli/restart-monitor.js +35 -13
  39. package/dist/cli/skill-command.js +13 -13
  40. package/dist/cli/task-command.js +3 -3
  41. package/dist/cli/task-context.js +1 -0
  42. package/dist/cli/thread-command.js +5 -3
  43. package/dist/cli/trigger-command.js +3 -3
  44. package/dist/cli/version.js +6 -2
  45. package/dist/config/config-field-policy.js +1 -0
  46. package/dist/contracts/ecweb-service-protocol.js +44 -0
  47. package/dist/core/agent-runtime.js +6 -0
  48. package/dist/core/auth/agent-delegation.js +7 -4
  49. package/dist/core/auth/auth-gateway.js +3 -1
  50. package/dist/core/auth/authorization-audit.js +5 -1
  51. package/dist/core/auth/debug-context.js +5 -1
  52. package/dist/core/auth/operation-catalog.js +17 -1
  53. package/dist/core/bootstrap-service.js +20 -12
  54. package/dist/core/command/cli-intent-parser.js +5 -2
  55. package/dist/core/command/command-handler.js +20 -5
  56. package/dist/core/command/menu-catalog.js +48 -12
  57. package/dist/core/command/menu-handler.js +74 -6
  58. package/dist/core/command/menu-protocol.js +20 -1
  59. package/dist/core/command/slash-gate.js +17 -3
  60. package/dist/core/command/slash-handler.js +90 -7
  61. package/dist/core/event-catalog.js +2 -0
  62. package/dist/core/interaction-router.js +2 -1
  63. package/dist/core/ipc-registration.js +26 -7
  64. package/dist/core/memory/batch-meta.js +2 -0
  65. package/dist/core/memory/consolidation-context.js +42 -51
  66. package/dist/core/memory/consolidation-ops-records.js +39 -7
  67. package/dist/core/memory/consolidation-trigger.js +71 -64
  68. package/dist/core/memory/consolidation.js +226 -20
  69. package/dist/core/memory/memory-writer.js +160 -34
  70. package/dist/core/memory/relation-idle-monitor.js +287 -0
  71. package/dist/core/memory/relation-sweep.js +206 -0
  72. package/dist/core/message/aun-ipc-sender.js +5 -0
  73. package/dist/core/message/message-bridge.js +64 -55
  74. package/dist/core/message/message-log.js +17 -0
  75. package/dist/core/message/message-queue.js +6 -0
  76. package/dist/core/message/outbound.js +70 -0
  77. package/dist/core/message/peer-mode.js +7 -2
  78. package/dist/core/message/response-engine.js +341 -73
  79. package/dist/core/ownership/owner-resolver.js +465 -140
  80. package/dist/core/permission/ec-command-parser.js +19 -3
  81. package/dist/core/permission/protected-paths.js +51 -0
  82. package/dist/core/permission/tool-policy.js +7 -7
  83. package/dist/core/role/runtime-policy.js +64 -0
  84. package/dist/core/session/session-manager.js +25 -5
  85. package/dist/core/system-session/memory.js +3 -18
  86. package/dist/ecweb/basic-settings-service.js +709 -0
  87. package/dist/ecweb/providers-service.js +172 -0
  88. package/dist/index.js +92 -4
  89. package/dist/ipc.js +78 -11
  90. package/dist/stats/db.js +33 -0
  91. package/dist/stats/index.js +2 -2
  92. package/dist/stats/query.js +246 -0
  93. package/dist/stats/writer.js +41 -1
  94. package/dist/trigger/application-service.js +12 -0
  95. package/dist/trigger/authorization.js +2 -2
  96. package/dist/trigger/history.js +36 -0
  97. package/dist/trigger/scheduler.js +3 -0
  98. package/dist/utils/ecweb-supervisor.js +31 -10
  99. package/dist/utils/error-utils.js +8 -4
  100. package/dist/utils/evolcore-version.js +3 -2
  101. package/dist/utils/host-info.js +52 -0
  102. package/dist/utils/welcome.js +18 -24
  103. package/ecagent/dist/harness/agent-harness.js +4 -1
  104. package/kits/docs/evolcore/INDEX.md +1 -1
  105. package/kits/docs/evolcore/agent.md +1 -1
  106. package/kits/docs/evolcore/aid.md +58 -8
  107. package/kits/docs/evolcore/config.md +9 -7
  108. package/kits/docs/evolcore/memory-architecture.md +3 -4
  109. package/kits/docs/evolcore/memory-consolidation.md +28 -7
  110. package/kits/docs/evolcore/memory-tags.md +1 -1
  111. package/kits/docs/evolcore/memory.md +1 -1
  112. package/kits/docs/evolcore/menu-client.md +1 -1
  113. package/kits/docs/evolcore/menu-protocol.md +6 -0
  114. package/kits/docs/evolcore/msg.md +1 -1
  115. package/kits/eck_manifest.json +3 -3
  116. package/kits/rules/04-relation.md +3 -2
  117. package/kits/schemas/agent-config.schema.13.json +5 -1
  118. package/kits/templates/memory-consolidation/generalize.md +32 -4
  119. package/kits/templates/memory-consolidation/notes.md +2 -2
  120. package/kits/templates/memory-consolidation/promote.md +10 -2
  121. package/kits/templates/system-fragments/baseagent.md +5 -0
  122. package/kits/templates/system-fragments/memory.md +12 -3
  123. package/kits/templates/system-fragments/session.md +1 -1
  124. package/kits/{rules/07-memory-and-thread.md → templates/system-fragments/thread.md} +17 -80
  125. package/package.json +3 -3
@@ -568,23 +568,18 @@ function setIfDefined(target, key, value) {
568
568
  if (value !== undefined)
569
569
  target[key] = value;
570
570
  }
571
- function truncateRawErrorForDisplay(raw) {
571
+ function rawErrorText(raw) {
572
572
  if (typeof raw !== 'string' || !raw.trim())
573
573
  return undefined;
574
574
  const text = raw.trim();
575
575
  const htmlStart = text.toLowerCase().indexOf('<html');
576
576
  return htmlStart >= 0 ? text.slice(0, htmlStart).trimEnd() : text;
577
577
  }
578
- function projectedRawError(raw, displayRaw) {
579
- if (typeof raw !== 'string')
580
- return raw;
581
- return raw.trim() ? displayRaw : raw;
582
- }
583
578
  function appendRawErrorText(text, raw) {
584
- const displayRaw = truncateRawErrorForDisplay(raw);
585
- if (!displayRaw || text.includes(displayRaw))
579
+ const rawText = rawErrorText(raw);
580
+ if (!rawText || text.includes(rawText))
586
581
  return text;
587
- return `${text}\n原因:${displayRaw}`;
582
+ return `${text}\n原因:${rawText}`;
588
583
  }
589
584
  /**
590
585
  * 把 AUNChannel 投递的 opts 映射成渠道无关的 InboundMessage。
@@ -4045,6 +4040,9 @@ export class AUNChannel {
4045
4040
  finalPayload.task_id = context.metadata.taskId;
4046
4041
  if (context?.metadata?.chatmode && !finalPayload.chatmode)
4047
4042
  finalPayload.chatmode = context.metadata.chatmode;
4043
+ // 结构化/通知类 payload 与文本路径共用同一份诊断字典;缺省不带,sentAt 由发送时刻盖戳。
4044
+ if (context?.debug)
4045
+ finalPayload.debug = { ...context.debug, sentAt: localTimestamp() };
4048
4046
  return finalPayload;
4049
4047
  }
4050
4048
  /**
@@ -4299,15 +4297,17 @@ export class AUNChannel {
4299
4297
  const now = Date.now();
4300
4298
  const mapTtl = typeof action.expiresAt === 'number' && Number.isFinite(action.expiresAt)
4301
4299
  ? Math.max(0, action.expiresAt - now)
4302
- : AUN_INTERACTION_CARD_TTL_MS;
4303
- const mapTimer = setTimeout(() => {
4304
- this.cardMessageIdMap.delete(messageId);
4305
- const ids = this.interactionCardMessageIds.get(action.requestId);
4306
- ids?.delete(messageId);
4307
- if (ids && ids.size === 0)
4308
- this.interactionCardMessageIds.delete(action.requestId);
4309
- }, mapTtl);
4310
- mapTimer.unref?.();
4300
+ : undefined;
4301
+ if (mapTtl !== undefined) {
4302
+ const mapTimer = setTimeout(() => {
4303
+ this.cardMessageIdMap.delete(messageId);
4304
+ const ids = this.interactionCardMessageIds.get(action.requestId);
4305
+ ids?.delete(messageId);
4306
+ if (ids && ids.size === 0)
4307
+ this.interactionCardMessageIds.delete(action.requestId);
4308
+ }, mapTtl);
4309
+ mapTimer.unref?.();
4310
+ }
4311
4311
  const ownedTimer = setTimeout(() => {
4312
4312
  this.ownedCardMsgIds.delete(messageId);
4313
4313
  this.ownedCardTopics.delete(messageId);
@@ -4492,6 +4492,7 @@ export class AUNChannel {
4492
4492
  channelId,
4493
4493
  delivery,
4494
4494
  dedupeKey: opts.dedupeKey,
4495
+ critical: opts.critical,
4495
4496
  type: 'payload',
4496
4497
  contentKind: opts.contentKind,
4497
4498
  payload: finalPayload,
@@ -4865,7 +4866,10 @@ export class AUNChannel {
4865
4866
  }).effectiveRole;
4866
4867
  }
4867
4868
  catch { /* 角色解析失败按未知处理 */ }
4868
- chatmode = chatmodeForPeerIdentity({ isMachine: info.type !== 'human', type: info.type ?? undefined }, peerRole);
4869
+ chatmode = chatmodeForPeerIdentity({ isMachine: info.type !== 'human', type: info.type ?? undefined }, peerRole, {
4870
+ self: this.config.aid,
4871
+ peerKey: formatPeerKey('aun', args.to),
4872
+ });
4869
4873
  }
4870
4874
  if (chatmode && !finalPayload.chatmode)
4871
4875
  finalPayload.chatmode = chatmode;
@@ -6604,26 +6608,36 @@ export class AUNChannelPlugin {
6604
6608
  }
6605
6609
  const taskBase = () => channel.buildTaskPayloadBase(envelope, replyCtx);
6606
6610
  switch (payload.kind) {
6607
- case 'result.text':
6608
- case 'command.result':
6609
6611
  case 'command.error': {
6612
+ const raw = rawErrorText(payload.raw) ?? payload.text;
6613
+ const errorPayload = {
6614
+ type: 'error',
6615
+ ...taskBase(),
6616
+ subtype: payload.kind,
6617
+ text: appendRawErrorText(payload.text, raw),
6618
+ message: payload.text,
6619
+ user_message: payload.text,
6620
+ raw,
6621
+ recoverable: true,
6622
+ };
6623
+ for (const key of ['format', 'reason', 'reasonCode', 'nextStep']) {
6624
+ setIfDefined(errorPayload, key, payload[key]);
6625
+ }
6626
+ await channel.sendReliableStructured(channelId, errorPayload, { ...(replyCtx ?? {}), suppressPeerMention: true }, payload.text);
6627
+ return;
6628
+ }
6629
+ case 'result.text':
6630
+ case 'command.result': {
6610
6631
  // Group command feedback is control traffic, not model input for
6611
6632
  // the other agents in the conversation.
6612
- if (delivery.chatType === 'group'
6613
- && (payload.kind === 'command.result' || payload.kind === 'command.error')) {
6614
- const isError = payload.kind === 'command.error';
6633
+ if (delivery.chatType === 'group' && payload.kind === 'command.result') {
6615
6634
  const feedback = {
6616
- type: isError ? 'error' : 'notice',
6635
+ type: 'notice',
6617
6636
  ...taskBase(),
6618
6637
  subtype: payload.kind,
6619
6638
  text: payload.text,
6620
- ...(isError
6621
- ? { message: payload.text, user_message: payload.text, recoverable: true }
6622
- : { severity: 'info' }),
6639
+ severity: 'info',
6623
6640
  };
6624
- for (const key of ['format', 'reason', 'reasonCode', 'nextStep']) {
6625
- setIfDefined(feedback, key, payload[key]);
6626
- }
6627
6641
  await channel.sendReliableStructured(channelId, feedback, { ...(replyCtx ?? {}), suppressPeerMention: true }, payload.text);
6628
6642
  return;
6629
6643
  }
@@ -6699,35 +6713,29 @@ export class AUNChannelPlugin {
6699
6713
  return;
6700
6714
  }
6701
6715
  case 'system.error': {
6702
- const raw = truncateRawErrorForDisplay(payload.raw);
6703
- const projectedRaw = projectedRawError(payload.raw, raw);
6716
+ const raw = rawErrorText(payload.raw) ?? payload.text;
6704
6717
  const errorPayload = {
6705
- type: payload.recoverable === true ? 'notice' : 'error',
6718
+ type: 'error',
6706
6719
  ...taskBase(),
6707
6720
  subtype: payload.subtype,
6708
- text: payload.recoverable === true
6709
- ? payload.text
6710
- : appendRawErrorText(payload.text, raw),
6711
- ...(projectedRaw !== undefined ? { raw: projectedRaw } : {}),
6721
+ text: appendRawErrorText(payload.text, raw),
6722
+ raw,
6712
6723
  ...(payload.reason !== undefined ? { reason: payload.reason } : {}),
6713
- ...(payload.recoverable === true
6714
- ? { severity: 'warn' }
6715
- : { recoverable: false }),
6724
+ ...(payload.recoverable !== undefined ? { recoverable: payload.recoverable } : {}),
6716
6725
  };
6717
6726
  await channel.sendReliableStructured(channelId, errorPayload, { ...(replyCtx ?? {}), suppressPeerMention: true }, payload.text);
6718
6727
  return;
6719
6728
  }
6720
6729
  case 'result.error': {
6721
- const raw = truncateRawErrorForDisplay(payload.raw);
6722
- const projectedRaw = projectedRawError(payload.raw, raw);
6730
+ const raw = rawErrorText(payload.raw) ?? payload.text;
6723
6731
  const errorPayload = {
6724
6732
  type: 'error',
6725
6733
  ...taskBase(),
6726
6734
  text: appendRawErrorText(payload.text, raw),
6735
+ raw,
6727
6736
  };
6728
6737
  setIfDefined(errorPayload, 'subtype', payload.subtype);
6729
6738
  setIfDefined(errorPayload, 'reason', payload.reason);
6730
- setIfDefined(errorPayload, 'raw', projectedRaw);
6731
6739
  await channel.sendReliableStructured(channelId, errorPayload, { ...(replyCtx ?? {}), suppressPeerMention: true }, payload.text);
6732
6740
  return;
6733
6741
  }
@@ -6859,9 +6867,10 @@ export class AUNChannelPlugin {
6859
6867
  case 'interaction': {
6860
6868
  const req = payload.interaction;
6861
6869
  const cardTtlMs = AUN_INTERACTION_CARD_TTL_MS;
6870
+ const indefiniteBlockingInteraction = req.blocking === true && req.expiresAt === undefined;
6862
6871
  const cardExpiresAt = typeof req.expiresAt === 'number' && Number.isFinite(req.expiresAt)
6863
6872
  ? req.expiresAt
6864
- : Date.now() + cardTtlMs;
6873
+ : indefiniteBlockingInteraction ? undefined : Date.now() + cardTtlMs;
6865
6874
  const toInteractionReceipt = (result) => {
6866
6875
  if ((result.status === undefined || result.status === 'sent') && result.messageId) {
6867
6876
  return sentReceipt(envelope, [result.messageId], replyCtx?.threadId);
@@ -6903,15 +6912,21 @@ export class AUNChannelPlugin {
6903
6912
  const aunCard = withAunAttention({
6904
6913
  type: 'action_card',
6905
6914
  title: action.title,
6915
+ ...(req.blocking === true ? { blocking: true } : {}),
6916
+ ...(action.recommendedOption ? { recommended_option: action.recommendedOption } : {}),
6906
6917
  actions: action.buttons.map(btn => ({
6907
- label: btn.label, value: btn.key, style: btn.style ?? 'default', behavior: 'reply',
6918
+ label: action.recommendedOption === btn.key ? `💡 ${btn.label}` : btn.label,
6919
+ value: btn.key, style: btn.style ?? 'default', behavior: 'reply',
6908
6920
  })),
6909
6921
  });
6910
6922
  // AUN action_card 以 compose behavior 声明自由文本动作。新版客户端
6911
6923
  // 在卡片内采集并确认;旧版客户端可继续从主编辑器采集。两者都结构化
6912
6924
  // 回传 action_card_reply{behavior:"compose"},Core 不拦截普通消息队列。
6913
6925
  if (action.allowCustomInput) {
6914
- aunCard.actions.push({ label: '✏️ 手动输入', style: 'default', behavior: 'compose', compose_timeout_ms: cardTtlMs });
6926
+ aunCard.actions.push({
6927
+ label: '✏️ 手动输入', style: 'default', behavior: 'compose',
6928
+ ...(cardExpiresAt !== undefined ? { compose_timeout_ms: Math.max(0, cardExpiresAt - Date.now()) } : {}),
6929
+ });
6915
6930
  }
6916
6931
  if (action.body) {
6917
6932
  aunCard.text = action.body;
@@ -6928,7 +6943,10 @@ export class AUNChannelPlugin {
6928
6943
  contentKind: 'card',
6929
6944
  context: cardReplyContext,
6930
6945
  dedupeKey: req.id.startsWith('gir-') || isAsyncQuestion ? operationId : undefined,
6931
- ttl: isAsyncQuestion ? Math.max(0, cardExpiresAt - Date.now()) : undefined,
6946
+ ...(isAsyncQuestion && cardExpiresAt !== undefined
6947
+ ? { ttl: Math.max(0, cardExpiresAt - Date.now()) }
6948
+ : {}),
6949
+ ...(indefiniteBlockingInteraction ? { critical: true } : {}),
6932
6950
  logText: action.title ? `[card] ${action.title}` : '[card]',
6933
6951
  postSend: {
6934
6952
  type: 'register_interaction_card',
@@ -88,7 +88,13 @@ export function buildDingtalkCardParamMap(interaction, state, status = '') {
88
88
  };
89
89
  for (let i = 0; i < DINGTALK_CARD_MAX_BUTTONS; i++) {
90
90
  const button = interaction.kind.buttons[i];
91
- map[`button_${i}_text`] = button?.label || '';
91
+ map[`button_${i}_text`] = button
92
+ ? (interaction.kind.kind === 'action'
93
+ && 'key' in button
94
+ && interaction.kind.recommendedOption === button.key
95
+ ? `💡 ${button.label}`
96
+ : button.label)
97
+ : '';
92
98
  map[`button_${i}_style`] = button?.style || 'default';
93
99
  map[`button_${i}_visible`] = button ? 'true' : 'false';
94
100
  map[`button_${i}_disabled`] = state !== 'pending' || !!(button && 'disabled' in button && button.disabled)
@@ -1005,7 +1011,7 @@ export class DingtalkChannel {
1005
1011
  continue;
1006
1012
  }
1007
1013
  const expired = (entry.interaction.expiresAt != null && entry.interaction.expiresAt <= now)
1008
- || entry.createdAt <= now - DINGTALK_CARD_PENDING_TTL_MS;
1014
+ || (entry.interaction.blocking !== true && entry.createdAt <= now - DINGTALK_CARD_PENDING_TTL_MS);
1009
1015
  if (!expired)
1010
1016
  continue;
1011
1017
  entry.invalidated = true;
@@ -1399,7 +1399,12 @@ export function buildCardV2(interaction, opts) {
1399
1399
  buttons.forEach((btn, idx) => {
1400
1400
  const buttonEl = {
1401
1401
  tag: 'button',
1402
- text: { tag: 'plain_text', content: btn.label },
1402
+ text: {
1403
+ tag: 'plain_text',
1404
+ content: kind.kind === 'action' && kind.recommendedOption === btn.key
1405
+ ? `💡 ${btn.label}`
1406
+ : btn.label,
1407
+ },
1403
1408
  type: btn.style === 'danger' ? 'danger' : btn.style === 'primary' ? 'primary' : 'default',
1404
1409
  action_type: 'form_submit',
1405
1410
  name: `btn_${idx}`,
@@ -257,8 +257,9 @@ export class PersistentWecomCardStore {
257
257
  let changed = false;
258
258
  for (const [taskId, record] of this.records) {
259
259
  const terminalExpiry = record.updatedAt + DEFAULT_CARD_RETENTION_MS;
260
- const pendingExpiry = (record.expiresAt ?? record.createdAt + 7 * DEFAULT_CARD_RETENTION_MS)
261
- + DEFAULT_CARD_RETENTION_MS;
260
+ const pendingExpiry = record.interaction.blocking === true
261
+ ? Number.POSITIVE_INFINITY
262
+ : (record.expiresAt ?? record.createdAt + 7 * DEFAULT_CARD_RETENTION_MS) + DEFAULT_CARD_RETENTION_MS;
262
263
  const expiresAt = record.status === 'pending' ? pendingExpiry : terminalExpiry;
263
264
  if (now > expiresAt) {
264
265
  this.records.delete(taskId);
@@ -322,7 +323,10 @@ export function buildWecomInteractionCard(interaction, taskId) {
322
323
  .filter(({ button }) => !('disabled' in button) || !button.disabled)
323
324
  .slice(0, 6)
324
325
  .map(({ button, index }) => ({
325
- text: truncate(button.label, 10) || `选项 ${index + 1}`,
326
+ text: truncate(interaction.kind.kind === 'action' && 'key' in button
327
+ && interaction.kind.recommendedOption === button.key
328
+ ? `💡 ${button.label}`
329
+ : button.label, 10) || `选项 ${index + 1}`,
326
330
  style: buttonStyle(button.style),
327
331
  key: interaction.kind.kind === 'command-card' ? `cmd_${index}` : `act_${index}`,
328
332
  })),
@@ -6,6 +6,7 @@ import { parse } from 'yaml';
6
6
  import { resolvePaths, aunPath as defaultAunPath, agentConfig, agentDataDir, agentPersonalDir, aidLocalDir, agentMdPath } from '../paths.js';
7
7
  import { loadDefaults, loadAgent, saveAgent, ensureAgentDirSkeleton } from '../config-store.js';
8
8
  import { ConfigTarget, write as cfgWrite, ensureFile as cfgEnsure, read as cfgRead } from '../config/config-manager.js';
9
+ import { daemonUnavailableMessage, queryDaemon } from './help.js';
9
10
  import { ipcQuery } from '../ipc.js';
10
11
  import { CONFIG_SCHEMA_VERSION } from '../types.js';
11
12
  import { ensureInitialRoleRegistry } from '../config/role-service.js';
@@ -880,8 +881,15 @@ export class AgentInitializationService {
880
881
  return;
881
882
  try {
882
883
  const record = readInitialization(aid);
883
- if (!record || record.status !== 'in_progress' || record.phase !== 'ownerpending'
884
- || this.running.has(aid))
884
+ if (!record || this.running.has(aid))
885
+ return;
886
+ const waitingForOwner = record.status === 'in_progress' && record.phase === 'ownerpending';
887
+ // Records written before ownerBindSubmitted existed could fail by resubmitting the bind
888
+ // while SDK ownership events invalidated the consensus cache. Retry them once the
889
+ // requested Owner is observable; other failures still require an explicit --resume.
890
+ const lostConsensusRace = record.status === 'failed' && record.code === 'CONSENSUS_REQUIRED'
891
+ && record.plan.ownerAid === ownerAid;
892
+ if (!waitingForOwner && !lostConsensusRace)
885
893
  return;
886
894
  this.resume(aid, record.actor);
887
895
  }
@@ -1051,8 +1059,20 @@ export class AgentInitializationService {
1051
1059
  }
1052
1060
  this.phase(r, 'hot_loading');
1053
1061
  await this.deps.hotLoad(p.aid);
1054
- if (this.deps.bindOwner)
1055
- await this.deps.bindOwner(p.aid, p.ownerAid);
1062
+ if (this.deps.bindOwner) {
1063
+ // Owner-consensus recovery and --resume re-run from the start. The SDK keeps emitting
1064
+ // ownership events for a few seconds after approval, each invalidating the cached
1065
+ // consensus, so resubmitting an accepted bind then fails with CONSENSUS_REQUIRED.
1066
+ // Retry when no pending or completed task for this Owner remains.
1067
+ const bindStillActive = this.deps.ownerBindActive
1068
+ ? this.deps.ownerBindActive(p.aid, p.ownerAid)
1069
+ : r.ownerBindSubmitted === true;
1070
+ if (!bindStillActive) {
1071
+ this.phase(r, 'binding_owner');
1072
+ await this.deps.bindOwner(p.aid, p.ownerAid);
1073
+ r.ownerBindSubmitted = true;
1074
+ }
1075
+ }
1056
1076
  this.phase(r, 'ownerpending');
1057
1077
  const ownership = await this.deps.checkOwner(p.aid, p.ownerAid);
1058
1078
  this.check(r);
@@ -1220,9 +1240,13 @@ export async function runInitializationCommand(args) {
1220
1240
  return initializationCommandResult(result);
1221
1241
  }
1222
1242
  const request = async (poll) => {
1223
- return await ipcQuery(resolvePaths().socket, {
1243
+ const { response, transportError } = await queryDaemon(resolvePaths().socket, {
1224
1244
  type: 'evolagent.initialize', argv, poll,
1225
- }, 10_000) ?? { ok: false, code: 'DAEMON_UNAVAILABLE', error: 'Initialization requires a running daemon' };
1245
+ }, 10_000);
1246
+ return response ?? {
1247
+ ok: false, code: 'DAEMON_UNAVAILABLE',
1248
+ error: daemonUnavailableMessage(transportError, 'Initialization requires a running daemon'),
1249
+ };
1226
1250
  };
1227
1251
  let result = await request(false);
1228
1252
  if (parsed.dryRun)
package/dist/cli/agent.js CHANGED
@@ -8,6 +8,7 @@ import { initConfigManager } from '../config/config-manager.js';
8
8
  import { resolveConfigCommand } from '../config/resolved-config-op.js';
9
9
  import { executeResolvedConfigCommandAsync } from '../config/config-operation-service.js';
10
10
  import { ipcQuery, IpcTransportError } from '../ipc.js';
11
+ import { daemonUnavailableMessage, queryDaemon } from './help.js';
11
12
  import { CONFIG_SCHEMA_VERSION } from '../types.js';
12
13
  import { ensureInitialRoleRegistry } from '../config/role-service.js';
13
14
  import { agentProjectRootFromDefaults, deriveAgentProjectPath } from '../utils/project-path.js';
@@ -30,22 +31,20 @@ export async function runManagedAgentOperation(argv, timeoutMs = 60_000) {
30
31
  if (!delegationToken) {
31
32
  return { ok: false, code: 'DELEGATION_REQUIRED', error: `${AGENT_DELEGATION_TOKEN_ENV} is not set for the active task` };
32
33
  }
33
- try {
34
- const response = await ipcQuery(resolvePaths().socket, {
35
- type: 'agent.op',
36
- argv: ['agent', ...argv],
37
- sessionId,
38
- delegationToken,
39
- }, timeoutMs);
40
- if (!response)
41
- return { ok: false, code: 'DAEMON_UNAVAILABLE', error: 'Unable to connect to the evolcore daemon' };
42
- if (!response.ok)
43
- return { ok: false, code: response.code, error: response.error || 'agent operation failed', ...(response.data ? { data: response.data } : {}) };
44
- return response.result ?? { ok: true, ...(response.data ?? {}) };
45
- }
46
- catch (error) {
47
- return { ok: false, code: 'DAEMON_UNAVAILABLE', error: error instanceof Error ? error.message : String(error) };
48
- }
34
+ const { response, transportError } = await queryDaemon(resolvePaths().socket, {
35
+ type: 'agent.op',
36
+ argv: ['agent', ...argv],
37
+ sessionId,
38
+ delegationToken,
39
+ }, timeoutMs);
40
+ if (!response)
41
+ return {
42
+ ok: false, code: 'DAEMON_UNAVAILABLE',
43
+ error: daemonUnavailableMessage(transportError, 'Unable to connect to the evolcore daemon'),
44
+ };
45
+ if (!response.ok)
46
+ return { ok: false, code: response.code, error: response.error || 'agent operation failed', ...(response.data ? { data: response.data } : {}) };
47
+ return response.result ?? { ok: true, ...(response.data ?? {}) };
49
48
  }
50
49
  function getAgentMdPath(aid) {
51
50
  return getAgentMdPathFromPaths(aid);
@@ -761,29 +760,23 @@ export async function agentReady(aid, commandArgs) {
761
760
  };
762
761
  }
763
762
  const p = resolvePaths();
764
- try {
765
- const delegationToken = managedSessionId ? readDelegationCredential() : undefined;
766
- if (managedSessionId && !delegationToken) {
767
- return { ok: false, code: 'DELEGATION_REQUIRED', error: `${AGENT_DELEGATION_TOKEN_ENV} is not set for the active task` };
768
- }
769
- const result = await ipcQuery(p.socket, {
770
- type: 'evolagent.bootstrapComplete',
771
- aid,
772
- ...(managedSessionId ? { argv: ['agent', ...(commandArgs ?? ['ready', aid])] } : {}),
773
- ...(managedSessionId ? { sessionId: managedSessionId, delegationToken } : {}),
774
- }, 30_000);
775
- if (!result)
776
- return { ok: false, code: 'DAEMON_UNAVAILABLE', error: 'Unable to connect to the EvolCore daemon; agent ready requires daemon IPC' };
777
- if (!result.ok) {
778
- return { ok: false, code: result.code, error: result.error || 'Failed to complete bootstrap' };
779
- }
780
- return { ...result, ok: true, aid, reloaded: result.reloaded === true };
763
+ const delegationToken = managedSessionId ? readDelegationCredential() : undefined;
764
+ if (managedSessionId && !delegationToken) {
765
+ return { ok: false, code: 'DELEGATION_REQUIRED', error: `${AGENT_DELEGATION_TOKEN_ENV} is not set for the active task` };
781
766
  }
782
- catch (e) {
767
+ const { response: result, transportError } = await queryDaemon(p.socket, {
768
+ type: 'evolagent.bootstrapComplete',
769
+ aid,
770
+ ...(managedSessionId ? { argv: ['agent', ...(commandArgs ?? ['ready', aid])] } : {}),
771
+ ...(managedSessionId ? { sessionId: managedSessionId, delegationToken } : {}),
772
+ }, 30_000);
773
+ if (!result)
783
774
  return {
784
- ok: false,
785
- code: 'DAEMON_UNAVAILABLE',
786
- error: e instanceof Error ? e.message : 'Unable to connect to the EvolCore daemon',
775
+ ok: false, code: 'DAEMON_UNAVAILABLE',
776
+ error: daemonUnavailableMessage(transportError, 'Unable to connect to the EvolCore daemon; agent ready requires daemon IPC'),
787
777
  };
778
+ if (!result.ok) {
779
+ return { ok: false, code: result.code, error: result.error || 'Failed to complete bootstrap' };
788
780
  }
781
+ return { ...result, ok: true, aid, reloaded: result.reloaded === true };
789
782
  }
@@ -2,9 +2,15 @@ import { ipcQuery } from '../ipc.js';
2
2
  import { resolvePaths } from '../paths.js';
3
3
  import { parseOwnershipCommand, ownershipFailure } from '../aun/aid/ownership-operation.js';
4
4
  import { readDelegationCredential } from '../core/auth/agent-delegation.js';
5
+ import { MAIN_CLI_BIN } from '../product.js';
5
6
  export async function cmdAidBind(args) {
6
7
  if (args.includes('--help') || args.includes('-h')) {
7
- console.log('用法: ec aid bind <ownerAid> [--subject <subjectAid>] [--format json]\nec aid bind status [--subject <subjectAid>] [--format json]');
8
+ console.log([
9
+ `用法: ${MAIN_CLI_BIN} aid bind <ownerAid> [--subject <subjectAid>] [--format json]`,
10
+ `${MAIN_CLI_BIN} aid bind status [--subject <subjectAid>] [--format json]`,
11
+ `${MAIN_CLI_BIN} aid bind accept <operationId> [--subject <subjectAid>] [--format json]`,
12
+ `${MAIN_CLI_BIN} aid bind reject <operationId> [--reason <text>] [--subject <subjectAid>] [--format json]`,
13
+ ].join('\n'));
8
14
  return;
9
15
  }
10
16
  const formatJson = args.includes('--format') && args[args.indexOf('--format') + 1] === 'json';
@@ -14,10 +20,11 @@ export async function cmdAidBind(args) {
14
20
  const delegationToken = sessionId ? readDelegationCredential() : undefined;
15
21
  if (sessionId && !delegationToken)
16
22
  throw Object.assign(new Error('Active task delegation is required'), { code: 'DELEGATION_REQUIRED' });
23
+ const isDecision = args[0] === 'accept' || args[0] === 'reject';
17
24
  const response = await ipcQuery(resolvePaths().socket, {
18
25
  type: 'aid.ownership', argv: ['aid', 'bind', ...args],
19
26
  ...(sessionId ? { sessionId, delegationToken } : {}),
20
- }, 30_000);
27
+ }, isDecision ? 150_000 : 30_000);
21
28
  if (!response?.ok)
22
29
  throw Object.assign(new Error(response?.error ?? 'daemon 未运行或 IPC 不可用'), { code: response?.code, retryable: response?.retryable });
23
30
  if (formatJson)
@@ -32,6 +39,13 @@ export async function cmdAidBind(args) {
32
39
  console.log(`运行态同步: ${state.synchronization.status}`);
33
40
  if (state.task)
34
41
  console.log(`绑定任务: ${state.task.operation_id} (${state.task.status}/${state.task.step})`);
42
+ if (state.decisionTask)
43
+ console.log(`审批任务: ${state.decisionTask.operation_id} (${state.decisionTask.status}/${state.decisionTask.step})`);
44
+ if (state.decisionConsensus)
45
+ console.log(`审批目标共识: ${state.decisionConsensus.status} (owner=${state.decisionConsensus.owner_aid ?? '-'}, version=${state.decisionConsensus.relation_version}, lock=${state.decisionConsensus.lock})`);
46
+ for (const request of state.requests ?? []) {
47
+ console.log(`待确认邀约: ${request.operation_id} (sub=${request.sub_aid}, owner=${request.owner_aid}, 本端=${request.local_role})`);
48
+ }
35
49
  if (state.requestedOwnerAid)
36
50
  console.log(`请求绑定: ${state.requestedOwnerAid}`);
37
51
  if (state.requestStatus)