@zaofan/dsh-qqbot 0.6.0

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 (227) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +308 -0
  3. package/README_EN.md +273 -0
  4. package/client/qqbot-settings.js +1463 -0
  5. package/cordis.patch.yml +36 -0
  6. package/dist/api/group-admin.d.ts +112 -0
  7. package/dist/api/group-admin.d.ts.map +1 -0
  8. package/dist/api/group-admin.js +179 -0
  9. package/dist/api/group-admin.js.map +1 -0
  10. package/dist/channel-tools.d.ts +14 -0
  11. package/dist/channel-tools.d.ts.map +1 -0
  12. package/dist/channel-tools.js +880 -0
  13. package/dist/channel-tools.js.map +1 -0
  14. package/dist/commands/help.d.ts +11 -0
  15. package/dist/commands/help.d.ts.map +1 -0
  16. package/dist/commands/help.js +21 -0
  17. package/dist/commands/help.js.map +1 -0
  18. package/dist/commands/index.d.ts +13 -0
  19. package/dist/commands/index.d.ts.map +1 -0
  20. package/dist/commands/index.js +29 -0
  21. package/dist/commands/index.js.map +1 -0
  22. package/dist/commands/misc.d.ts +14 -0
  23. package/dist/commands/misc.d.ts.map +1 -0
  24. package/dist/commands/misc.js +39 -0
  25. package/dist/commands/misc.js.map +1 -0
  26. package/dist/commands/model.d.ts +7 -0
  27. package/dist/commands/model.d.ts.map +1 -0
  28. package/dist/commands/model.js +58 -0
  29. package/dist/commands/model.js.map +1 -0
  30. package/dist/commands/session.d.ts +10 -0
  31. package/dist/commands/session.d.ts.map +1 -0
  32. package/dist/commands/session.js +26 -0
  33. package/dist/commands/session.js.map +1 -0
  34. package/dist/commands/status.d.ts +8 -0
  35. package/dist/commands/status.d.ts.map +1 -0
  36. package/dist/commands/status.js +24 -0
  37. package/dist/commands/status.js.map +1 -0
  38. package/dist/commands/types.d.ts +12 -0
  39. package/dist/commands/types.d.ts.map +1 -0
  40. package/dist/commands/types.js +2 -0
  41. package/dist/commands/types.js.map +1 -0
  42. package/dist/config.d.ts +205 -0
  43. package/dist/config.d.ts.map +1 -0
  44. package/dist/config.js +203 -0
  45. package/dist/config.js.map +1 -0
  46. package/dist/features/chat-ledger.d.ts +53 -0
  47. package/dist/features/chat-ledger.d.ts.map +1 -0
  48. package/dist/features/chat-ledger.js +185 -0
  49. package/dist/features/chat-ledger.js.map +1 -0
  50. package/dist/features/face-tags.d.ts +17 -0
  51. package/dist/features/face-tags.d.ts.map +1 -0
  52. package/dist/features/face-tags.js +79 -0
  53. package/dist/features/face-tags.js.map +1 -0
  54. package/dist/features/group-join-request.d.ts +65 -0
  55. package/dist/features/group-join-request.d.ts.map +1 -0
  56. package/dist/features/group-join-request.js +190 -0
  57. package/dist/features/group-join-request.js.map +1 -0
  58. package/dist/features/history-store.d.ts +8 -0
  59. package/dist/features/history-store.d.ts.map +1 -0
  60. package/dist/features/history-store.js +24 -0
  61. package/dist/features/history-store.js.map +1 -0
  62. package/dist/features/qq-approval.d.ts +78 -0
  63. package/dist/features/qq-approval.d.ts.map +1 -0
  64. package/dist/features/qq-approval.js +165 -0
  65. package/dist/features/qq-approval.js.map +1 -0
  66. package/dist/features/schedule-store.d.ts +62 -0
  67. package/dist/features/schedule-store.d.ts.map +1 -0
  68. package/dist/features/schedule-store.js +253 -0
  69. package/dist/features/schedule-store.js.map +1 -0
  70. package/dist/features/scheduler.d.ts +9 -0
  71. package/dist/features/scheduler.d.ts.map +1 -0
  72. package/dist/features/scheduler.js +146 -0
  73. package/dist/features/scheduler.js.map +1 -0
  74. package/dist/features/sticker-gate.d.ts +90 -0
  75. package/dist/features/sticker-gate.d.ts.map +1 -0
  76. package/dist/features/sticker-gate.js +391 -0
  77. package/dist/features/sticker-gate.js.map +1 -0
  78. package/dist/features/sticker-store.d.ts +158 -0
  79. package/dist/features/sticker-store.d.ts.map +1 -0
  80. package/dist/features/sticker-store.js +676 -0
  81. package/dist/features/sticker-store.js.map +1 -0
  82. package/dist/features/sticker-tagger.d.ts +10 -0
  83. package/dist/features/sticker-tagger.d.ts.map +1 -0
  84. package/dist/features/sticker-tagger.js +139 -0
  85. package/dist/features/sticker-tagger.js.map +1 -0
  86. package/dist/gateway/bootstrap.d.ts +12 -0
  87. package/dist/gateway/bootstrap.d.ts.map +1 -0
  88. package/dist/gateway/bootstrap.js +346 -0
  89. package/dist/gateway/bootstrap.js.map +1 -0
  90. package/dist/gateway/debounce.d.ts +40 -0
  91. package/dist/gateway/debounce.d.ts.map +1 -0
  92. package/dist/gateway/debounce.js +262 -0
  93. package/dist/gateway/debounce.js.map +1 -0
  94. package/dist/gateway/index.d.ts +3 -0
  95. package/dist/gateway/index.d.ts.map +1 -0
  96. package/dist/gateway/index.js +3 -0
  97. package/dist/gateway/index.js.map +1 -0
  98. package/dist/gateway/middleware-setup.d.ts +12 -0
  99. package/dist/gateway/middleware-setup.d.ts.map +1 -0
  100. package/dist/gateway/middleware-setup.js +156 -0
  101. package/dist/gateway/middleware-setup.js.map +1 -0
  102. package/dist/index.d.ts +14 -0
  103. package/dist/index.d.ts.map +1 -0
  104. package/dist/index.js +187 -0
  105. package/dist/index.js.map +1 -0
  106. package/dist/middleware/attachment.d.ts +11 -0
  107. package/dist/middleware/attachment.d.ts.map +1 -0
  108. package/dist/middleware/attachment.js +17 -0
  109. package/dist/middleware/attachment.js.map +1 -0
  110. package/dist/middleware/media-history.d.ts +30 -0
  111. package/dist/middleware/media-history.d.ts.map +1 -0
  112. package/dist/middleware/media-history.js +54 -0
  113. package/dist/middleware/media-history.js.map +1 -0
  114. package/dist/middleware/sticker-capture.d.ts +17 -0
  115. package/dist/middleware/sticker-capture.d.ts.map +1 -0
  116. package/dist/middleware/sticker-capture.js +97 -0
  117. package/dist/middleware/sticker-capture.js.map +1 -0
  118. package/dist/model/index.d.ts +10 -0
  119. package/dist/model/index.d.ts.map +1 -0
  120. package/dist/model/index.js +9 -0
  121. package/dist/model/index.js.map +1 -0
  122. package/dist/model/model-resolver.d.ts +88 -0
  123. package/dist/model/model-resolver.d.ts.map +1 -0
  124. package/dist/model/model-resolver.js +151 -0
  125. package/dist/model/model-resolver.js.map +1 -0
  126. package/dist/model/prefs-store.d.ts +24 -0
  127. package/dist/model/prefs-store.d.ts.map +1 -0
  128. package/dist/model/prefs-store.js +126 -0
  129. package/dist/model/prefs-store.js.map +1 -0
  130. package/dist/model/settings-reader.d.ts +19 -0
  131. package/dist/model/settings-reader.d.ts.map +1 -0
  132. package/dist/model/settings-reader.js +76 -0
  133. package/dist/model/settings-reader.js.map +1 -0
  134. package/dist/model/types.d.ts +15 -0
  135. package/dist/model/types.d.ts.map +1 -0
  136. package/dist/model/types.js +5 -0
  137. package/dist/model/types.js.map +1 -0
  138. package/dist/session/idle-evictor.d.ts +18 -0
  139. package/dist/session/idle-evictor.d.ts.map +1 -0
  140. package/dist/session/idle-evictor.js +31 -0
  141. package/dist/session/idle-evictor.js.map +1 -0
  142. package/dist/session/index.d.ts +9 -0
  143. package/dist/session/index.d.ts.map +1 -0
  144. package/dist/session/index.js +8 -0
  145. package/dist/session/index.js.map +1 -0
  146. package/dist/session/session-manager.d.ts +97 -0
  147. package/dist/session/session-manager.d.ts.map +1 -0
  148. package/dist/session/session-manager.js +624 -0
  149. package/dist/session/session-manager.js.map +1 -0
  150. package/dist/session/types.d.ts +130 -0
  151. package/dist/session/types.d.ts.map +1 -0
  152. package/dist/session/types.js +2 -0
  153. package/dist/session/types.js.map +1 -0
  154. package/dist/session/workspace-attach.d.ts +22 -0
  155. package/dist/session/workspace-attach.d.ts.map +1 -0
  156. package/dist/session/workspace-attach.js +21 -0
  157. package/dist/session/workspace-attach.js.map +1 -0
  158. package/dist/setup.d.ts +25 -0
  159. package/dist/setup.d.ts.map +1 -0
  160. package/dist/setup.js +198 -0
  161. package/dist/setup.js.map +1 -0
  162. package/dist/shared/index.d.ts +7 -0
  163. package/dist/shared/index.d.ts.map +1 -0
  164. package/dist/shared/index.js +7 -0
  165. package/dist/shared/index.js.map +1 -0
  166. package/dist/shared/scope.d.ts +14 -0
  167. package/dist/shared/scope.d.ts.map +1 -0
  168. package/dist/shared/scope.js +12 -0
  169. package/dist/shared/scope.js.map +1 -0
  170. package/dist/shared/send-helper.d.ts +11 -0
  171. package/dist/shared/send-helper.d.ts.map +1 -0
  172. package/dist/shared/send-helper.js +13 -0
  173. package/dist/shared/send-helper.js.map +1 -0
  174. package/dist/shared/utils.d.ts +28 -0
  175. package/dist/shared/utils.d.ts.map +1 -0
  176. package/dist/shared/utils.js +143 -0
  177. package/dist/shared/utils.js.map +1 -0
  178. package/dist/transport/attachment.d.ts +18 -0
  179. package/dist/transport/attachment.d.ts.map +1 -0
  180. package/dist/transport/attachment.js +106 -0
  181. package/dist/transport/attachment.js.map +1 -0
  182. package/dist/transport/chunker.d.ts +14 -0
  183. package/dist/transport/chunker.d.ts.map +1 -0
  184. package/dist/transport/chunker.js +78 -0
  185. package/dist/transport/chunker.js.map +1 -0
  186. package/dist/transport/events.d.ts +80 -0
  187. package/dist/transport/events.d.ts.map +1 -0
  188. package/dist/transport/events.js +62 -0
  189. package/dist/transport/events.js.map +1 -0
  190. package/dist/transport/inbound.d.ts +8 -0
  191. package/dist/transport/inbound.d.ts.map +1 -0
  192. package/dist/transport/inbound.js +292 -0
  193. package/dist/transport/inbound.js.map +1 -0
  194. package/dist/transport/index.d.ts +10 -0
  195. package/dist/transport/index.d.ts.map +1 -0
  196. package/dist/transport/index.js +10 -0
  197. package/dist/transport/index.js.map +1 -0
  198. package/dist/transport/inject-rules.d.ts +24 -0
  199. package/dist/transport/inject-rules.d.ts.map +1 -0
  200. package/dist/transport/inject-rules.js +72 -0
  201. package/dist/transport/inject-rules.js.map +1 -0
  202. package/dist/transport/outbound-buffer.d.ts +56 -0
  203. package/dist/transport/outbound-buffer.d.ts.map +1 -0
  204. package/dist/transport/outbound-buffer.js +131 -0
  205. package/dist/transport/outbound-buffer.js.map +1 -0
  206. package/dist/transport/outbound.d.ts +30 -0
  207. package/dist/transport/outbound.d.ts.map +1 -0
  208. package/dist/transport/outbound.js +139 -0
  209. package/dist/transport/outbound.js.map +1 -0
  210. package/dist/transport/rich-media.d.ts +41 -0
  211. package/dist/transport/rich-media.d.ts.map +1 -0
  212. package/dist/transport/rich-media.js +104 -0
  213. package/dist/transport/rich-media.js.map +1 -0
  214. package/dist/transport/streaming-writer.d.ts +47 -0
  215. package/dist/transport/streaming-writer.d.ts.map +1 -0
  216. package/dist/transport/streaming-writer.js +110 -0
  217. package/dist/transport/streaming-writer.js.map +1 -0
  218. package/dist/transport/tool-presenter.d.ts +38 -0
  219. package/dist/transport/tool-presenter.d.ts.map +1 -0
  220. package/dist/transport/tool-presenter.js +121 -0
  221. package/dist/transport/tool-presenter.js.map +1 -0
  222. package/dist/types.d.ts +45 -0
  223. package/dist/types.d.ts.map +1 -0
  224. package/dist/types.js +5 -0
  225. package/dist/types.js.map +1 -0
  226. package/package.json +106 -0
  227. package/settings-host.js +927 -0
