evolcore 0.0.21 → 0.0.23

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 (112) hide show
  1. package/AGENTS.md +62 -0
  2. package/CHANGELOG.md +53 -0
  3. package/README.md +2 -0
  4. package/bin/codex-managed-acl.ps1 +155 -0
  5. package/bin/codex-managed-hook.mjs +3 -0
  6. package/bin/install-codex-managed-hooks.mjs +376 -96
  7. package/dist/agents/baseagent-availability.js +48 -0
  8. package/dist/agents/baseagent.js +1 -1
  9. package/dist/agents/claude-runner.js +44 -226
  10. package/dist/agents/{codex-app-server-client.js → codex/app-server-client.js} +103 -17
  11. package/dist/{utils/codex-app-server-registry.js → agents/codex/app-server-registry.js} +2 -2
  12. package/dist/agents/codex/catalog.js +70 -0
  13. package/dist/agents/codex/diagnostics.js +154 -0
  14. package/dist/agents/codex-runner.js +1292 -219
  15. package/dist/agents/ecagent-runner.js +10 -31
  16. package/dist/agents/gemini-runner.js +2 -6
  17. package/dist/aun/aid/store.js +2 -0
  18. package/dist/aun/menu-client.js +272 -0
  19. package/dist/aun/msg/group.js +65 -4
  20. package/dist/aun/msg/index.js +1 -1
  21. package/dist/aun/msg/managed-operation.js +51 -4
  22. package/dist/aun/outbox.js +7 -0
  23. package/dist/aun/rpc/client.js +8 -1
  24. package/dist/channels/aun.js +240 -36
  25. package/dist/channels/wechat.js +81 -10
  26. package/dist/cli/aun-commands.js +48 -4
  27. package/dist/cli/command-log.js +21 -2
  28. package/dist/cli/daemon-commands.js +169 -51
  29. package/dist/cli/index.js +20 -0
  30. package/dist/cli/init.js +2 -10
  31. package/dist/cli/menu-client.js +95 -0
  32. package/dist/cli/model.js +23 -8
  33. package/dist/cli/task-context.js +16 -2
  34. package/dist/cli/trigger-command.js +22 -5
  35. package/dist/config/config-field-policy.js +3 -0
  36. package/dist/config/config-manager.js +32 -5
  37. package/dist/config/contact-book-store.js +25 -3
  38. package/dist/config/schema-migration.js +14 -2
  39. package/dist/core/auth/agent-delegation.js +15 -0
  40. package/dist/core/auth/auth-gateway.js +8 -0
  41. package/dist/core/auth/authorization-audit.js +70 -6
  42. package/dist/core/auth/operation-catalog.js +4 -0
  43. package/dist/core/auth/remote-access-store.js +135 -0
  44. package/dist/core/bootstrap-messages.js +8 -0
  45. package/dist/core/bootstrap-service.js +93 -25
  46. package/dist/core/command/command-handler.js +62 -5
  47. package/dist/core/command/daemon-diagnostics.js +448 -0
  48. package/dist/core/command/menu-catalog.js +3 -0
  49. package/dist/core/command/menu-handler.js +150 -33
  50. package/dist/core/command/menu-protocol.js +120 -1
  51. package/dist/core/command/slash-gate.js +3 -3
  52. package/dist/core/command/slash-handler.js +83 -40
  53. package/dist/core/data-migration.js +11 -1
  54. package/dist/core/event-catalog.js +32 -0
  55. package/dist/core/handoff/runtime.js +23 -3
  56. package/dist/core/message/im-renderer.js +7 -3
  57. package/dist/core/message/message-bridge.js +60 -2
  58. package/dist/core/message/message-log.js +33 -0
  59. package/dist/core/message/message-queue.js +41 -15
  60. package/dist/core/message/response-engine.js +246 -114
  61. package/dist/core/model/catalog-service.js +308 -0
  62. package/dist/core/model/model-catalog.js +124 -334
  63. package/dist/core/model/model-diagnostics.js +4 -5
  64. package/dist/core/model/model-fallback.js +9 -16
  65. package/dist/core/model/model-permission.js +1 -1
  66. package/dist/core/permission/approval-gateway.js +16 -1
  67. package/dist/core/permission/ec-command-parser.js +272 -70
  68. package/dist/core/permission/protected-paths.js +74 -14
  69. package/dist/core/permission/sandbox-runtime.js +4 -2
  70. package/dist/core/permission/tool-error-code.js +12 -0
  71. package/dist/core/permission/tool-policy.js +290 -30
  72. package/dist/core/runtime-lock.js +13 -8
  73. package/dist/core/session/session-manager.js +30 -0
  74. package/dist/core/session/session-renew.js +18 -1
  75. package/dist/core/session/session-turn-coordinator.js +5 -1
  76. package/dist/index.js +104 -34
  77. package/dist/ipc.js +218 -96
  78. package/dist/paths.js +18 -0
  79. package/dist/response-system/engines/v1/proactive-flow.js +7 -2
  80. package/dist/stats/price-resolver.js +4 -0
  81. package/dist/trigger/feedback.js +14 -2
  82. package/dist/trigger/manager.js +3 -1
  83. package/dist/trigger/parser.js +86 -13
  84. package/dist/trigger/patch.js +104 -1
  85. package/dist/trigger/scheduler.js +20 -3
  86. package/dist/utils/ecweb-supervisor.js +11 -2
  87. package/dist/utils/ecweb-utils.js +8 -26
  88. package/dist/utils/error-utils.js +7 -0
  89. package/dist/utils/logger.js +9 -4
  90. package/dist/utils/npm-ops.js +47 -15
  91. package/dist/utils/tool-summary.js +59 -0
  92. package/dist/utils/windows-shell-trust.js +201 -0
  93. package/kits/docs/evolcore/INDEX.md +3 -2
  94. package/kits/docs/evolcore/agent-create.md +159 -0
  95. package/kits/docs/evolcore/agent.md +6 -0
  96. package/kits/docs/evolcore/group-collaboration.md +256 -0
  97. package/kits/docs/evolcore/group-rules.md +1 -19
  98. package/kits/docs/evolcore/group.md +3 -1
  99. package/kits/docs/evolcore/menu-client.md +34 -0
  100. package/kits/docs/evolcore/trigger.md +6 -3
  101. package/kits/docs/prompt-loading-architecture.md +6 -0
  102. package/kits/eck_message_manifest.json +6 -6
  103. package/kits/migrations/migrate-contact-book-v2.mjs +9 -1
  104. package/kits/migrations/migrate-role-config-v4.mjs +11 -5
  105. package/kits/migrations/migrate-role-config-v5.mjs +8 -2
  106. package/kits/schemas/_meta.json +3 -2
  107. package/kits/schemas/agent-config.schema.12.json +427 -0
  108. package/kits/templates/message-fragments/item.md +1 -1
  109. package/kits/templates/roles/visitor.json +1 -0
  110. package/kits/templates/system-fragments/bootstrap.md +10 -2
  111. package/kits/templates/system-fragments/commands.md +2 -2
  112. package/package.json +11 -3
