evolcore 0.0.26 → 0.0.27

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 (169) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/bin/codex-managed-acl.ps1 +43 -0
  3. package/dist/agents/baseagent-availability.js +68 -0
  4. package/dist/agents/claude-runner.js +123 -22
  5. package/dist/agents/codex/app-server-client.js +63 -26
  6. package/dist/agents/codex-runner.js +316 -411
  7. package/dist/agents/ecagent-runner.js +170 -25
  8. package/dist/agents/gemini-runner.js +6 -0
  9. package/dist/agents/runner-types.js +50 -2
  10. package/dist/aun/aid/agentmd.js +73 -31
  11. package/dist/aun/aid/client.js +1 -1
  12. package/dist/aun/aid/control-aid.js +180 -1
  13. package/dist/aun/aid/identity.js +3 -3
  14. package/dist/aun/group/history.js +303 -0
  15. package/dist/aun/group/identity.js +43 -0
  16. package/dist/aun/group/index.js +5 -0
  17. package/dist/aun/{msg/group.js → group/operations.js} +161 -38
  18. package/dist/aun/{menu-client.js → menu/client.js} +12 -7
  19. package/dist/aun/menu/index.js +2 -0
  20. package/dist/aun/menu/transport.js +49 -0
  21. package/dist/aun/msg/index.js +2 -1
  22. package/dist/aun/msg/managed-operation.js +84 -18
  23. package/dist/aun/msg/mention-schema.js +51 -0
  24. package/dist/aun/msg/p2p.js +13 -8
  25. package/dist/aun/msg/upload.js +43 -88
  26. package/dist/aun/outbox.js +29 -7
  27. package/dist/aun/rpc/client.js +1 -0
  28. package/dist/aun/storage/client.js +39 -50
  29. package/dist/channels/aun.js +522 -233
  30. package/dist/channels/daemon.js +10 -6
  31. package/dist/cli/agent-command.js +108 -6
  32. package/dist/cli/agent-create.js +515 -2
  33. package/dist/cli/agent.js +109 -8
  34. package/dist/cli/aun-commands.js +191 -42
  35. package/dist/cli/config.js +8 -5
  36. package/dist/cli/contact.js +2 -2
  37. package/dist/cli/ctl-command.js +2 -2
  38. package/dist/cli/daemon-commands.js +35 -23
  39. package/dist/cli/diag.js +2 -2
  40. package/dist/cli/fs-command.js +3 -3
  41. package/dist/cli/handoff-command.js +6 -6
  42. package/dist/cli/index.js +8 -2
  43. package/dist/cli/init.js +34 -1
  44. package/dist/cli/managed-command-guard.js +2 -1
  45. package/dist/cli/menu-client.js +9 -5
  46. package/dist/cli/model.js +3 -3
  47. package/dist/cli/queue-command.js +2 -2
  48. package/dist/cli/response.js +2 -2
  49. package/dist/cli/restart-monitor.js +16 -2
  50. package/dist/cli/task-command.js +59 -0
  51. package/dist/cli/task-context.js +97 -49
  52. package/dist/cli/trigger-command.js +20 -10
  53. package/dist/config/builtin-roles.js +10 -0
  54. package/dist/config/config-field-policy.js +11 -0
  55. package/dist/config/config-manager.js +32 -4
  56. package/dist/config/config-operation-service.js +20 -5
  57. package/dist/config/contact-book-store.js +1 -1
  58. package/dist/config/contact-book.js +1 -1
  59. package/dist/config/contact-operation-service.js +1 -1
  60. package/dist/config/peer-role-resolver.js +1 -1
  61. package/dist/config/resolved-config-op.js +3 -0
  62. package/dist/config-store.js +1 -1
  63. package/dist/core/agent-application-service.js +5 -0
  64. package/dist/core/audit/event-key.js +17 -2
  65. package/dist/core/audit/log-integrity.js +34 -8
  66. package/dist/core/auth/agent-delegation.js +48 -14
  67. package/dist/core/auth/auth-gateway.js +2 -0
  68. package/dist/core/auth/authorization-audit.js +34 -38
  69. package/dist/core/auth/operation-authorizer.js +67 -5
  70. package/dist/core/auth/operation-catalog.js +110 -0
  71. package/dist/core/auth/remote-access-store.js +115 -9
  72. package/dist/core/bootstrap-messages.js +66 -7
  73. package/dist/core/bootstrap-service.js +110 -19
  74. package/dist/core/command/agent-control.js +88 -7
  75. package/dist/core/command/agent-storage-menu.js +443 -0
  76. package/dist/core/command/cli-intent-parser.js +19 -0
  77. package/dist/core/command/command-handler.js +76 -39
  78. package/dist/core/command/data-root-resolver.js +273 -0
  79. package/dist/core/command/group-menu.js +7 -2
  80. package/dist/core/command/menu-catalog.js +34 -4
  81. package/dist/core/command/menu-handler.js +348 -77
  82. package/dist/core/command/menu-protocol.js +40 -6
  83. package/dist/core/command/role-menu.js +2 -2
  84. package/dist/core/command/slash-handler.js +37 -22
  85. package/dist/core/event-catalog.js +4 -0
  86. package/dist/core/evolagent-registry.js +28 -2
  87. package/dist/core/group/invite-service.js +924 -0
  88. package/dist/core/interaction-router.js +22 -8
  89. package/dist/core/message/im-renderer.js +76 -15
  90. package/dist/core/message/message-bridge.js +94 -11
  91. package/dist/core/message/message-log.js +5 -3
  92. package/dist/core/message/message-queue.js +73 -41
  93. package/dist/core/message/response-engine.js +709 -117
  94. package/dist/core/permission/approval-gateway.js +36 -3
  95. package/dist/core/permission/ec-command-parser.js +61 -29
  96. package/dist/core/permission/protected-paths.js +134 -16
  97. package/dist/core/permission/sandbox-runtime.js +72 -3
  98. package/dist/core/permission/tool-error-code.js +15 -0
  99. package/dist/core/permission/tool-policy.js +7 -7
  100. package/dist/core/relation/peer-identity.js +57 -6
  101. package/dist/core/relation/shared-data.js +106 -0
  102. package/dist/core/role/runtime-policy.js +64 -0
  103. package/dist/core/runtime-lock.js +10 -26
  104. package/dist/core/session/runner-task-history.js +170 -0
  105. package/dist/core/session/session-manager.js +34 -6
  106. package/dist/core/session/session-mapper.js +41 -0
  107. package/dist/core/session/session-turn-coordinator.js +37 -7
  108. package/dist/core/session/session-turns.js +48 -7
  109. package/dist/core/task/task-recovery-service.js +176 -0
  110. package/dist/core/task/task-resume-operation.js +32 -0
  111. package/dist/eck/group-rules-sync.js +5 -3
  112. package/dist/eck/kit-renderer.js +1 -0
  113. package/dist/index.js +308 -54
  114. package/dist/ipc.js +141 -15
  115. package/dist/paths.js +17 -4
  116. package/dist/product.js +2 -0
  117. package/dist/trigger/event-source.js +19 -0
  118. package/dist/trigger/feedback.js +233 -40
  119. package/dist/trigger/parser.js +55 -5
  120. package/dist/trigger/patch.js +9 -0
  121. package/dist/trigger/scheduler.js +30 -4
  122. package/dist/trigger/validation.js +20 -3
  123. package/dist/utils/ecweb-utils.js +1 -0
  124. package/dist/utils/error-utils.js +40 -2
  125. package/dist/utils/logger.js +23 -13
  126. package/dist/utils/tool-summary.js +59 -3
  127. package/dist/utils/welcome.js +174 -1
  128. package/ecagent/dist/harness/agent-harness.js +3 -0
  129. package/ecagent/dist/harness/session/jsonl-storage.js +21 -2
  130. package/kits/docs/INDEX.md +0 -6
  131. package/kits/docs/channels/aun.md +15 -2
  132. package/kits/docs/channels/feishu.md +1 -1
  133. package/kits/docs/context-assembly.md +29 -17
  134. package/kits/docs/evolcore/INDEX.md +8 -3
  135. package/kits/docs/evolcore/agent-create.md +39 -7
  136. package/kits/docs/evolcore/agent.md +33 -1
  137. package/kits/docs/evolcore/aid.md +9 -1
  138. package/kits/docs/evolcore/config.md +40 -1
  139. package/kits/docs/evolcore/group-collaboration.md +1 -1
  140. package/kits/docs/evolcore/group.md +117 -2
  141. package/kits/docs/evolcore/menu-client.md +16 -0
  142. package/kits/docs/evolcore/msg.md +3 -0
  143. package/kits/docs/evolcore/storage.md +3 -0
  144. package/kits/docs/evolcore/task.md +14 -0
  145. package/kits/docs/evolcore/trigger.md +51 -3
  146. package/kits/docs/venues/group.md +5 -0
  147. package/kits/eck_manifest.json +27 -0
  148. package/kits/rules/01-overview.md +11 -7
  149. package/kits/rules/04-relation.md +2 -1
  150. package/kits/rules/05-venue.md +1 -1
  151. package/kits/rules/06-channel.md +6 -4
  152. package/kits/schemas/agent-config.schema.12.json +25 -1
  153. package/kits/schemas/daemon.schema.6.json +2 -2
  154. package/kits/schemas/defaults.schema.5.json +1 -1
  155. package/kits/schemas/relation-config.schema.1.json +1 -1
  156. package/kits/schemas/relation-config.schema.5.json +1 -1
  157. package/kits/schemas/role-config.schema.1.json +14 -0
  158. package/kits/templates/roles/member.json +5 -0
  159. package/kits/templates/roles/visitor.json +5 -0
  160. package/kits/templates/system-fragments/baseagent.md +1 -1
  161. package/kits/templates/system-fragments/bootstrap.md +26 -9
  162. package/kits/templates/system-fragments/channel.md +8 -44
  163. package/kits/templates/system-fragments/commands.md +10 -2
  164. package/kits/templates/system-fragments/group-unread-history.md +7 -0
  165. package/kits/templates/system-fragments/reply.md +56 -0
  166. package/package.json +7 -3
  167. package/dist/aun/group-identity.js +0 -9
  168. /package/dist/aun/{group-fs-download.js → group/fs-download.js} +0 -0
  169. /package/dist/aun/{msg/group-index.js → group/index-rpc.js} +0 -0
