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
@@ -136,24 +136,27 @@ function openAiMessages(context) {
136
136
  continue;
137
137
  }
138
138
  if (message.role === 'assistant') {
139
- const text = message.content
139
+ const content = Array.isArray(message.content) ? message.content : [];
140
+ const text = content
140
141
  .filter((block) => block.type === 'text')
141
142
  .map(block => block.text)
142
143
  .join('');
143
- const thinking = message.content
144
+ const thinking = content
144
145
  .filter((block) => block.type === 'thinking')
145
146
  .map((block) => block.thinking)
146
147
  .join('');
147
- const toolCalls = message.content
148
+ const toolCalls = content
148
149
  .filter((block) => block.type === 'toolCall')
149
150
  .map((block) => ({
150
151
  id: block.id,
151
152
  type: 'function',
152
153
  function: { name: block.name, arguments: JSON.stringify(block.arguments ?? {}) },
153
154
  }));
155
+ if (!text && !thinking && toolCalls.length === 0)
156
+ continue;
154
157
  messages.push({
155
158
  role: 'assistant',
156
- content: text || null,
159
+ content: text || [],
157
160
  ...(thinking ? { reasoning_content: thinking } : {}),
158
161
  ...(toolCalls.length > 0 ? { tool_calls: toolCalls } : {}),
159
162
  });
@@ -1383,15 +1386,23 @@ export class EcagentRunner {
1383
1386
  harness?.abort(),
1384
1387
  ...runs.map(run => run.done),
1385
1388
  ]), 'ecagent');
1389
+ for (const run of runs) {
1390
+ if (run.cancelled)
1391
+ run.interruptError = undefined;
1392
+ }
1386
1393
  }
1387
1394
  catch (error) {
1395
+ for (const run of runs) {
1396
+ if (run.cancelled && run.interruptError === undefined)
1397
+ run.interruptError = error;
1398
+ }
1388
1399
  logger.warn(`[EcagentRunner] Interrupted execution still requires quiescence: session=${sessionKey} error=${String(error)}`);
1389
1400
  }
1390
1401
  }
1391
1402
  assertInterruptSettled(sessionId) {
1392
- if ([...(this.sessionRuns.get(sessionId) ?? [])].some(run => run.cancelled)) {
1393
- throw unsettledInterruptError('ecagent');
1394
- }
1403
+ const unsettled = [...(this.sessionRuns.get(sessionId) ?? [])].find(run => run.cancelled);
1404
+ if (unsettled)
1405
+ throw unsettledInterruptError('ecagent', unsettled.interruptError);
1395
1406
  }
1396
1407
  injectUserMessage(sessionId, text) {
1397
1408
  const harness = this.runtimes.get(sessionId)?.harness;
@@ -760,8 +760,7 @@ export class GeminiRunner {
760
760
  if (context?.approvalInteractionPolicy !== 'deny')
761
761
  return;
762
762
  if (isGeminiUnattendedInteraction(toolName)) {
763
- logger.info(`[GeminiRunner] interaction.auto_resolved kind=${normalizeGeminiToolName(toolName)} session=${sessionId}`);
764
- return;
763
+ throw new Error('无人值守任务不能自动回答用户问题');
765
764
  }
766
765
  const lifecycleRequestId = requestId ?? `gemini-approval-${crypto.randomUUID()}`;
767
766
  const auditSummary = `${toolName} runtime authorization denied by Gemini headless policy`;
@@ -34,7 +34,23 @@ export function parseAsyncUserQuestions(value) {
34
34
  || question.options.length > 20 || question.options.some((option) => typeof option !== 'string' || !option.trim() || option.length > 4_096))) {
35
35
  throw new Error('Invalid asynchronous question options');
36
36
  }
37
- return { title: question.title, ...(question.options ? { options: [...question.options] } : {}) };
37
+ if (question.blocking != null && typeof question.blocking !== 'boolean') {
38
+ throw new Error('Invalid asynchronous question blocking flag');
39
+ }
40
+ if (question.recommendedOption != null
41
+ && (typeof question.recommendedOption !== 'string' || !question.recommendedOption.trim())) {
42
+ throw new Error('Invalid asynchronous question recommended option');
43
+ }
44
+ if (question.recommendedOption != null && question.options
45
+ && !question.options.includes(question.recommendedOption)) {
46
+ throw new Error('Asynchronous question recommended option is not in options');
47
+ }
48
+ return {
49
+ title: question.title,
50
+ ...(question.options ? { options: [...question.options] } : {}),
51
+ blocking: question.blocking !== false,
52
+ ...(question.recommendedOption ? { recommendedOption: question.recommendedOption } : {}),
53
+ };
38
54
  });
