@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.
Files changed (389) hide show
  1. package/README.md +3 -0
  2. package/docs/THIRD_PARTY_LICENSES.md +1 -1
  3. package/docs/character-cards-and-renewal.md +1 -1
  4. package/docs/qq-attention.md +51 -0
  5. package/docs/qq-channel.md +80 -0
  6. package/docs/releases/2.11.7.md +9 -0
  7. package/docs/releases/2.12.0.md +14 -0
  8. package/docs/releases/2.13.0.md +9 -0
  9. package/docs/releases/2.14.0.md +9 -0
  10. package/docs/releases/2.15.0.md +9 -0
  11. package/docs/releases/2.15.1.md +9 -0
  12. package/docs/releases/2.16.0.md +18 -0
  13. package/docs/releases/2.17.0.md +23 -0
  14. package/docs/releases/2.17.1.md +9 -0
  15. package/docs/releases/2.18.0.md +9 -0
  16. package/docs/releases/2.18.1.md +5 -0
  17. package/docs/releases/2.19.0.md +14 -0
  18. package/docs/releases/2.19.1.md +7 -0
  19. package/docs/releases/2.19.2.md +9 -0
  20. package/docs/releases/2.19.3.md +5 -0
  21. package/docs/releases/2.20.0.md +10 -0
  22. package/docs/releases/2.20.1.md +8 -0
  23. package/docs/releases/2.21.0.md +7 -0
  24. package/docs/releases/2.21.1.md +8 -0
  25. package/docs/releases/2.21.10.md +5 -0
  26. package/docs/releases/2.21.11.md +7 -0
  27. package/docs/releases/2.21.2.md +10 -0
  28. package/docs/releases/2.21.3.md +7 -0
  29. package/docs/releases/2.21.4.md +8 -0
  30. package/docs/releases/2.21.5.md +15 -0
  31. package/docs/releases/2.21.6.md +6 -0
  32. package/docs/releases/2.21.7.md +11 -0
  33. package/docs/releases/2.21.8.md +9 -0
  34. package/docs/releases/2.21.9.md +6 -0
  35. package/docs/tool-entry-compatibility.md +11 -0
  36. package/lib/agent-runtime.d.ts +25 -1
  37. package/lib/agent-runtime.d.ts.map +1 -1
  38. package/lib/agent-runtime.js +364 -62
  39. package/lib/agent-runtime.js.map +1 -1
  40. package/lib/api.d.ts +1 -0
  41. package/lib/api.d.ts.map +1 -1
  42. package/lib/api.js +94 -27
  43. package/lib/api.js.map +1 -1
  44. package/lib/channel-message.d.ts +14 -0
  45. package/lib/channel-message.d.ts.map +1 -1
  46. package/lib/channel-message.js.map +1 -1
  47. package/lib/channels/delivery-policy.js +1 -1
  48. package/lib/channels/delivery-policy.js.map +1 -1
  49. package/lib/channels/direct/dispatcher.d.ts +2 -2
  50. package/lib/channels/direct/dispatcher.d.ts.map +1 -1
  51. package/lib/channels/direct/dispatcher.js +13 -4
  52. package/lib/channels/direct/dispatcher.js.map +1 -1
  53. package/lib/channels/direct/login.d.ts.map +1 -1
  54. package/lib/channels/direct/login.js +4 -0
  55. package/lib/channels/direct/login.js.map +1 -1
  56. package/lib/channels/direct/media.d.ts +2 -0
  57. package/lib/channels/direct/media.d.ts.map +1 -1
  58. package/lib/channels/direct/media.js +27 -23
  59. package/lib/channels/direct/media.js.map +1 -1
  60. package/lib/channels/direct/transport.d.ts +26 -3
  61. package/lib/channels/direct/transport.d.ts.map +1 -1
  62. package/lib/channels/direct/transport.js +31 -5
  63. package/lib/channels/direct/transport.js.map +1 -1
  64. package/lib/channels/manager.d.ts +26 -0
  65. package/lib/channels/manager.d.ts.map +1 -1
  66. package/lib/channels/manager.js +538 -148
  67. package/lib/channels/manager.js.map +1 -1
  68. package/lib/channels/notification-delivery.d.ts +4 -0
  69. package/lib/channels/notification-delivery.d.ts.map +1 -1
  70. package/lib/channels/notification-delivery.js +15 -5
  71. package/lib/channels/notification-delivery.js.map +1 -1
  72. package/lib/channels/notification-route.d.ts.map +1 -1
  73. package/lib/channels/notification-route.js +10 -4
  74. package/lib/channels/notification-route.js.map +1 -1
  75. package/lib/channels/notification-timeout.d.ts.map +1 -1
  76. package/lib/channels/notification-timeout.js +2 -0
  77. package/lib/channels/notification-timeout.js.map +1 -1
  78. package/lib/channels/outbound-media.d.ts.map +1 -1
  79. package/lib/channels/outbound-media.js +2 -0
  80. package/lib/channels/outbound-media.js.map +1 -1
  81. package/lib/channels/qq/action-catalog.d.ts +38 -0
  82. package/lib/channels/qq/action-catalog.d.ts.map +1 -0
  83. package/lib/channels/qq/action-catalog.js +2731 -0
  84. package/lib/channels/qq/action-catalog.js.map +1 -0
  85. package/lib/channels/qq/actions.d.ts +21 -0
  86. package/lib/channels/qq/actions.d.ts.map +1 -0
  87. package/lib/channels/qq/actions.js +226 -0
  88. package/lib/channels/qq/actions.js.map +1 -0
  89. package/lib/channels/qq/affection-adapter.d.ts +3 -0
  90. package/lib/channels/qq/affection-adapter.d.ts.map +1 -0
  91. package/lib/channels/qq/affection-adapter.js +16 -0
  92. package/lib/channels/qq/affection-adapter.js.map +1 -0
  93. package/lib/channels/qq/attention-classifier.d.ts +10 -0
  94. package/lib/channels/qq/attention-classifier.d.ts.map +1 -0
  95. package/lib/channels/qq/attention-classifier.js +62 -0
  96. package/lib/channels/qq/attention-classifier.js.map +1 -0
  97. package/lib/channels/qq/authority.d.ts +11 -0
  98. package/lib/channels/qq/authority.d.ts.map +1 -0
  99. package/lib/channels/qq/authority.js +50 -0
  100. package/lib/channels/qq/authority.js.map +1 -0
  101. package/lib/channels/qq/autonomy.d.ts +30 -0
  102. package/lib/channels/qq/autonomy.d.ts.map +1 -0
  103. package/lib/channels/qq/autonomy.js +88 -0
  104. package/lib/channels/qq/autonomy.js.map +1 -0
  105. package/lib/channels/qq/catalog.d.ts +14 -0
  106. package/lib/channels/qq/catalog.d.ts.map +1 -0
  107. package/lib/channels/qq/catalog.js +34 -0
  108. package/lib/channels/qq/catalog.js.map +1 -0
  109. package/lib/channels/qq/connection.d.ts +28 -0
  110. package/lib/channels/qq/connection.d.ts.map +1 -0
  111. package/lib/channels/qq/connection.js +200 -0
  112. package/lib/channels/qq/connection.js.map +1 -0
  113. package/lib/channels/qq/identity-binding.d.ts +14 -0
  114. package/lib/channels/qq/identity-binding.d.ts.map +1 -0
  115. package/lib/channels/qq/identity-binding.js +60 -0
  116. package/lib/channels/qq/identity-binding.js.map +1 -0
  117. package/lib/channels/qq/latency.d.ts +22 -0
  118. package/lib/channels/qq/latency.d.ts.map +1 -0
  119. package/lib/channels/qq/latency.js +21 -0
  120. package/lib/channels/qq/latency.js.map +1 -0
  121. package/lib/channels/qq/message.d.ts +6 -0
  122. package/lib/channels/qq/message.d.ts.map +1 -0
  123. package/lib/channels/qq/message.js +49 -0
  124. package/lib/channels/qq/message.js.map +1 -0
  125. package/lib/channels/qq/reply-rules.d.ts +30 -0
  126. package/lib/channels/qq/reply-rules.d.ts.map +1 -0
  127. package/lib/channels/qq/reply-rules.js +72 -0
  128. package/lib/channels/qq/reply-rules.js.map +1 -0
  129. package/lib/channels/qq/reply-tool.d.ts +6 -0
  130. package/lib/channels/qq/reply-tool.d.ts.map +1 -0
  131. package/lib/channels/qq/reply-tool.js +124 -0
  132. package/lib/channels/qq/reply-tool.js.map +1 -0
  133. package/lib/channels/qq/settings.d.ts +42 -0
  134. package/lib/channels/qq/settings.d.ts.map +1 -0
  135. package/lib/channels/qq/settings.js +68 -0
  136. package/lib/channels/qq/settings.js.map +1 -0
  137. package/lib/channels/qq/sticker-adapter.d.ts +5 -0
  138. package/lib/channels/qq/sticker-adapter.d.ts.map +1 -0
  139. package/lib/channels/qq/sticker-adapter.js +43 -0
  140. package/lib/channels/qq/sticker-adapter.js.map +1 -0
  141. package/lib/channels/qq/transport.d.ts +29 -0
  142. package/lib/channels/qq/transport.d.ts.map +1 -0
  143. package/lib/channels/qq/transport.js +187 -0
  144. package/lib/channels/qq/transport.js.map +1 -0
  145. package/lib/channels/reply-access.d.ts +25 -0
  146. package/lib/channels/reply-access.d.ts.map +1 -0
  147. package/lib/channels/reply-access.js +29 -0
  148. package/lib/channels/reply-access.js.map +1 -0
  149. package/lib/channels/weixin/connect.d.ts +3 -1
  150. package/lib/channels/weixin/connect.d.ts.map +1 -1
  151. package/lib/character/activity-tool.d.ts +7 -0
  152. package/lib/character/activity-tool.d.ts.map +1 -0
  153. package/lib/character/activity-tool.js +38 -0
  154. package/lib/character/activity-tool.js.map +1 -0
  155. package/lib/character/affection-adapter.d.ts +13 -0
  156. package/lib/character/affection-adapter.d.ts.map +1 -0
  157. package/lib/character/affection-adapter.js +13 -0
  158. package/lib/character/affection-adapter.js.map +1 -0
  159. package/lib/character/affection-context.d.ts +7 -0
  160. package/lib/character/affection-context.d.ts.map +1 -0
  161. package/lib/character/affection-context.js +33 -0
  162. package/lib/character/affection-context.js.map +1 -0
  163. package/lib/character/affection.d.ts +47 -0
  164. package/lib/character/affection.d.ts.map +1 -0
  165. package/lib/character/affection.js +41 -0
  166. package/lib/character/affection.js.map +1 -0
  167. package/lib/character/conversation-behavior.d.ts +4 -4
  168. package/lib/character/conversation-behavior.d.ts.map +1 -1
  169. package/lib/character/conversation-behavior.js +26 -23
  170. package/lib/character/conversation-behavior.js.map +1 -1
  171. package/lib/character/domain.d.ts +5 -0
  172. package/lib/character/domain.d.ts.map +1 -1
  173. package/lib/character/domain.js +21 -4
  174. package/lib/character/domain.js.map +1 -1
  175. package/lib/character/fatigue.d.ts +15 -0
  176. package/lib/character/fatigue.d.ts.map +1 -0
  177. package/lib/character/fatigue.js +37 -0
  178. package/lib/character/fatigue.js.map +1 -0
  179. package/lib/character/identity-guide.d.ts +41 -0
  180. package/lib/character/identity-guide.d.ts.map +1 -0
  181. package/lib/character/identity-guide.js +20 -0
  182. package/lib/character/identity-guide.js.map +1 -0
  183. package/lib/character/idle-contact.d.ts +12 -5
  184. package/lib/character/idle-contact.d.ts.map +1 -1
  185. package/lib/character/idle-contact.js +61 -21
  186. package/lib/character/idle-contact.js.map +1 -1
  187. package/lib/character/persona-templates.d.ts +23 -0
  188. package/lib/character/persona-templates.d.ts.map +1 -0
  189. package/lib/character/persona-templates.js +33 -0
  190. package/lib/character/persona-templates.js.map +1 -0
  191. package/lib/character/post-reply.d.ts +19 -0
  192. package/lib/character/post-reply.d.ts.map +1 -0
  193. package/lib/character/post-reply.js +33 -0
  194. package/lib/character/post-reply.js.map +1 -0
  195. package/lib/character/preview.d.ts.map +1 -1
  196. package/lib/character/preview.js +9 -1
  197. package/lib/character/preview.js.map +1 -1
  198. package/lib/character/relationship-adapter.d.ts +18 -0
  199. package/lib/character/relationship-adapter.d.ts.map +1 -0
  200. package/lib/character/relationship-adapter.js +18 -0
  201. package/lib/character/relationship-adapter.js.map +1 -0
  202. package/lib/character/role-reference-data.d.ts.map +1 -0
  203. package/lib/character/role-reference-data.js.map +1 -0
  204. package/lib/character/sleep-service.d.ts +44 -0
  205. package/lib/character/sleep-service.d.ts.map +1 -0
  206. package/lib/character/sleep-service.js +359 -0
  207. package/lib/character/sleep-service.js.map +1 -0
  208. package/lib/character/sleep.d.ts +65 -0
  209. package/lib/character/sleep.d.ts.map +1 -0
  210. package/lib/character/sleep.js +45 -0
  211. package/lib/character/sleep.js.map +1 -0
  212. package/lib/character/state-policy.d.ts +4 -0
  213. package/lib/character/state-policy.d.ts.map +1 -0
  214. package/lib/character/state-policy.js +14 -0
  215. package/lib/character/state-policy.js.map +1 -0
  216. package/lib/character/store.d.ts.map +1 -1
  217. package/lib/character/store.js +3 -3
  218. package/lib/character/store.js.map +1 -1
  219. package/lib/chat/conversation.js +1 -1
  220. package/lib/chat/conversation.js.map +1 -1
  221. package/lib/chat/protocol.d.ts +8 -0
  222. package/lib/chat/protocol.d.ts.map +1 -1
  223. package/lib/chat/protocol.js +24 -5
  224. package/lib/chat/protocol.js.map +1 -1
  225. package/lib/chat/sticker-adapter.d.ts +34 -0
  226. package/lib/chat/sticker-adapter.d.ts.map +1 -0
  227. package/lib/chat/sticker-adapter.js +30 -0
  228. package/lib/chat/sticker-adapter.js.map +1 -0
  229. package/lib/chat/tools.d.ts +2 -1
  230. package/lib/chat/tools.d.ts.map +1 -1
  231. package/lib/chat/tools.js +59 -13
  232. package/lib/chat/tools.js.map +1 -1
  233. package/lib/client-api.d.ts +21 -1
  234. package/lib/client-api.d.ts.map +1 -1
  235. package/lib/client-api.js.map +1 -1
  236. package/lib/client.d.ts.map +1 -1
  237. package/lib/client.js +7007 -3166
  238. package/lib/client.js.map +4 -4
  239. package/lib/collaboration/composition.d.ts +2 -0
  240. package/lib/collaboration/composition.d.ts.map +1 -1
  241. package/lib/collaboration/composition.js +32 -10
  242. package/lib/collaboration/composition.js.map +1 -1
  243. package/lib/collaboration/tool-discovery.d.ts +12 -0
  244. package/lib/collaboration/tool-discovery.d.ts.map +1 -0
  245. package/lib/collaboration/tool-discovery.js +149 -0
  246. package/lib/collaboration/tool-discovery.js.map +1 -0
  247. package/lib/companions/identity-tool.d.ts +176 -0
  248. package/lib/companions/identity-tool.d.ts.map +1 -0
  249. package/lib/companions/identity-tool.js +69 -0
  250. package/lib/companions/identity-tool.js.map +1 -0
  251. package/lib/companions/identity.d.ts +24 -0
  252. package/lib/companions/identity.d.ts.map +1 -0
  253. package/lib/companions/identity.js +49 -0
  254. package/lib/companions/identity.js.map +1 -0
  255. package/lib/companions/management-tool.d.ts +1 -1
  256. package/lib/companions/management-tool.d.ts.map +1 -1
  257. package/lib/companions/management-tool.js +6 -3
  258. package/lib/companions/management-tool.js.map +1 -1
  259. package/lib/companions/management.d.ts +49 -10
  260. package/lib/companions/management.d.ts.map +1 -1
  261. package/lib/companions/management.js +21 -6
  262. package/lib/companions/management.js.map +1 -1
  263. package/lib/companions/service.js +1 -0
  264. package/lib/companions/service.js.map +1 -1
  265. package/lib/companions/session-configuration.js +2 -2
  266. package/lib/companions/session-configuration.js.map +1 -1
  267. package/lib/domain.d.ts +37 -1
  268. package/lib/domain.d.ts.map +1 -1
  269. package/lib/domain.js +6 -0
  270. package/lib/domain.js.map +1 -1
  271. package/lib/execution/agent-support.d.ts +1 -1
  272. package/lib/execution/agent-support.d.ts.map +1 -1
  273. package/lib/execution/agent-support.js +7 -2
  274. package/lib/execution/agent-support.js.map +1 -1
  275. package/lib/execution/operation-tool-access.d.ts +13 -0
  276. package/lib/execution/operation-tool-access.d.ts.map +1 -0
  277. package/lib/execution/operation-tool-access.js +49 -0
  278. package/lib/execution/operation-tool-access.js.map +1 -0
  279. package/lib/execution/service.d.ts.map +1 -1
  280. package/lib/execution/service.js +2 -10
  281. package/lib/execution/service.js.map +1 -1
  282. package/lib/execution/tool-access-adapter.d.ts +26 -0
  283. package/lib/execution/tool-access-adapter.d.ts.map +1 -0
  284. package/lib/execution/tool-access-adapter.js +25 -0
  285. package/lib/execution/tool-access-adapter.js.map +1 -0
  286. package/lib/heartbeat.js +1 -1
  287. package/lib/index.d.ts.map +1 -1
  288. package/lib/index.js +31 -2
  289. package/lib/index.js.map +1 -1
  290. package/lib/memory-domain.d.ts +1 -0
  291. package/lib/memory-domain.d.ts.map +1 -1
  292. package/lib/memory-prompt-context.js +1 -0
  293. package/lib/memory-prompt-context.js.map +1 -1
  294. package/lib/memory-reflection.d.ts.map +1 -1
  295. package/lib/memory-reflection.js +16 -5
  296. package/lib/memory-reflection.js.map +1 -1
  297. package/lib/memory-scope.d.ts +2 -1
  298. package/lib/memory-scope.d.ts.map +1 -1
  299. package/lib/memory-scope.js +3 -1
  300. package/lib/memory-scope.js.map +1 -1
  301. package/lib/memory-store.d.ts +1 -1
  302. package/lib/observation-knowledge-entry.d.ts +2 -0
  303. package/lib/observation-knowledge-entry.d.ts.map +1 -0
  304. package/lib/observation-knowledge-entry.js +13 -0
  305. package/lib/observation-knowledge-entry.js.map +1 -0
  306. package/lib/observation-loop.d.ts.map +1 -1
  307. package/lib/observation-loop.js +3 -0
  308. package/lib/observation-loop.js.map +1 -1
  309. package/lib/storage/split-state.d.ts +1 -0
  310. package/lib/storage/split-state.d.ts.map +1 -1
  311. package/lib/storage/split-state.js +9 -2
  312. package/lib/storage/split-state.js.map +1 -1
  313. package/lib/store.d.ts.map +1 -1
  314. package/lib/store.js +7 -0
  315. package/lib/store.js.map +1 -1
  316. package/lib/ui/affection-panel.d.ts +10 -0
  317. package/lib/ui/affection-panel.d.ts.map +1 -0
  318. package/lib/ui/affection-panel.js +44 -0
  319. package/lib/ui/affection-panel.js.map +1 -0
  320. package/lib/ui/capability-editor.d.ts.map +1 -1
  321. package/lib/ui/capability-editor.js +1 -1
  322. package/lib/ui/capability-editor.js.map +1 -1
  323. package/lib/ui/channels-panel.d.ts.map +1 -1
  324. package/lib/ui/channels-panel.js +14 -9
  325. package/lib/ui/channels-panel.js.map +1 -1
  326. package/lib/ui/character-editor.d.ts +3 -1
  327. package/lib/ui/character-editor.d.ts.map +1 -1
  328. package/lib/ui/character-editor.js +18 -17
  329. package/lib/ui/character-editor.js.map +1 -1
  330. package/lib/ui/character-memory-editor.d.ts +6 -1
  331. package/lib/ui/character-memory-editor.d.ts.map +1 -1
  332. package/lib/ui/character-memory-editor.js +17 -11
  333. package/lib/ui/character-memory-editor.js.map +1 -1
  334. package/lib/ui/companion-deletion.js +1 -1
  335. package/lib/ui/companion-deletion.js.map +1 -1
  336. package/lib/ui/companion-draft.d.ts +3 -0
  337. package/lib/ui/companion-draft.d.ts.map +1 -1
  338. package/lib/ui/companion-draft.js +1 -1
  339. package/lib/ui/companion-draft.js.map +1 -1
  340. package/lib/ui/companion-vitals.d.ts +6 -0
  341. package/lib/ui/companion-vitals.d.ts.map +1 -0
  342. package/lib/ui/companion-vitals.js +44 -0
  343. package/lib/ui/companion-vitals.js.map +1 -0
  344. package/lib/ui/identity-editor.d.ts.map +1 -1
  345. package/lib/ui/identity-editor.js +13 -6
  346. package/lib/ui/identity-editor.js.map +1 -1
  347. package/lib/ui/idle-contact-editor.d.ts.map +1 -1
  348. package/lib/ui/idle-contact-editor.js +3 -2
  349. package/lib/ui/idle-contact-editor.js.map +1 -1
  350. package/lib/ui/memory/memory-panel.d.ts.map +1 -1
  351. package/lib/ui/memory/memory-panel.js +5 -3
  352. package/lib/ui/memory/memory-panel.js.map +1 -1
  353. package/lib/ui/persona-template.d.ts +5 -0
  354. package/lib/ui/persona-template.d.ts.map +1 -0
  355. package/lib/ui/persona-template.js +7 -0
  356. package/lib/ui/persona-template.js.map +1 -0
  357. package/lib/ui/push-draft.d.ts +8 -8
  358. package/lib/ui/qq-capability-settings.d.ts +9 -0
  359. package/lib/ui/qq-capability-settings.d.ts.map +1 -0
  360. package/lib/ui/qq-capability-settings.js +24 -0
  361. package/lib/ui/qq-capability-settings.js.map +1 -0
  362. package/lib/ui/qq-channel-settings.d.ts +13 -0
  363. package/lib/ui/qq-channel-settings.d.ts.map +1 -0
  364. package/lib/ui/qq-channel-settings.js +52 -0
  365. package/lib/ui/qq-channel-settings.js.map +1 -0
  366. package/lib/ui/qq-connection-guide.d.ts +2 -0
  367. package/lib/ui/qq-connection-guide.d.ts.map +1 -0
  368. package/lib/ui/qq-connection-guide.js +5 -0
  369. package/lib/ui/qq-connection-guide.js.map +1 -0
  370. package/lib/ui/qq-identity-binding.d.ts +9 -0
  371. package/lib/ui/qq-identity-binding.d.ts.map +1 -0
  372. package/lib/ui/qq-identity-binding.js +6 -0
  373. package/lib/ui/qq-identity-binding.js.map +1 -0
  374. package/lib/ui/qq-voice-reply-settings.d.ts +9 -0
  375. package/lib/ui/qq-voice-reply-settings.d.ts.map +1 -0
  376. package/lib/ui/qq-voice-reply-settings.js +23 -0
  377. package/lib/ui/qq-voice-reply-settings.js.map +1 -0
  378. package/lib/ui/role-reference.d.ts.map +1 -1
  379. package/lib/ui/role-reference.js +3 -2
  380. package/lib/ui/role-reference.js.map +1 -1
  381. package/lib/ui/sleep-editor.d.ts +10 -0
  382. package/lib/ui/sleep-editor.d.ts.map +1 -0
  383. package/lib/ui/sleep-editor.js +55 -0
  384. package/lib/ui/sleep-editor.js.map +1 -0
  385. package/package.json +39 -5
  386. package/lib/ui/role-reference-data.d.ts.map +0 -1
  387. package/lib/ui/role-reference-data.js.map +0 -1
  388. /package/lib/{ui → character}/role-reference-data.d.ts +0 -0
  389. /package/lib/{ui → character}/role-reference-data.js +0 -0