@@ -0,0 +1,135 @@
1
+ import crypto from 'node:crypto';
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+ import { resolvePaths } from '../../paths.js';
5
+ export const REMOTE_ACCESS_TOKEN_TTL_MS = 30 * 24 * 60 * 60 * 1000;
6
+ export const REMOTE_ACCESS_PAIRING_TTL_MS = 5 * 60 * 1000;
7
+ export function remoteAccessTokenStorePath() {
8
+ return path.join(resolvePaths().instanceDir, 'control-access-tokens.json');
9
+ }
10
+ function loadStore() {
11
+ try {
12
+ const value = JSON.parse(fs.readFileSync(remoteAccessTokenStorePath(), 'utf8'));
13
+ if (Array.isArray(value.tokens))
14
+ return value;
15
+ }
16
+ catch { }
17
+ return { tokens: [] };
18
+ }
19
+ function saveStore(store) {
20
+ const file = remoteAccessTokenStorePath();
21
+ fs.mkdirSync(path.dirname(file), { recursive: true });
22
+ const temporary = `${file}.${process.pid}.tmp`;
23
+ try {
24
+ fs.writeFileSync(temporary, JSON.stringify(store, null, 2) + '\n', { mode: 0o600 });
25
+ fs.renameSync(temporary, file);
26
+ try {
27
+ fs.chmodSync(file, 0o600);
28
+ }
29
+ catch { }
30
+ }
31
+ finally {
32
+ if (fs.existsSync(temporary)) {
33
+ try {
34
+ fs.unlinkSync(temporary);
35
+ }
36
+ catch { }
37
+ }
38
+ }
39
+ }
40
+ function pruneExpired(store, now) {
41
+ const before = store.tokens.length;
42
+ store.tokens = store.tokens.filter(record => typeof record?.token === 'string'
43
+ && typeof record.lastActive === 'number'
44
+ && now - record.lastActive < REMOTE_ACCESS_TOKEN_TTL_MS);
45
+ return store.tokens.length !== before;
46
+ }
47
+ function validLabel(value) {
48
+ if (typeof value !== 'string')
49
+ return 'remote';
50
+ const normalized = value.trim().replace(/[\r\n]/g, ' ').slice(0, 256);
51
+ return normalized || 'remote';
52
+ }
53
+ function validOwner(value) {
54
+ if (typeof value !== 'string')
55
+ return undefined;
56
+ const normalized = value.trim();
57
+ if (!normalized || normalized.length > 512 || /[\r\n]/.test(normalized))
58
+ return undefined;
59
+ return normalized;
60
+ }
61
+ /** Validate and slide the expiry window of a daemon-owned remote token. */
62
+ export function validateAndRenewRemoteAccessToken(token, now = Date.now()) {
63
+ if (typeof token !== 'string' || !token || token.length > 512)
64
+ return { valid: false };
65
+ try {
66
+ const store = loadStore();
67
+ const changed = pruneExpired(store, now);
68
+ const record = store.tokens.find(candidate => candidate.token === token);
69
+ if (!record) {
70
+ if (changed)
71
+ saveStore(store);
72
+ return { valid: false };
73
+ }
74
+ record.lastActive = now;
75
+ saveStore(store);
76
+ const authorizedBy = validOwner(record.authorizedBy);
77
+ return { valid: true, ...(authorizedBy ? { authorizedBy } : {}) };
78
+ }
79
+ catch {
80
+ return { valid: false };
81
+ }
82
+ }
83
+ export class RemoteAccessService {
84
+ pairingCode = '';
85
+ pairingExpiresAt = 0;
86
+ pairingAuthorizedBy;
87
+ getPairingCode(authorizedBy, now = Date.now()) {
88
+ if (!this.pairingCode || now >= this.pairingExpiresAt) {
89
+ this.pairingCode = String(crypto.randomInt(0, 1_000_000)).padStart(6, '0');
90
+ this.pairingExpiresAt = now + REMOTE_ACCESS_PAIRING_TTL_MS;
91
+ this.pairingAuthorizedBy = undefined;
92
+ }
93
+ const owner = validOwner(authorizedBy);
94
+ if (!this.pairingAuthorizedBy && owner)
95
+ this.pairingAuthorizedBy = owner;
96
+ return { code: this.pairingCode, expiresAt: this.pairingExpiresAt };
97
+ }
98
+ redeemPairingCode(code, label, authorizedBy, now = Date.now()) {
99
+ if (typeof code !== 'string' || !/^\d{6}$/.test(code)) {
100
+ return { ok: false, code: 'INVALID_ARGUMENT', error: 'pairing code must be six digits' };
101
+ }
102
+ if (!this.pairingCode || now >= this.pairingExpiresAt) {
103
+ return { ok: false, code: 'PAIRING_EXPIRED', error: 'pairing code has expired' };
104
+ }
105
+ if (code !== this.pairingCode) {
106
+ return { ok: false, code: 'PAIRING_INVALID', error: 'pairing code is invalid' };
107
+ }
108
+ const createdAt = now;
109
+ const token = crypto.randomBytes(32).toString('hex');
110
+ const store = loadStore();
111
+ pruneExpired(store, now);
112
+ const owner = validOwner(authorizedBy) ?? this.pairingAuthorizedBy;
113
+ store.tokens.push({
114
+ token,
115
+ createdAt,
116
+ lastActive: createdAt,
117
+ label: validLabel(label),
118
+ ...(owner ? { authorizedBy: owner } : {}),
119
+ });
120
+ saveStore(store);
121
+ return { ok: true, token, expiresAt: createdAt + REMOTE_ACCESS_TOKEN_TTL_MS };
122
+ }
123
+ issueLocalToken(label, now = Date.now()) {
124
+ const token = crypto.randomBytes(32).toString('hex');
125
+ const store = loadStore();
126
+ pruneExpired(store, now);
127
+ store.tokens.push({ token, createdAt: now, lastActive: now, label: validLabel(label) });
128
+ saveStore(store);
129
+ return { ok: true, token, expiresAt: now + REMOTE_ACCESS_TOKEN_TTL_MS };
130
+ }
131
+ validateToken(token, now = Date.now()) {
132
+ return validateAndRenewRemoteAccessToken(token, now);
133
+ }
134
+ }
135
+ export const remoteAccessService = new RemoteAccessService();
@@ -7,9 +7,17 @@ import { chatDirPath } from './session/session-fs-store.js';
7
7
  import { hasMessageLogOperation } from './message/message-log.js';
