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,100 @@
|
|
|
1
|
+
import { formatTimestamp } from './session-fs-store.js';
|
|
2
|
+
import { formatSessionKey, DEFAULT_THREAD_ID } from './session-key.js';
|
|
3
|
+
export function sessionToFile(session) {
|
|
4
|
+
const metadata = {};
|
|
5
|
+
if (session.metadata) {
|
|
6
|
+
if (session.metadata.peerId)
|
|
7
|
+
metadata.peerId = session.metadata.peerId;
|
|
8
|
+
if (session.metadata.peerName)
|
|
9
|
+
metadata.peerName = session.metadata.peerName;
|
|
10
|
+
if (session.metadata.peerType)
|
|
11
|
+
metadata.peerType = session.metadata.peerType;
|
|
12
|
+
if (session.metadata.groupId)
|
|
13
|
+
metadata.groupId = session.metadata.groupId;
|
|
14
|
+
if (session.metadata.replyContext)
|
|
15
|
+
metadata.replyContext = session.metadata.replyContext;
|
|
16
|
+
if (session.metadata.agentSessions)
|
|
17
|
+
metadata.agentSessions = session.metadata.agentSessions;
|
|
18
|
+
if (session.metadata.resumeAt)
|
|
19
|
+
metadata.resumeAt = session.metadata.resumeAt;
|
|
20
|
+
for (const [k, v] of Object.entries(session.metadata)) {
|
|
21
|
+
if (['isActive', 'channelKey', 'channelName', 'permissionMode', 'peerId', 'peerName', 'peerType', 'groupId', 'replyContext', 'agentSessions', 'resumeAt'].includes(k))
|
|
22
|
+
continue;
|
|
23
|
+
if (v !== undefined)
|
|
24
|
+
metadata[k] = v;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
const now = session.updatedAt || Date.now();
|
|
28
|
+
const channelType = session.channelType || session.channel;
|
|
29
|
+
const threadId = session.threadId || DEFAULT_THREAD_ID;
|
|
30
|
+
if (!session.baseagent) {
|
|
31
|
+
throw new Error(`[session-mapper] sessionToFile: session.baseagent is empty for session ${session.id}`);
|
|
32
|
+
}
|
|
33
|
+
return {
|
|
34
|
+
id: session.id,
|
|
35
|
+
channel: session.channel,
|
|
36
|
+
channelType,
|
|
37
|
+
channelId: session.channelId,
|
|
38
|
+
sessionKey: formatSessionKey(channelType, session.channelId, threadId),
|
|
39
|
+
selfAID: session.selfAID,
|
|
40
|
+
baseagent: session.baseagent,
|
|
41
|
+
threadId: session.threadId || '',
|
|
42
|
+
chatType: session.chatType || 'private',
|
|
43
|
+
projectPath: session.projectPath,
|
|
44
|
+
agentSessionId: session.agentSessionId ?? null,
|
|
45
|
+
name: session.name ?? null,
|
|
46
|
+
activeTask: session.processingState ?? null,
|
|
47
|
+
metadata,
|
|
48
|
+
createdAt: session.createdAt,
|
|
49
|
+
createdAtStr: formatTimestamp(session.createdAt),
|
|
50
|
+
updatedAt: now,
|
|
51
|
+
updatedAtStr: formatTimestamp(now),
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
export function fileToSession(file) {
|
|
55
|
+
const metadata = {};
|
|
56
|
+
// 兼容旧字段名 agentType → baseagent
|
|
57
|
+
const baseagent = file.baseagent || file.agentType;
|
|
58
|
+
if (!baseagent) {
|
|
59
|
+
throw new Error(`[session-mapper] fileToSession: baseagent is empty for session ${file.id}`);
|
|
60
|
+
}
|
|
61
|
+
if (file.metadata.peerId)
|
|
62
|
+
metadata.peerId = file.metadata.peerId;
|
|
63
|
+
if (file.metadata.peerName)
|
|
64
|
+
metadata.peerName = file.metadata.peerName;
|
|
65
|
+
if (file.metadata.peerType)
|
|
66
|
+
metadata.peerType = file.metadata.peerType;
|
|
67
|
+
if (file.metadata.groupId)
|
|
68
|
+
metadata.groupId = file.metadata.groupId;
|
|
69
|
+
if (file.metadata.replyContext)
|
|
70
|
+
metadata.replyContext = file.metadata.replyContext;
|
|
71
|
+
if (file.metadata.agentSessions)
|
|
72
|
+
metadata.agentSessions = file.metadata.agentSessions;
|
|
73
|
+
if (file.metadata.resumeAt)
|
|
74
|
+
metadata.resumeAt = file.metadata.resumeAt;
|
|
75
|
+
// permissionMode 不再从文件还原到 metadata(运行时 per-message 解析)
|
|
76
|
+
for (const [k, v] of Object.entries(file.metadata)) {
|
|
77
|
+
if (['peerId', 'peerName', 'peerType', 'groupId', 'replyContext', 'agentSessions', 'resumeAt', 'permissionMode'].includes(k))
|
|
78
|
+
continue;
|
|
79
|
+
if (v !== undefined)
|
|
80
|
+
metadata[k] = v;
|
|
81
|
+
}
|
|
82
|
+
return {
|
|
83
|
+
id: file.id,
|
|
84
|
+
channel: file.channel,
|
|
85
|
+
channelType: file.channelType,
|
|
86
|
+
channelId: file.channelId,
|
|
87
|
+
sessionKey: file.sessionKey || formatSessionKey(file.channelType, file.channelId, file.threadId || DEFAULT_THREAD_ID),
|
|
88
|
+
selfAID: file.selfAID,
|
|
89
|
+
baseagent,
|
|
90
|
+
threadId: file.threadId,
|
|
91
|
+
chatType: file.chatType,
|
|
92
|
+
projectPath: file.projectPath,
|
|
93
|
+
agentSessionId: file.agentSessionId ?? undefined,
|
|
94
|
+
name: file.name ?? undefined,
|
|
95
|
+
processingState: file.activeTask ?? undefined,
|
|
96
|
+
metadata,
|
|
97
|
+
createdAt: file.createdAt,
|
|
98
|
+
updatedAt: file.updatedAt,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
import { resolveEffective } from '../../config/config-manager.js';
|
|
2
|
+
import { formatPeerKey } from '../relation/peer-identity.js';
|
|
3
|
+
import { logger } from '../../utils/logger.js';
|
|
4
|
+
import { isTransientProtocolMessage, messageLogPath } from '../message/message-log.js';
|
|
5
|
+
import { readAllJsonlLines } from './session-fs-store.js';
|
|
6
|
+
const DEFAULT_AFTER_HOURS = 24;
|
|
7
|
+
const DEFAULT_EFFORT = 'low';
|
|
8
|
+
const DEFAULT_FALLBACK = 'continue';
|
|
9
|
+
const MAX_CONTEXT_MESSAGES = 40;
|
|
10
|
+
const MAX_CONTEXT_CHARS = 16_000;
|
|
11
|
+
const MAX_MESSAGE_CHARS = 2_000;
|
|
12
|
+
const MODEL_TIMEOUT_MS = 10_000;
|
|
13
|
+
const RECENT_DECISION_TTL_MS = 30_000;
|
|
14
|
+
const NEW_CONFIDENCE_THRESHOLD = 0.85;
|
|
15
|
+
const JUDGE_BASEAGENT = 'claude';
|
|
16
|
+
const EXPLICIT_NEW_RE = /^(?:新话题|换个话题|重新开始|新开会话|新会话)[::,,;;。!!\n]/;
|
|
17
|
+
const EXPLICIT_CONTINUE_RE = /^(?:继续上次|延续上次|继续|接着)[::,,;;。!!\n]/;
|
|
18
|
+
const NON_CONVERSATION_TYPES = new Set([
|
|
19
|
+
'command',
|
|
20
|
+
'thought',
|
|
21
|
+
'status',
|
|
22
|
+
'event',
|
|
23
|
+
'tool_call',
|
|
24
|
+
'tool_result',
|
|
25
|
+
]);
|
|
26
|
+
export function classifyExplicitSessionRenewSignal(content) {
|
|
27
|
+
const normalized = content.trimStart();
|
|
28
|
+
if (EXPLICIT_NEW_RE.test(normalized))
|
|
29
|
+
return 'new';
|
|
30
|
+
if (EXPLICIT_CONTINUE_RE.test(normalized))
|
|
31
|
+
return 'continue';
|
|
32
|
+
return undefined;
|
|
33
|
+
}
|
|
34
|
+
export function isSessionRenewConversationEntry(entry, sessionId) {
|
|
35
|
+
return entry.sessionId === sessionId
|
|
36
|
+
&& !isTransientProtocolMessage(entry)
|
|
37
|
+
&& !NON_CONVERSATION_TYPES.has(entry.msgType)
|
|
38
|
+
&& entry.content.trim().length > 0;
|
|
39
|
+
}
|
|
40
|
+
export function selectSessionRenewContext(entries) {
|
|
41
|
+
if (entries.length <= MAX_CONTEXT_MESSAGES)
|
|
42
|
+
return trimContextByChars(entries);
|
|
43
|
+
const selected = [entries[0], ...entries.slice(-(MAX_CONTEXT_MESSAGES - 1))];
|
|
44
|
+
return trimContextByChars(selected);
|
|
45
|
+
}
|
|
46
|
+
export function selectLatestHaikuModel(models) {
|
|
47
|
+
let selected;
|
|
48
|
+
for (const rawModel of models) {
|
|
49
|
+
const id = rawModel.trim();
|
|
50
|
+
if (!id)
|
|
51
|
+
continue;
|
|
52
|
+
const leaf = id.toLowerCase().split('/').at(-1) ?? '';
|
|
53
|
+
const tokens = leaf.split('-');
|
|
54
|
+
const haikuIndex = tokens.indexOf('haiku');
|
|
55
|
+
if (haikuIndex < 0)
|
|
56
|
+
continue;
|
|
57
|
+
const versionTokens = haikuIndex <= 1
|
|
58
|
+
? tokens.slice(haikuIndex + 1)
|
|
59
|
+
: tokens.slice(1, haikuIndex);
|
|
60
|
+
const versions = versionTokens
|
|
61
|
+
.filter(token => /^\d{1,2}$/.test(token))
|
|
62
|
+
.map(Number);
|
|
63
|
+
const dates = tokens
|
|
64
|
+
.filter(token => /^\d{8}$/.test(token))
|
|
65
|
+
.map(Number);
|
|
66
|
+
const candidate = {
|
|
67
|
+
id,
|
|
68
|
+
major: versions[0] ?? 0,
|
|
69
|
+
minor: versions[1] ?? 0,
|
|
70
|
+
date: dates.length > 0 ? Math.max(...dates) : 0,
|
|
71
|
+
};
|
|
72
|
+
if (!selected
|
|
73
|
+
|| candidate.major > selected.major
|
|
74
|
+
|| (candidate.major === selected.major && candidate.minor > selected.minor)
|
|
75
|
+
|| (candidate.major === selected.major && candidate.minor === selected.minor && candidate.date > selected.date)) {
|
|
76
|
+
selected = candidate;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return selected?.id;
|
|
80
|
+
}
|
|
81
|
+
function trimContextByChars(entries) {
|
|
82
|
+
if (entries.length === 0)
|
|
83
|
+
return [];
|
|
84
|
+
const first = entries[0];
|
|
85
|
+
const tail = [];
|
|
86
|
+
let remaining = MAX_CONTEXT_CHARS - Math.min(first.content.length, MAX_MESSAGE_CHARS);
|
|
87
|
+
for (let index = entries.length - 1; index >= 1 && remaining > 0; index--) {
|
|
88
|
+
const entry = entries[index];
|
|
89
|
+
const length = Math.min(entry.content.length, MAX_MESSAGE_CHARS);
|
|
90
|
+
if (tail.length > 0 && length > remaining)
|
|
91
|
+
break;
|
|
92
|
+
tail.push(entry);
|
|
93
|
+
remaining -= length;
|
|
94
|
+
}
|
|
95
|
+
return [first, ...tail.reverse()];
|
|
96
|
+
}
|
|
97
|
+
export class SessionRenewService {
|
|
98
|
+
sessionManager;
|
|
99
|
+
inferenceProviderResolver;
|
|
100
|
+
locks = new Map();
|
|
101
|
+
recentDecisions = new Map();
|
|
102
|
+
now;
|
|
103
|
+
configResolver;
|
|
104
|
+
constructor(sessionManager, inferenceProviderResolver, options = {}) {
|
|
105
|
+
this.sessionManager = sessionManager;
|
|
106
|
+
this.inferenceProviderResolver = inferenceProviderResolver;
|
|
107
|
+
this.now = options.now ?? (() => Date.now());
|
|
108
|
+
this.configResolver = options.resolveConfig;
|
|
109
|
+
}
|
|
110
|
+
async resolve(request) {
|
|
111
|
+
const config = this.resolveConfig(request);
|
|
112
|
+
if (!config || config.enabled !== true)
|
|
113
|
+
return { session: request.session, renewed: false };
|
|
114
|
+
const routeKey = this.routeKey(request);
|
|
115
|
+
return await this.withLock(routeKey, async () => {
|
|
116
|
+
const cached = this.recentDecisions.get(routeKey);
|
|
117
|
+
if (cached && cached.candidateSessionId === request.session.id && cached.expiresAt > this.now()) {
|
|
118
|
+
const selected = cached.selectedSessionId === request.session.id
|
|
119
|
+
? request.session
|
|
120
|
+
: await this.sessionManager.getSessionById(cached.selectedSessionId);
|
|
121
|
+
if (selected) {
|
|
122
|
+
selected.identity = request.session.identity;
|
|
123
|
+
return {
|
|
124
|
+
session: selected,
|
|
125
|
+
renewed: cached.decision === 'new',
|
|
126
|
+
decision: cached.decision,
|
|
127
|
+
source: cached.source,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
const active = this.sessionManager.getActiveSessionSync(request.channelName, request.channelId, request.channelType, request.selfAid);
|
|
132
|
+
if (active && active.id !== request.session.id) {
|
|
133
|
+
active.identity = request.session.identity;
|
|
134
|
+
return { session: active, renewed: true, decision: 'new', source: 'fallback' };
|
|
135
|
+
}
|
|
136
|
+
return await this.resolveLocked(routeKey, request, config);
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
async resolveLocked(routeKey, request, config) {
|
|
140
|
+
if (request.session.threadId || request.session.processingState)
|
|
141
|
+
return { session: request.session, renewed: false };
|
|
142
|
+
const afterHours = validPositiveNumber(config.after_hours) ?? DEFAULT_AFTER_HOURS;
|
|
143
|
+
const entries = this.readConversationEntries(request.session);
|
|
144
|
+
const last = entries[entries.length - 1];
|
|
145
|
+
if (!last) {
|
|
146
|
+
if (request.isNewSession)
|
|
147
|
+
return { session: request.session, renewed: false };
|
|
148
|
+
const idleMs = this.now() - request.session.updatedAt;
|
|
149
|
+
return await this.finishDecision(routeKey, request, 'new', 'missing_history', idleMs);
|
|
150
|
+
}
|
|
151
|
+
const idleMs = this.now() - last.ts;
|
|
152
|
+
if (idleMs <= afterHours * 60 * 60 * 1000)
|
|
153
|
+
return { session: request.session, renewed: false };
|
|
154
|
+
let source;
|
|
155
|
+
let decision = classifyExplicitSessionRenewSignal(request.content);
|
|
156
|
+
if (decision) {
|
|
157
|
+
source = 'explicit';
|
|
158
|
+
}
|
|
159
|
+
else if (request.replyToMessageId
|
|
160
|
+
&& entries.some(entry => entry.msgId === request.replyToMessageId)) {
|
|
161
|
+
decision = 'continue';
|
|
162
|
+
source = 'reply';
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
let judgeModel;
|
|
166
|
+
try {
|
|
167
|
+
const provider = this.inferenceProviderResolver.getTextInferenceProvider(request.channelName, JUDGE_BASEAGENT, request.selfAid);
|
|
168
|
+
if (!provider)
|
|
169
|
+
throw new Error(`text inference provider unavailable: ${JUDGE_BASEAGENT}`);
|
|
170
|
+
const judged = await this.judgeWithModel(request, config, entries, idleMs, provider);
|
|
171
|
+
judgeModel = judged.model;
|
|
172
|
+
decision = judged.decision === 'new' && judged.confidence < NEW_CONFIDENCE_THRESHOLD
|
|
173
|
+
? 'continue'
|
|
174
|
+
: judged.decision;
|
|
175
|
+
source = 'model';
|
|
176
|
+
}
|
|
177
|
+
catch (error) {
|
|
178
|
+
decision = config.fallback_action ?? DEFAULT_FALLBACK;
|
|
179
|
+
source = 'fallback';
|
|
180
|
+
logger.warn(`[SessionRenew] judge failed, using fallback=${decision}: ${error instanceof Error ? error.message : String(error)}`);
|
|
181
|
+
}
|
|
182
|
+
return await this.finishDecision(routeKey, request, decision, source, idleMs, judgeModel);
|
|
183
|
+
}
|
|
184
|
+
return await this.finishDecision(routeKey, request, decision, source, idleMs);
|
|
185
|
+
}
|
|
186
|
+
async finishDecision(routeKey, request, decision, source, idleMs, judgeModel) {
|
|
187
|
+
const selected = decision === 'new'
|
|
188
|
+
? await this.sessionManager.createRenewedSession(request.session)
|
|
189
|
+
: request.session;
|
|
190
|
+
selected.identity = request.session.identity;
|
|
191
|
+
this.recentDecisions.set(routeKey, {
|
|
192
|
+
candidateSessionId: request.session.id,
|
|
193
|
+
selectedSessionId: selected.id,
|
|
194
|
+
decision,
|
|
195
|
+
source,
|
|
196
|
+
expiresAt: this.now() + RECENT_DECISION_TTL_MS,
|
|
197
|
+
});
|
|
198
|
+
logger.info(`[SessionRenew] session=${request.session.id} selected=${selected.id} decision=${decision} source=${source} idleHours=${(idleMs / 3_600_000).toFixed(2)} baseagent=${judgeModel ? JUDGE_BASEAGENT : 'none'} model=${judgeModel || 'none'}`);
|
|
199
|
+
return { session: selected, renewed: decision === 'new', decision, source };
|
|
200
|
+
}
|
|
201
|
+
resolveConfig(request) {
|
|
202
|
+
if (this.configResolver)
|
|
203
|
+
return this.configResolver(request);
|
|
204
|
+
if (!request.selfAid)
|
|
205
|
+
return undefined;
|
|
206
|
+
const peerKeyId = request.chatType === 'group'
|
|
207
|
+
? (request.groupId || request.channelId)
|
|
208
|
+
: (request.peerId || request.channelId);
|
|
209
|
+
const peerKey = formatPeerKey(request.channelType, peerKeyId);
|
|
210
|
+
return resolveEffective({
|
|
211
|
+
self: request.selfAid,
|
|
212
|
+
peerKey,
|
|
213
|
+
}, { cache: true }).session_renew;
|
|
214
|
+
}
|
|
215
|
+
readConversationEntries(session) {
|
|
216
|
+
const entries = readAllJsonlLines(messageLogPath(this.sessionManager.getChatDir(session)));
|
|
217
|
+
return entries
|
|
218
|
+
.filter(entry => isSessionRenewConversationEntry(entry, session.id))
|
|
219
|
+
.sort((left, right) => left.ts - right.ts);
|
|
220
|
+
}
|
|
221
|
+
async judgeWithModel(request, config, entries, idleMs, provider) {
|
|
222
|
+
const context = selectSessionRenewContext(entries).map(entry => ({
|
|
223
|
+
dir: entry.dir,
|
|
224
|
+
content: entry.content.slice(0, MAX_MESSAGE_CHARS),
|
|
225
|
+
ts: entry.ts,
|
|
226
|
+
msgType: entry.msgType,
|
|
227
|
+
}));
|
|
228
|
+
const prompt = JSON.stringify({
|
|
229
|
+
old_session_messages: context,
|
|
230
|
+
new_message: request.content,
|
|
231
|
+
reply_to_message_id: request.replyToMessageId || null,
|
|
232
|
+
chat_type: request.chatType,
|
|
233
|
+
idle_hours: Number((idleMs / 3_600_000).toFixed(2)),
|
|
234
|
+
});
|
|
235
|
+
const systemPrompt = [
|
|
236
|
+
'You are a session continuity classifier.',
|
|
237
|
+
'Decide whether the new message continues the old conversation or starts an independent conversation.',
|
|
238
|
+
'Use NEW only when the new intent is clearly independent. When uncertain, choose CONTINUE.',
|
|
239
|
+
'Do not call tools. Do not explain your reasoning.',
|
|
240
|
+
'Return exactly one JSON object: {"decision":"continue|new","confidence":0..1,"reason_code":"short_code"}.',
|
|
241
|
+
].join('\n');
|
|
242
|
+
const controller = new AbortController();
|
|
243
|
+
let timer;
|
|
244
|
+
try {
|
|
245
|
+
const operation = (async () => {
|
|
246
|
+
if (!provider.listModels)
|
|
247
|
+
throw new Error('Claude text inference provider does not expose a model list');
|
|
248
|
+
const models = await provider.listModels(controller.signal);
|
|
249
|
+
const model = selectLatestHaikuModel(models);
|
|
250
|
+
if (!model)
|
|
251
|
+
throw new Error('Claude model list contains no Haiku model');
|
|
252
|
+
const text = await provider.completeText({
|
|
253
|
+
model,
|
|
254
|
+
effort: config.effort || DEFAULT_EFFORT,
|
|
255
|
+
system: systemPrompt,
|
|
256
|
+
input: prompt,
|
|
257
|
+
signal: controller.signal,
|
|
258
|
+
});
|
|
259
|
+
return { model, text };
|
|
260
|
+
})();
|
|
261
|
+
const timeout = new Promise((_resolve, reject) => {
|
|
262
|
+
timer = setTimeout(() => {
|
|
263
|
+
controller.abort();
|
|
264
|
+
reject(new Error(`judge timed out after ${MODEL_TIMEOUT_MS}ms`));
|
|
265
|
+
}, MODEL_TIMEOUT_MS);
|
|
266
|
+
});
|
|
267
|
+
const result = await Promise.race([operation, timeout]);
|
|
268
|
+
return { ...parseModelResult(result.text), model: result.model };
|
|
269
|
+
}
|
|
270
|
+
finally {
|
|
271
|
+
if (timer)
|
|
272
|
+
clearTimeout(timer);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
routeKey(request) {
|
|
276
|
+
return [request.channelType, request.selfAid || '', request.channelId, request.session.threadId || ''].join('#');
|
|
277
|
+
}
|
|
278
|
+
async withLock(key, operation) {
|
|
279
|
+
const previous = this.locks.get(key) ?? Promise.resolve();
|
|
280
|
+
let release;
|
|
281
|
+
const current = new Promise(resolve => { release = resolve; });
|
|
282
|
+
const tail = previous.then(() => current);
|
|
283
|
+
this.locks.set(key, tail);
|
|
284
|
+
await previous;
|
|
285
|
+
try {
|
|
286
|
+
return await operation();
|
|
287
|
+
}
|
|
288
|
+
finally {
|
|
289
|
+
release();
|
|
290
|
+
if (this.locks.get(key) === tail)
|
|
291
|
+
this.locks.delete(key);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
function validPositiveNumber(value) {
|
|
296
|
+
return typeof value === 'number' && Number.isFinite(value) && value > 0 ? value : undefined;
|
|
297
|
+
}
|
|
298
|
+
function parseModelResult(text) {
|
|
299
|
+
const match = text.match(/\{[\s\S]*\}/);
|
|
300
|
+
if (!match)
|
|
301
|
+
throw new Error('judge returned no JSON object');
|
|
302
|
+
const parsed = JSON.parse(match[0]);
|
|
303
|
+
if (parsed.decision !== 'continue' && parsed.decision !== 'new') {
|
|
304
|
+
throw new Error('judge returned invalid decision');
|
|
305
|
+
}
|
|
306
|
+
if (typeof parsed.confidence !== 'number' || !Number.isFinite(parsed.confidence) || parsed.confidence < 0 || parsed.confidence > 1) {
|
|
307
|
+
throw new Error('judge returned invalid confidence');
|
|
308
|
+
}
|
|
309
|
+
return {
|
|
310
|
+
decision: parsed.decision,
|
|
311
|
+
confidence: parsed.confidence,
|
|
312
|
+
reasonCode: typeof parsed.reason_code === 'string' ? parsed.reason_code : undefined,
|
|
313
|
+
};
|
|
314
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
const SYSTEM_PROMPT_MARKERS = [
|
|
2
|
+
'--- [SYSTEM_PROMPT_END] ---',
|
|
3
|
+
'<system-reminder>',
|
|
4
|
+
'EvolCore Context Kit documents are shown below.',
|
|
5
|
+
];
|
|
6
|
+
// EvolCore 消息渲染器给每条入站消息加的路由前缀(kits/templates/message-fragments/item.md)。
|
|
7
|
+
// 两种形态:私聊 `‹… → self:AID›`、群聊 `‹… → 群:LABEL · self:AID›`——都以 `‹` 开头、含 `· from:`、
|
|
8
|
+
// 经 `→` 到达 `self:`、以 `›` 收尾。`[^›]*` 保证只吞到第一个 `›`,真人不可能手打这种全角前缀。
|
|
9
|
+
const MESSAGE_ROUTE_PREFIX_RE = /^‹[^›]*·\s*from:[^›]*→[^›]*self:[^›]*›\s*/;
|
|
10
|
+
/**
|
|
11
|
+
* 判断文本是否以 EvolCore 路由前缀开头,即该会话是 EvolCore 自身驱动 base agent 产生的
|
|
12
|
+
* (不是用户在终端直接发起的 CLI 会话)。用于 `/s cli` 列表把"自己的会话"排除在导入候选之外,
|
|
13
|
+
* 语义等同 Claude adapter 的 `entrypoint != sdk-ts`。判的是来源,与 isSyntheticCliPrompt(合成性)正交。
|
|
14
|
+
*/
|
|
15
|
+
export function isEvolcoreRoutedMessage(text) {
|
|
16
|
+
if (typeof text !== 'string')
|
|
17
|
+
return false;
|
|
18
|
+
return MESSAGE_ROUTE_PREFIX_RE.test(text.trimStart());
|
|
19
|
+
}
|
|
20
|
+
const SHARED_SYNTHETIC_CLI_PROMPT_PREFIXES = [
|
|
21
|
+
'Use Bash exactly once to run:',
|
|
22
|
+
'Use the Bash tool now to execute',
|
|
23
|
+
'Review this change for security vulnerabilities',
|
|
24
|
+
'You previously flagged these candidate vulnerabilities',
|
|
25
|
+
];
|
|
26
|
+
const BACKEND_SYNTHETIC_CLI_PROMPT_PREFIXES = {
|
|
27
|
+
claude: [
|
|
28
|
+
// 斜杠命令 / skill 注入的脚手架前缀
|
|
29
|
+
'Base directory for this skill:',
|
|
30
|
+
],
|
|
31
|
+
codex: [],
|
|
32
|
+
gemini: [],
|
|
33
|
+
};
|
|
34
|
+
// Claude Code 斜杠命令 / skill / hook 注入的脚手架,经真实会话数据核对,形态包括:
|
|
35
|
+
// - <local-command-caveat> / <local-command-stdout> / <local-command-stderr> 等 local-command 标签族
|
|
36
|
+
// - <command-message> / <command-name> / <command-args> 等 command 标签族
|
|
37
|
+
// - 整条消息就是一个内置斜杠命令调用(如 "/resume <uuid>"、"/model"、"/clear")
|
|
38
|
+
// 注意:只匹配已知内置命令,避免误伤以绝对路径开头的真人消息(如 "/tmp 清一下")。
|
|
39
|
+
const CLAUDE_SYNTHETIC_TAG_RE = /^<\/?(?:local-command|command)-[a-z]+>/i;
|
|
40
|
+
const CLAUDE_SYNTHETIC_SLASH_RE = /^\/(?:resume|clear|compact|model|init|review|cost|config|status|help|agents?|context|memory|export|hooks?|mcp|ide|login|logout|doctor|bug|release-notes|vim|terminal-setup|add-dir|permissions)(?:\s|$)/i;
|
|
41
|
+
/**
|
|
42
|
+
* 判断 user 文本是否为 CLI / skill / hook 注入的脚手架 prompt。
|
|
43
|
+
* 通用前缀跨后端复用,后端专属前缀 / 模式按 adapter 类型补充。
|
|
44
|
+
*/
|
|
45
|
+
export function isSyntheticCliPrompt(text, backend = 'claude') {
|
|
46
|
+
if (typeof text !== 'string')
|
|
47
|
+
return false;
|
|
48
|
+
const trimmed = text.trimStart();
|
|
49
|
+
if (!trimmed)
|
|
50
|
+
return false;
|
|
51
|
+
const prefixHit = [
|
|
52
|
+
...SHARED_SYNTHETIC_CLI_PROMPT_PREFIXES,
|
|
53
|
+
...BACKEND_SYNTHETIC_CLI_PROMPT_PREFIXES[backend],
|
|
54
|
+
].some(prefix => trimmed.startsWith(prefix));
|
|
55
|
+
if (prefixHit)
|
|
56
|
+
return true;
|
|
57
|
+
if (backend === 'claude') {
|
|
58
|
+
return CLAUDE_SYNTHETIC_TAG_RE.test(trimmed) || CLAUDE_SYNTHETIC_SLASH_RE.test(trimmed);
|
|
59
|
+
}
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
/** 将 adapter 提取的原始 user 文本归一为统一的真人/脚手架摘要。 */
|
|
63
|
+
export function summarizeCliPrompts(messages, backend) {
|
|
64
|
+
let firstSyntheticMessage;
|
|
65
|
+
for (const message of messages) {
|
|
66
|
+
if (typeof message !== 'string' || !message.trim())
|
|
67
|
+
continue;
|
|
68
|
+
const normalized = sanitizeSessionTitle(message);
|
|
69
|
+
if (!normalized)
|
|
70
|
+
continue;
|
|
71
|
+
if (isSyntheticCliPrompt(message, backend)) {
|
|
72
|
+
firstSyntheticMessage ??= normalized;
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
return { firstRealMessage: normalized, firstSyntheticMessage };
|
|
76
|
+
}
|
|
77
|
+
return { firstSyntheticMessage };
|
|
78
|
+
}
|
|
79
|
+
export function resolveCliSessionTitle(explicitTitle, summary, backend) {
|
|
80
|
+
const sanitizedTitle = sanitizeSessionTitle(explicitTitle);
|
|
81
|
+
if (sanitizedTitle && !isSyntheticCliPrompt(explicitTitle, backend))
|
|
82
|
+
return sanitizedTitle;
|
|
83
|
+
return summary.firstRealMessage;
|
|
84
|
+
}
|
|
85
|
+
export function buildCliSessionPreview(messages, backend, options = {}) {
|
|
86
|
+
const summary = summarizeCliPrompts(messages, backend);
|
|
87
|
+
const includeSyntheticFallback = options.includeSyntheticFallback ?? true;
|
|
88
|
+
const maxLength = options.maxLength ?? 50;
|
|
89
|
+
const resolvedTitle = resolveCliSessionTitle(options.explicitTitle, summary, backend)
|
|
90
|
+
|| (includeSyntheticFallback ? summary.firstSyntheticMessage : undefined);
|
|
91
|
+
const title = resolvedTitle
|
|
92
|
+
? resolvedTitle.substring(0, maxLength) + (resolvedTitle.length > maxLength ? '...' : '')
|
|
93
|
+
: null;
|
|
94
|
+
return { title, hasReal: !!summary.firstRealMessage };
|
|
95
|
+
}
|
|
96
|
+
export function sanitizeSessionTitle(title) {
|
|
97
|
+
if (typeof title !== 'string')
|
|
98
|
+
return undefined;
|
|
99
|
+
let cleanTitle = title;
|
|
100
|
+
for (const marker of SYSTEM_PROMPT_MARKERS) {
|
|
101
|
+
const markerIndex = cleanTitle.indexOf(marker);
|
|
102
|
+
if (markerIndex >= 0) {
|
|
103
|
+
cleanTitle = cleanTitle.slice(0, markerIndex);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
cleanTitle = cleanTitle.trim().replace(MESSAGE_ROUTE_PREFIX_RE, '').replace(/\s+/g, ' ');
|
|
107
|
+
if (!cleanTitle)
|
|
108
|
+
return undefined;
|
|
109
|
+
if (/^\.+$/.test(cleanTitle))
|
|
110
|
+
return undefined;
|
|
111
|
+
return cleanTitle.length > 80 ? `${cleanTitle.slice(0, 80)}...` : cleanTitle;
|
|
112
|
+
}
|
|
113
|
+
export function displaySessionTitle(title, fallback = '默认会话') {
|
|
114
|
+
return sanitizeSessionTitle(title) || sanitizeSessionTitle(fallback) || fallback;
|
|
115
|
+
}
|
|
116
|
+
export function deriveSessionTitle(currentTitle, messageContent, threadId) {
|
|
117
|
+
const normalizedCurrent = sanitizeSessionTitle(currentTitle);
|
|
118
|
+
if (normalizedCurrent && !shouldAutoFillSessionTitle(normalizedCurrent, threadId)) {
|
|
119
|
+
return normalizedCurrent;
|
|
120
|
+
}
|
|
121
|
+
return sanitizeSessionTitle(messageContent) || normalizedCurrent || '默认会话';
|
|
122
|
+
}
|
|
123
|
+
export function shouldAutoFillSessionTitle(title, threadId) {
|
|
124
|
+
const normalized = sanitizeSessionTitle(title);
|
|
125
|
+
if (normalized === '默认会话')
|
|
126
|
+
return true;
|
|
127
|
+
return !!threadId && threadId !== 'main' && normalized === '话题会话';
|
|
128
|
+
}
|