@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.
- package/README.md +93 -0
- package/lib/adapter.d.ts +76 -0
- package/lib/adapter.d.ts.map +1 -0
- package/lib/adapter.js +299 -0
- package/lib/adapter.js.map +1 -0
- package/lib/config.d.ts +62 -0
- package/lib/config.d.ts.map +1 -0
- package/lib/config.js +43 -0
- package/lib/config.js.map +1 -0
- package/lib/definition.d.ts +75 -0
- package/lib/definition.d.ts.map +1 -0
- package/lib/definition.js +232 -0
- package/lib/definition.js.map +1 -0
- package/lib/image-hydrator.d.ts +13 -0
- package/lib/image-hydrator.d.ts.map +1 -0
- package/lib/image-hydrator.js +13 -0
- package/lib/image-hydrator.js.map +1 -0
- package/lib/inbound.d.ts +49 -0
- package/lib/inbound.d.ts.map +1 -0
- package/lib/inbound.js +98 -0
- package/lib/inbound.js.map +1 -0
- package/lib/index.d.ts +53 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +113 -0
- package/lib/index.js.map +1 -0
- package/lib/interaction-mapper.d.ts +43 -0
- package/lib/interaction-mapper.d.ts.map +1 -0
- package/lib/interaction-mapper.js +91 -0
- package/lib/interaction-mapper.js.map +1 -0
- package/lib/manifest.d.ts +22 -0
- package/lib/manifest.d.ts.map +1 -0
- package/lib/manifest.js +35 -0
- package/lib/manifest.js.map +1 -0
- package/lib/mapper.d.ts +38 -0
- package/lib/mapper.d.ts.map +1 -0
- package/lib/mapper.js +109 -0
- package/lib/mapper.js.map +1 -0
- package/lib/media-hydrator.d.ts +61 -0
- package/lib/media-hydrator.d.ts.map +1 -0
- package/lib/media-hydrator.js +107 -0
- package/lib/media-hydrator.js.map +1 -0
- package/lib/outbound.d.ts +41 -0
- package/lib/outbound.d.ts.map +1 -0
- package/lib/outbound.js +111 -0
- package/lib/outbound.js.map +1 -0
- package/lib/sdk-client.d.ts +247 -0
- package/lib/sdk-client.d.ts.map +1 -0
- package/lib/sdk-client.js +312 -0
- package/lib/sdk-client.js.map +1 -0
- package/lib/streaming-reply.d.ts +19 -0
- package/lib/streaming-reply.d.ts.map +1 -0
- package/lib/streaming-reply.js +26 -0
- package/lib/streaming-reply.js.map +1 -0
- 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)
|
package/lib/adapter.d.ts
ADDED
|
@@ -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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"}
|
package/lib/config.d.ts
ADDED
|
@@ -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"}
|