8
8
  import { isDeliveryTarget, sameDeliveryTarget } from './message/message-utils.js';
9
9
  export const BOOTSTRAP_MESSAGE_TTL_MS = 30 * 24 * 60 * 60 * 1000;
10
+ export const BOOTSTRAP_NOTIFICATION_WINDOW_MS = 24 * 60 * 60 * 1000;
10
11
  export function bootstrapInitialMessageOperationId(aid) {
11
12
  return `bootstrap-initial:v1:${aid}`;
12
13
  }
14
+ export function bootstrapInitialMessageOperationPrefix(aid) {
15
+ return `bootstrap-initial:v1:${aid}`;
16
+ }
17
+ /** A new operation ID is required after the notification window expires. */
18
+ export function bootstrapInitialRetryOperationId(aid, at = Date.now()) {
19
+ return `${bootstrapInitialMessageOperationPrefix(aid)}:${at}`;
20
+ }
13
21
  export function postBootstrapWelcomeOperationId(aid) {
14
22
  return `bootstrap-complete:v1:${aid}`;
15
23
  }
@@ -1,7 +1,7 @@
1
1
  import fs from 'fs';
2
2
  import path from 'path';
3
3
  import { randomBytes } from 'crypto';
4
- import { kitsTemplatesDir, agentMdPath } from '../paths.js';
4
+ import { kitsTemplatesDir, agentMdPath, resolvePaths } from '../paths.js';
5
5
  import { buildInitialAgentMd, resolveAgentDisplayName } from '../aun/aid/agentmd.js';
