evolcore 0.0.35 → 0.0.36

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 +31 -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 +134 -48
  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 +109 -34
  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
@@ -0,0 +1,226 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import crypto from 'crypto';
4
+ import { resolvePaths } from '../../paths.js';
5
+ import { appendJsonlDurable } from '../session/session-fs-store.js';
6
+ const DEFAULT_REQUEST_TIMEOUT_MS = 60_000;
7
+ const MAX_TIMER_MS = 2_147_483_647;
8
+ /** AUN message.send accepts volatile TTL values from 1 through 3600 seconds. */
9
+ export const MAX_AUN_VOLATILE_TTL_SECONDS = 3_600;
10
+ export function requestTtlSeconds(timeoutMs) {
11
+ const value = timeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
12
+ if (!Number.isFinite(value) || value <= 0) {
13
+ throw new Error('request timeout must be a positive number');
14
+ }
15
+ const ttl = Math.ceil(value / 1_000);
16
+ if (ttl > MAX_AUN_VOLATILE_TTL_SECONDS) {
17
+ throw new Error(`request timeout cannot exceed ${MAX_AUN_VOLATILE_TTL_SECONDS}s for volatile AUN messages`);
18
+ }
19
+ return Math.max(1, ttl);
20
+ }
21
+ function requestFile(selfAid) {
22
+ return path.join(resolvePaths().dataDir, 'aun-requests', `${encodeURIComponent(selfAid)}.jsonl`);
23
+ }
24
+ function randomRequestId() {
25
+ return `req_${Date.now().toString(36)}_${crypto.randomBytes(8).toString('hex')}`;
26
+ }
27
+ function stringValue(value) {
28
+ return typeof value === 'string' && value.trim() ? value.trim() : undefined;
29
+ }
30
+ function normalizeTimeoutMs(value) {
31
+ if (typeof value !== 'number' || !Number.isFinite(value) || value <= 0)
32
+ return DEFAULT_REQUEST_TIMEOUT_MS;
33
+ return Math.min(MAX_TIMER_MS, Math.max(1, value));
34
+ }
35
+ export class AunRequestRegistry {
36
+ records = new Map();
37
+ waiters = new Map();
38
+ loaded = new Set();
39
+ key(selfAid, requestId) {
40
+ return `${selfAid}\0${requestId}`;
41
+ }
42
+ ensureLoaded(selfAid) {
43
+ if (this.loaded.has(selfAid))
44
+ return;
45
+ this.loaded.add(selfAid);
46
+ const file = requestFile(selfAid);
47
+ try {
48
+ const lines = fs.readFileSync(file, 'utf8').split(/\r?\n/);
49
+ for (const line of lines) {
50
+ if (!line.trim())
51
+ continue;
52
+ try {
53
+ const event = JSON.parse(line);
54
+ if (event?.event !== 'upsert' || !event.record?.requestId)
55
+ continue;
56
+ const recovered = event.record.state === 'created' || event.record.state === 'sending'
57
+ ? { ...event.record, state: 'unknown', updatedAt: Date.now() }
58
+ : event.record;
59
+ this.records.set(this.key(selfAid, recovered.requestId), recovered);
60
+ }
61
+ catch { /* ignore an incomplete/corrupt historical line */ }
62
+ }
63
+ for (const record of this.recordsFor(selfAid)) {
64
+ for (const alias of record.correlationKeys)
65
+ this.records.set(this.key(selfAid, alias), record);
66
+ }
67
+ }
68
+ catch { /* first use */ }
69
+ }
70
+ recordsFor(selfAid) {
71
+ const seen = new Set();
72
+ for (const record of this.records.values())
73
+ if (record.selfAid === selfAid)
74
+ seen.add(record);
75
+ return [...seen];
76
+ }
77
+ persist(record) {
78
+ const file = requestFile(record.selfAid);
79
+ fs.mkdirSync(path.dirname(file), { recursive: true });
80
+ appendJsonlDurable(file, { event: 'upsert', record });
81
+ this.records.set(this.key(record.selfAid, record.requestId), record);
82
+ for (const alias of record.correlationKeys)
83
+ this.records.set(this.key(record.selfAid, alias), record);
84
+ }
85
+ notify(record) {
86
+ if (!['completed', 'failed', 'unknown', 'pending'].includes(record.state))
87
+ return;
88
+ const waiters = this.waiters.get(this.key(record.selfAid, record.requestId));
89
+ if (!waiters)
90
+ return;
91
+ for (const waiter of waiters)
92
+ waiter(record);
93
+ this.waiters.delete(this.key(record.selfAid, record.requestId));
94
+ }
95
+ prepare(params) {
96
+ this.ensureLoaded(params.selfAid);
97
+ const requestId = stringValue(params.payload.request_id);
98
+ const callId = stringValue(params.payload.call_id);
99
+ const chosen = requestId || callId || randomRequestId();
100
+ const keys = [...new Set([chosen, requestId, callId].filter((value) => !!value))];
101
+ if (keys.some(key => this.records.has(this.key(params.selfAid, key)))) {
102
+ throw Object.assign(new Error('Request correlation ID is already registered'), { code: 'REQUEST_ID_CONFLICT' });
103
+ }
104
+ const correlationKind = requestId || !callId ? 'request_id' : 'call_id';
105
+ const now = Date.now();
106
+ const timeoutMs = normalizeTimeoutMs(params.timeoutMs);
107
+ const record = {
108
+ selfAid: params.selfAid,
109
+ targetAid: params.targetAid,
110
+ requestId: chosen,
111
+ correlationKind,
112
+ correlationKeys: keys,
113
+ ...(params.origin ? { origin: { ...params.origin } } : {}),
114
+ state: 'created',
115
+ createdAt: now,
116
+ updatedAt: now,
117
+ deadlineAt: now + timeoutMs,
118
+ };
119
+ const payload = { ...params.payload };
120
+ if (!requestId && !callId)
121
+ payload.request_id = chosen;
122
+ this.persist(record);
123
+ return { requestId: chosen, payload, record };
124
+ }
125
+ markSending(selfAid, requestId) {
126
+ return this.update(selfAid, requestId, { state: 'sending' });
127
+ }
128
+ markTransportAccepted(selfAid, requestId, messageId, status) {
129
+ const current = this.get(selfAid, requestId);
130
+ if (!current)
131
+ return undefined;
132
+ const state = current.state === 'created' || current.state === 'sending' ? 'sent' : current.state;
133
+ return this.update(selfAid, requestId, { state, outboundMessageId: messageId, transportStatus: status });
134
+ }
135
+ markFailed(selfAid, requestId, error) {
136
+ const current = this.get(selfAid, requestId);
137
+ if (current?.state === 'completed' || current?.state === 'failed')
138
+ return current;
139
+ return this.update(selfAid, requestId, { state: 'failed', error });
140
+ }
141
+ update(selfAid, requestId, patch) {
142
+ this.ensureLoaded(selfAid);
143
+ const existing = this.records.get(this.key(selfAid, requestId));
144
+ if (!existing)
145
+ return undefined;
146
+ const record = { ...existing, ...patch, updatedAt: Date.now() };
147
+ this.persist(record);
148
+ this.notify(record);
149
+ return record;
150
+ }
151
+ updateWithoutNotify(selfAid, requestId, patch) {
152
+ this.ensureLoaded(selfAid);
153
+ const existing = this.records.get(this.key(selfAid, requestId));
154
+ if (!existing)
155
+ return undefined;
156
+ const record = { ...existing, ...patch, updatedAt: Date.now() };
157
+ this.persist(record);
158
+ return record;
159
+ }
160
+ isIntermediateResponse(payload) {
161
+ // A generic application ACK means that the request was accepted. A later
162
+ // response carrying the same correlation key remains eligible to finish
163
+ // the wait; the transport does not interpret any business message type.
164
+ return payload.role === 'ack';
165
+ }
166
+ resolveIncoming(params) {
167
+ if (!params.payload || typeof params.payload !== 'object' || Array.isArray(params.payload))
168
+ return undefined;
169
+ this.ensureLoaded(params.selfAid);
170
+ const payload = params.payload;
171
+ const keys = [stringValue(payload.request_id), stringValue(payload.call_id)].filter((value) => !!value);
172
+ for (const key of keys) {
173
+ const record = this.records.get(this.key(params.selfAid, key));
174
+ if (!record || record.targetAid !== params.fromAid || ['failed', 'completed'].includes(record.state))
175
+ continue;
176
+ if (this.isIntermediateResponse(payload)) {
177
+ return this.updateWithoutNotify(params.selfAid, record.requestId, {
178
+ ackMessageId: params.messageId,
179
+ ackPayload: payload,
180
+ });
181
+ }
182
+ const updated = this.update(params.selfAid, record.requestId, {
183
+ state: 'completed',
184
+ responseMessageId: params.messageId,
185
+ responsePayload: payload,
186
+ });
187
+ return updated;
188
+ }
189
+ return undefined;
190
+ }
191
+ get(selfAid, requestId) {
192
+ this.ensureLoaded(selfAid);
193
+ const record = this.records.get(this.key(selfAid, requestId));
194
+ if (record && record.state === 'sent' && record.deadlineAt !== undefined && record.deadlineAt <= Date.now()) {
195
+ return this.update(selfAid, record.requestId, { state: 'pending' });
196
+ }
197
+ return record;
198
+ }
199
+ async wait(selfAid, requestId, timeoutMs) {
200
+ const current = this.get(selfAid, requestId);
201
+ if (!current || ['completed', 'failed', 'unknown'].includes(current.state))
202
+ return current;
203
+ const normalizedTimeoutMs = normalizeTimeoutMs(timeoutMs);
204
+ const waitMs = Math.max(0, Math.min(normalizedTimeoutMs, current.deadlineAt ? current.deadlineAt - Date.now() : normalizedTimeoutMs));
205
+ if (waitMs === 0)
206
+ return this.update(selfAid, requestId, { state: 'pending' });
207
+ return new Promise(resolve => {
208
+ const key = this.key(selfAid, requestId);
209
+ const callback = (record) => { clearTimeout(timer); resolve(record); };
210
+ const waiters = this.waiters.get(key) ?? new Set();
211
+ waiters.add(callback);
212
+ this.waiters.set(key, waiters);
213
+ const timer = setTimeout(() => {
214
+ if (!this.waiters.get(key)?.delete(callback))
215
+ return;
216
+ if (this.waiters.get(key)?.size === 0)
217
+ this.waiters.delete(key);
218
+ resolve(this.update(selfAid, requestId, { state: 'pending' }));
219
+ }, waitMs);
220
+ });
221
+ }
222
+ }
223
+ let defaultRegistry;
224
+ export function getAunRequestRegistry() {
225
+ return defaultRegistry ??= new AunRequestRegistry();
226
+ }
@@ -1,5 +1,7 @@
1
1
  import fs from 'fs';