@@ -1,15 +1,17 @@
1
+ import { hasSuppliedInitialization } from '../cli/agent-create.js';
1
2
  import fs from 'fs';
2
3
  import path from 'path';
3
4
  import { randomBytes } from 'crypto';
4
5
  import { kitsTemplatesDir, agentMdPath, resolvePaths } from '../paths.js';
5
6
  import { buildInitialAgentMd, resolveAgentDisplayName } from '../aun/aid/agentmd.js';
6
7
  import { logger } from '../utils/logger.js';
8
+ import { readAgentWelcomeTemplate } from '../utils/welcome.js';
7
9
  import { loadAgent, saveAgent } from '../config-store.js';
8
10
  import { resolveAgentLifecycle, withLifecycleForWrite } from '../config/lifecycle.js';
9
11
  import { renderTemplate } from '../eck/manifest-engine.js';
10
12
  import { activeBaseagent } from './model/config-scope.js';
11
13
  import { buildEnvelope } from './message/message-utils.js';
12
- import { BOOTSTRAP_MESSAGE_TTL_MS, BOOTSTRAP_NOTIFICATION_WINDOW_MS, bootstrapInitialMessageOperationId, bootstrapInitialMessageOperationPrefix, bootstrapInitialRetryOperationId, } from './bootstrap-messages.js';
14
+ import { BOOTSTRAP_MESSAGE_TTL_MS, BOOTSTRAP_NOTIFICATION_WINDOW_MS, bootstrapInitialMessageOperationId, bootstrapInitialMessageOperationPrefix, bootstrapInitialRetryOperationId, cancelPendingBootstrapPrompts, postBootstrapWelcomeStatus, } from './bootstrap-messages.js';
13
15
  import { findLatestMessageLogOperation } from './message/message-log.js';
