@gakr-gakr/qqbot 0.1.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 (149) hide show
  1. package/api.ts +56 -0
  2. package/autobot.plugin.json +167 -0
  3. package/channel-plugin-api.ts +1 -0
  4. package/index.ts +33 -0
  5. package/package.json +64 -0
  6. package/runtime-api.ts +9 -0
  7. package/secret-contract-api.ts +5 -0
  8. package/setup-entry.ts +13 -0
  9. package/setup-plugin-api.ts +3 -0
  10. package/skills/qqbot-channel/SKILL.md +262 -0
  11. package/skills/qqbot-channel/references/api_references.md +521 -0
  12. package/skills/qqbot-media/SKILL.md +37 -0
  13. package/skills/qqbot-remind/SKILL.md +153 -0
  14. package/src/bridge/approval/capability.ts +225 -0
  15. package/src/bridge/approval/handler-runtime.ts +204 -0
  16. package/src/bridge/bootstrap.ts +135 -0
  17. package/src/bridge/channel-entry.ts +18 -0
  18. package/src/bridge/commands/framework-context-adapter.ts +60 -0
  19. package/src/bridge/commands/framework-registration.ts +66 -0
  20. package/src/bridge/commands/from-parser.ts +60 -0
  21. package/src/bridge/commands/result-dispatcher.ts +76 -0
  22. package/src/bridge/config-shared.ts +132 -0
  23. package/src/bridge/config.ts +176 -0
  24. package/src/bridge/gateway.ts +178 -0
  25. package/src/bridge/logger.ts +31 -0
  26. package/src/bridge/narrowing.ts +31 -0
  27. package/src/bridge/plugin-version.ts +102 -0
  28. package/src/bridge/runtime.ts +25 -0
  29. package/src/bridge/sdk-adapter.ts +164 -0
  30. package/src/bridge/setup/finalize.ts +144 -0
  31. package/src/bridge/setup/surface.ts +34 -0
  32. package/src/bridge/tools/channel.ts +58 -0
  33. package/src/bridge/tools/index.ts +15 -0
  34. package/src/bridge/tools/remind.ts +91 -0
  35. package/src/channel.setup.ts +33 -0
  36. package/src/channel.ts +399 -0
  37. package/src/config-schema.ts +84 -0
  38. package/src/engine/access/index.ts +2 -0
  39. package/src/engine/access/resolve-policy.ts +30 -0
  40. package/src/engine/access/sender-match.ts +55 -0
  41. package/src/engine/access/types.ts +2 -0
  42. package/src/engine/adapter/audio.port.ts +27 -0
  43. package/src/engine/adapter/commands.port.ts +22 -0
  44. package/src/engine/adapter/history.port.ts +52 -0
  45. package/src/engine/adapter/index.ts +76 -0
  46. package/src/engine/adapter/mention-gate.port.ts +50 -0
  47. package/src/engine/adapter/types.ts +38 -0
  48. package/src/engine/api/api-client.ts +212 -0
  49. package/src/engine/api/media-chunked.ts +644 -0
  50. package/src/engine/api/media.ts +218 -0
  51. package/src/engine/api/messages.ts +293 -0
  52. package/src/engine/api/retry.ts +217 -0
  53. package/src/engine/api/routes.ts +95 -0
  54. package/src/engine/api/token.ts +277 -0
  55. package/src/engine/approval/index.ts +224 -0
  56. package/src/engine/commands/builtin/log-helpers.ts +341 -0
  57. package/src/engine/commands/builtin/register-all.ts +17 -0
  58. package/src/engine/commands/builtin/register-approve.ts +201 -0
  59. package/src/engine/commands/builtin/register-basic.ts +95 -0
  60. package/src/engine/commands/builtin/register-clear-storage.ts +187 -0
  61. package/src/engine/commands/builtin/register-logs.ts +20 -0
  62. package/src/engine/commands/builtin/register-streaming.ts +138 -0
  63. package/src/engine/commands/builtin/state.ts +31 -0
  64. package/src/engine/commands/slash-command-auth.ts +88 -0
  65. package/src/engine/commands/slash-command-handler.ts +168 -0
  66. package/src/engine/commands/slash-command-test-support.ts +39 -0
  67. package/src/engine/commands/slash-commands-impl.ts +61 -0
  68. package/src/engine/commands/slash-commands.ts +202 -0
  69. package/src/engine/config/credential-backup.ts +108 -0
  70. package/src/engine/config/credentials.ts +76 -0
  71. package/src/engine/config/group.ts +227 -0
  72. package/src/engine/config/resolve.ts +283 -0
  73. package/src/engine/config/setup-logic.ts +84 -0
  74. package/src/engine/gateway/active-cfg.ts +52 -0
  75. package/src/engine/gateway/codec.ts +47 -0
  76. package/src/engine/gateway/constants.ts +117 -0
  77. package/src/engine/gateway/event-dispatcher.ts +177 -0
  78. package/src/engine/gateway/gateway-connection.ts +356 -0
  79. package/src/engine/gateway/gateway.ts +267 -0
  80. package/src/engine/gateway/inbound-attachments.ts +360 -0
  81. package/src/engine/gateway/inbound-context.ts +82 -0
  82. package/src/engine/gateway/inbound-pipeline.ts +171 -0
  83. package/src/engine/gateway/interaction-handler.ts +345 -0
  84. package/src/engine/gateway/message-queue.ts +404 -0
  85. package/src/engine/gateway/outbound-dispatch.ts +590 -0
  86. package/src/engine/gateway/reconnect.ts +199 -0
  87. package/src/engine/gateway/stages/access-stage.ts +99 -0
  88. package/src/engine/gateway/stages/assembly-stage.ts +156 -0
  89. package/src/engine/gateway/stages/content-stage.ts +77 -0
  90. package/src/engine/gateway/stages/envelope-stage.ts +144 -0
  91. package/src/engine/gateway/stages/group-gate-stage.ts +223 -0
  92. package/src/engine/gateway/stages/index.ts +18 -0
  93. package/src/engine/gateway/stages/quote-stage.ts +113 -0
  94. package/src/engine/gateway/stages/refidx-stage.ts +62 -0
  95. package/src/engine/gateway/stages/stub-contexts.ts +77 -0
  96. package/src/engine/gateway/types.ts +230 -0
  97. package/src/engine/gateway/typing-keepalive.ts +102 -0
  98. package/src/engine/gateway/ws-client.ts +16 -0
  99. package/src/engine/group/activation.ts +88 -0
  100. package/src/engine/group/history.ts +321 -0
  101. package/src/engine/group/mention.ts +114 -0
  102. package/src/engine/group/message-gating.ts +108 -0
  103. package/src/engine/messaging/decode-media-path.ts +82 -0
  104. package/src/engine/messaging/media-source.ts +210 -0
  105. package/src/engine/messaging/media-type-detect.ts +27 -0
  106. package/src/engine/messaging/outbound-audio-port.ts +38 -0
  107. package/src/engine/messaging/outbound-deliver.ts +810 -0
  108. package/src/engine/messaging/outbound-media-send.ts +658 -0
  109. package/src/engine/messaging/outbound-reply.ts +27 -0
  110. package/src/engine/messaging/outbound-result-helpers.ts +54 -0
  111. package/src/engine/messaging/outbound-types.ts +47 -0
  112. package/src/engine/messaging/outbound.ts +485 -0
  113. package/src/engine/messaging/reply-dispatcher.ts +597 -0
  114. package/src/engine/messaging/reply-limiter.ts +164 -0
  115. package/src/engine/messaging/sender.ts +741 -0
  116. package/src/engine/messaging/streaming-c2c.ts +1192 -0
  117. package/src/engine/messaging/streaming-media-send.ts +544 -0
  118. package/src/engine/messaging/target-parser.ts +104 -0
  119. package/src/engine/ref/format-message-ref.ts +142 -0
  120. package/src/engine/ref/format-ref-entry.ts +27 -0
  121. package/src/engine/ref/store.ts +211 -0
  122. package/src/engine/ref/types.ts +27 -0
  123. package/src/engine/session/known-users.ts +138 -0
  124. package/src/engine/session/session-store.ts +207 -0
  125. package/src/engine/tools/channel-api.ts +244 -0
  126. package/src/engine/tools/remind-logic.ts +377 -0
  127. package/src/engine/types.ts +313 -0
  128. package/src/engine/utils/attachment-tags.ts +174 -0
  129. package/src/engine/utils/audio.ts +525 -0
  130. package/src/engine/utils/data-paths.ts +38 -0
  131. package/src/engine/utils/diagnostics.ts +93 -0
  132. package/src/engine/utils/file-utils.ts +215 -0
  133. package/src/engine/utils/format.ts +70 -0
  134. package/src/engine/utils/image-size.ts +249 -0
  135. package/src/engine/utils/log.ts +77 -0
  136. package/src/engine/utils/media-tags.ts +177 -0
  137. package/src/engine/utils/payload.ts +157 -0
  138. package/src/engine/utils/platform.ts +265 -0
  139. package/src/engine/utils/request-context.ts +60 -0
  140. package/src/engine/utils/string-normalize.ts +91 -0
  141. package/src/engine/utils/stt.ts +103 -0
  142. package/src/engine/utils/text-parsing.ts +155 -0
  143. package/src/engine/utils/upload-cache.ts +96 -0
  144. package/src/engine/utils/voice-text.ts +15 -0
  145. package/src/exec-approvals.ts +237 -0
  146. package/src/qqbot-test-support.ts +29 -0
  147. package/src/secret-contract.ts +82 -0
  148. package/src/types.ts +210 -0
  149. package/tsconfig.json +16 -0
