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.
- package/AGENTS.md +62 -0
- package/CHANGELOG.md +53 -0
- package/README.md +2 -0
- package/bin/codex-managed-acl.ps1 +155 -0
- package/bin/codex-managed-hook.mjs +3 -0
- package/bin/install-codex-managed-hooks.mjs +376 -96
- package/dist/agents/baseagent-availability.js +48 -0
- package/dist/agents/baseagent.js +1 -1
- package/dist/agents/claude-runner.js +44 -226
- package/dist/agents/{codex-app-server-client.js → codex/app-server-client.js} +103 -17
- package/dist/{utils/codex-app-server-registry.js → agents/codex/app-server-registry.js} +2 -2
- package/dist/agents/codex/catalog.js +70 -0
- package/dist/agents/codex/diagnostics.js +154 -0
- package/dist/agents/codex-runner.js +1292 -219
- package/dist/agents/ecagent-runner.js +10 -31
- package/dist/agents/gemini-runner.js +2 -6
- package/dist/aun/aid/store.js +2 -0
- package/dist/aun/menu-client.js +272 -0
- package/dist/aun/msg/group.js +65 -4
- package/dist/aun/msg/index.js +1 -1
- package/dist/aun/msg/managed-operation.js +51 -4
- package/dist/aun/outbox.js +7 -0
- package/dist/aun/rpc/client.js +8 -1
- package/dist/channels/aun.js +240 -36
- package/dist/channels/wechat.js +81 -10
- package/dist/cli/aun-commands.js +48 -4
- package/dist/cli/command-log.js +21 -2
- package/dist/cli/daemon-commands.js +169 -51
- package/dist/cli/index.js +20 -0
- package/dist/cli/init.js +2 -10
- package/dist/cli/menu-client.js +95 -0
- package/dist/cli/model.js +23 -8
- package/dist/cli/task-context.js +16 -2
- package/dist/cli/trigger-command.js +22 -5
- package/dist/config/config-field-policy.js +3 -0
- package/dist/config/config-manager.js +32 -5
- package/dist/config/contact-book-store.js +25 -3
- package/dist/config/schema-migration.js +14 -2
- package/dist/core/auth/agent-delegation.js +15 -0
- package/dist/core/auth/auth-gateway.js +8 -0
- package/dist/core/auth/authorization-audit.js +70 -6
- package/dist/core/auth/operation-catalog.js +4 -0
- package/dist/core/auth/remote-access-store.js +135 -0
- package/dist/core/bootstrap-messages.js +8 -0
- package/dist/core/bootstrap-service.js +93 -25
- package/dist/core/command/command-handler.js +62 -5
- package/dist/core/command/daemon-diagnostics.js +448 -0
- package/dist/core/command/menu-catalog.js +3 -0
- package/dist/core/command/menu-handler.js +150 -33
- package/dist/core/command/menu-protocol.js +120 -1
- package/dist/core/command/slash-gate.js +3 -3
- package/dist/core/command/slash-handler.js +83 -40
- package/dist/core/data-migration.js +11 -1
- package/dist/core/event-catalog.js +32 -0
- package/dist/core/handoff/runtime.js +23 -3
- package/dist/core/message/im-renderer.js +7 -3
- package/dist/core/message/message-bridge.js +60 -2
- package/dist/core/message/message-log.js +33 -0
- package/dist/core/message/message-queue.js +41 -15
- package/dist/core/message/response-engine.js +246 -114
- package/dist/core/model/catalog-service.js +308 -0
- package/dist/core/model/model-catalog.js +124 -334
- package/dist/core/model/model-diagnostics.js +4 -5
- package/dist/core/model/model-fallback.js +9 -16
- package/dist/core/model/model-permission.js +1 -1
- package/dist/core/permission/approval-gateway.js +16 -1
- package/dist/core/permission/ec-command-parser.js +272 -70
- package/dist/core/permission/protected-paths.js +74 -14
- package/dist/core/permission/sandbox-runtime.js +4 -2
- package/dist/core/permission/tool-error-code.js +12 -0
- package/dist/core/permission/tool-policy.js +290 -30
- package/dist/core/runtime-lock.js +13 -8
- package/dist/core/session/session-manager.js +30 -0
- package/dist/core/session/session-renew.js +18 -1
- package/dist/core/session/session-turn-coordinator.js +5 -1
- package/dist/index.js +104 -34
- package/dist/ipc.js +218 -96
- package/dist/paths.js +18 -0
- package/dist/response-system/engines/v1/proactive-flow.js +7 -2
- package/dist/stats/price-resolver.js +4 -0
- package/dist/trigger/feedback.js +14 -2
- package/dist/trigger/manager.js +3 -1
- package/dist/trigger/parser.js +86 -13
- package/dist/trigger/patch.js +104 -1
- package/dist/trigger/scheduler.js +20 -3
- package/dist/utils/ecweb-supervisor.js +11 -2
- package/dist/utils/ecweb-utils.js +8 -26
- package/dist/utils/error-utils.js +7 -0
- package/dist/utils/logger.js +9 -4
- package/dist/utils/npm-ops.js +47 -15
- package/dist/utils/tool-summary.js +59 -0
- package/dist/utils/windows-shell-trust.js +201 -0
- package/kits/docs/evolcore/INDEX.md +3 -2
- package/kits/docs/evolcore/agent-create.md +159 -0
- package/kits/docs/evolcore/agent.md +6 -0
- package/kits/docs/evolcore/group-collaboration.md +256 -0
- package/kits/docs/evolcore/group-rules.md +1 -19
- package/kits/docs/evolcore/group.md +3 -1
- package/kits/docs/evolcore/menu-client.md +34 -0
- package/kits/docs/evolcore/trigger.md +6 -3
- package/kits/docs/prompt-loading-architecture.md +6 -0
- package/kits/eck_message_manifest.json +6 -6
- package/kits/migrations/migrate-contact-book-v2.mjs +9 -1
- package/kits/migrations/migrate-role-config-v4.mjs +11 -5
- package/kits/migrations/migrate-role-config-v5.mjs +8 -2
- package/kits/schemas/_meta.json +3 -2
- package/kits/schemas/agent-config.schema.12.json +427 -0
- package/kits/templates/message-fragments/item.md +1 -1
- package/kits/templates/roles/visitor.json +1 -0
- package/kits/templates/system-fragments/bootstrap.md +10 -2
- package/kits/templates/system-fragments/commands.md +2 -2
- 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
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
/**
|
|
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
|
-
常用参数:--
|
|
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> — 查看触发器详情
|