evolcore 0.0.11 → 0.0.13
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/CHANGELOG.md +44 -0
- package/bin/codex-managed-hook.mjs +80 -0
- package/bin/ec-safe-output.js +26 -7
- package/dist/agents/claude-runner.js +86 -7
- package/dist/agents/codex-app-server-client.js +174 -20
- package/dist/agents/codex-runner.js +375 -92
- package/dist/agents/ecagent-runner.js +39 -4
- package/dist/agents/gemini-runner.js +1 -0
- package/dist/aun/aid/client.js +11 -21
- package/dist/aun/aid/control-aid.js +36 -13
- package/dist/aun/aid/managed-operation.js +107 -0
- package/dist/aun/aid/store.js +13 -7
- package/dist/aun/group-fs-download.js +56 -0
- package/dist/aun/group-identity.js +1 -2
- package/dist/aun/msg/group.js +91 -44
- package/dist/aun/msg/index.js +1 -2
- package/dist/aun/msg/managed-operation.js +865 -0
- package/dist/aun/msg/mention-schema.js +20 -0
- package/dist/aun/msg/p2p.js +7 -0
- package/dist/aun/msg/upload.js +16 -1
- package/dist/aun/outbox.js +31 -2
- package/dist/aun/rpc/client.js +66 -0
- package/dist/aun/rpc/index.js +1 -2
- package/dist/aun/storage/{upload.js → client.js} +11 -2
- package/dist/aun/storage/index.js +1 -2
- package/dist/channels/aun.js +655 -240
- package/dist/channels/daemon.js +13 -0
- package/dist/cli/agent-command.js +56 -8
- package/dist/cli/agent.js +108 -31
- package/dist/cli/aun-commands.js +270 -69
- package/dist/cli/bench.js +12 -3
- package/dist/cli/cli-argv.js +10 -0
- package/dist/cli/config.js +9 -17
- package/dist/cli/ctl-command.js +1 -1
- package/dist/cli/daemon-commands.js +70 -55
- package/dist/cli/fs-command.js +79 -10
- package/dist/cli/handoff-command.js +3 -0
- package/dist/cli/index.js +30 -2
- package/dist/cli/init.js +77 -8
- package/dist/cli/managed-command-guard.js +41 -0
- package/dist/cli/model.js +11 -4
- package/dist/cli/queue-command.js +53 -1
- package/dist/cli/response.js +71 -0
- package/dist/cli/restart-monitor.js +7 -32
- package/dist/cli/trigger-command.js +11 -0
- package/dist/config/access-policy.js +1 -2
- package/dist/config/builtin-roles.js +9 -0
- package/dist/config/config-manager.js +21 -1
- package/dist/config/contact-book-store.js +11 -3
- package/dist/config/contact-request-service.js +161 -15
- package/dist/config/mention-mode.js +8 -24
- package/dist/config/peer-role-resolver.js +6 -2
- package/dist/config/role-migration-startup.js +53 -0
- package/dist/config/role-store.js +1 -1
- package/dist/core/auth/agent-delegation.js +1 -2
- package/dist/core/auth/auth-gateway.js +29 -1
- package/dist/core/auth/authorization-audit.js +15 -0
- package/dist/core/auth/operation-authorizer.js +31 -7
- package/dist/core/auth/operation-catalog.js +196 -25
- package/dist/core/bootstrap-messages.js +25 -0
- package/dist/core/bootstrap-service.js +64 -4
- package/dist/core/capability/providers/claude-capability-provider.js +9 -31
- package/dist/core/capability/providers/codex-capability-provider.js +4 -15
- package/dist/core/capability/skill-discovery.js +55 -0
- package/dist/core/channel-loader.js +1 -1
- package/dist/core/command/cli-intent-parser.js +9 -0
- package/dist/core/command/command-handler.js +650 -42
- package/dist/core/command/connect-menu.js +15 -0
- package/dist/core/command/group-menu.js +4 -4
- package/dist/core/command/menu-catalog.js +370 -0
- package/dist/core/command/menu-handler.js +230 -51
- package/dist/core/command/menu-protocol.js +94 -2
- package/dist/core/command/slash-gate.js +5 -2
- package/dist/core/command/slash-handler.js +99 -31
- package/dist/core/daemon-file-cache.js +2 -1
- package/dist/core/data-migration.js +9 -2
- package/dist/core/event-catalog.js +6 -0
- package/dist/core/evolagent.js +4 -2
- package/dist/core/handoff/dispatcher.js +5 -2
- package/dist/core/handoff/runtime.js +157 -22
- package/dist/core/handoff/store.js +29 -1
- package/dist/core/handoff/types.js +1 -0
- package/dist/core/interaction-router.js +10 -0
- package/dist/core/message/im-renderer.js +19 -0
- package/dist/core/message/logical-queue-bridge.js +1 -0
- package/dist/core/message/mention-schema.js +126 -0
- package/dist/core/message/message-bridge.js +298 -88
- package/dist/core/message/message-log.js +3 -0
- package/dist/core/message/message-queue.js +268 -39
- package/dist/core/message/message-utils.js +81 -4
- package/dist/core/message/response-engine.js +161 -77
- package/dist/core/message/stream-debouncer.js +10 -2
- package/dist/core/permission/approval-gateway.js +1 -0
- package/dist/core/permission/ec-command-parser.js +243 -18
- package/dist/core/permission/index.js +1 -1
- package/dist/core/permission/mode.js +15 -0
- package/dist/core/permission/readonly-shell-query.js +148 -8
- package/dist/core/permission/sandbox-runtime.js +43 -1
- package/dist/core/permission/tool-policy.js +322 -57
- package/dist/core/protected-paths.js +114 -4
- package/dist/core/session/session-manager.js +42 -4
- package/dist/core/session/session-mapper.js +20 -1
- package/dist/eck/detect.js +18 -1
- package/dist/eck/group-rules-sync.js +15 -44
- package/dist/eck/manifest-engine.js +39 -13
- package/dist/index.js +541 -180
- package/dist/ipc.js +232 -10
- package/dist/paths.js +3 -0
- package/dist/response-system/coordinator.js +6 -4
- package/dist/response-system/engines/v1/proactive-flow.js +26 -8
- package/dist/response-system/modes/single-session/index.js +14 -1
- package/dist/response-system/selector.js +6 -5
- package/dist/trigger/anomaly-store.js +4 -0
- package/dist/trigger/feedback.js +64 -11
- package/dist/trigger/manager.js +21 -1
- package/dist/trigger/parser.js +14 -2
- package/dist/trigger/scheduler.js +1 -0
- package/dist/trigger/script-executor.js +1 -0
- package/dist/trigger/validation.js +67 -6
- package/dist/utils/codex-app-server-registry.js +90 -0
- package/dist/utils/codex-cli.js +5 -1
- package/dist/utils/cross-platform.js +15 -0
- package/dist/utils/ecweb-supervisor.js +332 -0
- package/dist/utils/error-utils.js +10 -0
- package/dist/utils/npm-ops.js +5 -12
- package/dist/utils/tool-summary.js +11 -0
- package/kits/docs/channels/aun.md +1 -1
- package/kits/docs/context-assembly.md +2 -2
- package/kits/docs/evolcore/agent.md +11 -6
- package/kits/docs/evolcore/aid.md +14 -0
- package/kits/docs/evolcore/trigger.md +17 -1
- package/kits/docs/prompt-loading-architecture.md +1 -2
- package/kits/docs/venues/group.md +1 -1
- package/kits/eck_manifest.json +0 -12
- package/kits/migrations/migrate-contact-book-v2.mjs +1 -2
- package/kits/rules/04-relation.md +1 -1
- package/kits/rules/05-venue.md +2 -2
- package/kits/schemas/role-config.schema.1.json +1 -0
- package/kits/schemas/single-session.schema.1.json +3 -3
- package/kits/schemas/single-session.schema.2.json +3 -3
- package/kits/templates/roles/admin.json +27 -0
- package/kits/templates/roles/member.json +34 -1
- package/kits/templates/roles/visitor.json +35 -3
- package/kits/templates/system-fragments/bootstrap.md +1 -1
- package/kits/templates/system-fragments/channel.md +1 -1
- package/kits/templates/system-fragments/session.md +1 -1
- package/package.json +2 -2
- package/dist/aun/msg/payload-type.js +0 -27
- package/dist/aun/rpc/caller.js +0 -42
- package/dist/aun/rpc/connection.js +0 -25
- package/dist/aun/storage/manage.js +0 -10
- package/dist/config/access-policy-domain.js +0 -18
- package/dist/config/config-batch-get.js +0 -11
- package/dist/config/owner-policy.js +0 -4
- package/dist/config/role-config-v4-startup.js +0 -32
- package/dist/config/role-config-v5-startup.js +0 -27
- package/dist/core/auth/trigger-authorization.js +0 -15
- package/dist/core/command/evol-menu-version-gate.js +0 -39
- package/dist/core/interaction-registration.js +0 -10
- package/dist/core/permission/execution-sandbox.js +0 -16
- package/dist/core/relation/peer-key.js +0 -1
- package/dist/core/session/session-key.js +0 -24
- package/dist/eck/baseagent-caps.js +0 -18
- package/dist/eck/rules-loader.js +0 -28
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import { createHash } from 'crypto';
|
|
2
|
+
import { loadDaemonConfig } from '../../config-store.js';
|
|
3
|
+
import { readInstalledEvolcoreVersion } from '../../utils/evolcore-version.js';
|
|
4
|
+
import { compareStableSemver, parseStableSemver } from '../../utils/stable-semver.js';
|
|
2
5
|
export const MENU_REQUEST_TYPES = new Set([
|
|
3
6
|
'menu.token.request',
|
|
4
7
|
'menu.list',
|
|
@@ -7,6 +10,72 @@ export const MENU_REQUEST_TYPES = new Set([
|
|
|
7
10
|
'menu.update',
|
|
8
11
|
'menu.action',
|
|
9
12
|
]);
|
|
13
|
+
export const MENU_NAME_COMMANDS = Object.freeze({
|
|
14
|
+
pwd: '/pwd',
|
|
15
|
+
session: '/session',
|
|
16
|
+
topic: '/topic',
|
|
17
|
+
baseagent: '/baseagent',
|
|
18
|
+
model: '/model',
|
|
19
|
+
effort: '/effort',
|
|
20
|
+
chatmode: '/chatmode',
|
|
21
|
+
mentionmode: '/mentionmode',
|
|
22
|
+
group: '/group',
|
|
23
|
+
permission: '/perm',
|
|
24
|
+
activity: '/activity',
|
|
25
|
+
dispatch: '/dispatch',
|
|
26
|
+
observable: '/observable',
|
|
27
|
+
system: '/system',
|
|
28
|
+
cli: '/cli',
|
|
29
|
+
agent: '/agent',
|
|
30
|
+
trigger: '/trigger',
|
|
31
|
+
file: '/file',
|
|
32
|
+
gateway: '/gateway',
|
|
33
|
+
config: '/config',
|
|
34
|
+
capability: '/capability',
|
|
35
|
+
role: '/role',
|
|
36
|
+
connect: '/connect',
|
|
37
|
+
});
|
|
38
|
+
export function menuCommandForName(name) {
|
|
39
|
+
if (typeof name !== 'string')
|
|
40
|
+
return undefined;
|
|
41
|
+
return MENU_NAME_COMMANDS[name];
|
|
42
|
+
}
|
|
43
|
+
export function evaluateEvolMenuVersionGate(input) {
|
|
44
|
+
const minimum = loadDaemonConfig().aun?.minEvolVersion;
|
|
45
|
+
if (!minimum)
|
|
46
|
+
return null;
|
|
47
|
+
const minimumVersion = parseStableSemver(minimum);
|
|
48
|
+
if (!minimumVersion) {
|
|
49
|
+
throw new Error('daemon.json.aun.minEvolVersion must use stable X.Y.Z format');
|
|
50
|
+
}
|
|
51
|
+
const received = input.protectedHeaders?.evol_version;
|
|
52
|
+
const receivedVersion = input.encrypted ? parseStableSemver(received) : null;
|
|
53
|
+
if (!receivedVersion || compareStableSemver(receivedVersion, minimumVersion) < 0) {
|
|
54
|
+
return {
|
|
55
|
+
code: 'UPGRADE_REQUIRED',
|
|
56
|
+
message: `Evol ${minimum} or later is required`,
|
|
57
|
+
data: {
|
|
58
|
+
minimum,
|
|
59
|
+
received: receivedVersion ? received : null,
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
export function isEvolMenuVersionGateEnabled() {
|
|
66
|
+
return loadDaemonConfig().aun?.minEvolVersion !== undefined;
|
|
67
|
+
}
|
|
68
|
+
/** Token may be issued while disabled, but only this flag makes it an access requirement. */
|
|
69
|
+
export function isAunMenuTokenRequired() {
|
|
70
|
+
return loadDaemonConfig().aun?.menuTokenRequired === true;
|
|
71
|
+
}
|
|
72
|
+
export function evolMenuResponseTransportMetadata() {
|
|
73
|
+
return {
|
|
74
|
+
// Clear inherited request state so AUNChannel applies daemon aun.defaultEncrypt.
|
|
75
|
+
encrypted: undefined,
|
|
76
|
+
protectedHeaders: { ec_version: readInstalledEvolcoreVersion() },
|
|
77
|
+
};
|
|
78
|
+
}
|
|
10
79
|
const EXPLICIT_SCOPE_UPDATE_NAMES = new Set([
|
|
11
80
|
'model',
|
|
12
81
|
'effort',
|
|
@@ -155,6 +224,8 @@ const STABLE_CODES = new Set([
|
|
|
155
224
|
'METHOD_NOT_FOUND', 'NOT_FOUND', 'CONFLICT', 'EXPIRED', 'EXECUTION_TIMEOUT',
|
|
156
225
|
'TEMPORARILY_UNAVAILABLE', 'INTERNAL_ERROR', 'MISSING_SCOPE', 'UPGRADE_REQUIRED',
|
|
157
226
|
'MENU_TOKEN_REQUIRED', 'MENU_TOKEN_REJECTED', 'MENU_TOKEN_ENCRYPTION_REQUIRED',
|
|
227
|
+
'INVALID_CONTEXT', 'SCHEMA_VERSION_UNSUPPORTED', 'DEPENDENCY_UNAVAILABLE',
|
|
228
|
+
'RATE_LIMITED', 'CATALOG_STALE',
|
|
158
229
|
]);
|
|
159
230
|
export function normalizeMenuError(error) {
|
|
160
231
|
const source = error && typeof error === 'object' ? error : {};
|
|
@@ -198,6 +269,9 @@ export class MenuRequestDeduper {
|
|
|
198
269
|
ttlMs;
|
|
199
270
|
maxEntries;
|
|
200
271
|
entries = new Map();
|
|
272
|
+
/** Retain failed request identities briefly so a subsequent identical
|
|
273
|
+
* request is observable as a retry instead of an unrelated first attempt. */
|
|
274
|
+
failures = new Map();
|
|
201
275
|
constructor(ttlMs = 30_000, maxEntries = 2_048) {
|
|
202
276
|
this.ttlMs = ttlMs;
|
|
203
277
|
this.maxEntries = maxEntries;
|
|
@@ -208,7 +282,13 @@ export class MenuRequestDeduper {
|
|
|
208
282
|
if (existing) {
|
|
209
283
|
if (existing.fingerprint !== fingerprint)
|
|
210
284
|
return { conflict: true };
|
|
211
|
-
return { value: await existing.promise, replayed: true };
|
|
285
|
+
return { value: await existing.promise, replayed: true, retry: false };
|
|
286
|
+
}
|
|
287
|
+
const previousFailure = this.failures.get(key);
|
|
288
|
+
if (previousFailure) {
|
|
289
|
+
if (previousFailure.fingerprint !== fingerprint)
|
|
290
|
+
return { conflict: true };
|
|
291
|
+
this.failures.delete(key);
|
|
212
292
|
}
|
|
213
293
|
const promise = operation();
|
|
214
294
|
const entry = { fingerprint, promise, expiresAt: Date.now() + this.ttlMs };
|
|
@@ -217,10 +297,12 @@ export class MenuRequestDeduper {
|
|
|
217
297
|
try {
|
|
218
298
|
const value = await promise;
|
|
219
299
|
entry.expiresAt = Date.now() + this.ttlMs;
|
|
220
|
-
return { value, replayed: false };
|
|
300
|
+
return { value, replayed: false, retry: !!previousFailure };
|
|
221
301
|
}
|
|
222
302
|
catch (error) {
|
|
223
303
|
this.entries.delete(key);
|
|
304
|
+
this.failures.set(key, { fingerprint, expiresAt: Date.now() + this.ttlMs });
|
|
305
|
+
this.enforceLimit();
|
|
224
306
|
throw error;
|
|
225
307
|
}
|
|
226
308
|
}
|
|
@@ -230,6 +312,10 @@ export class MenuRequestDeduper {
|
|
|
230
312
|
if (entry.expiresAt <= now)
|
|
231
313
|
this.entries.delete(key);
|
|
232
314
|
}
|
|
315
|
+
for (const [key, entry] of this.failures) {
|
|
316
|
+
if (entry.expiresAt <= now)
|
|
317
|
+
this.failures.delete(key);
|
|
318
|
+
}
|
|
233
319
|
}
|
|
234
320
|
enforceLimit() {
|
|
235
321
|
while (this.entries.size > this.maxEntries) {
|
|
@@ -238,6 +324,12 @@ export class MenuRequestDeduper {
|
|
|
238
324
|
break;
|
|
239
325
|
this.entries.delete(oldest);
|
|
240
326
|
}
|
|
327
|
+
while (this.failures.size > this.maxEntries) {
|
|
328
|
+
const oldest = this.failures.keys().next().value;
|
|
329
|
+
if (!oldest)
|
|
330
|
+
break;
|
|
331
|
+
this.failures.delete(oldest);
|
|
332
|
+
}
|
|
241
333
|
}
|
|
242
334
|
}
|
|
243
335
|
export class MenuDiagnosticLimiter {
|
|
@@ -114,7 +114,9 @@ export async function guardIdleCommand(opts) {
|
|
|
114
114
|
catch {
|
|
115
115
|
// Runner mismatch should not block recovery commands such as /baseagent.
|
|
116
116
|
}
|
|
117
|
-
const isBusy = hasActiveStream
|
|
117
|
+
const isBusy = hasActiveStream
|
|
118
|
+
|| opts.messageQueue?.isProcessing(threadSession.id)
|
|
119
|
+
|| (opts.messageQueue?.getQueueLength(threadSession.id) ?? 0) > 0;
|
|
118
120
|
if (isBusy) {
|
|
119
121
|
return { kind: 'command.error', text: '⚠️ 当前正在处理消息,请稍后再试\n使用 /stop 中断当前任务后重试' };
|
|
120
122
|
}
|
|
@@ -122,7 +124,8 @@ export async function guardIdleCommand(opts) {
|
|
|
122
124
|
}
|
|
123
125
|
else if (opts.activeSession) {
|
|
124
126
|
const isBusy = (opts.activeAgent?.hasActiveStream(opts.activeSession.id) ?? false) ||
|
|
125
|
-
opts.messageQueue?.isProcessing(opts.activeSession.id)
|
|
127
|
+
opts.messageQueue?.isProcessing(opts.activeSession.id) ||
|
|
128
|
+
(opts.messageQueue?.getQueueLength(opts.activeSession.id) ?? 0) > 0;
|
|
126
129
|
if (isBusy) {
|
|
127
130
|
return { kind: 'command.error', text: '⚠️ 当前正在处理消息,请稍后再试\n使用 /stop 中断当前任务后重试' };
|
|
128
131
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { hasModelSwitcher, hasPermissionController } from '../../agents/runner-types.js';
|
|
2
2
|
import { getCodexEfforts } from '../../agents/codex-runner.js';
|
|
3
|
-
import { buildEnvelope } from '../message/message-utils.js';
|
|
3
|
+
import { buildEnvelope, isDeliveryTargetForChannel } from '../message/message-utils.js';
|
|
4
4
|
import { resolvePaths, getPackageRoot, daemonControlDir } from '../../paths.js';
|
|
5
5
|
import { logger } from '../../utils/logger.js';
|
|
6
6
|
import { shouldSuppressRealRestart } from '../../utils/restart-safety.js';
|
|
@@ -21,9 +21,9 @@ import { filterModelsForRole, validateModelSelectionForRole } from '../model/mod
|
|
|
21
21
|
import { displaySessionTitle, isSyntheticCliPrompt } from '../session/session-title.js';
|
|
22
22
|
import { chatmodeFieldForPeer, resolveChatModeForField } from '../message/peer-mode.js';
|
|
23
23
|
import { normalizePermissionMode as normalizePermissionModeContract, PUBLIC_PERMISSION_MODES } from '../permission/mode.js';
|
|
24
|
-
import { dispatchToMentionMode } from '../../config/mention-mode.js';
|
|
25
24
|
import { isManagementRole } from '../../config/builtin-roles.js';
|
|
26
25
|
import { isSystemControlChannel } from '../system-channels.js';
|
|
26
|
+
import { spawnDetachedNode } from '../../utils/cross-platform.js';
|
|
27
27
|
import { guardIdleCommand, guardKnownCommand, guardRoleCommand, guardThreadCommand, isRecognizedSlashCommand, normalizeSlashContent, } from './slash-gate.js';
|
|
28
28
|
const allEfforts = ['low', 'medium', 'high', 'xhigh', 'max'];
|
|
29
29
|
const PERMISSION_MODE_KEYS = PUBLIC_PERMISSION_MODES;
|
|
@@ -259,7 +259,8 @@ async function getGitWorkingDirInfo(projectPath) {
|
|
|
259
259
|
cwd: projectPath,
|
|
260
260
|
encoding: 'utf8',
|
|
261
261
|
timeout: 1000,
|
|
262
|
-
stdio: ['ignore', 'pipe', 'ignore']
|
|
262
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
263
|
+
windowsHide: process.platform === 'win32',
|
|
263
264
|
}).trim();
|
|
264
265
|
if (isInsideWorkTree !== 'true')
|
|
265
266
|
return null;
|
|
@@ -268,14 +269,16 @@ async function getGitWorkingDirInfo(projectPath) {
|
|
|
268
269
|
cwd: projectPath,
|
|
269
270
|
encoding: 'utf8',
|
|
270
271
|
timeout: 1000,
|
|
271
|
-
stdio: ['ignore', 'pipe', 'ignore']
|
|
272
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
273
|
+
windowsHide: process.platform === 'win32',
|
|
272
274
|
}).trim();
|
|
273
275
|
if (!branch) {
|
|
274
276
|
branch = execFileSync('git', ['rev-parse', '--abbrev-ref', 'HEAD'], {
|
|
275
277
|
cwd: projectPath,
|
|
276
278
|
encoding: 'utf8',
|
|
277
279
|
timeout: 1000,
|
|
278
|
-
stdio: ['ignore', 'pipe', 'ignore']
|
|
280
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
281
|
+
windowsHide: process.platform === 'win32',
|
|
279
282
|
}).trim();
|
|
280
283
|
}
|
|
281
284
|
if (!branch)
|
|
@@ -285,7 +288,8 @@ async function getGitWorkingDirInfo(projectPath) {
|
|
|
285
288
|
cwd: projectPath,
|
|
286
289
|
encoding: 'utf8',
|
|
287
290
|
timeout: 1000,
|
|
288
|
-
stdio: ['ignore', 'pipe', 'ignore']
|
|
291
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
292
|
+
windowsHide: process.platform === 'win32',
|
|
289
293
|
});
|
|
290
294
|
// 解析文件状态统计
|
|
291
295
|
const stats = { modified: 0, added: 0, deleted: 0, untracked: 0 };
|
|
@@ -328,7 +332,13 @@ async function getGitWorkingDirInfo(projectPath) {
|
|
|
328
332
|
}
|
|
329
333
|
// 获取 ahead/behind 信息
|
|
330
334
|
try {
|
|
331
|
-
const revOutput = execFileSync('git', ['rev-list', '--left-right', '--count', '@{upstream}...HEAD'], {
|
|
335
|
+
const revOutput = execFileSync('git', ['rev-list', '--left-right', '--count', '@{upstream}...HEAD'], {
|
|
336
|
+
cwd: projectPath,
|
|
337
|
+
timeout: 1000,
|
|
338
|
+
encoding: 'utf8',
|
|
339
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
340
|
+
windowsHide: process.platform === 'win32',
|
|
341
|
+
});
|
|
332
342
|
const revParts = revOutput.trim().split(/\s+/);
|
|
333
343
|
if (revParts.length === 2) {
|
|
334
344
|
const behind = parseInt(revParts[0], 10) || 0;
|
|
@@ -1924,8 +1934,7 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
|
|
|
1924
1934
|
if ('error' in mentionTarget) {
|
|
1925
1935
|
return { kind: 'command.error', text: `❌ ${mentionTarget.error}` };
|
|
1926
1936
|
}
|
|
1927
|
-
|
|
1928
|
-
const mentionFallback = dispatchToMentionMode(mentionSession.metadata?.dispatchMode) ?? null;
|
|
1937
|
+
const mentionFallback = mentionSession.metadata?.mentionMode ?? null;
|
|
1929
1938
|
const currentMode = readSlashMentionMode(mentionTarget, mentionFallback);
|
|
1930
1939
|
if (!arg) {
|
|
1931
1940
|
const displayMode = currentMode ?? '未设置(跟随群设置)';
|
|
@@ -2121,20 +2130,19 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
|
|
|
2121
2130
|
selfAID,
|
|
2122
2131
|
role: sessionRole,
|
|
2123
2132
|
});
|
|
2124
|
-
|
|
2125
|
-
const mentionModeFallback = dispatchToMentionMode(session.metadata?.dispatchMode) ?? null;
|
|
2133
|
+
const mentionModeFallback = session.metadata?.mentionMode ?? null;
|
|
2126
2134
|
const mentionMode = 'error' in mentionModeTarget
|
|
2127
2135
|
? (mentionModeFallback ?? '未设置(跟随群设置)')
|
|
2128
2136
|
: (readSlashMentionMode(mentionModeTarget, mentionModeFallback) ?? '未设置(跟随群设置)');
|
|
2129
2137
|
const chatModeLine = `会话模式: ${chatMode}`;
|
|
2130
|
-
const
|
|
2138
|
+
const mentionModeLine = session.chatType === 'group' ? `@ 处理模式: ${mentionMode}` : null;
|
|
2131
2139
|
if (isAdmin) {
|
|
2132
2140
|
const gitInfo = await getGitWorkingDirInfo(session.projectPath);
|
|
2133
2141
|
lines.push(`📊 ${isThread ? '话题' : '会话'}状态 (Agent: ${agentName}):`, `渠道: ${this.resolveChannelType(channel)} / 项目: ${projectName} / 会话: ${displaySessionTitle(session.name, '(未命名)')}`, `会话ID: ${session.id}`, `项目路径: ${session.projectPath}`);
|
|
2134
2142
|
if (gitInfo) {
|
|
2135
2143
|
lines.push(`Git: ${gitInfo}`);
|
|
2136
2144
|
}
|
|
2137
|
-
lines.push(`会话状态: ${sessionStatus} / 轮数: ${sessionTurns}`, sessionRoleLine, chatModeLine, ...(
|
|
2145
|
+
lines.push(`会话状态: ${sessionStatus} / 轮数: ${sessionTurns}`, sessionRoleLine, chatModeLine, ...(mentionModeLine ? [mentionModeLine] : []));
|
|
2138
2146
|
if (health.consecutiveErrors > 0) {
|
|
2139
2147
|
lines.push(`异常计数: ${health.consecutiveErrors}`);
|
|
2140
2148
|
}
|
|
@@ -2142,7 +2150,7 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
|
|
|
2142
2150
|
}
|
|
2143
2151
|
else {
|
|
2144
2152
|
lines.push(`📊 ${isThread ? '话题' : '会话'}状态 (Agent: ${agentName}):`, `渠道: ${channel} / 项目: ${projectName} / ${session.baseagent}会话`);
|
|
2145
|
-
lines.push(`状态: ${sessionStatus} / 轮数: ${sessionTurns}`, sessionRoleLine, chatModeLine, ...(
|
|
2153
|
+
lines.push(`状态: ${sessionStatus} / 轮数: ${sessionTurns}`, sessionRoleLine, chatModeLine, ...(mentionModeLine ? [mentionModeLine] : []), `最后活跃: ${timeStr}`);
|
|
2146
2154
|
}
|
|
2147
2155
|
if (health.lastError) {
|
|
2148
2156
|
lines.push('');
|
|
@@ -2171,7 +2179,7 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
|
|
|
2171
2179
|
...(previousMetadata.channelKey ? { channelKey: previousMetadata.channelKey } : {}),
|
|
2172
2180
|
groupId: previousMetadata.groupId || channelId,
|
|
2173
2181
|
...(previousMetadata.groupName ? { groupName: previousMetadata.groupName } : {}),
|
|
2174
|
-
...(previousMetadata.
|
|
2182
|
+
...(previousMetadata.mentionMode ? { mentionMode: previousMetadata.mentionMode } : {}),
|
|
2175
2183
|
}
|
|
2176
2184
|
: {
|
|
2177
2185
|
...(previousMetadata.channelKey ? { channelKey: previousMetadata.channelKey } : {}),
|
|
@@ -2180,6 +2188,21 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
|
|
|
2180
2188
|
...(previousMetadata.peerType ? { peerType: previousMetadata.peerType } : {}),
|
|
2181
2189
|
};
|
|
2182
2190
|
const newSession = await this.sessionManager.createNewSession(channel, channelId, projectPath, sessionName, newSessionBaseagent, newSessionIdentityMetadata);
|
|
2191
|
+
const previousSession = session || activeSession;
|
|
2192
|
+
const handoffSelfAid = previousSession?.selfAID
|
|
2193
|
+
|| selfAID
|
|
2194
|
+
|| this.getOwningAgent(channel)?.aid
|
|
2195
|
+
|| this.resolveSelfAID(channel);
|
|
2196
|
+
let handoffDiscardWarning;
|
|
2197
|
+
if (previousSession?.id && handoffSelfAid && this.handoffRuntime) {
|
|
2198
|
+
try {
|
|
2199
|
+
await this.handoffRuntime.discardSession(handoffSelfAid, previousSession.id);
|
|
2200
|
+
}
|
|
2201
|
+
catch (error) {
|
|
2202
|
+
handoffDiscardWarning = '⚠️ 原会话 Handoff 清理失败,请检查 daemon 日志';
|
|
2203
|
+
logger.error(`[Handoff] failed to discard handoffs after /new: session=${previousSession.id}`, error);
|
|
2204
|
+
}
|
|
2205
|
+
}
|
|
2183
2206
|
const previousAgent = getActiveAgentIfAvailable();
|
|
2184
2207
|
if (session && previousAgent) {
|
|
2185
2208
|
// Reset agent backend state so the new
|
|
@@ -2214,7 +2237,7 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
|
|
|
2214
2237
|
?? newRunner.getEffort?.()
|
|
2215
2238
|
?? newAgent?.effort;
|
|
2216
2239
|
const backendBits = [newBaseagent, backendModel, backendEffort].filter(Boolean).join(' · ');
|
|
2217
|
-
return { kind: 'command.result', text: `✓ 已创建新会话${sessionName ? `: ${sessionName}` : ''}\n 项目: ${this.getProjectName(projectPath)}\n 后端: ${backendBits}\n 之前的对话历史已保留,可通过 /s
|
|
2240
|
+
return { kind: 'command.result', text: `✓ 已创建新会话${sessionName ? `: ${sessionName}` : ''}\n 项目: ${this.getProjectName(projectPath)}\n 后端: ${backendBits}\n 之前的对话历史已保留,可通过 /s 查看${handoffDiscardWarning ? `\n${handoffDiscardWarning}` : ''}` };
|
|
2218
2241
|
}
|
|
2219
2242
|
// /check 命令:检查 EvolAgent 实例健康(visitor/member 可用,详情仅 admin)
|
|
2220
2243
|
if (normalizedContent === '/check' || normalizedContent.startsWith('/check ')) {
|
|
@@ -2469,28 +2492,58 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
|
|
|
2469
2492
|
// 执行重启逻辑(共用于卡片回调和文本确认)
|
|
2470
2493
|
const executeRestart = async () => {
|
|
2471
2494
|
const suppressRealRestart = shouldSuppressRealRestart();
|
|
2472
|
-
let replyContext;
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2495
|
+
let restartReplyContext = replyContext;
|
|
2496
|
+
try {
|
|
2497
|
+
if (!restartReplyContext?.delivery && threadId) {
|
|
2498
|
+
const threadSession = await this.sessionManager.getThreadSession(channel, channelId, threadId);
|
|
2499
|
+
if (threadSession)
|
|
2500
|
+
restartReplyContext = this.getReplyContext(threadSession);
|
|
2501
|
+
}
|
|
2502
|
+
else if (!restartReplyContext?.delivery && activeSession) {
|
|
2503
|
+
restartReplyContext = this.getReplyContext(activeSession);
|
|
2504
|
+
}
|
|
2505
|
+
}
|
|
2506
|
+
catch (error) {
|
|
2507
|
+
const code = error?.code;
|
|
2508
|
+
if (channel.split('#', 1)[0].toLowerCase() !== 'aun'
|
|
2509
|
+
|| (code !== 'AUN_OUTBOUND_ROUTE_REQUIRED' && code !== 'AUN_OUTBOUND_ROUTE_MISMATCH')) {
|
|
2510
|
+
throw error;
|
|
2511
|
+
}
|
|
2512
|
+
logger.warn(`[System] Refusing restart from invalid persisted AUN session route: channel=${channel} channelId=${channelId} code=${code}`);
|
|
2513
|
+
return false;
|
|
2514
|
+
}
|
|
2515
|
+
// The authenticated inbound transport context takes precedence over a
|
|
2516
|
+
// potentially stale session snapshot. When no session exists, the
|
|
2517
|
+
// explicit command chat type is the only acceptable local fallback.
|
|
2518
|
+
if (restartReplyContext?.delivery === undefined) {
|
|
2519
|
+
restartReplyContext = {
|
|
2520
|
+
...(restartReplyContext ?? {}),
|
|
2521
|
+
...(chatType === 'group'
|
|
2522
|
+
? { delivery: { chatType: 'group', groupId: channelId } }
|
|
2523
|
+
: chatType === 'private'
|
|
2524
|
+
? { delivery: { chatType: 'private' } }
|
|
2525
|
+
: {}),
|
|
2526
|
+
};
|
|
2527
|
+
}
|
|
2528
|
+
if (channel.split('#', 1)[0].toLowerCase() === 'aun'
|
|
2529
|
+
&& !isDeliveryTargetForChannel(restartReplyContext?.delivery, channelId)) {
|
|
2530
|
+
logger.warn(`[System] Refusing restart without a valid explicit delivery route: channel=${channel} channelId=${channelId}`);
|
|
2531
|
+
return false;
|
|
2477
2532
|
}
|
|
2478
2533
|
const restartInfo = {
|
|
2479
2534
|
channel,
|
|
2480
2535
|
channelId,
|
|
2481
2536
|
timestamp: Date.now(),
|
|
2482
|
-
...(
|
|
2537
|
+
...(restartReplyContext?.delivery ? { delivery: restartReplyContext.delivery } : {}),
|
|
2538
|
+
...(restartReplyContext?.replyToMessageId ? { rootId: restartReplyContext.replyToMessageId } : {}),
|
|
2483
2539
|
};
|
|
2484
2540
|
if (!suppressRealRestart) {
|
|
2485
2541
|
const controlDir = daemonControlDir();
|
|
2486
2542
|
fs.mkdirSync(controlDir, { recursive: true });
|
|
2487
2543
|
fs.writeFileSync(path.join(controlDir, 'restart-pending.json'), JSON.stringify(restartInfo));
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
|
|
2491
|
-
stdio: 'ignore',
|
|
2492
|
-
env: { ...process.env, EVOLCORE_HOME: resolvePaths().root }
|
|
2493
|
-
}).unref();
|
|
2544
|
+
spawnDetachedNode([path.join(getPackageRoot(), 'dist', 'cli', 'index.js'), 'restart-monitor'], {
|
|
2545
|
+
EVOLCORE_HOME: resolvePaths().root,
|
|
2546
|
+
});
|
|
2494
2547
|
}
|
|
2495
2548
|
else {
|
|
2496
2549
|
logger.info('[System] Suppressed real restart in test runtime');
|
|
@@ -2507,7 +2560,11 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
|
|
|
2507
2560
|
channelId,
|
|
2508
2561
|
agentName: 'system',
|
|
2509
2562
|
chatmode: 'interactive',
|
|
2510
|
-
replyContext
|
|
2563
|
+
replyContext: restartReplyContext ?? (chatType === 'group'
|
|
2564
|
+
? { delivery: { chatType: 'group', groupId: channelId } }
|
|
2565
|
+
: chatType === 'private'
|
|
2566
|
+
? { delivery: { chatType: 'private' } }
|
|
2567
|
+
: undefined),
|
|
2511
2568
|
});
|
|
2512
2569
|
await adapter.send(envelope, { kind: 'command.result', text: '🔄 服务正在重启,请稍候...(约 5 秒后恢复)' });
|
|
2513
2570
|
// 等待消息发送完成后再延迟 kill
|
|
@@ -2550,7 +2607,10 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
|
|
|
2550
2607
|
return { kind: 'command.result', text: sessionsWithMessages.join('\n') + '\n再次输入 /restart 将强制重启。' };
|
|
2551
2608
|
}
|
|
2552
2609
|
}
|
|
2553
|
-
await executeRestart();
|
|
2610
|
+
const restarted = await executeRestart();
|
|
2611
|
+
if (!restarted) {
|
|
2612
|
+
return { kind: 'command.error', text: '❌ 无法确定重启通知的出站路由,请从可信会话上下文重试' };
|
|
2613
|
+
}
|
|
2554
2614
|
// executeRestart 内部已经发送了反馈消息,这里返回 null 避免重复发送
|
|
2555
2615
|
return null;
|
|
2556
2616
|
}
|
|
@@ -2708,7 +2768,15 @@ export async function handleSlashCommand(content, channel, channelId, sendMessag
|
|
|
2708
2768
|
}
|
|
2709
2769
|
// 发送文件
|
|
2710
2770
|
try {
|
|
2711
|
-
const replyCtx =
|
|
2771
|
+
const replyCtx = isCrossChannel
|
|
2772
|
+
? { delivery: { chatType: 'private' } }
|
|
2773
|
+
: sendSession
|
|
2774
|
+
? this.getReplyContext(sendSession)
|
|
2775
|
+
: chatType === 'group'
|
|
2776
|
+
? { delivery: { chatType: 'group', groupId: channelId } }
|
|
2777
|
+
: chatType === 'private'
|
|
2778
|
+
? { delivery: { chatType: 'private' } }
|
|
2779
|
+
: undefined;
|
|
2712
2780
|
await targetAdapter.send(buildEnvelope({ channel: targetAdapter.channelName, channelId: targetChannelId, replyContext: replyCtx }), { kind: 'result.file', filePath: realPath });
|
|
2713
2781
|
const sizeStr = stat.size < 1024 ? `${stat.size} B`
|
|
2714
2782
|
: stat.size < 1024 * 1024 ? `${(stat.size / 1024).toFixed(1)} KB`
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* - 只缓存"文件 → 解析后内容",不缓存按 vars 渲染后的结果。
|
|
11
11
|
*
|
|
12
12
|
* 策略(reload/重启永远全量失效,无视策略;策略只决定"平时每次读怎么检查"):
|
|
13
|
-
* - on-reload:平时不检查,直接用缓存(kits
|
|
13
|
+
* - on-reload:平时不检查,直接用缓存(kits 文件)。靠 reload/重启刷新。
|
|
14
14
|
* - manual:同 on-reload,额外支持显式 invalidate(file) 单刷。
|
|
15
15
|
* - mtime:每次读 statSync 门控 size + mtime,变了自动重读(working.md、config.json)。
|
|
16
16
|
*
|
|
@@ -35,6 +35,7 @@ function newCounters() {
|
|
|
35
35
|
*/
|
|
36
36
|
const GROUP_CAPS = {
|
|
37
37
|
'relation-prefs': 512,
|
|
38
|
+
'eck-runtime': 512,
|
|
38
39
|
};
|
|
39
40
|
export class FileCache {
|
|
40
41
|
cache = new Map();
|
|
@@ -1303,7 +1303,10 @@ function verifyArchiveContains(archivePath, sourcePaths) {
|
|
|
1303
1303
|
.replace(/\/+$/, '');
|
|
1304
1304
|
let entries;
|
|
1305
1305
|
try {
|
|
1306
|
-
entries = new Set(execFileSync(tar, ['-tzf', archivePath], {
|
|
1306
|
+
entries = new Set(execFileSync(tar, ['-tzf', archivePath], {
|
|
1307
|
+
encoding: 'utf8',
|
|
1308
|
+
windowsHide: process.platform === 'win32',
|
|
1309
|
+
})
|
|
1307
1310
|
.split('\n')
|
|
1308
1311
|
.map(normalizeArchivePath)
|
|
1309
1312
|
.filter(Boolean));
|
|
@@ -1401,7 +1404,11 @@ export function finalizeDataMigration(root, manifest) {
|
|
|
1401
1404
|
const temporary = `${archivePath}.${process.pid}.${crypto.randomBytes(6).toString('hex')}.tmp`;
|
|
1402
1405
|
const tar = process.platform === 'win32' ? 'tar.exe' : 'tar';
|
|
1403
1406
|
try {
|
|
1404
|
-
execFileSync(tar, ['-czf', temporary, ...sourcePaths], {
|
|
1407
|
+
execFileSync(tar, ['-czf', temporary, ...sourcePaths], {
|
|
1408
|
+
cwd: root,
|
|
1409
|
+
stdio: 'pipe',
|
|
1410
|
+
windowsHide: process.platform === 'win32',
|
|
1411
|
+
});
|
|
1405
1412
|
verifyArchiveContains(temporary, sourcePaths);
|
|
1406
1413
|
fs.renameSync(temporary, archivePath);
|
|
1407
1414
|
}
|
|
@@ -313,6 +313,9 @@ const CATALOG = [
|
|
|
313
313
|
{ path: 'sessionId', type: 'string' },
|
|
314
314
|
{ path: 'toolName', type: 'string' },
|
|
315
315
|
{ path: 'input', type: 'object' },
|
|
316
|
+
{ path: 'callId', type: 'string', optional: true },
|
|
317
|
+
{ path: 'agentAid', type: 'string', optional: true },
|
|
318
|
+
{ path: 'permissionMode', type: 'string', optional: true },
|
|
316
319
|
{ path: 'timestamp', type: 'number', optional: true },
|
|
317
320
|
],
|
|
318
321
|
},
|
|
@@ -326,6 +329,9 @@ const CATALOG = [
|
|
|
326
329
|
{ path: 'toolName', type: 'string' },
|
|
327
330
|
{ path: 'isError', type: 'boolean', optional: true },
|
|
328
331
|
{ path: 'agentName', type: 'string', optional: true },
|
|
332
|
+
{ path: 'callId', type: 'string', optional: true },
|
|
333
|
+
{ path: 'agentAid', type: 'string', optional: true },
|
|
334
|
+
{ path: 'permissionMode', type: 'string', optional: true },
|
|
329
335
|
{ path: 'timestamp', type: 'number', optional: true },
|
|
330
336
|
],
|
|
331
337
|
},
|
package/dist/core/evolagent.js
CHANGED
|
@@ -276,7 +276,8 @@ export class EvolAgent {
|
|
|
276
276
|
*/
|
|
277
277
|
getPersona() {
|
|
278
278
|
const personaPath = path.join(agentPersonalDir(this.aid), 'persona.md');
|
|
279
|
-
|
|
279
|
+
const raw = fileCache.getText(personaPath, { policy: 'mtime', group: this.agentFilesGroup() });
|
|
280
|
+
return raw === null ? null : (raw.trim() || null);
|
|
280
281
|
}
|
|
281
282
|
/**
|
|
282
283
|
* 读取 personal/memory/working.md 内容。走 fileCache(mtime 门控):
|
|
@@ -285,7 +286,8 @@ export class EvolAgent {
|
|
|
285
286
|
*/
|
|
286
287
|
getWorkingMemory() {
|
|
287
288
|
const workingPath = path.join(agentPersonalDir(this.aid), 'memory', 'working.md');
|
|
288
|
-
|
|
289
|
+
const raw = fileCache.getText(workingPath, { policy: 'mtime', group: this.agentFilesGroup() });
|
|
290
|
+
return raw === null ? null : (raw.trim() || null);
|
|
289
291
|
}
|
|
290
292
|
/** 清除本 agent 身份层缓存(reload 后重新读取)。只失效自己的文件组,不波及他人。 */
|
|
291
293
|
invalidatePersonaCache() {
|
|
@@ -117,8 +117,9 @@ export class HandoffDispatcher {
|
|
|
117
117
|
if (this.pausedAgents.has(selfAid))
|
|
118
118
|
return false;
|
|
119
119
|
const current = this.store.get(selfAid, next.handoff_id);
|
|
120
|
-
if (!current || current.state !== 'queued' || current.attention_required)
|
|
121
|
-
return current?.state === 'target_sent';
|
|
120
|
+
if (!current || current.state !== 'queued' || current.attention_required) {
|
|
121
|
+
return current?.state === 'target_sent' || current?.state === 'discarded';
|
|
122
|
+
}
|
|
122
123
|
this.store.recordSendStarted(selfAid, current.handoff_id);
|
|
123
124
|
try {
|
|
124
125
|
const send = await this.sendWithinQueueDeadline(current);
|
|
@@ -171,6 +172,8 @@ export class HandoffDispatcher {
|
|
|
171
172
|
await new Promise(resolve => setTimeout(resolve, this.retryDelaysMs[Math.min(attempt - 1, this.retryDelaysMs.length - 1)] ?? 0));
|
|
172
173
|
}
|
|
173
174
|
}
|
|
175
|
+
if (this.store.get(selfAid, next.handoff_id)?.state === 'discarded')
|
|
176
|
+
continue;
|
|
174
177
|
if (!sent) {
|
|
175
178
|
this.store.recordSendExhausted(selfAid, next.handoff_id);
|
|
176
179
|
logger.error(`[Handoff] target send retries exhausted: ${next.handoff_id}`);
|