@@ -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 || this.rebuildingAssemblies.has(context))
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
- await this.prepareAgentTurn(agent, turn, signal);
109
- return next();
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.isCompanionBusy(companionId) || this.queues.has(key) || this.taskQueues.has(key) || this.heartbeatQueues.has(key) || this.steeringQueues.has(key) || this.store.isCompanionRemoving(companionId))
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 route = state.sessions.find(r => r.id === companion?.idleContact?.routeId && r.companionId === companionId);
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.isCompanionBusy(companionId) || this.queues.has(key) || this.taskQueues.has(key))
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
- if (!idleContactDue(this.store.snapshot(), companion, user, Date.now(), timeZone))
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 current = this.store.snapshot().companions.find(c => c.id === companionId);
160
- if (!current?.idleContact?.enabled || current.updatedAt !== companion.updatedAt || this.isArchived(route))
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 assembly = await agent.ctx.systemPrompt.assemble(assembleContextFor(agent, signal));
342
- signal.throwIfAborted();
343
- assertWakeTools(entry, assembly.tools.map(tool => tool.name));
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
- if (this.preparedTurns.get(agent) === turn)
747
- return;
748
- await this.prepareAgentAssembly(agent, signal);
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 false;
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
- if ((!this.memory || !companion.automation.memory.enabled) && !companion.characterCard?.enabled) {
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 messages = agent.session.deriveMessages();
801
- const last = [...messages].reverse().find(m => m.role === 'user' && m.source.kind === 'user');
802
- const query = last && last.role === 'user' ? textContent(last.content) : '';
803
- const revision = `${config.revision}:${last?.id ?? ''}`;
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 text = [renderCharacterContext(companion.characterCard, messages.slice(-6).filter(m => m.role === 'user' || m.role === 'assistant').map(m => textContent(m.content)).join('\n').slice(-12000), story),
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
- isCompanionBusy(companionId) {
911
- return this.reflection?.isRunning(companionId) === true || this.observationJobs.has(companionId) || this.agentSetupJobs.has(companionId) || this.store.snapshot().sessions.some(route => route.companionId === companionId
912
- && (this.handles.get(route.sessionId)?.agent ?? this.ctx.agents.get(route.sessionId))?.status === 'running');
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 session = await this.ensureSession(companion, channelId, userId);
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 = progress ? this.ctx.on('session/event', (current, event) => {
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
- return prepareChannelReply(response, session.cwd ?? this.defaultCwd);
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 ensureSession(companion, channelId, userId) {
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 existing = this.store.snapshot().sessions.find(item => item.channelId === channelId && item.userId === userId);
1055
- if (existing !== undefined && existing.sessionId === primary.sessionId && canReuseSession(existing, companion.id, this.ctx.workspaceRegistry.archivedSessionIds))
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 now = Date.now();
1058
- const session = {
1059
- id: `route-${randomUUID()}`,
1060
- kind: 'channel',
1061
- channelId,
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
- state.sessions = state.sessions.filter(item => !(item.channelId === channelId && item.userId === userId));
1071
- state.sessions.push(session);
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 session;
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))