evolcore 0.0.32 → 0.0.34
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 +61 -0
- package/bin/ec.js +3 -2
- package/bin/install-codex-managed-hooks.mjs +135 -37
- package/dist/agents/baseagent-availability.js +20 -36
- package/dist/agents/claude-runner.js +111 -15
- package/dist/agents/codex/app-server-client.js +3 -0
- package/dist/agents/codex/diagnostics.js +112 -54
- package/dist/agents/codex-runner.js +241 -63
- package/dist/agents/ecagent-runner.js +78 -33
- package/dist/agents/runner-types.js +5 -6
- package/dist/aun/aid/identity.js +1 -1
- package/dist/aun/group/operations.js +0 -1
- package/dist/aun/msg/history.js +1 -0
- package/dist/aun/msg/p2p.js +0 -1
- package/dist/aun/outbox.js +50 -1
- package/dist/channels/aun.js +44 -8
- package/dist/channels/daemon.js +261 -3
- package/dist/cli/agent-command.js +58 -24
- package/dist/cli/agent.js +1 -1
- package/dist/cli/aun-commands.js +368 -31
- package/dist/cli/contact.js +8 -2
- package/dist/cli/ctl-command.js +3 -1
- package/dist/cli/daemon-commands.js +197 -27
- package/dist/cli/debug-mode.js +154 -0
- package/dist/cli/fs-command.js +2 -1
- package/dist/cli/handoff-command.js +3 -0
- package/dist/cli/index.js +98 -67
- package/dist/cli/init.js +2 -2
- package/dist/cli/integrity-command.js +196 -0
- package/dist/cli/managed-command-guard.js +5 -0
- package/dist/cli/memory-command.js +166 -146
- package/dist/cli/queue-command.js +9 -4
- package/dist/cli/session-command.js +194 -0
- package/dist/cli/skill-command.js +78 -2
- package/dist/cli/task-context.js +1 -0
- package/dist/cli/thread-command.js +622 -0
- package/dist/cli/trigger-command.js +10 -3
- package/dist/config/builtin-roles.js +1 -0
- package/dist/config/config-field-policy.js +6 -3
- package/dist/config/config-manager.js +26 -2
- package/dist/config/contact-book-store.js +5 -0
- package/dist/config/resolved-config-op.js +3 -0
- package/dist/config/role-service.js +13 -0
- package/dist/config/runtime-env.js +16 -0
- package/dist/config-store.js +45 -0
- package/dist/core/audit/log-integrity.js +3 -1
- package/dist/core/auth/agent-delegation.js +133 -36
- package/dist/core/auth/debug-context.js +139 -0
- package/dist/core/auth/delegation-diagnostics.js +414 -0
- package/dist/core/auth/operation-authorizer.js +25 -2
- package/dist/core/auth/operation-catalog.js +49 -0
- package/dist/core/background-session/session-cleanup.js +212 -0
- package/dist/core/background-session/types.js +10 -0
- package/dist/core/bootstrap-messages.js +1 -1
- package/dist/core/bootstrap-service.js +6 -3
- package/dist/core/command/agent-control.js +18 -26
- package/dist/core/command/command-handler.js +197 -27
- package/dist/core/command/daemon-diagnostics.js +1 -0
- package/dist/core/command/memory-slash.js +6 -2
- package/dist/core/command/menu-catalog.js +51 -1
- package/dist/core/command/menu-handler.js +114 -23
- package/dist/core/command/menu-protocol.js +25 -1
- package/dist/core/command/skill-slash.js +322 -0
- package/dist/core/command/slash-gate.js +6 -4
- package/dist/core/command/slash-handler.js +195 -13
- package/dist/core/command/topic-slash.js +528 -0
- package/dist/core/daemon-bootstrap.js +4 -7
- package/dist/core/evolagent-registry.js +20 -5
- package/dist/core/file-integrity/read.js +61 -0
- package/dist/core/file-integrity/store.js +262 -0
- package/dist/core/handoff/runtime.js +5 -11
- package/dist/core/handoff/store.js +5 -0
- package/dist/core/ipc-registration.js +175 -3
- package/dist/core/memory/batch-meta.js +132 -0
- package/dist/core/memory/consolidation-context.js +83 -29
- package/dist/core/memory/consolidation-ops-records.js +49 -16
- package/dist/core/memory/consolidation-queue-format.js +21 -0
- package/dist/core/memory/consolidation.js +264 -205
- package/dist/core/memory/index-manager.js +19 -28
- package/dist/core/memory/memory-admin.js +19 -14
- package/dist/core/memory/memory-document.js +53 -9
- package/dist/core/memory/memory-writer.js +30 -15
- package/dist/core/message/aun-ipc-sender.js +161 -6
- package/dist/core/message/im-renderer.js +169 -10
- package/dist/core/message/message-bridge.js +15 -4
- package/dist/core/message/message-log.js +1 -0
- package/dist/core/message/peer-mode.js +6 -1
- package/dist/core/message/response-engine.js +929 -110
- package/dist/core/message/subtask-tool-projection.js +140 -0
- package/dist/core/model/catalog-service.js +17 -12
- package/dist/core/permission/ec-command-parser.js +217 -9
- package/dist/core/permission/protected-paths.js +5 -1
- package/dist/core/permission/readonly-shell-query.js +43 -5
- package/dist/core/permission/session-path-grants.js +334 -0
- package/dist/core/permission/tool-canonical.js +226 -0
- package/dist/core/permission/tool-policy.js +30 -62
- package/dist/core/role/runtime-policy.js +21 -10
- package/dist/core/session/session-fs-store.js +123 -8
- package/dist/core/session/session-idle-monitor.js +249 -22
- package/dist/core/session/session-manager.js +129 -14
- package/dist/core/skill/command-parser.js +13 -0
- package/dist/core/skill/index-generator.js +21 -4
- package/dist/core/skill/startup.js +41 -0
- package/dist/core/skill/switch-resolver.js +55 -29
- package/dist/core/system-session/grants.js +40 -0
- package/dist/core/system-session/memory.js +222 -0
- package/dist/core/system-session/registry.js +74 -0
- package/dist/core/system-session/types.js +14 -0
- package/dist/core/thread/command-parser.js +125 -0
- package/dist/core/thread/index.js +1 -0
- package/dist/core/thread/ipc-operation.js +95 -0
- package/dist/core/thread/thread-context-storage.js +131 -0
- package/dist/core/thread/thread-operation.js +258 -0
- package/dist/core/thread/thread-route.js +33 -0
- package/dist/core/thread/thread-service.js +770 -0
- package/dist/core/thread/types.js +1 -0
- package/dist/eck/kit-renderer.js +7 -2
- package/dist/eck/manifest-engine.js +24 -4
- package/dist/eck/message-renderer.js +35 -0
- package/dist/index.js +19 -7
- package/dist/ipc.js +140 -50
- package/dist/paths.js +93 -0
- package/dist/response-system/engines/v1/proactive-flow.js +169 -80
- package/dist/stats/billing.js +20 -8
- package/dist/trigger/authorization.js +57 -28
- package/dist/trigger/feedback.js +13 -2
- package/dist/trigger/parser.js +70 -1
- package/dist/trigger/system-definitions.js +3 -3
- package/dist/trigger/validation.js +14 -0
- package/dist/utils/cross-platform.js +11 -2
- package/dist/utils/error-dict.json +18 -4
- package/dist/utils/error-utils.js +143 -1
- package/dist/utils/instance-registry.js +13 -8
- package/dist/utils/log-writer.js +12 -0
- package/dist/utils/process-introspect.js +11 -0
- package/kits/docs/context-assembly.md +1 -1
- package/kits/docs/evolcore/INDEX.md +1 -0
- package/kits/docs/evolcore/agent-create.md +40 -28
- package/kits/docs/evolcore/agent.md +21 -6
- package/kits/docs/evolcore/config.md +2 -2
- package/kits/docs/evolcore/ctl.md +2 -2
- package/kits/docs/evolcore/ec-capabilities.md +5 -1
- package/kits/docs/evolcore/fs-architecture.md +4 -5
- package/kits/docs/evolcore/fs.md +1 -1
- package/kits/docs/evolcore/group-collaboration.md +4 -6
- package/kits/docs/evolcore/group.md +11 -12
- package/kits/docs/evolcore/memory-architecture.md +4 -5
- package/kits/docs/evolcore/memory-consolidation.md +19 -15
- package/kits/docs/evolcore/memory-tags.md +3 -1
- package/kits/docs/evolcore/memory.md +24 -12
- package/kits/docs/evolcore/menu-exec-schema-commands.md +2 -2
- package/kits/docs/evolcore/menu-protocol.md +7 -0
- package/kits/docs/evolcore/model.md +16 -0
- package/kits/docs/evolcore/msg.md +8 -11
- package/kits/docs/evolcore/thread-chain.md +267 -0
- package/kits/docs/prompt-loading-architecture.md +1 -1
- package/kits/eck_manifest.json +55 -40
- package/kits/eck_manifest.memory.json +0 -12
- package/kits/eck_message_manifest.json +169 -10
- package/kits/rules/01-overview.md +11 -0
- package/kits/rules/03-identity.md +6 -3
- package/kits/rules/05-venue.md +1 -1
- package/kits/rules/06-channel.md +1 -1
- package/kits/rules/07-memory-and-thread.md +199 -0
- package/kits/schemas/_meta.json +7 -1
- package/kits/schemas/agent-config.schema.13.json +1 -1
- package/kits/schemas/daemon.schema.7.json +140 -0
- package/kits/schemas/relation-config.schema.9.json +18 -3
- package/kits/schemas/role-config.schema.1.json +6 -6
- package/kits/schemas/single-session.schema.1.json +6 -6
- package/kits/schemas/single-session.schema.2.json +6 -6
- package/kits/schemas/single-session.schema.3.json +6 -6
- package/kits/skills/.switches.json +12 -1
- package/kits/skills/ec-group/SKILL.md +234 -0
- package/kits/skills/ec-skill/SKILL.md +10 -1
- package/kits/skills/ec-skill/scripts/cli.mjs +16 -2
- package/kits/skills/ec-skill/scripts/skill.mjs +1 -1
- package/kits/skills/scrapling-article-fetch/SKILL.md +128 -0
- package/kits/skills/scrapling-article-fetch/scripts/check_python_env.py +54 -0
- package/kits/skills/scrapling-article-fetch/scripts/render_markdown_reply.py +35 -0
- package/kits/skills/scrapling-article-fetch/scripts/run_pipeline.sh +16 -0
- package/kits/skills/scrapling-article-fetch/scripts/scrapling_fetch.py +344 -0
- package/kits/skills/thread-ops/SKILL.md +492 -0
- package/kits/skills/wechat-article-media-download/SKILL.md +65 -0
- package/kits/skills/wechat-article-media-download/scripts/download.py +163 -0
- package/kits/skills/wechat-article-reader/SKILL.md +97 -0
- package/kits/skills/wechat-article-reader/scripts/fetch.mjs +256 -0
- package/kits/templates/bootstrap-welcome.md +7 -7
- package/kits/templates/memory-consolidation/forget.md +7 -2
- package/kits/templates/memory-consolidation/generalize.md +9 -4
- package/kits/templates/memory-consolidation/notes.md +11 -11
- package/kits/templates/memory-consolidation/profile.md +8 -3
- package/kits/templates/memory-consolidation/promote.md +8 -3
- package/kits/templates/memory-consolidation/workflow.md +6 -6
- package/kits/templates/message-fragments/handoff-request-to-target.md +1 -1
- package/kits/templates/message-fragments/item.md +2 -1
- package/kits/templates/message-fragments/thread-context.md +6 -0
- package/kits/templates/message-fragments/trigger-session.md +2 -0
- package/kits/templates/message-fragments/trigger-thread-check.md +2 -0
- package/kits/templates/roles/member.json +2 -0
- package/kits/templates/roles/visitor.json +2 -0
- package/kits/templates/system-fragments/bootstrap.md +67 -72
- package/kits/templates/system-fragments/channel.md +3 -6
- package/kits/templates/system-fragments/commands.md +1 -0
- package/kits/templates/system-fragments/managed-runtime.md +15 -0
- package/kits/templates/system-fragments/memory.md +16 -9
- package/kits/templates/system-fragments/reply.md +7 -6
- package/kits/templates/system-fragments/session.md +5 -14
- package/package.json +6 -3
- package/dist/core/memory/memory-session-policy.js +0 -41
- package/dist/core/memory/memory-signature.js +0 -257
- package/kits/docs/evolcore/group-fs.md +0 -17
- package/kits/docs/venues/aun-group.md +0 -45
- package/kits/docs/venues/group.md +0 -43
|
@@ -2,7 +2,7 @@ import { memoryHelpEntries } from './memory-slash.js';
|
|
|
2
2
|
import { getBaseagentSnapshot, loadedBaseagentIds } from '../../agents/baseagent-availability.js';
|
|
3
3
|
import { normalizePeer, writeScope } from '../model/config-scope.js';
|
|
4
4
|
import { formatPeerKey } from '../relation/peer-identity.js';
|
|
5
|
-
import { modelMatches, getCatalog, getBaseagentCatalog, resolveCatalogModel, assertCatalogSelectable,
|
|
5
|
+
import { modelMatches, getCatalog, getBaseagentCatalog, resolveCatalogModel, assertCatalogSelectable, CONFIGURABLE_EFFORTS, configurableEfforts } from '../model/model-catalog.js';
|
|
6
6
|
import { constrainResolvedModelForRole, filterModelsForRole, validateModelSelectionForRole } from '../model/model-permission.js';
|
|
7
7
|
import { hasModelSwitcher } from '../../agents/runner-types.js';
|
|
8
8
|
import { resolvePaths, getPackageRoot, daemonControlDir } from '../../paths.js';
|
|
@@ -45,6 +45,7 @@ import { AGENT_DELEGATION_TOKEN_ENV } from '../auth/agent-delegation.js';
|
|
|
45
45
|
import { evolMenuResponseTransportMetadata, menuFailure, menuCommandForName, menuSuccess, normalizeMenuError, validateTaskActivityQueryArgs, validateConfigWriteScope, validateMenuRequest, } from './menu-protocol.js';
|
|
46
46
|
import { parseMenuClientProtectedHeaders } from './menu-protocol.js';
|
|
47
47
|
import { TaskActivityStore } from '../task/task-activity-store.js';
|
|
48
|
+
import { DEFAULT_SESSION_LOOP_SECONDS, MAX_SESSION_LOOP_SECONDS, MIN_SESSION_LOOP_SECONDS, refreshSessionLoopTarget, resolveCurrentSessionLoopTarget, sessionLoopPayload, validateIdleSeconds, writeSessionLoopConfig, } from '../session/session-idle-monitor.js';
|
|
48
49
|
import { remoteAccessService, validateAndRenewRemoteAccessToken } from '../auth/remote-access-store.js';
|
|
49
50
|
import { listDiagnosticLogOptions, listRawConfigResources, queryDiagnosticLogs, readRawConfigResource, } from './daemon-diagnostics.js';
|
|
50
51
|
import { roleMenuAction, roleMenuOperation, roleMenuOptions, roleMenuQuery, roleMenuUpdate, } from './role-menu.js';
|
|
@@ -63,7 +64,7 @@ import { normalizeTopicLabel, normalizeTopicStatus, resolveTopicArchiveFilter, }
|
|
|
63
64
|
function getBaseagentVersion(cmd) {
|
|
64
65
|
return cachedCliVersion(cmd);
|
|
65
66
|
}
|
|
66
|
-
const allEfforts =
|
|
67
|
+
const allEfforts = CONFIGURABLE_EFFORTS;
|
|
67
68
|
const PERMISSION_MODE_KEYS = PUBLIC_PERMISSION_MODES;
|
|
68
69
|
/** menu file: fetch 文件大小上限(与 /file 和 AUN sendFile 一致) */
|
|
69
70
|
const FILE_FETCH_MAX_SIZE = MAX_AUN_ATTACHMENT_SIZE;
|
|
@@ -402,14 +403,14 @@ function tokenizeArgv(line) {
|
|
|
402
403
|
return out;
|
|
403
404
|
}
|
|
404
405
|
async function getAvailableEfforts(agent, model) {
|
|
406
|
+
const baseagent = agent?.baseagent || agent?.name;
|
|
405
407
|
const dynamic = await agent.getAvailableEfforts?.(model);
|
|
406
408
|
if (Array.isArray(dynamic))
|
|
407
|
-
return dynamic;
|
|
409
|
+
return configurableEfforts(baseagent, dynamic);
|
|
408
410
|
// Keep Menu usable with legacy/embedded runner implementations that do not
|
|
409
411
|
// expose the optional catalog method. Full runners still take the dynamic
|
|
410
412
|
// path above, so this fallback cannot broaden their advertised capabilities.
|
|
411
|
-
|
|
412
|
-
return (TRANSPORT_EFFORTS[baseagent] ?? []);
|
|
413
|
+
return configurableEfforts(baseagent);
|
|
413
414
|
}
|
|
414
415
|
function isDaemonControlSubject(subject) {
|
|
415
416
|
return subject.isDaemonOwner === true
|
|
@@ -1143,6 +1144,18 @@ function buildMenuAuthSubject(owner, params) {
|
|
|
1143
1144
|
fromControlChannel: params.fromControlChannel,
|
|
1144
1145
|
});
|
|
1145
1146
|
}
|
|
1147
|
+
function agentFullAccessMenuSubject(subject, source) {
|
|
1148
|
+
return {
|
|
1149
|
+
...subject,
|
|
1150
|
+
agentFullAccessManagement: source === 'control'
|
|
1151
|
+
? subject.agentFullAccessManagement === true
|
|
1152
|
+
: source === 'menu'
|
|
1153
|
+
&& subject.channelType === 'aun'
|
|
1154
|
+
&& !subject.fromControlChannel
|
|
1155
|
+
&& subject.canApprovePersistentFullAccess === true
|
|
1156
|
+
&& subject.processRole === 'daemon-owner',
|
|
1157
|
+
};
|
|
1158
|
+
}
|
|
1146
1159
|
async function authorizeMenuIntent(params) {
|
|
1147
1160
|
const { intent, identity, session, channel, channelId, userId, fromControlChannel } = params;
|
|
1148
1161
|
if (!intent)
|
|
@@ -1456,12 +1469,7 @@ export function getMenuItems(role, chatType = 'private', scope = 'agent', authSu
|
|
|
1456
1469
|
desc: canUseModel ? '切换当前 Agent 使用的模型版本' : '查看当前 Agent 使用的模型版本',
|
|
1457
1470
|
next: canUseModel ? { type: 'select', dynamic: true } : undefined,
|
|
1458
1471
|
}] : []),
|
|
1459
|
-
...(canSetEffort ? [{ cmd: '/effort', label: '切换推理强度', desc: '调整模型推理深度,影响响应速度与质量', next: { type: 'select',
|
|
1460
|
-
{ value: 'low', label: 'Low' },
|
|
1461
|
-
{ value: 'medium', label: 'Medium' },
|
|
1462
|
-
{ value: 'high', label: 'High' },
|
|
1463
|
-
{ value: 'max', label: 'Max' },
|
|
1464
|
-
] } }] : []),
|
|
1472
|
+
...(canSetEffort ? [{ cmd: '/effort', label: '切换推理强度', desc: '调整模型推理深度,影响响应速度与质量', next: { type: 'select', dynamic: true } }] : []),
|
|
1465
1473
|
...(canSetChatmode ? [{ cmd: '/chatmode', label: '切换会话模式', desc: '控制 Agent 主动性(被动响应或主动推进)', next: { type: 'select', items: [
|
|
1466
1474
|
{ value: 'interactive', label: '交互模式', desc: '仅在收到消息时响应' },
|
|
1467
1475
|
{ value: 'proactive', label: '主动模式', desc: 'Agent 可主动推进任务' },
|
|
@@ -1723,8 +1731,8 @@ export async function getSubMenuItems(cmd, channel, channelId, userId, args, ove
|
|
|
1723
1731
|
if (!selfAid)
|
|
1724
1732
|
throw { code: 'FORBIDDEN', message: '当前 channel 无绑定 agent' };
|
|
1725
1733
|
const res = this.agentApplicationService
|
|
1726
|
-
? await execAgentQuery({ aid: selfAid }, this.agentApplicationService, subject)
|
|
1727
|
-
: await execAgentQuery({ aid: selfAid }, undefined, subject);
|
|
1734
|
+
? await execAgentQuery({ aid: selfAid }, this.agentApplicationService, agentFullAccessMenuSubject(subject, source))
|
|
1735
|
+
: await execAgentQuery({ aid: selfAid }, undefined, agentFullAccessMenuSubject(subject, source));
|
|
1728
1736
|
if ('error' in res)
|
|
1729
1737
|
throw { code: res.code, message: res.error };
|
|
1730
1738
|
const ag = res.data;
|
|
@@ -1966,7 +1974,7 @@ export async function getSubMenuItems(cmd, channel, channelId, userId, args, ove
|
|
|
1966
1974
|
}
|
|
1967
1975
|
catch (error) {
|
|
1968
1976
|
if (isDaemonControlSubject(subject)) {
|
|
1969
|
-
return [
|
|
1977
|
+
return [...configurableEfforts(target.baseagent), 'auto'].map(e => ({ value: e, label: e === 'auto' ? 'auto (SDK默认)' : e }));
|
|
1970
1978
|
}
|
|
1971
1979
|
throw error;
|
|
1972
1980
|
}
|
|
@@ -2066,6 +2074,26 @@ export async function execMenuQuery(cmd, channel, channelId, userId, args, expli
|
|
|
2066
2074
|
fromControlChannel,
|
|
2067
2075
|
subject: authSubject,
|
|
2068
2076
|
});
|
|
2077
|
+
if (cmdBase === '/session' && args?.view === 'loop') {
|
|
2078
|
+
if (Object.keys(args).some(key => key !== 'view' && key !== 'context')) {
|
|
2079
|
+
return { error: 'session.loop query only accepts args.view', code: 'INVALID_ARGUMENT' };
|
|
2080
|
+
}
|
|
2081
|
+
try {
|
|
2082
|
+
const route = { channel, channelId, selfAid: subject.selfAid ?? '', threadId: transportReplyContext?.threadId };
|
|
2083
|
+
const target = await resolveCurrentSessionLoopTarget(this.sessionManager, route);
|
|
2084
|
+
const denied = await authorizeMenuIntent.call(this, {
|
|
2085
|
+
intent: { operation: 'session.loop.current', scope: 'relation', source, args: { sessionId: target.session.id, peer: target.peerKey } },
|
|
2086
|
+
identity, subject: { ...subject, sessionId: target.session.id }, session: target.session, channel, channelId, userId, fromControlChannel, source,
|
|
2087
|
+
});
|
|
2088
|
+
if (denied)
|
|
2089
|
+
return denied;
|
|
2090
|
+
const current = await refreshSessionLoopTarget(this.sessionManager, route, target);
|
|
2091
|
+
return { data: sessionLoopPayload(current) };
|
|
2092
|
+
}
|
|
2093
|
+
catch (error) {
|
|
2094
|
+
return menuResultFailure(error);
|
|
2095
|
+
}
|
|
2096
|
+
}
|
|
2069
2097
|
const storageRequest = { type: 'menu.query', cmd, args };
|
|
2070
2098
|
if (isAgentStorageMenuRequest(storageRequest)) {
|
|
2071
2099
|
try {
|
|
@@ -2270,8 +2298,8 @@ export async function execMenuQuery(cmd, channel, channelId, userId, args, expli
|
|
|
2270
2298
|
if (fromControlChannel) {
|
|
2271
2299
|
if (args?.aid) {
|
|
2272
2300
|
return this.agentApplicationService
|
|
2273
|
-
? await execAgentQuery(args, this.agentApplicationService, subject)
|
|
2274
|
-
: await execAgentQuery(args, undefined, subject);
|
|
2301
|
+
? await execAgentQuery(args, this.agentApplicationService, agentFullAccessMenuSubject(subject, source))
|
|
2302
|
+
: await execAgentQuery(args, undefined, agentFullAccessMenuSubject(subject, source));
|
|
2275
2303
|
}
|
|
2276
2304
|
return this.agentApplicationService
|
|
2277
2305
|
? await execAgentOptions(args, this.agentApplicationService)
|
|
@@ -2281,8 +2309,8 @@ export async function execMenuQuery(cmd, channel, channelId, userId, args, expli
|
|
|
2281
2309
|
if (!selfAid)
|
|
2282
2310
|
return { error: '当前 channel 无绑定 agent', code: 'FORBIDDEN' };
|
|
2283
2311
|
return this.agentApplicationService
|
|
2284
|
-
? await execAgentQuery({ ...(args ?? {}), aid: selfAid }, this.agentApplicationService, subject)
|
|
2285
|
-
: await execAgentQuery({ ...(args ?? {}), aid: selfAid }, undefined, subject);
|
|
2312
|
+
? await execAgentQuery({ ...(args ?? {}), aid: selfAid }, this.agentApplicationService, agentFullAccessMenuSubject(subject, source))
|
|
2313
|
+
: await execAgentQuery({ ...(args ?? {}), aid: selfAid }, undefined, agentFullAccessMenuSubject(subject, source));
|
|
2286
2314
|
}
|
|
2287
2315
|
// ── /gateway 查询(只读,列出全部作用域的网关配置;apiKey 已掩码) ──
|
|
2288
2316
|
if (cmdBase === '/gateway') {
|
|
@@ -2863,6 +2891,21 @@ export async function execMenuUpdate(cmd, value, channel, channelId, userId, ove
|
|
|
2863
2891
|
const cmdBase = cmd.trim().split(' ')[0];
|
|
2864
2892
|
if (!cmdBase)
|
|
2865
2893
|
return { error: '缺少命令', code: 'MISSING_CMD' };
|
|
2894
|
+
const isLoopUpdate = cmdBase === '/session' && args?.resource === 'loop';
|
|
2895
|
+
if (isLoopUpdate) {
|
|
2896
|
+
if (typeof value !== 'boolean' || Object.keys(args ?? {}).some(key => key !== 'resource' && key !== 'idleSeconds' && key !== 'context')) {
|
|
2897
|
+
return { error: 'session.loop requires a boolean value and only accepts resource / idleSeconds', code: 'INVALID_ARGUMENT' };
|
|
2898
|
+
}
|
|
2899
|
+
if (!value && Object.hasOwn(args ?? {}, 'idleSeconds')) {
|
|
2900
|
+
return { error: '关闭时不能提供 idleSeconds', code: 'INVALID_ARGUMENT' };
|
|
2901
|
+
}
|
|
2902
|
+
if (Object.hasOwn(args ?? {}, 'idleSeconds') && validateIdleSeconds(args?.idleSeconds) === undefined) {
|
|
2903
|
+
return { error: `idleSeconds 必须是 ${MIN_SESSION_LOOP_SECONDS}-${MAX_SESSION_LOOP_SECONDS} 的整数`, code: 'INVALID_ARGUMENT' };
|
|
2904
|
+
}
|
|
2905
|
+
}
|
|
2906
|
+
else if (typeof value !== 'string') {
|
|
2907
|
+
return { error: 'value 必须是字符串', code: 'INVALID_ARGUMENT' };
|
|
2908
|
+
}
|
|
2866
2909
|
if (cmdBase === '/perm') {
|
|
2867
2910
|
return { error: 'permissionMode 只能通过角色策略编辑器修改', code: 'ROLE_POLICY_MANAGED' };
|
|
2868
2911
|
}
|
|
@@ -2888,9 +2931,6 @@ export async function execMenuUpdate(cmd, value, channel, channelId, userId, ove
|
|
|
2888
2931
|
}
|
|
2889
2932
|
if (cmdBase === '/baseagent-models')
|
|
2890
2933
|
return { error: 'Baseagent catalog only supports menu.query', code: 'NOT_SUPPORTED' };
|
|
2891
|
-
const arg = value.trim();
|
|
2892
|
-
if (!arg)
|
|
2893
|
-
return { error: '缺少 value 参数', code: 'MISSING_VALUE' };
|
|
2894
2934
|
const identity = overrideIdentity ?? this.sessionManager.resolveIdentity(channel, userId, ...menuIdentityArgs(session));
|
|
2895
2935
|
const isAdmin = isManagementRole(identity.role);
|
|
2896
2936
|
const subject = buildMenuAuthSubject(this, {
|
|
@@ -2902,6 +2942,31 @@ export async function execMenuUpdate(cmd, value, channel, channelId, userId, ove
|
|
|
2902
2942
|
fromControlChannel,
|
|
2903
2943
|
subject: authSubject,
|
|
2904
2944
|
});
|
|
2945
|
+
if (isLoopUpdate) {
|
|
2946
|
+
try {
|
|
2947
|
+
const route = { channel, channelId, selfAid: subject.selfAid ?? '', threadId: transportReplyContext?.threadId };
|
|
2948
|
+
const target = await resolveCurrentSessionLoopTarget(this.sessionManager, route);
|
|
2949
|
+
const denied = await authorizeMenuIntent.call(this, {
|
|
2950
|
+
intent: { operation: 'session.loop.update', scope: 'relation', source, args: { sessionId: target.session.id, peer: target.peerKey, value, ...(value ? { idleSeconds: args?.idleSeconds ?? DEFAULT_SESSION_LOOP_SECONDS } : {}) } },
|
|
2951
|
+
identity, subject: { ...subject, sessionId: target.session.id }, session: target.session, channel, channelId, userId, fromControlChannel, source,
|
|
2952
|
+
});
|
|
2953
|
+
if (denied)
|
|
2954
|
+
return denied;
|
|
2955
|
+
const current = await refreshSessionLoopTarget(this.sessionManager, route, target);
|
|
2956
|
+
const config = value === true
|
|
2957
|
+
? { enabled: true, idleSeconds: args?.idleSeconds ?? DEFAULT_SESSION_LOOP_SECONDS }
|
|
2958
|
+
: { enabled: false };
|
|
2959
|
+
if (config.enabled)
|
|
2960
|
+
this.assertSessionLoopTrigger(current.session);
|
|
2961
|
+
return { data: sessionLoopPayload(writeSessionLoopConfig(this.sessionManager, current, config)) };
|
|
2962
|
+
}
|
|
2963
|
+
catch (error) {
|
|
2964
|
+
return menuResultFailure(error);
|
|
2965
|
+
}
|
|
2966
|
+
}
|
|
2967
|
+
const arg = value.trim();
|
|
2968
|
+
if (!arg)
|
|
2969
|
+
return { error: '缺少 value 参数', code: 'MISSING_VALUE' };
|
|
2905
2970
|
if (cmdBase === '/role') {
|
|
2906
2971
|
const authorized = await authorizeRoleMenu(this, {
|
|
2907
2972
|
kind: 'update', args, value: arg, identity, subject: authSubject, session, channel, channelId,
|
|
@@ -3654,7 +3719,7 @@ export async function execMenuAction(cmd, action, args, channel, channelId, user
|
|
|
3654
3719
|
}
|
|
3655
3720
|
}
|
|
3656
3721
|
}
|
|
3657
|
-
return await execAgentAction(action, a, userId ?? '', this.eventBus, this.agentApplicationService, subject);
|
|
3722
|
+
return await execAgentAction(action, a, userId ?? '', this.eventBus, this.agentApplicationService, agentFullAccessMenuSubject(subject, source));
|
|
3658
3723
|
}
|
|
3659
3724
|
// ── 关系级 /trigger(不走 owners;复用 isAdmin + scoped 逻辑,D4 直调底层) ──
|
|
3660
3725
|
if (cmdBase === '/trigger') {
|
|
@@ -4123,6 +4188,21 @@ export async function execMenuAction(cmd, action, args, channel, channelId, user
|
|
|
4123
4188
|
}
|
|
4124
4189
|
catch { }
|
|
4125
4190
|
}
|
|
4191
|
+
// P1a.5.3: 若为话题会话,同步更新话题索引
|
|
4192
|
+
if (topic.threadId) {
|
|
4193
|
+
try {
|
|
4194
|
+
const { ThreadService } = await import('../../core/thread/index.js');
|
|
4195
|
+
const selfAid = topic.selfAID;
|
|
4196
|
+
const peer = topic.channelId;
|
|
4197
|
+
if (selfAid && peer) {
|
|
4198
|
+
const svc = new ThreadService(this.sessionManager);
|
|
4199
|
+
await svc.renameThread({ selfAid, peer, threadId: topic.threadId, name: newName });
|
|
4200
|
+
}
|
|
4201
|
+
}
|
|
4202
|
+
catch (err) {
|
|
4203
|
+
logger.debug('[MenuHandler] Thread index rename sync failed:', err);
|
|
4204
|
+
}
|
|
4205
|
+
}
|
|
4126
4206
|
this.eventBus.publish({ type: 'session:renamed', sessionId: topic.id, oldName, newName });
|
|
4127
4207
|
return {
|
|
4128
4208
|
data: {
|
|
@@ -4754,6 +4834,7 @@ async function execMenuForSystemControl(payload, context) {
|
|
|
4754
4834
|
: undefined,
|
|
4755
4835
|
});
|
|
4756
4836
|
const trustedIdentity = trustedSubject.identity;
|
|
4837
|
+
trustedSubject.agentFullAccessManagement = context.source === 'control' && context.authMethod === 'daemon-owner';
|
|
4757
4838
|
const cmd = name ? (menuCommandForName(name) ?? payload.cmd) : payload.cmd;
|
|
4758
4839
|
if (payload?.type === 'menu.action'
|
|
4759
4840
|
&& menuCommandToken(cmd) === '/system'
|
|
@@ -4781,6 +4862,14 @@ async function execMenuForSystemControl(payload, context) {
|
|
|
4781
4862
|
request: payload.args.context,
|
|
4782
4863
|
paircodeAllowed: context.authMethod === 'daemon-owner'
|
|
4783
4864
|
&& (context.source !== 'control' || context.encrypted),
|
|
4865
|
+
resolveLoopTarget: async (catalogContext) => {
|
|
4866
|
+
const target = await resolveCurrentSessionLoopTarget(this.sessionManager, {
|
|
4867
|
+
channel: targetChannel, channelId: catalogContext.channelId, selfAid: catalogContext.self ?? '',
|
|
4868
|
+
});
|
|
4869
|
+
if (target.peerKey !== catalogContext.peerKey)
|
|
4870
|
+
throw { code: 'INVALID_CONTEXT', message: '当前会话关系不匹配' };
|
|
4871
|
+
return { level: target.level, sessionId: target.session.id };
|
|
4872
|
+
},
|
|
4784
4873
|
});
|
|
4785
4874
|
return menuSuccess({ id, ...(name ? { name } : {}) }, data);
|
|
4786
4875
|
}
|
|
@@ -4839,8 +4928,10 @@ async function execMenuForSystemControl(payload, context) {
|
|
|
4839
4928
|
case 'menu.update': {
|
|
4840
4929
|
if (!cmd)
|
|
4841
4930
|
return ecwebErr(id, name, 'MISSING_CMD', '缺少 name/cmd');
|
|
4842
|
-
if (
|
|
4931
|
+
if (payload.value === undefined || payload.value === null
|
|
4932
|
+
|| (typeof payload.value === 'string' && payload.value.length === 0)) {
|
|
4843
4933
|
return ecwebErr(id, name, 'MISSING_VALUE', '缺少 value');
|
|
4934
|
+
}
|
|
4844
4935
|
const execution = await resolveExplicitMenuExecutionContext({
|
|
4845
4936
|
subject: trustedSubject,
|
|
4846
4937
|
channelType: 'aun',
|
|
@@ -4,6 +4,7 @@ import { loadDaemonConfig } from '../../config-store.js';
|
|
|
4
4
|
import { readInstalledEvolcoreVersion } from '../../utils/evolcore-version.js';
|
|
5
5
|
import { compareStableSemver, parseStableSemver } from '../../utils/stable-semver.js';
|
|
6
6
|
import { logger } from '../../utils/logger.js';
|
|
7
|
+
import { validateIdleSeconds } from '../session/session-idle-monitor.js';
|
|
7
8
|
export const MENU_REQUEST_TYPES = new Set([
|
|
8
9
|
'menu.token.request',
|
|
9
10
|
'menu.list',
|
|
@@ -334,9 +335,31 @@ export function validateMenuRequest(request) {
|
|
|
334
335
|
if (scopeError)
|
|
335
336
|
return scopeError;
|
|
336
337
|
}
|
|
337
|
-
|
|
338
|
+
const isSessionLoopUpdate = type === 'menu.update'
|
|
339
|
+
&& (request.name === 'session' || request.cmd === '/session')
|
|
340
|
+
&& request.args?.resource === 'loop';
|
|
341
|
+
if (isSessionLoopUpdate) {
|
|
342
|
+
if (typeof request.value !== 'boolean')
|
|
343
|
+
return { code: 'INVALID_ARGUMENT', message: 'session.loop value must be a boolean' };
|
|
344
|
+
const args = request.args;
|
|
345
|
+
const unknown = Object.keys(args ?? {}).find(key => !['resource', 'idleSeconds', 'context'].includes(key));
|
|
346
|
+
if (unknown)
|
|
347
|
+
return { code: 'INVALID_ARGUMENT', message: `session.loop update does not accept args.${unknown}` };
|
|
348
|
+
if (Object.hasOwn(args ?? {}, 'idleSeconds') && validateIdleSeconds(args?.idleSeconds) === undefined) {
|
|
349
|
+
return { code: 'INVALID_ARGUMENT', message: 'idleSeconds must be an integer between 30 and 86400' };
|
|
350
|
+
}
|
|
351
|
+
if (request.value === false && Object.hasOwn(args ?? {}, 'idleSeconds')) {
|
|
352
|
+
return { code: 'INVALID_ARGUMENT', message: 'idleSeconds is not allowed when session.loop is disabled' };
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
else if (type === 'menu.update' && (typeof request.value !== 'string' || request.value.length === 0)) {
|
|
338
356
|
return { code: 'INVALID_ARGUMENT', message: 'Menu request value is required' };
|
|
339
357
|
}
|
|
358
|
+
if (type === 'menu.query' && (request.name === 'session' || request.cmd === '/session')
|
|
359
|
+
&& request.args?.view === 'loop'
|
|
360
|
+
&& Object.keys(request.args).some(key => key !== 'view' && key !== 'context')) {
|
|
361
|
+
return { code: 'INVALID_ARGUMENT', message: 'session.loop query only accepts args.view' };
|
|
362
|
+
}
|
|
340
363
|
if (type === 'menu.action' && (typeof request.action !== 'string' || request.action.trim().length === 0)) {
|
|
341
364
|
return { code: 'INVALID_ARGUMENT', message: 'Menu request action is required' };
|
|
342
365
|
}
|
|
@@ -447,6 +470,7 @@ const STABLE_CODES = new Set([
|
|
|
447
470
|
'RESPONSE_TOO_LARGE',
|
|
448
471
|
'ACTIVITY_NOT_FOUND', 'ACTIVITY_EXPIRED',
|
|
449
472
|
'TOPIC_SESSION_NOT_FOUND', 'TOPIC_SESSION_LOOKUP_FAILED',
|
|
473
|
+
'LOOP_TRIGGER_NOT_FOUND', 'LOOP_TRIGGER_DISABLED', 'LOOP_TRIGGER_INVALID',
|
|
450
474
|
'ROOT_NOT_FOUND', 'ROOT_FORBIDDEN', 'ROOT_EXPIRED', 'ROOT_SCOPE_MISMATCH', 'ROOT_PEER_MISMATCH',
|
|
451
475
|
'INVALID_ROOT_ID', 'MISSING_ROOT_ID', 'INVALID_ROOT_PATH', 'NOT_A_DIRECTORY', 'FILE_TOO_LARGE',
|
|
452
476
|
]);
|
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* skill-slash.ts — `/skill` 斜杠命令:技能查看与索引刷新
|
|
3
|
+
*
|
|
4
|
+
* 权限:
|
|
5
|
+
* - 只读(list / show / where):私聊所有角色可看;群聊仅 admin+ 可看
|
|
6
|
+
* - refresh:私聊仅 admin+;群聊一律拒绝
|
|
7
|
+
*
|
|
8
|
+
* 本文件刻意**只做只读 + refresh**:写操作(install / remove / enable / disable)
|
|
9
|
+
* 的权限门禁在 `kits/skills/ec-skill/scripts/skill.mjs` 的 `assertPermission()` 里,
|
|
10
|
+
* 那套判据(群聊拒绝、agent 级需本 Agent owner、本机级需 daemonowner)依赖运行时上下文,
|
|
11
|
+
* 在 slash 层重实现一份必然漂移。写操作请走 `ec skill` 或 ec-skill 技能。
|
|
12
|
+
*/
|
|
13
|
+
import fs from 'fs';
|
|
14
|
+
import path from 'path';
|
|
15
|
+
import { agentDir, getPackageRoot, resolveRoot } from '../../paths.js';
|
|
16
|
+
import { resolveAllSkillStates } from '../skill/switch-resolver.js';
|
|
17
|
+
import { readSkillDescription } from '../skill/index-generator.js';
|
|
18
|
+
import { evaluateWhen, loadManifest, resolvePathWithDiag, } from '../../eck/manifest-engine.js';
|
|
19
|
+
import { DEFAULT_MANIFEST_FILE } from '../../eck/kit-renderer.js';
|
|
20
|
+
import { resolveAgentLifecycle } from '../../config/lifecycle.js';
|
|
21
|
+
const USAGE = [
|
|
22
|
+
'用法:',
|
|
23
|
+
' /skill 技能概览(启用数 / 总数)',
|
|
24
|
+
' /skill list [all] 技能列表(默认仅启用;all 显示未启用的)',
|
|
25
|
+
' /skill index 查看技能索引加载情况(哪些索引文件被加载、时间、技能数)',
|
|
26
|
+
' /skill index builtin 查看内置技能索引正文',
|
|
27
|
+
' /skill index local 查看本机技能索引正文',
|
|
28
|
+
' /skill show <name> 单个技能详情:来源、状态、生效依据、描述',
|
|
29
|
+
' /skill where 打印三级技能根目录',
|
|
30
|
+
' /skill refresh 重建当前 Agent 的技能索引',
|
|
31
|
+
'',
|
|
32
|
+
'写操作(install / remove / enable / disable)请用 ec skill 或 ec-skill 技能。',
|
|
33
|
+
].join('\n');
|
|
34
|
+
const result = (text) => ({ kind: 'command.result', text });
|
|
35
|
+
const error = (text) => ({ kind: 'command.error', text: `❌ ${text}` });
|
|
36
|
+
/** 只读子命令:私聊所有角色;群聊仅 admin+。 */
|
|
37
|
+
export function canReadSkillSlash(ctx) {
|
|
38
|
+
return ctx.chatType === 'group' ? ctx.isAdmin : true;
|
|
39
|
+
}
|
|
40
|
+
/** refresh:私聊仅 admin+;群聊一律拒绝。 */
|
|
41
|
+
export function canRefreshSkillSlash(ctx) {
|
|
42
|
+
return ctx.chatType === 'group' ? false : ctx.isAdmin;
|
|
43
|
+
}
|
|
44
|
+
export function canUseSkillSlash(ctx) {
|
|
45
|
+
return canReadSkillSlash(ctx);
|
|
46
|
+
}
|
|
47
|
+
/** evolhelp「技能管理」条目。可见范围与 canReadSkillSlash / canRefreshSkillSlash 一致。 */
|
|
48
|
+
export function skillHelpEntries(ctx) {
|
|
49
|
+
if (!canUseSkillSlash(ctx))
|
|
50
|
+
return [];
|
|
51
|
+
const category = '技能管理';
|
|
52
|
+
const roles = [ctx.role];
|
|
53
|
+
const out = [
|
|
54
|
+
{ command: '/skill list', description: '已启用的技能列表', category, roles },
|
|
55
|
+
{ command: '/skill list all', description: '全部技能列表(含未启用)', category, roles },
|
|
56
|
+
{ command: '/skill index', description: '技能索引加载情况(文件、时间、技能数)', category, roles },
|
|
57
|
+
{ command: '/skill index builtin', description: '查看内置技能索引正文', category, roles },
|
|
58
|
+
{ command: '/skill index local', description: '查看本机技能索引正文', category, roles },
|
|
59
|
+
{ command: '/skill where', description: '三级技能根目录', category, roles },
|
|
60
|
+
];
|
|
61
|
+
if (canRefreshSkillSlash(ctx)) {
|
|
62
|
+
out.push({ command: '/skill refresh', description: '重建当前 Agent 的技能索引', category, roles });
|
|
63
|
+
}
|
|
64
|
+
out.push({ command: '/skill show', description: '单个技能详情', args: '<name>', category, roles });
|
|
65
|
+
return out;
|
|
66
|
+
}
|
|
67
|
+
/** 技能三级根目录;与 ec-skill 脚本的 skillRoots() 保持一致。 */
|
|
68
|
+
function skillRoots(selfAid) {
|
|
69
|
+
return {
|
|
70
|
+
builtin: path.join(getPackageRoot(), 'kits', 'skills'),
|
|
71
|
+
local: path.join(resolveRoot(), 'skills'),
|
|
72
|
+
agent: selfAid ? path.join(agentDir(selfAid), 'skills') : '(未确定 agent)',
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
/** 目录本身是 agent 级 skills/,resolveAllSkillStates 要的是 agent 根目录。 */
|
|
76
|
+
function agentRootFor(selfAid) {
|
|
77
|
+
return selfAid ? agentDir(selfAid) : undefined;
|
|
78
|
+
}
|
|
79
|
+
function sourceLabel(source) {
|
|
80
|
+
return source === 'builtin' ? '内置' : '本机';
|
|
81
|
+
}
|
|
82
|
+
function stateLabel(s) {
|
|
83
|
+
return s.enabled ? '开' : '关';
|
|
84
|
+
}
|
|
85
|
+
function overviewLine(states) {
|
|
86
|
+
const enabled = states.filter(s => s.enabled).length;
|
|
87
|
+
return `${states.length} 个技能,${enabled} 个启用`;
|
|
88
|
+
}
|
|
89
|
+
function formatList(states, showAll) {
|
|
90
|
+
const shown = showAll ? states : states.filter(s => s.enabled);
|
|
91
|
+
if (shown.length === 0) {
|
|
92
|
+
return showAll ? '(没有任何技能)' : '(没有启用的技能;用 /skill list all 查看全部)';
|
|
93
|
+
}
|
|
94
|
+
const nameW = Math.max(20, ...shown.map(s => s.name.length + 2));
|
|
95
|
+
const lines = [`${overviewLine(states)}${showAll ? '' : '(仅列启用;/skill list all 查看全部)'}`, ''];
|
|
96
|
+
for (const s of shown) {
|
|
97
|
+
const lock = s.locked ? ' [locked]' : '';
|
|
98
|
+
lines.push(` ${s.name.padEnd(nameW)}${sourceLabel(s.source).padEnd(6)}${stateLabel(s)}${lock}`);
|
|
99
|
+
}
|
|
100
|
+
return lines.join('\n');
|
|
101
|
+
}
|
|
102
|
+
function formatInfo(s) {
|
|
103
|
+
const desc = readSkillDescription(s.skillDir);
|
|
104
|
+
const lines = [
|
|
105
|
+
`技能: ${s.name}`,
|
|
106
|
+
`来源: ${sourceLabel(s.source)}`,
|
|
107
|
+
`路径: ${s.skillDir}`,
|
|
108
|
+
`状态: ${stateLabel(s)}(由 ${s.decidedBy} 决定)`,
|
|
109
|
+
];
|
|
110
|
+
if (s.locked)
|
|
111
|
+
lines.push('锁定: 内置级 locked,任何级别都关不掉');
|
|
112
|
+
lines.push('', `描述: ${desc || '(无)'}`);
|
|
113
|
+
return lines.join('\n');
|
|
114
|
+
}
|
|
115
|
+
function formatWhere(selfAid) {
|
|
116
|
+
const roots = skillRoots(selfAid);
|
|
117
|
+
return [
|
|
118
|
+
'三级技能根目录:',
|
|
119
|
+
` 内置级: ${roots.builtin}`,
|
|
120
|
+
` 本机级: ${roots.local}`,
|
|
121
|
+
` Agent 级: ${roots.agent}`,
|
|
122
|
+
'',
|
|
123
|
+
'Agent 级只放 .switches.json(开关),不放 SKILL.md。',
|
|
124
|
+
].join('\n');
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* 读**生效的 manifest**(含 ECK override),挑出指向技能索引文件的段,
|
|
128
|
+
* 再逐段判定实际是否会被注入。
|
|
129
|
+
*
|
|
130
|
+
* 为什么不直接扫磁盘:`optional: true` 的文件缺失会被静默跳过,`when` 不满足或
|
|
131
|
+
* 总闸超限也会让段不加载。只有走 manifest 判定,`/skill index` 才能反映"实际加载"。
|
|
132
|
+
*/
|
|
133
|
+
function inspectSkillIndexes(agentRoot, selfAid, lifecycle) {
|
|
134
|
+
const manifestFile = DEFAULT_MANIFEST_FILE;
|
|
135
|
+
const sections = loadManifest(manifestFile);
|
|
136
|
+
// 只关心指向 <agentRoot>/skills/.index-*.md 的段;override 可能加段或改路径,
|
|
137
|
+
// 所以按解析后的路径判断,而不是按段 id 匹配。
|
|
138
|
+
const skillsDir = path.join(agentRoot, 'skills');
|
|
139
|
+
// lifecycle 与渲染器同源解析:显式非法值按 null(fail-closed),缺省按 active 兼容。
|
|
140
|
+
const resolvedLifecycle = resolveAgentLifecycle({ lifecycle });
|
|
141
|
+
const vars = {
|
|
142
|
+
selfAid,
|
|
143
|
+
EVOLCORE_HOME: resolveRoot(),
|
|
144
|
+
AGENT_DIR: agentRoot,
|
|
145
|
+
...(resolvedLifecycle === null ? {} : { lifecycle: resolvedLifecycle }),
|
|
146
|
+
};
|
|
147
|
+
const out = [];
|
|
148
|
+
for (const section of sections) {
|
|
149
|
+
if (section.type !== 'file' || !section.file)
|
|
150
|
+
continue;
|
|
151
|
+
// 与 kit-renderer 共用同一个解析器:变量替换 + path.normalize,不做任何空白裁剪。
|
|
152
|
+
// 变量缺失时 status !== 'ok',resolved 可能为 null —— 段会被跳过,而不是拼出一个
|
|
153
|
+
// 看起来合理的错误路径(原先的 stripBlankLines 版本会静默拼错,还吃掉路径里的空格)。
|
|
154
|
+
const r = resolvePathWithDiag(section.file, vars);
|
|
155
|
+
if (r.status !== 'ok' || !r.resolved)
|
|
156
|
+
continue;
|
|
157
|
+
const resolved = r.resolved;
|
|
158
|
+
if (!isSkillIndexPath(resolved, skillsDir))
|
|
159
|
+
continue;
|
|
160
|
+
const enabled = section.enabled !== false;
|
|
161
|
+
const whenPassed = evaluateWhen(section.when ?? 'always', vars);
|
|
162
|
+
const fileExists = fs.existsSync(resolved);
|
|
163
|
+
let reason;
|
|
164
|
+
if (!enabled)
|
|
165
|
+
reason = 'manifest 中该段 enabled: false';
|
|
166
|
+
else if (!whenPassed)
|
|
167
|
+
reason = `manifest 的 when 条件不满足(当前 lifecycle=${resolvedLifecycle ?? '值非法'})`;
|
|
168
|
+
else if (!fileExists)
|
|
169
|
+
reason = section.optional ? '文件不存在(optional,静默跳过)' : '文件不存在';
|
|
170
|
+
const diag = {
|
|
171
|
+
id: section.id,
|
|
172
|
+
rawPath: section.file,
|
|
173
|
+
resolvedPath: resolved,
|
|
174
|
+
loaded: enabled && whenPassed && fileExists,
|
|
175
|
+
...(reason ? { reason } : {}),
|
|
176
|
+
fileExists,
|
|
177
|
+
};
|
|
178
|
+
if (fileExists) {
|
|
179
|
+
try {
|
|
180
|
+
const st = fs.statSync(resolved);
|
|
181
|
+
diag.sizeBytes = st.size;
|
|
182
|
+
diag.mtimeMs = st.mtimeMs;
|
|
183
|
+
diag.skillCount = countSkillsInIndex(fs.readFileSync(resolved, 'utf-8'));
|
|
184
|
+
}
|
|
185
|
+
catch { /* 读失败保持字段缺省 */ }
|
|
186
|
+
}
|
|
187
|
+
out.push(diag);
|
|
188
|
+
}
|
|
189
|
+
return out;
|
|
190
|
+
}
|
|
191
|
+
/** 判断解析后的路径是否落在本 agent 的 skills 索引目录里,且文件名形如 .index-*.md */
|
|
192
|
+
function isSkillIndexPath(resolved, skillsDir) {
|
|
193
|
+
const dir = path.dirname(resolved);
|
|
194
|
+
if (path.normalize(dir).toLowerCase() !== path.normalize(skillsDir).toLowerCase())
|
|
195
|
+
return false;
|
|
196
|
+
return /^\.index-.*\.md$/i.test(path.basename(resolved));
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* 从索引正文里数技能条目的数量。
|
|
200
|
+
*
|
|
201
|
+
* 索引由 index-generator 生成,段头形如 `本 Agent 已启用 N 个内置技能。`;
|
|
202
|
+
* 拿不到就退化为数 `- ` 开头的条目行。
|
|
203
|
+
*/
|
|
204
|
+
function countSkillsInIndex(content) {
|
|
205
|
+
const headerMatch = content.match(/已启用\s*(\d+)\s*个/);
|
|
206
|
+
if (headerMatch)
|
|
207
|
+
return Number(headerMatch[1]);
|
|
208
|
+
const items = content.split('\n').filter(l => /^-\s+\S/.test(l.trim()));
|
|
209
|
+
return items.length > 0 ? items.length : undefined;
|
|
210
|
+
}
|
|
211
|
+
function formatIndexOverview(sections, selfAid) {
|
|
212
|
+
if (sections.length === 0) {
|
|
213
|
+
return '当前生效的 manifest 里没有引用任何技能索引文件。\n'
|
|
214
|
+
+ '(技能索引由 kits/eck_manifest.json 的 skills-builtin / skills-local 段注入)';
|
|
215
|
+
}
|
|
216
|
+
const lines = [`技能索引加载情况(agent: ${selfAid})`, ''];
|
|
217
|
+
for (const s of sections) {
|
|
218
|
+
const state = s.loaded ? '已加载' : '未加载';
|
|
219
|
+
lines.push(`${state} ${path.basename(s.resolvedPath)}`);
|
|
220
|
+
lines.push(` manifest 段: ${s.id}`);
|
|
221
|
+
if (s.loaded) {
|
|
222
|
+
lines.push(` 技能数: ${s.skillCount ?? '(解析不到)'}`);
|
|
223
|
+
lines.push(` 文件时间: ${s.mtimeMs ? new Date(s.mtimeMs).toLocaleString('zh-CN') : '(未知)'}`);
|
|
224
|
+
lines.push(` 大小: ${s.sizeBytes ?? '?'} 字节`);
|
|
225
|
+
}
|
|
226
|
+
else {
|
|
227
|
+
lines.push(` 原因: ${s.reason ?? '未知'}`);
|
|
228
|
+
}
|
|
229
|
+
lines.push(` 路径: ${s.resolvedPath}`);
|
|
230
|
+
lines.push('');
|
|
231
|
+
}
|
|
232
|
+
const loaded = sections.filter(s => s.loaded);
|
|
233
|
+
const total = loaded.reduce((n, s) => n + (s.skillCount ?? 0), 0);
|
|
234
|
+
lines.push(`合计:${loaded.length}/${sections.length} 个索引文件被加载,共 ${total} 个技能`);
|
|
235
|
+
lines.push('查看正文: /skill index builtin 或 /skill index local');
|
|
236
|
+
return lines.join('\n');
|
|
237
|
+
}
|
|
238
|
+
/** 读某个索引文件正文;kind 由段 id 或路径推断。 */
|
|
239
|
+
function readIndexFile(selfAid, kind, lifecycle) {
|
|
240
|
+
const root = agentRootFor(selfAid);
|
|
241
|
+
if (!root)
|
|
242
|
+
return '❌ 无法确定当前 agent 目录';
|
|
243
|
+
const file = path.join(root, 'skills', kind === 'builtin' ? '.index-builtin.md' : '.index-local.md');
|
|
244
|
+
const sections = inspectSkillIndexes(root, selfAid, lifecycle);
|
|
245
|
+
const section = sections.find(s => path.basename(s.resolvedPath).toLowerCase() === path.basename(file).toLowerCase());
|
|
246
|
+
if (section && !section.loaded) {
|
|
247
|
+
return `⚠️ ${path.basename(file)} 当前未加载(${section.reason ?? '未知原因'})\n`
|
|
248
|
+
+ `路径: ${file}`;
|
|
249
|
+
}
|
|
250
|
+
if (!fs.existsSync(file)) {
|
|
251
|
+
return `(${path.basename(file)} 不存在——该来源当前没有启用任何技能,索引生成时会被删除)`;
|
|
252
|
+
}
|
|
253
|
+
const content = fs.readFileSync(file, 'utf-8');
|
|
254
|
+
const count = countSkillsInIndex(content);
|
|
255
|
+
const header = `${path.basename(file)}(${count ?? '?'} 个技能,${new Date(fs.statSync(file).mtimeMs).toLocaleString('zh-CN')})`;
|
|
256
|
+
return `${header}\n\n${content}`;
|
|
257
|
+
}
|
|
258
|
+
export async function handleSkillSlash(ctx) {
|
|
259
|
+
const args = ctx.content.trim().split(/\s+/).slice(1);
|
|
260
|
+
const sub = args[0];
|
|
261
|
+
if (!sub || sub === 'help') {
|
|
262
|
+
if (sub === 'help')
|
|
263
|
+
return result(USAGE);
|
|
264
|
+
if (!canReadSkillSlash(ctx))
|
|
265
|
+
return error('群聊中技能列表仅限管理员查看');
|
|
266
|
+
const root = agentRootFor(ctx.selfAid);
|
|
267
|
+
if (!root)
|
|
268
|
+
return error('无法确定当前 agent 目录');
|
|
269
|
+
return result(`${overviewLine(resolveAllSkillStates(root))}\n\n输入 /skill help 查看全部子命令`);
|
|
270
|
+
}
|
|
271
|
+
if (!canReadSkillSlash(ctx))
|
|
272
|
+
return error('群聊中技能查看仅限管理员');
|
|
273
|
+
const root = agentRootFor(ctx.selfAid);
|
|
274
|
+
if (!root)
|
|
275
|
+
return error('无法确定当前 agent 目录');
|
|
276
|
+
try {
|
|
277
|
+
if (sub === 'list') {
|
|
278
|
+
const showAll = args[1] === 'all';
|
|
279
|
+
return result(formatList(resolveAllSkillStates(root), showAll));
|
|
280
|
+
}
|
|
281
|
+
if (sub === 'show' || sub === 'info') {
|
|
282
|
+
const name = args[1];
|
|
283
|
+
if (!name)
|
|
284
|
+
return error('用法: /skill show <name>');
|
|
285
|
+
const found = resolveAllSkillStates(root).find(s => s.name === name);
|
|
286
|
+
if (!found)
|
|
287
|
+
return error(`技能不存在: ${name}\n用 /skill list all 查看全部技能`);
|
|
288
|
+
return result(formatInfo(found));
|
|
289
|
+
}
|
|
290
|
+
if (sub === 'index') {
|
|
291
|
+
const kind = args[1];
|
|
292
|
+
if (kind === 'builtin' || kind === 'local') {
|
|
293
|
+
return result(readIndexFile(ctx.selfAid, kind, ctx.lifecycle));
|
|
294
|
+
}
|
|
295
|
+
if (kind !== undefined) {
|
|
296
|
+
return error(`未知索引: ${kind}\n用法: /skill index [builtin|local]`);
|
|
297
|
+
}
|
|
298
|
+
return result(formatIndexOverview(inspectSkillIndexes(root, ctx.selfAid, ctx.lifecycle), ctx.selfAid));
|
|
299
|
+
}
|
|
300
|
+
if (sub === 'where')
|
|
301
|
+
return result(formatWhere(ctx.selfAid));
|
|
302
|
+
if (sub === 'refresh') {
|
|
303
|
+
if (!canRefreshSkillSlash(ctx)) {
|
|
304
|
+
return error(ctx.chatType === 'group'
|
|
305
|
+
? '群聊中不支持重建技能索引,请私聊或由管理员在终端执行'
|
|
306
|
+
: '无权限:重建技能索引仅限管理员');
|
|
307
|
+
}
|
|
308
|
+
if (!ctx.selfAid)
|
|
309
|
+
return error('无法确定当前 agent AID');
|
|
310
|
+
if (!ctx.refresher)
|
|
311
|
+
return error('daemon 未注入索引刷新入口');
|
|
312
|
+
const r = await ctx.refresher(ctx.selfAid);
|
|
313
|
+
if (!r.ok)
|
|
314
|
+
return error(`重建技能索引失败:${r.error ?? '未知错误'}`);
|
|
315
|
+
return result('✅ 已重建当前 Agent 的技能索引');
|
|
316
|
+
}
|
|
317
|
+
return error(`未知子命令: ${sub}\n\n${USAGE}`);
|
|
318
|
+
}
|
|
319
|
+
catch (e) {
|
|
320
|
+
return error(e instanceof Error ? e.message : String(e));
|
|
321
|
+
}
|
|
322
|
+
}
|