2
2
  import path from 'path';
3
+ import { randomUUID } from 'node:crypto';
4
+ import { creationOwnershipBinding } from '../ownership/owner-resolver.js';
3
5
  const FILE = 'create-status.json';
4
6
  export function readCreateStatus(agentDir) {
5
7
  try {
@@ -22,12 +24,13 @@ export function removeCreateStatus(agentDir) {
22
24
  export class CreateStatusWriter {
23
25
  agentDir;
24
26
  status;
25
- constructor(agentDir, aid) {
27
+ creationId = randomUUID();
28
+ constructor(agentDir, aid, context) {
26
29
  this.agentDir = agentDir;
27
30
  const now = Date.now();
28
- this.status = { aid, status: 'in_progress', currentPhase: null, steps: [], error: null, startedAt: now, updatedAt: now };
31
+ this.status = { aid, creationId: this.creationId, ...context, status: 'in_progress', currentPhase: null, steps: [], error: null, startedAt: now, updatedAt: now };
29
32
  fs.mkdirSync(agentDir, { recursive: true });
30
- this.flush();
33
+ this.flush(true);
31
34
  }
32
35
  begin(phase) {
33
36
  this.status.currentPhase = phase;
@@ -36,6 +39,28 @@ export class CreateStatusWriter {
36
39
  }
37
40
  done(phase, detail) { this.mark(phase, 'done', detail); }
38
41
  warn(phase, detail) { this.mark(phase, 'warn', detail); }
42
+ /** Immutable correlation; a later CLI bind/transfer must not replace it. */
43
+ setOwnershipBinding(binding) {
44
+ const expected = creationOwnershipBinding({
45
+ operation: binding.operation, operation_id: binding.operationId,
46
+ sub_aid: binding.subjectAid, owner_aid: binding.ownerAid,
47
+ base_relation_version: binding.baseRelationVersion, target_relation_version: binding.targetRelationVersion,
48
+ status: 'pending',
49
+ }, this.status.aid, this.status.ownerAid ?? '');
50
+ if (!this.status.requesterAid || binding.source !== 'agent_create' || !expected) {
51
+ throw new Error('Creation Ownership binding does not match the creation request');
52
+ }
53
+ if (this.status.ownershipBinding) {
54
+ if (this.status.ownershipBinding.operationId !== binding.operationId) {
55
+ throw new Error('Creation Ownership binding is already recorded');
56
+ }
57
+ return;
58
+ }
59
+ if (this.status.status !== 'in_progress')
60
+ throw new Error('Agent creation has already finished');
61
+ this.status.ownershipBinding = expected;
62
+ this.flush();
63
+ }
39
64
  finishReady() { this.status.status = 'ready'; this.status.currentPhase = null; this.flush(); }
40
65
  finishFailed(phase, error) {
41
66
  this.mark(phase, 'failed', error);
@@ -57,7 +82,11 @@ export class CreateStatusWriter {
57
82
  }
58
83
  this.flush();
59
84
  }
60
- flush() {
85
+ flush(initial = false) {
86
+ // A failed worker or a deleted Agent must not overwrite a newer creation.
87
+ if (!initial && readCreateStatus(this.agentDir)?.creationId !== this.creationId) {
88
+ throw new Error('Agent creation record was removed or replaced');
89
+ }
61
90
  this.status.updatedAt = Date.now();
62
91
  const file = path.join(this.agentDir, FILE);
63
92
  const tmp = `${file}.tmp`;
@@ -1,4 +1,5 @@
1
1
  import { loadAgent } from '../../config-store.js';
2
+ import { subscribeUpgradeJob } from '../../utils/upgrade-jobs.js';
2
3
  import { initializationBlocksTasks } from '../../cli/agent-create.js';
3
4
  import { createHash, randomBytes } from 'crypto';
4
5
  import fs from 'fs';
@@ -17,6 +18,8 @@ import { resolveContactView, resolvePeerDisplayLabel, resolvePrincipal } from '.
17
18
  import { findContactRequestNote, expirePendingContactRequests, reviewContactRequest, submitContactRequest, contactRequestSubmissionProtocolResult, queryOwnContactRequestStatus, } from '../../config/contact-request-service.js';
18
19
  import { isValidAid } from '../../aun/aid/validation.js';
19
20
  import { PeerIdentityCache, formatPeerKey } from '../relation/peer-identity.js';
21
+ import { ownerResolver } from '../ownership/owner-resolver.js';
22
+ import { isTopicSession, sessionLocalTopicId } from '../thread/topic-id.js';
20
23
  import { resolveEffective } from '../../config/config-manager.js';
21
24
  import { createDecisionInferenceProvider } from '../inference/text-inference.js';
22
25
  import { AutoMentionGate } from './auto-mention-gate.js';
@@ -116,8 +119,8 @@ export class MessageBridge {
116
119
  self: item.selfAid,
117
120
  peerKey: formatPeerKey('aun', item.groupId),
118
121
  }, { cache: true, expand: true });
119
- const model = config.decision_model?.model?.trim();
120
- const provider = createDecisionInferenceProvider(config.decision_model);
122
+ const model = config.Services?.decision ? config.Services.decision.model?.trim() : config.decision_model?.model?.trim();
123
+ const provider = createDecisionInferenceProvider(config.decision_model, config.Services);
121
124
  return model && provider ? { model, provider } : undefined;
122
125
  },
123
126
  resolveResources: async (item) => {
@@ -245,11 +248,11 @@ export class MessageBridge {
245
248
  this.groupInviteReviewService?.attach(binding);
246
249
  }
247
250
  /**
248
- * AUN supplies an authenticated sender AID, while the IM-side code proves
249
- * possession of the external account. This command only creates that
250
- * identity mapping; it never changes a role assignment.
251
+ * Handle AUN's two slash-bind forms: bare `/bind` requests protocol
252
+ * ownership from the first configured Owner; `/bind <channel>` creates an
253
+ * external-channel contact mapping.
251
254
  */
252
- handleAunSelfServiceContactBind(input) {
255
+ async handleAunBindCommand(input) {
253
256
  const match = /^\s*\/bind(?:\s+(.+?))?\s*$/i.exec(input.content);
254
257
  if (!match)
255
258
  return { handled: false };
@@ -270,7 +273,42 @@ export class MessageBridge {
270
273
  };
271
274
  }
272
275
  const args = String(match[1] || '').trim().split(/\s+/).filter(Boolean);
273
- if (args.length < 1 || args.length > 2) {
276
+ if (args.length === 0) {
277
+ const configuredOwner = ownerResolver.configuredOwnerList(selfAid)[0];
278
+ if (configuredOwner !== actorId) {
279
+ return { handled: true, status: 'not-first-owner', reply: '仅 Agent 配置中的首个 Owner 可在 AUN 私聊发送 /bind 发起 ownership 绑定。' };
280
+ }
281
+ try {
282
+ const state = await ownerResolver.bind(selfAid, actorId);
283
+ const alreadyBound = state.consensusStatus === 'consensus' && state.ownerAid === actorId;
284
+ if (!alreadyBound && state.task && ['rejected', 'stopped', 'error'].includes(state.task.status)) {
285
+ return {
286
+ handled: true,
287
+ status: 'ownership-failed',
288
+ reply: `协议 Owner 绑定未完成:${state.task.reason ?? state.task.code ?? state.task.status}`,
289
+ };
290
+ }
291
+ return {
292
+ handled: true,
293
+ status: alreadyBound ? 'already-bound' : 'ownership-submitted',
294
+ reply: alreadyBound
295
+ ? '协议 Owner 已绑定,无需重复操作。'
296
+ : state.task?.status === 'completed'
297
+ ? '绑定任务已完成,等待协议共识核验。'
298
+ : `已向 ${actorId} 发起 ownership 绑定邀约,请在 Evol 中确认。`,
299
+ };
300
+ }
301
+ catch (error) {
302
+ const message = error instanceof Error ? error.message : String(error);
303
+ logger.warn(`[MessageBridge] AUN ownership bind failed: subject=${selfAid} owner=${actorId} error=${message}`);
304
+ return {
305
+ handled: true,
306
+ status: 'ownership-failed',
307
+ reply: `协议 Owner 绑定未完成:${message}`,
308
+ };
309
+ }
310
+ }
311
+ if (args.length > 2) {
274
312
  return { handled: true, status: 'format', reply: this.aunSelfServiceBindUsage() };
275
313
  }
276
314
  const channelType = args[0].toLowerCase();
@@ -359,7 +397,7 @@ export class MessageBridge {
359
397
  }
360
398
  }
361
399
  aunSelfServiceBindUsage() {
362
- return `用法:/bind <渠道> [实例名]。支持渠道:${SELF_SERVICE_CONTACT_BIND_TYPES.join('、')}。`;
400
+ return `用法:/bind(首个配置 Owner 发起 ownership 绑定);/bind <渠道> [实例名](联系人绑定)。支持渠道:${SELF_SERVICE_CONTACT_BIND_TYPES.join('、')}。`;
363
401
  }
364
402
  aunSelfServiceBindInstances(channelType, instances) {
365
403
  const names = instances.map(instance => String(instance.name || '').trim()).filter(Boolean);
@@ -396,7 +434,7 @@ export class MessageBridge {
396
434
  return true;
397
435
  }
398
436
  if (context.channelType === 'aun') {
399
- const aunSelfServiceBind = this.handleAunSelfServiceContactBind({
437
+ const aunSelfServiceBind = await this.handleAunBindCommand({
400
438
  selfAid: context.selfAid,
401
439
  actorId: context.actorId,
402
440
  chatType: context.chatType,
@@ -404,7 +442,7 @@ export class MessageBridge {
404
442
  owningAgent,
405
443
  });
406
444
  if (aunSelfServiceBind.handled) {
407
- logger.info(`[MessageBridge] AUN self-service contact bind handled before admission: channel=${context.channelKey} actor=${context.actorId || '<none>'} status=${aunSelfServiceBind.status}`);
445
+ logger.info(`[MessageBridge] AUN bind command handled before admission: channel=${context.channelKey} actor=${context.actorId || '<none>'} status=${aunSelfServiceBind.status}`);
408
446
  if (aunSelfServiceBind.reply) {
409
447
  await this.sendAdmissionControlReply(context, msg, sendReply, aunSelfServiceBind.reply);
410
448
  }
@@ -1414,7 +1452,7 @@ export class MessageBridge {
1414
1452
  releaseAdmission = await this.acquireAdmission(admissionRoute);
1415
1453
  const hadMainSession = msg.threadId ? true : this.sessionManager.hasMainSession(channelName, msg.channelId, msg.channelType || effectiveChannelType, msg.selfAID || selfAid);
1416
1454
  let session = diagnosticSession ?? await this.sessionManager.getOrCreateSession(channelName, msg.channelId, effectiveProjectPath, msg.threadId, Object.keys(metadata).length ? metadata : undefined, this.extractTopicName(msg), msg.peerId, chatType, owningAgent?.baseagent, msg.selfAID, msg.channelType || effectiveChannelType, msg.peerType, identity, readTopicLabel(msg) ?? readTopicLabel(msg.replyContext?.metadata), readTopicStatus(msg) ?? readTopicStatus(msg.replyContext?.metadata));
1417
- if (session.threadId && typeof this.messageQueue.reserveSessionIngress === 'function') {
1455
+ if (isTopicSession(session) && typeof this.messageQueue.reserveSessionIngress === 'function') {
1418
1456
  releaseSessionIngress = this.messageQueue.reserveSessionIngress(session.id);
1419
1457
  }
1420
1458
  const replyToMessageId = typeof msg.replyContext?.metadata?.refMessageId === 'string'
@@ -1549,7 +1587,8 @@ export class MessageBridge {
1549
1587
  from: msg.peerId || 'unknown',
1550
1588
  to: msg.selfAID || 'self',
1551
1589
  sessionId: session.id,
1552
- threadId: session.threadId || '',
1590
+ // 消息日志用本端身份(含主话题规范 id),与出站日志的 localTopicId 一致。
1591
+ threadId: sessionLocalTopicId(session) ?? '',
1553
1592
  chatType,
1554
1593
  groupId: msg.groupId ?? null,
1555
1594
  msgId: msg.messageId ?? null,
@@ -1608,7 +1647,7 @@ export class MessageBridge {
1608
1647
  const member = item.value;
1609
1648
  appendMessageLog(inboundEntry.chatDir, buildInboundEntry({
1610
1649
  from: item.sender, to: selfAid || '', sessionId: session.id,
1611
- threadId: session.threadId || '', chatType: 'group', groupId: msg.groupId,
1650
+ threadId: sessionLocalTopicId(session) ?? '', chatType: 'group', groupId: msg.groupId,
1612
1651
  msgId: item.messageId, content: member.messageLogContent ?? item.content,
1613
1652
  permMode: item.role, timestamp: item.receivedAt,
1614
1653
  receivedAt: member.receivedAt, gatewaySeq: member.gatewaySeq,
@@ -2047,6 +2086,16 @@ export class MessageBridge {
2047
2086
  try {
2048
2087
  await adapter.send(envelope, { kind: 'custom', channelType: channel, payload: timedResponse });
2049
2088
  logMenuRequestCompleted(request, timedResponse, flowContext, { delivery: 'sent' });
2089
+ const upgradeData = 'data' in response ? response.data : undefined;
2090
+ if (request.type === 'menu.action' && request.action === 'upgrade' && (request.name === 'system' || request.cmd === '/system') && upgradeData?.jobId) {
2091
+ const jobId = upgradeData.jobId;
2092
+ subscribeUpgradeJob(jobId, async (event) => {
2093
+ const permission = await this.cmdHandler.execMenuAction('/system', 'upgrade', { mode: 'status', jobId }, channel, msg.channelId, msg.peerId, undefined, msg.replyContext?.delivery?.chatType, undefined, false, undefined, 'menu', msg.replyContext);
2094
+ if ('error' in permission)
2095
+ throw new Error('Upgrade event authorization expired');
2096
+ await adapter.send(envelope, { kind: 'custom', channelType: channel, payload: event });
2097
+ }, [channel, msg.channelId, msg.peerId, msg.peerDeviceId || '', jobId].join('\u001f'));
2098
+ }
2050
2099
  }
2051
2100
  catch (error) {
2052
2101
  logMenuRequestCompleted(request, timedResponse, flowContext, {
@@ -21,7 +21,6 @@ const TRANSIENT_PROTOCOL_TYPES = new Set([
21
21
  'handoff_state',
22
22
  'handoff_result',
23
23
  'topic-created', // 话题创建系统消息,不进大模型上下文
24
- 'topic-renamed', // 话题改名通知,由 channel 层直接消费,不进大模型上下文
25
24
  ]);
26
25
  const KNOWN_AUN_PAYLOAD_TYPES = new Set([
27
26
  'text',
@@ -5,6 +5,7 @@ import { LogicalQueueBridge } from './logical-queue-bridge.js';
5
5
  import { normalizeCausation } from '../causation/context.js';
6
6
  import { KeyedFairMutex } from '../handoff/mutex.js';
7
7
  import { ExecutionTimeoutError, OrphanedTargetSessionError, isExecutionFailure } from '../../utils/error-utils.js';
8
+ import { isTopicThreadId } from '../thread/topic-id.js';
8
9
  const DEFAULT_AGENT_NAME = '<unknown>';
9
10
  const RESTART_RESUME_CONTENT = 'evolcore 服务已重启,请继续之前未完成的任务。';
10
11
  export class MessageQueue {
@@ -462,7 +463,9 @@ export class MessageQueue {
462
463
  return false;
463
464
  if (scope.groupId !== undefined && message.groupId !== scope.groupId)
464
465
  return false;
465
- if (scope.threadId !== undefined && (message.threadId || '') !== (scope.threadId || ''))
466
+ // 主会话的三种形态('' / 'main' / 规范 id)视为同一个:只比较话题 id。
467
+ if (scope.threadId !== undefined
468
+ && (isTopicThreadId(message.threadId) ? message.threadId : '') !== (isTopicThreadId(scope.threadId) ? scope.threadId : ''))
466
469
  return false;
467
470
  return true;
468
471
  }
@@ -3,6 +3,7 @@
3
3
  *
4
4
  * 从 message-processor.ts 提取的独立工具函数,供 ResponseEngine 和其他模块使用。
5
5
  */
6
+ import { sessionLocalTopicId, sessionTopicThreadId } from '../thread/topic-id.js';
6
7
  import { renderCommandCardAsText, renderActionAsText } from '../interaction-router.js';
7
8
  /** A legacy adapter resolved its send call without providing a structured receipt. */
8
9
  export const LEGACY_INTERACTION_ACCEPTED = 'legacy-accepted';
@@ -99,9 +100,15 @@ export function deliveryTargetFromSession(session) {
99
100
  }
100
101
  /** Preserve channel-specific reply fields while filling the session route. */
101
102
  export function replyContextFromSession(session, context) {
103
+ const topicThreadId = sessionTopicThreadId(session);
104
+ const localTopicId = sessionLocalTopicId(session);
102
105
  return {
103
106
  ...(session.metadata?.replyContext ?? {}),
104
- ...(session.threadId ? { threadId: session.threadId } : {}),
107
+ // 线上只带话题 id:主话题 id 是"本端+对端"拼出来的身份,发出去会让对端
108
+ // 凭空建一个叫 `main:…` 的话题。主话题的线上形态就是"不带话题字段"。
109
+ ...(topicThreadId ? { threadId: topicThreadId } : {}),
110
+ // 本地日志要的是完整身份(含主话题规范 id),单独用不线上的字段携带。
111
+ ...(localTopicId ? { localTopicId } : {}),
105
112
  ...(context ?? {}),
106
113
  // Session chatType/groupId is the persisted routing authority. The nested
107
114
  // reply context can outlive a route correction, so only an explicit
@@ -1,6 +1,7 @@
1
1
  import { loadSchema } from '../../config/schema-registry.js';
2
2
  import { resolveEffective } from '../../config/config-manager.js';
3
3
  import { resolveRuntimeStringField } from '../role/runtime-policy.js';
4
+ import { isManagementRole } from '../../config/builtin-roles.js';
4
5
  /** Resolve the effective chat mode from the ConfigManager layering context. */
5
6
  export function resolveChatMode(params) {
6
7
  let configured;
@@ -20,6 +21,11 @@ export function resolveChatMode(params) {
20
21
  if (kind === 'agent')
21
22
  return 'proactive';
22
23
  const field = chatmodeFieldForPeer(params.chatType, params.peerType);
24
+ // 人类私聊的角色门槛(硬约束):interactive 是「输出即回复」的高信任模式,
25
+ // 只有 owner/admin 才走下面的分层配置;其余角色(含未分配/未知)直接 proactive,
26
+ // 关系级或 agent 级把 chatmode.private 写成 interactive 都不生效。
27
+ if (isPrivateChatmodeRoleLocked(field, params.role))
28
+ return 'proactive';
23
29
  const decision = resolveRuntimeStringField({
24
30
  selfAid: params.self,
25
31
  role: params.role,
@@ -32,16 +38,18 @@ export function resolveChatMode(params) {
32
38
  : chatmodeFactoryDefaults()[field];
33
39
  }
34
40
  /**
35
- * 出站发送路径(`ec msg send` / daemon 私聊发送)用的最小判定。
36
- *
37
- * 这两条路径只有对端身份、拿不到角色,因此不能复用 `resolveChatMode` 的
38
- * 分层配置。口径与 `resolveChatMode` 的硬约束保持一致:
41
+ * 人类私聊(chatmode.private)是否被角色锁定为 proactive。
42
+ * owner/admin 之外的任何角色(含 null/'none'/未知)都锁定。
43
+ */
44
+ export function isPrivateChatmodeRoleLocked(field, role) {
45
+ return field === 'private' && !isManagementRole(role);
46
+ }
47
+ /**
48
+ * 出站发送路径(`ec msg send` / daemon 私聊发送)用的判定,与入站共用
49
+ * `resolveChatMode` 的硬约束:
39
50
  * - 非人类对端(isMachine)→ proactive
40
- * - 人类但角色未知 → proactive(保守侧)
41
- * - 人类且 owner/admin → interactive
42
- *
43
- * 角色未知时不能假设高信任:interactive 表示"输出即回复"的同步交互,
44
- * 只应给已确认的管理者。
51
+ * - 人类且非 owner/admin(含角色未知)→ proactive
52
+ * - 人类且 owner/admin → 分层配置(未配置时取出厂默认)
45
53
  */
46
54
  export function chatmodeForPeerIdentity(identity, role) {
47
55
  if (identity.isMachine)