evolcore 0.0.1 → 0.0.3
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 +64 -0
- package/LICENSE +21 -0
- package/MIGRATION-0.5.0.md +378 -0
- package/README.md +318 -14
- package/ROLE_ACCESS_CONTROL.md +174 -0
- package/assets/.env.template +4 -0
- package/bin/ec-safe-output.js +161 -0
- package/bin/ec.js +29 -0
- package/dist/agents/baseagent.js +163 -0
- package/dist/agents/claude-runner.js +2565 -0
- package/dist/agents/codex-app-server-client.js +448 -0
- package/dist/agents/codex-runner.js +2682 -0
- package/dist/agents/gemini-runner.js +666 -0
- package/dist/agents/runner-types.js +75 -0
- package/dist/aun/aid/agentmd.js +216 -0
- package/dist/aun/aid/client.js +132 -0
- package/dist/aun/aid/control-aid.js +91 -0
- package/dist/aun/aid/identity.js +518 -0
- package/dist/aun/aid/index.js +4 -0
- package/dist/aun/aid/store.js +74 -0
- package/dist/aun/aid/types.js +1 -0
- package/dist/aun/aid/validation.js +21 -0
- package/dist/aun/group-identity.js +10 -0
- package/dist/aun/msg/group-index.js +6 -0
- package/dist/aun/msg/group.js +1231 -0
- package/dist/aun/msg/history.js +123 -0
- package/dist/aun/msg/index.js +5 -0
- package/dist/aun/msg/p2p.js +393 -0
- package/dist/aun/msg/payload-type.js +27 -0
- package/dist/aun/msg/upload.js +137 -0
- package/dist/aun/outbox.js +168 -0
- package/dist/aun/rpc/caller.js +42 -0
- package/dist/aun/rpc/connection.js +25 -0
- package/dist/aun/rpc/index.js +2 -0
- package/dist/aun/service-proxy.js +225 -0
- package/dist/aun/storage/download.js +29 -0
- package/dist/aun/storage/index.js +3 -0
- package/dist/aun/storage/manage.js +10 -0
- package/dist/aun/storage/upload.js +68 -0
- package/dist/channels/aun.js +4164 -0
- package/dist/channels/contact-bind-code.js +134 -0
- package/dist/channels/daemon.js +422 -0
- package/dist/channels/dingtalk.js +1479 -0
- package/dist/channels/feishu.js +1865 -0
- package/dist/channels/qqbot.js +409 -0
- package/dist/channels/wechat.js +817 -0
- package/dist/channels/wecom-card.js +101 -0
- package/dist/channels/wecom-onboarding.js +82 -0
- package/dist/channels/wecom-state.js +191 -0
- package/dist/channels/wecom.js +1157 -0
- package/dist/cli/agent-command.js +642 -0
- package/dist/cli/agent.js +1059 -0
- package/dist/cli/aun-commands.js +2003 -0
- package/dist/cli/bench.js +1228 -0
- package/dist/cli/cli-argv.js +66 -0
- package/dist/cli/code-stats.js +329 -0
- package/dist/cli/command-log.js +82 -0
- package/dist/cli/config-selector.js +69 -0
- package/dist/cli/config.js +261 -0
- package/dist/cli/contact.js +71 -0
- package/dist/cli/ctl-command.js +62 -0
- package/dist/cli/daemon-commands.js +2750 -0
- package/dist/cli/fs-command.js +1447 -0
- package/dist/cli/handoff-command.js +302 -0
- package/dist/cli/help.js +35 -0
- package/dist/cli/index.js +374 -0
- package/dist/cli/init-channel.js +1372 -0
- package/dist/cli/init.js +590 -0
- package/dist/cli/link-rules.js +240 -0
- package/dist/cli/model.js +591 -0
- package/dist/cli/net-check.js +723 -0
- package/dist/cli/queue-command.js +150 -0
- package/dist/cli/raw-key-input.js +25 -0
- package/dist/cli/response.js +344 -0
- package/dist/cli/restart-monitor.js +480 -0
- package/dist/cli/stats.js +609 -0
- package/dist/cli/task-context.js +80 -0
- package/dist/cli/trigger-command.js +545 -0
- package/dist/cli/version.js +93 -0
- package/dist/cli/watch-logs.js +33 -0
- package/dist/cli/watch-msg.js +673 -0
- package/dist/config/boot-log.js +266 -0
- package/dist/config/builtin-role-templates.js +42 -0
- package/dist/config/builtin-roles.js +91 -0
- package/dist/config/config-batch-get.js +11 -0
- package/dist/config/config-field-policy.js +261 -0
- package/dist/config/config-manager.js +1120 -0
- package/dist/config/config-operation-service.js +384 -0
- package/dist/config/contact-alias.js +68 -0
- package/dist/config/contact-book-store.js +454 -0
- package/dist/config/contact-book-v2-startup.js +35 -0
- package/dist/config/contact-book.js +224 -0
- package/dist/config/contact-operation-service.js +110 -0
- package/dist/config/gateway-config.js +858 -0
- package/dist/config/lifecycle.js +17 -0
- package/dist/config/mention-mode.js +27 -0
- package/dist/config/merge.js +161 -0
- package/dist/config/owner-policy.js +4 -0
- package/dist/config/peer-role-resolver.js +218 -0
- package/dist/config/resolved-config-op.js +483 -0
- package/dist/config/role-config-v4-startup.js +32 -0
- package/dist/config/role-config-v5-startup.js +27 -0
- package/dist/config/role-ranks.js +18 -0
- package/dist/config/role-schema.js +105 -0
- package/dist/config/role-service.js +156 -0
- package/dist/config/role-store.js +215 -0
- package/dist/config/roles.js +64 -0
- package/dist/config/schema-registry.js +154 -0
- package/dist/config/snapshot.js +598 -0
- package/dist/config-store.js +501 -0
- package/dist/core/auth/agent-delegation.js +111 -0
- package/dist/core/auth/auth-gateway.js +166 -0
- package/dist/core/auth/authenticated-actor.js +6 -0
- package/dist/core/auth/authorization-audit.js +119 -0
- package/dist/core/auth/operation-authorizer.js +720 -0
- package/dist/core/auth/operation-catalog.js +731 -0
- package/dist/core/baseagent-loader.js +54 -0
- package/dist/core/bootstrap-service.js +175 -0
- package/dist/core/capability/capability-manager.js +316 -0
- package/dist/core/capability/providers/claude-capability-provider.js +176 -0
- package/dist/core/capability/providers/codex-capability-provider.js +148 -0
- package/dist/core/capability/providers/gemini-capability-provider.js +10 -0
- package/dist/core/capability/types.js +27 -0
- package/dist/core/causation/audit.js +103 -0
- package/dist/core/causation/aun-association.js +111 -0
- package/dist/core/causation/context.js +93 -0
- package/dist/core/causation/index.js +4 -0
- package/dist/core/causation/types.js +2 -0
- package/dist/core/channel-loader.js +277 -0
- package/dist/core/command/agent-control.js +616 -0
- package/dist/core/command/cli-intent-parser.js +225 -0
- package/dist/core/command/command-handler.js +1733 -0
- package/dist/core/command/connect-menu.js +374 -0
- package/dist/core/command/menu-handler.js +3452 -0
- package/dist/core/command/menu-protocol.js +247 -0
- package/dist/core/command/role-menu.js +1623 -0
- package/dist/core/command/slash-gate.js +148 -0
- package/dist/core/command/slash-handler.js +3066 -0
- package/dist/core/daemon-file-cache.js +222 -0
- package/dist/core/event-bus.js +32 -0
- package/dist/core/event-catalog.js +810 -0
- package/dist/core/evolagent-registry.js +545 -0
- package/dist/core/evolagent.js +342 -0
- package/dist/core/handoff/dispatcher.js +229 -0
- package/dist/core/handoff/mutex.js +46 -0
- package/dist/core/handoff/runtime.js +324 -0
- package/dist/core/handoff/store.js +537 -0
- package/dist/core/handoff/types.js +12 -0
- package/dist/core/inference/text-inference.js +173 -0
- package/dist/core/interaction-registration.js +10 -0
- package/dist/core/interaction-router.js +278 -0
- package/dist/core/message/create-status.js +67 -0
- package/dist/core/message/im-renderer.js +659 -0
- package/dist/core/message/items-formatter.js +76 -0
- package/dist/core/message/logical-queue-bridge.js +123 -0
- package/dist/core/message/message-bridge.js +874 -0
- package/dist/core/message/message-cache.js +56 -0
- package/dist/core/message/message-log.js +339 -0
- package/dist/core/message/message-processor.js +4 -0
- package/dist/core/message/message-queue.js +1446 -0
- package/dist/core/message/message-utils.js +76 -0
- package/dist/core/message/peer-mode.js +105 -0
- package/dist/core/message/pending-hints.js +232 -0
- package/dist/core/message/response-depth.js +33 -0
- package/dist/core/message/response-engine.js +4020 -0
- package/dist/core/message/response-snapshot.js +83 -0
- package/dist/core/message/send-receipt.js +24 -0
- package/dist/core/message/stream-debouncer.js +139 -0
- package/dist/core/message/stream-idle-monitor.js +124 -0
- package/dist/core/model/config-scope.js +162 -0
- package/dist/core/model/field-scope.js +78 -0
- package/dist/core/model/model-catalog.js +227 -0
- package/dist/core/model/model-diagnostics.js +182 -0
- package/dist/core/model/model-permission.js +90 -0
- package/dist/core/permission/approval-gateway.js +1017 -0
- package/dist/core/permission/ec-command-parser.js +347 -0
- package/dist/core/permission/execution-sandbox.js +16 -0
- package/dist/core/permission/index.js +6 -0
- package/dist/core/permission/mode.js +24 -0
- package/dist/core/permission/sandbox-runtime.js +265 -0
- package/dist/core/permission/tool-policy.js +1019 -0
- package/dist/core/permission/unix-socket-policy.js +99 -0
- package/dist/core/protected-paths.js +332 -0
- package/dist/core/relation/peer-identity.js +222 -0
- package/dist/core/relation/peer-key.js +1 -0
- package/dist/core/role/runtime-policy.js +141 -0
- package/dist/core/session/adapters/claude-session-file-adapter.js +218 -0
- package/dist/core/session/adapters/codex-session-file-adapter.js +333 -0
- package/dist/core/session/adapters/gemini-session-file-adapter.js +181 -0
- package/dist/core/session/session-file-adapter.js +7 -0
- package/dist/core/session/session-file-health.js +45 -0
- package/dist/core/session/session-fs-store.js +273 -0
- package/dist/core/session/session-key.js +24 -0
- package/dist/core/session/session-manager.js +1643 -0
- package/dist/core/session/session-mapper.js +100 -0
- package/dist/core/session/session-renew.js +314 -0
- package/dist/core/session/session-title.js +128 -0
- package/dist/core/session/session-turn-coordinator.js +205 -0
- package/dist/core/session/session-turns.js +67 -0
- package/dist/core/system-channels.js +29 -0
- package/dist/eck/baseagent-caps.js +18 -0
- package/dist/eck/detect.js +47 -0
- package/dist/eck/group-rules-sync.js +345 -0
- package/dist/eck/init.js +77 -0
- package/dist/eck/kit-renderer.js +359 -0
- package/dist/eck/manifest-engine.js +446 -0
- package/dist/eck/message-renderer.js +199 -0
- package/dist/eck/rules-loader.js +28 -0
- package/dist/index.js +2926 -4
- package/dist/ipc.js +777 -0
- package/dist/paths.js +262 -0
- package/dist/product.js +18 -0
- package/dist/response-system/context-builder.js +71 -0
- package/dist/response-system/coordinator.js +117 -0
- package/dist/response-system/decision-executor.js +86 -0
- package/dist/response-system/engines/v1/index.js +21 -0
- package/dist/response-system/engines/v1/interactive-flow.js +27 -0
- package/dist/response-system/engines/v1/proactive-flow.js +137 -0
- package/dist/response-system/engines/v1/types.js +1 -0
- package/dist/response-system/extensions.js +41 -0
- package/dist/response-system/index.js +6 -0
- package/dist/response-system/modes/index.js +7 -0
- package/dist/response-system/modes/single-session/index.js +72 -0
- package/dist/response-system/queues/fifo-queue.js +44 -0
- package/dist/response-system/queues/index.js +6 -0
- package/dist/response-system/queues/lifo-queue.js +42 -0
- package/dist/response-system/queues/priority-queue.js +63 -0
- package/dist/response-system/registry.js +97 -0
- package/dist/response-system/resolver.js +37 -0
- package/dist/response-system/selector.js +23 -0
- package/dist/response-system/types.js +7 -0
- package/dist/stats/billing.js +163 -0
- package/dist/stats/budget.js +93 -0
- package/dist/stats/db.js +403 -0
- package/dist/stats/eck-vars.js +89 -0
- package/dist/stats/index.js +11 -0
- package/dist/stats/normalizer.js +80 -0
- package/dist/stats/price-resolver.js +138 -0
- package/dist/stats/query.js +763 -0
- package/dist/stats/role-budget.js +168 -0
- package/dist/stats/writer.js +151 -0
- package/dist/trigger/anomaly-store.js +258 -0
- package/dist/trigger/audit.js +152 -0
- package/dist/trigger/event-source.js +119 -0
- package/dist/trigger/feedback.js +685 -0
- package/dist/trigger/history.js +290 -0
- package/dist/trigger/manager.js +294 -0
- package/dist/trigger/parser.js +595 -0
- package/dist/trigger/patch.js +155 -0
- package/dist/trigger/scheduler.js +1602 -0
- package/dist/trigger/script-executor.js +155 -0
- package/dist/trigger/state.js +145 -0
- package/dist/trigger/types.js +1 -0
- package/dist/trigger/validation.js +634 -0
- package/dist/types.js +12 -0
- package/dist/utils/aid-bind.js +313 -0
- package/dist/utils/atomic-write.js +95 -0
- package/dist/utils/avatar-upload.js +123 -0
- package/dist/utils/cross-platform.js +297 -0
- package/dist/utils/ecweb-utils.js +73 -0
- package/dist/utils/error-dict.json +153 -0
- package/dist/utils/error-utils.js +349 -0
- package/dist/utils/instance-registry.js +444 -0
- package/dist/utils/locale.js +21 -0
- package/dist/utils/log-writer.js +270 -0
- package/dist/utils/logger.js +89 -0
- package/dist/utils/markdown-to-plain-text.js +20 -0
- package/dist/utils/media-cache.js +274 -0
- package/dist/utils/model-prices.jsonl +20 -0
- package/dist/utils/npm-ops.js +210 -0
- package/dist/utils/process-introspect.js +133 -0
- package/dist/utils/process-tree-stats.js +271 -0
- package/dist/utils/project-path.js +74 -0
- package/dist/utils/restart-safety.js +31 -0
- package/dist/utils/stats.js +410 -0
- package/dist/utils/system-memory.js +62 -0
- package/dist/utils/tool-summary.js +284 -0
- package/dist/utils/welcome.js +268 -0
- package/kits/docs/GUIDE.md +20 -0
- package/kits/docs/INDEX.md +66 -0
- package/kits/docs/aun/CHEATSHEET.md +19 -0
- package/kits/docs/aun/SYNC_PROTOCOL.md +15 -0
- package/kits/docs/channels/aun.md +65 -0
- package/kits/docs/channels/feishu.md +56 -0
- package/kits/docs/context-assembly.md +366 -0
- package/kits/docs/eck_templates/GUIDE.template.md +22 -0
- package/kits/docs/eck_templates/INDEX.template.md +28 -0
- package/kits/docs/eck_templates/path-registry.template.md +33 -0
- package/kits/docs/eck_templates/runtime.template.md +19 -0
- package/kits/docs/evolcore/INDEX.md +68 -0
- package/kits/docs/evolcore/agent.md +77 -0
- package/kits/docs/evolcore/aid.md +52 -0
- package/kits/docs/evolcore/config.md +149 -0
- package/kits/docs/evolcore/contact.md +57 -0
- package/kits/docs/evolcore/ctl.md +46 -0
- package/kits/docs/evolcore/event.md +216 -0
- package/kits/docs/evolcore/fs-architecture.md +1215 -0
- package/kits/docs/evolcore/fs.md +110 -0
- package/kits/docs/evolcore/group-fs.md +17 -0
- package/kits/docs/evolcore/group-rules.md +226 -0
- package/kits/docs/evolcore/group.md +150 -0
- package/kits/docs/evolcore/model.md +50 -0
- package/kits/docs/evolcore/msg.md +136 -0
- package/kits/docs/evolcore/response.md +75 -0
- package/kits/docs/evolcore/rpc.md +37 -0
- package/kits/docs/evolcore/self-summary.md +29 -0
- package/kits/docs/evolcore/stats.md +83 -0
- package/kits/docs/evolcore/storage.md +50 -0
- package/kits/docs/evolcore/trigger.md +539 -0
- package/kits/docs/identity/AID_PROFILE_SPEC.md +26 -0
- package/kits/docs/identity/PATH_OPS.md +16 -0
- package/kits/docs/identity/ROLE_DETAIL.md +23 -0
- package/kits/docs/identity/identity-tools.md +26 -0
- package/kits/docs/path-registry.md +43 -0
- package/kits/docs/prompt-loading-architecture.md +266 -0
- package/kits/docs/venues/aun-group.md +45 -0
- package/kits/docs/venues/aun-private.md +10 -0
- package/kits/docs/venues/client-desktop.md +10 -0
- package/kits/docs/venues/client-mobile.md +10 -0
- package/kits/docs/venues/feishu-group.md +13 -0
- package/kits/docs/venues/feishu-private.md +9 -0
- package/kits/docs/venues/group.md +25 -0
- package/kits/docs/venues/private.md +10 -0
- package/kits/eck_manifest.auxiliary.json +43 -0
- package/kits/eck_manifest.json +203 -0
- package/kits/eck_message_manifest.json +63 -0
- package/kits/migrations/README-role-config-v4.md +32 -0
- package/kits/migrations/migrate-contact-book-v2.mjs +747 -0
- package/kits/migrations/migrate-role-config-v4.mjs +623 -0
- package/kits/migrations/migrate-role-config-v5.mjs +346 -0
- package/kits/migrations/rename-config-file.mjs +99 -0
- package/kits/rules/01-overview.md +142 -0
- package/kits/rules/02-navigation.md +76 -0
- package/kits/rules/03-identity.md +34 -0
- package/kits/rules/04-relation.md +59 -0
- package/kits/rules/05-venue.md +44 -0
- package/kits/rules/06-channel.md +59 -0
- package/kits/schemas/_meta.json +32 -0
- package/kits/schemas/agent-config.schema.1.json +177 -0
- package/kits/schemas/agent-config.schema.2.json +239 -0
- package/kits/schemas/agent-config.schema.3.json +119 -0
- package/kits/schemas/agent-config.schema.4.json +208 -0
- package/kits/schemas/agent-config.schema.5.json +326 -0
- package/kits/schemas/agent-config.schema.6.json +322 -0
- package/kits/schemas/contact-book.schema.1.json +36 -0
- package/kits/schemas/contact-book.schema.2.json +43 -0
- package/kits/schemas/daemon.schema.1.json +90 -0
- package/kits/schemas/defaults.schema.1.json +81 -0
- package/kits/schemas/menu-exec-schema-commands.md +208 -0
- package/kits/schemas/migrations/README.md +28 -0
- package/kits/schemas/relation-config.schema.1.json +158 -0
- package/kits/schemas/relation-config.schema.2.json +73 -0
- package/kits/schemas/relation-config.schema.3.json +50 -0
- package/kits/schemas/relation-config.schema.4.json +47 -0
- package/kits/schemas/relation-config.schema.5.json +47 -0
- package/kits/schemas/role-config.schema.1.json +201 -0
- package/kits/schemas/role-registry.schema.1.json +35 -0
- package/kits/schemas/single-session.schema.1.json +31 -0
- package/kits/templates/bootstrap-welcome.md +15 -0
- package/kits/templates/message-fragments/handoff-request-to-target.md +13 -0
- package/kits/templates/message-fragments/handoff-response-to-origin.md +10 -0
- package/kits/templates/message-fragments/inject-default.md +2 -0
- package/kits/templates/message-fragments/item.md +2 -0
- package/kits/templates/roles/admin.json +9 -0
- package/kits/templates/roles/member.json +41 -0
- package/kits/templates/roles/owner.json +9 -0
- package/kits/templates/roles/visitor.json +40 -0
- package/kits/templates/system-fragments/baseagent.md +14 -0
- package/kits/templates/system-fragments/bootstrap.md +16 -0
- package/kits/templates/system-fragments/channel.md +48 -0
- package/kits/templates/system-fragments/commands.md +28 -0
- package/kits/templates/system-fragments/identity.md +11 -0
- package/kits/templates/system-fragments/relation.md +19 -0
- package/kits/templates/system-fragments/session.md +53 -0
- package/kits/templates/system-fragments/venue.md +31 -0
- package/package.json +50 -15
- package/dist/index.d.ts +0 -7
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
|
@@ -0,0 +1,446 @@
|
|
|
1
|
+
// 声明式 manifest 渲染引擎(共享)。
|
|
2
|
+
// 系统提示词渲染(kit-renderer)与消息渲染(message-renderer)共用同一套
|
|
3
|
+
// when 求值、模板渲染、路径解析、manifest 加载/缓存原语,避免两套实现漂移。
|
|
4
|
+
import fs from 'fs';
|
|
5
|
+
import path from 'path';
|
|
6
|
+
import { kitsDir, resolveRoot, getPackageRoot } from '../paths.js';
|
|
7
|
+
import { logger } from '../utils/logger.js';
|
|
8
|
+
import { fileCache } from '../core/daemon-file-cache.js';
|
|
9
|
+
// ── 限额默认值 ──
|
|
10
|
+
/** 单目录段默认最多文件数 */
|
|
11
|
+
export const DIR_MAX_FILES = 20;
|
|
12
|
+
/** 单目录段默认最多总字节(40KB) */
|
|
13
|
+
export const DIR_MAX_BYTES = 40 * 1024;
|
|
14
|
+
/** 整个清单默认最多文件数 */
|
|
15
|
+
export const TOTAL_MAX_FILES = 50;
|
|
16
|
+
/** 整个清单默认最多总字节(100KB) */
|
|
17
|
+
export const TOTAL_MAX_BYTES = 100 * 1024;
|
|
18
|
+
// ── Manifest loading / cache ──
|
|
19
|
+
// manifest 定义随包发布、运行期靠 reload/重启刷新 → on-reload(group 'kits')。
|
|
20
|
+
// base + eck override 合成结果以 base 文件路径为键缓存;loader 内读两个文件。
|
|
21
|
+
/** 清空所有 manifest 缓存(manifest 结构变更后调用,由 invalidateKitCache 串联)。 */
|
|
22
|
+
export function invalidateManifestCache() {
|
|
23
|
+
fileCache.invalidateGroup('kits');
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* 加载并合并 manifest。基础文件在 $KITS/<filename>,
|
|
27
|
+
* 覆盖文件在 $EVOLCORE_HOME/eck/<filename>(可选)。结果按 order 升序缓存。
|
|
28
|
+
*/
|
|
29
|
+
export function loadManifest(filename) {
|
|
30
|
+
const kitsPath = path.join(kitsDir(), filename);
|
|
31
|
+
return fileCache.get(kitsPath, () => {
|
|
32
|
+
const sections = loadAndMergeManifest(filename);
|
|
33
|
+
logger.info(`[ManifestEngine] Loaded ${filename}: ${sections.length} sections`);
|
|
34
|
+
return sections;
|
|
35
|
+
}, { policy: 'on-reload', group: 'kits' });
|
|
36
|
+
}
|
|
37
|
+
function loadAndMergeManifest(filename) {
|
|
38
|
+
const kitsPath = path.join(kitsDir(), filename);
|
|
39
|
+
const eckPath = path.join(resolveRoot(), 'eck', filename);
|
|
40
|
+
let base;
|
|
41
|
+
try {
|
|
42
|
+
base = JSON.parse(fs.readFileSync(kitsPath, 'utf-8'));
|
|
43
|
+
}
|
|
44
|
+
catch (err) {
|
|
45
|
+
logger.error(`[ManifestEngine] Failed to load kits/${filename}: ${err}`);
|
|
46
|
+
return [];
|
|
47
|
+
}
|
|
48
|
+
if (!fs.existsSync(eckPath))
|
|
49
|
+
return sortSections(base.sections);
|
|
50
|
+
try {
|
|
51
|
+
const override = JSON.parse(fs.readFileSync(eckPath, 'utf-8'));
|
|
52
|
+
if (override.mode === 'replace')
|
|
53
|
+
return sortSections(override.sections);
|
|
54
|
+
const merged = new Map();
|
|
55
|
+
for (const s of base.sections)
|
|
56
|
+
merged.set(s.id, { ...s });
|
|
57
|
+
for (const s of override.sections) {
|
|
58
|
+
const existing = merged.get(s.id);
|
|
59
|
+
merged.set(s.id, existing ? { ...existing, ...s } : s);
|
|
60
|
+
}
|
|
61
|
+
return sortSections([...merged.values()]);
|
|
62
|
+
}
|
|
63
|
+
catch (err) {
|
|
64
|
+
logger.warn(`[ManifestEngine] Failed to load eck override for ${filename}, using kits only: ${err}`);
|
|
65
|
+
return sortSections(base.sections);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
function sortSections(sections) {
|
|
69
|
+
return sections.slice().sort((a, b) => a.order - b.order);
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* 读取清单顶层总闸限额(base + eck override 合并,override 优先)。
|
|
73
|
+
* 与 loadManifest 分开,避免改动现有 `ManifestSection[]` 返回契约。
|
|
74
|
+
*/
|
|
75
|
+
export function loadManifestMeta(filename) {
|
|
76
|
+
const kitsPath = path.join(kitsDir(), filename);
|
|
77
|
+
return fileCache.get(`${kitsPath}#meta`, () => {
|
|
78
|
+
const kits = readRawManifest(kitsPath);
|
|
79
|
+
const eck = readRawManifest(path.join(resolveRoot(), 'eck', filename));
|
|
80
|
+
return {
|
|
81
|
+
totalMaxFiles: eck?.totalMaxFiles ?? kits?.totalMaxFiles ?? TOTAL_MAX_FILES,
|
|
82
|
+
totalMaxBytes: eck?.totalMaxBytes ?? kits?.totalMaxBytes ?? TOTAL_MAX_BYTES,
|
|
83
|
+
};
|
|
84
|
+
}, { policy: 'on-reload', group: 'kits' });
|
|
85
|
+
}
|
|
86
|
+
function readRawManifest(p) {
|
|
87
|
+
try {
|
|
88
|
+
if (!fs.existsSync(p))
|
|
89
|
+
return null;
|
|
90
|
+
return JSON.parse(fs.readFileSync(p, 'utf-8'));
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* 从 manifest 抽出每个 modeType 标了 isDefault 的 modeName。
|
|
98
|
+
* 供 message-renderer 在 agent config.render 未配某类型时回退。
|
|
99
|
+
* 同一 modeType 有多个 isDefault 时取 order 最小(已排序)的第一个。
|
|
100
|
+
*/
|
|
101
|
+
export function defaultModeNames(sections) {
|
|
102
|
+
const out = {};
|
|
103
|
+
for (const s of sections) {
|
|
104
|
+
if (s.modeType && s.isDefault && s.modeName && out[s.modeType] === undefined) {
|
|
105
|
+
out[s.modeType] = s.modeName;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return out;
|
|
109
|
+
}
|
|
110
|
+
// ── When condition evaluation ──
|
|
111
|
+
export function evaluateWhen(when, vars) {
|
|
112
|
+
if (when === 'always')
|
|
113
|
+
return true;
|
|
114
|
+
// 复合条件优先:and / or 递归求值。
|
|
115
|
+
if (when.and)
|
|
116
|
+
return when.and.every(c => evaluateWhen(c, vars));
|
|
117
|
+
if (when.or)
|
|
118
|
+
return when.or.some(c => evaluateWhen(c, vars));
|
|
119
|
+
if (when.var !== undefined) {
|
|
120
|
+
const val = vars[when.var];
|
|
121
|
+
if (when.eq !== undefined) {
|
|
122
|
+
if (when.eq === null)
|
|
123
|
+
return val === null || val === undefined;
|
|
124
|
+
return val === when.eq;
|
|
125
|
+
}
|
|
126
|
+
if (when.neq !== undefined) {
|
|
127
|
+
if (when.neq === null)
|
|
128
|
+
return val !== null && val !== undefined;
|
|
129
|
+
return val !== when.neq;
|
|
130
|
+
}
|
|
131
|
+
if (when.in !== undefined)
|
|
132
|
+
return when.in.includes(val);
|
|
133
|
+
if (when.nin !== undefined)
|
|
134
|
+
return !when.nin.includes(val);
|
|
135
|
+
}
|
|
136
|
+
if (when.any)
|
|
137
|
+
return when.any.some(k => isTruthy(vars[k]));
|
|
138
|
+
if (when.all)
|
|
139
|
+
return when.all.every(k => isTruthy(vars[k]));
|
|
140
|
+
return true;
|
|
141
|
+
}
|
|
142
|
+
export function isTruthy(val) {
|
|
143
|
+
if (Array.isArray(val))
|
|
144
|
+
return val.length > 0; // 空数组视为假,使 {{?arr}} / {{#each}} 落空
|
|
145
|
+
return val !== undefined && val !== null && val !== false && val !== '' && val !== 0;
|
|
146
|
+
}
|
|
147
|
+
// ── Template rendering ──
|
|
148
|
+
/**
|
|
149
|
+
* 展开 {{#each KEY}}BODY{{/each}} 循环块(在条件/变量替换之前跑)。
|
|
150
|
+
* - vars[KEY] 为非空数组才展开;每个元素构造子作用域:
|
|
151
|
+
* 对象元素 → { ...vars, ...el }(字段可用 {{field}} 访问)
|
|
152
|
+
* 标量元素 → { ...vars, '.': el }({{.}} 访问当前元素)
|
|
153
|
+
* 另注入 {{@index}}(0 基序号)。
|
|
154
|
+
* - body 经完整 renderTemplate 递归渲染,天然支持嵌套 each / 条件。
|
|
155
|
+
* - 非数组或空数组 → 整块渲染为空串。
|
|
156
|
+
* 用深度扫描定位**最外层** each 块(正则无法平衡嵌套),从外向内展开。
|
|
157
|
+
*/
|
|
158
|
+
function resolveEach(template, vars, stripBlankLines) {
|
|
159
|
+
const OPEN = /\{\{#each\s+([A-Za-z_]\w*)\}\}/g;
|
|
160
|
+
let result = '';
|
|
161
|
+
let cursor = 0;
|
|
162
|
+
OPEN.lastIndex = 0;
|
|
163
|
+
let m;
|
|
164
|
+
while ((m = OPEN.exec(template)) !== null) {
|
|
165
|
+
const blockStart = m.index;
|
|
166
|
+
const key = m[1];
|
|
167
|
+
const bodyStart = OPEN.lastIndex;
|
|
168
|
+
// 从 bodyStart 起按深度找配对的 {{/each}}
|
|
169
|
+
const TOKEN = /\{\{#each\s+[A-Za-z_]\w*\}\}|\{\{\/each\}\}/g;
|
|
170
|
+
TOKEN.lastIndex = bodyStart;
|
|
171
|
+
let depth = 1;
|
|
172
|
+
let bodyEnd = -1;
|
|
173
|
+
let blockEnd = -1;
|
|
174
|
+
let t;
|
|
175
|
+
while ((t = TOKEN.exec(template)) !== null) {
|
|
176
|
+
if (t[0].startsWith('{{#each'))
|
|
177
|
+
depth++;
|
|
178
|
+
else {
|
|
179
|
+
depth--;
|
|
180
|
+
if (depth === 0) {
|
|
181
|
+
bodyEnd = t.index;
|
|
182
|
+
blockEnd = TOKEN.lastIndex;
|
|
183
|
+
break;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
if (bodyEnd === -1)
|
|
188
|
+
break; // 无配对,剩余原样输出
|
|
189
|
+
// 输出块前的原文
|
|
190
|
+
result += template.slice(cursor, blockStart);
|
|
191
|
+
const body = template.slice(bodyStart, bodyEnd);
|
|
192
|
+
const arr = vars[key];
|
|
193
|
+
if (Array.isArray(arr)) {
|
|
194
|
+
for (let i = 0; i < arr.length; i++) {
|
|
195
|
+
const el = arr[i];
|
|
196
|
+
const scope = (el && typeof el === 'object' && !Array.isArray(el))
|
|
197
|
+
? { ...vars, ...el, '@index': i }
|
|
198
|
+
: { ...vars, '.': el, '@index': i };
|
|
199
|
+
result += renderTemplate(body, scope, stripBlankLines);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
// 数组以外(含 undefined / 非数组)→ 整块跳过(不输出)
|
|
203
|
+
cursor = blockEnd;
|
|
204
|
+
OPEN.lastIndex = blockEnd;
|
|
205
|
+
}
|
|
206
|
+
result += template.slice(cursor);
|
|
207
|
+
return result;
|
|
208
|
+
}
|
|
209
|
+
function resolveConditions(template, vars) {
|
|
210
|
+
// 只匹配**最内层** {{?...}}...{{/}} 块(逐字符负向前瞻排除嵌套),do/while 由内向外消解。
|
|
211
|
+
const inner = /\{\{\?(\w+)(?:(!=|=)([^}]*))?\}\}((?:(?!\{\{\?)[^])*?)\{\{\/\}\}/;
|
|
212
|
+
let result = template;
|
|
213
|
+
let prev;
|
|
214
|
+
do {
|
|
215
|
+
prev = result;
|
|
216
|
+
result = result.replace(inner, (_match, key, op, value, body) => {
|
|
217
|
+
if (op === '=')
|
|
218
|
+
return String(vars[key]) === value ? body : '';
|
|
219
|
+
if (op === '!=')
|
|
220
|
+
return String(vars[key]) !== value ? body : '';
|
|
221
|
+
return isTruthy(vars[key]) ? body : '';
|
|
222
|
+
});
|
|
223
|
+
} while (result !== prev);
|
|
224
|
+
return result;
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* 渲染模板:条件块 + 变量替换。stripBlankLines=true 时删除空行(系统提示词用,
|
|
228
|
+
* 紧凑);false 时保留空行(消息正文用,正文多段结构不能被压扁)。
|
|
229
|
+
*/
|
|
230
|
+
export function renderTemplate(template, vars, stripBlankLines = true) {
|
|
231
|
+
let result = resolveEach(template, vars, stripBlankLines);
|
|
232
|
+
result = resolveConditions(result, vars);
|
|
233
|
+
// 变量替换:支持普通名、当前元素 {{.}}、循环序号 {{@index}}。
|
|
234
|
+
result = result.replace(/\{\{(\.|@index|\w+)\}\}/g, (_match, key) => {
|
|
235
|
+
const val = vars[key];
|
|
236
|
+
if (!isTruthy(val) && val !== 0)
|
|
237
|
+
return ''; // 0 是有效序号/值,保留
|
|
238
|
+
if (val === 0)
|
|
239
|
+
return '0';
|
|
240
|
+
return String(val);
|
|
241
|
+
});
|
|
242
|
+
if (stripBlankLines)
|
|
243
|
+
return result.split('\n').filter(line => line.trim() !== '').join('\n');
|
|
244
|
+
return result;
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* 三段式循环渲染:wrapper 里的 `{{@loop}}` 占位处,用 childTpl 对 vars[forEach]
|
|
248
|
+
* 数组每个元素渲染并拼接后填入。复用现有 `{{#each}}` 原语(不新写循环):
|
|
249
|
+
* 把 wrapper 的 `{{@loop}}` 替换为 `{{#each forEach}}<childTpl>{{/each}}`,整体丢给 renderTemplate。
|
|
250
|
+
* 因此 childTpl 内可再嵌套 `{{#each}}`、访问元素字段与外层 vars(子作用域已合并)。
|
|
251
|
+
*
|
|
252
|
+
* 若 wrapper 不含 `{{@loop}}`,childTpl 循环结果直接追加到 wrapper 末尾(容错)。
|
|
253
|
+
*/
|
|
254
|
+
/**
|
|
255
|
+
* 三段式循环渲染:对 vars[forEach] 数组每个元素渲染 childTpl,以 separator 连接,
|
|
256
|
+
* 填入 wrapperTpl 的 `{{@loop}}` 占位处。
|
|
257
|
+
*
|
|
258
|
+
* - 每个元素构造子作用域(对象元素 → { ...vars, ...el, '@index': i };标量 → { ...vars, '.': el, '@index': i }),
|
|
259
|
+
* 因此 childTpl 内可访问元素字段、外层 vars、`{{@index}}`,也可再嵌套 `{{#each}}` / 另一个占位。
|
|
260
|
+
* - separator 默认换行;可为 ""(无分隔)、"\n\n" 等。
|
|
261
|
+
* - onElementScope 可选钩子:在渲染每个元素前修改其子作用域(供 message-renderer 做 content 哨兵化)。
|
|
262
|
+
* - wrapper 不含 `{{@loop}}` 时,循环结果追加到 wrapper 末尾(容错)。
|
|
263
|
+
* - 空数组 / 非数组 → 循环结果为空串。
|
|
264
|
+
*/
|
|
265
|
+
export function renderLoopSection(wrapperTpl, childTpl, forEach, vars, stripBlankLines = true, separator = '\n', onElementScope) {
|
|
266
|
+
const arr = vars[forEach];
|
|
267
|
+
const parts = [];
|
|
268
|
+
if (Array.isArray(arr)) {
|
|
269
|
+
for (let i = 0; i < arr.length; i++) {
|
|
270
|
+
const el = arr[i];
|
|
271
|
+
const scope = (el && typeof el === 'object' && !Array.isArray(el))
|
|
272
|
+
? { ...vars, ...el, '@index': i }
|
|
273
|
+
: { ...vars, '.': el, '@index': i };
|
|
274
|
+
if (onElementScope)
|
|
275
|
+
onElementScope(scope, i);
|
|
276
|
+
parts.push(renderTemplate(childTpl, scope, stripBlankLines));
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
const loopResult = parts.join(separator);
|
|
280
|
+
// wrapper 用哨兵占位 {{@loop}} → 先渲染 wrapper 外层变量,再字面量替换回 loopResult,
|
|
281
|
+
// 避免已渲染的 loopResult(可能含元素文本里的 {{}})被二次解析。
|
|
282
|
+
const LOOP_SENTINEL = '\x00ECLOOP\x00';
|
|
283
|
+
const wrapperWithSentinel = wrapperTpl.includes('{{@loop}}')
|
|
284
|
+
? wrapperTpl.split('{{@loop}}').join(LOOP_SENTINEL)
|
|
285
|
+
: wrapperTpl + '\n' + LOOP_SENTINEL;
|
|
286
|
+
const renderedWrapper = renderTemplate(wrapperWithSentinel, vars, stripBlankLines);
|
|
287
|
+
return renderedWrapper.split(LOOP_SENTINEL).join(loopResult);
|
|
288
|
+
}
|
|
289
|
+
export function resolvePathWithDiag(rawPath, vars) {
|
|
290
|
+
const unresolved = [];
|
|
291
|
+
let resolved = rawPath.replace(/\$([A-Z_]+)/g, (_m, name) => {
|
|
292
|
+
const val = vars[name];
|
|
293
|
+
if (val === undefined || val === null || val === false || val === '') {
|
|
294
|
+
unresolved.push(`$${name}`);
|
|
295
|
+
return '';
|
|
296
|
+
}
|
|
297
|
+
return String(val);
|
|
298
|
+
});
|
|
299
|
+
resolved = resolved.replace(/\{\{(\w+)\}\}/g, (_m, key) => {
|
|
300
|
+
const val = vars[key];
|
|
301
|
+
if (val === undefined || val === null || val === false || val === '') {
|
|
302
|
+
unresolved.push(`{{${key}}}`);
|
|
303
|
+
return '';
|
|
304
|
+
}
|
|
305
|
+
return String(val);
|
|
306
|
+
});
|
|
307
|
+
if (!resolved || resolved.includes('$') || resolved.includes('{{')) {
|
|
308
|
+
return { resolved: resolved || null, status: 'unresolved-vars', unresolvedTokens: unresolved };
|
|
309
|
+
}
|
|
310
|
+
if (unresolved.length > 0) {
|
|
311
|
+
return { resolved, status: 'unresolved-vars', unresolvedTokens: unresolved };
|
|
312
|
+
}
|
|
313
|
+
// 路径规范化:模板里 ../ 等相对片段折叠成真实路径。
|
|
314
|
+
// 不再在此 existsSync——存在性由随后经 fileCache 的内容读取顺带得到(file
|
|
315
|
+
// section 读出 null 即不存在),避免每 section 每消息一次 syscall。
|
|
316
|
+
resolved = path.normalize(resolved);
|
|
317
|
+
return { resolved, status: 'ok', unresolvedTokens: unresolved };
|
|
318
|
+
}
|
|
319
|
+
function resolvePath(rawPath, vars) {
|
|
320
|
+
const r = resolvePathWithDiag(rawPath, vars);
|
|
321
|
+
return r.status === 'ok' ? r.resolved : null;
|
|
322
|
+
}
|
|
323
|
+
/** 加载 loop 的 child 子模板文件内容(路径解析 + fileCache)。找不到返回 null。 */
|
|
324
|
+
export function loadChildTemplate(childFile, vars) {
|
|
325
|
+
const resolved = resolvePath(childFile, vars);
|
|
326
|
+
if (!resolved)
|
|
327
|
+
return null;
|
|
328
|
+
return fileCache.getText(resolved, { policy: 'on-reload', group: 'kits' });
|
|
329
|
+
}
|
|
330
|
+
/**
|
|
331
|
+
* 返回 [filePath, rawContent][];按 sessionId 缓存已读文件内容。
|
|
332
|
+
* 若传入 overflowOut 且目录段触发单目录限额(maxFiles/maxBytes),
|
|
333
|
+
* 会把溢出信息写入 overflowOut.value(不传则不启用限额收集,行为同旧版但仍应用默认上限)。
|
|
334
|
+
*/
|
|
335
|
+
export function loadSectionFiles(section, vars, sessionCache, overflowOut) {
|
|
336
|
+
if (section.type === 'file' && section.file) {
|
|
337
|
+
const result = loadFileSection(section.file, vars, sessionCache);
|
|
338
|
+
return result ? [result] : [];
|
|
339
|
+
}
|
|
340
|
+
if (section.type === 'directory' && section.path) {
|
|
341
|
+
const resolved = resolvePath(section.path, vars);
|
|
342
|
+
if (!resolved)
|
|
343
|
+
return [];
|
|
344
|
+
const maxFiles = section.maxFiles ?? DIR_MAX_FILES;
|
|
345
|
+
const maxBytes = section.maxBytes ?? DIR_MAX_BYTES;
|
|
346
|
+
const { files, overflow } = readDirectoryFiles(resolved, section.pattern, maxFiles, maxBytes);
|
|
347
|
+
if (overflow && overflowOut)
|
|
348
|
+
overflowOut.value = overflow;
|
|
349
|
+
return files.map(([name, content]) => [path.join(resolved, name), content]);
|
|
350
|
+
}
|
|
351
|
+
return [];
|
|
352
|
+
}
|
|
353
|
+
function loadFileSection(filePath, vars, sessionCache) {
|
|
354
|
+
void sessionCache; // 内容跨 session 共享,改走全局 fileCache(on-reload)
|
|
355
|
+
const resolved = resolvePath(filePath, vars);
|
|
356
|
+
if (!resolved)
|
|
357
|
+
return null;
|
|
358
|
+
// 内容跨 session 共享:用全局 fileCache(on-reload,reload/重启时失效),
|
|
359
|
+
// 不再按 session 重复缓存同一文件内容。
|
|
360
|
+
const content = fileCache.getText(resolved, { policy: 'on-reload', group: 'kits' });
|
|
361
|
+
return content === null ? null : [resolved, content];
|
|
362
|
+
}
|
|
363
|
+
/**
|
|
364
|
+
* 读取目录下匹配文件,受单目录限额(maxFiles/maxBytes)约束。
|
|
365
|
+
* 返回已加载文件 + 溢出信息(超限时)。按 fileCache 缓存目录列表与文件内容。
|
|
366
|
+
*/
|
|
367
|
+
function readDirectoryFiles(dirPath, pattern, maxFiles, maxBytes) {
|
|
368
|
+
const glob = pattern || '*.md';
|
|
369
|
+
// 目录列表 + 各文件内容均走 fileCache(on-reload)。目录列表以 "<dir>|<glob>"
|
|
370
|
+
// 为键缓存文件名数组;各文件内容走 fileCache.getText 共享。
|
|
371
|
+
const names = fileCache.get(`${dirPath} ${glob}`, () => {
|
|
372
|
+
try {
|
|
373
|
+
return fs.readdirSync(dirPath).filter(f => matchGlob(f, glob)).sort();
|
|
374
|
+
}
|
|
375
|
+
catch {
|
|
376
|
+
return [];
|
|
377
|
+
}
|
|
378
|
+
}, { policy: 'on-reload', group: 'kits' });
|
|
379
|
+
const out = [];
|
|
380
|
+
let usedBytes = 0;
|
|
381
|
+
let overflow;
|
|
382
|
+
for (let i = 0; i < names.length; i++) {
|
|
383
|
+
const f = names[i];
|
|
384
|
+
// 文件数限额
|
|
385
|
+
if (out.length >= maxFiles) {
|
|
386
|
+
overflow = { droppedFiles: names.length - out.length, reason: 'files', limit: maxFiles };
|
|
387
|
+
break;
|
|
388
|
+
}
|
|
389
|
+
const fp = path.join(dirPath, f);
|
|
390
|
+
const content = fileCache.getText(fp, { policy: 'on-reload', group: 'kits' });
|
|
391
|
+
if (content === null)
|
|
392
|
+
continue;
|
|
393
|
+
// 字节限额(至少加载一个文件,避免单个超大文件导致空目录)
|
|
394
|
+
const size = Buffer.byteLength(content, 'utf-8');
|
|
395
|
+
if (out.length > 0 && usedBytes + size > maxBytes) {
|
|
396
|
+
overflow = { droppedFiles: names.length - out.length, reason: 'bytes', limit: maxBytes };
|
|
397
|
+
break;
|
|
398
|
+
}
|
|
399
|
+
usedBytes += size;
|
|
400
|
+
out.push([f, content]);
|
|
401
|
+
}
|
|
402
|
+
return { files: out, overflow };
|
|
403
|
+
}
|
|
404
|
+
function matchGlob(filename, pattern) {
|
|
405
|
+
const regex = pattern
|
|
406
|
+
.replace(/\./g, '\\.')
|
|
407
|
+
.replace(/\*/g, '.*')
|
|
408
|
+
.replace(/\{([^}]+)\}/g, (_, alts) => `(${alts.split(',').join('|')})`);
|
|
409
|
+
return new RegExp(`^${regex}$`).test(filename);
|
|
410
|
+
}
|
|
411
|
+
export function buildPathMappings(vars) {
|
|
412
|
+
const pkgRoot = getPackageRoot();
|
|
413
|
+
const evolHome = String(vars['EVOLCORE_HOME'] || resolveRoot());
|
|
414
|
+
const selfAid = vars['selfAid'] ? String(vars['selfAid']) : '';
|
|
415
|
+
const currentProject = vars['CURRENT_PROJECT'] ? String(vars['CURRENT_PROJECT']) : '';
|
|
416
|
+
const mappings = [
|
|
417
|
+
{ prefix: path.join(pkgRoot, 'kits', 'rules'), alias: '$KITS_RULES' },
|
|
418
|
+
{ prefix: path.join(pkgRoot, 'kits', 'templates', 'system-fragments'), alias: '$KITS_FRAGMENTS' },
|
|
419
|
+
{ prefix: path.join(pkgRoot, 'kits', 'templates', 'message-fragments'), alias: '$KITS_MESSAGE_FRAGMENTS' },
|
|
420
|
+
{ prefix: path.join(pkgRoot, 'kits', 'templates'), alias: '$KITS_TEMPLATES' },
|
|
421
|
+
{ prefix: path.join(pkgRoot, 'kits', 'docs'), alias: '$KITS_DOCS' },
|
|
422
|
+
{ prefix: path.join(pkgRoot, 'kits'), alias: '$KITS' },
|
|
423
|
+
{ prefix: pkgRoot, alias: '$PACKAGE_ROOT' },
|
|
424
|
+
];
|
|
425
|
+
if (selfAid) {
|
|
426
|
+
mappings.push({ prefix: path.join(evolHome, 'agents', selfAid, 'personal'), alias: '$PERSONAL_DIR' });
|
|
427
|
+
mappings.push({ prefix: path.join(evolHome, 'agents', selfAid, 'relations'), alias: '$RELATIONS_DIR' });
|
|
428
|
+
mappings.push({ prefix: path.join(evolHome, 'agents', selfAid, 'venues'), alias: '$VENUES_DIR' });
|
|
429
|
+
mappings.push({ prefix: path.join(evolHome, 'agents', selfAid), alias: '$AGENT_DIR' });
|
|
430
|
+
}
|
|
431
|
+
mappings.push({ prefix: evolHome, alias: '$EVOLCORE_HOME' });
|
|
432
|
+
if (currentProject)
|
|
433
|
+
mappings.push({ prefix: currentProject, alias: '$CURRENT_PROJECT' });
|
|
434
|
+
mappings.sort((a, b) => b.prefix.length - a.prefix.length);
|
|
435
|
+
return mappings;
|
|
436
|
+
}
|
|
437
|
+
export function shortenPath(filePath, mappings) {
|
|
438
|
+
const normalized = filePath.replace(/\\/g, '/');
|
|
439
|
+
for (const { prefix, alias } of mappings) {
|
|
440
|
+
const normalizedPrefix = prefix.replace(/\\/g, '/');
|
|
441
|
+
if (normalized.startsWith(normalizedPrefix)) {
|
|
442
|
+
return alias + normalized.slice(normalizedPrefix.length);
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
return filePath;
|
|
446
|
+
}
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
// Message rendering layer: render a batch of sub-messages one-by-one through the
|
|
2
|
+
// message manifest, then assemble the final body fed to the base agent. Shares the
|
|
3
|
+
// manifest-engine primitives with system-prompt rendering, with two key differences:
|
|
4
|
+
// 1. The raw message text {{content}} is injected as a LITERAL in the final step,
|
|
5
|
+
// never going through template parsing again (otherwise {{...}} inside a user
|
|
6
|
+
// message would be treated as a template -- garbled at best, injection at worst).
|
|
7
|
+
// 2. Blank lines are preserved (multi-paragraph message bodies must not be squashed).
|
|
8
|
+
import fs from 'fs';
|
|
9
|
+
import path from 'path';
|
|
10
|
+
import { randomUUID } from 'crypto';
|
|
11
|
+
import { isEckSnapshotsEnabled } from '../config-store.js';
|
|
12
|
+
import { eckDebugDir } from '../paths.js';
|
|
13
|
+
import { logger } from '../utils/logger.js';
|
|
14
|
+
import { loadManifest, evaluateWhen, renderTemplate, loadSectionFiles, defaultModeNames, } from './manifest-engine.js';
|
|
15
|
+
const MESSAGE_MANIFEST_FILE = 'eck_message_manifest.json';
|
|
16
|
+
// ── time formatting (per IANA timezone) ──
|
|
17
|
+
function timeParts(epochMs, timeZone, opts) {
|
|
18
|
+
const d = new Date(epochMs);
|
|
19
|
+
const p = new Intl.DateTimeFormat('en-US', { ...(timeZone ? { timeZone } : {}), ...opts }).formatToParts(d);
|
|
20
|
+
const out = {};
|
|
21
|
+
for (const part of p)
|
|
22
|
+
out[part.type] = part.value;
|
|
23
|
+
return out;
|
|
24
|
+
}
|
|
25
|
+
/** "2026-06-04 21:33:07 +08:00" */
|
|
26
|
+
export function formatLocalTime(epochMs, timeZone) {
|
|
27
|
+
const g = timeParts(epochMs, timeZone, {
|
|
28
|
+
year: 'numeric', month: '2-digit', day: '2-digit',
|
|
29
|
+
hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: false,
|
|
30
|
+
});
|
|
31
|
+
const hour = g.hour === '24' ? '00' : g.hour; // hour12:false may yield "24" at midnight
|
|
32
|
+
const off = timeParts(epochMs, timeZone, { timeZoneName: 'longOffset' }).timeZoneName || '';
|
|
33
|
+
const offset = off.replace(/^GMT/, '') || '+00:00';
|
|
34
|
+
return `${g.year}-${g.month}-${g.day} ${hour}:${g.minute}:${g.second} ${offset}`;
|
|
35
|
+
}
|
|
36
|
+
// ── single item render ──
|
|
37
|
+
function renderOneItem(item, sessionVars, sessionCache, contentSentinel) {
|
|
38
|
+
const sections = loadManifest(MESSAGE_MANIFEST_FILE);
|
|
39
|
+
// 各 modeType 当前激活的 modeName:agent config.render(经 sessionVars.renderModes 透传)
|
|
40
|
+
// 覆盖 manifest 里标 isDefault 的缺省。详见 docs/observer-insert-design.md 第二部分。
|
|
41
|
+
const defaults = defaultModeNames(sections);
|
|
42
|
+
const configured = (sessionVars.renderModes && typeof sessionVars.renderModes === 'object' && !Array.isArray(sessionVars.renderModes))
|
|
43
|
+
? sessionVars.renderModes
|
|
44
|
+
: {};
|
|
45
|
+
const activeMode = (type) => {
|
|
46
|
+
const c = configured[type];
|
|
47
|
+
if (typeof c === 'string' && c)
|
|
48
|
+
return c;
|
|
49
|
+
return defaults[type];
|
|
50
|
+
};
|
|
51
|
+
const isOwnerHint = item.kind === 'owner-hint';
|
|
52
|
+
const isHandoff = item.kind === 'handoff';
|
|
53
|
+
const handoffPreviousSentinel = `\x00ECMSG-HANDOFF-PREV-${randomUUID()}\x00`;
|
|
54
|
+
// item-level vars: session vars overlaid with this message's own sender/timestamp.
|
|
55
|
+
const itemVars = {
|
|
56
|
+
...sessionVars,
|
|
57
|
+
peerId: item.peerId ?? sessionVars.peerId,
|
|
58
|
+
peerName: item.peerName ?? sessionVars.peerName,
|
|
59
|
+
peerType: item.peerType ?? sessionVars.peerType,
|
|
60
|
+
peerRole: item.peerRole ?? sessionVars.peerRole,
|
|
61
|
+
sameDevice: item.sameDevice ?? sessionVars.sameDevice,
|
|
62
|
+
sameNetwork: item.sameNetwork ?? sessionVars.sameNetwork,
|
|
63
|
+
sameEgressIp: item.sameEgressIp ?? sessionVars.sameEgressIp,
|
|
64
|
+
// 入站加密态(仅 aun 有意义;非 aun 渠道为 undefined)。透传原始布尔:
|
|
65
|
+
// 模板 {{?encrypted=true}}→🔒密文、{{?encrypted=false}}→✉️明文、undefined 两者均落空。
|
|
66
|
+
// 注意:必须传原始布尔(含 false),不能折成 true|undefined,否则 false 分支永不命中。
|
|
67
|
+
encrypted: item.encrypted,
|
|
68
|
+
// 模板引擎不支持数组循环:被 @ 的 AID 预先 join 成串,空则 undefined 使 {{?mentionAids}} 落空。
|
|
69
|
+
mentionAids: (item.mentionAids && item.mentionAids.length > 0) ? item.mentionAids.join(',') : undefined,
|
|
70
|
+
now: formatLocalTime(item.timestamp ?? Date.now(), sessionVars.timezone ? String(sessionVars.timezone) : undefined),
|
|
71
|
+
// 渲染模式命中变量(manifest section 的 when 用它选中唯一模式)
|
|
72
|
+
renderMode_private: activeMode('private'),
|
|
73
|
+
renderMode_group: activeMode('group'),
|
|
74
|
+
renderMode_inject: activeMode('inject'),
|
|
75
|
+
// owner 插话提示标记 + 信封头字段
|
|
76
|
+
isOwnerHint,
|
|
77
|
+
ownerAid: isOwnerHint ? (item.ownerAid ?? undefined) : undefined,
|
|
78
|
+
injectTime: isOwnerHint
|
|
79
|
+
? formatLocalTime(item.injectTime ?? item.timestamp ?? Date.now(), sessionVars.timezone ? String(sessionVars.timezone) : undefined)
|
|
80
|
+
: undefined,
|
|
81
|
+
// msg send 跨会话一次性上下文
|
|
82
|
+
isHandoff,
|
|
83
|
+
handoffKind: item.handoff?.kind,
|
|
84
|
+
handoffId: item.handoff?.handoffId,
|
|
85
|
+
handoffOriginChannel: item.handoff?.origin?.channel,
|
|
86
|
+
handoffOriginPeerId: item.handoff?.origin?.peerId,
|
|
87
|
+
handoffOriginThreadId: item.handoff?.origin?.threadId,
|
|
88
|
+
handoffOriginPeerName: item.handoff?.origin?.peerName,
|
|
89
|
+
handoffOriginPeerType: item.handoff?.origin?.peerType,
|
|
90
|
+
handoffOriginRole: item.handoff?.origin?.role,
|
|
91
|
+
handoffPreviousMessageId: item.handoff?.previousMessageId ?? undefined,
|
|
92
|
+
handoffPreviousContent: item.handoff ? handoffPreviousSentinel : undefined,
|
|
93
|
+
handoffReturnPolicy: item.handoff?.returnPolicy,
|
|
94
|
+
// content held as a per-call random sentinel, swapped back post-render.
|
|
95
|
+
// Using a UUID means no real message can collide with it.
|
|
96
|
+
content: contentSentinel,
|
|
97
|
+
};
|
|
98
|
+
const out = [];
|
|
99
|
+
for (const section of sections) {
|
|
100
|
+
if (section.enabled === false)
|
|
101
|
+
continue;
|
|
102
|
+
if (section.loop)
|
|
103
|
+
continue; // loop 段是批次级包裹,由 renderMessageBody 处理,不参与逐条
|
|
104
|
+
if (!evaluateWhen(section.when, itemVars))
|
|
105
|
+
continue;
|
|
106
|
+
const files = loadSectionFiles(section, itemVars, sessionCache);
|
|
107
|
+
for (const [, rawContent] of files) {
|
|
108
|
+
const rendered = section.needsInjection
|
|
109
|
+
? renderTemplate(rawContent, itemVars, /* stripBlankLines */ false)
|
|
110
|
+
: rawContent;
|
|
111
|
+
// swap the sentinel back to the real message text (literal replace, no parsing).
|
|
112
|
+
const withContent = rendered
|
|
113
|
+
.split(contentSentinel).join(item.content)
|
|
114
|
+
.split(handoffPreviousSentinel).join(item.handoff?.previousContent ?? '');
|
|
115
|
+
if (withContent.trim())
|
|
116
|
+
out.push(withContent.replace(/\s+$/, ''));
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
// if the manifest produced nothing, fall back to raw text -- never drop a message.
|
|
120
|
+
return out.length > 0 ? out.join('\n') : item.content;
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Render each sub-message then assemble the final message body.
|
|
124
|
+
* Also collects all images across items in order, preserving per-item attribution.
|
|
125
|
+
*
|
|
126
|
+
* One item -> single render; many (group batch / same-peer merge) -> each carries
|
|
127
|
+
* its own sender and timestamp.
|
|
128
|
+
*/
|
|
129
|
+
export function renderMessageBody(items, sessionVars, sessionId) {
|
|
130
|
+
if (!items || items.length === 0)
|
|
131
|
+
return { body: '', images: [] };
|
|
132
|
+
// One random sentinel per renderMessageBody call -- impossible for user text to match.
|
|
133
|
+
const contentSentinel = `\x00ECMSG-${randomUUID()}\x00`;
|
|
134
|
+
const sessionCache = new Map(); // render-local cache (template files are small & fixed)
|
|
135
|
+
const allImages = [];
|
|
136
|
+
for (const item of items) {
|
|
137
|
+
if (item.images && item.images.length > 0)
|
|
138
|
+
allImages.push(...item.images);
|
|
139
|
+
}
|
|
140
|
+
// 逐条渲染每个 item(自带哨兵,防用户文本里的 {{}} 被二次解析)。
|
|
141
|
+
const renderedParts = items.map(item => renderOneItem(item, sessionVars, sessionCache, contentSentinel));
|
|
142
|
+
// 批次包裹层:message manifest 里若有 loop 段(wrapper file + 批次 vars),
|
|
143
|
+
// 用它包裹逐条结果——wrapper 渲染批次 vars(如 remainingInQueue,从 sessionVars 透传),
|
|
144
|
+
// {{@loop}} 处字面量填入逐条结果(已渲染完成,不再过模板 → 哨兵天然生效)。
|
|
145
|
+
const body = wrapBatch(renderedParts, sessionVars, sessionCache) ?? renderedParts.join('\n\n');
|
|
146
|
+
writeMessageDebug(sessionId, items, body);
|
|
147
|
+
return { body, images: allImages };
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* 若 message manifest 含 loop 段,用其 wrapper 包裹逐条渲染结果。返回包裹后 body,
|
|
151
|
+
* 无 loop 段则返回 null(调用方回退到 join)。
|
|
152
|
+
* 逐条结果 renderedParts 已是最终文本,作为字面量填入 wrapper 的 {{@loop}},不二次解析。
|
|
153
|
+
*/
|
|
154
|
+
function wrapBatch(renderedParts, sessionVars, sessionCache) {
|
|
155
|
+
const sections = loadManifest(MESSAGE_MANIFEST_FILE);
|
|
156
|
+
const loopSection = sections.find(s => s.loop && s.enabled !== false && evaluateWhen(s.when, sessionVars));
|
|
157
|
+
if (!loopSection || !loopSection.loop || !loopSection.file)
|
|
158
|
+
return null;
|
|
159
|
+
const wrapperFiles = loadSectionFiles(loopSection, sessionVars, sessionCache);
|
|
160
|
+
if (wrapperFiles.length === 0)
|
|
161
|
+
return null;
|
|
162
|
+
const wrapperTpl = wrapperFiles[0][1];
|
|
163
|
+
const sep = loopSection.loop.separator ?? '\n\n';
|
|
164
|
+
const loopResult = renderedParts.join(sep);
|
|
165
|
+
// wrapper 渲染批次 vars,{{@loop}} 用哨兵占位后字面量替换回 loopResult(避免逐条结果被二次解析)。
|
|
166
|
+
const LOOP_SENTINEL = '\x00ECLOOP\x00';
|
|
167
|
+
const wrapperWithSentinel = wrapperTpl.includes('{{@loop}}')
|
|
168
|
+
? wrapperTpl.split('{{@loop}}').join(LOOP_SENTINEL)
|
|
169
|
+
: wrapperTpl + '\n' + LOOP_SENTINEL;
|
|
170
|
+
const rendered = renderTemplate(wrapperWithSentinel, sessionVars, /* stripBlankLines */ false);
|
|
171
|
+
return rendered.split(LOOP_SENTINEL).join(loopResult);
|
|
172
|
+
}
|
|
173
|
+
// ── Debug ──
|
|
174
|
+
function writeMessageDebug(sessionId, items, body) {
|
|
175
|
+
if (!isEckSnapshotsEnabled())
|
|
176
|
+
return;
|
|
177
|
+
try {
|
|
178
|
+
const ts = new Date().toISOString().replace(/[T:.]/g, '-').slice(0, 19);
|
|
179
|
+
const dir = eckDebugDir();
|
|
180
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
181
|
+
const out = [
|
|
182
|
+
`# Message Render`,
|
|
183
|
+
`- sessionId: ${sessionId}`,
|
|
184
|
+
`- items: ${items.length}`,
|
|
185
|
+
`- images: ${items.reduce((n, i) => n + (i.images?.length ?? 0), 0)}`,
|
|
186
|
+
``,
|
|
187
|
+
`## Rendered body`,
|
|
188
|
+
``,
|
|
189
|
+
body,
|
|
190
|
+
].join('\n');
|
|
191
|
+
fs.writeFile(path.join(dir, `msg-render-${ts}.md`), out, (error) => {
|
|
192
|
+
if (error)
|
|
193
|
+
logger.debug(`[MessageRenderer] debug write failed: ${error.message}`);
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
catch (e) {
|
|
197
|
+
logger.debug(`[MessageRenderer] debug write failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { kitsRulesDir } from '../paths.js';
|
|
4
|
+
let _cachedRules = null;
|
|
5
|
+
export function loadRulesForInjection() {
|
|
6
|
+
if (_cachedRules !== null)
|
|
7
|
+
return _cachedRules;
|
|
8
|
+
const rulesDir = kitsRulesDir();
|
|
9
|
+
if (!fs.existsSync(rulesDir)) {
|
|
10
|
+
_cachedRules = '';
|
|
11
|
+
return '';
|
|
12
|
+
}
|
|
13
|
+
const files = fs.readdirSync(rulesDir)
|
|
14
|
+
.filter(f => f.endsWith('.md'))
|
|
15
|
+
.sort();
|
|
16
|
+
const parts = [];
|
|
17
|
+
for (const file of files) {
|
|
18
|
+
try {
|
|
19
|
+
parts.push(fs.readFileSync(path.join(rulesDir, file), 'utf-8'));
|
|
20
|
+
}
|
|
21
|
+
catch { /* skip unreadable files */ }
|
|
22
|
+
}
|
|
23
|
+
_cachedRules = parts.join('\n\n');
|
|
24
|
+
return _cachedRules;
|
|
25
|
+
}
|
|
26
|
+
export function invalidateRulesCache() {
|
|
27
|
+
_cachedRules = null;
|
|
28
|
+
}
|