@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.
- package/api.ts +56 -0
- package/autobot.plugin.json +167 -0
- package/channel-plugin-api.ts +1 -0
- package/index.ts +33 -0
- package/package.json +64 -0
- package/runtime-api.ts +9 -0
- package/secret-contract-api.ts +5 -0
- package/setup-entry.ts +13 -0
- package/setup-plugin-api.ts +3 -0
- package/skills/qqbot-channel/SKILL.md +262 -0
- package/skills/qqbot-channel/references/api_references.md +521 -0
- package/skills/qqbot-media/SKILL.md +37 -0
- package/skills/qqbot-remind/SKILL.md +153 -0
- package/src/bridge/approval/capability.ts +225 -0
- package/src/bridge/approval/handler-runtime.ts +204 -0
- package/src/bridge/bootstrap.ts +135 -0
- package/src/bridge/channel-entry.ts +18 -0
- package/src/bridge/commands/framework-context-adapter.ts +60 -0
- package/src/bridge/commands/framework-registration.ts +66 -0
- package/src/bridge/commands/from-parser.ts +60 -0
- package/src/bridge/commands/result-dispatcher.ts +76 -0
- package/src/bridge/config-shared.ts +132 -0
- package/src/bridge/config.ts +176 -0
- package/src/bridge/gateway.ts +178 -0
- package/src/bridge/logger.ts +31 -0
- package/src/bridge/narrowing.ts +31 -0
- package/src/bridge/plugin-version.ts +102 -0
- package/src/bridge/runtime.ts +25 -0
- package/src/bridge/sdk-adapter.ts +164 -0
- package/src/bridge/setup/finalize.ts +144 -0
- package/src/bridge/setup/surface.ts +34 -0
- package/src/bridge/tools/channel.ts +58 -0
- package/src/bridge/tools/index.ts +15 -0
- package/src/bridge/tools/remind.ts +91 -0
- package/src/channel.setup.ts +33 -0
- package/src/channel.ts +399 -0
- package/src/config-schema.ts +84 -0
- package/src/engine/access/index.ts +2 -0
- package/src/engine/access/resolve-policy.ts +30 -0
- package/src/engine/access/sender-match.ts +55 -0
- package/src/engine/access/types.ts +2 -0
- package/src/engine/adapter/audio.port.ts +27 -0
- package/src/engine/adapter/commands.port.ts +22 -0
- package/src/engine/adapter/history.port.ts +52 -0
- package/src/engine/adapter/index.ts +76 -0
- package/src/engine/adapter/mention-gate.port.ts +50 -0
- package/src/engine/adapter/types.ts +38 -0
- package/src/engine/api/api-client.ts +212 -0
- package/src/engine/api/media-chunked.ts +644 -0
- package/src/engine/api/media.ts +218 -0
- package/src/engine/api/messages.ts +293 -0
- package/src/engine/api/retry.ts +217 -0
- package/src/engine/api/routes.ts +95 -0
- package/src/engine/api/token.ts +277 -0
- package/src/engine/approval/index.ts +224 -0
- package/src/engine/commands/builtin/log-helpers.ts +341 -0
- package/src/engine/commands/builtin/register-all.ts +17 -0
- package/src/engine/commands/builtin/register-approve.ts +201 -0
- package/src/engine/commands/builtin/register-basic.ts +95 -0
- package/src/engine/commands/builtin/register-clear-storage.ts +187 -0
- package/src/engine/commands/builtin/register-logs.ts +20 -0
- package/src/engine/commands/builtin/register-streaming.ts +138 -0
- package/src/engine/commands/builtin/state.ts +31 -0
- package/src/engine/commands/slash-command-auth.ts +88 -0
- package/src/engine/commands/slash-command-handler.ts +168 -0
- package/src/engine/commands/slash-command-test-support.ts +39 -0
- package/src/engine/commands/slash-commands-impl.ts +61 -0
- package/src/engine/commands/slash-commands.ts +202 -0
- package/src/engine/config/credential-backup.ts +108 -0
- package/src/engine/config/credentials.ts +76 -0
- package/src/engine/config/group.ts +227 -0
- package/src/engine/config/resolve.ts +283 -0
- package/src/engine/config/setup-logic.ts +84 -0
- package/src/engine/gateway/active-cfg.ts +52 -0
- package/src/engine/gateway/codec.ts +47 -0
- package/src/engine/gateway/constants.ts +117 -0
- package/src/engine/gateway/event-dispatcher.ts +177 -0
- package/src/engine/gateway/gateway-connection.ts +356 -0
- package/src/engine/gateway/gateway.ts +267 -0
- package/src/engine/gateway/inbound-attachments.ts +360 -0
- package/src/engine/gateway/inbound-context.ts +82 -0
- package/src/engine/gateway/inbound-pipeline.ts +171 -0
- package/src/engine/gateway/interaction-handler.ts +345 -0
- package/src/engine/gateway/message-queue.ts +404 -0
- package/src/engine/gateway/outbound-dispatch.ts +590 -0
- package/src/engine/gateway/reconnect.ts +199 -0
- package/src/engine/gateway/stages/access-stage.ts +99 -0
- package/src/engine/gateway/stages/assembly-stage.ts +156 -0
- package/src/engine/gateway/stages/content-stage.ts +77 -0
- package/src/engine/gateway/stages/envelope-stage.ts +144 -0
- package/src/engine/gateway/stages/group-gate-stage.ts +223 -0
- package/src/engine/gateway/stages/index.ts +18 -0
- package/src/engine/gateway/stages/quote-stage.ts +113 -0
- package/src/engine/gateway/stages/refidx-stage.ts +62 -0
- package/src/engine/gateway/stages/stub-contexts.ts +77 -0
- package/src/engine/gateway/types.ts +230 -0
- package/src/engine/gateway/typing-keepalive.ts +102 -0
- package/src/engine/gateway/ws-client.ts +16 -0
- package/src/engine/group/activation.ts +88 -0
- package/src/engine/group/history.ts +321 -0
- package/src/engine/group/mention.ts +114 -0
- package/src/engine/group/message-gating.ts +108 -0
- package/src/engine/messaging/decode-media-path.ts +82 -0
- package/src/engine/messaging/media-source.ts +210 -0
- package/src/engine/messaging/media-type-detect.ts +27 -0
- package/src/engine/messaging/outbound-audio-port.ts +38 -0
- package/src/engine/messaging/outbound-deliver.ts +810 -0
- package/src/engine/messaging/outbound-media-send.ts +658 -0
- package/src/engine/messaging/outbound-reply.ts +27 -0
- package/src/engine/messaging/outbound-result-helpers.ts +54 -0
- package/src/engine/messaging/outbound-types.ts +47 -0
- package/src/engine/messaging/outbound.ts +485 -0
- package/src/engine/messaging/reply-dispatcher.ts +597 -0
- package/src/engine/messaging/reply-limiter.ts +164 -0
- package/src/engine/messaging/sender.ts +741 -0
- package/src/engine/messaging/streaming-c2c.ts +1192 -0
- package/src/engine/messaging/streaming-media-send.ts +544 -0
- package/src/engine/messaging/target-parser.ts +104 -0
- package/src/engine/ref/format-message-ref.ts +142 -0
- package/src/engine/ref/format-ref-entry.ts +27 -0
- package/src/engine/ref/store.ts +211 -0
- package/src/engine/ref/types.ts +27 -0
- package/src/engine/session/known-users.ts +138 -0
- package/src/engine/session/session-store.ts +207 -0
- package/src/engine/tools/channel-api.ts +244 -0
- package/src/engine/tools/remind-logic.ts +377 -0
- package/src/engine/types.ts +313 -0
- package/src/engine/utils/attachment-tags.ts +174 -0
- package/src/engine/utils/audio.ts +525 -0
- package/src/engine/utils/data-paths.ts +38 -0
- package/src/engine/utils/diagnostics.ts +93 -0
- package/src/engine/utils/file-utils.ts +215 -0
- package/src/engine/utils/format.ts +70 -0
- package/src/engine/utils/image-size.ts +249 -0
- package/src/engine/utils/log.ts +77 -0
- package/src/engine/utils/media-tags.ts +177 -0
- package/src/engine/utils/payload.ts +157 -0
- package/src/engine/utils/platform.ts +265 -0
- package/src/engine/utils/request-context.ts +60 -0
- package/src/engine/utils/string-normalize.ts +91 -0
- package/src/engine/utils/stt.ts +103 -0
- package/src/engine/utils/text-parsing.ts +155 -0
- package/src/engine/utils/upload-cache.ts +96 -0
- package/src/engine/utils/voice-text.ts +15 -0
- package/src/exec-approvals.ts +237 -0
- package/src/qqbot-test-support.ts +29 -0
- package/src/secret-contract.ts +82 -0
- package/src/types.ts +210 -0
- 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
|
+
}
|