@krischoichoi/channel-qq 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 (54) hide show
  1. package/README.md +93 -0
  2. package/lib/adapter.d.ts +76 -0
  3. package/lib/adapter.d.ts.map +1 -0
  4. package/lib/adapter.js +299 -0
  5. package/lib/adapter.js.map +1 -0
  6. package/lib/config.d.ts +62 -0
  7. package/lib/config.d.ts.map +1 -0
  8. package/lib/config.js +43 -0
  9. package/lib/config.js.map +1 -0
  10. package/lib/definition.d.ts +75 -0
  11. package/lib/definition.d.ts.map +1 -0
  12. package/lib/definition.js +232 -0
  13. package/lib/definition.js.map +1 -0
  14. package/lib/image-hydrator.d.ts +13 -0
  15. package/lib/image-hydrator.d.ts.map +1 -0
  16. package/lib/image-hydrator.js +13 -0
  17. package/lib/image-hydrator.js.map +1 -0
  18. package/lib/inbound.d.ts +49 -0
  19. package/lib/inbound.d.ts.map +1 -0
  20. package/lib/inbound.js +98 -0
  21. package/lib/inbound.js.map +1 -0
  22. package/lib/index.d.ts +53 -0
  23. package/lib/index.d.ts.map +1 -0
  24. package/lib/index.js +113 -0
  25. package/lib/index.js.map +1 -0
  26. package/lib/interaction-mapper.d.ts +43 -0
  27. package/lib/interaction-mapper.d.ts.map +1 -0
  28. package/lib/interaction-mapper.js +91 -0
  29. package/lib/interaction-mapper.js.map +1 -0
  30. package/lib/manifest.d.ts +22 -0
  31. package/lib/manifest.d.ts.map +1 -0
  32. package/lib/manifest.js +35 -0
  33. package/lib/manifest.js.map +1 -0
  34. package/lib/mapper.d.ts +38 -0
  35. package/lib/mapper.d.ts.map +1 -0
  36. package/lib/mapper.js +109 -0
  37. package/lib/mapper.js.map +1 -0
  38. package/lib/media-hydrator.d.ts +61 -0
  39. package/lib/media-hydrator.d.ts.map +1 -0
  40. package/lib/media-hydrator.js +107 -0
  41. package/lib/media-hydrator.js.map +1 -0
  42. package/lib/outbound.d.ts +41 -0
  43. package/lib/outbound.d.ts.map +1 -0
  44. package/lib/outbound.js +111 -0
  45. package/lib/outbound.js.map +1 -0
  46. package/lib/sdk-client.d.ts +247 -0
  47. package/lib/sdk-client.d.ts.map +1 -0
  48. package/lib/sdk-client.js +312 -0
  49. package/lib/sdk-client.js.map +1 -0
  50. package/lib/streaming-reply.d.ts +19 -0
  51. package/lib/streaming-reply.d.ts.map +1 -0
  52. package/lib/streaming-reply.js +26 -0
  53. package/lib/streaming-reply.js.map +1 -0
  54. package/package.json +46 -0
