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,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 响应模式迁移:行为快照探针
|
|
3
|
+
*
|
|
4
|
+
* 用途:迁移前后对比 message-processor 的实际行为是否逐字节一致(防线 1)。
|
|
5
|
+
*
|
|
6
|
+
* 设计:
|
|
7
|
+
* - 按 (sessionId, taskId) 聚合一条快照,任务处理过程中逐步填充字段
|
|
8
|
+
* - 任务结束时落盘到 $EVOLCORE_HOME/data/eck-debug/response-snapshots.jsonl
|
|
9
|
+
* - 零侵入:探针关闭时所有方法是 no-op,不影响行为
|
|
10
|
+
* - 仅记录「模式特有决策点」的输入输出,不记录消息内容(隐私 + 体积)
|
|
11
|
+
*
|
|
12
|
+
* 开关:进程级 debug.eckSnapshots 总闸开启,且 RESPONSE_SNAPSHOT=1 时启用。
|
|
13
|
+
*/
|
|
14
|
+
import fs from 'fs';
|
|
15
|
+
import path from 'path';
|
|
16
|
+
import { isEckSnapshotsEnabled } from '../../config-store.js';
|
|
17
|
+
import { eckDebugDir } from '../../paths.js';
|
|
18
|
+
import { appendJsonl } from '../session/session-fs-store.js';
|
|
19
|
+
import { logger } from '../../utils/logger.js';
|
|
20
|
+
// 动态检查环境变量(而非模块级常量)
|
|
21
|
+
function isEnabled() {
|
|
22
|
+
return isEckSnapshotsEnabled() && process.env.RESPONSE_SNAPSHOT === '1';
|
|
23
|
+
}
|
|
24
|
+
class SnapshotRecorder {
|
|
25
|
+
/** (sessionId::taskId) → 进行中的快照 */
|
|
26
|
+
active = new Map();
|
|
27
|
+
key(sessionId, taskId) {
|
|
28
|
+
return `${sessionId}::${taskId}`;
|
|
29
|
+
}
|
|
30
|
+
getOutPath() {
|
|
31
|
+
const dir = eckDebugDir();
|
|
32
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
33
|
+
return path.join(dir, 'response-snapshots.jsonl');
|
|
34
|
+
}
|
|
35
|
+
/** 开始一条快照(beforeProcess 阶段调用) */
|
|
36
|
+
begin(sessionId, taskId, source, msgId) {
|
|
37
|
+
if (!isEnabled())
|
|
38
|
+
return;
|
|
39
|
+
this.active.set(this.key(sessionId, taskId), {
|
|
40
|
+
ts: Date.now(), sessionId, taskId, source, msgId,
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
/** 填充字段(处理过程中逐步调用) */
|
|
44
|
+
set(sessionId, taskId, patch) {
|
|
45
|
+
if (!isEnabled())
|
|
46
|
+
return;
|
|
47
|
+
const snap = this.active.get(this.key(sessionId, taskId));
|
|
48
|
+
if (snap)
|
|
49
|
+
Object.assign(snap, patch);
|
|
50
|
+
}
|
|
51
|
+
/** 向数组字段追加(outbound 等) */
|
|
52
|
+
pushOutbound(sessionId, taskId, entry) {
|
|
53
|
+
if (!isEnabled())
|
|
54
|
+
return;
|
|
55
|
+
const snap = this.active.get(this.key(sessionId, taskId));
|
|
56
|
+
if (!snap)
|
|
57
|
+
return;
|
|
58
|
+
if (!snap.outbound)
|
|
59
|
+
snap.outbound = [];
|
|
60
|
+
snap.outbound.push(entry);
|
|
61
|
+
}
|
|
62
|
+
/** 结束并落盘(任务收尾时调用) */
|
|
63
|
+
end(sessionId, taskId) {
|
|
64
|
+
if (!isEnabled())
|
|
65
|
+
return;
|
|
66
|
+
const k = this.key(sessionId, taskId);
|
|
67
|
+
const snap = this.active.get(k);
|
|
68
|
+
if (!snap)
|
|
69
|
+
return;
|
|
70
|
+
this.active.delete(k);
|
|
71
|
+
try {
|
|
72
|
+
appendJsonl(this.getOutPath(), snap);
|
|
73
|
+
}
|
|
74
|
+
catch (e) {
|
|
75
|
+
logger.debug(`[snapshot] write failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
isEnabled() {
|
|
79
|
+
return isEnabled();
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
/** 全局单例探针 */
|
|
83
|
+
export const snapshot = new SnapshotRecorder();
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export function sendOperationId(envelope) {
|
|
2
|
+
return envelope.operationId || envelope.taskId;
|
|
3
|
+
}
|
|
4
|
+
export function sentReceipt(envelope, messageIds, threadId) {
|
|
5
|
+
const messages = messageIds
|
|
6
|
+
.filter((messageId) => typeof messageId === 'string' && messageId.length > 0)
|
|
7
|
+
.map((messageId, partIndex) => ({ messageId, partIndex, threadId }));
|
|
8
|
+
if (messages.length === 0) {
|
|
9
|
+
throw new Error(`MISSING_MESSAGE_ID: operation=${sendOperationId(envelope)}`);
|
|
10
|
+
}
|
|
11
|
+
return {
|
|
12
|
+
status: 'sent',
|
|
13
|
+
operationId: sendOperationId(envelope),
|
|
14
|
+
messages: messages,
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
export function suppressedReceipt(envelope, reason) {
|
|
18
|
+
return {
|
|
19
|
+
status: 'suppressed',
|
|
20
|
+
operationId: sendOperationId(envelope),
|
|
21
|
+
messages: [],
|
|
22
|
+
reason,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { logger } from '../../utils/logger.js';
|
|
2
|
+
/**
|
|
3
|
+
* 入站消息去抖器
|
|
4
|
+
*
|
|
5
|
+
* 在 debounceMs 窗口内收到的同一 session 的多条消息合并为一次 enqueue:
|
|
6
|
+
* - content 用 \n 连接
|
|
7
|
+
* - images / mentions 合并
|
|
8
|
+
* - replyContext / 其余字段取最后一条
|
|
9
|
+
*
|
|
10
|
+
* cancel(messageId) 可精确移除窗口中的某条消息,不影响其余消息。
|
|
11
|
+
*/
|
|
12
|
+
export class StreamDebouncer {
|
|
13
|
+
pending = new Map();
|
|
14
|
+
delayMs;
|
|
15
|
+
maxWaitMs;
|
|
16
|
+
maxMessages;
|
|
17
|
+
constructor(debounceSeconds, maxMessages = 5) {
|
|
18
|
+
this.delayMs = debounceSeconds * 1000;
|
|
19
|
+
this.maxWaitMs = this.delayMs * 3;
|
|
20
|
+
this.maxMessages = maxMessages;
|
|
21
|
+
}
|
|
22
|
+
get enabled() {
|
|
23
|
+
return this.delayMs > 0;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* 提交一条消息。如果窗口内已有消息则追加并重置 timer;
|
|
27
|
+
* 否则新建窗口。timer 到期后自动 flush。
|
|
28
|
+
*/
|
|
29
|
+
submit(key, message, enqueue) {
|
|
30
|
+
const { content, images, mentions, messageId, sourceMessageIds, replyContext, ...rest } = message;
|
|
31
|
+
return new Promise((resolve, reject) => {
|
|
32
|
+
const entry = { messageId, sourceMessageIds, content, images, mentions, replyContext, rest, resolve, reject };
|
|
33
|
+
const win = this.pending.get(key);
|
|
34
|
+
if (win) {
|
|
35
|
+
clearTimeout(win.timer);
|
|
36
|
+
win.entries.push(entry);
|
|
37
|
+
if (win.entries.length >= this.maxMessages) {
|
|
38
|
+
logger.debug(`[Debounce] Max messages (${this.maxMessages}) reached for ${key}, flushing immediately`);
|
|
39
|
+
clearTimeout(win.maxWaitTimer);
|
|
40
|
+
this.flush(key, enqueue);
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
win.timer = setTimeout(() => this.flush(key, enqueue), this.delayMs);
|
|
44
|
+
logger.debug(`[Debounce] Appended message for ${key}, ${win.entries.length} pending`);
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
const timer = setTimeout(() => this.flush(key, enqueue), this.delayMs);
|
|
48
|
+
const maxWaitTimer = setTimeout(() => this.flush(key, enqueue), this.maxWaitMs);
|
|
49
|
+
this.pending.set(key, { entries: [entry], timer, maxWaitTimer });
|
|
50
|
+
logger.debug(`[Debounce] New window for ${key}, debounce=${this.delayMs}ms, maxWait=${this.maxWaitMs}ms`);
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* 从 debounce 窗口中撤回指定 messageId 的消息。
|
|
56
|
+
* 如果窗口只剩这一条,整个窗口取消。
|
|
57
|
+
* @returns true 如果找到并移除
|
|
58
|
+
*/
|
|
59
|
+
cancel(messageId) {
|
|
60
|
+
for (const [key, win] of this.pending) {
|
|
61
|
+
const idx = win.entries.findIndex(e => e.messageId === messageId);
|
|
62
|
+
if (idx === -1)
|
|
63
|
+
continue;
|
|
64
|
+
// resolve 被撤回的那条(静默完成,不报错)
|
|
65
|
+
win.entries.splice(idx, 1)[0].resolve();
|
|
66
|
+
logger.info(`[Debounce] Cancelled message ${messageId} from window ${key}`);
|
|
67
|
+
// 窗口空了 → 整个取消
|
|
68
|
+
if (win.entries.length === 0) {
|
|
69
|
+
clearTimeout(win.timer);
|
|
70
|
+
clearTimeout(win.maxWaitTimer);
|
|
71
|
+
this.pending.delete(key);
|
|
72
|
+
logger.info(`[Debounce] Window ${key} empty after cancel, removed`);
|
|
73
|
+
}
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
flush(key, enqueue) {
|
|
79
|
+
const win = this.pending.get(key);
|
|
80
|
+
if (!win)
|
|
81
|
+
return;
|
|
82
|
+
this.pending.delete(key);
|
|
83
|
+
clearTimeout(win.timer);
|
|
84
|
+
clearTimeout(win.maxWaitTimer);
|
|
85
|
+
const { entries } = win;
|
|
86
|
+
// 合并:content 用 \n 连接,images/mentions 扁平合并,其余取最后一条
|
|
87
|
+
const allImages = [];
|
|
88
|
+
const allMentions = [];
|
|
89
|
+
const contents = [];
|
|
90
|
+
const sourceMessageIds = [];
|
|
91
|
+
const seenSourceMessageIds = new Set();
|
|
92
|
+
for (const e of entries) {
|
|
93
|
+
contents.push(e.content);
|
|
94
|
+
if (e.images)
|
|
95
|
+
allImages.push(...e.images);
|
|
96
|
+
if (e.mentions)
|
|
97
|
+
allMentions.push(...e.mentions);
|
|
98
|
+
for (const id of [...(e.sourceMessageIds ?? []), ...(e.messageId ? [e.messageId] : [])]) {
|
|
99
|
+
if (!seenSourceMessageIds.has(id)) {
|
|
100
|
+
seenSourceMessageIds.add(id);
|
|
101
|
+
sourceMessageIds.push(id);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
const last = entries[entries.length - 1];
|
|
106
|
+
// 合并后保留最新一条的 messageId(用于 thought 锚定与中断追踪)
|
|
107
|
+
let latestMessageId;
|
|
108
|
+
for (let i = entries.length - 1; i >= 0; i--) {
|
|
109
|
+
if (entries[i].messageId) {
|
|
110
|
+
latestMessageId = entries[i].messageId;
|
|
111
|
+
break;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
const merged = {
|
|
115
|
+
...last.rest,
|
|
116
|
+
content: contents.join('\n'),
|
|
117
|
+
images: allImages.length > 0 ? allImages : undefined,
|
|
118
|
+
mentions: allMentions.length > 0 ? allMentions : undefined,
|
|
119
|
+
replyContext: last.replyContext,
|
|
120
|
+
messageId: latestMessageId,
|
|
121
|
+
sourceMessageIds: sourceMessageIds.length > 0 ? sourceMessageIds : undefined,
|
|
122
|
+
};
|
|
123
|
+
const resolves = entries.map(e => e.resolve);
|
|
124
|
+
const rejects = entries.map(e => e.reject);
|
|
125
|
+
enqueue(merged).then(() => resolves.forEach(r => r()), (e) => rejects.forEach(r => r(e)));
|
|
126
|
+
}
|
|
127
|
+
/** 当前挂起的 key 数量(用于测试/调试) */
|
|
128
|
+
get pendingCount() {
|
|
129
|
+
return this.pending.size;
|
|
130
|
+
}
|
|
131
|
+
/** 清理所有挂起的 timer(用于 shutdown) */
|
|
132
|
+
dispose() {
|
|
133
|
+
for (const win of this.pending.values()) {
|
|
134
|
+
clearTimeout(win.timer);
|
|
135
|
+
clearTimeout(win.maxWaitTimer);
|
|
136
|
+
}
|
|
137
|
+
this.pending.clear();
|
|
138
|
+
}
|
|
139
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 事件流空闲监控器
|
|
3
|
+
* 记录事件流执行上下文,分级响应空闲状态
|
|
4
|
+
*/
|
|
5
|
+
// 分级倍率
|
|
6
|
+
const NOTIFY_MULTIPLIER = 1; // 1× idleMs
|
|
7
|
+
const WARN_MULTIPLIER = 2.5; // 2.5× idleMs
|
|
8
|
+
const KILL_MULTIPLIER = 5; // 5× idleMs
|
|
9
|
+
export class StreamIdleMonitor {
|
|
10
|
+
state;
|
|
11
|
+
triggeredLevels = new Set();
|
|
12
|
+
idleMs;
|
|
13
|
+
paused = false;
|
|
14
|
+
constructor(idleMs) {
|
|
15
|
+
this.idleMs = idleMs;
|
|
16
|
+
this.state = {
|
|
17
|
+
lastEventType: '',
|
|
18
|
+
lastEventTime: Date.now(),
|
|
19
|
+
lastToolName: undefined,
|
|
20
|
+
lastToolStartTime: undefined,
|
|
21
|
+
totalEvents: 0,
|
|
22
|
+
totalToolCalls: 0,
|
|
23
|
+
hasReceivedText: false,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* 暂停空闲计时(等待用户交互期间调用,如权限确认 / AskUserQuestion / PlanMode)。
|
|
28
|
+
* 暂停期间 check() 始终返回 null,等待时长不计入 idle。
|
|
29
|
+
*/
|
|
30
|
+
pause() {
|
|
31
|
+
this.paused = true;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* 恢复空闲计时。从恢复时刻重新起算 idle,并清空已触发级别——
|
|
35
|
+
* 用户应答后是一次全新的执行周期,不应继承等待前的 idle 状态。
|
|
36
|
+
*/
|
|
37
|
+
resume() {
|
|
38
|
+
if (!this.paused)
|
|
39
|
+
return;
|
|
40
|
+
this.paused = false;
|
|
41
|
+
this.state.lastEventTime = Date.now();
|
|
42
|
+
this.triggeredLevels.clear();
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* 记录 SDK 事件,更新状态并重置空闲计时
|
|
46
|
+
*/
|
|
47
|
+
recordEvent(type, toolName) {
|
|
48
|
+
this.state.lastEventType = type;
|
|
49
|
+
this.state.lastEventTime = Date.now();
|
|
50
|
+
this.state.totalEvents++;
|
|
51
|
+
if (toolName) {
|
|
52
|
+
this.state.lastToolName = toolName;
|
|
53
|
+
this.state.lastToolStartTime = Date.now();
|
|
54
|
+
this.state.totalToolCalls++;
|
|
55
|
+
}
|
|
56
|
+
if (type === 'text' || type === 'text_delta' || type === 'complete' || type === 'result') {
|
|
57
|
+
this.state.hasReceivedText = true;
|
|
58
|
+
}
|
|
59
|
+
// 收到新事件,重置已触发级别
|
|
60
|
+
this.triggeredLevels.clear();
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* 检查空闲状态,返回 null(未空闲)或分级结果
|
|
64
|
+
*/
|
|
65
|
+
check() {
|
|
66
|
+
if (this.paused)
|
|
67
|
+
return null;
|
|
68
|
+
const now = Date.now();
|
|
69
|
+
const idleDuration = now - this.state.lastEventTime;
|
|
70
|
+
const notifyThreshold = this.idleMs * NOTIFY_MULTIPLIER;
|
|
71
|
+
const warnThreshold = this.idleMs * WARN_MULTIPLIER;
|
|
72
|
+
const killThreshold = this.idleMs * KILL_MULTIPLIER;
|
|
73
|
+
// Check from lowest to highest — return first untriggered level
|
|
74
|
+
if (idleDuration >= notifyThreshold && !this.triggeredLevels.has('notify')) {
|
|
75
|
+
this.triggeredLevels.add('notify');
|
|
76
|
+
return {
|
|
77
|
+
action: 'notify',
|
|
78
|
+
idleSec: Math.round(idleDuration / 1000),
|
|
79
|
+
state: { ...this.state },
|
|
80
|
+
message: this.buildMessage('notify', idleDuration),
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
if (idleDuration >= warnThreshold && !this.triggeredLevels.has('warn')) {
|
|
84
|
+
this.triggeredLevels.add('warn');
|
|
85
|
+
return {
|
|
86
|
+
action: 'warn',
|
|
87
|
+
idleSec: Math.round(idleDuration / 1000),
|
|
88
|
+
state: { ...this.state },
|
|
89
|
+
message: this.buildMessage('warn', idleDuration),
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
if (idleDuration >= killThreshold && !this.triggeredLevels.has('kill')) {
|
|
93
|
+
this.triggeredLevels.add('kill');
|
|
94
|
+
return {
|
|
95
|
+
action: 'kill',
|
|
96
|
+
idleSec: Math.round(idleDuration / 1000),
|
|
97
|
+
state: { ...this.state },
|
|
98
|
+
message: this.buildMessage('kill', idleDuration),
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* 获取当前执行状态快照
|
|
105
|
+
*/
|
|
106
|
+
getState() {
|
|
107
|
+
return { ...this.state };
|
|
108
|
+
}
|
|
109
|
+
buildMessage(action, idleDuration) {
|
|
110
|
+
const idleSec = Math.round(idleDuration / 1000);
|
|
111
|
+
const lastToolInfo = this.state.lastToolName
|
|
112
|
+
? `🔧 ${this.state.lastToolName}`
|
|
113
|
+
: '无工具调用';
|
|
114
|
+
const timeSinceLastEvent = idleSec;
|
|
115
|
+
if (action === 'kill') {
|
|
116
|
+
return `🛑 执行超时(${idleSec}秒无响应),已自动中断\n\n执行状态:\n- 已处理 ${this.state.totalEvents} 个事件,${this.state.totalToolCalls} 次工具调用\n- 最后活动:${lastToolInfo}(${timeSinceLastEvent}秒前)`;
|
|
117
|
+
}
|
|
118
|
+
if (action === 'warn') {
|
|
119
|
+
return `⚠️ 执行监控:已 ${idleSec} 秒无输出\n\n执行状态:\n- 已处理 ${this.state.totalEvents} 个事件,${this.state.totalToolCalls} 次工具调用\n- 最后活动:${lastToolInfo}(${timeSinceLastEvent}秒前)\n\n如果持续无响应,将在 ${Math.round((this.idleMs * KILL_MULTIPLIER - idleDuration) / 1000)} 秒后自动中断。\n使用 /stop 可立即中断当前任务。`;
|
|
120
|
+
}
|
|
121
|
+
// notify
|
|
122
|
+
return `🔍 执行监控:已 ${idleSec} 秒无输出\n\n执行状态:\n- 已处理 ${this.state.totalEvents} 个事件,${this.state.totalToolCalls} 次工具调用\n- 最后活动:${lastToolInfo}(${timeSinceLastEvent}秒前)\n\n仍在运行中。使用 /stop 可中断当前任务。`;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* config-scope: model/effort 的多作用域读写,以及角色 permissionMode 解析。
|
|
3
|
+
*
|
|
4
|
+
* 所有参数统一在 config.json。
|
|
5
|
+
* 存储经 ConfigManager(全项目唯一合并实现点)。本模块是面向 CLI / 运行时的薄封装:
|
|
6
|
+
* 把"作用域 + baseagent"语义映射到 config 链的读写。
|
|
7
|
+
*
|
|
8
|
+
* model/effort 作用域(越具体越优先):关系 > 角色 > agent。
|
|
9
|
+
* agent agents/<self>/config.json → baseagents.<ba>.{model,effort}
|
|
10
|
+
* 角色 agents/<self>/config.json 的 roles.definitions 块 → model 白名单/默认值
|
|
11
|
+
* 关系 agents/<self>/relations/<peerKey>/config.json → baseagents
|
|
12
|
+
* global(历史保留):全局 model 作用域已退场,只读 agent 兜底。
|
|
13
|
+
*
|
|
14
|
+
* 改任一作用域后,对应范围所有会话的下一条消息即时生效(运行时每条消息解析,不缓存)。
|
|
15
|
+
*
|
|
16
|
+
* 详见 docs/config/
|
|
17
|
+
*/
|
|
18
|
+
import { formatPeerKey, parsePeerKey } from '../relation/peer-identity.js';
|
|
19
|
+
import { ConfigTarget, read, write, ensureFile, } from '../../config/config-manager.js';
|
|
20
|
+
import { resolveRuntimePermissionMode } from '../role/runtime-policy.js';
|
|
21
|
+
export class ModelScopeError extends Error {
|
|
22
|
+
code;
|
|
23
|
+
constructor(code, message) {
|
|
24
|
+
super(message);
|
|
25
|
+
this.code = code;
|
|
26
|
+
this.name = 'ModelScopeError';
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
// ── peer 归一化 ────────────────────────────────────────────────────────
|
|
30
|
+
/**
|
|
31
|
+
* 把 `--peer` 入参归一为规范 peerKey。
|
|
32
|
+
* - `channelType#channelId`(统一 round-trip 重编码)
|
|
33
|
+
* - 裸 aid(无 '#')→ 视为 `aun#<aid>`
|
|
34
|
+
*/
|
|
35
|
+
export function normalizePeer(input) {
|
|
36
|
+
const raw = (input || '').trim();
|
|
37
|
+
if (!raw)
|
|
38
|
+
throw new ModelScopeError('INVALID_PEER', '--peer 不能为空');
|
|
39
|
+
if (raw.includes('#')) {
|
|
40
|
+
let parsed;
|
|
41
|
+
try {
|
|
42
|
+
parsed = parsePeerKey(raw);
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
throw new ModelScopeError('INVALID_PEER', `无法解析 --peer: ${raw}`);
|
|
46
|
+
}
|
|
47
|
+
if (!parsed.channelType || !parsed.channelId) {
|
|
48
|
+
throw new ModelScopeError('INVALID_PEER', `无法解析 --peer: ${raw}`);
|
|
49
|
+
}
|
|
50
|
+
return formatPeerKey(parsed.channelType, parsed.channelId);
|
|
51
|
+
}
|
|
52
|
+
return formatPeerKey('aun', raw);
|
|
53
|
+
}
|
|
54
|
+
// ── 作用域判定(CLI 写入目标)─────────────────────────────────────────────
|
|
55
|
+
/**
|
|
56
|
+
* 由选择器判定**写入目标**作用域。依赖:peerKey/role 须配 self。
|
|
57
|
+
*/
|
|
58
|
+
export function determineScope(sel) {
|
|
59
|
+
const hasSelf = !!sel.self;
|
|
60
|
+
if (sel.peerKey && !hasSelf)
|
|
61
|
+
throw new ModelScopeError('PEER_WITHOUT_SELF', '--peer 必须配合 --self 使用');
|
|
62
|
+
if (sel.role && !hasSelf)
|
|
63
|
+
throw new ModelScopeError('ROLE_WITHOUT_SELF', '--role 必须配合 --self 使用');
|
|
64
|
+
if (sel.peerKey)
|
|
65
|
+
return 'relation';
|
|
66
|
+
if (sel.role)
|
|
67
|
+
return 'role';
|
|
68
|
+
if (hasSelf)
|
|
69
|
+
return 'agent';
|
|
70
|
+
return 'global';
|
|
71
|
+
}
|
|
72
|
+
// ── baseagent 解析 ───────────────────────────────────────────────────────
|
|
73
|
+
/** 取本端的活跃 baseagent(从 agent/config 读 active_baseagent);缺省 'claude'。 */
|
|
74
|
+
export function activeBaseagent(self) {
|
|
75
|
+
try {
|
|
76
|
+
if (self) {
|
|
77
|
+
const b = read(ConfigTarget.Agent, { self }, { cache: true });
|
|
78
|
+
if (b?.active_baseagent)
|
|
79
|
+
return b.active_baseagent;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
catch { /* fall through */ }
|
|
83
|
+
return 'claude';
|
|
84
|
+
}
|
|
85
|
+
/** codex 的推理强度字段名是 reasoning,其余是 effort。 */
|
|
86
|
+
function effortField(ba) {
|
|
87
|
+
return ba === 'codex' ? 'reasoning' : 'effort';
|
|
88
|
+
}
|
|
89
|
+
// ── 写:单作用域 ──────────────────────────────────────────────────────
|
|
90
|
+
/** 写入指定作用域。patch 中 undefined 不动;显式 null 删除字段。 */
|
|
91
|
+
export function writeScope(scope, sel, ba, patch) {
|
|
92
|
+
const ef = effortField(ba);
|
|
93
|
+
if (scope === 'global') {
|
|
94
|
+
throw new ModelScopeError('NO_GLOBAL_SCOPE', '全局 model 作用域已退场:请用 --self 逐 agent 设置');
|
|
95
|
+
}
|
|
96
|
+
if (scope === 'agent') {
|
|
97
|
+
requireSelf(sel);
|
|
98
|
+
const target = ConfigTarget.Agent;
|
|
99
|
+
const cur = read(target, { self: sel.self }) || {};
|
|
100
|
+
applyBaPatch(cur, ba, ef, patch);
|
|
101
|
+
ensureFile(target, { self: sel.self });
|
|
102
|
+
write(target, cur, { self: sel.self });
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
if (scope === 'role') {
|
|
106
|
+
throw new ModelScopeError('ROLE_POLICY_MANAGED', '角色模型策略只能通过角色策略编辑器修改');
|
|
107
|
+
}
|
|
108
|
+
// relation
|
|
109
|
+
requirePeer(sel);
|
|
110
|
+
const target = ConfigTarget.Relation;
|
|
111
|
+
const selr = { self: sel.self, peerKey: sel.peerKey };
|
|
112
|
+
const cur = read(target, selr) || {};
|
|
113
|
+
applyBaPatch(cur, ba, ef, patch);
|
|
114
|
+
ensureFile(target, selr);
|
|
115
|
+
write(target, cur, selr);
|
|
116
|
+
}
|
|
117
|
+
function applyBaPatch(cfg, ba, ef, patch) {
|
|
118
|
+
cfg.baseagents = cfg.baseagents || {};
|
|
119
|
+
applyBaPatchTo(cfg.baseagents, ba, ef, patch);
|
|
120
|
+
}
|
|
121
|
+
function applyBaPatchTo(block, ba, ef, patch) {
|
|
122
|
+
block[ba] = block[ba] || {};
|
|
123
|
+
const sub = block[ba];
|
|
124
|
+
if (patch.model === null)
|
|
125
|
+
delete sub.model;
|
|
126
|
+
else if (patch.model !== undefined)
|
|
127
|
+
sub.model = patch.model;
|
|
128
|
+
if (patch.effort === null)
|
|
129
|
+
delete sub[ef];
|
|
130
|
+
else if (patch.effort !== undefined)
|
|
131
|
+
sub[ef] = patch.effort;
|
|
132
|
+
}
|
|
133
|
+
// ── 清除:单作用域 ────────────────────────────────────────────────────
|
|
134
|
+
/** 清除指定作用域的 model+effort。 */
|
|
135
|
+
export function clearScope(scope, sel, ba) {
|
|
136
|
+
if (scope === 'global')
|
|
137
|
+
return;
|
|
138
|
+
writeScope(scope, sel, ba, { model: null, effort: null });
|
|
139
|
+
}
|
|
140
|
+
// ── permissionMode 解析 ────────────────────────────────────────────────────
|
|
141
|
+
const FALLBACK_PERMISSION_MODE = 'readonly';
|
|
142
|
+
/**
|
|
143
|
+
* 解析实际生效的 permissionMode。不抛出——运行时 per-message 调用。
|
|
144
|
+
* permissionMode 只来自角色定义;缺失或未知角色安全回退 readonly。
|
|
145
|
+
*/
|
|
146
|
+
export function resolvePermissionMode(sel) {
|
|
147
|
+
try {
|
|
148
|
+
return resolveRuntimePermissionMode({ selfAid: sel.self, role: sel.role }).effectiveValue;
|
|
149
|
+
}
|
|
150
|
+
catch {
|
|
151
|
+
return FALLBACK_PERMISSION_MODE;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
// ── helpers ──────────────────────────────────────────────────────────────────
|
|
155
|
+
function requireSelf(sel) {
|
|
156
|
+
if (!sel.self)
|
|
157
|
+
throw new ModelScopeError('AGENT_NOT_FOUND', 'agent 作用域需要 --self');
|
|
158
|
+
}
|
|
159
|
+
function requirePeer(sel) {
|
|
160
|
+
if (!sel.self || !sel.peerKey)
|
|
161
|
+
throw new ModelScopeError('PEER_WITHOUT_SELF', '关系作用域需要 --self + --peer');
|
|
162
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* field-scope: 顶层配置字段的多作用域读写(通用框架)。
|
|
3
|
+
*
|
|
4
|
+
* 与 config-scope.ts(model 专用,处理 baseagents/roles 嵌套结构)互补:
|
|
5
|
+
* - config-scope:model/effort → config.json / relation config.json 的嵌套结构
|
|
6
|
+
* - field-scope:顶层字段(如 responseMode / responseModeParams)→ config.json(H 链)
|
|
7
|
+
*
|
|
8
|
+
* 复用 config-scope 的作用域判定与 peer 归一化(同一套语义,不重复造)。
|
|
9
|
+
*
|
|
10
|
+
* 作用域(越具体越优先):关系 > agent > 全局(defaults)。
|
|
11
|
+
* defaults agents/defaults.json
|
|
12
|
+
* agent agents/<self>/config.json
|
|
13
|
+
* relation agents/<self>/relations/<peerKey>/config.json
|
|
14
|
+
*
|
|
15
|
+
* 读写单个顶层字段:read-modify-write(整文件读出 → 改字段 → 写回,带 schema 校验)。
|
|
16
|
+
* 改任一作用域后,对应范围所有会话的下一条消息即时生效(运行时每条消息解析)。
|
|
17
|
+
*/
|
|
18
|
+
import { ConfigTarget, read, write } from '../../config/config-manager.js';
|
|
19
|
+
import { normalizePeer, determineScope, ModelScopeError } from './config-scope.js';
|
|
20
|
+
/** 把 ScopeSelector 映射到 FieldScope;角色策略必须走专用编辑器。 */
|
|
21
|
+
export function determineFieldScope(sel) {
|
|
22
|
+
const scope = determineScope(sel);
|
|
23
|
+
if (scope === 'global')
|
|
24
|
+
return 'defaults';
|
|
25
|
+
if (scope === 'role')
|
|
26
|
+
throw new ModelScopeError('ROLE_POLICY_MANAGED', '角色策略只能通过角色策略编辑器修改');
|
|
27
|
+
return scope; // 'agent' | 'relation'
|
|
28
|
+
}
|
|
29
|
+
/** scope → ConfigTarget */
|
|
30
|
+
function targetFor(scope) {
|
|
31
|
+
switch (scope) {
|
|
32
|
+
case 'defaults': return ConfigTarget.Defaults;
|
|
33
|
+
case 'agent': return ConfigTarget.Agent;
|
|
34
|
+
case 'relation': return ConfigTarget.Relation;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* 读取指定作用域的某个顶层字段(未设返回 undefined)。不抛出。
|
|
39
|
+
*/
|
|
40
|
+
export function readField(scope, sel, field) {
|
|
41
|
+
try {
|
|
42
|
+
const target = targetFor(scope);
|
|
43
|
+
const cfg = read(target, sel, { cache: true });
|
|
44
|
+
return cfg ? cfg[field] : undefined;
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
return undefined;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* 写入指定作用域的某个顶层字段(read-modify-write,带 schema 校验)。
|
|
52
|
+
* value 为 undefined 时删除该字段。
|
|
53
|
+
*/
|
|
54
|
+
export function writeField(scope, sel, field, value) {
|
|
55
|
+
if (scope === 'agent' && !sel.self) {
|
|
56
|
+
throw new ModelScopeError('SELF_REQUIRED', 'agent 作用域必须提供 --self');
|
|
57
|
+
}
|
|
58
|
+
if (scope === 'relation' && (!sel.self || !sel.peerKey)) {
|
|
59
|
+
throw new ModelScopeError('PEER_WITHOUT_SELF', 'relation 作用域必须提供 --self 和 --peer');
|
|
60
|
+
}
|
|
61
|
+
const target = targetFor(scope);
|
|
62
|
+
const cur = (read(target, sel) || {});
|
|
63
|
+
if (value === undefined) {
|
|
64
|
+
delete cur[field];
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
cur[field] = value;
|
|
68
|
+
}
|
|
69
|
+
write(target, cur, sel);
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* 清除指定作用域的某个顶层字段(等价于 writeField(..., undefined))。
|
|
73
|
+
*/
|
|
74
|
+
export function clearField(scope, sel, field) {
|
|
75
|
+
writeField(scope, sel, field, undefined);
|
|
76
|
+
}
|
|
77
|
+
// re-export 复用 config-scope 的 peer 归一化(同一套语义)
|
|
78
|
+
export { normalizePeer, ModelScopeError };
|