evolcore 0.0.29 → 0.0.30

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 (68) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/dist/agents/codex-runner.js +61 -14
  3. package/dist/aun/aid/agentmd.js +224 -19
  4. package/dist/aun/aid/index.js +2 -1
  5. package/dist/aun/aid/managed-operation.js +70 -4
  6. package/dist/aun/group/index.js +1 -0
  7. package/dist/{core → aun}/group/invite-service.js +4 -4
  8. package/dist/channels/daemon.js +1 -1
  9. package/dist/cli/agent-command.js +7 -7
  10. package/dist/cli/aun-commands.js +97 -37
  11. package/dist/cli/data-command.js +2 -1
  12. package/dist/cli/managed-command-guard.js +2 -2
  13. package/dist/cli/model.js +1 -1
  14. package/dist/cli/response.js +1 -1
  15. package/dist/cli/trigger-command.js +52 -29
  16. package/dist/config/boot-log.js +95 -1
  17. package/dist/config/builtin-roles.js +1 -0
  18. package/dist/config/peer-role-resolver.js +21 -0
  19. package/dist/core/agent-ipc-registration.js +409 -0
  20. package/dist/core/agent-runtime.js +435 -0
  21. package/dist/core/auth/delegation-authorization.js +58 -0
  22. package/dist/core/auth/operation-catalog.js +8 -0
  23. package/dist/core/bootstrap-service.js +65 -1
  24. package/dist/core/channel-runtime.js +435 -0
  25. package/dist/core/command/command-handler.js +4 -1
  26. package/dist/core/daemon-bootstrap.js +151 -0
  27. package/dist/core/daemon-control.js +205 -0
  28. package/dist/core/daemon-lifecycle.js +225 -0
  29. package/dist/core/data-migration.js +4 -1
  30. package/dist/core/handoff/ipc-registration.js +156 -0
  31. package/dist/core/ipc-registration.js +457 -0
  32. package/dist/core/memory/consolidation-trigger.js +8 -0
  33. package/dist/core/memory/consolidation.js +838 -0
  34. package/dist/core/message/aun-ipc-sender.js +226 -0
  35. package/dist/core/message/outbound.js +165 -0
  36. package/dist/core/permission/approval-gateway.js +13 -0
  37. package/dist/core/permission/ec-command-parser.js +80 -35
  38. package/dist/core/permission/sandbox-runtime.js +23 -2
  39. package/dist/core/permission/tool-error-code.js +1 -0
  40. package/dist/core/permission/tool-policy.js +61 -24
  41. package/dist/core/session/session-idle-monitor.js +17 -0
  42. package/dist/index.js +352 -4841
  43. package/dist/ipc.js +2 -1
  44. package/dist/stats/db.js +24 -0
  45. package/dist/stats/index.js +1 -1
  46. package/dist/stats/writer.js +25 -1
  47. package/dist/trigger/application-service.js +157 -0
  48. package/dist/trigger/authorization.js +371 -0
  49. package/dist/trigger/ipc-service.js +406 -0
  50. package/dist/trigger/local-files.js +133 -0
  51. package/dist/trigger/manager.js +235 -22
  52. package/dist/trigger/scheduler.js +30 -5
  53. package/dist/trigger/script-executor.js +176 -39
  54. package/dist/trigger/script-sandbox.js +458 -0
  55. package/dist/trigger/system-definitions.js +188 -0
  56. package/dist/trigger/validation.js +0 -3
  57. package/dist/utils/aid-bind.js +1 -1
  58. package/dist/utils/evolcore-version.js +74 -0
  59. package/dist/utils/logger.js +16 -0
  60. package/kits/docs/evolcore/INDEX.md +1 -1
  61. package/kits/docs/evolcore/aid.md +9 -1
  62. package/kits/docs/evolcore/model.md +3 -2
  63. package/kits/docs/evolcore/rpc.md +4 -0
  64. package/kits/docs/evolcore/trigger.md +15 -1
  65. package/kits/schemas/relation-config.schema.9.json +1 -1
  66. package/kits/templates/system-fragments/commands.md +0 -1
  67. package/package.json +1 -1
  68. package/dist/core/memory/launch-consolidation.js +0 -395