@@ -0,0 +1,230 @@
1
+ import type { AutoBotConfig } from "autobot/plugin-sdk/core";
2
+ import type { EngineLogger } from "../types.js";
3
+ export type { EngineLogger };
4
+
5
+ import type { GatewayAccount as _GatewayAccount } from "../types.js";
6
+ export type GatewayAccount = _GatewayAccount;
7
+
8
+ export interface GatewayPluginRuntime {
9
+ channel: {
10
+ activity: {
11
+ record: (params: {
12
+ channel: string;
13
+ accountId: string;
14
+ direction: "inbound" | "outbound";
15
+ }) => void;
16
+ };
17
+ routing: {
18
+ resolveAgentRoute: (params: {
19
+ cfg: unknown;
20
+ channel: string;
21
+ accountId: string;
22
+ peer: { kind: "group" | "direct"; id: string };
23
+ }) => { sessionKey: string; accountId: string; agentId?: string };
24
+ };
25
+ commands?: {
26
+ isControlCommandMessage?: (text?: string, cfg?: unknown) => boolean;
27
+ };
28
+ reply: {
29
+ dispatchReplyWithBufferedBlockDispatcher: (params: unknown) => Promise<unknown>;
30
+ resolveEffectiveMessagesConfig: (
31
+ cfg: unknown,
32
+ agentId?: string,
33
+ ) => { responsePrefix?: string };
34
+ finalizeInboundContext: (fields: Record<string, unknown>) => unknown;
35
+ formatInboundEnvelope: (params: unknown) => string;
36
+ resolveEnvelopeFormatOptions: (cfg: unknown) => unknown;
37
+ };
38
+ session: {
39
+ resolveStorePath: (store: unknown, params: { agentId: string }) => string;
40
+ recordInboundSession: (params: unknown) => Promise<unknown>;
41
+ };
42
+ turn: {
43
+ run: (params: unknown) => Promise<unknown>;
44
+ };
45
+ text: {
46
+ chunkMarkdownText: (text: string, limit: number) => string[];
47
+ };
48
+ };
49
+ tts: {
50
+ textToSpeech: (params: {
51
+ text: string;
52
+ cfg: unknown;
53
+ channel: string;
54
+ accountId?: string;
55
+ }) => Promise<{
56
+ success: boolean;
57
+ audioPath?: string;
58
+ provider?: string;
59
+ outputFormat?: string;
60
+ error?: string;
61
+ }>;
62
+ };
63
+ config?: {
64
+ current: () => Record<string, unknown>;
65
+ replaceConfigFile: (params: {
66
+ nextConfig: unknown;
67
+ afterWrite: { mode: "auto" };
68
+ }) => Promise<unknown>;
69
+ };
70
+ }
71
+
72
+ export type { ProcessedAttachments } from "./inbound-attachments.js";
73
+
74
+ export interface OutboundResult {
75
+ channel: string;
76
+ messageId?: string;
77
+ timestamp?: string | number;
78
+ error?: string;
79
+ }
80
+
81
+ export type { RefAttachmentSummary } from "../ref/types.js";
82
+
83
+ export interface WSPayload {
84
+ op: number;
85
+ d: unknown;
86
+ s?: number;
87
+ t?: string;
88
+ }
89
+
90
+ interface RawMessageAttachment {
91
+ content_type: string;
92
+ url: string;
93
+ filename?: string;
94
+ voice_wav_url?: string;
95
+ asr_refer_text?: string;
96
+ }
97
+
98
+ interface RawMsgElement {
99
+ msg_idx?: string;
100
+ content?: string;
101
+ attachments?: Array<
102
+ RawMessageAttachment & {
103
+ height?: number;
104
+ width?: number;
105
+ size?: number;
106
+ }
107
+ >;
108
+ }
109
+
110
+ export interface C2CMessageEvent {
111
+ id: string;
112
+ content: string;
113
+ timestamp: string;
114
+ author: { user_openid: string };
115
+ attachments?: RawMessageAttachment[];
116
+ message_scene?: { ext?: string[] };
117
+ message_type?: number;
118
+ msg_elements?: RawMsgElement[];
119
+ }
120
+
121
+ export interface GuildMessageEvent {
122
+ id: string;
123
+ content: string;
124
+ timestamp: string;
125
+ author: { id: string; username?: string };
126
+ channel_id: string;
127
+ guild_id: string;
128
+ attachments?: RawMessageAttachment[];
129
+ message_scene?: { ext?: string[] };
130
+ }
131
+
132
+ export interface GroupMessageEvent {
133
+ id: string;
134
+ content: string;
135
+ timestamp: string;
136
+ author: {
137
+ member_openid: string;
138
+ username?: string;
139
+ /** True when the sender is itself a bot. */
140
+ bot?: boolean;
141
+ };
142
+ group_openid: string;
143
+ attachments?: RawMessageAttachment[];
144
+ /** Optional @mentions list with per-entry is_you / member_openid / nickname. */
145
+ mentions?: Array<{
146
+ scope?: "all" | "single";
147
+ id?: string;
148
+ user_openid?: string;
149
+ member_openid?: string;
150
+ nickname?: string;
151
+ username?: string;
152
+ bot?: boolean;
153
+ /** `true` when this mention targets the bot itself. */
154
+ is_you?: boolean;
155
+ }>;
156
+ message_scene?: { source?: string; ext?: string[] };
157
+ message_type?: number;
158
+ msg_elements?: RawMsgElement[];
159
+ }
160
+
161
+ // ============ Gateway Context ============
162
+
163
+ import type { EngineAdapters } from "../adapter/index.js";
164
+
165
+ /**
166
+ * Group-chat behaviour options.
167
+ *
168
+ * Grouped under a dedicated sub-object on {@link CoreGatewayContext} so
169
+ * future additions (admin lookup, proactive push, per-group toggles)
170
+ * don't keep polluting the top-level context type.
171
+ */
172
+ interface GatewayGroupOptions {
173
+ /**
174
+ * Whether group-chat gating is enabled. Defaults to `true`; set to
175
+ * `false` to disable all group processing (e.g. for a DM-only smoke
176
+ * test). When disabled, the engine does not allocate a history
177
+ * buffer and does not instantiate the session-store reader.
178
+ */
179
+ enabled?: boolean;
180
+ /**
181
+ * Whether the framework has text-based control commands enabled. When
182
+ * `false`, the group gate skips the "unauthorized command" check and
183
+ * the command-bypass path.
184
+ */
185
+ allowTextCommands?: boolean;
186
+ /**
187
+ * Optional probe that returns true when `content` is a recognised
188
+ * control command. Injected to avoid hard-coding a command list in
189
+ * the engine. When omitted, no message is treated as a control
190
+ * command and the bypass path never activates.
191
+ */
192
+ isControlCommand?: (content: string) => boolean;
193
+ /**
194
+ * Platform hook that contributes a channel-level group intro hint
195
+ * (e.g. "当前群: 开发讨论组"). Invoked per-group when building the
196
+ * system prompt.
197
+ */
198
+ resolveIntroHint?: (params: {
199
+ cfg: unknown;
200
+ accountId: string;
201
+ groupId: string;
202
+ }) => string | undefined;
203
+ /**
204
+ * Session-store reader for the `/activation` command override. When
205
+ * omitted, the engine loads a default node-based reader lazily.
206
+ */
207
+ sessionStoreReader?: import("../group/activation.js").SessionStoreReader;
208
+ }
209
+
210
+ /** Full gateway startup context. */
211
+ export interface CoreGatewayContext {
212
+ account: GatewayAccount;
213
+ abortSignal: AbortSignal;
214
+ cfg: AutoBotConfig;
215
+ onReady?: (data: unknown) => void;
216
+ /**
217
+ * Invoked when a RESUMED event is received after reconnect.
218
+ * Falls back to `onReady` when not provided so existing callers
219
+ * keep their current behaviour.
220
+ */
221
+ onResumed?: (data: unknown) => void;
222
+ onError?: (error: Error) => void;
223
+ log?: EngineLogger;
224
+ /** PluginRuntime injected by the framework — same object in both versions. */
225
+ runtime: GatewayPluginRuntime;
226
+ /** Group-chat tuning options. */
227
+ group?: GatewayGroupOptions;
228
+ /** Adapter ports — delegates audio, history, mention gating, commands to bridge implementations. */
229
+ adapters: EngineAdapters;
230
+ }
@@ -0,0 +1,102 @@
1
+ /**
2
+ * Periodically refresh C2C typing state while a response is in progress.
3
+ *
4
+ * All I/O operations are injected via constructor parameters so this
5
+ * module has zero external dependencies and can run in both plugin versions.
6
+ */
7
+
8
+ import { formatErrorMessage } from "../utils/format.js";
9
+
10
+ /** Function that sends a typing indicator to one user. */
11
+ type SendInputNotifyFn = (
12
+ token: string,
13
+ openid: string,
14
+ msgId: string | undefined,
15
+ inputSecond: number,
16
+ ) => Promise<unknown>;
17
+
18
+ /** Refresh every 5s for the QQ API's 10s input-notify window. */
19
+ const TYPING_INTERVAL_MS = 5_000;
20
+ export const TYPING_INPUT_SECOND = 10;
21
+ const QQ_C2C_PASSIVE_REPLY_LIMIT = 5;
22
+ const INITIAL_TYPING_NOTIFY_COUNT = 1;
23
+ const FINAL_REPLY_RESERVE_COUNT = 1;
24
+ export const TYPING_RENEWAL_LIMIT =
25
+ QQ_C2C_PASSIVE_REPLY_LIMIT - INITIAL_TYPING_NOTIFY_COUNT - FINAL_REPLY_RESERVE_COUNT;
26
+
27
+ export class TypingKeepAlive {
28
+ private timer: ReturnType<typeof setInterval> | null = null;
29
+ private stopped = false;
30
+ private renewalsRemaining = TYPING_RENEWAL_LIMIT;
31
+
32
+ constructor(
33
+ private readonly getToken: () => Promise<string>,
34
+ private readonly clearCache: () => void,
35
+ private readonly sendInputNotify: SendInputNotifyFn,
36
+ private readonly openid: string,
37
+ private readonly msgId: string | undefined,
38
+ private readonly log?: {
39
+ info: (msg: string) => void;
40
+ error: (msg: string) => void;
41
+ debug?: (msg: string) => void;
42
+ },
43
+ ) {}
44
+
45
+ /** Start periodic keep-alive sends. */
46
+ start(): void {
47
+ if (this.stopped) {
48
+ return;
49
+ }
50
+ this.timer = setInterval(() => {
51
+ if (this.stopped) {
52
+ this.stop();
53
+ return;
54
+ }
55
+ this.send().catch(() => {});
56
+ }, TYPING_INTERVAL_MS);
57
+ }
58
+
59
+ /** Stop periodic keep-alive sends. */
60
+ stop(): void {
61
+ this.stopped = true;
62
+ if (this.timer) {
63
+ clearInterval(this.timer);
64
+ this.timer = null;
65
+ }
66
+ }
67
+
68
+ private async send(): Promise<void> {
69
+ try {
70
+ const token = await this.getToken();
71
+ await this.sendAttempt(token);
72
+ } catch (err) {
73
+ try {
74
+ this.clearCache();
75
+ const token = await this.getToken();
76
+ await this.sendAttempt(token);
77
+ } catch {
78
+ this.log?.debug?.(
79
+ `Typing keep-alive failed for ${this.openid}: ${formatErrorMessage(err)}`,
80
+ );
81
+ }
82
+ }
83
+ }
84
+
85
+ private async sendAttempt(token: string): Promise<void> {
86
+ if (this.stopped || this.renewalsRemaining <= 0) {
87
+ this.stop();
88
+ return;
89
+ }
90
+
91
+ this.renewalsRemaining--;
92
+ try {
93
+ await this.sendInputNotify(token, this.openid, this.msgId, TYPING_INPUT_SECOND);
94
+ this.log?.debug?.(`Typing keep-alive sent to ${this.openid}`);
95
+ } finally {
96
+ if (this.renewalsRemaining <= 0) {
97
+ this.log?.debug?.(`Typing keep-alive budget exhausted for ${this.openid}`);
98
+ this.stop();
99
+ }
100
+ }
101
+ }
102
+ }
@@ -0,0 +1,16 @@
1
+ import type { Agent } from "node:http";
2
+ import { resolveAmbientNodeProxyAgent } from "autobot/plugin-sdk/extension-shared";
3
+ import WebSocket from "ws";
4
+
5
+ export interface QQWSClientOptions {
6
+ gatewayUrl: string;
7
+ userAgent: string;
8
+ }
9
+
10
+ export async function createQQWSClient(options: QQWSClientOptions): Promise<WebSocket> {
11
+ const wsAgent = await resolveAmbientNodeProxyAgent<Agent>();
12
+ return new WebSocket(options.gatewayUrl, {
13
+ headers: { "User-Agent": options.userAgent },
14
+ ...(wsAgent ? { agent: wsAgent } : {}),
15
+ });
16
+ }
@@ -0,0 +1,88 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+
4
+ export type GroupActivationMode = "mention" | "always";
5
+
6
+ export interface SessionStoreReader {
7
+ read(params: {
8
+ cfg: Record<string, unknown>;
9
+ agentId: string;
10
+ }): Record<string, { groupActivation?: string }> | null;
11
+ }
12
+
13
+ export function resolveGroupActivation(params: {
14
+ cfg: Record<string, unknown>;
15
+ agentId: string;
16
+ sessionKey: string;
17
+ configRequireMention: boolean;
18
+ sessionStoreReader?: SessionStoreReader;
19
+ }): GroupActivationMode {
20
+ const fallback: GroupActivationMode = params.configRequireMention ? "mention" : "always";
21
+
22
+ const store = params.sessionStoreReader?.read({
23
+ cfg: params.cfg,
24
+ agentId: params.agentId,
25
+ });
26
+ if (!store) {
27
+ return fallback;
28
+ }
29
+
30
+ const entry = store[params.sessionKey];
31
+ if (!entry?.groupActivation) {
32
+ return fallback;
33
+ }
34
+
35
+ const normalized = entry.groupActivation.trim().toLowerCase();
36
+ if (normalized === "mention" || normalized === "always") {
37
+ return normalized;
38
+ }
39
+ return fallback;
40
+ }
41
+
42
+ function resolveSessionStorePath(
43
+ cfg: Record<string, unknown>,
44
+ agentId: string | undefined,
45
+ ): string {
46
+ const resolvedAgentId = agentId || "default";
47
+
48
+ const session =
49
+ typeof cfg.session === "object" && cfg.session !== null
50
+ ? (cfg.session as { store?: unknown })
51
+ : undefined;
52
+ const rawStore = typeof session?.store === "string" ? session.store : undefined;
53
+
54
+ if (rawStore) {
55
+ let expanded = rawStore;
56
+ if (expanded.includes("{agentId}")) {
57
+ expanded = expanded.replaceAll("{agentId}", resolvedAgentId);
58
+ }
59
+ if (expanded.startsWith("~")) {
60
+ const home = process.env.HOME || process.env.USERPROFILE || "";
61
+ expanded = expanded.replace(/^~/, home);
62
+ }
63
+ return path.resolve(expanded);
64
+ }
65
+
66
+ const stateDir =
67
+ process.env.AUTOBOT_STATE_DIR?.trim() ||
68
+ process.env.CLAWDBOT_STATE_DIR?.trim() ||
69
+ path.join(process.env.HOME || process.env.USERPROFILE || "", ".autobot");
70
+ return path.join(stateDir, "agents", resolvedAgentId, "sessions", "sessions.json");
71
+ }
72
+
73
+ export function createNodeSessionStoreReader(): SessionStoreReader {
74
+ return {
75
+ read: ({ cfg, agentId }) => {
76
+ try {
77
+ const storePath = resolveSessionStorePath(cfg, agentId);
78
+ if (!fs.existsSync(storePath)) {
79
+ return null;
80
+ }
81
+ const raw = fs.readFileSync(storePath, "utf-8");
82
+ return JSON.parse(raw) as Record<string, { groupActivation?: string }>;
83
+ } catch {
84
+ return null;
85
+ }
86
+ },
87
+ };
88
+ }