package/README.md ADDED
@@ -0,0 +1,93 @@
1
+ # @krischoichoi/channel-qq
2
+
3
+ QQ Bot channel adapter for DeepSeek Harness.
4
+
5
+ Consumes the official Tencent SDK `@tencent-connect/qqbot-nodejs` and maps it to
6
+ the stable Channel Contract. The SDK owns token acquisition, the WebSocket
7
+ gateway, media upload and C2C streaming; the adapter adds dedup, reply routing,
8
+ image hydration and DSH lifecycle handling.
9
+
10
+ ## Install
11
+
12
+ ```bash
13
+ pnpm add @krischoichoi/channel-qq
14
+ ```
15
+
16
+ Or install the whole bundle:
17
+
18
+ ```bash
19
+ npx @deepseek-ai/dsh plugin --profile web add -w @krischoichoi/dsh-channels@latest
20
+ ```
21
+
22
+ ## Configuration
23
+
24
+ ```yaml
25
+ - id: channels-qq
26
+ name: '@krischoichoi/channel-qq'
27
+ inject: [channels, credentials, channelControl]
28
+ config:
29
+ enabled: true
30
+ accountId: main
31
+ appId: "102345678" # QQ 开放平台 AppId(非机密)
32
+ appSecretRef: QQBOT_APP_SECRET # 真实 AppSecret 只存 ctx.credentials
33
+ markdownSupport: false
34
+ streaming:
35
+ enabled: true
36
+ throttleMs: 500
37
+ dedup:
38
+ enabled: true
39
+ windowMs: 5000
40
+ startupTimeoutMs: 15000
41
+ ```
42
+
43
+ The AppSecret is resolved through `ctx.credentials` at startup and injected as
44
+ `deps.appSecret`. It never appears in config, logs or fixtures.
45
+
46
+ ## Streaming
47
+
48
+ Streaming is target-aware:
49
+
50
+ - **C2C + reply-to-message-id** → `native` (full-text replace streaming)
51
+ - **group / other targets** → `buffered` (send once at turn end)
52
+
53
+ ## Capabilities
54
+
55
+ | Capability | Value |
56
+ | --- | --- |
57
+ | text / image / file / audio / video | ✅ |
58
+ | markdown | depends on `markdownSupport` |
59
+ | cards / reactions / threads | ❌ |
60
+ | interactive actions | ✅(新版 QQ Markdown inline keyboard) |
61
+ | streaming | `native` (C2C) / `buffered` (default) |
62
+
63
+ Interactive questions always use the new QQ Markdown keyboard payload
64
+ (`msg_type=2`, `markdown.content`, `keyboard`). Button callbacks use action
65
+ type `1` and require the `INTERACTION` Gateway intent. Ordinary text replies
66
+ continue to follow `markdownSupport`. The QQ Bot account must have the current
67
+ Markdown/interactive-message platform capability; this is a live platform
68
+ entitlement and cannot be proven from local configuration alone.
69
+
70
+ ## Upstream
71
+
72
+ | Field | Value |
73
+ | --- | --- |
74
+ | SDK | `@tencent-connect/qqbot-nodejs` |
75
+ | Tested version | `1.0.4` |
76
+ | Status | `tested` (offline contract + fixture + E2E suites) |
77
+
78
+ ## Development
79
+
80
+ ```bash
81
+ pnpm --filter @krischoichoi/channel-qq build
82
+ pnpm --filter @krischoichoi/channel-qq typecheck
83
+ pnpm --filter @krischoichoi/channel-qq test
84
+ ```
85
+
86
+ ## Related
87
+
88
+ - [Repository root](../../README.md)
89
+ - [Adapter authoring guide](../../docs/adapter-authoring.md)
90
+
91
+ ## License
92
+
93
+ [MIT](../../LICENSE)
@@ -0,0 +1,76 @@
1
+ /**
2
+ * QQ channel adapter — maps the Tencent SDK to the Channel Contract.
3
+ *
4
+ * The SDK owns Token acquisition/refresh, the WebSocket gateway (hello /
5
+ * identify / heartbeat / RESUME / reconnect) and the OpenAPI REST surface.
6
+ * The adapter only registers lifecycle handlers (`ready`/`resumed`/`error`/
7
+ * `message`) and drives `start`/`stop`. It never runs its own receive or
8
+ * reconnect loop (no double reconnect, no backoff).
9
+ *
10
+ * Streaming is target-aware: C2C + triggering `replyToMessageId` → native
11
+ * (`createReply` → `QQStreamingReply`); everywhere else → buffered (reply
12
+ * router accumulates and sends once at `turn/end`).
13
+ *
14
+ * The QQ AppSecret never lives in config: it is resolved via `ctx.credentials`
15
+ * by the plugin (`appSecretRef`) and handed to the adapter as
16
+ * `deps.appSecret`. Missing credential → startup fails loudly (QQ-R5).
17
+ */
18
+ import type { ChannelAdapter, ChannelAdapterContext, ChannelCapabilities, ChannelHealth, ChannelTarget, OutboundMessage, ReplyHandle, SendResult, StreamingMode } from '@krischoichoi/channel-core';
19
+ import { SecureRemoteMediaFetcher } from '@krischoichoi/channel-core';
20
+ import type { QQConfig } from './config.js';
21
+ import { type QQSdkClient } from './sdk-client.js';
22
+ import { type QQManifest } from './manifest.js';
23
+ export interface QQAdapterDeps {
24
+ /** SDK client (tests inject the Fake). Defaults to a real Tencent client. */
25
+ sdkClient?: QQSdkClient;
26
+ /** Resolved QQ AppSecret credential value (injected by the plugin). Tests using the Fake client may omit it. */
27
+ appSecret?: string;
28
+ /**
29
+ * Optional secure remote media fetcher used to hydrate inbound media bytes
30
+ * (image/file/audio/video) before emit. Tests inject a
31
+ * fake so the inbound path is fully offline; production defaults to a real
32
+ * `SecureRemoteMediaFetcher`. Configurable here (not in config) because it
33
+ * is a low-level seam, not a user-facing knob.
34
+ */
35
+ secureFetch?: SecureRemoteMediaFetcher;
36
+ /** Injectable clock (tests). */
37
+ now?: () => number;
38
+ /** Called after the platform-delivered C2C sender is accepted as owner. */
39
+ onOwnerDiscovered?: (ownerId: string) => Promise<void> | void;
40
+ }
41
+ export declare class QQAdapter implements ChannelAdapter {
42
+ private readonly config;
43
+ readonly id = "qq";
44
+ /** Upstream compatibility manifest (read structurally by `channels doctor`). */
45
+ readonly manifest: QQManifest;
46
+ readonly capabilities: ChannelCapabilities;
47
+ /**
48
+ * Test-only accessor exposing the injected `appSecret` so unit tests can
49
+ * assert the resolved credential reached the adapter without triggering the
50
+ * real Tencent client build. Produces no value in any production path.
51
+ */
52
+ get testAppSecret(): string | undefined;
53
+ private ctx?;
54
+ private readonly deps;
55
+ private readonly now;
56
+ private client;
57
+ private inbound;
58
+ private outbound;
59
+ private started;
60
+ private connected;
61
+ private error?;
62
+ private runPromise?;
63
+ constructor(config: QQConfig, deps?: QQAdapterDeps);
64
+ resolveStreamingMode(target: ChannelTarget): StreamingMode;
65
+ start(ctx: ChannelAdapterContext): Promise<void>;
66
+ stop(): Promise<void>;
67
+ send(target: ChannelTarget, message: OutboundMessage): Promise<SendResult>;
68
+ createReply(target: ChannelTarget): Promise<ReplyHandle>;
69
+ getHealth(): Promise<ChannelHealth>;
70
+ private handleSdkError;
71
+ private handleInbound;
72
+ private handleInteraction;
73
+ private emitAuth;
74
+ private emitConnection;
75
+ }
76
+ //# sourceMappingURL=adapter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../src/adapter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AACH,OAAO,KAAK,EACV,cAAc,EACd,qBAAqB,EACrB,mBAAmB,EACnB,aAAa,EACb,aAAa,EACb,eAAe,EACf,WAAW,EACX,UAAU,EACV,aAAa,EACd,MAAM,4BAA4B,CAAC;AACpC,OAAO,EAAgB,wBAAwB,EAAE,MAAM,4BAA4B,CAAC;AAEpF,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAI5C,OAAO,EAA8C,KAAK,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAE/F,OAAO,EAA0B,KAAK,UAAU,EAAE,MAAM,eAAe,CAAC;AAExE,MAAM,WAAW,aAAa;IAC5B,6EAA6E;IAC7E,SAAS,CAAC,EAAE,WAAW,CAAC;IACxB,gHAAgH;IAChH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;;;OAMG;IACH,WAAW,CAAC,EAAE,wBAAwB,CAAC;IACvC,gCAAgC;IAChC,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IACnB,2EAA2E;IAC3E,iBAAiB,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;CAC/D;AAED,qBAAa,SAAU,YAAW,cAAc;IA2BlC,OAAO,CAAC,QAAQ,CAAC,MAAM;IA1BnC,QAAQ,CAAC,EAAE,QAAQ;IAEnB,gFAAgF;IAChF,QAAQ,CAAC,QAAQ,EAAE,UAAU,CAAc;IAC3C,QAAQ,CAAC,YAAY,EAAE,mBAAmB,CAAC;IAE3C;;;;OAIG;IACH,IAAI,aAAa,IAAI,MAAM,GAAG,SAAS,CAEtC;IAED,OAAO,CAAC,GAAG,CAAC,CAAwB;IACpC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAgB;IACrC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAe;IACnC,OAAO,CAAC,MAAM,CAAe;IAC7B,OAAO,CAAC,OAAO,CAAoB;IACnC,OAAO,CAAC,QAAQ,CAAkB;IAClC,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,KAAK,CAAC,CAAQ;IACtB,OAAO,CAAC,UAAU,CAAC,CAAgB;gBAEN,MAAM,EAAE,QAAQ,EAAE,IAAI,GAAE,aAAkB;IA+BvE,oBAAoB,CAAC,MAAM,EAAE,aAAa,GAAG,aAAa;IAYpD,KAAK,CAAC,GAAG,EAAE,qBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC;IA0EhD,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAUrB,IAAI,CAAC,MAAM,EAAE,aAAa,EAAE,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,UAAU,CAAC;IAO1E,WAAW,CAAC,MAAM,EAAE,aAAa,GAAG,OAAO,CAAC,WAAW,CAAC;IAcxD,SAAS,IAAI,OAAO,CAAC,aAAa,CAAC;IA6BzC,OAAO,CAAC,cAAc;YAOR,aAAa;YAOb,iBAAiB;IA8C/B,OAAO,CAAC,QAAQ;IAYhB,OAAO,CAAC,cAAc;CAWvB"}
package/lib/adapter.js ADDED
@@ -0,0 +1,299 @@
1
+ import { ChannelError, SecureRemoteMediaFetcher } from '@krischoichoi/channel-core';
2
+ import { InboundProcessor } from './inbound.js';
3
+ import { mapInteraction } from './interaction-mapper.js';
4
+ import { OutboundSender } from './outbound.js';
5
+ import { TencentQQSdkClient } from './sdk-client.js';
6
+ import { QQStreamingReply } from './streaming-reply.js';
7
+ import { manifest as qqManifest } from './manifest.js';
8
+ export class QQAdapter {
9
+ config;
10
+ id = 'qq';
11
+ /** Upstream compatibility manifest (read structurally by `channels doctor`). */
12
+ manifest = qqManifest;
13
+ capabilities;
14
+ /**
15
+ * Test-only accessor exposing the injected `appSecret` so unit tests can
16
+ * assert the resolved credential reached the adapter without triggering the
17
+ * real Tencent client build. Produces no value in any production path.
18
+ */
19
+ get testAppSecret() {
20
+ return this.deps.appSecret;
21
+ }
22
+ ctx;
23
+ deps;
24
+ now;
25
+ client;
26
+ inbound;
27
+ outbound;
28
+ started = false;
29
+ connected = false;
30
+ error;
31
+ runPromise;
32
+ constructor(config, deps = {}) {
33
+ this.config = config;
34
+ this.deps = deps;
35
+ this.now = deps.now ?? Date.now;
36
+ this.capabilities = {
37
+ text: true,
38
+ image: true,
39
+ file: true,
40
+ audio: true,
41
+ video: true,
42
+ markdown: config.markdownSupport,
43
+ cards: false,
44
+ reactions: false,
45
+ threads: false,
46
+ // Native inline keyboard round-trip (P1): `OutboundMessage.actions` →
47
+ // QQ inline keyboard, button presses → `interaction.received`.
48
+ interactiveActions: true,
49
+ // Directional, per-kind media precision:
50
+ // inbound 'bytes' — the inbound path hydrates every binary kind through
51
+ // `SecureRemoteMediaFetcher` before emit (image/file/audio/video);
52
+ // outbound 'bytes' — `sendMedia` uploads `localData` bytes via the SDK
53
+ // `fileData` carrier for every kind (IMAGE / VOICE / VIDEO / FILE).
54
+ media: {
55
+ inbound: { image: 'bytes', file: 'bytes', audio: 'bytes', video: 'bytes' },
56
+ outbound: { image: 'bytes', file: 'bytes', audio: 'bytes', video: 'bytes' },
57
+ },
58
+ // Conservative default; `resolveStreamingMode` upgrades C2C+msgId to
59
+ // native streaming.
60
+ streaming: 'buffered',
61
+ };
62
+ }
63
+ resolveStreamingMode(target) {
64
+ // `streaming.enabled: false` is a hard off-switch: it forces the buffered
65
+ // send-once strategy even for a C2C target with a reply message id.
66
+ if (!this.config.streaming.enabled) {
67
+ return 'buffered';
68
+ }
69
+ if (target.conversationType === 'dm' && target.replyToMessageId) {
70
+ return 'native';
71
+ }
72
+ return 'buffered';
73
+ }
74
+ async start(ctx) {
75
+ if (this.started)
76
+ return;
77
+ this.ctx = ctx;
78
+ this.connected = false;
79
+ this.error = undefined;
80
+ const ready = createDeferred();
81
+ this.client =
82
+ this.deps.sdkClient ??
83
+ new TencentQQSdkClient(this.config, ctx.logger, requireAppSecret(this.deps.appSecret, this.config));
84
+ this.inbound = new InboundProcessor({
85
+ ctx,
86
+ meta: { channel: this.id, accountId: this.config.accountId },
87
+ dedupEnabled: this.config.dedup.enabled,
88
+ dedupWindowMs: this.config.dedup.windowMs,
89
+ secureFetch: this.deps.secureFetch,
90
+ now: this.now,
91
+ });
92
+ this.outbound = new OutboundSender(this.client, ctx.logger);
93
+ this.client.onReady(() => {
94
+ this.connected = true;
95
+ void this.emitAuth('authenticated');
96
+ void this.emitConnection('connected');
97
+ ready.resolve();
98
+ });
99
+ this.client.onResumed(() => {
100
+ this.connected = true;
101
+ void this.emitConnection('connected');
102
+ });
103
+ this.client.onError((error) => {
104
+ this.handleSdkError(error);
105
+ });
106
+ this.client.onMessage((message) => {
107
+ void this.handleInbound(message);
108
+ });
109
+ this.client.onInteraction((event) => {
110
+ void this.handleInteraction(event);
111
+ });
112
+ // Never `await bot.start()` inline — it resolves only on stop/abort and
113
+ // would block the Cordis plugin init. Track the raw promise and surface
114
+ // failures through `handleSdkError`.
115
+ this.runPromise = this.client.start(ctx.signal);
116
+ // Fail-fast (QQ-R14): `tokenPrefetch: 'sync'` makes the SDK reject on bad
117
+ // credentials before `ready`. Propagate that rejection to the startup
118
+ // deferred instead of letting it hang until the timeout, and still record
119
+ // the error for health/event reporting.
120
+ this.runPromise.catch((error) => {
121
+ this.handleSdkError(error);
122
+ ready.reject(error);
123
+ });
124
+ try {
125
+ await withTimeout(ready.promise, this.config.startupTimeoutMs);
126
+ this.started = true;
127
+ }
128
+ catch (error) {
129
+ // Rollback: stop the (possibly half-started) SDK client and settle the
130
+ // run promise so a failed start never leaks a live client behind a
131
+ // `started === false` adapter.
132
+ this.client.stop();
133
+ await this.runPromise?.catch(() => undefined);
134
+ this.runPromise = undefined;
135
+ throw error;
136
+ }
137
+ }
138
+ async stop() {
139
+ if (!this.started && this.runPromise === undefined)
140
+ return;
141
+ this.started = false;
142
+ this.connected = false;
143
+ this.client.stop();
144
+ await this.runPromise?.catch(() => undefined);
145
+ this.runPromise = undefined;
146
+ void this.emitConnection('closed');
147
+ }
148
+ async send(target, message) {
149
+ if (!this.started || !this.outbound) {
150
+ throw new ChannelError('CHANNEL_NOT_STARTED', 'qq adapter is not started');
151
+ }
152
+ return this.outbound.send(target, message);
153
+ }
154
+ async createReply(target) {
155
+ if (target.conversationType !== 'dm' || !target.replyToMessageId) {
156
+ throw new ChannelError('CHANNEL_ERROR', 'QQ native streaming requires a C2C reply target with message id');
157
+ }
158
+ const stream = this.client.openStream({ scope: 'c2c', targetId: target.conversationId, msgId: target.replyToMessageId }, { throttleMs: this.config.streaming.throttleMs });
159
+ return new QQStreamingReply(stream);
160
+ }
161
+ async getHealth() {
162
+ if (!this.started) {
163
+ return { status: 'down', detail: 'qq adapter is not started', authenticated: false };
164
+ }
165
+ if (this.error) {
166
+ return {
167
+ status: 'degraded',
168
+ detail: 'sdk error',
169
+ connection: this.connected ? 'connected' : 'disconnected',
170
+ authenticated: this.connected,
171
+ error: this.error.message,
172
+ };
173
+ }
174
+ if (this.connected) {
175
+ return {
176
+ status: 'ok',
177
+ detail: 'connected',
178
+ connection: 'connected',
179
+ authenticated: true,
180
+ };
181
+ }
182
+ return {
183
+ status: 'degraded',
184
+ detail: 'sdk not ready',
185
+ connection: 'disconnected',
186
+ authenticated: false,
187
+ };
188
+ }
189
+ handleSdkError(error) {
190
+ this.error = error;
191
+ this.connected = false;
192
+ this.ctx?.logger.error('[channel-qq] sdk error', error);
193
+ void this.emitConnection('disconnected');
194
+ }
195
+ async handleInbound(message) {
196
+ if (message.kind === 'c2c' && message.senderId) {
197
+ await this.deps.onOwnerDiscovered?.(message.senderId);
198
+ }
199
+ await this.inbound.handle(message);
200
+ }
201
+ async handleInteraction(event) {
202
+ // QQ requires an interaction ACK within ~5s; ACK fire-and-forget first so
203
+ // the platform is satisfied regardless of how long Harness answer
204
+ // resolution takes. Never awaits Harness resolution before ACKing.
205
+ if (event.id) {
206
+ void this.client
207
+ .acknowledgeInteraction(event.id, 0)
208
+ .catch((error) => this.ctx?.logger.debug('[channel-qq] interaction ACK failed', {
209
+ interactionId: event.id,
210
+ error: error instanceof Error ? error.message : error,
211
+ }));
212
+ }
213
+ const result = mapInteraction(event, {
214
+ channel: this.id,
215
+ accountId: this.config.accountId,
216
+ });
217
+ if (!result.ok) {
218
+ // Fail closed: never emit a guessed event (security doc). Drop with a
219
+ // debug note — no raw payload dump.
220
+ this.ctx?.logger.debug('[channel-qq] dropped interaction', { reason: result.reason });
221
+ return;
222
+ }
223
+ const ev = result.event;
224
+ // Inbound summary (project rule): no raw payload dump.
225
+ this.ctx?.logger.info(`[channel-qq] interaction from ${ev.sender.id} in ${ev.conversation.id}`, {
226
+ channel: this.id,
227
+ accountId: this.config.accountId,
228
+ conversationType: ev.conversation.type,
229
+ interactionId: ev.interactionId,
230
+ });
231
+ // Adapter only emits the canonical event; the Harness Access Gate
232
+ // authorizes (never implement ACL here).
233
+ if (this.ctx) {
234
+ await this.ctx.emit(ev);
235
+ }
236
+ }
237
+ emitAuth(state) {
238
+ if (!this.ctx)
239
+ return Promise.resolve();
240
+ return this.ctx
241
+ .emit({
242
+ type: 'auth.changed',
243
+ channel: this.id,
244
+ accountId: this.config.accountId,
245
+ state,
246
+ })
247
+ .then(() => undefined);
248
+ }
249
+ emitConnection(state) {
250
+ if (!this.ctx)
251
+ return Promise.resolve();
252
+ return this.ctx
253
+ .emit({
254
+ type: 'connection.changed',
255
+ channel: this.id,
256
+ accountId: this.config.accountId,
257
+ state,
258
+ })
259
+ .then(() => undefined);
260
+ }
261
+ }
262
+ /** Minimal deferred primitive. */
263
+ function createDeferred() {
264
+ let resolve;
265
+ let reject;
266
+ const promise = new Promise((res, rej) => {
267
+ resolve = res;
268
+ reject = rej;
269
+ });
270
+ return { promise, resolve, reject };
271
+ }
272
+ /** Resolve a promise or reject after `timeoutMs`. */
273
+ async function withTimeout(promise, timeoutMs) {
274
+ let timer;
275
+ try {
276
+ return await Promise.race([
277
+ promise,
278
+ new Promise((_, reject) => {
279
+ timer = setTimeout(() => reject(new ChannelError('CHANNEL_START_FAILED', 'qq adapter startup timed out')), timeoutMs);
280
+ }),
281
+ ]);
282
+ }
283
+ finally {
284
+ if (timer)
285
+ clearTimeout(timer);
286
+ }
287
+ }
288
+ /**
289
+ * Fail loudly when the resolved QQ AppSecret credential is missing/empty —
290
+ * the adapter must not construct a real Tencent client with an empty secret
291
+ * (v1.1 QQ-R5). The Fake client path never reaches this helper.
292
+ */
293
+ function requireAppSecret(appSecret, config) {
294
+ if (!appSecret) {
295
+ throw new ChannelError('CHANNEL_ERROR', `QQ credential "${config.appSecretRef}" is not configured`);
296
+ }
297
+ return appSecret;
298
+ }
299
+ //# sourceMappingURL=adapter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"adapter.js","sourceRoot":"","sources":["../src/adapter.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,62 @@
1
+ /**
2
+ * QQ adapter configuration (Schemastery).
3
+ *
4
+ * V1 consumes the official Tencent SDK `@tencent-connect/qqbot-nodejs`, which
5
+ * owns Token acquisition/refresh, the WebSocket gateway (hello/identify/
6
+ * heartbeat/RESUME/reconnect) and the OpenAPI REST surface. The adapter only
7
+ * needs the AppId and behavioural tuning — no base URL, transport, reconnect
8
+ * budget or QR auth config.
9
+ *
10
+ * The real AppSecret is **never** written into config / profile / git. Config
11
+ * only carries its credential reference name (`appSecretRef`); the actual
12
+ * secret is resolved at startup through `ctx.credentials` (v1.1 §6.2, QQ-R5).
13
+ */
14
+ import Schema from '@deepseek-ai/schemastery';
15
+ import type { Volatile } from '@deepseek-ai/cordis';
16
+ export declare const QQ_APP_SECRET_REF = "QQBOT_APP_SECRET";
17
+ export interface QQStreamingConfig {
18
+ /** Whether incremental outbound streaming is enabled. */
19
+ enabled: boolean;
20
+ /** Stream flush throttle (ms). Tencent SDK: default 500, min 300. */
21
+ throttleMs: number;
22
+ }
23
+ export interface QQDedupConfig {
24
+ enabled: boolean;
25
+ windowMs: number;
26
+ }
27
+ export interface QQConfig {
28
+ enabled: boolean;
29
+ /** Account id within the qq channel (defaults to 'main'). */
30
+ accountId: string;
31
+ /** QQ Open Platform AppId (not a secret — may live in config/git). */
32
+ appId: string;
33
+ /**
34
+ * Credential reference name for the QQ Open Platform AppSecret (e.g.
35
+ * `'QQBOT_APP_SECRET'`). Only the reference — never the real secret — is
36
+ * stored here; the secret is resolved via `ctx.credentials` at startup.
37
+ */
38
+ appSecretRef: string;
39
+ /** Whether ordinary bot replies may use Markdown. Interactive keyboard questions use the new QQ Markdown wire format regardless. */
40
+ markdownSupport: boolean;
41
+ streaming: QQStreamingConfig;
42
+ dedup: QQDedupConfig;
43
+ /** How long start() waits for the SDK `ready` event before failing. */
44
+ startupTimeoutMs: number;
45
+ }
46
+ /**
47
+ * Activation-time config shape (Harness 0.2.0): every volatile field resolves
48
+ * to a `Volatile<T>` live handle instead of a plain value. `apply()` unwraps
49
+ * these once via `resolveVolatileConfig` into a plain `QQConfig`.
50
+ */
51
+ export interface QQConfigLive {
52
+ enabled: Volatile<boolean>;
53
+ accountId: Volatile<string>;
54
+ appId: Volatile<string>;
55
+ appSecretRef: string;
56
+ markdownSupport: Volatile<boolean>;
57
+ streaming: Volatile<QQStreamingConfig>;
58
+ dedup: Volatile<QQDedupConfig>;
59
+ startupTimeoutMs: Volatile<number>;
60
+ }
61
+ export declare const Config: Schema<QQConfigLive>;
62
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,MAAM,MAAM,0BAA0B,CAAC;AAC9C,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAEpD,eAAO,MAAM,iBAAiB,qBAAqB,CAAC;AAEpD,MAAM,WAAW,iBAAiB;IAChC,yDAAyD;IACzD,OAAO,EAAE,OAAO,CAAC;IACjB,qEAAqE;IACrE,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,QAAQ;IACvB,OAAO,EAAE,OAAO,CAAC;IACjB,6DAA6D;IAC7D,SAAS,EAAE,MAAM,CAAC;IAClB,sEAAsE;IACtE,KAAK,EAAE,MAAM,CAAC;IACd;;;;OAIG;IACH,YAAY,EAAE,MAAM,CAAC;IACrB,oIAAoI;IACpI,eAAe,EAAE,OAAO,CAAC;IACzB,SAAS,EAAE,iBAAiB,CAAC;IAC7B,KAAK,EAAE,aAAa,CAAC;IACrB,uEAAuE;IACvE,gBAAgB,EAAE,MAAM,CAAC;CAC1B;AAED;;;;GAIG;AACH,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,QAAQ,CAAC,OAAO,CAAC,CAAC;IAC3B,SAAS,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;IAC5B,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;IACxB,YAAY,EAAE,MAAM,CAAC;IACrB,eAAe,EAAE,QAAQ,CAAC,OAAO,CAAC,CAAC;IACnC,SAAS,EAAE,QAAQ,CAAC,iBAAiB,CAAC,CAAC;IACvC,KAAK,EAAE,QAAQ,CAAC,aAAa,CAAC,CAAC;IAC/B,gBAAgB,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;CACpC;AAMD,eAAO,MAAM,MAAM,EAsBF,MAAM,CAAC,YAAY,CAAC,CAAC"}
package/lib/config.js ADDED
@@ -0,0 +1,43 @@
1
+ /**
2
+ * QQ adapter configuration (Schemastery).
3
+ *
4
+ * V1 consumes the official Tencent SDK `@tencent-connect/qqbot-nodejs`, which
5
+ * owns Token acquisition/refresh, the WebSocket gateway (hello/identify/
6
+ * heartbeat/RESUME/reconnect) and the OpenAPI REST surface. The adapter only
7
+ * needs the AppId and behavioural tuning — no base URL, transport, reconnect
8
+ * budget or QR auth config.
9
+ *
10
+ * The real AppSecret is **never** written into config / profile / git. Config
11
+ * only carries its credential reference name (`appSecretRef`); the actual
12
+ * secret is resolved at startup through `ctx.credentials` (v1.1 §6.2, QQ-R5).
13
+ */
14
+ import Schema from '@deepseek-ai/schemastery';
15
+ export const QQ_APP_SECRET_REF = 'QQBOT_APP_SECRET';
16
+ // The runtime schema is genuine Schemastery (validation happens at the Loader
17
+ // boundary); the annotation records the activation-time output shape. The cast
18
+ // is required because Schemastery's inferred ObjectT models per-field optionality
19
+ // more precisely than the hand-written `QQConfigLive` alias.
20
+ export const Config = Schema.object({
21
+ // Harness 0.2.0 settings model: fields the control plane / settings page may
22
+ // write at runtime are declared `.volatile()` — they surface on the
23
+ // auto-generated settings form, `ctx.settings.update(entryId, patch)` accepts
24
+ // exactly these paths, and at activation they resolve to `Volatile<T>`
25
+ // handles (unwrapped by `resolveVolatileConfig` in definition.ts / apply()).
26
+ enabled: Schema.boolean().default(true).volatile(),
27
+ accountId: Schema.string().default('main').volatile(),
28
+ // QQ Open Platform AppId (not a secret — may live in config/git).
29
+ appId: Schema.string().volatile(),
30
+ // Credential reference name only — never the secret value itself.
31
+ appSecretRef: Schema.string().default(QQ_APP_SECRET_REF),
32
+ markdownSupport: Schema.boolean().default(false).volatile(),
33
+ streaming: Schema.object({
34
+ enabled: Schema.boolean().default(true),
35
+ throttleMs: Schema.natural().min(300).default(500),
36
+ }).default({}).volatile(),
37
+ dedup: Schema.object({
38
+ enabled: Schema.boolean().default(true),
39
+ windowMs: Schema.natural().default(5000),
40
+ }).default({}).volatile(),
41
+ startupTimeoutMs: Schema.natural().default(15000).volatile(),
42
+ });
43
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,MAAM,MAAM,0BAA0B,CAAC;AAG9C,MAAM,CAAC,MAAM,iBAAiB,GAAG,kBAAkB,CAAC;AAkDpD,8EAA8E;AAC9E,+EAA+E;AAC/E,kFAAkF;AAClF,6DAA6D;AAC7D,MAAM,CAAC,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC;IAClC,6EAA6E;IAC7E,oEAAoE;IACpE,8EAA8E;IAC9E,uEAAuE;IACvE,6EAA6E;IAC7E,OAAO,EAAE,MAAM,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE;IAClD,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,QAAQ,EAAE;IACrD,kEAAkE;IAClE,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IACjC,kEAAkE;IAClE,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,OAAO,CAAC,iBAAiB,CAAC;IACxD,eAAe,EAAE,MAAM,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,QAAQ,EAAE;IAC3D,SAAS,EAAE,MAAM,CAAC,MAAM,CAAC;QACvB,OAAO,EAAE,MAAM,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC;QACvC,UAAU,EAAE,MAAM,CAAC,OAAO,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC;KACnD,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,QAAQ,EAAE;IACzB,KAAK,EAAE,MAAM,CAAC,MAAM,CAAC;QACnB,OAAO,EAAE,MAAM,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC;QACvC,QAAQ,EAAE,MAAM,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC;KACzC,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,QAAQ,EAAE;IACzB,gBAAgB,EAAE,MAAM,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,QAAQ,EAAE;CAC7D,CAAoC,CAAC"}