@@ -0,0 +1,435 @@
1
+ import { buildReloadHooks } from './channel-loader.js';
2
+ import { AgentReloadBusyError, AgentReloadCoordinator } from './agent-reload-coordinator.js';
3
+ import { detectDuplicates } from './evolagent-registry.js';
4
+ import { createRunnerReloadTransaction, disposeAgentInstances } from './runner-reload-transaction.js';
5
+ import { ConfigTarget, read as readConfig } from '../config/config-manager.js';
6
+ import { invalidateKitCache } from '../eck/kit-renderer.js';
7
+ import { logger } from '../utils/logger.js';
8
+ import fs from 'fs';
9
+ import path from 'path';
10
+ import { resolvePaths } from '../paths.js';
11
+ import { loadDaemonConfig } from '../config-store.js';
12
+ import { AgentInitializationService, readInitialization } from '../cli/agent-create.js';
13
+ export function loadStartupAgentRegistry(agentRegistry) {
14
+ agentRegistry.loadAll();
15
+ const agentInfos = agentRegistry.list();
16
+ if (agentInfos.length === 0) {
17
+ const skipped = agentRegistry.getSkipped();
18
+ logger.info('✓ No self-agent configured; starting Control Plane only');
19
+ if (skipped.length > 0) {
20
+ logger.warn(`[startup] skipped ${skipped.length} agent director${skipped.length === 1 ? 'y' : 'ies'} while starting empty runtime`);
21
+ for (const entry of skipped)
22
+ logger.warn(` - ${entry.dirName}: ${entry.reason}`);
23
+ }
24
+ }
25
+ else {
26
+ logger.info(`✓ Loaded ${agentInfos.length} self-agent(s)`);
27
+ for (const info of agentInfos) {
28
+ if (info.status === 'error')
29
+ logger.error(` ✗ ${info.name}: ${info.error}`);
30
+ else if (info.status === 'disabled')
31
+ logger.info(` ○ ${info.name} (disabled)`);
32
+ else
33
+ logger.info(` ● ${info.name} ${info.baseagent} @ ${path.basename(info.projectPath)}`);
34
+ }
35
+ }
36
+ for (const duplicate of detectDuplicates(agentRegistry.runnableAgents())) {
37
+ const owners = duplicate.agents.map(owner => `${owner.aid}(${owner.channelName})`).join(', ');
38
+ logger.warn(`⚠ Duplicate channel credential: ${duplicate.fingerprint} claimed by ${owners}.`);
39
+ }
40
+ const primaryAgent = agentRegistry.runnableAgents()[0];
41
+ const runtimeStatus = { state: primaryAgent ? 'starting' : 'empty' };
42
+ if (!primaryAgent && agentInfos.length > 0) {
43
+ runtimeStatus.state = 'error';
44
+ runtimeStatus.error = 'No runnable self-agent (all are error/disabled).';
45
+ logger.warn(`[startup] ${runtimeStatus.error} Control Plane will remain available.`);
46
+ }
47
+ return { primaryAgent, runtimeStatus };
48
+ }
49
+ export async function createStartupAgentRunners({ agentRegistry, agentLoader, sessionManager, primaryAgent, }) {
50
+ const creationErrors = [];
51
+ const agentInstances = await agentLoader.createAll(agentRegistry, {
52
+ onSessionIdUpdate: async (sessionId, agentSessionId, context) => (await sessionManager.updateAgentSessionIdBySessionId(sessionId, agentSessionId, context?.turn)),
53
+ }, creationErrors);
54
+ const agentMap = new Map();
55
+ for (const instance of agentInstances) {
56
+ agentMap.set(`${instance.evolagentName}::${instance.baseagent}`, instance.agent);
57
+ }
58
+ const primaryBaseagent = primaryAgent?.baseagent ?? 'claude';
59
+ const primaryRunnerKey = primaryAgent ? `${primaryAgent.aid}::${primaryBaseagent}` : '<empty>::claude';
60
+ const agentRunner = primaryAgent ? agentMap.get(primaryRunnerKey) : agentInstances[0]?.agent;
61
+ let runtimeError;
62
+ if (primaryAgent && !agentRunner) {
63
+ const primaryCreationErrors = creationErrors.filter(error => error.evolagentName === primaryAgent.name);
64
+ const creationDetail = primaryCreationErrors.length > 0
65
+ ? primaryCreationErrors.map(({ baseagent, message }) => `${baseagent}: ${message}`).join('; ')
66
+ : `active_baseagent=${primaryAgent.config.active_baseagent ?? '(unset)'}, configured=${Object.keys(primaryAgent.config.baseagents ?? {}).join(',') || '(none)'}`;
67
+ runtimeError = primaryCreationErrors.length > 0
68
+ ? creationDetail
69
+ : `No agent runner created for ${primaryAgent.name}: ${creationDetail}`;
70
+ primaryAgent.status = 'error';
71
+ primaryAgent.error = runtimeError;
72
+ logger.error(runtimeError);
73
+ }
74
+ else if (!primaryAgent) {
75
+ logger.info('✓ Agent Runtime empty; no runners created');
76
+ }
77
+ else {
78
+ logger.info(`✓ Runners ready (primary key: ${primaryRunnerKey}, total: ${agentMap.size}, keys: ${[...agentMap.keys()].join(', ')})`);
79
+ }
80
+ return { agentInstances, agentMap, primaryBaseagent, primaryRunnerKey, runtimeError };
81
+ }
82
+ export async function completeStartupAgentRuntime(agentRegistry, current) {
83
+ try {
84
+ const { watchAgentSkills } = await import('./skill/startup.js');
85
+ for (const info of agentRegistry.list()) {
86
+ if (info.status === 'running')
87
+ watchAgentSkills(info.aid);
88
+ }
89
+ }
90
+ catch (error) {
91
+ logger.warn(`[skill] agent 监控注册失败: ${error instanceof Error ? error.message : String(error)}`);
92
+ }
93
+ if (agentRegistry.list().some(info => info.status === 'running')) {
94
+ return { state: 'running', error: undefined };
95
+ }
96
+ if (agentRegistry.runnableAgents().length === 0 && agentRegistry.list().length === 0) {
97
+ return { ...current, state: 'empty' };
98
+ }
99
+ if (current.state === 'starting') {
100
+ return { state: 'error', error: current.error ?? 'No runnable self-agent reached running state.' };
101
+ }
102
+ return current;
103
+ }
104
+ export function createAgentReloadRuntime({ agentRegistry, agentLoader, sessionManager, agentMap, permissionGateway, processor, channelLoader, channelInstances, registerChannelInstance, unregisterRuntimeChannel, markChannelConnected, bootstrapService, aidStatsCollector, handoffRuntime, messageQueue, }) {
105
+ const reloadHooks = buildReloadHooks({
106
+ channelLoader,
107
+ channelInstances,
108
+ registerChannelInstance,
109
+ unregisterChannelInstance: unregisterRuntimeChannel,
110
+ onChannelStarted: instance => {
111
+ if (instance.channelType === 'aun') {
112
+ const channel = instance.channel;
113
+ if (typeof channel?.setAidStatsCollector === 'function')
114
+ channel.setAidStatsCollector(aidStatsCollector);
115
+ }
116
+ },
117
+ onChannelConnected: markChannelConnected,
118
+ onAgentReloaded: async (agent) => {
119
+ const instance = channelInstances.find(candidate => {
120
+ const owner = agentRegistry.resolveByChannel(candidate.adapter.channelKey)
121
+ ?? agentRegistry.resolveByChannel(candidate.adapter.channelName);
122
+ return owner?.aid === agent.aid;
123
+ });
124
+ if (!instance)
125
+ return;
126
+ await bootstrapService.tryStartBootstrap({
127
+ adapter: instance.adapter,
128
+ channelKey: instance.adapter.channelKey,
129
+ channelType: instance.channelType,
130
+ agentAid: agent.aid,
131
+ source: 'owner-bound',
132
+ });
133
+ },
134
+ messageQueue,
135
+ handoffRuntime,
136
+ stageAgentRunners: async (candidate) => {
137
+ const creationErrors = [];
138
+ const staged = candidate.config.enabled === false
139
+ ? []
140
+ : await agentLoader.createForAgent(candidate, {
141
+ onSessionIdUpdate: async (sessionId, agentSessionId, context) => (await sessionManager.updateAgentSessionIdBySessionId(sessionId, agentSessionId, context?.turn)),
142
+ }, creationErrors);
143
+ const onDisposeError = (instance, error) => {
144
+ logger.warn(`[Reload] Failed to dispose runner ${instance.evolagentName}::${instance.baseagent}: ${error instanceof Error ? error.message : String(error)}`);
145
+ };
146
+ if (creationErrors.length > 0) {
147
+ await disposeAgentInstances(staged, onDisposeError);
148
+ throw new Error(creationErrors.map(error => `${error.baseagent}: ${error.message}`).join('; '));
149
+ }
150
+ if (candidate.config.enabled !== false && staged.length === 0 && candidate.config.active_baseagent) {
151
+ throw new Error(`No runner could be created for ${candidate.aid}::${candidate.config.active_baseagent}`);
152
+ }
153
+ for (const instance of staged) {
154
+ instance.agent.setPermissionGateway?.(permissionGateway);
155
+ instance.agent.setCompactStartCallback?.((sessionId) => processor.handleCompactStart(sessionId));
156
+ }
157
+ return createRunnerReloadTransaction({ aid: candidate.aid, agentMap, staged, onDisposeError });
158
+ },
159
+ });
160
+ const reloadOwnedMutes = new Set();
161
+ const reloadCoordinator = new AgentReloadCoordinator({
162
+ registry: agentRegistry,
163
+ hooks: reloadHooks,
164
+ getBusyCount: aid => {
165
+ const agentName = agentRegistry.get(aid)?.name;
166
+ if (!agentName)
167
+ return 0;
168
+ return messageQueue.getProcessingCountByAgent(agentName) + messageQueue.getQueueLengthByAgent(agentName);
169
+ },
170
+ beforeReload: invalidateKitCache,
171
+ forceReload: async (aid) => {
172
+ const agentName = agentRegistry.get(aid)?.name;
173
+ if (!agentName)
174
+ return;
175
+ messageQueue.clearByAgent(agentName);
176
+ await messageQueue.interruptByAgentAndWait(agentName);
177
+ const deadline = Date.now() + 5_000;
178
+ while (messageQueue.getProcessingCountByAgent(agentName) > 0) {
179
+ if (Date.now() >= deadline)
180
+ throw new Error(`Timed out waiting for Agent "${aid}" tasks to stop`);
181
+ await new Promise(resolve => setTimeout(resolve, 25));
182
+ }
183
+ },
184
+ acquireWorkGate: aid => {
185
+ const agentName = agentRegistry.get(aid)?.name;
186
+ if (!agentName)
187
+ return;
188
+ const inheritedReloadMute = reloadOwnedMutes.delete(agentName);
189
+ const alreadyMuted = messageQueue.isAgentMuted(agentName);
190
+ const ownsMute = inheritedReloadMute || !alreadyMuted;
191
+ if (!alreadyMuted)
192
+ messageQueue.muteAgent(agentName);
193
+ return () => {
194
+ const current = agentRegistry.get(aid);
195
+ if (!current) {
196
+ messageQueue.clearByAgent(agentName);
197
+ if (ownsMute)
198
+ messageQueue.unmuteAgent(agentName);
199
+ return;
200
+ }
201
+ if (current.status === 'disabled') {
202
+ messageQueue.clearByAgent(agentName);
203
+ if (ownsMute)
204
+ reloadOwnedMutes.add(agentName);
205
+ return;
206
+ }
207
+ if (ownsMute)
208
+ messageQueue.unmuteAgent(agentName);
209
+ };
210
+ },
211
+ });
212
+ return { reloadHooks, reloadCoordinator };
213
+ }
214
+ export function createAgentInitializationRuntime({ agentRegistry, agentMap, channelInstances, hotLoad, activate, }) {
215
+ const isConnected = (aid) => channelInstances.some(instance => instance.channelType === 'aun'
216
+ && instance.channel.getAidState?.()?.aid === aid
217
+ && instance.channel.getAidState?.()?.status === 'connected');
218
+ return new AgentInitializationService({
219
+ register: async (aid) => {
220
+ const { aidCreate } = await import('../aun/aid/identity.js');
221
+ const created = await aidCreate(aid);
222
+ await created.client.close();
223
+ },
224
+ publish: async (aid, content) => {
225
+ const { agentmdPut } = await import('../aun/aid/agentmd.js');
226
+ await agentmdPut(content, { aid });
227
+ },
228
+ fetch: async (aid) => {
229
+ const { agentmdGet } = await import('../aun/aid/agentmd.js');
230
+ const card = await agentmdGet(aid, { withVerification: true, remoteOnly: true });
231
+ return { content: card.content, verified: card.verification.status === 'verified' };
232
+ },
233
+ hotLoad: async (aid) => {
234
+ await hotLoad(aid);
235
+ const until = Date.now() + 20_000;
236
+ while (Date.now() < until) {
237
+ if (isConnected(aid))
238
+ return;
239
+ await new Promise(resolve => setTimeout(resolve, 250));
240
+ }
241
+ throw Object.assign(new Error('AUN connection unavailable'), { code: 'INITIALIZE_CHANNEL_UNAVAILABLE' });
242
+ },
243
+ activate,
244
+ authorize: actor => actor === 'local-control' || (loadDaemonConfig().owners ?? []).includes(actor),
245
+ usable: aid => {
246
+ const agent = agentRegistry.get(aid);
247
+ return !!agent && agent.config.enabled !== false && agentMap.has(`${aid}::${agent.baseagent}`) && isConnected(aid);
248
+ },
249
+ });
250
+ }
251
+ export function recoverIncompleteAgentInitializations(service, agentsDir = resolvePaths().agentsDir) {
252
+ for (const entry of fs.readdirSync(agentsDir, { withFileTypes: true })) {
253
+ if (!entry.isDirectory())
254
+ continue;
255
+ try {
256
+ const record = readInitialization(entry.name);
257
+ if (record?.status === 'in_progress')
258
+ service.resume(entry.name, record.actor);
259
+ }
260
+ catch {
261
+ logger.warn('[Initialization] Recovery requires review for an incomplete operation');
262
+ }
263
+ }
264
+ }
265
+ export function createAgentRuntimeOperations({ agentRegistry, agentLoader, sessionManager, agentMap, permissionGateway, processor, channelLoader, channelInstances, registerChannelInstance, markChannelConnected, aidStatsCollector, handoffRuntime, triggerSchedulers, ensureTriggerSchedulerStarted, startTriggerScheduler, reloadCoordinator, unregisterRuntimeChannel, messageQueue, setAgentRuntimeState, setAgentRuntimeError, }) {
266
+ const hotLoad = async (aid) => {
267
+ handoffRuntime.pauseAgent(aid);
268
+ let resumed = false;
269
+ try {
270
+ setAgentRuntimeState('starting');
271
+ setAgentRuntimeError(undefined);
272
+ const agent = agentRegistry.loadNewAgent(aid);
273
+ if (!agent) {
274
+ const state = agentRegistry.runnableAgents().length > 0 ? 'running' : 'error';
275
+ setAgentRuntimeState(state);
276
+ const error = `Failed to load agent ${aid}`;
277
+ setAgentRuntimeError(error);
278
+ throw new Error(error);
279
+ }
280
+ const creationErrors = [];
281
+ const newAgentInstances = await agentLoader.createForAgent(agent, {
282
+ onSessionIdUpdate: async (sessionId, agentSessionId, context) => (await sessionManager.updateAgentSessionIdBySessionId(sessionId, agentSessionId, context?.turn)),
283
+ }, creationErrors);
284
+ for (const instance of newAgentInstances) {
285
+ agentMap.set(`${instance.evolagentName}::${instance.baseagent}`, instance.agent);
286
+ instance.agent.setPermissionGateway?.(permissionGateway);
287
+ instance.agent.setCompactStartCallback?.((sessionId) => {
288
+ processor.handleCompactStart(sessionId);
289
+ });
290
+ }
291
+ if (newAgentInstances.length === 0) {
292
+ const creationDetail = creationErrors.length > 0
293
+ ? creationErrors.map(({ baseagent, message }) => `${baseagent}: ${message}`).join('; ')
294
+ : `active_baseagent=${agent.config.active_baseagent ?? '(unset)'}, configured=${Object.keys(agent.config.baseagents ?? {}).join(',') || '(none)'}`;
295
+ agent.status = 'error';
296
+ agent.error = creationErrors.length > 0
297
+ ? creationDetail
298
+ : `No baseagent runner created for hot-loaded agent: ${creationDetail}`;
299
+ setAgentRuntimeState('error');
300
+ setAgentRuntimeError(agent.error);
301
+ throw new Error(agent.error);
302
+ }
303
+ const instances = await channelLoader.createForAgent(agent);
304
+ for (const instance of instances) {
305
+ registerChannelInstance(instance);
306
+ if (instance.channelType === 'aun') {
307
+ const channel = instance.channel;
308
+ if (typeof channel?.setAidStatsCollector === 'function')
309
+ channel.setAidStatsCollector(aidStatsCollector);
310
+ }
311
+ agent.channels.set(instance.adapter.channelKey, instance.adapter);
312
+ channelInstances.push(instance);
313
+ }
314
+ agent.status = 'running';
315
+ await channelLoader.connectAll(instances, { onConnected: markChannelConnected });
316
+ await handoffRuntime.recover([aid], { discardExpired: false });
317
+ await ensureTriggerSchedulerStarted(agent);
318
+ handoffRuntime.resumeAgent(aid);
319
+ resumed = true;
320
+ setAgentRuntimeState('running');
321
+ setAgentRuntimeError(undefined);
322
+ logger.info(`[HotLoad] ✓ Agent ${aid} online with ${instances.length} channel(s)`);
323
+ }
324
+ finally {
325
+ if (!resumed)
326
+ handoffRuntime.resumeAgent(aid);
327
+ }
328
+ };
329
+ let resyncInFlight;
330
+ const resync = async () => {
331
+ if (resyncInFlight)
332
+ return resyncInFlight;
333
+ const operation = (async () => {
334
+ invalidateKitCache();
335
+ const { loadAllAgents: scanAgents } = await import('../config-store.js');
336
+ const { agents: diskAgents } = scanAgents();
337
+ const results = [];
338
+ for (const [aid] of [...agentRegistry.agents.entries()]) {
339
+ const diskCfg = diskAgents.find(agent => agent.aid === aid);
340
+ if (!diskCfg || diskCfg.enabled === false) {
341
+ try {
342
+ const offlined = await reloadCoordinator.runExclusive(aid, async () => {
343
+ const latestDiskCfg = readConfig(ConfigTarget.Agent, { self: aid }, { cache: false, expand: true });
344
+ if (latestDiskCfg && latestDiskCfg.enabled !== false)
345
+ return false;
346
+ const runtimeAgent = agentRegistry.get(aid);
347
+ if (!runtimeAgent)
348
+ return false;
349
+ let removed = false;
350
+ handoffRuntime.pauseAgent(aid);
351
+ try {
352
+ await handoffRuntime.drainAgent(aid);
353
+ await triggerSchedulers.remove(aid);
354
+ for (const channelName of runtimeAgent.channelInstanceNames()) {
355
+ const instance = channelInstances.find(candidate => candidate.adapter.channelName === channelName);
356
+ if (instance) {
357
+ try {
358
+ await instance.disconnect();
359
+ }
360
+ catch { }
361
+ const index = channelInstances.indexOf(instance);
362
+ if (index >= 0)
363
+ channelInstances.splice(index, 1);
364
+ }
365
+ unregisterRuntimeChannel(channelName);
366
+ }
367
+ agentRegistry.agents.delete(aid);
368
+ messageQueue.clearByAgent(runtimeAgent.name);
369
+ removed = true;
370
+ return true;
371
+ }
372
+ finally {
373
+ if (!removed)
374
+ handoffRuntime.resumeAgent(aid);
375
+ }
376
+ });
377
+ if (offlined)
378
+ results.push(`- ${aid} (offline)`);
379
+ }
380
+ catch (error) {
381
+ if (error instanceof AgentReloadBusyError) {
382
+ results.push(`⏸ ${aid} (busy: ${error.busyCount} task(s); reload skipped)`);
383
+ }
384
+ else {
385
+ results.push(`⚠ ${aid}: ${error instanceof Error ? error.message : String(error)}`);
386
+ }
387
+ }
388
+ continue;
389
+ }
390
+ }
391
+ for (const config of diskAgents) {
392
+ if (config.enabled === false)
393
+ continue;
394
+ if (agentRegistry.agents.has(config.aid))
395
+ continue;
396
+ try {
397
+ await hotLoad(config.aid);
398
+ results.push(`+ ${config.aid} (online)`);
399
+ }
400
+ catch (error) {
401
+ results.push(`✗ ${config.aid}: ${error?.message || error}`);
402
+ }
403
+ }
404
+ for (const config of diskAgents) {
405
+ if (config.enabled === false)
406
+ continue;
407
+ if (!agentRegistry.agents.has(config.aid))
408
+ continue;
409
+ try {
410
+ await reloadCoordinator.reload(config.aid);
411
+ const runtimeAgent = agentRegistry.get(config.aid);
412
+ if (runtimeAgent)
413
+ await startTriggerScheduler(runtimeAgent);
414
+ results.push(`↻ ${config.aid} (reloaded)`);
415
+ }
416
+ catch (error) {
417
+ results.push(`⚠ ${config.aid}: ${error?.message || error}`);
418
+ }
419
+ }
420
+ agentRegistry.channelIndex.clear();
421
+ agentRegistry.buildChannelIndex();
422
+ logger.info(`[Resync] Done: ${results.length} agent(s) processed`);
423
+ return results;
424
+ })();
425
+ resyncInFlight = operation;
426
+ try {
427
+ return await operation;
428
+ }
429
+ finally {
430
+ if (resyncInFlight === operation)
431
+ resyncInFlight = undefined;
432
+ }
433
+ };
434
+ return { hotLoad, resync };
435
+ }
@@ -0,0 +1,58 @@
1
+ import { authorizeOperation, buildAuthSubject } from './auth-gateway.js';
2
+ import { trustedExecutionIdentityInput } from './agent-delegation.js';
3
+ import { isManagementRole } from '../../config/builtin-roles.js';
4
+ import { parsePeerKey } from '../relation/peer-identity.js';
5
+ export function createDelegationAuthorizer({ agentDelegationRegistry, sessionManager, getProcessOwners, }) {
6
+ return async function authorizeManagedAgentOperation(params, operation) {
7
+ const delegation = agentDelegationRegistry.validate(params.delegationToken, params.sessionId, params.delegationCommandHash);
8
+ if (!delegation.ok)
9
+ return { ok: false, code: delegation.code, error: delegation.reason };
10
+ const session = await sessionManager.getSessionById(params.sessionId);
11
+ if (!session || session.selfAID !== delegation.grant.selfAid) {
12
+ return { ok: false, code: 'INVALID_DELEGATION', error: 'delegation does not match the current session' };
13
+ }
14
+ let conversationId;
15
+ try {
16
+ conversationId = parsePeerKey(delegation.grant.peerKey).channelId;
17
+ }
18
+ catch {
19
+ return { ok: false, code: 'INVALID_DELEGATION', error: 'delegation contains an invalid relation key' };
20
+ }
21
+ const subject = buildAuthSubject({
22
+ selfAid: delegation.grant.selfAid,
23
+ actorId: delegation.grant.actorId,
24
+ channel: delegation.grant.channel,
25
+ channelType: delegation.grant.channelType,
26
+ channelId: conversationId,
27
+ chatType: delegation.grant.chatType,
28
+ conversationId,
29
+ processOwners: getProcessOwners(),
30
+ ...trustedExecutionIdentityInput(delegation.grant),
31
+ });
32
+ if (params.targetAid
33
+ && params.targetAid !== delegation.grant.selfAid
34
+ && subject.dataScope !== 'daemon'
35
+ && !(operation === 'agent.show' && isManagementRole(subject.role))) {
36
+ return { ok: false, code: 'SCOPE_MISMATCH', error: 'only the current Agent can be targeted' };
37
+ }
38
+ const decision = authorizeOperation({
39
+ source: 'agent-tool',
40
+ subject,
41
+ intent: {
42
+ operation,
43
+ scope: operation === 'system.status' ? 'process' : 'agent',
44
+ source: 'agent-tool',
45
+ args: { self: delegation.grant.selfAid, aid: params.targetAid ?? delegation.grant.selfAid },
46
+ },
47
+ auditMetadata: { taskId: delegation.grant.taskId, messageId: delegation.grant.messageId },
48
+ });
49
+ return decision.allow
50
+ ? {
51
+ ok: true,
52
+ aid: subject.dataScope === 'daemon' ? (params.targetAid ?? delegation.grant.selfAid) : delegation.grant.selfAid,
53
+ dataScope: subject.dataScope,
54
+ management: isManagementRole(subject.role),
55
+ }
56
+ : { ok: false, code: decision.code, error: decision.reason };
57
+ };
58
+ }
@@ -1094,6 +1094,14 @@ const OPERATIONS = [
1094
1094
  description: 'Sign and publish the current Agent local agent.md',
1095
1095
  sources: ['agent-tool'],
1096
1096
  },