14
16
  import * as outbox from '../aun/outbox.js';
15
17
  import { chatDirPath } from './session/session-fs-store.js';
@@ -21,6 +23,21 @@ export async function completeBootstrapWithWelcome(service, aid, welcome, sessio
21
23
  return { ...result, reloaded: false };
22
24
  }
23
25
  if (lifecycle === 'bootstrapping') {
26
+ // Existing per-Owner records are immutable retry snapshots. New interactive
27
+ // Bootstrap completions require the text saved after Owner confirmation.
28
+ const config = loadAgent(aid);
29
+ const deliveries = config?.owners?.length ? postBootstrapWelcomeStatus(aid, config.owners).deliveries : [];
30
+ const alreadyPrepared = deliveries.length > 0 && deliveries.every(delivery => delivery.status !== 'not_prepared');
31
+ if (config?.initialization?.mode !== 'supplied' && !alreadyPrepared) {
32
+ try {
33
+ if (readAgentWelcomeTemplate(aid) === null) {
34
+ return { ok: false, code: 'WELCOME_REQUIRED', error: '请先在 Bootstrap 对话中生成欢迎文案,经 Owner 确认后保存 welcome.md', reloaded: false };
35
+ }
36
+ }
37
+ catch (error) {
38
+ return { ok: false, code: 'WELCOME_INVALID', error: error instanceof Error ? error.message : String(error), reloaded: false };
39
+ }
40
+ }
24
41
  if (!welcome) {
25
42
  return {
26
43
  ok: false,
@@ -41,7 +58,9 @@ export async function completeBootstrapWithWelcome(service, aid, welcome, sessio
41
58
  const welcomePending = welcome
42
59
  ? await welcome.hasPendingPostBootstrapWelcome()
43
60
  : false;
44
- const needsMainSessionBoundary = lifecycle === 'bootstrapping' || welcomePending;
61
+ const pendingOwners = welcome?.pendingPostBootstrapWelcomeOwners?.();
62
+ const needsMainSessionBoundary = lifecycle === 'bootstrapping'
63
+ || (welcomePending && (pendingOwners === undefined || pendingOwners.length > 0));
45
64
  let mainSession;
46
65
  if (needsMainSessionBoundary) {
47
66
  if (!sessions) {
@@ -52,7 +71,6 @@ export async function completeBootstrapWithWelcome(service, aid, welcome, sessio
52
71
  reloaded: false,
53
72
  };
54
73
  }
55
- const pendingOwners = welcome?.pendingPostBootstrapWelcomeOwners?.();
56
74
  const sessionOwners = pendingOwners?.length ? pendingOwners : [undefined];
57
75
  for (const owner of sessionOwners) {
58
76
  let ownerSession;
@@ -98,11 +116,27 @@ export async function completeBootstrapWithWelcome(service, aid, welcome, sessio
98
116
  mainSessionCreated: mainSession?.created,
99
117
  };
100
118
  }
101
- const welcomeQueued = await welcome.reconcilePostBootstrapWelcome();
119
+ let welcomeResult;
120
+ try {
121
+ welcomeResult = await welcome.reconcilePostBootstrapWelcome();
122
+ }
123
+ catch (error) {
124
+ const message = error instanceof Error ? error.message : String(error);
125
+ logger.warn(`[Bootstrap] Completion committed; welcome reconciliation failed for ${aid}: ${message}`);
126
+ welcomeResult = { deliveries: [], error: message };
127
+ try {
128
+ welcomeResult = { ...postBootstrapWelcomeStatus(aid, loadAgent(aid)?.owners ?? []), error: message };
129
+ }
130
+ catch {
131
+ // A local store failure must not turn an already committed lifecycle
132
+ // transition into a reported activation failure.
133
+ }
134
+ }
102
135
  return {
103
136
  ...result,
104
137
  reloaded: true,
105
- welcomeQueued,
138
+ welcomeQueued: welcomeResult.deliveries.some(delivery => delivery.status === 'queued'),
139
+ welcome: welcomeResult,
106
140
  mainSessionId: mainSession?.sessionId,
107
141
  mainSessionCreated: mainSession?.created,
108
142
  };
@@ -123,11 +157,15 @@ export class BootstrapService {
123
157
  const aid = agent?.aid || ctx.agentAid;
124
158
  if (!aid)
125
159
  return false;
160
+ if (hasSuppliedInitialization(aid) || loadAgent(aid)?.initialization)
161
+ return false;
126
162
  const key = aid;
127
163
  if (this.inFlight.has(key))
128
164
  return false;
129
165
  this.inFlight.add(key);
130
166
  let lifecycleStarted = false;
167
+ let notificationPersisted = false;
168
+ let attemptedOperationId;
131
169
  const loadedConfig = loadAgent(aid) || agent?.config;
132
170
  if (!loadedConfig) {
133
171
  this.inFlight.delete(key);
@@ -180,13 +218,18 @@ export class BootstrapService {
180
218
  const operationId = starting && !notificationState.hasCompletedHistory
181
219
  ? bootstrapInitialMessageOperationId(aid)
182
220
  : bootstrapInitialRetryOperationId(aid);
183
- const shouldSend = !notificationState.recentlyNotified;
221
+ const shouldSend = !notificationState.recentlyNotified && !notificationState.pending;
222
+ if (notificationState.failure) {
223
+ logger.warn(`[Bootstrap] Initial notification terminated: ${notificationState.failure}; failure retained`);
224
+ return false;
225
+ }
184
226
  if (starting) {
185
227
  await this.publishAgentMdIfSupported(ctx.adapter, aid, agentName);
186
228
  this.setLifecycle(agent, aid, 'bootstrapping');
187
229
  lifecycleStarted = true;
188
230
  }
189
231
  if (shouldSend) {
232
+ attemptedOperationId = operationId;
190
233
  const text = this.renderWelcome({
191
234
  agentAid: aid,
192
235
  agentName,
@@ -194,7 +237,7 @@ export class BootstrapService {
194
237
  channel: channelType || ctx.channelKey,
195
238
  baseagent,
196
239
  });
197
- await ctx.adapter.send(buildEnvelope({
240
+ const receipt = await ctx.adapter.send(buildEnvelope({
198
241
  taskId: `bootstrap-${randomBytes(5).toString('hex')}`,
199
242
  operationId,
200
243
  channel: ctx.adapter.channelKey || ctx.adapter.channelName,
@@ -211,7 +254,26 @@ export class BootstrapService {
211
254
  },
212
255
  },
213
256
  }), { kind: 'result.text', text, isFinal: true });
214
- this.notifiedInProcess.set(aid, Date.now());
257
+ const persisted = channelType === 'aun'
258
+ ? outbox.findByDedupeKey(aid, operationId, { includeTerminal: true }) : undefined;
259
+ notificationPersisted = !!persisted;
260
+ if (receipt?.status === 'sent' && receipt.messageId?.trim()) {
261
+ this.notifiedInProcess.set(aid, Date.now());
262
+ }
263
+ else if (receipt?.status === 'queued' && receipt.outboxId
264
+ && (channelType !== 'aun' || (persisted?.id === receipt.outboxId && !persisted.terminal))) {
265
+ logger.info(`[Bootstrap] Initial notification queued: aid=${aid} outbox=${receipt.outboxId}`);
266
+ }
267
+ else if (!receipt && channelType !== 'aun') {
268
+ // Legacy non-AUN adapters have not all migrated to SendReceipt.
269
+ logger.info(`[Bootstrap] Legacy adapter accepted notification for ${aid} without a receipt`);
270
+ this.notifiedInProcess.set(aid, Date.now());
271
+ }
272
+ else {
273
+ const error = receipt?.status === 'failed' ? receipt.error
274
+ : receipt?.status === 'suppressed' ? receipt.reason : 'missing valid send receipt';
275
+ throw new Error(error);
276
+ }
215
277
  }
216
278
  if (starting) {
217
279
  this.eventBus.publish({ type: 'agent:bootstrap-started', aid, channel: channelType || ctx.channelKey, timestamp: Date.now() });
@@ -221,12 +283,24 @@ export class BootstrapService {
221
283
  logger.info(`[Bootstrap] Reconciled initial message for ${aid} via ${ctx.channelKey} (${ctx.source})`);
222
284
  }
223
285
  else {
224
- logger.debug(`[Bootstrap] Initial message already sent within the notification window for ${aid}`);
286
+ logger.debug(`[Bootstrap] Initial message already accepted or pending for ${aid}`);
225
287
  }
226
288
  return true;
227
289
  }
228
290
  catch (e) {
229
- if (lifecycleStarted) {
291
+ if (channelType === 'aun' && attemptedOperationId) {
292
+ try {
293
+ notificationPersisted ||= !!outbox.findByDedupeKey(aid, attemptedOperationId, { includeTerminal: true });
294
+ }
295
+ catch (error) {
296
+ logger.warn(`[Bootstrap] Cannot inspect persisted notification for ${aid}: ${error}`);
297
+ // Its absence is not proven. Preserve the started lifecycle.
298
+ notificationPersisted = true;
299
+ }
300
+ }
301
+ // A persisted operation owns delivery recovery. Do not reset the setup
302
+ // lifecycle merely because its network attempt or receipt failed.
303
+ if (lifecycleStarted && !notificationPersisted) {
230
304
  try {
231
305
  this.setLifecycle(agent, aid, 'created');
232
306
  }
@@ -257,6 +331,13 @@ export class BootstrapService {
257
331
  };
258
332
  }
259
333
  this.setLifecycle(agent, aid, 'active');
334
+ try {
335
+ cancelPendingBootstrapPrompts(aid);
336
+ }
337
+ catch (error) {
338
+ // Delivery also checks lifecycle immediately before sending a prompt.
339
+ logger.warn(`[Bootstrap] Failed to cancel stale prompts for ${aid}: ${error}`);
340
+ }
260
341
  this.eventBus.publish({ type: 'agent:bootstrap-complete', aid, timestamp: Date.now() });
261
342
  logger.info(`[Bootstrap] Completed for ${aid} (previous lifecycle=${lifecycle})`);
262
343
  return { ok: true, aid, transitioned: true };
@@ -341,26 +422,36 @@ export class BootstrapService {
341
422
  catch {
342
423
  // A missing or malformed session path must not prevent bootstrap.
343
424
  }
425
+ let pending = false;
426
+ let failure;
427
+ let hasCompletedHistory = !!latestLoggedOperationId;
344
428
  for (const entry of outbox.findByDedupePrefix(input.aid, prefix, { includeTerminal: true })) {
345
- // A completed operation no longer needs its queued duplicate. Entries
346
- // older than the notification window must not be delivered alongside
347
- // the newly generated reminder operation.
348
- if (entry.terminal) {
349
- outbox.remove(input.aid, entry.id);
429
+ if (entry.dedupeKey === latestLoggedOperationId) {
430
+ // The log proves gateway acceptance, including pre-migration records.
431
+ if (!entry.deliveryResult && !entry.deliveryReceipt)
432
+ outbox.remove(input.aid, entry.id);
350
433
  continue;
351
434
  }
352
- if (entry.dedupeKey === latestLoggedOperationId || entry.ts < cutoff) {
353
- outbox.remove(input.aid, entry.id);
435
+ if (entry.channelId !== input.channelId || entry.delivery?.chatType !== 'private')
354
436
  continue;
437
+ if (entry.deliveryResult || entry.deliveryReceipt) {
438
+ hasCompletedHistory = true;
439
+ latestTs = Math.max(latestTs, entry.deliveryResult?.at ?? entry.deliveryReceipt?.acceptedAt ?? entry.ts);
440
+ }
441
+ else if (entry.terminal) {
442
+ failure = entry.terminal.error;
443
+ }
444
+ else {
445
+ // Pending operations do not expire at the reminder window boundary.
446
+ pending = true;
355
447
  }
356
- latestTs = Math.max(latestTs, entry.ts || 0);
357
448
  }
358
449
  const recentlyNotified = latestTs >= cutoff;
359
450
  if (recentlyNotified)
360
451
  this.notifiedInProcess.set(input.aid, latestTs);
361
452
  else
362
453
  this.notifiedInProcess.delete(input.aid);
363
- return { recentlyNotified, hasCompletedHistory: !!latestLoggedOperationId };
454
+ return { recentlyNotified, hasCompletedHistory, pending, failure };
364
455
  }
365
456
  async publishAgentMdIfSupported(adapter, aid, fallbackName) {
366
457
  if (typeof adapter.uploadAgentMd !== 'function')
@@ -1,3 +1,5 @@
1
+ import { agentFullAccessStatus, agentFullAccessWriteError } from '../role/runtime-policy.js';
2
+ import { initializationSummary } from '../../cli/agent-create.js';
1
3
  import { agentDelete, agentEnable, agentDisable, agentList, agentShow, agentSet, agentReload, } from '../../cli/agent.js';
2
4
  import { agentCreateNonInteractive } from '../../cli/agent-create.js';
3
5
  import { logger } from '../../utils/logger.js';
@@ -10,7 +12,7 @@ import { uploadAvatar, uploadAvatarUrl } from '../../utils/avatar-upload.js';
10
12
  import { agentmdGet, agentmdPut, updateAgentMdFrontmatterName } from '../../aun/aid/agentmd.js';
11
13
  import { isValidAid } from '../../aun/aid/validation.js';
12
14
  import { isSupportedBaseagent } from '../../agents/baseagent.js';
13
- const SUPPORTED_AGENT_PATCH_FIELDS = new Set(['aid', 'name', 'active_baseagent', 'baseagents', 'projects', 'owners', 'chatmode', 'channels', 'channelOwners']);
15
+ const SUPPORTED_AGENT_PATCH_FIELDS = new Set(['aid', 'name', 'avatar', 'active_baseagent', 'baseagents', 'projects', 'owners', 'fullaccess', 'chatmode', 'channels', 'channelOwners']);
14
16
  const HIDDEN_VALUE = '[hidden]';
15
17
  const SENSITIVE_CONFIG_KEYS = new Set([
16
18
  'apikey',
@@ -413,8 +415,29 @@ async function runCreateInBackground(opts, w, service) {
413
415
  /** name=agent 的 menu.action 执行。create 使用显式 owner AID;peerId 仅作为控制面鉴权主体。
414
416
  * create 受理即返回(D3);delete/enable/disable 同步等结果。
415
417
  * 调用方负责传入已兜底的 args.project(见 command-handler 装配)。 */
416
- export async function execAgentAction(action, args, peerId, eventBus, service) {
418
+ export async function execAgentAction(action, args, peerId, eventBus, service, subject) {
417
419
  const a = args ?? {};
420
+ if (action === 'initialize') {
421
+ if (!service?.initialization)
422
+ return { error: '初始化服务不可用', code: 'NOT_CONFIGURED' };
423
+ const plan = a.plan && typeof a.plan === 'object' ? a.plan : {
424
+ aid: a.aid,
425
+ project: typeof a.project === 'string' ? a.project : '',
426
+ baseagent: a.baseagent,
427
+ owners: Array.isArray(a.owners) ? a.owners : (a.owner ? [a.owner] : []),
428
+ name: a.name,
429
+ description: a.description,
430
+ type: a.type || 'agent',
431
+ tags: a.tags,
432
+ ...(typeof a.persona === 'string' ? { persona: a.persona } : {}),
433
+ };
434
+ try {
435
+ return { data: service.initialization.start(plan, peerId, a.dryRun === true) };
436
+ }
437
+ catch (error) {
438
+ return { error: error instanceof Error ? error.message : String(error), code: error.code || 'INITIALIZE_FAILED' };
439
+ }
440
+ }
418
441
  if (action === 'create') {
419
442
  if (!peerId)
420
443
  return { error: '缺少发起者 AID(无法校验控制权限)', code: 'INVALID_ARGS' };
@@ -497,8 +520,22 @@ export async function execAgentAction(action, args, peerId, eventBus, service) {
497
520
  return { data: { aid: a.aid, reloaded: true } };
498
521
  }
499
522
  if (action === 'update') {
500
- const result = await execAgentUpdate(a);
523
+ const result = await execAgentUpdate(a, subject);
501
524
  if (!('error' in result)) {
525
+ const data = result.data;
526
+ if (data.avatar) {
527
+ eventBus?.publish({
528
+ type: 'agent:avatar_updated',
529
+ aid: data.aid,
530
+ avatar: data.avatar,
531
+ sha256: data.sha256,
532
+ mime: data.mime,
533
+ width: data.width,
534
+ height: data.height,
535
+ aspectRatio: data.aspectRatio,
536
+ timestamp: Date.now(),
537
+ });
538
+ }
502
539
  eventBus?.publish({
503
540
  type: 'agent:updated',
504
541
  aid: a.aid,
@@ -514,9 +551,9 @@ export async function execAgentAction(action, args, peerId, eventBus, service) {
514
551
  * 直接通过 ConfigManager 读写 agent config(不走 agentSet,避免其内部自动 evolagent.reload)——
515
552
  * 重载由用户在 Agents 页操作列手动触发(带任务执行检查)。
516
553
  * AUN 渠道绑定 agent 顶层 aid,不可通过 patch 编辑:拒绝改 aid、拒绝 channels 数组里出现 aun 条目。
517
- * 可写字段:name / active_baseagent / baseagents / projects / owners / chatmode / channels(非 aun)/
554
+ * 可写字段:name / avatar(旧版 App 兼容,单独提交 data URL)/ active_baseagent / baseagents / projects / owners / fullaccess / chatmode / channels(非 aun)/
518
555
  * channelOwners(按 type/name 局部更新非 AUN channel owners,避免前端回写脱敏 credentials)。 */
519
- export async function execAgentUpdate(args) {
556
+ export async function execAgentUpdate(args, subject) {
520
557
  const a = args ?? {};
521
558
  if (!a.aid)
522
559
  return { error: '缺少 aid', code: 'INVALID_ARGS' };
@@ -531,6 +568,9 @@ export async function execAgentUpdate(args) {
531
568
  if (p.aid !== undefined) {
532
569
  return { error: 'aid 不可修改(AUN 身份绑定,如需换 AID 请删除后重建)', code: 'INVALID_ARGS' };
533
570
  }
571
+ if (Object.hasOwn(p, 'avatar') && Object.keys(p).length !== 1) {
572
+ return { error: 'avatar 不能与其他 patch 字段同时提交,请拆分为两次 update', code: 'INVALID_ARGS' };
573
+ }
534
574
  if (p.name !== undefined) {
535
575
  const otherKeys = Object.keys(p).filter(k => k !== 'name');
536
576
  if (otherKeys.length > 0) {
@@ -546,9 +586,12 @@ export async function execAgentUpdate(args) {
546
586
  const config = cfgRead(ConfigTarget.Agent, { self: a.aid });
547
587
  if (!config)
548
588
  return { error: `Agent "${a.aid}" not found`, code: 'NOT_FOUND' };
589
+ // Legacy App requests share the canonical validation, storage and agent.md publication flow.
590
+ if (Object.hasOwn(p, 'avatar'))
591
+ return execAidAvatar({ aid: a.aid, data: p.avatar });
549
592
  let touched = false;
550
593
  const data = { aid: a.aid };
551
- const hasConfigPatch = p.active_baseagent !== undefined || p.baseagents !== undefined || p.projects !== undefined || p.owners !== undefined || p.chatmode !== undefined || p.channels !== undefined || p.channelOwners !== undefined;
594
+ const hasConfigPatch = p.active_baseagent !== undefined || p.baseagents !== undefined || p.projects !== undefined || p.owners !== undefined || p.fullaccess !== undefined || p.chatmode !== undefined || p.channels !== undefined || p.channelOwners !== undefined;
552
595
  if (p.active_baseagent !== undefined) {
553
596
  if (typeof p.active_baseagent !== 'string' || !p.active_baseagent.trim()) {
554
597
  return { error: `无效 active_baseagent: ${JSON.stringify(p.active_baseagent)}(必须是非空字符串)`, code: 'INVALID_ARGS' };
@@ -596,6 +639,30 @@ export async function execAgentUpdate(args) {
596
639
  config.owners = owners;
597
640
  touched = true;
598
641
  }
642
+ if (p.fullaccess !== undefined) {
643
+ if (!p.fullaccess || typeof p.fullaccess !== 'object' || Array.isArray(p.fullaccess)) {
644
+ return { error: 'fullaccess 必须是对象', code: 'INVALID_ARGS' };
645
+ }
646
+ const fullaccess = p.fullaccess;
647
+ if (Object.keys(fullaccess).some(key => key !== 'enabled')) {
648
+ return { error: 'fullaccess 只支持 enabled 字段', code: 'INVALID_ARGS' };
649
+ }
650
+ if (fullaccess.enabled !== undefined && typeof fullaccess.enabled !== 'boolean') {
651
+ return { error: 'fullaccess.enabled 必须是布尔值', code: 'INVALID_ARGS' };
652
+ }
653
+ if (typeof fullaccess.enabled !== 'boolean')
654
+ return { error: 'fullaccess.enabled is required', code: 'INVALID_ARGS' };
655
+ const denied = agentFullAccessWriteError({
656
+ daemonOwner: subject?.isDaemonOwner === true,
657
+ ecwebManagement: subject?.ecwebManagement === true,
658
+ delegated: subject?.canApprovePersistentFullAccess === false || subject?.chatType === 'group',
659
+ enabled: fullaccess.enabled,
660
+ });
661
+ if (denied)
662
+ return { error: denied, code: 'FORBIDDEN' };
663
+ config.fullaccess = { enabled: fullaccess.enabled };
664
+ touched = true;
665
+ }
599
666
  if (p.chatmode !== undefined) {
600
667
  config.chatmode = p.chatmode;
601
668
  touched = true;
@@ -645,17 +712,20 @@ export function resolveProjectPath(explicit, aid, defaults) {
645
712
  return deriveAgentProjectPath(root, aid);
646
713
  }
647
714
  /** name=agent 的 menu.query:查单个 agent 详情,附构建进度(D3)。 */
648
- export async function execAgentQuery(args, service) {
715
+ export async function execAgentQuery(args, service, subject) {
649
716
  const aid = args?.aid;
650
717
  if (!aid)
651
718
  return { error: '缺少 aid', code: 'INVALID_ARGS' };
652
719
  const agentDir = path.join(resolvePaths().agentsDir, aid);
653
720
  const progress = readCreateStatus(agentDir);
721
+ const initialization = initializationSummary(aid);
654
722
  const res = service
655
723
  ? await service.show(aid)
656
724
  : await agentShow(aid);
657
725
  if (!('ok' in res) || res.ok !== true) {
658
726
  const code = classifyError(res.error);
727
+ if (code === 'NOT_FOUND' && initialization)
728
+ return { data: { aid, lifecycle: service?.readConfig(aid)?.lifecycle ?? null, initialization } };
659
729
  if (code === 'NOT_FOUND' && progress)
660
730
  return { data: buildCreateProgressOnlyAgent(aid, progress) };
661
731
  return { error: res.error, code };
@@ -663,8 +733,10 @@ export async function execAgentQuery(args, service) {
663
733
  const config = cfgRead(ConfigTarget.Agent, { self: aid });
664
734
  const safeConfig = buildSafeAgentConfig(config);
665
735
  const safeAgentConfig = safeConfig ?? {};
736
+ const fullaccessStatus = agentFullAccessStatus(aid, subject?.principalId ?? subject?.actorId);
666
737
  const data = {
667
738
  ...res,
739
+ ...(initialization ? { initialization } : {}),
668
740
  config: {
669
741
  ...(res.config ?? {}),
670
742
  active_baseagent: safeAgentConfig.active_baseagent ?? res.config?.baseagent ?? null,
@@ -673,6 +745,14 @@ export async function execAgentQuery(args, service) {
673
745
  owners: safeAgentConfig.owners ?? res.config?.owners ?? [],
674
746
  },
675
747
  safeConfig,
748
+ fullaccessStatus: {
749
+ ...fullaccessStatus,
750
+ canConfigure: subject?.ecwebManagement === true
751
+ && subject?.isDaemonOwner === true
752
+ && subject.canApprovePersistentFullAccess !== false
753
+ && subject.chatType !== 'group'
754
+ && fullaccessStatus.hostEnabled,
755
+ },
676
756
  };
677
757
  // 叠加构建进度(create 受理后、ready 前可见;ready 后文件仍在,可反映软失败 warn)
678
758
  return { data: progress ? { ...data, createProgress: progress } : data };
@@ -686,6 +766,7 @@ export async function execAgentOptions(args, service) {
686
766
  name: info.name,
687
767
  personalName: info.personalName || null,
688
768
  status: info.status || 'stopped',
769
+ lifecycle: info.lifecycle ?? null,
689
770
  channels: info.channels || [],
690
771
  projectPath: info.projectPath ? toPosix(info.projectPath) : null,
691
772
  baseagent: info.baseagent || null,