39
55
  }
40
56
  export function hasTrustedFullAccessContext(context) {
@@ -44,10 +60,13 @@ export function hasTrustedFullAccessContext(context) {
44
60
  || authorization.processRole !== 'fullaccess-run'
45
61
  || authorization.dataScope !== 'daemon')
46
62
  return false;
47
- if (authorization.source === 'fullaccess-command' || authorization.source === 'agent-policy') {
63
+ if (authorization.source === 'fullaccess-command' || authorization.source === 'agent-policy' || authorization.source === 'session-policy') {
48
64
  return typeof authorization.authorizedBy === 'string' && authorization.authorizedBy.length > 0
49
- && (authorization.source !== 'agent-policy'
50
- || (typeof authorization.agentAid === 'string' && authorization.agentAid.length > 0));
65
+ && (authorization.source === 'fullaccess-command'
66
+ || (typeof authorization.agentAid === 'string'
67
+ && authorization.agentAid.length > 0
68
+ && (authorization.source !== 'session-policy'
69
+ || (typeof authorization.sessionId === 'string' && authorization.sessionId.length > 0))));
51
70
  }
52
71
  return authorization.source === 'trigger'
53
72
  && typeof authorization.authorizedBy === 'string' && authorization.authorizedBy.length > 0
@@ -75,10 +94,13 @@ export async function withInterruptDeadline(operation, label, timeoutMs = 5_000)
75
94
  clearTimeout(timer);
76
95
  }
77
96
  }