1097
+ {
1098
+ id: 'ec.aid.agentmd.set',
1099
+ category: 'write-agent',
1100
+ dangerous: true,
1101
+ defaultScopes: ['agent'],
1102
+ description: 'Update one field and publish the current Agent agent.md',
1103
+ sources: ['agent-tool'],
1104
+ },
1097
1105
  {
1098
1106
  id: 'ec.aid.avatar',
1099
1107
  category: 'write-agent',
@@ -10,8 +10,9 @@ import { loadAgent, saveAgent } from '../config-store.js';
10
10
  import { resolveAgentLifecycle, withLifecycleForWrite } from '../config/lifecycle.js';
11
11
  import { renderTemplate } from '../eck/manifest-engine.js';
12
12
  import { activeBaseagent } from './model/config-scope.js';
13
+ import { getFirstStaticAgentOwner } from '../config/peer-role-resolver.js';
13
14
  import { buildEnvelope } from './message/message-utils.js';
14
- import { BOOTSTRAP_MESSAGE_TTL_MS, BOOTSTRAP_NOTIFICATION_WINDOW_MS, bootstrapInitialMessageOperationId, bootstrapInitialMessageOperationPrefix, bootstrapInitialRetryOperationId, cancelPendingBootstrapPrompts, postBootstrapWelcomeStatus, } from './bootstrap-messages.js';
15
+ import { BOOTSTRAP_MESSAGE_TTL_MS, BOOTSTRAP_NOTIFICATION_WINDOW_MS, bootstrapInitialMessageOperationId, bootstrapInitialMessageOperationPrefix, bootstrapInitialRetryOperationId, cancelPendingBootstrapPrompts, postBootstrapWelcomeStatus, postBootstrapWelcomeOperationId, } from './bootstrap-messages.js';
15
16
  import { findLatestMessageLogOperation } from './message/message-log.js';
16
17
  import * as outbox from '../aun/outbox.js';
17
18
  import { chatDirPath } from './session/session-fs-store.js';
@@ -141,6 +142,69 @@ export async function completeBootstrapWithWelcome(service, aid, welcome, sessio
141
142
  mainSessionCreated: mainSession?.created,
142
143
  };
143
144
  }
