evolcore 0.0.35 → 0.0.37

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 (224) hide show
  1. package/CHANGELOG.md +42 -0
  2. package/README.md +6 -0
  3. package/bin/install-codex-managed-hooks.mjs +20 -3
  4. package/dist/agents/baseagent-availability.js +1 -1
  5. package/dist/agents/baseagent.js +88 -0
  6. package/dist/agents/claude-runner.js +59 -12
  7. package/dist/agents/codex/app-server-client.js +18 -1
  8. package/dist/agents/codex-runner.js +262 -21
  9. package/dist/agents/ecagent-runner.js +47 -11
  10. package/dist/agents/gemini-runner.js +36 -12
  11. package/dist/aun/aid/client.js +4 -3
  12. package/dist/aun/aid/control-aid.js +270 -8
  13. package/dist/aun/aid/identity.js +6 -3
  14. package/dist/aun/aid/ownership-operation.js +58 -0
  15. package/dist/aun/aid/store.js +1 -0
  16. package/dist/aun/group/fs-download.js +67 -2
  17. package/dist/aun/group/history.js +20 -5
  18. package/dist/aun/group/operations.js +8 -2
  19. package/dist/aun/menu/client.js +39 -2
  20. package/dist/aun/menu/transport.js +1 -1
  21. package/dist/aun/msg/history.js +43 -4
  22. package/dist/aun/msg/managed-operation.js +30 -53
  23. package/dist/aun/msg/p2p.js +51 -15
  24. package/dist/aun/outbox.js +4 -0
  25. package/dist/channels/aun.js +260 -73
  26. package/dist/channels/daemon.js +6 -4
  27. package/dist/channels/feishu.js +3 -0
  28. package/dist/cli/agent-command.js +161 -223
  29. package/dist/cli/agent-create.js +190 -223
  30. package/dist/cli/agent.js +3 -1
  31. package/dist/cli/aid-bind-command.js +53 -0
  32. package/dist/cli/aun-commands.js +384 -91
  33. package/dist/cli/bench.js +1 -1
  34. package/dist/cli/cli-argv.js +74 -0
  35. package/dist/cli/daemon-commands.js +105 -74
  36. package/dist/cli/diag.js +1 -1
  37. package/dist/cli/fs-command.js +18 -19
  38. package/dist/cli/getmeta-command.js +159 -6
  39. package/dist/cli/index.js +6 -12
  40. package/dist/cli/init-channel.js +8 -55
  41. package/dist/cli/init.js +125 -218
  42. package/dist/cli/queue-command.js +11 -8
  43. package/dist/cli/restart-monitor.js +177 -85
  44. package/dist/cli/session-command.js +1 -1
  45. package/dist/cli/stats.js +7 -5
  46. package/dist/cli/thread-command.js +103 -30
  47. package/dist/cli/trigger-command.js +1 -1
  48. package/dist/cli/watch-msg-diag.js +1 -0
  49. package/dist/config/access-policy.js +2 -2
  50. package/dist/config/boot-log.js +14 -11
  51. package/dist/config/config-field-policy.js +9 -1
  52. package/dist/config/config-manager.js +176 -90
  53. package/dist/config/config-operation-service.js +7 -14
  54. package/dist/config/contact-book-store.js +142 -26
  55. package/dist/config/contact-book.js +4 -2
  56. package/dist/config/lifecycle.js +1 -1
  57. package/dist/config/owner-config.js +96 -0
  58. package/dist/config/peer-role-resolver.js +12 -18
  59. package/dist/config/providers-config.js +382 -0
  60. package/dist/config/providers-import.js +74 -0
  61. package/dist/config/providers-management.js +328 -0
  62. package/dist/config/providers.js +22 -0
  63. package/dist/config/resolved-config-op.js +17 -14
  64. package/dist/config/snapshot.js +28 -16
  65. package/dist/config-store.js +18 -14
  66. package/dist/core/agent-ipc-registration.js +21 -63
  67. package/dist/core/agent-runtime.js +16 -5
  68. package/dist/core/auth/agent-delegation.js +199 -25
  69. package/dist/core/auth/auth-gateway.js +44 -2
  70. package/dist/core/auth/authorization-denial.js +1 -1
  71. package/dist/core/auth/debug-context.js +2 -1
  72. package/dist/core/auth/delegation-authorization.js +2 -2
  73. package/dist/core/auth/delegation-diagnostics.js +4 -1
  74. package/dist/core/auth/operation-authorizer.js +10 -1
  75. package/dist/core/auth/operation-catalog.js +19 -1
  76. package/dist/core/bootstrap-service.js +43 -20
  77. package/dist/core/channel-loader.js +0 -1
  78. package/dist/core/channel-runtime.js +30 -4
  79. package/dist/core/command/agent-control.js +196 -37
  80. package/dist/core/command/cli-intent-parser.js +60 -4
  81. package/dist/core/command/command-handler.js +212 -60
  82. package/dist/core/command/connect-menu.js +1 -0
  83. package/dist/core/command/diag-service.js +5 -4
  84. package/dist/core/command/file-search.js +368 -0
  85. package/dist/core/command/menu-catalog.js +2 -1
  86. package/dist/core/command/menu-handler.js +292 -107
  87. package/dist/core/command/menu-protocol.js +23 -0
  88. package/dist/core/command/role-menu.js +7 -19
  89. package/dist/core/command/skill-slash.js +130 -25
  90. package/dist/core/command/slash-handler.js +101 -53
  91. package/dist/core/command/topic-slash.js +16 -5
  92. package/dist/core/daemon-bootstrap.js +7 -1
  93. package/dist/core/daemon-control.js +19 -1
  94. package/dist/core/daemon-lifecycle.js +2 -1
  95. package/dist/core/event-catalog.js +2 -0
  96. package/dist/core/evolagent-registry.js +13 -2
  97. package/dist/core/evolagent.js +3 -6
  98. package/dist/core/handoff/ipc-registration.js +2 -2
  99. package/dist/core/handoff/runtime.js +40 -6
  100. package/dist/core/handoff/store.js +1 -0
  101. package/dist/core/inference/text-inference.js +95 -6
  102. package/dist/core/ipc-registration.js +43 -24
  103. package/dist/core/memory/consolidation-context.js +4 -1
  104. package/dist/core/memory/consolidation.js +25 -14
  105. package/dist/core/message/aun-ipc-sender.js +296 -88
  106. package/dist/core/message/aun-request-registry.js +226 -0
  107. package/dist/core/message/create-status.js +33 -4
  108. package/dist/core/message/message-bridge.js +62 -13
  109. package/dist/core/message/message-log.js +0 -1
  110. package/dist/core/message/message-queue.js +4 -1
  111. package/dist/core/message/message-utils.js +8 -1
  112. package/dist/core/message/peer-mode.js +17 -9
  113. package/dist/core/message/response-engine.js +266 -124
  114. package/dist/core/message/retry-scheduler.js +11 -8
  115. package/dist/core/model/catalog-service.js +60 -18
  116. package/dist/core/model/model-catalog.js +112 -9
  117. package/dist/core/model/model-fallback.js +94 -26
  118. package/dist/core/ownership/owner-resolver.js +687 -0
  119. package/dist/core/permission/approval-gateway.js +196 -62
  120. package/dist/core/permission/ec-command-parser.js +138 -25
  121. package/dist/core/permission/filesystem-approval-scope.js +141 -0
  122. package/dist/core/permission/protected-paths.js +14 -1
  123. package/dist/core/permission/sandbox-runtime.js +171 -18
  124. package/dist/core/permission/tool-policy.js +13 -2
  125. package/dist/core/role/runtime-policy.js +3 -4
  126. package/dist/core/session/session-idle-monitor.js +10 -8
  127. package/dist/core/session/session-manager.js +72 -45
  128. package/dist/core/session/session-mapper.js +18 -7
  129. package/dist/core/session/session-renew.js +5 -4
  130. package/dist/core/session/session-title.js +2 -1
  131. package/dist/core/session/session-turn-coordinator.js +4 -2
  132. package/dist/core/skill/index-generator.js +2 -16
  133. package/dist/core/task/task-recovery-service.js +15 -2
  134. package/dist/core/thread/command-parser.js +28 -13
  135. package/dist/core/thread/index.js +1 -0
  136. package/dist/core/thread/ipc-operation.js +11 -8
  137. package/dist/core/thread/thread-operation.js +79 -44
  138. package/dist/core/thread/thread-route.js +54 -6
  139. package/dist/core/thread/thread-service.js +236 -45
  140. package/dist/core/thread/topic-id.js +248 -0
  141. package/dist/eck/kit-renderer.js +1 -1
  142. package/dist/eck/message-renderer.js +12 -2
  143. package/dist/index.js +50 -27
  144. package/dist/ipc.js +59 -6
  145. package/dist/paths.js +1 -0
  146. package/dist/product.js +1 -0
  147. package/dist/response-system/engines/v1/proactive-flow.js +10 -3
  148. package/dist/stats/db.js +8 -0
  149. package/dist/stats/writer.js +26 -4
  150. package/dist/trigger/authorization.js +48 -3
  151. package/dist/trigger/feedback.js +28 -10
  152. package/dist/trigger/ipc-service.js +86 -10
  153. package/dist/trigger/local-files.js +55 -23
  154. package/dist/trigger/scheduler.js +57 -11
  155. package/dist/trigger/system-definitions.js +1 -0
  156. package/dist/trigger/validation.js +44 -15
  157. package/dist/utils/aid-bind.js +17 -297
  158. package/dist/utils/avatar-upload.js +16 -4
  159. package/dist/utils/cross-platform.js +115 -36
  160. package/dist/utils/ecweb-supervisor.js +1 -1
  161. package/dist/utils/ecweb-utils.js +8 -1
  162. package/dist/utils/error-utils.js +44 -6
  163. package/dist/utils/instance-registry.js +12 -2
  164. package/dist/utils/npm-ops.js +102 -27
  165. package/dist/utils/package-version-info.js +72 -27
  166. package/dist/utils/restart-safety.js +50 -0
  167. package/dist/utils/upgrade-jobs.js +304 -0
  168. package/dist/utils/windows-autostart.js +17 -10
  169. package/kits/docs/INDEX.md +5 -0
  170. package/kits/docs/aun/ABILITY_CALLING.md +239 -0
  171. package/kits/docs/evolcore/INDEX.md +14 -3
  172. package/kits/docs/evolcore/agent-create.md +55 -141
  173. package/kits/docs/evolcore/agent.md +38 -16
  174. package/kits/docs/evolcore/aid.md +34 -0
  175. package/kits/docs/evolcore/config.md +20 -1
  176. package/kits/docs/evolcore/event.md +2 -2
  177. package/kits/docs/evolcore/fs.md +18 -2
  178. package/kits/docs/evolcore/group-collaboration.md +13 -246
  179. package/kits/docs/evolcore/group.md +2 -1
  180. package/kits/docs/evolcore/memory-consolidation-batches.md +1 -1
  181. package/kits/docs/evolcore/menu-protocol.md +65 -1
  182. package/kits/docs/evolcore/msg.md +64 -2
  183. package/kits/docs/evolcore/skill.md +86 -0
  184. package/kits/docs/evolcore/stats.md +6 -0
  185. package/kits/docs/evolcore/task.md +2 -0
  186. package/kits/docs/evolcore/thread-chain.md +145 -51
  187. package/kits/docs/evolcore/trigger.md +48 -3
  188. package/kits/eck_manifest.json +21 -0
  189. package/kits/eck_message_manifest.json +4 -4
  190. package/kits/migrations/migrate-role-config-v5.mjs +20 -7
  191. package/kits/rules/01-overview.md +2 -1
  192. package/kits/rules/03-identity.md +3 -1
  193. package/kits/rules/04-relation.md +1 -1
  194. package/kits/rules/07-memory-and-thread.md +1 -1
  195. package/kits/schemas/_meta.json +16 -1
  196. package/kits/schemas/agent-config.schema.13.json +44 -2
  197. package/kits/schemas/contact-book.schema.3.json +5 -0
  198. package/kits/schemas/daemon.schema.6.json +3 -1
  199. package/kits/schemas/daemon.schema.8.json +329 -0
  200. package/kits/schemas/defaults.schema.5.json +39 -3
  201. package/kits/schemas/providers.schema.1.json +76 -0
  202. package/kits/schemas/relation-config.schema.9.json +5 -1
  203. package/kits/skills/.switches.json +28 -0
  204. package/kits/skills/aid-ability/SKILL.md +214 -0
  205. package/kits/skills/ec-agent-create/SKILL.md +162 -0
  206. package/kits/skills/ec-group/SKILL.md +2 -1
  207. package/kits/skills/ec-group-collaboration/SKILL.md +228 -0
  208. package/kits/skills/ec-identity/SKILL.md +143 -0
  209. package/kits/skills/ec-skill/scripts/cli.mjs +36 -3
  210. package/kits/skills/ec-skill/scripts/skill.mjs +202 -15
  211. package/kits/skills/ec-trigger/SKILL.md +167 -0
  212. package/kits/skills/ec-trigger/references/examples.md +153 -0
  213. package/kits/skills/thread-ops/SKILL.md +108 -420
  214. package/kits/templates/message-fragments/thread-context.md +1 -2
  215. package/kits/templates/message-fragments/trigger-thread-check.md +1 -1
  216. package/kits/templates/roles/member.json +1 -1
  217. package/kits/templates/roles/visitor.json +1 -0
  218. package/kits/templates/system-fragments/bootstrap.md +1 -0
  219. package/kits/templates/system-fragments/channel.md +2 -1
  220. package/kits/templates/system-fragments/commands.md +5 -4
  221. package/kits/templates/system-fragments/identity.md +2 -0
  222. package/kits/templates/system-fragments/session.md +1 -1
  223. package/kits/templates/system-fragments/venue.md +1 -1
  224. package/package.json +4 -3