78
- export function unsettledInterruptError(runner) {
79
- return Object.assign(new Error(`${runner} 的上一次执行尚未确认停止,本次任务未启动;旧执行结束后可在原会话继续。`), {
97
+ export function unsettledInterruptError(runner, cause) {
98
+ const error = Object.assign(new Error(`${runner} 的上一次执行尚未确认停止,本次任务未启动;旧执行结束后可在原会话继续。`), {
80
99
  code: 'RUNNER_INTERRUPT_UNCONFIRMED',
81
100
  });
101
+ if (cause !== undefined)
102
+ error.cause = cause;
103
+ return error;
82
104
  }
83
105
  // ── 类型守卫 ──
84
106
  export function hasModelSwitcher(agent) {
@@ -538,6 +538,27 @@ function verifyLocal(store, aid, content) {
538
538
  return { status: 'invalid', reason: `certificate not available: ${String(e?.message || e).slice(0, 100)}` };
539
539
  }
540
540
  }
541
+ /** Verify a locally stored agent.md without contacting the AUN gateway. */
542
+ export async function verifyLocalAgentMd(aid, opts = {}) {
543
+ const aunPath = resolveAgentMdAunPath(opts);
544
+ const localPath = agentMdPath(aid, aunPath);
545
+ if (!fs.existsSync(localPath))
546
+ return { status: 'missing' };
547
+ const store = opts.store ?? await getAidStore({ slotId: SLOT.cli, aunPath });
548
+ const ownStore = !opts.store;
549
+ try {
550
+ const content = fs.readFileSync(localPath, 'utf-8');
551
+ const result = verifyLocal(store, aid, content);
552
+ return { status: result.status, ...(result.reason ? { reason: result.reason } : {}) };
553
+ }
554
+ finally {
555
+ if (ownStore)
556
+ try {
557
+ store.close();
558
+ }
559
+ catch { /* ignore */ }
560
+ }
561
+ }
541
562
  export async function agentmdGet(aid, opts) {
542
563
  const aunPath = resolveAgentMdAunPath(opts);
543
564
  const store = opts?.store ?? await getAidStore({ slotId: SLOT.cli, aunPath });
@@ -39,7 +39,7 @@ export function suppressSdkLogs() {
39
39
  };
40
40
  }
41
41
  // ==================== SDK package ====================
42
- export const MIN_AUN_CORE_SDK = [0, 5, 22];
42
+ export const MIN_AUN_CORE_SDK = [0, 5, 25];
43
43
  export const AUN_CORE_SDK_PKG = '@agentunion/fastaun';
44
44
  function compareVersion(a, min) {
45
45
  const parts = a.split('.').map(n => parseInt(n, 10));
@@ -8,7 +8,6 @@ import { getAidStore, loadClient, SLOT } from './store.js';
8
8
  import { LocalTokenStore, validateAgentMdDocument, verifyAgentMdUpload, } from '@agentunion/fastaun';
9
9
  import { AUN_KEYSTORE_ENCRYPTION_SEED } from './encryption-seed-policy.js';
10
10
  import { isValidAid } from './validation.js';
11
- import { OwnerResolver } from '../../core/ownership/owner-resolver.js';
12
11
  import { agentMdPath, resolvePaths } from '../../paths.js';
13
12
  import { PeerIdentityCache } from '../../core/relation/peer-identity.js';
14
13
  import { agentmdPut, buildInitialAgentMd, invalidateAgentDisplayName, stripAgentMdSignature } from './agentmd.js';
@@ -331,7 +330,6 @@ export async function submitControlAidRegistration(aid, owner, options = {}) {
331
330
  let connecting = false;
332
331
  let sentOperationIds = new Set();
333
332
  let invitationObserver;
334
- const resolver = new OwnerResolver();
335
333
  async function bounded(operation, timeoutMs = deadline - Date.now()) {
336
334
  let timer;
337
335
  try {
@@ -346,27 +344,6 @@ export async function submitControlAidRegistration(aid, owner, options = {}) {
346
344
  clearTimeout(timer);
347
345
  }
348
346
  }
349
- function readTask() {
350
- const raw = records.loadOwnershipTask(aid, operationId)?.task;
351
- if (!raw)
352
- return;
353
- if (raw.operation_id !== operationId || raw.operation !== 'bind' || raw.sub_aid !== aid
354
- || raw.owner_aid !== ownerAid || raw.initiator_aid !== aid || raw.new_owner_aid !== undefined
355
- || !['pending', 'completed', 'rejected', 'stopped', 'error'].includes(String(raw.status))
356
- || typeof raw.step !== 'string' || !Number.isSafeInteger(raw.base_relation_version)
357
- || raw.target_relation_version !== Number(raw.base_relation_version) + 1) {
358
- throw Object.assign(new Error('SDK registration task does not match its original intent'), { code: 'TASK_MISMATCH' });
359
- }
360
- task = {
361
- operation_id: operationId, operation: 'bind', sub_aid: aid, owner_aid: ownerAid,
362
- base_relation_version: raw.base_relation_version,
363
- target_relation_version: raw.target_relation_version,
364
- status: raw.status, step: raw.step,
365
- ...(typeof raw.code === 'string' ? { code: raw.code } : {}),
366
- ...(typeof raw.reason === 'string' ? { reason: raw.reason } : {}),
367
- ...(typeof raw.retryable === 'boolean' ? { retryable: raw.retryable } : {}),
368
- };
369
- }
370
347
  function result(status, error) {
371
348
  return { status, ...(operationId ? { operationId } : {}), ...(task ? { task } : {}), ...(error ? { error } : {}) };
372
349
  }
@@ -374,10 +351,8 @@ export async function submitControlAidRegistration(aid, owner, options = {}) {
374
351
  records = new LocalTokenStore(resolvePaths().root, { encryptionSeed: AUN_KEYSTORE_ENCRYPTION_SEED });
375
352
  const intent = records.loadMetadata(aid)?.registration_intent;
376
353
  const registrationIntentOwner = typeof intent?.owner === 'string' ? intent.owner.toLowerCase() : undefined;
377
- // Preserve an existing SDK registration task and let the same SDK client
378
- // resume it during connect before OwnerResolver checks/binds the subject.
379
354
  if (intent && Object.prototype.hasOwnProperty.call(intent, 'owner')) {
380
- if (!registrationIntentOwner) {
355
+ if (!registrationIntentOwner || !isValidAid(registrationIntentOwner)) {
381
356
  return result('unknown', {
382
357
  code: 'REGISTRATION_INTENT_PRESENT',
383
358
  message: 'Control AID has an invalid SDK registration Owner intent',
@@ -386,93 +361,44 @@ export async function submitControlAidRegistration(aid, owner, options = {}) {
386
361
  if (registrationIntentOwner !== ownerAid) {
387
362
  return result('failed', { code: 'OWNER_INTENT_CONFLICT', message: 'Control AID was registered for a different Owner' });
388
363
  }
389
- if (typeof intent.operation_id === 'string' && REGISTRATION_OPERATION_ID.test(intent.operation_id)) {
390
- operationId = intent.operation_id;
391
- readTask();
392
- }
393
- if (!operationId) {
394
- return result('unknown', {
395
- code: 'REGISTRATION_INTENT_PRESENT',
396
- message: 'Control AID has an SDK registration intent without a valid operation ID',
397
- });
398
- }
399
364
  }
400
365
  if (options.agentMdError) {
401
366
  return result('failed', { code: 'CONTROL_AGENTMD_NOT_READY', message: options.agentMdError });
402
367
  }
403
- store = await bounded(getAidStore({ slotId: SLOT.cli }));
404
- const ownershipTasks = records.listOwnershipTasks(aid).map(snapshot => snapshot.task);
405
- const conflictingTask = ownershipTasks.find(candidate => candidate.sub_aid === aid
406
- && (candidate.status === 'pending' || candidate.status === 'error' && candidate.retryable === true)
407
- && !(candidate.operation === 'bind' && candidate.owner_aid === ownerAid
408
- && candidate.initiator_aid === aid && candidate.new_owner_aid === undefined));
409
- if (conflictingTask) {
410
- return result('failed', { code: 'BIND_CONFLICT', message: 'A different active ownership task must be resolved first' });
411
- }
368
+ store = await bounded(getAidStore({ slotId: `ownership-${crypto.randomUUID()}` }));
412
369
  client = await bounded(loadClient(store, aid));
413
370
  connecting = true;
414
371
  sentOperationIds = new Set();
415
372
  invitationObserver = observeInvitationSend(client, ownerAid, sentOperationIds);
416
373
  await bounded(client.connect({
417
- connection_kind: 'long', auto_reconnect: false, background_sync: false,
374
+ connection_kind: 'short', short_ttl_ms: 30_000, auto_reconnect: false, background_sync: false,
418
375
  connect_timeout: 10, call_timeout: 10,
419
376
  }));
420
- if (registrationIntentOwner === ownerAid && operationId) {
421
- readTask();
422
- if ((!task || task.status === 'pending') && !sentOperationIds.has(operationId)) {
423
- await bounded(invitationObserver.waitFor(operationId));
424
- readTask();
377
+ task = await bounded(client.bindOwneraid(ownerAid));
378
+ if (task.operation !== 'bind' || task.sub_aid !== aid || task.owner_aid !== ownerAid
379
+ || task.new_owner_aid !== undefined || !REGISTRATION_OPERATION_ID.test(task.operation_id)
380
+ || !Number.isSafeInteger(task.base_relation_version)
381
+ || task.target_relation_version !== task.base_relation_version + 1) {
382
+ throw Object.assign(new Error('SDK returned a binding result for a different subject or Owner'), { code: 'TASK_MISMATCH' });
383
+ }
384
+ operationId = task.operation_id;
385
+ if (task.status === 'completed') {
386
+ let consensus;
387
+ try {
388
+ consensus = await bounded(client.checkOwnershipConsensus(aid));
425
389
  }
426
- if (!task) {
390
+ catch (error) {
427
391
  return result('unknown', {
428
- code: 'BIND_TASK_NOT_PERSISTED',
429
- message: 'SDK did not expose the recovered registration task after connect',
430
- });
431
- }
432
- if (task?.status === 'completed') {
433
- const consensus = await bounded(client.checkOwnershipConsensus(aid));
434
- if (consensus.status === 'consensus' && consensus.sub_aid === aid && consensus.owner_aid === ownerAid) {
435
- return result('already_bound');
436
- }
437
- return result('failed', {
438
- code: 'CONSENSUS_REQUIRED',
439
- message: 'A completed binding task has no current verified Owner consensus',
392
+ code: 'BIND_SUBMIT_UNKNOWN',
393
+ message: error instanceof Error ? error.message : String(error),
440
394
  });
441
395
  }
442
- if (task && (['rejected', 'stopped', 'error'].includes(task.status) || task.code)) {
443
- return result('failed', { code: task.code ?? 'BIND_SUBMIT_FAILED', message: task.reason ?? `Binding task ${task.status}` });
444
- }
445
- if (sentOperationIds.has(operationId))
446
- return result('submitted');
447
- return result('unknown', {
448
- code: 'BIND_SUBMIT_UNKNOWN',
449
- message: 'SDK is recovering the original registration task; no matching message.send acknowledgement was observed',
450
- });
451
- }
452
- // Reuse the same service as agent-create, including its fresh consensus
453
- // check, explicit SDK bind call, and persistent task projection.
454
- resolver.attachClient(aid, client, true);
455
- const view = await bounded(resolver.bind(aid, ownerAid));
456
- if (view.consensusStatus === 'consensus' && view.ownerAid === ownerAid) {
457
- return result('already_bound');
458
- }
459
- const returnedTask = view.task;
460
- if (!returnedTask || returnedTask.operation !== 'bind' || returnedTask.sub_aid !== aid
461
- || returnedTask.owner_aid !== ownerAid || returnedTask.new_owner_aid !== undefined
462
- || typeof returnedTask.operation_id !== 'string' || !REGISTRATION_OPERATION_ID.test(returnedTask.operation_id)) {
463
- throw Object.assign(new Error('SDK returned a binding task for a different subject or Owner'), { code: 'TASK_MISMATCH' });
464
- }
465
- operationId = returnedTask.operation_id;
466
- readTask();
467
- if (!task) {
468
- throw Object.assign(new Error('SDK binding task was not persisted'), { code: 'BIND_TASK_NOT_PERSISTED' });
469
- }
470
- if (task.status === 'completed') {
471
- const consensus = await bounded(client.checkOwnershipConsensus(aid));
472
396
  if (consensus.status === 'consensus' && consensus.sub_aid === aid && consensus.owner_aid === ownerAid) {
473
397
  return result('already_bound');
474
398
  }
475
- return result('failed', { code: 'CONSENSUS_REQUIRED', message: 'Completed binding has no current verified Owner consensus' });
399
+ if (sentOperationIds.has(operationId))
400
+ return result('submitted');
401
+ return result('unknown', { code: 'BIND_SUBMIT_UNKNOWN', message: 'SDK completed the local bind without a message.send acknowledgement' });
476
402
  }
477
403
  if (['rejected', 'stopped', 'error'].includes(task.status)) {
478
404
  return result('failed', { code: task.code ?? 'BIND_SUBMIT_FAILED', message: task.reason ?? `Binding task ${task.status}` });
@@ -489,15 +415,14 @@ export async function submitControlAidRegistration(aid, owner, options = {}) {
489
415
  const details = error;
490
416
  const code = typeof details.stringCode === 'string' ? details.stringCode
491
417
  : typeof details.code === 'string' ? details.code : undefined;
492
- const uncertain = connecting && (code === 'BIND_SUBMIT_TIMEOUT' || !code
493
- || resolver.snapshot(aid).requestStatus === 'interrupted');
418
+ operationId ??= sentOperationIds.values().next().value;
419
+ const uncertain = connecting && (code === 'BIND_SUBMIT_TIMEOUT' || !code || !!operationId);
494
420
  return result(uncertain ? 'unknown' : 'failed', {
495
421
  code: uncertain ? 'BIND_SUBMIT_UNKNOWN' : code ?? 'BIND_SUBMIT_FAILED',
496
422
  message: details.message ?? String(error),
497
423
  });
498
424
  }
499
425
  finally {
500
- resolver.stop();
501
426
  invitationObserver?.restore();
502
427
  if (client) {
503
428
  try {
@@ -1,6 +1,8 @@
1
1
  import { isValidAid } from './validation.js';
2
2
  import { ownerResolver, OwnershipError } from '../../core/ownership/owner-resolver.js';
3
3
  import { logger } from '../../utils/logger.js';
4
+ import { MAIN_CLI_BIN } from '../../product.js';
5
+ const OWNERSHIP_OPERATION_ID = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
4
6
  export function ownershipFailure(error) {
5
7
  const details = error && typeof error === 'object' ? error : {};
6
8
  return {
@@ -14,34 +16,55 @@ export function ownershipFailure(error) {
14
16
  export function parseOwnershipCommand(argv) {
15
17
  const args = argv[0] === 'aid' ? argv.slice(1) : [...argv];
16
18
  if (args[0] !== 'bind' || !args[1]) {
17
- throw new OwnershipError('INVALID_ARGUMENT', 'Use ec aid bind <ownerAid> or ec aid bind status');
19
+ throw new OwnershipError('INVALID_ARGUMENT', `Use ${MAIN_CLI_BIN} aid bind <ownerAid>, status, accept, or reject`);
18
20
  }
19
- const action = args[1] === 'status' ? 'status' : 'bind';
21
+ const action = ['status', 'accept', 'reject'].includes(args[1]) ? args[1] : 'bind';
20
22
  if (action === 'bind' && !isValidAid(args[1]))
21
23
  throw new OwnershipError('INVALID_AID', 'A valid owner AID is required');
24
+ if ((action === 'accept' || action === 'reject') && !OWNERSHIP_OPERATION_ID.test(args[2] ?? '')) {
25
+ throw new OwnershipError('INVALID_OPERATION', 'A valid ownership operation ID is required');
26
+ }
22
27
  let subjectAid;
28
+ let reason;
23
29
  let formatSeen = false;
24
- for (let index = 2; index < args.length; index += 2) {
25
- if (args[index] === '--subject' && !subjectAid && isValidAid(args[index + 1] ?? ''))
26
- subjectAid = args[index + 1];
27
- else if (args[index] === '--format' && args[index + 1] === 'json' && !formatSeen)
30
+ const positionalEnd = action === 'bind' || action === 'status' ? 2 : 3;
31
+ for (let index = positionalEnd; index < args.length; index += 2) {
32
+ const flag = args[index];
33
+ const value = args[index + 1];
34
+ if (flag === '--subject' && !subjectAid && isValidAid(value ?? '')) {
35
+ subjectAid = value;
36
+ }
37
+ else if (flag === '--format' && value === 'json' && !formatSeen) {
28
38
  formatSeen = true;
29
- else
30
- throw new OwnershipError('INVALID_ARGUMENT', 'Only one --subject <subjectAid> and --format json are supported');
39
+ }
40
+ else if (flag === '--reason' && action === 'reject' && reason === undefined
41
+ && value !== undefined && !value.startsWith('--')) {
42
+ reason = value;
43
+ }
44
+ else {
45
+ throw new OwnershipError('INVALID_ARGUMENT', 'Supported options are --subject <subjectAid>, --reason <text>, and --format json');
46
+ }
31
47
  }
32
- return action === 'status' ? { action, subjectAid } : { action, subjectAid, ownerAid: args[1] };
48
+ if (action === 'status')
49
+ return { action, subjectAid };
50
+ if (action === 'accept')
51
+ return { action, operationId: args[2], subjectAid };
52
+ if (action === 'reject')
53
+ return { action, operationId: args[2], ...(reason !== undefined ? { reason } : {}), subjectAid };
54
+ return { action, subjectAid, ownerAid: args[1] };
33
55
  }
34
56
  export async function executeOwnershipCommand(command, defaultSubjectAid) {
35
57
  const subjectAid = command.subjectAid ?? defaultSubjectAid;
36
58
  if (!subjectAid)
37
59
  throw new OwnershipError('SUBJECT_REQUIRED', 'No local subject AID is available');
38
60
  if (command.action === 'status') {
39
- ownerResolver.assertLocalSubject(subjectAid);
40
- return ownerResolver.refresh(subjectAid);
61
+ return ownerResolver.status(subjectAid);
62
+ }
63
+ if (command.action === 'accept' || command.action === 'reject') {
64
+ return ownerResolver.decide(subjectAid, command.operationId, command.action, command.action === 'reject' ? command.reason : undefined);
41
65
  }
42
66
  logger.info(`[ownership] bind requested subject=${subjectAid} owner=${command.ownerAid}`);
43
67
  try {
44
- ownerResolver.assertLocalSubject(subjectAid);
45
68
  const result = await ownerResolver.bind(subjectAid, command.ownerAid);
46
69
  const task = result.task;
47
70
  const log = task && ['rejected', 'stopped', 'error'].includes(task.status) ? logger.warn : logger.info;
@@ -372,6 +372,12 @@ export class GroupInviteReviewService {
372
372
  if (!runtime)
373
373
  return Promise.reject(new Error('邀请审核服务未就绪'));
374
374
  const task = this.serial(selfAid, async () => {
375
+ if (!this.active(runtime)) {
376
+ runtime.ready = false;
377
+ runtime.failures = 0;
378
+ runtime.nextSync = Date.now() + 15 * 60_000;
379
+ return;
380
+ }
375
381
  try {
376
382
  await this.sync(runtime);
377
383
  runtime.failures = 0;
@@ -10,6 +10,7 @@ import { resolvePaths } from '../../paths.js';
10
10
  import { ipcQuery } from '../../ipc.js';
11
11
  import { readDelegationCredential } from '../../core/auth/agent-delegation.js';
12
12
  import { loadDaemonConfig } from '../../config-store.js';
13
+ import { formatPeerKey } from '../../core/relation/peer-identity.js';
13
14
  import { normalizeAunMentionEntries, withAunAttention } from './mention-schema.js';
14
15
  import { isTopicThreadId } from '../../core/thread/topic-id.js';
15
16
  const DAEMON_SEND_TIMEOUT_GRACE_MS = 10_000;
@@ -60,9 +61,6 @@ function buildSimplePayload(body) {
60
61
  }
61
62
  }
62
63
  }
63
- function isRawPayloadBody(body) {
64
- return body.mode === 'payload';
65
- }
66
64
  function messageLogContent(body) {
67
65
  return body.mode === 'text' ? body.text
68
66
  : body.mode === 'link' ? `[link] ${body.url}`
@@ -134,7 +132,7 @@ async function tryDaemonMsgSend(args, payload, runtimeContext, file) {
134
132
  if (args.slotId || args.aunPath) {
135
133
  return { ok: false, code: 'INVALID_DELEGATION', error: 'Task sends cannot override the daemon AUN connection' };
136
134
  }
137
- const thread = args.body.mode === 'payload' ? undefined : msgSendThreadId(args, payload, runtimeContext);
135
+ const thread = msgSendThreadId(args, payload, runtimeContext);
138
136
  const response = await ipcQuery(resolvePaths().socket, {
139
137
  type: 'aun-msg-send',
140
138
  aid: args.from,
@@ -254,8 +252,7 @@ export async function msgSend(args) {
254
252
  if (args.body.mode !== 'payload' && Object.prototype.hasOwnProperty.call(payload, 'mentions')) {
255
253
  payload.mentions = normalizeAunMentionEntries(payload.mentions);
256
254
  }
257
- if (!isRawPayloadBody(args.body))
258
- applyRoutingPayloadFields(payload, args, runtimeContext, false);
255
+ applyRoutingPayloadFields(payload, args, runtimeContext, false);
259
256
  const daemonResult = await tryDaemonMsgSend(args, payload, runtimeContext);
260
257
  if (daemonResult)
261
258
  return daemonMsgSendResult(args, payload, runtimeContext, daemonResult);
@@ -327,7 +324,10 @@ export async function msgSend(args) {
327
324
  }).effectiveRole;
328
325
  }
329
326
  catch { /* 角色解析失败时按未知处理(proactive) */ }
330
- const chatmode = chatmodeForPeerIdentity(peerIdentity, peerRole);
327
+ const chatmode = chatmodeForPeerIdentity(peerIdentity, peerRole, {
328
+ self: args.from,
329
+ peerKey: formatPeerKey('aun', args.to),
330
+ });
331
331
  // 3. 构建 payload
332
332
  if (!payload) {
333
333
  payload = buildSimplePayload(args.body);
@@ -336,11 +336,7 @@ export async function msgSend(args) {
336
336
  if (args.body.mode !== 'payload')
337
337
  payload.chatmode = chatmode;
338
338
  // 5. 写入 payload.thread_id/ref_message_id(如果指定)
339
- if (args.body.mode !== 'payload') {
340
- if (!isRawPayloadBody(args.body))
341
- applyRoutingPayloadFields(payload, args, runtimeContext, false);
342
- applyRoutingPayloadFields(payload, args, runtimeContext);
343
- }
339
+ applyRoutingPayloadFields(payload, args, runtimeContext, args.body.mode !== 'payload');
344
340
  if (args.body.mode !== 'payload' && Object.prototype.hasOwnProperty.call(payload, 'mentions')) {
345
341
  payload.mentions = normalizeAunMentionEntries(payload.mentions);
346
342
  }