@@ -0,0 +1,151 @@
1
+ import { PrefsStore } from './prefs-store.js';
2
+ import { SettingsReader } from './settings-reader.js';
3
+ export class ModelResolver {
4
+ ctx;
5
+ config;
6
+ logger;
7
+ prefs;
8
+ settings;
9
+ constructor(ctx, config, logger) {
10
+ this.ctx = ctx;
11
+ this.config = config;
12
+ this.logger = logger;
13
+ this.prefs = new PrefsStore(config.debug ? (msg) => this.logger?.debug(msg) : undefined);
14
+ this.settings = new SettingsReader();
15
+ }
16
+ /**
17
+ * 获取指定 sessionKey 的有效模型路由(create 用)
18
+ *
19
+ * 优先级:per-peer 偏好 > config 显式指定 > settings.yaml > 宿主服务
20
+ */
21
+ getEffectiveRoute(sessionKey) {
22
+ return this.prefs.getOverride(sessionKey) ?? this.resolveDefault();
23
+ }
24
+ /**
25
+ * 获取 resume 时覆盖 session 的模型路由
26
+ *
27
+ * 优先级:per-peer 偏好 > cordis.yml 显式配置 > 默认链(settings.yaml > host)
28
+ *
29
+ * 注意:不能像 dsh-TUI 那样返回 undefined 让 session 沿用 requestHeader。
30
+ * dsh-TUI 靠 installModelSelection 从 session.requestHeader 恢复 {{model}},
31
+ * 而我们未装 installModelSelection,system-prompt 的 {{model}} 变量直接读
32
+ * agent.options.model(agent-loop index.ts:352)——若无值会抛
33
+ * "prompt variable {{model}} has no value for this assembly"。
34
+ * 因此这里兜底到默认链,确保 agent.options.model 始终有值。
35
+ */
36
+ getResumeRoute(sessionKey) {
37
+ const override = this.prefs.getOverride(sessionKey);
38
+ if (override)
39
+ return override;
40
+ if (this.config.provider && this.config.model) {
41
+ return { provider: this.config.provider, model: this.config.model };
42
+ }
43
+ return this.resolveDefault();
44
+ }
45
+ /**
46
+ * 设置 per-peer 模型偏好并持久化到隔离文件
47
+ */
48
+ setOverride(sessionKey, route) {
49
+ this.prefs.setOverride(sessionKey, route);
50
+ }
51
+ /**
52
+ * 清除 per-peer 模型偏好并持久化
53
+ */
54
+ clearOverride(sessionKey) {
55
+ this.prefs.clearOverride(sessionKey);
56
+ }
57
+ /**
58
+ * 是否存在指定 session 的模型偏好
59
+ */
60
+ hasOverride(sessionKey) {
61
+ return this.prefs.hasOverride(sessionKey);
62
+ }
63
+ /**
64
+ * 获取指定 sessionKey 的最新 sessionId(fork 后记录,重启恢复用)
65
+ */
66
+ getSessionId(sessionKey) {
67
+ return this.prefs.getSessionId(sessionKey);
68
+ }
69
+ /**
70
+ * 记录指定 sessionKey 的最新 sessionId(fork 后调用)并持久化
71
+ */
72
+ setSessionId(sessionKey, sessionId) {
73
+ this.prefs.setSessionId(sessionKey, sessionId);
74
+ }
75
+ /**
76
+ * 清除指定 sessionKey 的 sessionId 记录
77
+ */
78
+ clearSessionId(sessionKey) {
79
+ this.prefs.clearSessionId(sessionKey);
80
+ }
81
+ /**
82
+ * 解析默认模型路由(不含 per-peer 偏好)
83
+ *
84
+ * 优先级:config 显式指定 > settings.yaml(只读) > 宿主 agentDefaultModel
85
+ * 最终兜底 deepseek-official/deepseek-v4-flash,确保 {{model}} 变量始终有值。
86
+ */
87
+ resolveDefault() {
88
+ if (this.config.provider && this.config.model) {
89
+ return { provider: this.config.provider, model: this.config.model };
90
+ }
91
+ const fromSettings = this.settings.readDefaultRoute();
92
+ if (fromSettings)
93
+ return fromSettings;
94
+ const fromHost = this.readFromHost();
95
+ if (fromHost)
96
+ return fromHost;
97
+ return { provider: 'deepseek-official', model: 'deepseek-v4-flash' };
98
+ }
99
+ /**
100
+ * 列出所有可用模型
101
+ */
102
+ listModels() {
103
+ return this.settings.readModels();
104
+ }
105
+ /**
106
+ * 列出可用 provider 名称
107
+ */
108
+ listProviders() {
109
+ try {
110
+ const llm = this.getService('llm');
111
+ if (llm && typeof llm.listProviders === 'function') {
112
+ const providers = llm.listProviders();
113
+ if (providers.length > 0) {
114
+ const first = providers[0];
115
+ if (typeof first === 'string')
116
+ return providers;
117
+ return providers.map((p) => p.id);
118
+ }
119
+ }
120
+ }
121
+ catch {
122
+ // 忽略
123
+ }
124
+ return this.settings.readProviders();
125
+ }
126
+ // ── 私有方法 ──
127
+ readFromHost() {
128
+ try {
129
+ const agentDefaultModel = this.getService('agentDefaultModel');
130
+ if (agentDefaultModel && typeof agentDefaultModel.currentSelection === 'function') {
131
+ const selection = agentDefaultModel.currentSelection();
132
+ if (selection?.provider && selection?.model) {
133
+ return { provider: selection.provider, model: selection.model };
134
+ }
135
+ }
136
+ }
137
+ catch (err) {
138
+ if (this.config.debug) {
139
+ this.logger?.debug(`ModelResolver: host service failed: ${err instanceof Error ? err.message : String(err)}`);
140
+ }
141
+ }
142
+ return undefined;
143
+ }
144
+ /** 统一的 Cordis 服务访问 */
145
+ getService(name) {
146
+ const ctxAny = this.ctx;
147
+ return ctxAny[name] ??
148
+ (typeof ctxAny.get === 'function' ? ctxAny.get(name) : undefined);
149
+ }
150
+ }
151
+ //# sourceMappingURL=model-resolver.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"model-resolver.js","sourceRoot":"","sources":["../../src/model/model-resolver.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,24 @@
1
+ import type { ModelRoute } from './types.js';
2
+ /** 日志回调(可选) */
3
+ type DebugFn = (msg: string) => void;
4
+ export declare class PrefsStore {
5
+ /** per-peer 模型偏好(内存态) */
6
+ private overrides;
7
+ /** per-peer 最新 sessionId(fork 后更新,内存态) */
8
+ private sessionIds;
9
+ /** 隔离偏好文件路径 */
10
+ private readonly prefsPath;
11
+ private readonly debugLog?;
12
+ constructor(debugLog?: DebugFn);
13
+ getOverride(sessionKey: string): ModelRoute | undefined;
14
+ setOverride(sessionKey: string, route: ModelRoute): void;
15
+ clearOverride(sessionKey: string): boolean;
16
+ hasOverride(sessionKey: string): boolean;
17
+ getSessionId(sessionKey: string): string | undefined;
18
+ setSessionId(sessionKey: string, sessionId: string): void;
19
+ clearSessionId(sessionKey: string): boolean;
20
+ private load;
21
+ private write;
22
+ }
23
+ export {};
24
+ //# sourceMappingURL=prefs-store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prefs-store.d.ts","sourceRoot":"","sources":["../../src/model/prefs-store.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAE7C,eAAe;AACf,KAAK,OAAO,GAAG,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;AASrC,qBAAa,UAAU;IACrB,yBAAyB;IACzB,OAAO,CAAC,SAAS,CAAiC;IAClD,0CAA0C;IAC1C,OAAO,CAAC,UAAU,CAA6B;IAC/C,eAAe;IACf,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAU;gBAExB,QAAQ,CAAC,EAAE,OAAO;IAQ9B,WAAW,CAAC,UAAU,EAAE,MAAM,GAAG,UAAU,GAAG,SAAS;IAIvD,WAAW,CAAC,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,UAAU,GAAG,IAAI;IAKxD,aAAa,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO;IAM1C,WAAW,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO;IAMxC,YAAY,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAIpD,YAAY,CAAC,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI;IAKzD,cAAc,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO;IAQ3C,OAAO,CAAC,IAAI;IAmCZ,OAAO,CAAC,KAAK;CAwBd"}
@@ -0,0 +1,126 @@
1
+ /**
2
+ * PrefsStore — per-peer 模型偏好持久化
3
+ *
4
+ * 隔离文件 I/O 操作,便于单元测试时 mock。
5
+ * 存储路径:~/.dsh-qqbot/model-prefs.json
6
+ *
7
+ * 2026-09-06 移植上游 tencent-connect/dsh-qqbot PR #41:
8
+ * - 写入改原子(先写 .tmp 再 renameSync 覆盖, 同卷 rename 由 OS 保证原子,
9
+ * 避免写盘瞬间被 kill 留下半截 JSON);
10
+ * - load 解析失败不再静默吞掉: 损坏文件改名 .corrupt-<ts> 保留取证, 空偏好继续。
11
+ */
12
+ import { readFileSync, writeFileSync, existsSync, mkdirSync, renameSync } from 'node:fs';
13
+ import { resolve, dirname } from 'node:path';
14
+ import { homedir } from 'node:os';
15
+ export class PrefsStore {
16
+ /** per-peer 模型偏好(内存态) */
17
+ overrides = new Map();
18
+ /** per-peer 最新 sessionId(fork 后更新,内存态) */
19
+ sessionIds = new Map();
20
+ /** 隔离偏好文件路径 */
21
+ prefsPath;
22
+ debugLog;
23
+ constructor(debugLog) {
24
+ this.prefsPath = resolve(homedir(), '.dsh-qqbot', 'model-prefs.json');
25
+ this.debugLog = debugLog;
26
+ this.load();
27
+ }
28
+ // ── Override 操作 ──
29
+ getOverride(sessionKey) {
30
+ return this.overrides.get(sessionKey);
31
+ }
32
+ setOverride(sessionKey, route) {
33
+ this.overrides.set(sessionKey, route);
34
+ this.write();
35
+ }
36
+ clearOverride(sessionKey) {
37
+ const deleted = this.overrides.delete(sessionKey);
38
+ if (deleted)
39
+ this.write();
40
+ return deleted;
41
+ }
42
+ hasOverride(sessionKey) {
43
+ return this.overrides.has(sessionKey);
44
+ }
45
+ // ── SessionId 操作 ──
46
+ getSessionId(sessionKey) {
47
+ return this.sessionIds.get(sessionKey);
48
+ }
49
+ setSessionId(sessionKey, sessionId) {
50
+ this.sessionIds.set(sessionKey, sessionId);
51
+ this.write();
52
+ }
53
+ clearSessionId(sessionKey) {
54
+ const deleted = this.sessionIds.delete(sessionKey);
55
+ if (deleted)
56
+ this.write();
57
+ return deleted;
58
+ }
59
+ // ── 私有方法 ──
60
+ load() {
61
+ try {
62
+ if (!existsSync(this.prefsPath))
63
+ return;
64
+ const content = readFileSync(this.prefsPath, 'utf8');
65
+ const data = JSON.parse(content);
66
+ if (data.overrides && typeof data.overrides === 'object') {
67
+ for (const [key, route] of Object.entries(data.overrides)) {
68
+ if (route.provider && route.model) {
69
+ this.overrides.set(key, { provider: route.provider, model: route.model });
70
+ }
71
+ }
72
+ }
73
+ if (data.sessionIds && typeof data.sessionIds === 'object') {
74
+ for (const [key, sessionId] of Object.entries(data.sessionIds)) {
75
+ if (typeof sessionId === 'string' && sessionId) {
76
+ this.sessionIds.set(key, sessionId);
77
+ }
78
+ }
79
+ }
80
+ }
81
+ catch (err) {
82
+ // 2026-09-06 (PR #41): 解析失败不静默 —— 损坏文件改名 .corrupt-<ts> 保留取证, 空偏好继续。
83
+ // 旧行为只在 debug 时打一行日志然后以空偏好继续, 坏文件会被下次 write 覆盖, 无法事后排查。
84
+ this.debugLog?.(`loadPrefs failed: ${err instanceof Error ? err.message : String(err)}`);
85
+ try {
86
+ if (existsSync(this.prefsPath)) {
87
+ const quarantine = `${this.prefsPath}.corrupt-${Date.now()}`;
88
+ renameSync(this.prefsPath, quarantine);
89
+ this.debugLog?.(`prefs 文件损坏, 已隔离到 ${quarantine} 保留取证`);
90
+ }
91
+ }
92
+ catch (qErr) {
93
+ this.debugLog?.(`prefs 损坏文件隔离失败: ${qErr instanceof Error ? qErr.message : String(qErr)}`);
94
+ }
95
+ }
96
+ }
97
+ write() {
98
+ try {
99
+ mkdirSync(dirname(this.prefsPath), { recursive: true });
100
+ const data = {
101
+ overrides: Object.fromEntries(this.overrides.entries()),
102
+ sessionIds: Object.fromEntries(this.sessionIds.entries()),
103
+ };
104
+ // 2026-09-06 (PR #41): 原子写入 —— 先写 .tmp 再 renameSync 覆盖(同卷 rename 原子, OS 保证)。
105
+ // 旧行为 writeFileSync 就地全量覆盖, 写盘瞬间进程被 kill → 留下半截 JSON, 下次 load 静默重置。
106
+ const tmpPath = `${this.prefsPath}.tmp`;
107
+ writeFileSync(tmpPath, JSON.stringify(data, null, 2), 'utf8');
108
+ try {
109
+ renameSync(tmpPath, this.prefsPath);
110
+ }
111
+ catch (renameErr) {
112
+ // 极端情况下 rename 失败(如目标被占用): 清掉 tmp 残留, 避免堆积
113
+ try {
114
+ if (existsSync(tmpPath))
115
+ renameSync(tmpPath, `${this.prefsPath}.stale-${Date.now()}`);
116
+ }
117
+ catch { /* ignore */ }
118
+ throw renameErr;
119
+ }
120
+ }
121
+ catch (err) {
122
+ this.debugLog?.(`writePrefs failed: ${err instanceof Error ? err.message : String(err)}`);
123
+ }
124
+ }
125
+ }
126
+ //# sourceMappingURL=prefs-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prefs-store.js","sourceRoot":"","sources":["../../src/model/prefs-store.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ import type { ModelRoute, ModelEntry } from './types.js';
2
+ export declare class SettingsReader {
3
+ /** settings.yaml 缓存 */
4
+ private settingsCache;
5
+ /**
6
+ * 从 settings.yaml 的 agent-default-model 字段读取默认模型路由
7
+ */
8
+ readDefaultRoute(): ModelRoute | undefined;
9
+ /**
10
+ * 列出 settings.yaml 中配置的所有模型
11
+ */
12
+ readModels(): ModelEntry[];
13
+ /**
14
+ * 列出 settings.yaml 中配置的 provider 名称
15
+ */
16
+ readProviders(): string[];
17
+ private loadSettings;
18
+ }
19
+ //# sourceMappingURL=settings-reader.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"settings-reader.d.ts","sourceRoot":"","sources":["../../src/model/settings-reader.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAazD,qBAAa,cAAc;IACzB,uBAAuB;IACvB,OAAO,CAAC,aAAa,CAA6C;IAElE;;OAEG;IACH,gBAAgB,IAAI,UAAU,GAAG,SAAS;IAc1C;;OAEG;IACH,UAAU,IAAI,UAAU,EAAE;IAsB1B;;OAEG;IACH,aAAa,IAAI,MAAM,EAAE;IAMzB,OAAO,CAAC,YAAY;CAiBrB"}
@@ -0,0 +1,76 @@
1
+ /**
2
+ * SettingsReader — settings.yaml 只读解析
3
+ *
4
+ * 从 ~/.dsh/settings.yaml 中读取模型配置信息。
5
+ * 只读,不修改 settings.yaml。
6
+ */
7
+ import { readFileSync, existsSync } from 'node:fs';
8
+ import { resolve } from 'node:path';
9
+ import { homedir } from 'node:os';
10
+ import yaml from 'js-yaml';
11
+ export class SettingsReader {
12
+ /** settings.yaml 缓存 */
13
+ settingsCache;
14
+ /**
15
+ * 从 settings.yaml 的 agent-default-model 字段读取默认模型路由
16
+ */
17
+ readDefaultRoute() {
18
+ const settings = this.loadSettings();
19
+ if (!settings)
20
+ return undefined;
21
+ const defaultModel = settings['agent-default-model'];
22
+ if (defaultModel?.provider && defaultModel?.model) {
23
+ return { provider: defaultModel.provider, model: defaultModel.model };
24
+ }
25
+ return undefined;
26
+ }
27
+ /**
28
+ * 列出 settings.yaml 中配置的所有模型
29
+ */
30
+ readModels() {
31
+ const settings = this.loadSettings();
32
+ if (!settings)
33
+ return [];
34
+ const models = [];
35
+ const llmPiAi = settings['llm-pi-ai'];
36
+ if (llmPiAi?.providers) {
37
+ for (const [providerName, providerConfig] of Object.entries(llmPiAi.providers)) {
38
+ if (Array.isArray(providerConfig?.models)) {
39
+ for (const m of providerConfig.models) {
40
+ if (m.id) {
41
+ models.push({ provider: providerName, id: m.id, name: m.name || undefined });
42
+ }
43
+ }
44
+ }
45
+ }
46
+ }
47
+ return models;
48
+ }
49
+ /**
50
+ * 列出 settings.yaml 中配置的 provider 名称
51
+ */
52
+ readProviders() {
53
+ const settings = this.loadSettings();
54
+ const llmPiAi = settings?.['llm-pi-ai'];
55
+ return llmPiAi?.providers ? Object.keys(llmPiAi.providers) : [];
56
+ }
57
+ loadSettings() {
58
+ if (this.settingsCache !== undefined)
59
+ return this.settingsCache;
60
+ try {
61
+ const settingsPath = resolve(homedir(), '.dsh', 'settings.yaml');
62
+ if (!existsSync(settingsPath)) {
63
+ this.settingsCache = null;
64
+ return null;
65
+ }
66
+ const content = readFileSync(settingsPath, 'utf8');
67
+ this.settingsCache = yaml.load(content);
68
+ return this.settingsCache;
69
+ }
70
+ catch {
71
+ this.settingsCache = null;
72
+ return null;
73
+ }
74
+ }
75
+ }
76
+ //# sourceMappingURL=settings-reader.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"settings-reader.js","sourceRoot":"","sources":["../../src/model/settings-reader.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * 模型路由层类型定义
3
+ */
4
+ /** 模型路由 */
5
+ export interface ModelRoute {
6
+ provider: string;
7
+ model: string;
8
+ }
9
+ /** 模型信息条目 */
10
+ export interface ModelEntry {
11
+ provider: string;
12
+ id: string;
13
+ name?: string;
14
+ }
15
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/model/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,WAAW;AACX,MAAM,WAAW,UAAU;IACzB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;CACf;AAED,aAAa;AACb,MAAM,WAAW,UAAU;IACzB,QAAQ,EAAE,MAAM,CAAC;IACjB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,CAAC,EAAE,MAAM,CAAC;CACf"}
@@ -0,0 +1,5 @@
1
+ /**
2
+ * 模型路由层类型定义
3
+ */
4
+ export {};
5
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/model/types.ts"],"names":[],"mappings":"AAAA;;GAEG"}
@@ -0,0 +1,18 @@
1
+ /**
2
+ * IdleEvictor — 闲置会话回收器
3
+ *
4
+ * 定期检查会话最后活跃时间,超时则自动回收释放资源。
5
+ */
6
+ import type { SessionRecord } from './types.js';
7
+ export declare class IdleEvictor {
8
+ private readonly sessions;
9
+ private readonly timeoutMs;
10
+ private readonly onEvict;
11
+ private timer;
12
+ constructor(sessions: Map<string, SessionRecord>, timeoutMs: number, onEvict: (key: string, record: SessionRecord) => void);
13
+ /** 执行一轮回收检查 */
14
+ check(): void;
15
+ /** 停止回收定时器 */
16
+ dispose(): void;
17
+ }
18
+ //# sourceMappingURL=idle-evictor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"idle-evictor.d.ts","sourceRoot":"","sources":["../../src/session/idle-evictor.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAEhD,qBAAa,WAAW;IAIpB,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,OAAO;IAL1B,OAAO,CAAC,KAAK,CAA+C;gBAGzC,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,aAAa,CAAC,EACpC,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,aAAa,KAAK,IAAI;IAOxE,eAAe;IACf,KAAK,IAAI,IAAI;IASb,cAAc;IACd,OAAO,IAAI,IAAI;CAMhB"}
@@ -0,0 +1,31 @@
1
+ export class IdleEvictor {
2
+ sessions;
3
+ timeoutMs;
4
+ onEvict;
5
+ timer = null;
6
+ constructor(sessions, timeoutMs, onEvict) {
7
+ this.sessions = sessions;
8
+ this.timeoutMs = timeoutMs;
9
+ this.onEvict = onEvict;
10
+ if (timeoutMs > 0) {
11
+ this.timer = setInterval(() => this.check(), 60_000);
12
+ }
13
+ }
14
+ /** 执行一轮回收检查 */
15
+ check() {
16
+ const now = Date.now();
17
+ for (const [key, record] of this.sessions) {
18
+ if (now - record.lastActivity > this.timeoutMs) {
19
+ this.onEvict(key, record);
20
+ }
21
+ }
22
+ }
23
+ /** 停止回收定时器 */
24
+ dispose() {
25
+ if (this.timer) {
26
+ clearInterval(this.timer);
27
+ this.timer = null;
28
+ }
29
+ }
30
+ }
31
+ //# sourceMappingURL=idle-evictor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"idle-evictor.js","sourceRoot":"","sources":["../../src/session/idle-evictor.ts"],"names":[],"mappings":"AAOA,MAAM,OAAO,WAAW;IAIH;IACA;IACA;IALX,KAAK,GAA0C,IAAI,CAAC;IAE5D,YACmB,QAAoC,EACpC,SAAiB,EACjB,OAAqD;QAFrD,aAAQ,GAAR,QAAQ,CAA4B;QACpC,cAAS,GAAT,SAAS,CAAQ;QACjB,YAAO,GAAP,OAAO,CAA8C;QAEtE,IAAI,SAAS,GAAG,CAAC,EAAE,CAAC;YAClB,IAAI,CAAC,KAAK,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,KAAK,EAAE,EAAE,MAAM,CAAC,CAAC;QACvD,CAAC;IACH,CAAC;IAED,eAAe;IACf,KAAK;QACH,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACvB,KAAK,MAAM,CAAC,GAAG,EAAE,MAAM,CAAC,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;YAC1C,IAAI,GAAG,GAAG,MAAM,CAAC,YAAY,GAAG,IAAI,CAAC,SAAS,EAAE,CAAC;gBAC/C,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;YAC5B,CAAC;QACH,CAAC;IACH,CAAC;IAED,cAAc;IACd,OAAO;QACL,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;YACf,aAAa,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YAC1B,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC;QACpB,CAAC;IACH,CAAC;CACF"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * 会话管理层
3
+ *
4
+ * 管理 QQ peer → dsh Agent 的映射和生命周期。
5
+ */
6
+ export { SessionManager } from './session-manager.js';
7
+ export { IdleEvictor } from './idle-evictor.js';
8
+ export type { AgentSetup, SessionEventLike, DshAgent, DshAgentHandle, SessionsService, DshAgentRegistry, AgentPresetsLike, PresetComposition, SessionRecord, SessionStatus, TokenUsageStats, } from './types.js';
9
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/session/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,YAAY,EACV,UAAU,EACV,gBAAgB,EAChB,QAAQ,EACR,cAAc,EACd,eAAe,EACf,gBAAgB,EAChB,gBAAgB,EAChB,iBAAiB,EACjB,aAAa,EACb,aAAa,EACb,eAAe,GAChB,MAAM,YAAY,CAAC"}
@@ -0,0 +1,8 @@
1
+ /**
2
+ * 会话管理层
3
+ *
4
+ * 管理 QQ peer → dsh Agent 的映射和生命周期。
5
+ */
6
+ export { SessionManager } from './session-manager.js';
7
+ export { IdleEvictor } from './idle-evictor.js';
8
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/session/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC"}
@@ -0,0 +1,97 @@
1
+ import type { Context } from '@deepseek-ai/cordis';
2
+ import type { ChatScope, Logger, ReplyTarget } from '../types.js';
3
+ import type { ImQQBotConfig } from '../config.js';
4
+ import type { ModelRoute, ModelEntry } from '../model/types.js';
5
+ import type { DshAgent, DshAgentRegistry, SessionRecord, SessionStatus, TokenUsageStats } from './types.js';
6
+ import type { QQBotSender } from '../transport/outbound-buffer.js';
7
+ import { createGroupAdmin } from '../api/group-admin.js';
8
+ export declare class SessionManager {
9
+ private readonly ctx;
10
+ private readonly agents;
11
+ private readonly config;
12
+ private readonly logger;
13
+ private sessions;
14
+ private readonly evictor;
15
+ private readonly modelResolver;
16
+ /** QQ 通道发送能力(由 bootstrap 注入);仅作为 qqChannel service 暴露给通道工具,不在此注册工具 */
17
+ private channelSender;
18
+ /** 最近一次 setup 收到的 agent ctx(工具自愈用;getOrCreate 完成后读走并清空) */
19
+ private lastSetupCtx;
20
+ /** 注入 QQ 通道发送能力(供通道工具);bootstrap 调用 */
21
+ installChannelSender(sender: QQBotSender): void;
22
+ /** 本实例图库目录(多账号: 每实例 cwd 独立; config.sticker.dataDir 可覆盖) */
23
+ get stickerDataDir(): string;
24
+ /** 本实例定时任务目录(多账号: 每实例 cwd 独立) */
25
+ get scheduleDataDir(): string;
26
+ /** 本实例群管理客户端(懒建; groupAdmin.enabled=false 时 undefined) */
27
+ get groupAdmin(): ReturnType<typeof createGroupAdmin> | undefined;
28
+ private _groupAdmin?;
29
+ /** 对话内默认管理群(web/非群会话时群工具用它; 空=未配置) */
30
+ get manageGroup(): string;
31
+ constructor(ctx: Context, agents: DshAgentRegistry, config: ImQQBotConfig, logger: Logger);
32
+ /**
33
+ * 动态获取 sessions 服务(fork 能力,可选)
34
+ */
35
+ private getSessionsService;
36
+ getEffectiveModel(scope: ChatScope, peerId: string): ModelRoute | undefined;
37
+ /**
38
+ * 切换模型(fork + 重建,对齐 dsh-TUI 的 switchModel)
39
+ */
40
+ setModelOverride(scope: ChatScope, peerId: string, route: ModelRoute): Promise<void>;
41
+ clearModelOverride(scope: ChatScope, peerId: string): void;
42
+ listAvailableModels(): ModelEntry[];
43
+ listProviders(): string[];
44
+ getSessionRecord(scope: ChatScope, peerId: string): SessionRecord | undefined;
45
+ getStatus(scope: ChatScope, peerId: string): SessionStatus;
46
+ getTokenUsage(scope: ChatScope, peerId: string): TokenUsageStats;
47
+ exportMarkdown(scope: ChatScope, peerId: string): string;
48
+ private countMessages;
49
+ private sessionKey;
50
+ private deriveSessionId;
51
+ private currentSessionId;
52
+ private composePreset;
53
+ /** 获取或恢复或创建会话(get → resume → create) */
54
+ getOrCreate(scope: ChatScope, peerId: string, senderId: string, replyTarget: ReplyTarget): Promise<SessionRecord>;
55
+ /**
56
+ * 通道工具自愈 + 固化(2026-09-05 升级): 每次消息都全量幂等注册 channel-tools(静态 import,
57
+ * 进程加载的就是最新 dist) → 重启后"恢复"会话的第一条消息也会自动把 reply_gate 等正式工具
58
+ * 全部注册上,**工具永久固化, 重启不再需要 /tools-reload**(热刷只留给进程内"新增"工具的即时生效)。
59
+ * 已注册工具重复注册会被 channel-tools 内部静默跳过(already registered), 无副作用无噪音。
60
+ */
61
+ ensureChannelTools(record: SessionRecord): Promise<void>;
62
+ /**
63
+ * 工具热刷新(2026-09-05, 主人定 A 方案): cache-bust 动态重载最新 dist 的 channel-tools
64
+ * 并注册到给定 agentCtx。DSH 工具表每步现收集 → 新工具**下一轮立即对 LLM 可见**, 无需重启宿主。
65
+ * 说明: 新增工具即时生效; 与已有工具同名的注册会被 channel-tools 内部 try/catch 跳过(保留旧实现),
66
+ * 想更新已有工具逻辑仍需正式重启(或临时换新工具名)。
67
+ */
68
+ /**
69
+ * qqChannel 上下文自愈(2026-09-05): 重启后"恢复"的会话不跑 setup → agent.ctx 上没有
70
+ * qqChannel → 通道工具路由不到本实例(channelOf 失败 → 图库/定时落到别的实例或 C 盘幽灵库)。
71
+ * 每次消息补一次 provide(幂等: 已是本实例就跳过)。用 record.agent.ctx(恒有)。
72
+ */
73
+ ensureChannelContext(record: SessionRecord): Promise<void>;
74
+ hotReloadChannelTools(agentCtx: Context): Promise<void>;
75
+ /** 对所有活会话的 agentCtx 执行一次工具热刷新。返回给人看的摘要文本。 */
76
+ reloadAllChannelTools(): Promise<string>;
77
+ /**
78
+ * 守则/身份 context 注册自愈(幂等): 每次消息处理路径都会调用。
79
+ * 用 record.agent.ctx(恒有, 不依赖 setup; 重启恢复会话也覆盖)。
80
+ * 注册方式照审批 approval:policy: ctx.inject(['systemPrompt']) → systemPrompt.context;
81
+ * text 每次渲染现读 live config(空串不贡献、热更新)。
82
+ */
83
+ ensureGroupRules(record: SessionRecord): Promise<void>;
84
+ findBySessionId(sessionId: string): SessionRecord | undefined;
85
+ findByAgent(agent: DshAgent): SessionRecord | undefined;
86
+ /** 按会话键找活跃会话(group/c2c); 无则 undefined(会话被回收/未建立) */
87
+ findByPeer(scope: ChatScope, peerId: string): SessionRecord | undefined;
88
+ /**
89
+ * 定时/主动注入: 向某会话塞一条新消息(像收到新消息, AI 带上下文处理并回复到 QQ)。
90
+ * 会话当前不在(被回收/未建立)→ 返回 false, 由调用方决定(计数/跳过)。
91
+ */
92
+ injectToPeer(scope: ChatScope, peerId: string, text: string): Promise<boolean>;
93
+ remove(scope: ChatScope, peerId: string): Promise<void>;
94
+ disposeAll(): Promise<void>;
95
+ get size(): number;
96
+ }
97
+ //# sourceMappingURL=session-manager.d.ts.map