@@ -12,6 +12,7 @@
12
12
  import crypto from 'crypto';
13
13
  import { formatPeerKey } from '../relation/peer-identity.js';
14
14
  import { logger } from '../../utils/logger.js';
15
+ import { sessionTopicThreadId } from '../thread/topic-id.js';
15
16
  // ---------------------------------------------------------------------------
16
17
  // DebugDelegationRegistry
17
18
  // ---------------------------------------------------------------------------
@@ -101,7 +102,7 @@ export function buildDebugTaskContext(input) {
101
102
  let peerKeyId = chatType === 'group'
102
103
  ? (meta.groupId || session.channelId)
103
104
  : (meta.peerId || session.channelId);
104
- let threadId = session.threadId || undefined;
105
+ let threadId = sessionTopicThreadId(session);
105
106
  if (session.channelId?.startsWith('trigger:') && session.selfAID && input.getTrigger) {
106
107
  const triggerId = session.channelId.slice('trigger:'.length);
107
108
  const origin = input.getTrigger(session.selfAID, triggerId)?.origin;
@@ -1,4 +1,4 @@
1
- import { authorizeOperation, buildAuthSubject } from './auth-gateway.js';
1
+ import { authorizeFreshOperation as authorizeOperation, buildAuthSubject } from './auth-gateway.js';
2
2
  import { trustedExecutionIdentityInput } from './agent-delegation.js';
3
3
  import { isManagementRole } from '../../config/builtin-roles.js';
4
4
  import { parsePeerKey } from '../relation/peer-identity.js';
@@ -35,7 +35,7 @@ export function createDelegationAuthorizer({ agentDelegationRegistry, sessionMan
35
35
  && !(operation === 'agent.show' && isManagementRole(subject.role))) {
36
36
  return { ok: false, code: 'SCOPE_MISMATCH', error: 'only the current Agent can be targeted' };
37
37
  }
38
- const decision = authorizeOperation({
38
+ const decision = await authorizeOperation({
39
39
  source: 'agent-tool',
40
40
  subject,
41
41
  intent: {
@@ -89,7 +89,7 @@ function readState(value) {
89
89
  }
90
90
  if (state.credentialType === 'task' || state.credentialType === 'codex-session')
91
91
  sanitized.credentialType = state.credentialType;
92
- if (state.revocationReason && ['completed', 'interrupted', 'replaced', 'policy_revoked', 'failed', 'revoked'].includes(state.revocationReason)) {
92
+ if (state.revocationReason && ['completed', 'tool_completed', 'interrupted', 'replaced', 'policy_revoked', 'failed', 'revoked'].includes(state.revocationReason)) {
93
93
  sanitized.revocationReason = state.revocationReason;
94
94
  }
95
95
  return sanitized;
@@ -166,6 +166,9 @@ export class DelegationDiagnostics {
166
166
  if (approval.status === 'expired' || approval.expiresAt !== undefined && approval.expiresAt <= Date.now()) {
167
167
  return 'approval_expired';
168
168
  }
169
+ // Provider finished the tool item without spending it; the task itself is still live.
170
+ if (approval.revocationReason === 'tool_completed')
171
+ return 'approval_not_found';
169
172
  if (approval.status !== 'active')
170
173
  return this.taskCause(approval);
171
174
  return 'approval_not_found';
@@ -115,7 +115,8 @@ function authorizeCommandInternal(ctx, resolvedConfigCommand) {
115
115
  // still has to name a known operation and pass structural command parsing,
116
116
  // but relation/Agent role rules, source lists, and daemon-owner gates are
117
117
  // not meaningful limits for code already approved for host-level execution.
118
- if (ctx.processRole === 'fullaccess-run' && ctx.dataScope === 'daemon') {
118
+ if (ctx.processRole === 'fullaccess-run' && ctx.dataScope === 'daemon'
119
+ && operation !== 'ec.aid.bind' && operation !== 'ec.aid.bind.status') {
119
120
  return {
120
121
  allow: true,
121
122
  operation,
@@ -136,6 +137,14 @@ function authorizeCommandInternal(ctx, resolvedConfigCommand) {
136
137
  if (!sourceAllowed) {
137
138
  return denyDecision(ctx, 'NOT_ALLOWED', `Operation ${operation} is not available from source ${ctx.source}`, operation, intent.scope, opMeta.dangerous);
138
139
  }
140
+ if (operation === 'ec.aid.bind' || operation === 'ec.aid.bind.status') {
141
+ const permitted = intent.scope === 'agent' && ctx.processRole !== 'fullaccess-run' && ctx.processRole !== 'daemon-service'
142
+ && (ctx.isDaemonOwner === true || ctx.processRole === 'daemon-owner'
143
+ || (isCurrentAgentTarget(ctx) && (role === 'owner' || (operation === 'ec.aid.bind.status' && role === 'admin'))));
144
+ if (!permitted)
145
+ return denyDecision(ctx, 'NOT_ALLOWED', 'Protocol Owner binding requires the subject Owner or daemon Owner', operation, intent.scope, opMeta.dangerous);
146
+ return { allow: true, operation, scope: intent.scope, role, dangerous: opMeta.dangerous, matchedRule: 'protocol-owner-binding' };
147
+ }
139
148
  if (ctx.processRole === 'daemon-service') {
140
149
  // ECWeb uses the authenticated daemon service for its read-only menu
141
150
  // bootstrap (model/effort, agent metadata, trigger views, etc.). These
@@ -388,6 +388,14 @@ const OPERATIONS = [
388
388
  description: '获取文件内容(需路径沙箱检查)',
389
389
  sources: ['slash', 'menu'],
390
390
  },
391
+ {
392
+ id: 'file.search',
393
+ category: 'read',
394
+ dangerous: false,
395
+ defaultScopes: ['filesystem'],
396
+ description: '搜索当前项目文件(需文件权限和项目路径沙箱检查)',
397
+ sources: ['menu'],
398
+ },
391
399
  {
392
400
  id: 'file.root.agent',
393
401
  category: 'read',
@@ -798,7 +806,9 @@ const OPERATIONS = [
798
806
  dangerous: false,
799
807
  defaultScopes: ['relation', 'agent'],
800
808
  description: `${action} managed conversation threads`,
801
- sources: ['agent-tool'],
809
+ sources: (['summary-show', 'summary-log'].includes(action)
810
+ ? ['agent-tool', 'menu.cli']
811
+ : ['agent-tool']),
802
812
  })),
803
813
  {
804
814
  id: 'agent.bootstrapComplete',
@@ -1111,6 +1121,14 @@ const OPERATIONS = [
1111
1121
  description: 'Inspect the current Agent local AID identity and signing state',
1112
1122
  sources: ['agent-tool'],
1113
1123
  },
1124
+ {
1125
+ id: 'ec.aid.bind', category: 'write-agent', dangerous: true, defaultScopes: ['agent'],
1126
+ description: 'Request a protocol Owner binding using the subject SDK client', sources: ['agent-tool', 'menu.cli'],
1127
+ },
1128
+ {
1129
+ id: 'ec.aid.bind.status', category: 'read', dangerous: false, defaultScopes: ['agent'],
1130
+ description: 'Refresh the protocol Owner consensus and binding task projection', sources: ['agent-tool', 'menu.cli'],
1131
+ },
1114
1132
  {
1115
1133
  id: 'ec.aid.lookup',
1116
1134
  category: 'read',
@@ -1,4 +1,5 @@
1
1
  import { hasSuppliedInitialization } from '../cli/agent-create.js';
2
+ import { ownerResolver, OWNERSHIP_AUTHORIZATION_MAX_AGE_MS } from './ownership/owner-resolver.js';
2
3
  import fs from 'fs';
3
4
  import path from 'path';
4
5
  import { randomBytes } from 'crypto';
@@ -10,7 +11,6 @@ import { loadAgent, saveAgent } from '../config-store.js';
10
11
  import { resolveAgentLifecycle, withLifecycleForWrite } from '../config/lifecycle.js';
11
12
  import { renderTemplate } from '../eck/manifest-engine.js';
12
13
  import { activeBaseagent } from './model/config-scope.js';
13
- import { getFirstStaticAgentOwner } from '../config/peer-role-resolver.js';
14
14
  import { buildEnvelope } from './message/message-utils.js';
15
15
  import { BOOTSTRAP_MESSAGE_TTL_MS, BOOTSTRAP_NOTIFICATION_WINDOW_MS, bootstrapInitialMessageOperationId, bootstrapInitialMessageOperationPrefix, bootstrapInitialRetryOperationId, cancelPendingBootstrapPrompts, postBootstrapWelcomeStatus, postBootstrapWelcomeOperationId, } from './bootstrap-messages.js';
16
16
  import { findLatestMessageLogOperation } from './message/message-log.js';
@@ -19,15 +19,24 @@ import { chatDirPath } from './session/session-fs-store.js';
19
19
  /** Coordinate the write-ahead completion welcome with the lifecycle commit. */
20
20
  export async function completeBootstrapWithWelcome(service, aid, welcome, sessions) {
21
21
  const lifecycle = service.lifecycleOf(aid);
22
- if (lifecycle !== 'bootstrapping' && lifecycle !== 'active') {
22
+ const config = loadAgent(aid);
23
+ const suppliedOwnerPending = lifecycle === 'ownerpending' && config?.initialization?.mode === 'supplied';
24
+ if (lifecycle !== 'bootstrapping' && lifecycle !== 'active' && !suppliedOwnerPending) {
23
25
  const result = service.completeBootstrap(aid);
24
26
  return { ...result, reloaded: false };
25
27
  }
26
- if (lifecycle === 'bootstrapping') {
28
+ if (lifecycle === 'bootstrapping' || suppliedOwnerPending) {
27
29
  // Existing per-Owner records are immutable retry snapshots. New interactive
28
30
  // Bootstrap completions require the welcome text prepared by the Agent.
29
- const config = loadAgent(aid);
30
- const deliveries = config?.owners?.length ? postBootstrapWelcomeStatus(aid, config.owners).deliveries : [];
31
+ await ownerResolver.refresh(aid, { maxAgeMs: OWNERSHIP_AUTHORIZATION_MAX_AGE_MS });
32
+ const owners = ownerResolver.protocolOwners(aid);
33
+ if (owners.length === 0) {
34
+ return {
35
+ ok: false, code: 'OWNER_CONSENSUS_PENDING', reloaded: false,
36
+ error: `Fresh Ownership consensus is required before activating ${aid}`,
37
+ };
38
+ }
39
+ const deliveries = owners.length ? postBootstrapWelcomeStatus(aid, owners).deliveries : [];
31
40
  const alreadyPrepared = deliveries.length > 0 && deliveries.every(delivery => delivery.status !== 'not_prepared');
32
41
  if (config?.initialization?.mode !== 'supplied' && !alreadyPrepared) {
33
42
  try {
@@ -108,7 +117,7 @@ export async function completeBootstrapWithWelcome(service, aid, welcome, sessio
108
117
  mainSession ??= ownerSession;
109
118
  }
110
119
  }
111
- const result = service.completeBootstrap(aid);
120
+ const result = service.completeBootstrap(aid, { allowSuppliedOwnerPending: suppliedOwnerPending });
112
121
  if (!result.ok)
113
122
  return { ...result, reloaded: false };
114
123
  if (!welcome) {
@@ -129,7 +138,7 @@ export async function completeBootstrapWithWelcome(service, aid, welcome, sessio
129
138
  logger.warn(`[Bootstrap] Completion committed; welcome reconciliation failed for ${aid}: ${message}`);
130
139
  welcomeResult = { deliveries: [], error: message };
131
140
  try {
132
- welcomeResult = { ...postBootstrapWelcomeStatus(aid, loadAgent(aid)?.owners ?? []), error: message };
141
+ welcomeResult = { ...postBootstrapWelcomeStatus(aid, ownerResolver.protocolOwners(aid)), error: message };
133
142
  }
134
143
  catch {
135
144
  // A local store failure must not turn an already committed lifecycle
@@ -151,6 +160,8 @@ export function createBootstrapOwnerBoundHandler({ getService, agentRegistry, ch
151
160
  if (!service)
152
161
  return;
153
162
  const instance = channelInstances.find(candidate => {
163
+ if (candidate.channelType !== 'aun')
164
+ return false;
154
165
  const owner = agentRegistry.resolveByChannel(candidate.adapter.channelKey)
155
166
  ?? agentRegistry.resolveByChannel(candidate.adapter.channelName);
156
167
  return owner?.aid === agentAid;
@@ -182,7 +193,8 @@ export function createBootstrapCompletionHandler({ service, agentRegistry, chann
182
193
  });
183
194
  const welcome = aunInstance?.channel;
184
195
  const owningAgent = agentRegistry.get(aid);
185
- const firstOwnerAid = getFirstStaticAgentOwner(aid);
196
+ await ownerResolver.refresh(aid, { maxAgeMs: OWNERSHIP_AUTHORIZATION_MAX_AGE_MS });
197
+ const firstOwnerAid = ownerResolver.owner(aid);
186
198
  const sessions = aunInstance && firstOwnerAid && owningAgent
187
199
  ? {
188
200
  ensurePostBootstrapMainSession: async (owner) => {
@@ -224,7 +236,8 @@ export class BootstrapService {
224
236
  const aid = agent?.aid || ctx.agentAid;
225
237
  if (!aid)
226
238
  return false;
227
- if (hasSuppliedInitialization(aid) || loadAgent(aid)?.initialization)
239
+ const loadedConfig = loadAgent(aid) || agent?.config;
240
+ if (hasSuppliedInitialization(aid) || loadedConfig?.initialization)
228
241
  return false;
229
242
  const key = aid;
230
243
  if (this.inFlight.has(key))
@@ -233,7 +246,6 @@ export class BootstrapService {
233
246
  let lifecycleStarted = false;
234
247
  let notificationPersisted = false;
235
248
  let attemptedOperationId;
236
- const loadedConfig = loadAgent(aid) || agent?.config;
237
249
  if (!loadedConfig) {
238
250
  this.inFlight.delete(key);
239
251
  return false;
@@ -243,15 +255,21 @@ export class BootstrapService {
243
255
  this.inFlight.delete(key);
244
256
  return false;
245
257
  }
246
- if (lifecycle !== 'created' && lifecycle !== 'bootstrapping') {
258
+ if (lifecycle !== 'created' && lifecycle !== 'ownerpending' && lifecycle !== 'bootstrapping') {
247
259
  this.inFlight.delete(key);
248
260
  logger.warn(`[Bootstrap] Refusing invalid lifecycle for ${aid}: ${String(loadedConfig.lifecycle)}`);
249
261
  return false;
250
262
  }
251
263
  const config = { ...loadedConfig, lifecycle };
252
- const starting = lifecycle === 'created';
264
+ const starting = lifecycle !== 'bootstrapping';
253
265
  const channelType = ctx.channelType || this.channelTypeFromKey(ctx.channelKey);
266
+ if (channelType === 'aun')
267
+ await ownerResolver.refresh(aid, { maxAgeMs: OWNERSHIP_AUTHORIZATION_MAX_AGE_MS });
254
268
  const configuredRecipient = this.resolveConfiguredRecipient(config, ctx.channelKey, channelType);
269
+ if (channelType === 'aun' && (!configuredRecipient || ownerResolver.owner(aid) !== configuredRecipient)) {
270
+ this.inFlight.delete(key);
271
+ return false;
272
+ }
255
273
  const recipientId = ctx.recipientId || configuredRecipient;
256
274
  if (!recipientId) {
257
275
  this.inFlight.delete(key);
@@ -261,10 +279,9 @@ export class BootstrapService {
261
279
  this.inFlight.delete(key);
262
280
  return false;
263
281
  }
264
- // Bootstrap is initiated by connection/owner binding, not by a reply to
265
- // an existing group message. `owners[]` contains personal Owner AIDs, so
266
- // the first prompt must always use the private message route. Inbound
267
- // group context must never change this initial delivery target.
282
+ // Bootstrap is initiated by connection/Owner binding, not by a reply to
283
+ // an existing group message. The initial prompt always uses a private
284
+ // route to the protocol-confirmed Owner.
268
285
  const delivery = { chatType: 'private' };
269
286
  const channelId = channelType === 'aun'
270
287
  ? recipientId
@@ -382,7 +399,7 @@ export class BootstrapService {
382
399
  this.inFlight.delete(key);
383
400
  }
384
401
  }
385
- completeBootstrap(aid) {
402
+ completeBootstrap(aid, options = {}) {
386
403
  const agent = this.agentRegistry.get(aid);
387
404
  const loadedConfig = loadAgent(aid) || agent?.config;
388
405
  if (!loadedConfig)
@@ -390,7 +407,9 @@ export class BootstrapService {
390
407
  const lifecycle = resolveAgentLifecycle(loadedConfig);
391
408
  if (lifecycle === 'active')
392
409
  return { ok: true, aid, transitioned: false };
393
- if (lifecycle !== 'bootstrapping') {
410
+ const suppliedOwnerPending = options.allowSuppliedOwnerPending === true
411
+ && lifecycle === 'ownerpending' && loadedConfig.initialization?.mode === 'supplied';
412
+ if (lifecycle !== 'bootstrapping' && !suppliedOwnerPending) {
394
413
  return {
395
414
  ok: false,
396
415
  code: 'INVALID_LIFECYCLE',
@@ -426,8 +445,12 @@ export class BootstrapService {
426
445
  throw new Error(`Agent "${aid}" disappeared while updating lifecycle`);
427
446
  saveAgent(withLifecycleForWrite(cfg, lifecycle));
428
447
  }
429
- resolveConfiguredRecipient(config, _channelKey, _channelType) {
430
- return config.owners?.[0];
448
+ resolveConfiguredRecipient(config, channelKey, channelType) {
449
+ if (channelType === 'aun' || channelKey === 'aun' || channelKey.startsWith('aun#'))
450
+ return ownerResolver.owner(config.aid);
451
+ const channel = config.channels?.find(item => item.type !== 'aun'
452
+ && (item.type === channelKey || `${item.type}#${config.aid}#${item.name}` === channelKey));
453
+ return channel?.owner ?? channel?.owners?.[0];
431
454
  }
432
455
  defaultChannelIdForConnection(channelType, recipientId) {
433
456
  if (channelType === 'aun')
@@ -62,7 +62,6 @@ export class ChannelLoader {
62
62
  type: 'aun',
63
63
  name: aunEffName,
64
64
  aid: agent.aid,
65
- owner: agent.getOwner(aunEffName),
66
65
  enabled: true,
67
66
  keystorePath: agent.config.aun?.keystorePath,
68
67
  };
@@ -1,5 +1,7 @@
1
1
  import path from 'path';
2
+ import { getOwners } from './ownership/owner-resolver.js';
2
3
  import fs from 'fs';
4
+ import { removeRestartRequest } from '../utils/restart-safety.js';
3
5
  import { tryParseChannelKey } from './channel-loader.js';
4
6
  import { agentMdPath } from '../paths.js';
5
7
  import { registerAdapterInteractions } from './interaction-router.js';
@@ -13,6 +15,7 @@ import { getFirstStaticAgentOwner } from '../config/peer-role-resolver.js';
13
15
  import { isManagementRole } from '../config/builtin-roles.js';
14
16
  import { pendingInterruptHints } from './message/pending-hints.js';
15
17
  import { logger } from '../utils/logger.js';
18
+ import { sessionLocalTopicId } from './thread/topic-id.js';
16
19
  export function createAgentStorageClientResolver(channelInstances) {
17
20
  return (channelKey, targetAid) => {
18
21
  const instance = channelInstances.find(candidate => candidate.channelType === 'aun'
@@ -145,7 +148,7 @@ export function registerChannelInstance(inst, dependencies) {
145
148
  const owningAgent = resolveOwningAgent(channelKey, inst.adapter.channelName);
146
149
  return {
147
150
  observable: owningAgent?.getObservable() ?? false,
148
- owners: owningAgent ? Array.from(new Set(owningAgent.config.owners ?? [])) : [],
151
+ owners: owningAgent ? getOwners(owningAgent.aid) : [],
149
152
  };
150
153
  });
151
154
  }
@@ -163,6 +166,7 @@ export function createChannelStartupNotifications({ channelInstances, agentRegis
163
166
  const onlineNoticeSent = new Set();
164
167
  let pendingRestartNoticeInFlight = null;
165
168
  let pendingRestartNoticeSent = false;
169
+ let pendingRestartRetry;
166
170
  const sendOnlineNoticeForChannel = (inst) => {
167
171
  const name = inst.adapter.channelName;
168
172
  const agent = agentRegistry.resolveByChannel(inst.adapter.channelKey) ?? agentRegistry.resolveByChannel(name);
@@ -219,6 +223,20 @@ export function createChannelStartupNotifications({ channelInstances, agentRegis
219
223
  if (pendingRestartNoticeSent || !fs.existsSync(pendingFile))
220
224
  return;
221
225
  const pending = JSON.parse(fs.readFileSync(pendingFile, 'utf-8'));
226
+ if (pending.requestId && pending.requesterPid) {
227
+ if (pending.requesterPid === process.pid)
228
+ return;
229
+ if (pending.status !== 'ready' || pending.daemonPid !== process.pid) {
230
+ if (!pendingRestartRetry) {
231
+ pendingRestartRetry = setTimeout(() => {
232
+ pendingRestartRetry = undefined;
233
+ void trySendPendingRestartNotice();
234
+ }, 1_000);
235
+ pendingRestartRetry.unref();
236
+ }
237
+ return;
238
+ }
239
+ }
222
240
  const adapter = commandHandler.getAdapter(pending.channel)
223
241
  ?? channelInstances.find(inst => inst.adapter.channelKey === pending.channel)?.adapter;
224
242
  if (!adapter)
@@ -237,7 +255,10 @@ export function createChannelStartupNotifications({ channelInstances, agentRegis
237
255
  });
238
256
  if (!restartDelivery.valid) {
239
257
  logger.warn(`[Restart] Discarding pending notification without a valid persisted delivery route: channel=${pending.channel} channelId=${pending.channelId}`);
240
- fs.rmSync(pendingFile, { force: true });
258
+ if (pending.requestId)
259
+ removeRestartRequest(pendingFile, pending.requestId);
260
+ else
261
+ fs.rmSync(pendingFile, { force: true });
241
262
  return;
242
263
  }
243
264
  const replyContext = {
@@ -271,7 +292,12 @@ export function createChannelStartupNotifications({ channelInstances, agentRegis
271
292
  subtype: restartNotice.subtype,
272
293
  });
273
294
  pendingRestartNoticeSent = true;
274
- fs.rmSync(pendingFile, { force: true });
295
+ if (pendingRestartRetry)
296
+ clearTimeout(pendingRestartRetry);
297
+ if (pending.requestId)
298
+ removeRestartRequest(pendingFile, pending.requestId);
299
+ else
300
+ fs.rmSync(pendingFile, { force: true });
275
301
  logger.info(`[Restart] Notification sent via ${pending.channel}`);
276
302
  })();
277
303
  try {
@@ -390,7 +416,7 @@ export function wrapChannelSend(inst, dependencies) {
390
416
  from: owningAgent?.aid ?? envelope.agentName ?? session.selfAID ?? 'self',
391
417
  to: String(target || envelope.channelId),
392
418
  sessionId: session.id,
393
- threadId: envelope.replyContext?.threadId || session.threadId || '',
419
+ threadId: envelope.replyContext?.localTopicId || sessionLocalTopicId(session) || '',
394
420
  chatType: isGroup ? 'group' : 'private',
395
421
  groupId: isGroup ? String(session.metadata?.groupId || session.channelId) : null,
396
422
  msgId: `${envelope.taskId || 'out'}_${Date.now()}`,