6
6
  import { logger } from '../utils/logger.js';
7
7
  import { loadAgent, saveAgent } from '../config-store.js';
@@ -9,7 +9,10 @@ import { resolveAgentLifecycle, withLifecycleForWrite } from '../config/lifecycl
9
9
  import { renderTemplate } from '../eck/manifest-engine.js';
10
10
  import { activeBaseagent } from './model/config-scope.js';
11
11
  import { buildEnvelope } from './message/message-utils.js';
12
- import { BOOTSTRAP_MESSAGE_TTL_MS, bootstrapInitialMessageOperationId } from './bootstrap-messages.js';
12
+ import { BOOTSTRAP_MESSAGE_TTL_MS, BOOTSTRAP_NOTIFICATION_WINDOW_MS, bootstrapInitialMessageOperationId, bootstrapInitialMessageOperationPrefix, bootstrapInitialRetryOperationId, } from './bootstrap-messages.js';
13
+ import { findLatestMessageLogOperation } from './message/message-log.js';
14
+ import * as outbox from '../aun/outbox.js';
15
+ import { chatDirPath } from './session/session-fs-store.js';
13
16
  /** Coordinate the write-ahead completion welcome with the lifecycle commit. */
14
17
  export async function completeBootstrapWithWelcome(service, aid, welcome, sessions) {
15
18
  const lifecycle = service.lifecycleOf(aid);
@@ -102,6 +105,7 @@ export class BootstrapService {
102
105
  agentRegistry;
103
106
  eventBus;
104
107
  inFlight = new Set();
108
+ notifiedInProcess = new Map();
105
109
  constructor(agentRegistry, eventBus) {
106
110
  this.agentRegistry = agentRegistry;
107
111
  this.eventBus = eventBus;
@@ -161,41 +165,58 @@ export class BootstrapService {
161
165
  try {
162
166
  const agentName = this.resolveAgentDisplayName(aid);
163
167
  const baseagent = this.resolveBaseagent(agent, aid);
168
+ const notificationState = this.bootstrapNotificationState({
169
+ aid,
170
+ channelType,
171
+ channelId,
172
+ channelKey: ctx.channelKey,
173
+ });
174
+ const operationId = starting && !notificationState.hasCompletedHistory
175
+ ? bootstrapInitialMessageOperationId(aid)
176
+ : bootstrapInitialRetryOperationId(aid);
177
+ const shouldSend = !notificationState.recentlyNotified;
164
178
  if (starting) {
165
179
  await this.publishAgentMdIfSupported(ctx.adapter, aid, agentName);
166
180
  this.setLifecycle(agent, aid, 'bootstrapping');
167
181
  lifecycleStarted = true;
168
182
  }
169
- const text = this.renderWelcome({
170
- agentAid: aid,
171
- agentName,
172
- ownerName: ctx.recipientName || recipientId,
173
- channel: channelType || ctx.channelKey,
174
- baseagent,
175
- });
176
- await ctx.adapter.send(buildEnvelope({
177
- taskId: `bootstrap-${randomBytes(5).toString('hex')}`,
178
- channel: ctx.adapter.channelKey || ctx.adapter.channelName,
179
- channelId,
180
- agentName: aid,
181
- replyContext: {
182
- delivery,
183
- metadata: {
184
- source: 'daemon',
185
- persistRequired: true,
186
- operationId: bootstrapInitialMessageOperationId(aid),
187
- outboxTtl: BOOTSTRAP_MESSAGE_TTL_MS,
188
- criticalDelivery: true,
183
+ if (shouldSend) {
184
+ const text = this.renderWelcome({
185
+ agentAid: aid,
186
+ agentName,
187
+ ownerName: ctx.recipientName || recipientId,
188
+ channel: channelType || ctx.channelKey,
189
+ baseagent,
190
+ });
191
+ await ctx.adapter.send(buildEnvelope({
192
+ taskId: `bootstrap-${randomBytes(5).toString('hex')}`,
193
+ operationId,
194
+ channel: ctx.adapter.channelKey || ctx.adapter.channelName,
195
+ channelId,
196
+ agentName: aid,
197
+ replyContext: {
198
+ delivery,
199
+ metadata: {
200
+ source: 'daemon',
201
+ persistRequired: true,
202
+ operationId,
203
+ outboxTtl: BOOTSTRAP_MESSAGE_TTL_MS,
204
+ criticalDelivery: true,
205
+ },
189
206
  },
190
- },
191
- }), { kind: 'result.text', text, isFinal: true });
207
+ }), { kind: 'result.text', text, isFinal: true });
208
+ this.notifiedInProcess.set(aid, Date.now());
209
+ }
192
210
  if (starting) {
193
211
  this.eventBus.publish({ type: 'agent:bootstrap-started', aid, channel: channelType || ctx.channelKey, timestamp: Date.now() });
194
212
  logger.info(`[Bootstrap] Started for ${aid} via ${ctx.channelKey} (${ctx.source})`);
195
213
  }
196
- else {
214
+ else if (shouldSend) {
197
215
  logger.info(`[Bootstrap] Reconciled initial message for ${aid} via ${ctx.channelKey} (${ctx.source})`);
198
216
  }
217
+ else {
218
+ logger.debug(`[Bootstrap] Initial message already sent within the notification window for ${aid}`);
219
+ }
199
220
  return true;
200
221
  }
201
222
  catch (e) {
@@ -288,6 +309,53 @@ export class BootstrapService {
288
309
  return 'unknown';
289
310
  }
290
311
  }
312
+ bootstrapNotificationState(input) {
313
+ const prefix = bootstrapInitialMessageOperationPrefix(input.aid);
314
+ const now = Date.now();
315
+ const cutoff = now - BOOTSTRAP_NOTIFICATION_WINDOW_MS;
316
+ let latestTs = this.notifiedInProcess.get(input.aid) ?? 0;
317
+ let latestLoggedOperationId;
318
+ try {
319
+ const chatDir = chatDirPath(resolvePaths().sessionsDir, input.channelType || 'aun', input.channelId, input.aid, input.channelKey);
320
+ let latestLogged = findLatestMessageLogOperation(chatDir, prefix);
321
+ // Releases before the stable envelope operation ID fix persisted the
322
+ // random bootstrap task ID instead. Recognize that exact legacy shape
323
+ // so an upgrade/restart does not immediately resend the welcome.
324
+ if (!latestLogged) {
325
+ const legacyLogged = findLatestMessageLogOperation(chatDir, 'bootstrap-');
326
+ if (legacyLogged && /^bootstrap-[a-f0-9]{10}$/u.test(legacyLogged.operationId)) {
327
+ latestLogged = legacyLogged;
328
+ }
329
+ }
330
+ if (latestLogged) {
331
+ latestTs = Math.max(latestTs, latestLogged.ts);
332
+ latestLoggedOperationId = latestLogged.operationId;
333
+ }
334
+ }
335
+ catch {
336
+ // A missing or malformed session path must not prevent bootstrap.
337
+ }
338
+ for (const entry of outbox.findByDedupePrefix(input.aid, prefix, { includeTerminal: true })) {
339
+ // A completed operation no longer needs its queued duplicate. Entries
340
+ // older than the notification window must not be delivered alongside
341
+ // the newly generated reminder operation.
342
+ if (entry.terminal) {
343
+ outbox.remove(input.aid, entry.id);
344
+ continue;
345
+ }
346
+ if (entry.dedupeKey === latestLoggedOperationId || entry.ts < cutoff) {
347
+ outbox.remove(input.aid, entry.id);
348
+ continue;
349
+ }
350
+ latestTs = Math.max(latestTs, entry.ts || 0);
351
+ }
352
+ const recentlyNotified = latestTs >= cutoff;
353
+ if (recentlyNotified)
354
+ this.notifiedInProcess.set(input.aid, latestTs);
355
+ else
356
+ this.notifiedInProcess.delete(input.aid);
357
+ return { recentlyNotified, hasCompletedHistory: !!latestLoggedOperationId };
358
+ }
291
359
  async publishAgentMdIfSupported(adapter, aid, fallbackName) {
292
360
  if (typeof adapter.uploadAgentMd !== 'function')
293
361
  return;
@@ -114,7 +114,12 @@ export class CommandHandler {
114
114
  triggerSchedulerResolver;
115
115
  triggerSchedulerFallback;
116
116
  triggerSchedulerFallbackAid;
117
+ /** Runtime-only owner for the daemon trigger scheduler. It is intentionally
118
+ * kept outside EvolAgentRegistry because the daemon channel is not an Agent. */
119
+ daemonTriggerOwnerAid;
120
+ daemonTriggerChannel = 'daemon';
117
121
  daemonStatusProvider;
122
+ daemonMonitorProvider;
118
123
  agentDelegationRegistry;
119
124
  handoffRuntime;
120
125
  contactRequestSubmitter;
@@ -219,6 +224,32 @@ export class CommandHandler {
219
224
  setTriggerSchedulerResolver(resolver) {
220
225
  this.triggerSchedulerResolver = resolver;
221
226
  }
227
+ /** Bind the virtual daemon channel to its trigger scheduler owner AID. */
228
+ setDaemonTriggerOwner(aid, channel = 'daemon') {
229
+ const normalizedAid = String(aid || '').trim();
230
+ if (!normalizedAid)
231
+ throw new Error('daemon trigger owner AID is required');
232
+ this.daemonTriggerOwnerAid = normalizedAid;
233
+ this.daemonTriggerChannel = String(channel || 'daemon').trim() || 'daemon';
234
+ }
235
+ /** Used by control-menu routing; daemon owner is not in the Agent registry. */
236
+ getDaemonTriggerOwnerAid(channel = 'daemon') {
237
+ if (channel !== this.daemonTriggerChannel)
238
+ return undefined;
239
+ if (this.daemonTriggerOwnerAid)
240
+ return this.daemonTriggerOwnerAid;
241
+ try {
242
+ const controlAid = String(loadDaemonConfig().aid || '__daemon__').trim() || '__daemon__';
243
+ const existing = this.agentRegistry?.get?.(controlAid);
244
+ // Match index.ts: only a runnable (pre-start `stopped`) Agent causes
245
+ // the daemon scheduler owner to receive the collision suffix.
246
+ const conflicts = !!existing && (existing.status === undefined || existing.status === 'stopped');
247
+ return conflicts ? `${controlAid}#daemon` : controlAid;
248
+ }
249
+ catch {
250
+ return undefined;
251
+ }
252
+ }
222
253
  /** Compatibility entrypoint for older tests/callers that inject a single scheduler. */
223
254
  setTriggerScheduler(scheduler, manager) {
224
255
  this.triggerSchedulerFallback = scheduler;
@@ -233,6 +264,9 @@ export class CommandHandler {
233
264
  setDaemonStatusProvider(provider) {
234
265
  this.daemonStatusProvider = provider;
235
266
  }
267
+ setDaemonMonitorProvider(provider) {
268
+ this.daemonMonitorProvider = provider;
269
+ }
236
270
  /** 返回管理当前通道的 EvolAgent,无则返回 null */
237
271
  getOwningAgent(channel) {
238
272
  if (!this.agentRegistry)
@@ -240,7 +274,9 @@ export class CommandHandler {
240
274
  return this.agentRegistry.resolveByChannel(channel);
241
275
  }
242
276
  getTriggerSchedulerForChannel(channel) {
243
- const agentAid = this.getOwningAgent(channel)?.aid ?? tryParseChannelKey(channel)?.selfAID;
277
+ const agentAid = this.getOwningAgent(channel)?.aid
278
+ ?? this.getDaemonTriggerOwnerAid(channel)
279
+ ?? tryParseChannelKey(channel)?.selfAID;
244
280
  return agentAid ? this.triggerSchedulerResolver?.(agentAid) : this.triggerSchedulerFallback;
245
281
  }
246
282
  getTriggerSchedulerForAgent(agentAid) {
@@ -975,9 +1011,9 @@ export class CommandHandler {
975
1011
  async execMenuForEcweb(payload, auth) {
976
1012
  return await menuExecMenuForEcweb.call(this, payload, auth);
977
1013
  }
978
- /** 控制 AID channel 专用入口:peerId 须 ∈ evolcore.owners,全量权限。 */
979
- async execMenuForControl(payload, peerId) {
980
- return await menuExecMenuForControl.call(this, payload, peerId);
1014
+ /** Control AID Menu entry: daemon-owner or validated remote-access token. */
1015
+ async execMenuForControl(payload, peerId, auth) {
1016
+ return await menuExecMenuForControl.call(this, payload, peerId, auth);
981
1017
  }
982
1018
  /**
983
1019
  * CLI 透传执行:spawn `node dist/cli/index.js <argv>` 子进程,捕获输出回传。
@@ -1119,6 +1155,27 @@ export class CommandHandler {
1119
1155
  async handle(content, channel, channelId, sendMessage, userId, threadId, chatType, source, messageId, selfAID, overrideIdentity, authSubject, replyContext) {
1120
1156
  try {
1121
1157
  const result = await this._handleInternal(content, channel, channelId, sendMessage, userId, threadId, chatType, source, messageId, selfAID, overrideIdentity, authSubject, replyContext);
1158
+ // Lifecycle commands are control-plane operations. Their replies must
1159
+ // use notification payloads so other agents in a group do not treat the
1160
+ // text as ordinary model input. Normalize guard-path replies here too,
1161
+ // since those return before slash-handler command-specific branches.
1162
+ if (/^\/(?:new|renew)(?:\s|$)/i.test(content.trim())
1163
+ && result && typeof result === 'object'
1164
+ && (result.kind === 'command.result' || result.kind === 'command.error')) {
1165
+ const isError = result.kind === 'command.error';
1166
+ return isError
1167
+ ? {
1168
+ kind: 'system.error',
1169
+ text: result.text,
1170
+ subtype: `session.${content.trim().toLowerCase().startsWith('/new') ? 'new' : 'renew'}.error`,
1171
+ recoverable: true,
1172
+ }
1173
+ : {
1174
+ kind: 'system.notice',
1175
+ text: result.text,
1176
+ subtype: `session.${content.trim().toLowerCase().startsWith('/new') ? 'new' : 'renew'}.result`,
1177
+ };
1178
+ }
1122
1179
  return result;
1123
1180
  }
1124
1181
  catch (error) {
@@ -1323,7 +1380,7 @@ export class CommandHandler {
1323
1380
  /trigger list [--all] — 查看所有触发器
1324
1381
  /trigger create <参数> — 创建触发器(set 为兼容别名)
1325
1382
  /trigger update <名称|ID> <参数> — 修改触发器
1326
- 常用参数:--exec script|trigger-session|target-session(create 必填)、--delay/--at/--cron/--every、--prompt、--model、--effort、--permission(省略则继承;update 可用 inherit 清除覆盖)
1383
+ 常用参数:--delay/--at/--cron/--every、--prompt、--description、--model、--effort、--permission(省略则继承;update 可用 inherit 清除覆盖)、--feedback、--target-channel/--target-channel-id、--trigger-thread、--baseagent、--on-error、--retry-attempts/--retry-backoff、--script-runtime/--script-args/--script-timeout
1327
1384
  /trigger enable <名称|ID> — 启用触发器
1328
1385
  /trigger disable <名称|ID> — 暂停触发器
1329
1386
  /trigger show <名称|ID> — 查看触发器详情