@lemoncat7/dsh-partner 2.11.6 → 2.21.11
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/README.md +3 -0
- package/docs/THIRD_PARTY_LICENSES.md +1 -1
- package/docs/character-cards-and-renewal.md +1 -1
- package/docs/qq-attention.md +51 -0
- package/docs/qq-channel.md +80 -0
- package/docs/releases/2.11.7.md +9 -0
- package/docs/releases/2.12.0.md +14 -0
- package/docs/releases/2.13.0.md +9 -0
- package/docs/releases/2.14.0.md +9 -0
- package/docs/releases/2.15.0.md +9 -0
- package/docs/releases/2.15.1.md +9 -0
- package/docs/releases/2.16.0.md +18 -0
- package/docs/releases/2.17.0.md +23 -0
- package/docs/releases/2.17.1.md +9 -0
- package/docs/releases/2.18.0.md +9 -0
- package/docs/releases/2.18.1.md +5 -0
- package/docs/releases/2.19.0.md +14 -0
- package/docs/releases/2.19.1.md +7 -0
- package/docs/releases/2.19.2.md +9 -0
- package/docs/releases/2.19.3.md +5 -0
- package/docs/releases/2.20.0.md +10 -0
- package/docs/releases/2.20.1.md +8 -0
- package/docs/releases/2.21.0.md +7 -0
- package/docs/releases/2.21.1.md +8 -0
- package/docs/releases/2.21.10.md +5 -0
- package/docs/releases/2.21.11.md +7 -0
- package/docs/releases/2.21.2.md +10 -0
- package/docs/releases/2.21.3.md +7 -0
- package/docs/releases/2.21.4.md +8 -0
- package/docs/releases/2.21.5.md +15 -0
- package/docs/releases/2.21.6.md +6 -0
- package/docs/releases/2.21.7.md +11 -0
- package/docs/releases/2.21.8.md +9 -0
- package/docs/releases/2.21.9.md +6 -0
- package/docs/tool-entry-compatibility.md +11 -0
- package/lib/agent-runtime.d.ts +25 -1
- package/lib/agent-runtime.d.ts.map +1 -1
- package/lib/agent-runtime.js +364 -62
- package/lib/agent-runtime.js.map +1 -1
- package/lib/api.d.ts +1 -0
- package/lib/api.d.ts.map +1 -1
- package/lib/api.js +94 -27
- package/lib/api.js.map +1 -1
- package/lib/channel-message.d.ts +14 -0
- package/lib/channel-message.d.ts.map +1 -1
- package/lib/channel-message.js.map +1 -1
- package/lib/channels/delivery-policy.js +1 -1
- package/lib/channels/delivery-policy.js.map +1 -1
- package/lib/channels/direct/dispatcher.d.ts +2 -2
- package/lib/channels/direct/dispatcher.d.ts.map +1 -1
- package/lib/channels/direct/dispatcher.js +13 -4
- package/lib/channels/direct/dispatcher.js.map +1 -1
- package/lib/channels/direct/login.d.ts.map +1 -1
- package/lib/channels/direct/login.js +4 -0
- package/lib/channels/direct/login.js.map +1 -1
- package/lib/channels/direct/media.d.ts +2 -0
- package/lib/channels/direct/media.d.ts.map +1 -1
- package/lib/channels/direct/media.js +27 -23
- package/lib/channels/direct/media.js.map +1 -1
- package/lib/channels/direct/transport.d.ts +26 -3
- package/lib/channels/direct/transport.d.ts.map +1 -1
- package/lib/channels/direct/transport.js +31 -5
- package/lib/channels/direct/transport.js.map +1 -1
- package/lib/channels/manager.d.ts +26 -0
- package/lib/channels/manager.d.ts.map +1 -1
- package/lib/channels/manager.js +538 -148
- package/lib/channels/manager.js.map +1 -1
- package/lib/channels/notification-delivery.d.ts +4 -0
- package/lib/channels/notification-delivery.d.ts.map +1 -1
- package/lib/channels/notification-delivery.js +15 -5
- package/lib/channels/notification-delivery.js.map +1 -1
- package/lib/channels/notification-route.d.ts.map +1 -1
- package/lib/channels/notification-route.js +10 -4
- package/lib/channels/notification-route.js.map +1 -1
- package/lib/channels/notification-timeout.d.ts.map +1 -1
- package/lib/channels/notification-timeout.js +2 -0
- package/lib/channels/notification-timeout.js.map +1 -1
- package/lib/channels/outbound-media.d.ts.map +1 -1
- package/lib/channels/outbound-media.js +2 -0
- package/lib/channels/outbound-media.js.map +1 -1
- package/lib/channels/qq/action-catalog.d.ts +38 -0
- package/lib/channels/qq/action-catalog.d.ts.map +1 -0
- package/lib/channels/qq/action-catalog.js +2731 -0
- package/lib/channels/qq/action-catalog.js.map +1 -0
- package/lib/channels/qq/actions.d.ts +21 -0
- package/lib/channels/qq/actions.d.ts.map +1 -0
- package/lib/channels/qq/actions.js +226 -0
- package/lib/channels/qq/actions.js.map +1 -0
- package/lib/channels/qq/affection-adapter.d.ts +3 -0
- package/lib/channels/qq/affection-adapter.d.ts.map +1 -0
- package/lib/channels/qq/affection-adapter.js +16 -0
- package/lib/channels/qq/affection-adapter.js.map +1 -0
- package/lib/channels/qq/attention-classifier.d.ts +10 -0
- package/lib/channels/qq/attention-classifier.d.ts.map +1 -0
- package/lib/channels/qq/attention-classifier.js +62 -0
- package/lib/channels/qq/attention-classifier.js.map +1 -0
- package/lib/channels/qq/authority.d.ts +11 -0
- package/lib/channels/qq/authority.d.ts.map +1 -0
- package/lib/channels/qq/authority.js +50 -0
- package/lib/channels/qq/authority.js.map +1 -0
- package/lib/channels/qq/autonomy.d.ts +30 -0
- package/lib/channels/qq/autonomy.d.ts.map +1 -0
- package/lib/channels/qq/autonomy.js +88 -0
- package/lib/channels/qq/autonomy.js.map +1 -0
- package/lib/channels/qq/catalog.d.ts +14 -0
- package/lib/channels/qq/catalog.d.ts.map +1 -0
- package/lib/channels/qq/catalog.js +34 -0
- package/lib/channels/qq/catalog.js.map +1 -0
- package/lib/channels/qq/connection.d.ts +28 -0
- package/lib/channels/qq/connection.d.ts.map +1 -0
- package/lib/channels/qq/connection.js +200 -0
- package/lib/channels/qq/connection.js.map +1 -0
- package/lib/channels/qq/identity-binding.d.ts +14 -0
- package/lib/channels/qq/identity-binding.d.ts.map +1 -0
- package/lib/channels/qq/identity-binding.js +60 -0
- package/lib/channels/qq/identity-binding.js.map +1 -0
- package/lib/channels/qq/latency.d.ts +22 -0
- package/lib/channels/qq/latency.d.ts.map +1 -0
- package/lib/channels/qq/latency.js +21 -0
- package/lib/channels/qq/latency.js.map +1 -0
- package/lib/channels/qq/message.d.ts +6 -0
- package/lib/channels/qq/message.d.ts.map +1 -0
- package/lib/channels/qq/message.js +49 -0
- package/lib/channels/qq/message.js.map +1 -0
- package/lib/channels/qq/reply-rules.d.ts +30 -0
- package/lib/channels/qq/reply-rules.d.ts.map +1 -0
- package/lib/channels/qq/reply-rules.js +72 -0
- package/lib/channels/qq/reply-rules.js.map +1 -0
- package/lib/channels/qq/reply-tool.d.ts +6 -0
- package/lib/channels/qq/reply-tool.d.ts.map +1 -0
- package/lib/channels/qq/reply-tool.js +124 -0
- package/lib/channels/qq/reply-tool.js.map +1 -0
- package/lib/channels/qq/settings.d.ts +42 -0
- package/lib/channels/qq/settings.d.ts.map +1 -0
- package/lib/channels/qq/settings.js +68 -0
- package/lib/channels/qq/settings.js.map +1 -0
- package/lib/channels/qq/sticker-adapter.d.ts +5 -0
- package/lib/channels/qq/sticker-adapter.d.ts.map +1 -0
- package/lib/channels/qq/sticker-adapter.js +43 -0
- package/lib/channels/qq/sticker-adapter.js.map +1 -0
- package/lib/channels/qq/transport.d.ts +29 -0
- package/lib/channels/qq/transport.d.ts.map +1 -0
- package/lib/channels/qq/transport.js +187 -0
- package/lib/channels/qq/transport.js.map +1 -0
- package/lib/channels/reply-access.d.ts +25 -0
- package/lib/channels/reply-access.d.ts.map +1 -0
- package/lib/channels/reply-access.js +29 -0
- package/lib/channels/reply-access.js.map +1 -0
- package/lib/channels/weixin/connect.d.ts +3 -1
- package/lib/channels/weixin/connect.d.ts.map +1 -1
- package/lib/character/activity-tool.d.ts +7 -0
- package/lib/character/activity-tool.d.ts.map +1 -0
- package/lib/character/activity-tool.js +38 -0
- package/lib/character/activity-tool.js.map +1 -0
- package/lib/character/affection-adapter.d.ts +13 -0
- package/lib/character/affection-adapter.d.ts.map +1 -0
- package/lib/character/affection-adapter.js +13 -0
- package/lib/character/affection-adapter.js.map +1 -0
- package/lib/character/affection-context.d.ts +7 -0
- package/lib/character/affection-context.d.ts.map +1 -0
- package/lib/character/affection-context.js +33 -0
- package/lib/character/affection-context.js.map +1 -0
- package/lib/character/affection.d.ts +47 -0
- package/lib/character/affection.d.ts.map +1 -0
- package/lib/character/affection.js +41 -0
- package/lib/character/affection.js.map +1 -0
- package/lib/character/conversation-behavior.d.ts +4 -4
- package/lib/character/conversation-behavior.d.ts.map +1 -1
- package/lib/character/conversation-behavior.js +26 -23
- package/lib/character/conversation-behavior.js.map +1 -1
- package/lib/character/domain.d.ts +5 -0
- package/lib/character/domain.d.ts.map +1 -1
- package/lib/character/domain.js +21 -4
- package/lib/character/domain.js.map +1 -1
- package/lib/character/fatigue.d.ts +15 -0
- package/lib/character/fatigue.d.ts.map +1 -0
- package/lib/character/fatigue.js +37 -0
- package/lib/character/fatigue.js.map +1 -0
- package/lib/character/identity-guide.d.ts +41 -0
- package/lib/character/identity-guide.d.ts.map +1 -0
- package/lib/character/identity-guide.js +20 -0
- package/lib/character/identity-guide.js.map +1 -0
- package/lib/character/idle-contact.d.ts +12 -5
- package/lib/character/idle-contact.d.ts.map +1 -1
- package/lib/character/idle-contact.js +61 -21
- package/lib/character/idle-contact.js.map +1 -1
- package/lib/character/persona-templates.d.ts +23 -0
- package/lib/character/persona-templates.d.ts.map +1 -0
- package/lib/character/persona-templates.js +33 -0
- package/lib/character/persona-templates.js.map +1 -0
- package/lib/character/post-reply.d.ts +19 -0
- package/lib/character/post-reply.d.ts.map +1 -0
- package/lib/character/post-reply.js +33 -0
- package/lib/character/post-reply.js.map +1 -0
- package/lib/character/preview.d.ts.map +1 -1
- package/lib/character/preview.js +9 -1
- package/lib/character/preview.js.map +1 -1
- package/lib/character/relationship-adapter.d.ts +18 -0
- package/lib/character/relationship-adapter.d.ts.map +1 -0
- package/lib/character/relationship-adapter.js +18 -0
- package/lib/character/relationship-adapter.js.map +1 -0
- package/lib/character/role-reference-data.d.ts.map +1 -0
- package/lib/character/role-reference-data.js.map +1 -0
- package/lib/character/sleep-service.d.ts +44 -0
- package/lib/character/sleep-service.d.ts.map +1 -0
- package/lib/character/sleep-service.js +359 -0
- package/lib/character/sleep-service.js.map +1 -0
- package/lib/character/sleep.d.ts +65 -0
- package/lib/character/sleep.d.ts.map +1 -0
- package/lib/character/sleep.js +45 -0
- package/lib/character/sleep.js.map +1 -0
- package/lib/character/state-policy.d.ts +4 -0
- package/lib/character/state-policy.d.ts.map +1 -0
- package/lib/character/state-policy.js +14 -0
- package/lib/character/state-policy.js.map +1 -0
- package/lib/character/store.d.ts.map +1 -1
- package/lib/character/store.js +3 -3
- package/lib/character/store.js.map +1 -1
- package/lib/chat/conversation.js +1 -1
- package/lib/chat/conversation.js.map +1 -1
- package/lib/chat/protocol.d.ts +8 -0
- package/lib/chat/protocol.d.ts.map +1 -1
- package/lib/chat/protocol.js +24 -5
- package/lib/chat/protocol.js.map +1 -1
- package/lib/chat/sticker-adapter.d.ts +34 -0
- package/lib/chat/sticker-adapter.d.ts.map +1 -0
- package/lib/chat/sticker-adapter.js +30 -0
- package/lib/chat/sticker-adapter.js.map +1 -0
- package/lib/chat/tools.d.ts +2 -1
- package/lib/chat/tools.d.ts.map +1 -1
- package/lib/chat/tools.js +59 -13
- package/lib/chat/tools.js.map +1 -1
- package/lib/client-api.d.ts +21 -1
- package/lib/client-api.d.ts.map +1 -1
- package/lib/client-api.js.map +1 -1
- package/lib/client.d.ts.map +1 -1
- package/lib/client.js +7007 -3166
- package/lib/client.js.map +4 -4
- package/lib/collaboration/composition.d.ts +2 -0
- package/lib/collaboration/composition.d.ts.map +1 -1
- package/lib/collaboration/composition.js +32 -10
- package/lib/collaboration/composition.js.map +1 -1
- package/lib/collaboration/tool-discovery.d.ts +12 -0
- package/lib/collaboration/tool-discovery.d.ts.map +1 -0
- package/lib/collaboration/tool-discovery.js +149 -0
- package/lib/collaboration/tool-discovery.js.map +1 -0
- package/lib/companions/identity-tool.d.ts +176 -0
- package/lib/companions/identity-tool.d.ts.map +1 -0
- package/lib/companions/identity-tool.js +69 -0
- package/lib/companions/identity-tool.js.map +1 -0
- package/lib/companions/identity.d.ts +24 -0
- package/lib/companions/identity.d.ts.map +1 -0
- package/lib/companions/identity.js +49 -0
- package/lib/companions/identity.js.map +1 -0
- package/lib/companions/management-tool.d.ts +1 -1
- package/lib/companions/management-tool.d.ts.map +1 -1
- package/lib/companions/management-tool.js +6 -3
- package/lib/companions/management-tool.js.map +1 -1
- package/lib/companions/management.d.ts +49 -10
- package/lib/companions/management.d.ts.map +1 -1
- package/lib/companions/management.js +21 -6
- package/lib/companions/management.js.map +1 -1
- package/lib/companions/service.js +1 -0
- package/lib/companions/service.js.map +1 -1
- package/lib/companions/session-configuration.js +2 -2
- package/lib/companions/session-configuration.js.map +1 -1
- package/lib/domain.d.ts +37 -1
- package/lib/domain.d.ts.map +1 -1
- package/lib/domain.js +6 -0
- package/lib/domain.js.map +1 -1
- package/lib/execution/agent-support.d.ts +1 -1
- package/lib/execution/agent-support.d.ts.map +1 -1
- package/lib/execution/agent-support.js +7 -2
- package/lib/execution/agent-support.js.map +1 -1
- package/lib/execution/operation-tool-access.d.ts +13 -0
- package/lib/execution/operation-tool-access.d.ts.map +1 -0
- package/lib/execution/operation-tool-access.js +49 -0
- package/lib/execution/operation-tool-access.js.map +1 -0
- package/lib/execution/service.d.ts.map +1 -1
- package/lib/execution/service.js +2 -10
- package/lib/execution/service.js.map +1 -1
- package/lib/execution/tool-access-adapter.d.ts +26 -0
- package/lib/execution/tool-access-adapter.d.ts.map +1 -0
- package/lib/execution/tool-access-adapter.js +25 -0
- package/lib/execution/tool-access-adapter.js.map +1 -0
- package/lib/heartbeat.js +1 -1
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +31 -2
- package/lib/index.js.map +1 -1
- package/lib/memory-domain.d.ts +1 -0
- package/lib/memory-domain.d.ts.map +1 -1
- package/lib/memory-prompt-context.js +1 -0
- package/lib/memory-prompt-context.js.map +1 -1
- package/lib/memory-reflection.d.ts.map +1 -1
- package/lib/memory-reflection.js +16 -5
- package/lib/memory-reflection.js.map +1 -1
- package/lib/memory-scope.d.ts +2 -1
- package/lib/memory-scope.d.ts.map +1 -1
- package/lib/memory-scope.js +3 -1
- package/lib/memory-scope.js.map +1 -1
- package/lib/memory-store.d.ts +1 -1
- package/lib/observation-knowledge-entry.d.ts +2 -0
- package/lib/observation-knowledge-entry.d.ts.map +1 -0
- package/lib/observation-knowledge-entry.js +13 -0
- package/lib/observation-knowledge-entry.js.map +1 -0
- package/lib/observation-loop.d.ts.map +1 -1
- package/lib/observation-loop.js +3 -0
- package/lib/observation-loop.js.map +1 -1
- package/lib/storage/split-state.d.ts +1 -0
- package/lib/storage/split-state.d.ts.map +1 -1
- package/lib/storage/split-state.js +9 -2
- package/lib/storage/split-state.js.map +1 -1
- package/lib/store.d.ts.map +1 -1
- package/lib/store.js +7 -0
- package/lib/store.js.map +1 -1
- package/lib/ui/affection-panel.d.ts +10 -0
- package/lib/ui/affection-panel.d.ts.map +1 -0
- package/lib/ui/affection-panel.js +44 -0
- package/lib/ui/affection-panel.js.map +1 -0
- package/lib/ui/capability-editor.d.ts.map +1 -1
- package/lib/ui/capability-editor.js +1 -1
- package/lib/ui/capability-editor.js.map +1 -1
- package/lib/ui/channels-panel.d.ts.map +1 -1
- package/lib/ui/channels-panel.js +14 -9
- package/lib/ui/channels-panel.js.map +1 -1
- package/lib/ui/character-editor.d.ts +3 -1
- package/lib/ui/character-editor.d.ts.map +1 -1
- package/lib/ui/character-editor.js +18 -17
- package/lib/ui/character-editor.js.map +1 -1
- package/lib/ui/character-memory-editor.d.ts +6 -1
- package/lib/ui/character-memory-editor.d.ts.map +1 -1
- package/lib/ui/character-memory-editor.js +17 -11
- package/lib/ui/character-memory-editor.js.map +1 -1
- package/lib/ui/companion-deletion.js +1 -1
- package/lib/ui/companion-deletion.js.map +1 -1
- package/lib/ui/companion-draft.d.ts +3 -0
- package/lib/ui/companion-draft.d.ts.map +1 -1
- package/lib/ui/companion-draft.js +1 -1
- package/lib/ui/companion-draft.js.map +1 -1
- package/lib/ui/companion-vitals.d.ts +6 -0
- package/lib/ui/companion-vitals.d.ts.map +1 -0
- package/lib/ui/companion-vitals.js +44 -0
- package/lib/ui/companion-vitals.js.map +1 -0
- package/lib/ui/identity-editor.d.ts.map +1 -1
- package/lib/ui/identity-editor.js +13 -6
- package/lib/ui/identity-editor.js.map +1 -1
- package/lib/ui/idle-contact-editor.d.ts.map +1 -1
- package/lib/ui/idle-contact-editor.js +3 -2
- package/lib/ui/idle-contact-editor.js.map +1 -1
- package/lib/ui/memory/memory-panel.d.ts.map +1 -1
- package/lib/ui/memory/memory-panel.js +5 -3
- package/lib/ui/memory/memory-panel.js.map +1 -1
- package/lib/ui/persona-template.d.ts +5 -0
- package/lib/ui/persona-template.d.ts.map +1 -0
- package/lib/ui/persona-template.js +7 -0
- package/lib/ui/persona-template.js.map +1 -0
- package/lib/ui/push-draft.d.ts +8 -8
- package/lib/ui/qq-capability-settings.d.ts +9 -0
- package/lib/ui/qq-capability-settings.d.ts.map +1 -0
- package/lib/ui/qq-capability-settings.js +24 -0
- package/lib/ui/qq-capability-settings.js.map +1 -0
- package/lib/ui/qq-channel-settings.d.ts +13 -0
- package/lib/ui/qq-channel-settings.d.ts.map +1 -0
- package/lib/ui/qq-channel-settings.js +52 -0
- package/lib/ui/qq-channel-settings.js.map +1 -0
- package/lib/ui/qq-connection-guide.d.ts +2 -0
- package/lib/ui/qq-connection-guide.d.ts.map +1 -0
- package/lib/ui/qq-connection-guide.js +5 -0
- package/lib/ui/qq-connection-guide.js.map +1 -0
- package/lib/ui/qq-identity-binding.d.ts +9 -0
- package/lib/ui/qq-identity-binding.d.ts.map +1 -0
- package/lib/ui/qq-identity-binding.js +6 -0
- package/lib/ui/qq-identity-binding.js.map +1 -0
- package/lib/ui/qq-voice-reply-settings.d.ts +9 -0
- package/lib/ui/qq-voice-reply-settings.d.ts.map +1 -0
- package/lib/ui/qq-voice-reply-settings.js +23 -0
- package/lib/ui/qq-voice-reply-settings.js.map +1 -0
- package/lib/ui/role-reference.d.ts.map +1 -1
- package/lib/ui/role-reference.js +3 -2
- package/lib/ui/role-reference.js.map +1 -1
- package/lib/ui/sleep-editor.d.ts +10 -0
- package/lib/ui/sleep-editor.d.ts.map +1 -0
- package/lib/ui/sleep-editor.js +55 -0
- package/lib/ui/sleep-editor.js.map +1 -0
- package/package.json +39 -5
- package/lib/ui/role-reference-data.d.ts.map +0 -1
- package/lib/ui/role-reference-data.js.map +0 -1
- /package/lib/{ui → character}/role-reference-data.d.ts +0 -0
- /package/lib/{ui → character}/role-reference-data.js +0 -0
package/lib/agent-runtime.js
CHANGED
|
@@ -1,3 +1,15 @@
|
|
|
1
|
+
import { personaReferenceRequested, renderPersonaReference } from './character/identity-guide.js';
|
|
2
|
+
import { activityPolicyCurrent, postReplyAllowed, postReplyCurrent, postReplyProbability, POST_REPLY_PROMPT } from './character/post-reply.js';
|
|
3
|
+
import { PARTNER_TOOL_ENTRIES } from './collaboration/tool-discovery.js';
|
|
4
|
+
import { setReplyTiming } from './channels/qq/latency.js';
|
|
5
|
+
import { resolveToolAccess, installToolAccessGuard } from './execution/tool-access-adapter.js';
|
|
6
|
+
import { renderIdentityRelationship, relationshipTextForIdentity } from './character/relationship-adapter.js';
|
|
7
|
+
import { affectionEnabled, awardAffection, renderAffection } from './character/affection.js';
|
|
8
|
+
import { affectionTurnEligible, currentAffectionAudience } from './character/affection-context.js';
|
|
9
|
+
import { currentQqIdentityRelation, renderQqIdentityRelation } from './channels/qq/identity-binding.js';
|
|
10
|
+
import { qqSettings, qqSessionTarget } from './channels/qq/settings.js';
|
|
11
|
+
import { fatigueValue } from './character/fatigue.js';
|
|
12
|
+
import { renderSleepContext, isSleeping } from './character/sleep.js';
|
|
1
13
|
import { questionOrigin } from './channels/question-origin.js';
|
|
2
14
|
import { renderCharacterContext } from './character/domain.js';
|
|
3
15
|
import { renderConversationTurn } from './character/conversation-behavior.js';
|
|
@@ -12,7 +24,7 @@ import { conversationMemoryScope } from './memory-scope.js';
|
|
|
12
24
|
import { mkdir, realpath, stat, writeFile } from 'node:fs/promises';
|
|
13
25
|
import { isAbsolute, join, relative, resolve, sep } from 'node:path';
|
|
14
26
|
import { createUserMessage } from '@deepseek-ai/dsh-llm';
|
|
15
|
-
import { IDLE_CONTACT_NOTICE, idleContactDue, idleContactDay, lastIdleContactUser } from './character/idle-contact.js';
|
|
27
|
+
import { IDLE_CONTACT_NOTICE, idleActivityPrompt, isIdleContactNotice, idleActivityProbability, idleContactDue, idleContactDay, idleContactRouteAllowed, lastIdleContactUser } from './character/idle-contact.js';
|
|
16
28
|
import { scopeOf, scopeParentOf } from '@deepseek-ai/dsh-scope';
|
|
17
29
|
import { SessionConfigurationIndex } from './companions/session-configuration.js';
|
|
18
30
|
import { isMissingSession, SessionRecoveryJobs } from './companions/session-recovery.js';
|
|
@@ -27,6 +39,7 @@ import { concernExecutionNotes } from './concern-execution-notes.js';
|
|
|
27
39
|
import { bindTaskSshAccess } from './collaboration/ssh-task-access.js';
|
|
28
40
|
import { hasConcernReply } from './concern-execution-receipt.js';
|
|
29
41
|
import { executeObservationLoop } from './observation-loop.js';
|
|
42
|
+
import { observationKnowledgeEntryDenial } from './observation-knowledge-entry.js';
|
|
30
43
|
import { waitForBoardTurn } from './tasks/continuation-yield.js';
|
|
31
44
|
import { assistantTextAfter, renderPartnerPersona, renderToolProtocol, resolvePartnerAgentOptions as resolveAgentOptions } from './execution/agent-support.js';
|
|
32
45
|
import { channelReplyPartsAfter, isInternalTaskNotice, isRequirementDeliveryReceipt } from './channels/delivery-policy.js';
|
|
@@ -36,6 +49,7 @@ export { extractOutboundAttachments } from './channels/outbound-media.js';
|
|
|
36
49
|
export { renderToolProtocol, resolvePartnerAgentOptions as resolveAgentOptions } from './execution/agent-support.js';
|
|
37
50
|
const PARTNER_TASK_TIMEOUT_MS = 30 * 60_000;
|
|
38
51
|
const HEARTBEAT_READ_ONLY_TOOLS = new Set([
|
|
52
|
+
'knowledge',
|
|
39
53
|
'knowledge_base_search',
|
|
40
54
|
'knowledge_search',
|
|
41
55
|
'knowledge_read',
|
|
@@ -55,6 +69,7 @@ export class PartnerAgentRuntime {
|
|
|
55
69
|
reflection;
|
|
56
70
|
concerns;
|
|
57
71
|
composer;
|
|
72
|
+
latestPostReplyInput = new Map();
|
|
58
73
|
handles = new Map();
|
|
59
74
|
queues = new Map();
|
|
60
75
|
heartbeatQueues = new Map();
|
|
@@ -65,7 +80,9 @@ export class PartnerAgentRuntime {
|
|
|
65
80
|
restoredCompositionJobs = new Map();
|
|
66
81
|
disposeAgentListener;
|
|
67
82
|
disposeConfigurationListener;
|
|
83
|
+
disposeToolAuthority;
|
|
68
84
|
disposeAssemblyListener;
|
|
85
|
+
autonomousAssemblyContexts = new WeakSet();
|
|
69
86
|
rebuildingAssemblies = new WeakSet();
|
|
70
87
|
configurations;
|
|
71
88
|
characterContexts = new Map();
|
|
@@ -84,33 +101,65 @@ export class PartnerAgentRuntime {
|
|
|
84
101
|
this.concerns = concerns;
|
|
85
102
|
this.composer = composer;
|
|
86
103
|
this.configurations = new SessionConfigurationIndex(store);
|
|
104
|
+
this.disposeToolAuthority = typeof this.ctx.tools?.guard === 'function' ? installToolAccessGuard(this.ctx.tools, () => this.store.snapshot()) : (() => { });
|
|
87
105
|
// Current hosts snapshot providers BEFORE agent/pre-step. Rebuild the first
|
|
88
106
|
// assembly after installing scoped contributions; returning the old snapshot
|
|
89
107
|
// would still omit tools for one request even though registration succeeded.
|
|
90
108
|
this.disposeAssemblyListener = typeof this.ctx.on === 'function'
|
|
91
109
|
? this.ctx.on('system-prompt/assemble', async (_assembly, context, next) => {
|
|
92
|
-
if (!context.agent || !context.signal
|
|
110
|
+
if (!context.agent || !context.signal)
|
|
93
111
|
return next();
|
|
112
|
+
const enforce = (assembly) => {
|
|
113
|
+
if (this.autonomousAssemblyContexts.has(context))
|
|
114
|
+
return assembly;
|
|
115
|
+
const configuration = this.configurations.forSession(context.agent.session.id);
|
|
116
|
+
if (configuration?.companion.toolDiscovery)
|
|
117
|
+
return { ...assembly, tools: assembly.tools.filter(tool => PARTNER_TOOL_ENTRIES.some(name => name === tool.name)) };
|
|
118
|
+
const access = resolveToolAccess({ agent: context.agent, state: this.store.snapshot(), ...(this.admittedInputs.get(context.agent)?.length ? { events: this.inputEvents(context.agent) } : {}) });
|
|
119
|
+
return { ...assembly, tools: !access.sendTools ? [] : access.allowedTools ? assembly.tools.filter(tool => access.allowedTools.includes(tool.name)) : assembly.tools };
|
|
120
|
+
};
|
|
121
|
+
if (this.rebuildingAssemblies.has(context))
|
|
122
|
+
return enforce(await next());
|
|
94
123
|
const changed = await this.prepareAgentAssembly(context.agent, context.signal);
|
|
95
124
|
const contextual = await this.prepareConversationContext(context.agent, context.signal);
|
|
96
125
|
if (!changed && !contextual)
|
|
97
|
-
return next();
|
|
126
|
+
return enforce(await next());
|
|
98
127
|
this.rebuildingAssemblies.add(context);
|
|
99
128
|
try {
|
|
100
|
-
return await this.ctx.systemPrompt.assemble(context);
|
|
129
|
+
return enforce(await this.ctx.systemPrompt.assemble(context));
|
|
101
130
|
}
|
|
102
131
|
finally {
|
|
103
132
|
this.rebuildingAssemblies.delete(context);
|
|
104
133
|
}
|
|
105
134
|
}) : () => { };
|
|
106
135
|
this.disposeConfigurationListener = typeof this.ctx.on === 'function'
|
|
107
|
-
? this.ctx.on('agent/pre-step', async ({ agent, turn, signal }, next) => {
|
|
108
|
-
|
|
109
|
-
|
|
136
|
+
? this.ctx.on('agent/pre-step', async ({ agent, turn, signal, messages }, next) => {
|
|
137
|
+
try {
|
|
138
|
+
await this.prepareAgentTurn(agent, turn, signal, messages);
|
|
139
|
+
const decision = await next();
|
|
140
|
+
if (decision.kind === 'reject') {
|
|
141
|
+
this.admittedInputs.delete(agent);
|
|
142
|
+
this.contextRevisions.delete(agent);
|
|
143
|
+
}
|
|
144
|
+
else if (decision.kind === 'enter') {
|
|
145
|
+
this.admittedInputs.set(agent, decision.messages);
|
|
146
|
+
await this.sleepService?.acceptInput(agent.session.id, decision.messages);
|
|
147
|
+
await this.prepareConversationContext(agent, signal);
|
|
148
|
+
}
|
|
149
|
+
return decision;
|
|
150
|
+
}
|
|
151
|
+
catch (error) {
|
|
152
|
+
this.admittedInputs.delete(agent);
|
|
153
|
+
this.contextRevisions.delete(agent);
|
|
154
|
+
throw error;
|
|
155
|
+
}
|
|
110
156
|
}) : () => { };
|
|
111
157
|
this.disposeAgentListener = typeof this.ctx.on === 'function'
|
|
112
158
|
? this.ctx.on('agent/disposed', ({ agent }) => {
|
|
159
|
+
this.replyPresentations.get(agent)?.();
|
|
160
|
+
this.replyPresentations.delete(agent);
|
|
113
161
|
this.preparedTurns.delete(agent);
|
|
162
|
+
this.admittedInputs.delete(agent);
|
|
114
163
|
this.characterContexts.get(agent.session.id)?.();
|
|
115
164
|
this.characterContexts.delete(agent.session.id);
|
|
116
165
|
this.contextRevisions.delete(agent);
|
|
@@ -122,51 +171,143 @@ export class PartnerAgentRuntime {
|
|
|
122
171
|
})
|
|
123
172
|
: () => { };
|
|
124
173
|
}
|
|
174
|
+
sleepService;
|
|
175
|
+
setSleepService(service) { this.sleepService = service; }
|
|
125
176
|
setQuestionAnswerer(answerer) {
|
|
126
177
|
this.questionAnswerer = answerer;
|
|
127
178
|
}
|
|
179
|
+
/** Cancel pending/claimed continuations as soon as another real input is admitted. */
|
|
180
|
+
async cancelPostReply(companionId, keepUserMessageId) {
|
|
181
|
+
const needsCancel = (r) => r.companionId === companionId && r.postReplyOpportunity
|
|
182
|
+
&& ['pending', 'claimed'].includes(r.postReplyOpportunity.status) && r.postReplyOpportunity.userMessageId !== keepUserMessageId;
|
|
183
|
+
if (!this.store.snapshot().sessions.some(needsCancel))
|
|
184
|
+
return;
|
|
185
|
+
await this.store.update(state => { for (const route of state.sessions)
|
|
186
|
+
if (needsCancel(route))
|
|
187
|
+
route.postReplyOpportunity.status = 'cancelled'; });
|
|
188
|
+
}
|
|
189
|
+
async localReplyDisplayed(source) {
|
|
190
|
+
const state = this.store.snapshot(), route = state.sessions.find(r => r.id === source.routeId && r.sessionId === source.sessionId && r.kind === 'local');
|
|
191
|
+
const companion = state.companions.find(c => c.id === route?.companionId);
|
|
192
|
+
if (!route || !companion || !postReplyAllowed(state, companion, route))
|
|
193
|
+
return;
|
|
194
|
+
const agent = await this.ensureAgent(companion, route);
|
|
195
|
+
if (agent.status !== 'idle')
|
|
196
|
+
await agent.whenIdle();
|
|
197
|
+
await this.replyDelivered(source);
|
|
198
|
+
}
|
|
199
|
+
/** Called only after delivery succeeds; autonomous notices never carry this provenance. */
|
|
200
|
+
async replyDelivered(source) {
|
|
201
|
+
const state = this.store.snapshot(), route = state.sessions.find(r => r.id === source.routeId && r.sessionId === source.sessionId);
|
|
202
|
+
const companion = state.companions.find(c => c.id === route?.companionId);
|
|
203
|
+
if (!route || !companion || !postReplyAllowed(state, companion, route) || this.closed || this.isArchived(route) || this.store.isCompanionRemoving(companion.id))
|
|
204
|
+
return;
|
|
205
|
+
const key = `companion:${companion.id}`;
|
|
206
|
+
if (this.queues.has(key) || this.steeringQueues.has(key))
|
|
207
|
+
return;
|
|
208
|
+
const agent = await this.ensureAgent(companion, route);
|
|
209
|
+
if (agent.status !== 'idle' || (this.latestPostReplyInput.has(companion.id) && this.latestPostReplyInput.get(companion.id) !== source.userMessageId) || lastIdleContactUser(agent.session.snapshotEvents())?.id !== source.userMessageId)
|
|
210
|
+
return;
|
|
211
|
+
await this.store.update(draft => {
|
|
212
|
+
const current = draft.companions.find(c => c.id === companion.id), target = draft.sessions.find(r => r.id === route.id);
|
|
213
|
+
if (!current || !target || target.sessionId !== source.sessionId || !postReplyAllowed(draft, current, target)
|
|
214
|
+
|| target.postReplyOpportunity?.userMessageId === source.userMessageId || agent.status !== 'idle'
|
|
215
|
+
|| this.queues.has(key) || this.steeringQueues.has(key) || (this.latestPostReplyInput.has(companion.id) && this.latestPostReplyInput.get(companion.id) !== source.userMessageId) || lastIdleContactUser(agent.session.snapshotEvents())?.id !== source.userMessageId)
|
|
216
|
+
return;
|
|
217
|
+
// At most one pending continuation per companion; different owner accounts share it.
|
|
218
|
+
for (const other of draft.sessions)
|
|
219
|
+
if (other.companionId === companion.id && other.postReplyOpportunity?.status === 'pending')
|
|
220
|
+
other.postReplyOpportunity.status = 'cancelled';
|
|
221
|
+
const now = Date.now(), probability = postReplyProbability(current);
|
|
222
|
+
target.postReplyOpportunity = { userMessageId: source.userMessageId, sessionId: source.sessionId, policyRevision: current.updatedAt,
|
|
223
|
+
deliveredAt: now, dueAt: now + 20_000 + Math.floor(Math.random() * 70_000), expiresAt: now + 180_000,
|
|
224
|
+
probability, status: Math.random() < probability ? 'pending' : 'missed' };
|
|
225
|
+
});
|
|
226
|
+
}
|
|
128
227
|
/** Use the existing timer and real conversation; never impersonate user input. */
|
|
129
228
|
async idleContact(companionId, timeZone = 'Asia/Shanghai') {
|
|
130
229
|
const key = `companion:${companionId}`;
|
|
131
|
-
if (this.closed || this.
|
|
230
|
+
if (this.closed || this.companionActivity(companionId).some(a => a.kind !== 'memory' && a.kind !== 'observation') || this.queues.has(key) || this.taskQueues.has(key) || this.heartbeatQueues.has(key) || this.steeringQueues.has(key) || this.store.isCompanionRemoving(companionId))
|
|
132
231
|
return;
|
|
133
232
|
const state = this.store.snapshot(), companion = state.companions.find(c => c.id === companionId);
|
|
134
|
-
const
|
|
233
|
+
const pendingRoute = state.sessions.find(r => r.companionId === companionId && r.postReplyOpportunity?.status === 'pending');
|
|
234
|
+
let route = pendingRoute ?? state.sessions.find(r => r.id === companion?.idleContact?.routeId && r.companionId === companionId);
|
|
235
|
+
const continuation = !!pendingRoute;
|
|
236
|
+
if (pendingRoute && companion) {
|
|
237
|
+
const pending = pendingRoute.postReplyOpportunity;
|
|
238
|
+
if (!postReplyCurrent(state, companion, pendingRoute) || Date.now() > pending.expiresAt) {
|
|
239
|
+
await this.cancelPostReply(companionId);
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
if (Date.now() < pending.dueAt)
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
135
245
|
if (!companion?.idleContact?.enabled || !route || this.isArchived(route))
|
|
136
246
|
return;
|
|
247
|
+
const channel = state.channels.find(c => c.id === route.channelId);
|
|
248
|
+
if (channel?.platform === 'qq' && route.qqSessionMode !== (qqSettings(channel.qq).mainSessionPeers.includes(qqSessionTarget(route.userId)) ? 'main' : 'separate'))
|
|
249
|
+
route = await this.ensureSession(companion, route.channelId, route.userId);
|
|
137
250
|
const agent = await this.ensureAgent(companion, route);
|
|
138
|
-
if (agent.status !== 'idle' || this.
|
|
251
|
+
if (agent.status !== 'idle' || this.companionActivity(companionId).some(a => a.kind !== 'memory' && a.kind !== 'observation') || this.queues.has(key) || this.taskQueues.has(key))
|
|
139
252
|
return;
|
|
140
253
|
const user = lastIdleContactUser(agent.session.snapshotEvents());
|
|
141
|
-
|
|
254
|
+
const completed = [...agent.session.snapshotEvents()].reverse().find(e => e.type === 'turn/end');
|
|
255
|
+
if (user && completed)
|
|
256
|
+
user.at = Math.max(user.at, completed.time);
|
|
257
|
+
if (continuation) {
|
|
258
|
+
if (user?.id !== route.postReplyOpportunity?.userMessageId) {
|
|
259
|
+
await this.cancelPostReply(companionId);
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
else if (!idleContactDue(this.store.snapshot(), companion, user, Date.now(), timeZone))
|
|
142
264
|
return;
|
|
143
265
|
const stableSeq = agent.session.seq;
|
|
144
|
-
const message = createUserMessage({ content: [{ type: 'text', text: '
|
|
266
|
+
const message = createUserMessage({ content: [{ type: 'text', text: idleActivityPrompt(this.store.snapshot(), companion, agent.session.snapshotEvents()) + (continuation ? '\n' + POST_REPLY_PROMPT : '')
|
|
145
267
|
}], source: { kind: 'plugin:@lemoncat7/dsh-partner', plugin: '@lemoncat7/dsh-partner', form: 'notice', summary: IDLE_CONTACT_NOTICE } });
|
|
146
268
|
let claimed = false;
|
|
147
269
|
await this.store.update(draft => {
|
|
148
270
|
const current = draft.companions.find(c => c.id === companionId), target = draft.sessions.find(r => r.id === route.id);
|
|
149
271
|
const now = Date.now();
|
|
150
|
-
if (!current || current.updatedAt !== companion.updatedAt || !target || target.sessionId !== route.sessionId || !idleContactDue(draft, current, user, now, timeZone))
|
|
272
|
+
if (!current || current.updatedAt !== companion.updatedAt || !target || target.sessionId !== route.sessionId || (continuation ? !postReplyCurrent(draft, current, target, now) || target.postReplyOpportunity?.status !== 'pending' || target.postReplyOpportunity.userMessageId !== user?.id || now > target.postReplyOpportunity.expiresAt : !idleContactDue(draft, current, user, now, timeZone)))
|
|
151
273
|
return;
|
|
274
|
+
const probability = continuation ? target.postReplyOpportunity.probability : idleActivityProbability(draft, current, user, now);
|
|
275
|
+
target.idleActivityCheck = { at: now, probability };
|
|
276
|
+
if (!continuation && Math.random() >= probability)
|
|
277
|
+
return;
|
|
278
|
+
if (continuation)
|
|
279
|
+
target.postReplyOpportunity.status = 'claimed';
|
|
152
280
|
const day = idleContactDay(now, timeZone);
|
|
153
|
-
target.idleContactState = { userMessageId: user.id, messageId: message.id, attemptedAt: now, day, count: (target.idleContactState?.day === day ? target.idleContactState.count : 0) + 1, timeZone, policyRevision: current.updatedAt };
|
|
281
|
+
target.idleContactState = { ...(continuation ? { kind: 'post-reply' } : {}), userMessageId: user.id, messageId: message.id, attemptedAt: now, day, count: (target.idleContactState?.day === day ? target.idleContactState.count : 0) + 1, timeZone, policyRevision: current.updatedAt };
|
|
154
282
|
claimed = true;
|
|
155
283
|
});
|
|
156
284
|
// Claim before generation survives restart. Don't replay uncertain sends or steal active turns.
|
|
157
285
|
if (!claimed || this.closed || this.store.isCompanionRemoving(companionId) || agent.status !== 'idle' || agent.session.seq !== stableSeq || this.queues.has(key) || this.taskQueues.has(key))
|
|
158
286
|
return;
|
|
159
|
-
const
|
|
160
|
-
if (!current
|
|
287
|
+
const currentState = this.store.snapshot(), current = currentState.companions.find(c => c.id === companionId), currentRoute = currentState.sessions.find(r => r.id === route.id);
|
|
288
|
+
if (!current || !currentRoute || !activityPolicyCurrent(currentState, current, currentRoute) || isSleeping(current) || current.updatedAt !== companion.updatedAt || this.isArchived(route))
|
|
161
289
|
return;
|
|
162
290
|
agent.followup(message);
|
|
163
291
|
}
|
|
292
|
+
async idleContactDeliveryCurrent(origin) {
|
|
293
|
+
const state = this.store.snapshot(), route = state.sessions.find(r => r.id === origin.routeId);
|
|
294
|
+
const companion = state.companions.find(c => c.id === route?.companionId);
|
|
295
|
+
if (!route || !companion || route.sessionId !== origin.sessionId || !activityPolicyCurrent(state, companion, route) || companion.updatedAt !== origin.policyRevision || route.idleContactState?.messageId !== origin.messageId || !idleContactRouteAllowed(state, companion, route.id) || isSleeping(companion) || this.isArchived(route) || this.store.isCompanionRemoving(companion.id))
|
|
296
|
+
return false;
|
|
297
|
+
const channel = state.channels.find(c => c.id === route.channelId);
|
|
298
|
+
if (channel?.platform === 'qq' && route.qqSessionMode !== (qqSettings(channel.qq).mainSessionPeers.includes(qqSessionTarget(route.userId)) ? 'main' : 'separate'))
|
|
299
|
+
return false;
|
|
300
|
+
const agent = await this.ensureAgent(companion, route);
|
|
301
|
+
return lastIdleContactUser(agent.session.snapshotEvents())?.id === origin.userMessageId;
|
|
302
|
+
}
|
|
164
303
|
async reply(companion, channelId, userId, message, progress) {
|
|
165
304
|
const key = `companion:${companion.id}`;
|
|
305
|
+
await this.cancelPostReply(companion.id);
|
|
306
|
+
const queuedAt = Date.now();
|
|
166
307
|
const previous = this.queues.get(key) ?? Promise.resolve();
|
|
167
308
|
let output = { text: '', attachments: [] };
|
|
168
309
|
reportProgress(progress, 'queued');
|
|
169
|
-
const current = previous.catch(() => { }).then(async () => { output = await this.drive(companion, channelId, userId, message, progress); });
|
|
310
|
+
const current = previous.catch(() => { }).then(async () => { output = await this.drive(companion, channelId, userId, message, progress, queuedAt); });
|
|
170
311
|
this.queues.set(key, current);
|
|
171
312
|
try {
|
|
172
313
|
await current;
|
|
@@ -178,6 +319,7 @@ export class PartnerAgentRuntime {
|
|
|
178
319
|
}
|
|
179
320
|
}
|
|
180
321
|
async steer(companion, channelId, userId, message) {
|
|
322
|
+
await this.cancelPostReply(companion.id);
|
|
181
323
|
const key = `companion:${companion.id}`;
|
|
182
324
|
const previous = this.steeringQueues.get(key) ?? Promise.resolve();
|
|
183
325
|
let accepted = false;
|
|
@@ -196,6 +338,9 @@ export class PartnerAgentRuntime {
|
|
|
196
338
|
const route = this.store.snapshot().sessions.find(item => item.channelId === channelId && item.userId === userId && item.companionId === companion.id);
|
|
197
339
|
if (route === undefined)
|
|
198
340
|
return false;
|
|
341
|
+
const channel = this.store.snapshot().channels.find(c => c.id === channelId);
|
|
342
|
+
if (channel?.platform === 'qq' && route.qqSessionMode !== (qqSettings(channel.qq).mainSessionPeers.includes(qqSessionTarget(userId)) ? 'main' : 'separate'))
|
|
343
|
+
return false;
|
|
199
344
|
const agent = this.handles.get(route.sessionId)?.agent ?? this.ctx.agents.get(route.sessionId);
|
|
200
345
|
if (agent === undefined || agent.status !== 'running')
|
|
201
346
|
return false;
|
|
@@ -258,7 +403,7 @@ export class PartnerAgentRuntime {
|
|
|
258
403
|
// If there was no local conversation, continue the newest existing channel
|
|
259
404
|
// conversation. Do not concatenate unrelated historical transcripts.
|
|
260
405
|
if (!existing) {
|
|
261
|
-
const previous = state.sessions.filter(item => item.companionId === companion.id && canReuseSession(item, companion.id, this.ctx.workspaceRegistry.archivedSessionIds)).sort((a, b) => b.lastMessageAt - a.lastMessageAt)[0];
|
|
406
|
+
const previous = state.sessions.filter(item => item.companionId === companion.id && !state.channels.some(c => c.id === item.channelId && c.platform === 'qq') && canReuseSession(item, companion.id, this.ctx.workspaceRegistry.archivedSessionIds)).sort((a, b) => b.lastMessageAt - a.lastMessageAt)[0];
|
|
262
407
|
if (previous) {
|
|
263
408
|
route.sessionId = previous.sessionId;
|
|
264
409
|
if (previous.cwd)
|
|
@@ -338,9 +483,16 @@ export class PartnerAgentRuntime {
|
|
|
338
483
|
const agent = await this.ensureAgent(companion, route);
|
|
339
484
|
if (!this.store.snapshot().sessions.some(s => s.id === route.id && s.sessionId === wake.originSessionId) || this.isArchived(route))
|
|
340
485
|
throw new Error('原续接会话已变更');
|
|
341
|
-
const
|
|
342
|
-
|
|
343
|
-
|
|
486
|
+
const preflight = assembleContextFor(agent, signal);
|
|
487
|
+
this.autonomousAssemblyContexts.add(preflight);
|
|
488
|
+
try {
|
|
489
|
+
const assembly = await agent.ctx.systemPrompt.assemble(preflight);
|
|
490
|
+
signal.throwIfAborted();
|
|
491
|
+
assertWakeTools(entry, assembly.tools.map(tool => tool.name));
|
|
492
|
+
}
|
|
493
|
+
finally {
|
|
494
|
+
this.autonomousAssemblyContexts.delete(preflight);
|
|
495
|
+
}
|
|
344
496
|
const message = createUserMessage({ content: [{ type: 'text', text: continuationPrompt(entry) }],
|
|
345
497
|
source: { kind: 'plugin:@lemoncat7/dsh-partner', plugin: '@lemoncat7/dsh-partner', form: 'notice', summary: '伙伴长任务续接' } });
|
|
346
498
|
await this.store.update(state => {
|
|
@@ -584,6 +736,19 @@ export class PartnerAgentRuntime {
|
|
|
584
736
|
}
|
|
585
737
|
}
|
|
586
738
|
async observeSessionEvent(session, event) {
|
|
739
|
+
if (event.type === 'turn/end') {
|
|
740
|
+
const agent = this.ctx?.agents?.get(session.id);
|
|
741
|
+
if (agent)
|
|
742
|
+
this.admittedInputs?.delete(agent);
|
|
743
|
+
}
|
|
744
|
+
if (event.type === 'user/message' && event.data.source.kind === 'user') {
|
|
745
|
+
const owner = this.store.snapshot().sessions.find(r => r.sessionId === session.id)?.companionId;
|
|
746
|
+
if (owner) {
|
|
747
|
+
this.latestPostReplyInput.set(owner, event.data.id);
|
|
748
|
+
await this.cancelPostReply(owner, event.data.id);
|
|
749
|
+
}
|
|
750
|
+
return;
|
|
751
|
+
}
|
|
587
752
|
if (event.type !== 'turn/end' || event.data.reason.kind !== 'completed')
|
|
588
753
|
return;
|
|
589
754
|
const route = this.store.snapshot().sessions.find(item => item.sessionId === session.id);
|
|
@@ -612,6 +777,7 @@ export class PartnerAgentRuntime {
|
|
|
612
777
|
if (companion === undefined)
|
|
613
778
|
return;
|
|
614
779
|
const events = completedTurnEvents(session.snapshotEvents(), event);
|
|
780
|
+
const identityRelation = currentQqIdentityRelation(session.id, events, this.store.snapshot().sessions, this.store.snapshot().channels ?? []);
|
|
615
781
|
const prompt = events.find(item => item.type === 'user/message' && item.data.source.kind === 'user');
|
|
616
782
|
const replies = events.filter(item => item.type === 'assistant/message' && !item.data.interrupted);
|
|
617
783
|
const userText = events.filter(e => e.type === 'user/message' && e.data.source.kind === 'user').map(e => e.type === 'user/message' ? textContent(e.data.content) : '').filter(Boolean).join('\n\n');
|
|
@@ -620,10 +786,11 @@ export class PartnerAgentRuntime {
|
|
|
620
786
|
if (!userText || !assistantText)
|
|
621
787
|
return;
|
|
622
788
|
const scopeId = memoryScope(route.channelId, route.userId);
|
|
623
|
-
const concernDirective = await this.concerns?.applyUserDirective(companion.id, scopeId, userText, event.time);
|
|
789
|
+
const concernDirective = identityRelation && !identityRelation.matched ? undefined : await this.concerns?.applyUserDirective(companion.id, scopeId, userText, event.time);
|
|
624
790
|
const created = companion.automation.memory.enabled && this.reflection !== undefined ? await this.reflection.reflect(companion, {
|
|
625
791
|
id: `turn-${createHash('sha256').update(JSON.stringify([session.id, event.time, userText])).digest('hex').slice(0, 32)}`, companionId: companion.id, scopeId: conversationMemoryScope(route, this.store.snapshot().sessions),
|
|
626
792
|
characterEnabled: companion.characterCard?.enabled === true,
|
|
793
|
+
...(identityRelation ? { identityRelation } : {}),
|
|
627
794
|
userMessageIds: events.flatMap(e => e.type === 'user/message' && e.data.source.kind === 'user' ? [e.data.id] : []),
|
|
628
795
|
sessionId: session.id, at: event.time, user: userText, assistant: assistantText, concernScopeId: scopeId,
|
|
629
796
|
...(concernDirective === undefined ? {} : { concernDirective }),
|
|
@@ -645,6 +812,9 @@ export class PartnerAgentRuntime {
|
|
|
645
812
|
const target = state.sessions.find(item => item.id === route.id);
|
|
646
813
|
if (target)
|
|
647
814
|
target.lastMessageAt = Date.now();
|
|
815
|
+
const current = state.companions.find(c => c.id === companion.id);
|
|
816
|
+
if (current && affectionEnabled(current) && affectionTurnEligible(state, current.id, session.id, events))
|
|
817
|
+
awardAffection(current, `${session.id}:${event.seq}`, event.time);
|
|
648
818
|
});
|
|
649
819
|
}
|
|
650
820
|
async recordConcernCreatedNotice(companion, scopeId, concerns) {
|
|
@@ -740,12 +910,12 @@ export class PartnerAgentRuntime {
|
|
|
740
910
|
throw new Error('伙伴不存在');
|
|
741
911
|
}
|
|
742
912
|
/** Awaited by the host before prompt/tool assembly, never from a running tool. */
|
|
743
|
-
async prepareAgentTurn(agent, turn, signal) {
|
|
913
|
+
async prepareAgentTurn(agent, turn, signal, messages = []) {
|
|
744
914
|
if (this.closed)
|
|
745
915
|
throw new Error('伙伴运行时已关闭');
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
916
|
+
this.admittedInputs.set(agent, messages);
|
|
917
|
+
if (this.preparedTurns.get(agent) !== turn)
|
|
918
|
+
await this.prepareAgentAssembly(agent, signal);
|
|
749
919
|
await this.prepareConversationContext(agent, signal);
|
|
750
920
|
this.preparedTurns.set(agent, turn);
|
|
751
921
|
}
|
|
@@ -765,6 +935,18 @@ export class PartnerAgentRuntime {
|
|
|
765
935
|
if (this.store.isCompanionRemoving(configuration.companion.id))
|
|
766
936
|
throw new Error('伙伴正在删除,不能启动新轮次');
|
|
767
937
|
signal.throwIfAborted();
|
|
938
|
+
const access = resolveToolAccess({ agent, state: this.store.snapshot(), ...(this.admittedInputs.get(agent)?.length ? { events: this.inputEvents(agent) } : {}) });
|
|
939
|
+
let presentationChanged = false;
|
|
940
|
+
const stableNative = configuration.companion.toolDiscovery || !!access.allowedTools;
|
|
941
|
+
if (stableNative && !this.replyPresentations.has(agent) && typeof agent.ctx.tools?.presentAs === 'function') {
|
|
942
|
+
this.replyPresentations.set(agent, agent.ctx.tools.presentAs('native'));
|
|
943
|
+
presentationChanged = true;
|
|
944
|
+
}
|
|
945
|
+
else if (!stableNative && this.replyPresentations.has(agent)) {
|
|
946
|
+
this.replyPresentations.get(agent)();
|
|
947
|
+
this.replyPresentations.delete(agent);
|
|
948
|
+
presentationChanged = true;
|
|
949
|
+
}
|
|
768
950
|
const installed = this.restoredCompositions.get(agent.session.id);
|
|
769
951
|
if (installed?.agent !== agent || installed.revision !== configuration.revision) {
|
|
770
952
|
const job = (async () => {
|
|
@@ -783,24 +965,48 @@ export class PartnerAgentRuntime {
|
|
|
783
965
|
}
|
|
784
966
|
return true;
|
|
785
967
|
}
|
|
786
|
-
return
|
|
968
|
+
return presentationChanged;
|
|
787
969
|
}
|
|
970
|
+
replyPresentations = new Map();
|
|
788
971
|
contextRevisions = new WeakMap();
|
|
972
|
+
admittedInputs = new WeakMap();
|
|
973
|
+
/** Projection only: the host commits admitted input after prompt preparation. */
|
|
974
|
+
inputEvents(agent) {
|
|
975
|
+
const events = agent.session.snapshotEvents();
|
|
976
|
+
const pending = this.admittedInputs?.get(agent) ?? [];
|
|
977
|
+
if (!pending.length)
|
|
978
|
+
return events;
|
|
979
|
+
const committed = new Set(events.flatMap(e => e.type === 'user/message' ? [e.data.id] : []));
|
|
980
|
+
return [...events, ...pending.filter(m => !committed.has(m.id)).map((data, index) => ({
|
|
981
|
+
type: 'user/message', data, seq: (agent.session.seq + index + 1), time: Date.now(), surfaceOp: 'append',
|
|
982
|
+
}))];
|
|
983
|
+
}
|
|
789
984
|
async prepareConversationContext(agent, signal) {
|
|
790
985
|
const config = this.configurations.forSession(agent.session.id);
|
|
791
986
|
if (!config)
|
|
792
987
|
return false;
|
|
793
988
|
const { companion, route } = config;
|
|
794
|
-
|
|
989
|
+
const state = this.store.snapshot();
|
|
990
|
+
const relationEvents = state.channels?.some(c => c.platform === 'qq' && state.sessions.some(r => r.sessionId === agent.session.id && r.channelId === c.id))
|
|
991
|
+
? this.inputEvents(agent) : undefined;
|
|
992
|
+
const identityRelation = relationEvents ? currentQqIdentityRelation(agent.session.id, relationEvents, state.sessions, state.channels) : undefined;
|
|
993
|
+
const committedMessages = agent.session.deriveMessages?.() ?? [];
|
|
994
|
+
const committedIds = new Set(committedMessages.map(m => m.id));
|
|
995
|
+
const messages = [...committedMessages, ...(this.admittedInputs?.get(agent) ?? []).filter(m => !committedIds.has(m.id))];
|
|
996
|
+
const last = [...messages].reverse().find(m => m.role === 'user' && m.source.kind === 'user');
|
|
997
|
+
const query = last && last.role === 'user' ? textContent(last.content) : '';
|
|
998
|
+
if (!identityRelation && !relationshipTextForIdentity(companion) && (!this.memory || !companion.automation.memory.enabled) && !companion.characterCard?.enabled && !renderSleepContext(companion, route.id) && !personaReferenceRequested(query)) {
|
|
795
999
|
this.characterContexts.get(agent.session.id)?.();
|
|
796
1000
|
this.characterContexts.delete(agent.session.id);
|
|
797
1001
|
this.contextRevisions.delete(agent);
|
|
798
1002
|
return false;
|
|
799
1003
|
}
|
|
800
|
-
const
|
|
801
|
-
const
|
|
802
|
-
const
|
|
803
|
-
const
|
|
1004
|
+
const events = relationEvents ?? this.inputEvents(agent);
|
|
1005
|
+
const origin = [...events].reverse().find(e => e.type === 'user/message' && (e.data.source.kind === 'user' || isIdleContactNotice(e) || isInternalTaskNotice(e)));
|
|
1006
|
+
const autonomous = Boolean(origin && isIdleContactNotice(origin));
|
|
1007
|
+
const affectionAudience = currentAffectionAudience(state, companion.id, agent.session.id, events);
|
|
1008
|
+
const access = resolveToolAccess({ agent, state, events });
|
|
1009
|
+
const revision = `${JSON.stringify(access)}:${JSON.stringify(identityRelation)}:${config.revision}:${last?.id ?? ''}:${autonomous && origin?.type === 'user/message' ? origin.data.id : ''}:${JSON.stringify([companion.sleepState?.episode, companion.sleepState?.reception, Math.round(fatigueValue(companion))])}:${companion.sleepState?.dreams?.at(-1)?.id ?? ''}:${affectionAudience}:${companion.affection?.updatedAt ?? 0}`;
|
|
804
1010
|
if (this.contextRevisions.get(agent) === revision)
|
|
805
1011
|
return false;
|
|
806
1012
|
const scope = conversationMemoryScope(route, this.store.snapshot().sessions);
|
|
@@ -814,10 +1020,16 @@ export class PartnerAgentRuntime {
|
|
|
814
1020
|
consecutiveUserMessages++;
|
|
815
1021
|
}
|
|
816
1022
|
signal.throwIfAborted();
|
|
817
|
-
const
|
|
1023
|
+
const relationshipAllowed = !identityRelation || identityRelation.matched;
|
|
1024
|
+
const text = [origin?.type === 'user/message' && origin.data.source.kind === 'user' && access.sendTools && (!access.allowedTools || access.allowedTools.includes('partner_identity')) && personaReferenceRequested(query) ? renderPersonaReference() : '', access.allowedTools || !access.sendTools ? access.denialReason + ' 回复能力通过 partner_tools 查询和调用(未开启统一入口时使用原工具);语音能力为 partner_qq_reply;不能代办操作,不承诺使用私有能力。' : '',
|
|
1025
|
+
renderIdentityRelationship(companion, identityRelation ? { channel: 'qq', matched: identityRelation.matched } : { channel: 'default' }),
|
|
1026
|
+
renderAffection(companion, affectionAudience),
|
|
1027
|
+
renderCharacterContext(companion.characterCard, messages.slice(-6).filter(m => m.role === 'user' || m.role === 'assistant').map(m => textContent(m.content)).join('\n').slice(-12000), relationshipAllowed ? story : undefined),
|
|
818
1028
|
recalled ? renderMemory(recalled.relevant, recalled.connections, recalled.scenes, recalled.history) : '',
|
|
819
|
-
recalled ? renderProfile(recalled.profile) : '',
|
|
820
|
-
renderConversationTurn(companion.characterCard, consecutiveUserMessages),
|
|
1029
|
+
recalled && relationshipAllowed ? renderProfile(recalled.profile) : '',
|
|
1030
|
+
renderConversationTurn(companion.characterCard, consecutiveUserMessages, autonomous),
|
|
1031
|
+
renderQqIdentityRelation(identityRelation, autonomous && access.sendTools && !access.allowedTools),
|
|
1032
|
+
renderSleepContext(companion, this.store.snapshot().sessions.filter(r => r.companionId === companion.id && r.sessionId === route.sessionId).map(r => r.id), autonomous ? undefined : last?.id),
|
|
821
1033
|
].filter(Boolean).join('\n\n');
|
|
822
1034
|
this.characterContexts.get(agent.session.id)?.();
|
|
823
1035
|
// A changing .section() appends an entire system prompt on in-history routes.
|
|
@@ -907,10 +1119,25 @@ export class PartnerAgentRuntime {
|
|
|
907
1119
|
}
|
|
908
1120
|
return renewed;
|
|
909
1121
|
}
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
1122
|
+
companionActivity(companionId) {
|
|
1123
|
+
const activity = [];
|
|
1124
|
+
if (this.reflection?.isRunning(companionId))
|
|
1125
|
+
activity.push({ kind: 'memory', label: '后台记忆整理(不阻止群聊判断)' });
|
|
1126
|
+
if (this.observationJobs.has(companionId))
|
|
1127
|
+
activity.push({ kind: 'observation', label: '归档已完成的聊天' });
|
|
1128
|
+
if (this.agentSetupJobs.has(companionId))
|
|
1129
|
+
activity.push({ kind: 'setup', label: '准备伙伴会话' });
|
|
1130
|
+
const seen = new Set();
|
|
1131
|
+
for (const route of this.store.snapshot().sessions.filter(r => r.companionId === companionId)) {
|
|
1132
|
+
if (seen.has(route.sessionId))
|
|
1133
|
+
continue;
|
|
1134
|
+
seen.add(route.sessionId);
|
|
1135
|
+
if ((this.handles.get(route.sessionId)?.agent ?? this.ctx.agents.get(route.sessionId))?.status === 'running')
|
|
1136
|
+
activity.push({ kind: 'conversation', label: route.qqSessionTitle || (route.kind === 'local' ? '主会话正在运行' : '渠道会话正在运行'), sessionId: route.sessionId });
|
|
1137
|
+
}
|
|
1138
|
+
return activity;
|
|
913
1139
|
}
|
|
1140
|
+
isCompanionBusy(companionId) { return this.companionActivity(companionId).length > 0; }
|
|
914
1141
|
async releaseCompanion(companionId) {
|
|
915
1142
|
const sessions = this.store.snapshot().sessions.filter(item => item.companionId === companionId);
|
|
916
1143
|
await Promise.all(sessions.map(async (item) => {
|
|
@@ -936,7 +1163,11 @@ export class PartnerAgentRuntime {
|
|
|
936
1163
|
this.closed = true;
|
|
937
1164
|
this.disposeConfigurationListener();
|
|
938
1165
|
this.disposeAssemblyListener();
|
|
1166
|
+
this.disposeToolAuthority();
|
|
939
1167
|
this.disposeAgentListener();
|
|
1168
|
+
for (const dispose of this.replyPresentations.values())
|
|
1169
|
+
dispose();
|
|
1170
|
+
this.replyPresentations.clear();
|
|
940
1171
|
this.configurations.close();
|
|
941
1172
|
await Promise.allSettled(this.restoredCompositionJobs.values());
|
|
942
1173
|
for (const restored of this.restoredCompositions.values())
|
|
@@ -948,6 +1179,7 @@ export class PartnerAgentRuntime {
|
|
|
948
1179
|
this.restoredCompositionJobs.clear();
|
|
949
1180
|
await Promise.all([...this.handles.values()].map(handle => handle.dispose().catch(() => { })));
|
|
950
1181
|
this.handles.clear();
|
|
1182
|
+
this.latestPostReplyInput.clear();
|
|
951
1183
|
this.heartbeatQueues.clear();
|
|
952
1184
|
this.taskQueues.clear();
|
|
953
1185
|
this.steeringQueues.clear();
|
|
@@ -962,9 +1194,11 @@ export class PartnerAgentRuntime {
|
|
|
962
1194
|
restored?.dispose();
|
|
963
1195
|
this.restoredCompositions.delete(sessionId);
|
|
964
1196
|
}
|
|
965
|
-
async drive(companion, channelId, userId, message, progress) {
|
|
1197
|
+
async drive(companion, channelId, userId, message, progress, queuedAt = Date.now()) {
|
|
966
1198
|
reportProgress(progress, 'working');
|
|
967
|
-
const
|
|
1199
|
+
const enteredAt = Date.now();
|
|
1200
|
+
let submittedAt = enteredAt, modelEndedAt = enteredAt, lastTurnEnd;
|
|
1201
|
+
const session = await this.ensureSession(companion, channelId, userId, message.sessionTitle);
|
|
968
1202
|
const agent = await this.ensureAgent(companion, session);
|
|
969
1203
|
if (agent.status !== 'idle')
|
|
970
1204
|
await agent.whenIdle();
|
|
@@ -976,10 +1210,13 @@ export class PartnerAgentRuntime {
|
|
|
976
1210
|
source: { kind: 'plugin:@lemoncat7/dsh-partner', plugin: '@lemoncat7/dsh-partner', form: 'notice', summary: '伙伴想起了与当前消息相关的挂念' },
|
|
977
1211
|
}));
|
|
978
1212
|
const startSeq = agent.session.seq;
|
|
1213
|
+
let deliveredUserMessageId = '';
|
|
979
1214
|
let directProgress = false;
|
|
980
|
-
const detach =
|
|
1215
|
+
const detach = this.ctx.on('session/event', (current, event) => {
|
|
981
1216
|
if (current.id !== agent.session.id || event.seq < startSeq)
|
|
982
1217
|
return;
|
|
1218
|
+
if (event.type === 'turn/end')
|
|
1219
|
+
lastTurnEnd = event.time;
|
|
983
1220
|
if (event.type === 'turn/start' || event.type === 'turn/end' || (isInternalTaskNotice(event) && !isRequirementDeliveryReceipt(event)))
|
|
984
1221
|
directProgress = false;
|
|
985
1222
|
if (event.type === 'user/message' && event.data.source.kind === 'user')
|
|
@@ -992,28 +1229,38 @@ export class PartnerAgentRuntime {
|
|
|
992
1229
|
if (activity)
|
|
993
1230
|
reportProgress(progress, activity);
|
|
994
1231
|
}
|
|
995
|
-
})
|
|
1232
|
+
});
|
|
996
1233
|
try {
|
|
997
1234
|
const userMessage = createUserMessage({
|
|
998
1235
|
content: inbound.content,
|
|
999
1236
|
source: { kind: 'user' },
|
|
1000
1237
|
});
|
|
1238
|
+
deliveredUserMessageId = userMessage.id;
|
|
1001
1239
|
await this.rememberMessageOrigin(session, userMessage.id);
|
|
1240
|
+
submittedAt = Date.now();
|
|
1002
1241
|
agent.followup(userMessage);
|
|
1003
1242
|
await agent.whenIdle();
|
|
1243
|
+
modelEndedAt = Date.now();
|
|
1004
1244
|
}
|
|
1005
1245
|
finally {
|
|
1006
1246
|
detach();
|
|
1007
1247
|
}
|
|
1008
1248
|
const response = channelReplyPartsAfter(agent.session.snapshotEvents(), startSeq);
|
|
1249
|
+
const stateStarted = Date.now();
|
|
1009
1250
|
await this.store.update(state => {
|
|
1010
1251
|
for (const target of state.sessions)
|
|
1011
1252
|
if (target.sessionId === session.sessionId)
|
|
1012
1253
|
target.lastMessageAt = Date.now();
|
|
1013
1254
|
});
|
|
1255
|
+
const stateEnded = Date.now();
|
|
1014
1256
|
if (deferred && deferred.length > 0)
|
|
1015
1257
|
await this.concerns?.markMentioned(companion.id, deferred.map(item => item.id)).catch(() => { });
|
|
1016
|
-
|
|
1258
|
+
const reply = await prepareChannelReply(response, session.cwd ?? this.defaultCwd);
|
|
1259
|
+
reply.postReplySource = { routeId: session.id, sessionId: session.sessionId, userMessageId: deliveredUserMessageId };
|
|
1260
|
+
if (this.store.snapshot().channels.some(c => c.id === channelId && c.platform === 'qq'))
|
|
1261
|
+
this.ctx.logger?.info?.(`[qq-latency] ${JSON.stringify({ stage: 'agent', sessionId: session.sessionId, inputPrepMs: submittedAt - enteredAt, modelMs: modelEndedAt - submittedAt, afterModelMs: Date.now() - modelEndedAt })}`);
|
|
1262
|
+
setReplyTiming(reply, { queueMs: enteredAt - queuedAt, inputPrepMs: submittedAt - enteredAt, modelAndIdleMs: modelEndedAt - submittedAt, idleAfterTurnEndMs: lastTurnEnd === undefined ? 0 : Math.max(0, modelEndedAt - lastTurnEnd), stateCommitMs: stateEnded - stateStarted, outboundPrepMs: Date.now() - stateEnded });
|
|
1263
|
+
return reply;
|
|
1017
1264
|
}
|
|
1018
1265
|
async rememberMessageOrigin(route, messageId) {
|
|
1019
1266
|
await this.store.update(state => {
|
|
@@ -1049,28 +1296,78 @@ export class PartnerAgentRuntime {
|
|
|
1049
1296
|
content.push({ type: 'text', text: '[渠道附件消息]' });
|
|
1050
1297
|
return { content, query: [message.text, ...message.attachments.map(item => item.name)].filter(Boolean).join(' ') };
|
|
1051
1298
|
}
|
|
1052
|
-
async
|
|
1299
|
+
async reconcileQqSessions() {
|
|
1300
|
+
const state = this.store.snapshot();
|
|
1301
|
+
// Startup only: workers have not started, so stale leases can be recovered.
|
|
1302
|
+
for (const route of state.sessions.filter(r => r.kind === 'channel' && state.channels.some(c => c.id === r.channelId && c.platform === 'qq'))) {
|
|
1303
|
+
const companion = state.companions.find(c => c.id === route.companionId);
|
|
1304
|
+
if (companion)
|
|
1305
|
+
await this.ensureSession(companion, route.channelId, route.userId, undefined, true);
|
|
1306
|
+
}
|
|
1307
|
+
}
|
|
1308
|
+
async ensureSession(companion, channelId, userId, title, recoverLeases = false) {
|
|
1053
1309
|
const primary = await this.ensureLocalSessionRecord(companion.id);
|
|
1054
|
-
const
|
|
1055
|
-
|
|
1310
|
+
const snapshot = this.store.snapshot(), channel = snapshot.channels.find(c => c.id === channelId);
|
|
1311
|
+
const existing = snapshot.sessions.find(item => item.channelId === channelId && item.userId === userId && item.companionId === companion.id);
|
|
1312
|
+
const isQq = channel?.platform === 'qq', target = qqSessionTarget(userId);
|
|
1313
|
+
if (!isQq && existing?.sessionId === primary.sessionId && canReuseSession(existing, companion.id, this.ctx.workspaceRegistry.archivedSessionIds))
|
|
1056
1314
|
return existing;
|
|
1057
|
-
const
|
|
1058
|
-
const
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
userId,
|
|
1063
|
-
companionId: companion.id,
|
|
1064
|
-
sessionId: primary.sessionId,
|
|
1065
|
-
cwd: primary.cwd ?? partnerCwd(this.defaultCwd, companion.id),
|
|
1066
|
-
lastMessageAt: now,
|
|
1067
|
-
};
|
|
1068
|
-
await this.memory?.mergeScopes(companion.id, conversationMemoryScope(primary, [primary]), [memoryScope(channelId, userId)]);
|
|
1315
|
+
const sharedScope = conversationMemoryScope(primary, [primary]);
|
|
1316
|
+
const sources = isQq ? snapshot.sessions.filter(r => r.channelId === channelId && r.companionId === companion.id && qqSessionTarget(r.userId) === target && r.memoryScopeId !== sharedScope).map(r => memoryScope(r.channelId, r.userId)) : [];
|
|
1317
|
+
if (existing?.memoryScopeId !== sharedScope || sources.length)
|
|
1318
|
+
await this.memory?.mergeScopes(companion.id, sharedScope, [...new Set([...sources, memoryScope(channelId, userId)])], recoverLeases);
|
|
1319
|
+
let result;
|
|
1069
1320
|
await this.store.update(state => {
|
|
1070
|
-
|
|
1071
|
-
state.
|
|
1321
|
+
const currentChannel = state.channels.find(c => c.id === channelId);
|
|
1322
|
+
if (this.store.isCompanionRemoving(companion.id) || !state.companions.some(c => c.id === companion.id))
|
|
1323
|
+
throw Error('伙伴已移除');
|
|
1324
|
+
const main = !isQq || qqSettings(currentChannel?.qq).mainSessionPeers.includes(target);
|
|
1325
|
+
const current = state.sessions.find(r => r.channelId === channelId && r.userId === userId && r.companionId === companion.id);
|
|
1326
|
+
const peers = state.sessions.filter(r => r.channelId === channelId && r.companionId === companion.id && qqSessionTarget(r.userId) === target).sort((a, b) => b.lastMessageAt - a.lastMessageAt);
|
|
1327
|
+
const archived = this.ctx.workspaceRegistry.archivedSessionIds;
|
|
1328
|
+
// Prefer an active shared group route, then its saved separate conversation.
|
|
1329
|
+
const separate = peers.find(r => r.qqSessionMode !== 'main' && r.sessionId !== primary.sessionId && !archived.includes(r.sessionId));
|
|
1330
|
+
const saved = peers.find(r => r.qqSeparateSessionId && !archived.includes(r.qqSeparateSessionId));
|
|
1331
|
+
const separateId = separate?.sessionId ?? saved?.qqSeparateSessionId ?? `session-${randomUUID()}`;
|
|
1332
|
+
const separateCwd = separate?.cwd ?? saved?.qqSeparateCwd ?? partnerCwd(this.defaultCwd, companion.id);
|
|
1333
|
+
const sessionId = main ? primary.sessionId : separateId;
|
|
1334
|
+
const name = title?.replace(/[\x00-\x1f\x7f]/g, ' ').trim().slice(0, 140) || peers.find(r => r.qqSessionTitle)?.qqSessionTitle || `${target.startsWith('group:') ? '群聊' : '私聊'} · ${target.split(':')[1]}`;
|
|
1335
|
+
const route = { ...current, id: current?.id ?? `route-${randomUUID()}`, kind: 'channel', channelId, userId, companionId: companion.id, sessionId, cwd: main ? (primary.cwd ?? partnerCwd(this.defaultCwd, companion.id)) : separateCwd, lastMessageAt: current?.lastMessageAt ?? Date.now(),
|
|
1336
|
+
...(isQq ? { memoryScopeId: sharedScope, qqSessionTarget: target, qqSessionMode: main ? 'main' : 'separate', qqSeparateSessionId: separateId, qqSeparateCwd: separateCwd, qqSessionTitle: name } : {}) };
|
|
1337
|
+
const applied = peers.find(r => r.sessionId === sessionId && r.qqAppliedTitle)?.qqAppliedTitle;
|
|
1338
|
+
if (applied)
|
|
1339
|
+
route.qqAppliedTitle = applied;
|
|
1340
|
+
if (current?.sessionId !== sessionId)
|
|
1341
|
+
delete route.inboundMessageIds;
|
|
1342
|
+
// One group context for all members, with separate delivery/grant identities.
|
|
1343
|
+
if (isQq)
|
|
1344
|
+
for (const peer of peers) {
|
|
1345
|
+
if (peer.sessionId !== sessionId)
|
|
1346
|
+
delete peer.inboundMessageIds;
|
|
1347
|
+
Object.assign(peer, { sessionId, cwd: route.cwd, memoryScopeId: sharedScope, qqSessionTarget: target, qqSessionMode: route.qqSessionMode, qqSeparateSessionId: separateId, qqSeparateCwd: separateCwd, qqSessionTitle: name });
|
|
1348
|
+
}
|
|
1349
|
+
state.sessions = state.sessions.filter(r => !(r.channelId === channelId && r.userId === userId));
|
|
1350
|
+
state.sessions.push(route);
|
|
1351
|
+
result = route;
|
|
1072
1352
|
});
|
|
1073
|
-
return
|
|
1353
|
+
return result;
|
|
1354
|
+
}
|
|
1355
|
+
async nameQqSession(agent, route) {
|
|
1356
|
+
if (route.qqSessionMode !== 'separate' || !route.qqSessionTitle)
|
|
1357
|
+
return;
|
|
1358
|
+
const titles = this.ctx.get('sessionTitle');
|
|
1359
|
+
if (!titles)
|
|
1360
|
+
return;
|
|
1361
|
+
const current = titles.get(agent.session);
|
|
1362
|
+
// Respect a user-edited title; only refresh our own generated names.
|
|
1363
|
+
if (current?.source.kind === 'user' && current.title !== route.qqAppliedTitle)
|
|
1364
|
+
return;
|
|
1365
|
+
if (current?.title === route.qqSessionTitle)
|
|
1366
|
+
return;
|
|
1367
|
+
titles.rename(agent.session, route.qqSessionTitle);
|
|
1368
|
+
await this.store.update(state => { for (const peer of state.sessions)
|
|
1369
|
+
if (peer.sessionId === route.sessionId)
|
|
1370
|
+
peer.qqAppliedTitle = route.qqSessionTitle; });
|
|
1074
1371
|
}
|
|
1075
1372
|
async ensureAgent(companion, route) {
|
|
1076
1373
|
if (this.store.isCompanionRemoving(companion.id) || !this.store.snapshot().companions.some(item => item.id === companion.id))
|
|
@@ -1094,11 +1391,13 @@ export class PartnerAgentRuntime {
|
|
|
1094
1391
|
const held = this.handles.get(route.sessionId);
|
|
1095
1392
|
if (held !== undefined) {
|
|
1096
1393
|
await this.attachSession(companion, route);
|
|
1394
|
+
await this.nameQqSession(held.agent, route);
|
|
1097
1395
|
return held.agent;
|
|
1098
1396
|
}
|
|
1099
1397
|
const live = this.ctx.agents.get(route.sessionId);
|
|
1100
1398
|
if (live !== undefined) {
|
|
1101
1399
|
await this.attachSession(companion, route);
|
|
1400
|
+
await this.nameQqSession(live, route);
|
|
1102
1401
|
return live;
|
|
1103
1402
|
}
|
|
1104
1403
|
const options = resolveAgentOptions(this.ctx.agentDefaultModel, companion);
|
|
@@ -1134,6 +1433,7 @@ export class PartnerAgentRuntime {
|
|
|
1134
1433
|
}
|
|
1135
1434
|
this.handles.set(route.sessionId, handle);
|
|
1136
1435
|
await this.attachSession(companion, route);
|
|
1436
|
+
await this.nameQqSession(handle.agent, route);
|
|
1137
1437
|
return handle.agent;
|
|
1138
1438
|
}
|
|
1139
1439
|
async composeRestoredAgent(agent, companion, route, revision, signal) {
|
|
@@ -1157,7 +1457,7 @@ export class PartnerAgentRuntime {
|
|
|
1157
1457
|
try {
|
|
1158
1458
|
// Native DEPLOYMENT_PERSONA_SUFFIX: delivery style follows the general work preset,
|
|
1159
1459
|
// while all tool definitions and execution/permission policies remain present.
|
|
1160
|
-
disposers.push(agentCtx.systemPrompt.section({ name: 'partner-identity', order: companion.characterCard?.enabled ? 10200 : -10, interpolate: false, text: renderPartnerPersona(companion, route.kind === 'local' ? 'local' : 'conversation') }));
|
|
1460
|
+
disposers.push(agentCtx.systemPrompt.section({ name: 'partner-identity', order: companion.characterCard?.enabled ? 10200 : -10, interpolate: false, text: renderPartnerPersona(companion, route.kind === 'local' ? 'local' : 'conversation', false) }));
|
|
1161
1461
|
disposers.push(agentCtx.systemPrompt.section({ name: 'partner-tool-routing', order: -9, text: renderToolProtocol() }));
|
|
1162
1462
|
if (this.memory)
|
|
1163
1463
|
disposers.push(agentCtx.tools.register({
|
|
@@ -1371,6 +1671,8 @@ async function resolveHeartbeatFileAccess(cwd, concerns) {
|
|
|
1371
1671
|
return { root, writable };
|
|
1372
1672
|
}
|
|
1373
1673
|
export function heartbeatToolDenial(name, argumentsValue, access) {
|
|
1674
|
+
if (name === 'knowledge')
|
|
1675
|
+
return observationKnowledgeEntryDenial(argumentsValue);
|
|
1374
1676
|
if (['knowledge_note_references', 'knowledge_note_read', 'knowledge_note_update'].includes(name))
|
|
1375
1677
|
return '关注不处理依据中的笔记引用;记录仅使用手动配置的 recordTarget 和专用记录工具。';
|
|
1376
1678
|
if (!isRecord(argumentsValue))
|