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
@@ -1,308 +1,28 @@
1
- import crypto from 'crypto';
2
- import { loadAgent, loadDaemonConfig, saveAgent, saveDaemonConfig } from '../config-store.js';
3
- import { resolveEffective } from '../config/config-manager.js';
4
- import { readInstalledEvolcoreVersion as readEvolcoreVersion } from './evolcore-version.js';
1
+ import { randomUUID } from 'crypto';
2
+ const BIND_TTL_MS = 10 * 60 * 1000;
3
+ const deferredMessage = 'Evol App ownership binding is deferred; use ec aid bind <ownerAid> [--subject <subjectAid>]';
5
4
  export class BindService {
6
- opts;
7
- tasks = new Map();
8
- taskIdsByTokenHash = new Map();
9
- cleanupTimer = null;
10
- constructor(opts) {
11
- this.opts = opts;
12
- }
13
- startCleanup() {
14
- if (this.cleanupTimer)
15
- return;
16
- this.cleanupTimer = setInterval(() => this.cleanup(), 60_000);
17
- this.cleanupTimer.unref?.();
18
- }
19
- stopCleanup() {
20
- if (!this.cleanupTimer)
21
- return;
22
- clearInterval(this.cleanupTimer);
23
- this.cleanupTimer = null;
24
- }
25
- begin(req) {
26
- if (req.bindType !== 'daemon' && req.bindType !== 'agent') {
27
- return { ok: false, code: 'INVALID_BIND_TYPE', error: 'invalid bindType' };
28
- }
29
- const targetAid = req.bindType === 'daemon' ? (req.targetAid ?? this.opts.receiverAid) : req.targetAid;
30
- if (!targetAid || typeof targetAid !== 'string') {
31
- return { ok: false, code: 'MISSING_TARGET', error: 'missing targetAid' };
32
- }
33
- if (req.bindType === 'daemon' && targetAid !== this.opts.receiverAid) {
34
- return { ok: false, code: 'TARGET_MISMATCH', error: 'daemon targetAid must equal control AID' };
35
- }
36
- if (req.bindType === 'agent' && !loadAgent(targetAid)) {
37
- return { ok: false, code: 'TARGET_NOT_FOUND', error: `agent not found: ${targetAid}` };
38
- }
39
- const taskId = crypto.randomUUID();
40
- let token = generatePublicToken();
41
- let tokenHash = hashToken(token);
42
- while (this.taskIdsByTokenHash.has(tokenHash)) {
43
- token = generatePublicToken();
44
- tokenHash = hashToken(token);
45
- }
46
- const ttlMs = clampTtl(req.ttlMs);
47
- const expiresAt = Date.now() + ttlMs;
48
- const task = {
49
- taskId,
50
- bindType: req.bindType,
51
- receiverAid: this.opts.receiverAid,
52
- targetAid,
53
- agentName: req.agentName,
54
- tokenHash,
55
- createdAt: Date.now(),
56
- expiresAt,
57
- status: 'pending',
58
- };
59
- this.tasks.set(taskId, task);
60
- this.taskIdsByTokenHash.set(tokenHash, taskId);
61
- this.cleanup();
5
+ constructor(_options) { }
6
+ startCleanup() { }
7
+ stopCleanup() { }
8
+ begin(_request) {
62
9
  return {
63
10
  ok: true,
64
- taskId,
65
- expiresAt,
66
- qrData: {
67
- type: 'evolcore.bind',
68
- version: readEvolcoreVersion(),
69
- bindType: req.bindType,
70
- daemonAid: this.opts.receiverAid,
71
- ...(req.bindType === 'daemon' ? { platform: getPlatform() } : {}),
72
- ...(req.bindType === 'agent' ? { agentAid: targetAid, agentName: req.agentName ?? null } : {}),
73
- token,
74
- expiresAt,
75
- },
11
+ taskId: randomUUID(),
12
+ qrData: {},
13
+ expiresAt: Date.now() + BIND_TTL_MS,
76
14
  };
77
15
  }
78
- status(taskId) {
79
- if (!taskId)
80
- return { ok: false, code: 'MISSING_TASK', error: 'missing taskId' };
81
- const task = this.tasks.get(taskId);
82
- if (!task)
83
- return { ok: false, code: 'TASK_NOT_FOUND', error: 'binding task not found' };
84
- if (task.status === 'pending' && Date.now() > task.expiresAt) {
85
- task.status = 'expired';
86
- task.finishedAt ??= task.expiresAt;
87
- }
88
- return {
89
- ok: true,
90
- taskId: task.taskId,
91
- status: task.status,
92
- boundAid: task.boundAid,
93
- boundName: task.boundName,
94
- };
16
+ status(_taskId) {
17
+ return { ok: false, code: 'OWNERSHIP_APP_DEFERRED', error: deferredMessage };
95
18
  }
96
- cancel(taskId) {
97
- if (!taskId)
98
- return { ok: false, code: 'MISSING_TASK', error: 'missing taskId' };
99
- const task = this.tasks.get(taskId);
100
- if (!task)
101
- return { ok: false, code: 'TASK_NOT_FOUND', error: 'binding task not found' };
102
- if (task.status === 'pending') {
103
- task.status = 'cancelled';
104
- task.finishedAt = Date.now();
105
- }
106
- return {
107
- ok: true,
108
- taskId: task.taskId,
109
- status: task.status,
110
- boundAid: task.boundAid,
111
- boundName: task.boundName,
112
- };
19
+ cancel(_taskId) {
20
+ return { ok: false, code: 'OWNERSHIP_APP_DEFERRED', error: deferredMessage };
113
21
  }
114
- async handleRequest(payload, fromAid) {
22
+ async handleRequest(payload, _fromAid) {
115
23
  if (payload?.type !== 'bind.request')
116
24
  return null;
117
- const bindType = payload.bindType === 'agent' ? 'agent' : 'daemon';
118
- const fail = (code, message) => ({
119
- type: 'bind.response',
120
- bindType,
121
- success: false,
122
- error: { code, message },
123
- });
124
- if (!payload.token)
125
- return fail('INVALID_TOKEN', 'invalid token');
126
- const taskId = this.taskIdsByTokenHash.get(hashToken(payload.token));
127
- if (!taskId)
128
- return fail('TASK_NOT_FOUND', 'binding task not found');
129
- const task = this.tasks.get(taskId);
130
- if (!task)
131
- return fail('TASK_NOT_FOUND', 'binding task not found');
132
- if (payload.bindType !== task.bindType)
133
- return fail('BIND_TYPE_MISMATCH', 'bindType mismatch');
134
- if (!fromAid)
135
- return fail('INTERNAL_ERROR', 'missing AUN sender');
136
- if (task.status === 'pending' && Date.now() > task.expiresAt) {
137
- task.status = 'expired';
138
- task.finishedAt ??= task.expiresAt;
139
- return fail('TASK_EXPIRED', 'binding task expired');
140
- }
141
- if (task.status === 'bound') {
142
- // The owner write and task transition necessarily happen before the caller
143
- // can send bind.response. If that send fails, the App retries the same
144
- // authenticated request. Replay the original success only to the AID that
145
- // completed the bind; other senders must not receive daemonInfo.
146
- if (task.boundAid === fromAid && task.boundResponse)
147
- return task.boundResponse;
148
- return fail('ALREADY_BOUND', 'binding task already completed');
149
- }
150
- if (task.status !== 'pending')
151
- return fail('TASK_NOT_FOUND', 'binding task is not pending');
152
- if (!timingSafeTokenEqual(payload.token, task.tokenHash)) {
153
- return fail('INVALID_TOKEN', 'invalid token');
154
- }
155
- try {
156
- // Build and retain the exact response before committing the bind so a
157
- // post-commit transport failure can be recovered by an idempotent retry.
158
- const response = this.buildSuccessResponse(task);
159
- this.applyOwnerBinding(task, fromAid);
160
- task.status = 'bound';
161
- task.boundAid = fromAid;
162
- task.boundName = payload.clientName;
163
- task.boundResponse = response;
164
- task.finishedAt = Date.now();
165
- return response;
166
- }
167
- catch (e) {
168
- return fail('INTERNAL_ERROR', e?.message || String(e));
169
- }
170
- }
171
- buildSuccessResponse(task) {
172
- const details = task.bindType === 'agent' ? this.buildAgentBindDetails(task) : null;
173
- return {
174
- type: 'bind.response',
175
- bindType: task.bindType,
176
- success: true,
177
- daemonInfo: {
178
- daemonAid: task.receiverAid,
179
- ...(task.bindType === 'agent' ? { agentAid: task.targetAid } : {}),
180
- bindType: task.bindType,
181
- ...(task.bindType === 'daemon'
182
- ? {
183
- version: readEvolcoreVersion(),
184
- platform: getPlatform(),
185
- uptime: this.opts.getUptimeSeconds(),
186
- baseagents: this.opts.getAvailableBaseagents(),
187
- }
188
- : {
189
- active_baseagent: details.baseagent.active_baseagent,
190
- model: details.baseagent.model,
191
- effort: details.baseagent.effort,
192
- channels: details.channels,
193
- agentName: task.agentName,
194
- }),
195
- },
196
- };
197
- }
198
- buildAgentBindDetails(task) {
199
- const agent = loadAgent(task.targetAid);
200
- const config = resolveEffective({ self: task.targetAid }, { expand: true });
201
- return {
202
- baseagent: resolveBaseagentInfo(config),
203
- channels: summarizeChannels(agent?.channels ?? []),
204
- };
205
- }
206
- applyOwnerBinding(task, ownerAid) {
207
- if (task.bindType === 'daemon') {
208
- const cfg = loadDaemonConfig();
209
- const current = cfg.owners ?? [];
210
- const owners = [ownerAid, ...current.filter(o => o !== ownerAid)];
211
- saveDaemonConfig({ ...cfg, $schema_version: cfg.$schema_version ?? 1, owners });
212
- this.opts.onDaemonOwnersUpdated?.(owners);
213
- return;
214
- }
215
- const agent = loadAgent(task.targetAid);
216
- if (!agent)
217
- throw new Error(`agent not found: ${task.targetAid}`);
218
- const current = agent.owners ?? [];
219
- const owners = [ownerAid, ...current.filter(o => o !== ownerAid)];
220
- saveAgent({ ...agent, owners });
221
- this.opts.onAgentOwnerUpdated?.(task.targetAid, ownerAid);
222
- }
223
- cleanup() {
224
- const now = Date.now();
225
- for (const [id, task] of this.tasks) {
226
- if (task.status === 'pending' && now > task.expiresAt) {
227
- task.status = 'expired';
228
- task.finishedAt ??= task.expiresAt;
229
- }
230
- if (task.status === 'cancelled') {
231
- this.deleteTask(id);
232
- }
233
- else if (task.status === 'expired' && now - (task.finishedAt ?? task.expiresAt) > 10 * 60_000) {
234
- this.deleteTask(id);
235
- }
236
- else if (task.status === 'bound' && task.finishedAt && now - task.finishedAt > 10 * 60_000) {
237
- this.deleteTask(id);
238
- }
239
- }
240
- if (this.tasks.size <= 128)
241
- return;
242
- const sorted = [...this.tasks.values()].sort((a, b) => a.createdAt - b.createdAt);
243
- for (const task of sorted) {
244
- if (this.tasks.size <= 128)
245
- break;
246
- if (task.status !== 'pending')
247
- this.deleteTask(task.taskId);
248
- }
249
- }
250
- deleteTask(taskId) {
251
- const task = this.tasks.get(taskId);
252
- if (!task)
253
- return;
254
- this.tasks.delete(taskId);
255
- this.taskIdsByTokenHash.delete(task.tokenHash);
256
- }
257
- }
258
- function clampTtl(ttlMs) {
259
- if (!ttlMs || !Number.isFinite(ttlMs))
260
- return 10 * 60_000;
261
- return Math.max(60_000, Math.min(ttlMs, 10 * 60_000));
262
- }
263
- function hashToken(token) {
264
- return crypto.createHash('sha256').update(token, 'utf8').digest('hex');
265
- }
266
- function generatePublicToken() {
267
- return crypto.randomBytes(16).toString('base64url'); // 128 bits for stronger security
268
- }
269
- function timingSafeTokenEqual(token, expectedHash) {
270
- const actual = Buffer.from(hashToken(token), 'hex');
271
- const expected = Buffer.from(expectedHash, 'hex');
272
- if (actual.length !== expected.length)
273
- return false;
274
- return crypto.timingSafeEqual(actual, expected);
275
- }
276
- function resolveBaseagentInfo(config) {
277
- const baseagents = (config?.baseagents ?? {});
278
- const active_baseagent = ('active_baseagent' in (config ?? {}) && typeof config?.active_baseagent === 'string')
279
- ? config.active_baseagent
280
- : pickFirstBaseagent(baseagents);
281
- const block = active_baseagent ? baseagents[active_baseagent] : undefined;
282
- const model = typeof block?.model === 'string' ? block.model : undefined;
283
- const effort = typeof block?.effort === 'string'
284
- ? block.effort
285
- : (typeof block?.reasoning === 'string' ? block.reasoning : undefined);
286
- return { active_baseagent, model, effort };
287
- }
288
- function pickFirstBaseagent(baseagents) {
289
- for (const name of ['claude', 'codex', 'gemini', 'ecagent']) {
290
- if (baseagents[name] !== undefined)
291
- return name;
25
+ return { type: 'bind.response', bindType: payload.bindType === 'agent' ? 'agent' : 'daemon', success: false,
26
+ error: { code: 'OWNERSHIP_APP_DEFERRED', message: deferredMessage } };
292
27
  }
293
- return 'claude';
294
- }
295
- function summarizeChannels(channels) {
296
- return channels.map(ch => ({
297
- type: ch.type,
298
- name: ch.name,
299
- enabled: ch.enabled !== false,
300
- }));
301
- }
302
- function getPlatform() {
303
- if (process.platform === 'win32')
304
- return 'windows';
305
- if (process.platform === 'darwin')
306
- return 'macos';
307
- return 'linux';
308
28
  }
@@ -16,6 +16,7 @@ const MAX_AVATAR_RATIO = 1.25;
16
16
  const MAX_AVATAR_REDIRECTS = 3;
17
17
  const AVATAR_DOWNLOAD_TIMEOUT_MS = 10_000;
18
18
  const AVATAR_DNS_TIMEOUT_MS = 2_000;
19
+ const ALLOWED_AUN_STORAGE_PORTS = new Set(['9470', '9471', '39470']);
19
20
  const ALLOWED_AVATAR_MIMES = new Set(['image/png', 'image/jpeg', 'image/webp']);
20
21
  const MIME_EXT = {
21
22
  'image/png': 'png',
@@ -210,7 +211,7 @@ function isPrivateAddress(address) {
210
211
  }
211
212
  return false;
212
213
  }
213
- async function validateAvatarUrl(url) {
214
+ async function validateAvatarUrl(url, aunStorageHost) {
214
215
  let parsed;
215
216
  try {
216
217
  parsed = new URL(url);
@@ -224,8 +225,12 @@ async function validateAvatarUrl(url) {
224
225
  return { ok: false, reason: '头像 URL 缺少主机名' };
225
226
  if (parsed.username || parsed.password)
226
227
  return { ok: false, reason: '头像 URL 不允许携带认证信息' };
227
- if (parsed.port && parsed.port !== '443')
228
- return { ok: false, reason: '头像 URL 仅支持 HTTPS 默认端口' };
228
+ if (parsed.port && parsed.port !== '443') {
229
+ const isAunStorageRedirect = parsed.hostname === aunStorageHost
230
+ && ALLOWED_AUN_STORAGE_PORTS.has(parsed.port);
231
+ if (!isAunStorageRedirect)
232
+ return { ok: false, reason: '头像 URL 仅支持 HTTPS 默认端口' };
233
+ }
229
234
  const hostname = parsed.hostname.replace(/^\[|\]$/g, '');
230
235
  if (isPrivateAddress(hostname))
231
236
  return { ok: false, reason: '头像 URL 指向受限地址' };
@@ -250,10 +255,17 @@ async function validateAvatarUrl(url) {
250
255
  }
251
256
  async function downloadAvatarBuffer(sourceUrl) {
252
257
  let current = sourceUrl;
258
+ let aunStorageHost;
253
259
  for (let redirect = 0; redirect <= MAX_AVATAR_REDIRECTS; redirect++) {
254
- const checked = await validateAvatarUrl(current);
260
+ const checked = await validateAvatarUrl(current, aunStorageHost);
255
261
  if (!checked.ok)
256
262
  return fail(checked.reason, 'INVALID_ARGS');
263
+ if (redirect === 0) {
264
+ // Canonical AID URLs redirect to their domain's Storage service on a public service port.
265
+ const aidDomain = checked.url.hostname.split('.').slice(1);
266
+ if (aidDomain.length >= 2)
267
+ aunStorageHost = `storage.${aidDomain.join('.')}`;
268
+ }
257
269
  const controller = new AbortController();
258
270
  const timeout = setTimeout(() => controller.abort(), AVATAR_DOWNLOAD_TIMEOUT_MS);
259
271
  let response;
@@ -286,55 +286,120 @@ export function spawnDetachedNode(args, env = {}) {
286
286
  * In a checkout the monitor is executed through tsx, where a syntax error
287
287
  * anywhere in its import graph is fatal at load time — and the usual
288
288
  * `stdio: 'ignore'` would throw that diagnostic away. So stderr is captured,
289
- * and success requires the child to still be alive after it had a chance to
290
- * fail, not merely to have been spawned.
289
+ * and success requires the child to register as a monitor, not merely to have
290
+ * been spawned. A live but unregistered child at timeout is reported as
291
+ * pending, since it may still take ownership after the caller returns.
291
292
  *
292
293
  * `isAliveInRegistry` lets the caller define proof-of-life without dragging the
293
294
  * instance registry into this module.
294
295
  */
295
- export async function launchMonitorAndConfirm(args, env, isAliveInRegistry, hasOtherLiveMonitor, timeoutMs = 15_000) {
296
- const child = spawn(process.execPath, [...args], {
297
- detached: true,
298
- stdio: ['ignore', 'ignore', 'pipe'],
299
- windowsHide: isWindows,
300
- env: { ...process.env, ...env },
301
- });
296
+ export async function launchMonitorAndConfirm(args, env, isAliveInRegistry, hasOtherLiveMonitor, options = {}) {
297
+ const startedAt = Date.now();
298
+ const diagnostic = (message) => {
299
+ if (!options.logFilePath)
300
+ return;
301
+ try {
302
+ fs.appendFileSync(options.logFilePath, `[${new Date().toISOString()}] [requestId=${options.requestId ?? 'unknown'}] ${message}\n`);
303
+ }
304
+ catch { }
305
+ };
306
+ let logFd;
307
+ let child;
308
+ try {
309
+ if (options.logFilePath) {
310
+ fs.mkdirSync(path.dirname(options.logFilePath), { recursive: true });
311
+ logFd = fs.openSync(options.logFilePath, 'a');
312
+ }
313
+ child = spawn(process.execPath, [...args], {
314
+ detached: true,
315
+ stdio: logFd === undefined ? ['ignore', 'pipe', 'pipe'] : ['ignore', logFd, logFd],
316
+ windowsHide: isWindows,
317
+ env: { ...process.env, ...env },
318
+ });
319
+ }
320
+ catch (error) {
321
+ diagnostic(`Launch failed: ${error.message}`);
322
+ return { ok: false, reason: error.message };
323
+ }
324
+ finally {
325
+ if (logFd !== undefined)
326
+ fs.closeSync(logFd);
327
+ }
302
328
  let stderrTail = '';
303
- child.stderr?.on('data', (chunk) => {
304
- stderrTail = (stderrTail + chunk.toString()).slice(-2000);
305
- });
329
+ const finish = (result) => {
330
+ diagnostic(`Launch result: pid=${child.pid ?? 'unknown'} ok=${result.ok} pending=${result.pending ?? false} elapsedMs=${Date.now() - startedAt} reason=${result.reason ?? 'accepted'}`);
331
+ if (!result.ok && options.logFilePath) {
332
+ let tailFd;
333
+ try {
334
+ tailFd = fs.openSync(options.logFilePath, 'r');
335
+ const size = fs.fstatSync(tailFd).size;
336
+ const buffer = Buffer.alloc(Math.min(size, 2000));
337
+ fs.readSync(tailFd, buffer, 0, buffer.length, Math.max(0, size - buffer.length));
338
+ stderrTail = buffer.toString('utf8');
339
+ }
340
+ catch { }
341
+ finally {
342
+ if (tailFd !== undefined)
343
+ fs.closeSync(tailFd);
344
+ }
345
+ }
346
+ return { ...result, ...(!result.ok ? { stderrTail } : {}) };
347
+ };
348
+ child.stdout?.on('data', () => { });
349
+ child.stderr?.on('data', (chunk) => { stderrTail = (stderrTail + chunk.toString()).slice(-2000); });
306
350
  // Capturing stderr must not defeat detached-process semantics. A referenced
307
351
  // pipe keeps the caller's event loop alive until the monitor exits, which
308
352
  // makes restart commands wait for the monitor's full lifetime.
353
+ child.stdout?.unref?.();
309
354
  child.stderr?.unref?.();
310
355
  let exitCode = null;
356
+ let exited = false;
357
+ let exitSignal = null;
311
358
  let spawnError = null;
312
- child.on('exit', code => { exitCode = code; });
313
- child.on('error', err => { spawnError = err.message; });
359
+ child.on('exit', (code, signal) => {
360
+ exited = true;
361
+ exitCode = code;
362
+ exitSignal = signal;
363
+ diagnostic(`Child exited: pid=${child.pid ?? 'unknown'} exit=${code} signal=${signal ?? 'none'} elapsedMs=${Date.now() - startedAt}`);
364
+ });
365
+ child.on('error', err => {
366
+ spawnError = err.message;
367
+ diagnostic(`Child error: ${err.message}`);
368
+ });
314
369
  const childPid = child.pid;
315
370
  child.unref();
371
+ diagnostic(`Child spawned: pid=${childPid ?? 'unknown'}`);
316
372
  if (!childPid)
317
- return { ok: false, reason: 'spawn 未返回 PID' };
318
- const deadline = Date.now() + timeoutMs;
373
+ return finish({ ok: false, reason: 'spawn 未返回 PID' });
374
+ try {
375
+ options.onSpawn?.(childPid);
376
+ }
377
+ catch (error) {
378
+ diagnostic(`Spawn callback failed: ${error.message}`);
379
+ }
380
+ const accepted = () => isAliveInRegistry(childPid) || hasOtherLiveMonitor(childPid);
381
+ const deadline = Date.now() + (options.timeoutMs ?? 15_000);
319
382
  while (Date.now() < deadline) {
320
383
  await new Promise(resolve => setTimeout(resolve, 300));
384
+ if (accepted())
385
+ return finish({ ok: true, pid: childPid });
321
386
  if (spawnError)
322
- return { ok: false, reason: spawnError, stderrTail };
323
- if (exitCode !== null) {
324
- // Exiting cleanly is legitimate when another monitor already owns this
325
- // restart (single-instance election) — that one is alive and in charge.
326
- if (exitCode === 0 && hasOtherLiveMonitor(childPid))
327
- return { ok: true };
328
- return { ok: false, reason: `进程已退出,exit=${exitCode}`, stderrTail };
387
+ return finish({ ok: false, pid: childPid, reason: spawnError });
388
+ if (exited && !options.isPendingInRegistry?.()) {
389
+ return finish({ ok: false, pid: childPid, reason: `进程已退出,exit=${exitCode}, signal=${exitSignal ?? 'none'}` });
329
390
  }
330
- // Registering means the module graph loaded and the monitor started work.
331
- if (isAliveInRegistry(childPid))
332
- return { ok: true };
333
391
  }
334
- // Still running but not yet registered: a slow start is not a failure.
335
- if (isProcessRunning(childPid))
336
- return { ok: true };
337
- return { ok: false, reason: '进程未登记且已消失', stderrTail };
392
+ if (accepted())
393
+ return finish({ ok: true, pid: childPid });
394
+ if ((!exited && isProcessRunning(childPid)) || options.isPendingInRegistry?.()) {
395
+ return finish({
396
+ ok: false,
397
+ pid: childPid,
398
+ pending: true,
399
+ reason: '进程仍在运行但尚未登记为 restart-monitor',
400
+ });
401
+ }
402
+ return finish({ ok: false, pid: childPid, reason: '进程未登记且已消失' });
338
403
  }
339
404
  /**
340
405
  * Copy the monitor out of the package directory so an upgrade cannot replace it
@@ -867,10 +932,10 @@ export function commandExists(cmd) {
867
932
  * 因此优先选 PATHEXT 可执行后缀(.cmd/.exe/.bat/.com),都没有才退回首行。
868
933
  * 失败返回 null。不缓存——刚安装的命令需要重新探测。
869
934
  */
870
- export function resolveCommandPath(cmd) {
935
+ export function resolveCommandPath(cmd, env = process.env) {
871
936
  try {
872
937
  if (isWindows) {
873
- const r = spawnSync('where.exe', [cmd], { encoding: 'utf-8', stdio: 'pipe', windowsHide: true });
938
+ const r = spawnSync('where.exe', [cmd], { encoding: 'utf-8', stdio: 'pipe', windowsHide: true, env });
874
939
  if (r.status !== 0 || !r.stdout)
875
940
  return null;
876
941
  const candidates = r.stdout.split(/\r?\n/).map(s => s.trim()).filter(Boolean);
@@ -881,8 +946,18 @@ export function resolveCommandPath(cmd) {
881
946
  return runnable || candidates[0];
882
947
  }
883
948
  else {
884
- const out = execFileSync('which', [cmd], { encoding: 'utf-8', stdio: 'pipe' }).trim();
885
- return out || null;
949
+ const candidates = cmd.includes(path.sep)
950
+ ? [path.resolve(cmd)]
951
+ : (env.PATH ?? '').split(path.delimiter).map(directory => path.resolve(directory || '.', cmd));
952
+ for (const candidate of candidates) {
953
+ try {
954
+ fs.accessSync(candidate, fs.constants.X_OK);
955
+ if (fs.statSync(candidate).isFile())
956
+ return candidate;
957
+ }
958
+ catch { }
959
+ }
960
+ return null;
886
961
  }
887
962
  }
888
963
  catch {
@@ -196,7 +196,7 @@ export function createEcwebSupervisor(options) {
196
196
  catch { }
197
197
  }
198
198
  if (!launch) {
199
- options.logger.warn(`ECWeb 启动入口不存在,请安装 ${WEB_PACKAGE_NAME}@latest`);
199
+ options.logger.warn('ECWeb 启动入口不存在,请重新安装包含匹配 ECWeb 的 EvolCore 版本。');
200
200
  return;
201
201
  }
202
202
  const versionError = ecwebVersionRequirementError(version);
@@ -5,6 +5,13 @@ import { isWindows as platformIsWindows, resolveCommandPath } from './cross-plat
5
5
  import { MIN_ECWEB_VERSION, WEB_CLI_BIN, WEB_PACKAGE_NAME } from '../product.js';
6
6
  import { resolvePaths } from '../paths.js';
7
7
  import { ipcQuery } from '../ipc.js';
8
+ import { ecwebService } from '../config/daemon-services.js';
9
+ export function resolveEcwebExternalUrl(config) {
10
+ const aid = config.aid?.trim();
11
+ if (!aid || ecwebService(config.services)?.enabled !== true)
12
+ return null;
13
+ return `https://${aid}/proxy/ecweb`;
14
+ }
8
15
  /** EC Web owns the session-log reader, so older builds cannot safely serve a newer daemon. */
9
16
  export function isEcwebVersionSupported(version) {
10
17
  const normalized = version?.trim();
@@ -16,7 +23,7 @@ export function ecwebVersionRequirementError(version) {
16
23
  if (isEcwebVersionSupported(version))
17
24
  return null;
18
25
  const detected = version?.trim() || 'unknown';
19
- return `EC Web ${detected} 低于最低要求 ${MIN_ECWEB_VERSION},拒绝启动。请运行: npm install -g ${WEB_PACKAGE_NAME}@latest`;
26
+ return `EC Web ${detected} 低于最低要求 ${MIN_ECWEB_VERSION},拒绝启动。请升级 EvolCore 到包含匹配 EC Web 的版本。`;
20
27
  }
21
28
  function readEcwebPackageEntry(pkgJsonPath) {
22
29
  try {