145
+ export function createBootstrapOwnerBoundHandler({ getService, agentRegistry, channelInstances, eventBus, }) {
146
+ return (agentAid, ownerAid) => {
147
+ const service = getService();
148
+ if (!service)
149
+ return;
150
+ const instance = channelInstances.find(candidate => {
151
+ const owner = agentRegistry.resolveByChannel(candidate.adapter.channelKey)
152
+ ?? agentRegistry.resolveByChannel(candidate.adapter.channelName);
153
+ return owner?.aid === agentAid;
154
+ });
155
+ if (!instance)
156
+ return;
157
+ eventBus.publish({ type: 'channel:owner-bound', channel: instance.channelType || instance.adapter.channelName, channelName: instance.adapter.channelName, userId: ownerAid });
158
+ void service.tryStartBootstrap({
159
+ adapter: instance.adapter,
160
+ channelKey: instance.adapter.channelKey,
161
+ channelType: instance.channelType,
162
+ agentAid,
163
+ recipientId: ownerAid,
164
+ channelId: instance.channelType === 'aun' ? ownerAid : undefined,
165
+ source: 'owner-bound',
166
+ }).catch(error => {
167
+ logger.warn(`[Bootstrap] Owner-bound start failed for ${agentAid}: ${error instanceof Error ? error.message : String(error)}`);
168
+ });
169
+ };
170
+ }
171
+ export function createBootstrapCompletionHandler({ service, agentRegistry, channelInstances, sessionManager, defaultProjectPath, primaryBaseagent, }) {
172
+ return async (aid) => {
173
+ const aunInstance = channelInstances.find(candidate => {
174
+ if (candidate.channelType !== 'aun')
175
+ return false;
176
+ const owner = agentRegistry.resolveByChannel(candidate.adapter.channelKey)
177
+ ?? agentRegistry.resolveByChannel(candidate.adapter.channelName);
178
+ return owner?.aid === aid;
179
+ });
180
+ const welcome = aunInstance?.channel;
181
+ const owningAgent = agentRegistry.get(aid);
182
+ const firstOwnerAid = getFirstStaticAgentOwner(aid);
183
+ const sessions = aunInstance && firstOwnerAid && owningAgent
184
+ ? {
185
+ ensurePostBootstrapMainSession: async (owner) => {
186
+ const ownerAid = owner || firstOwnerAid;
187
+ const channel = aunInstance.adapter.channelName;
188
+ const active = await sessionManager.getActiveSession(channel, ownerAid);
189
+ const ensured = await sessionManager.ensureNewMainSessionForOperation({
190
+ channel,
191
+ channelId: ownerAid,
192
+ projectPath: active?.projectPath || owningAgent.projectPath || defaultProjectPath,
193
+ baseagent: owningAgent.baseagent || active?.baseagent || primaryBaseagent,
194
+ operationId: postBootstrapWelcomeOperationId(aid, ownerAid),
195
+ identityMetadata: {
196
+ ...(active?.metadata ?? {}),
197
+ channelKey: aunInstance.adapter.channelKey,
198
+ peerId: ownerAid,
199
+ },
200
+ });
201
+ return { sessionId: ensured.session.id, created: ensured.created };
202
+ },
203
+ }
204
+ : undefined;
205
+ return completeBootstrapWithWelcome(service, aid, welcome, sessions);
206
+ };
207
+ }
144
208
  export class BootstrapService {
145
209
  agentRegistry;
146
210
  eventBus;