@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
@@ -0,0 +1,75 @@
1
+ /**
2
+ * QQ ChannelDefinition for the universal Channel Control Plane (doc §14–§18,
3
+ * §29–§33, §47).
4
+ *
5
+ * Implements the shared `ChannelDefinition` contract so the control plane
6
+ * drives setup / configured-state / config-save / instantiation for QQ purely
7
+ * through this interface. The existing `appSecretRef` credential design is
8
+ * kept: config only carries the reference name; the real AppSecret is resolved
9
+ * through the injected credential seam at createAdapter() time and never
10
+ * enters config, logs or errors (QQ-R5).
11
+ *
12
+ * The definition is fully offline-testable: it depends only on the injected
13
+ * structural credential seam and the `QQAdapterDeps` seam (tests inject the
14
+ * `FakeQQSdkClient`).
15
+ *
16
+ * QQ Bot setup uses the official AppID/AppSecret flow. Optional CLI onboarding
17
+ * in the upstream plugin is outside this channel Web control-plane contract.
18
+ */
19
+ import type { ChannelDefinition } from '@krischoichoi/channel-control';
20
+ import type { QQConfig } from './config.js';
21
+ import { type QQAdapterDeps } from './adapter.js';
22
+ /**
23
+ * Structural credential seam the definition uses (mirrors channel-control's
24
+ * credentials/manager.ts). Deliberately NOT an import of `@deepseek-ai/dsh-credentials`
25
+ * — the definition is adapter-agnostic and the concrete provider is injected.
26
+ */
27
+ export interface CredentialSeam {
28
+ resolve(ref: string): Promise<{
29
+ value: string;
30
+ source: string;
31
+ } | undefined>;
32
+ describe(ref: string): Promise<{
33
+ configured: boolean;
34
+ source?: string;
35
+ writable: boolean;
36
+ }>;
37
+ set(ref: string, value: string): Promise<void>;
38
+ unset(ref: string): Promise<void>;
39
+ }
40
+ /** Options for building the QQ definition. */
41
+ export interface QQDefinitionOptions {
42
+ config: QQConfig;
43
+ /** Adapter construction deps (e.g. an injected `FakeQQSdkClient` in tests). */
44
+ deps: QQAdapterDeps;
45
+ /** The credential seam used to resolve/describe `appSecretRef`. */
46
+ credentials: CredentialSeam;
47
+ /** Durable store for the non-secret setup field when the host provides one. */
48
+ persistSetup?: (patch: Pick<QQConfig, 'appId'>) => Promise<void>;
49
+ /** Durable store for the enabled intent (doc §21) when the host provides one. */
50
+ persistEnabled?: (enabled: boolean) => Promise<void>;
51
+ /** Durable canonical owner identity learned from a platform-delivered C2C. */
52
+ resolveOwnerIdentity?: (accountId: string, appId: string) => Promise<string | undefined>;
53
+ persistOwnerIdentity?: (ownerId: string, accountId: string, appId: string) => Promise<void>;
54
+ }
55
+ /**
56
+ * Official QQ bot console — one-step entry straight to the QQ openclaw bot page
57
+ * (not the q.qq.com homepage). When the bot's appId is known the link deep-links
58
+ * to that bot (`?appid=`), mirroring the openclaw-toolkit desktop console.
59
+ */
60
+ export declare const QQ_OPEN_PLATFORM_URL = "https://q.qq.com/qqbot/openclaw/";
61
+ export declare function qqConsoleUrl(appId: string | undefined): string;
62
+ /**
63
+ * Opaque, non-secret directory scope for one QQ AppID. It partitions observed
64
+ * group_openid identities when an operator replaces the Bot AppID while
65
+ * keeping the same local channel account. The value is never sent to the Web
66
+ * and never participates in ACL authorization.
67
+ */
68
+ export declare function qqConversationScopeFingerprint(appId: string): string | undefined;
69
+ /**
70
+ * Build the QQ `ChannelDefinition`. The returned definition reads from a
71
+ * mutable internal config snapshot (updated by saveConfig) so persisted patches
72
+ * are reflected by createAdapter() and getConfiguredState().
73
+ */
74
+ export declare function createQQDefinition(options: QQDefinitionOptions): ChannelDefinition;
75
+ //# sourceMappingURL=definition.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"definition.d.ts","sourceRoot":"","sources":["../src/definition.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH,OAAO,KAAK,EAEV,iBAAiB,EAIlB,MAAM,+BAA+B,CAAC;AAGvC,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAE5C,OAAO,EAAa,KAAK,aAAa,EAAE,MAAM,cAAc,CAAC;AAG7D;;;;GAIG;AACH,MAAM,WAAW,cAAc;IAC7B,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,GAAG,SAAS,CAAC,CAAC;IAC7E,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,UAAU,EAAE,OAAO,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;IAC5F,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/C,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACnC;AAED,8CAA8C;AAC9C,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,QAAQ,CAAC;IACjB,+EAA+E;IAC/E,IAAI,EAAE,aAAa,CAAC;IACpB,mEAAmE;IACnE,WAAW,EAAE,cAAc,CAAC;IAC5B,+EAA+E;IAC/E,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,IAAI,CAAC,QAAQ,EAAE,OAAO,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACjE,iFAAiF;IACjF,cAAc,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACrD,8EAA8E;IAC9E,oBAAoB,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;IACzF,oBAAoB,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CAC7F;AAgCD;;;;GAIG;AACH,eAAO,MAAM,oBAAoB,qCAAqC,CAAC;AAEvE,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CAG9D;AAED;;;;;GAKG;AACH,wBAAgB,8BAA8B,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAIhF;AAwCD;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,mBAAmB,GAAG,iBAAiB,CAiJlF"}
@@ -0,0 +1,232 @@
1
+ import { ControlError } from '@krischoichoi/channel-control';
2
+ import { createHash } from 'node:crypto';
3
+ import { QQ_APP_SECRET_REF } from './config.js';
4
+ import { QQAdapter } from './adapter.js';
5
+ import { resolveVolatileConfig } from '@krischoichoi/channel-core';
6
+ /**
7
+ * Non-secret config keys saveConfig() applies onto the internal config snapshot.
8
+ * Mirrors QQConfig but omits `enabled` / `appSecretRef` (the reference is
9
+ * wired at construction and never rewritten via saveConfig). Unknown keys in a
10
+ * patch are ignored — the control plane already rejects secret keys before the
11
+ * definition is reached (SECRET_FIELD_REJECTED).
12
+ */
13
+ const SAVABLE_KEYS = [
14
+ 'appId',
15
+ 'markdownSupport',
16
+ 'streaming',
17
+ 'dedup',
18
+ 'startupTimeoutMs',
19
+ 'accountId',
20
+ ];
21
+ /** Deep-ish clone of the mutable top-level config sub-objects. */
22
+ function cloneSnapshot(config) {
23
+ // Harness 0.2.0: volatile fields arrive as `Volatile<T>` handles; unwrap
24
+ // them once so the snapshot (and everything reading it) sees plain values.
25
+ const plain = resolveVolatileConfig(config);
26
+ return {
27
+ ...plain,
28
+ streaming: { ...plain.streaming },
29
+ dedup: { ...plain.dedup },
30
+ };
31
+ }
32
+ /**
33
+ * Official QQ bot console — one-step entry straight to the QQ openclaw bot page
34
+ * (not the q.qq.com homepage). When the bot's appId is known the link deep-links
35
+ * to that bot (`?appid=`), mirroring the openclaw-toolkit desktop console.
36
+ */
37
+ export const QQ_OPEN_PLATFORM_URL = 'https://q.qq.com/qqbot/openclaw/';
38
+ export function qqConsoleUrl(appId) {
39
+ const id = appId?.trim();
40
+ return id ? `${QQ_OPEN_PLATFORM_URL}?appid=${encodeURIComponent(id)}` : QQ_OPEN_PLATFORM_URL;
41
+ }
42
+ /**
43
+ * Opaque, non-secret directory scope for one QQ AppID. It partitions observed
44
+ * group_openid identities when an operator replaces the Bot AppID while
45
+ * keeping the same local channel account. The value is never sent to the Web
46
+ * and never participates in ACL authorization.
47
+ */
48
+ export function qqConversationScopeFingerprint(appId) {
49
+ const normalized = appId.trim();
50
+ if (!normalized)
51
+ return undefined;
52
+ return createHash('sha256').update(`qq-app:${normalized}`).digest('hex');
53
+ }
54
+ /** Validate + clamp one non-secret patch value onto the snapshot. */
55
+ function applyPatch(snapshot, key, value) {
56
+ switch (key) {
57
+ case 'appId':
58
+ if (typeof value === 'string')
59
+ snapshot.appId = value;
60
+ return;
61
+ case 'markdownSupport':
62
+ if (typeof value === 'boolean')
63
+ snapshot.markdownSupport = value;
64
+ return;
65
+ case 'startupTimeoutMs':
66
+ if (typeof value === 'number' && Number.isFinite(value) && value > 0) {
67
+ snapshot.startupTimeoutMs = Math.floor(value);
68
+ }
69
+ return;
70
+ case 'accountId':
71
+ if (typeof value === 'string' && value)
72
+ snapshot.accountId = value;
73
+ return;
74
+ case 'streaming':
75
+ if (typeof value === 'object' && value !== null) {
76
+ const part = value;
77
+ if (typeof part?.enabled === 'boolean')
78
+ snapshot.streaming.enabled = part.enabled;
79
+ if (typeof part?.throttleMs === 'number' && Number.isFinite(part.throttleMs)) {
80
+ snapshot.streaming.throttleMs = Math.max(300, Math.floor(part.throttleMs));
81
+ }
82
+ }
83
+ return;
84
+ case 'dedup':
85
+ if (typeof value === 'object' && value !== null) {
86
+ const part = value;
87
+ if (typeof part?.enabled === 'boolean')
88
+ snapshot.dedup.enabled = part.enabled;
89
+ if (typeof part?.windowMs === 'number' && Number.isFinite(part.windowMs)) {
90
+ snapshot.dedup.windowMs = Math.max(0, Math.floor(part.windowMs));
91
+ }
92
+ }
93
+ return;
94
+ }
95
+ }
96
+ /**
97
+ * Build the QQ `ChannelDefinition`. The returned definition reads from a
98
+ * mutable internal config snapshot (updated by saveConfig) so persisted patches
99
+ * are reflected by createAdapter() and getConfiguredState().
100
+ */
101
+ export function createQQDefinition(options) {
102
+ // Internal mutable config snapshot; saveConfig mutates it, createAdapter reads it.
103
+ const snapshot = cloneSnapshot(options.config);
104
+ const appSecretRef = snapshot.appSecretRef || QQ_APP_SECRET_REF;
105
+ // Static setup surface. The appSecret field's configured/writable flags are
106
+ // best-effort here (a synchronous property cannot await describe()); the
107
+ // authoritative dynamic state is reported by getConfiguredState().
108
+ const fields = [
109
+ {
110
+ name: 'appId',
111
+ kind: 'text',
112
+ secret: false,
113
+ configured: Boolean(snapshot.appId),
114
+ writable: true,
115
+ },
116
+ {
117
+ name: 'appSecret',
118
+ kind: 'secret',
119
+ secret: true,
120
+ configured: false,
121
+ writable: true,
122
+ ref: appSecretRef,
123
+ },
124
+ ];
125
+ const setup = {
126
+ fields,
127
+ authMethods: ['credentials'],
128
+ setupUrl: qqConsoleUrl(snapshot.appId),
129
+ };
130
+ const refreshSetup = () => {
131
+ setup.fields[0].configured = Boolean(snapshot.appId);
132
+ setup.setupUrl = qqConsoleUrl(snapshot.appId);
133
+ };
134
+ return {
135
+ id: 'qq',
136
+ get enabled() {
137
+ return snapshot.enabled;
138
+ },
139
+ async setEnabled(enabled) {
140
+ snapshot.enabled = enabled;
141
+ await options.persistEnabled?.(enabled);
142
+ },
143
+ setup,
144
+ autoStart: true,
145
+ // QQ platform restricts C2C private messages to the bot creator. The
146
+ // control plane materializes that narrow private grant; groups stay closed
147
+ // until the operator explicitly configures them.
148
+ access: {
149
+ directMessages: true,
150
+ groups: true,
151
+ // New QQ gateway distinguishes GROUP_AT_MESSAGE_CREATE from ordinary
152
+ // GROUP_MESSAGE_CREATE; the mapper publishes this as a strict boolean
153
+ // activation fact.
154
+ mentions: true,
155
+ // QQ platform restricts C2C to the bot creator, so the owner never
156
+ // claims locally; the control plane materializes that private grant.
157
+ ownerDiscovery: 'platform',
158
+ // QQ only exposes the canonical group_openid reliably. The Web lists
159
+ // group OpenIDs discovered from inbound events; it never asks operators
160
+ // to type a QQ group number or guesses an identity from one.
161
+ identityLabels: { user: 'QQ', group: 'QQ群' },
162
+ identity: {
163
+ conversation: {
164
+ conversationDiscoverable: true,
165
+ },
166
+ },
167
+ defaults: { requireMention: true },
168
+ },
169
+ async getConfiguredState() {
170
+ const appId = Boolean(snapshot.appId);
171
+ let appSecret = {
172
+ configured: false,
173
+ writable: false,
174
+ };
175
+ if (appSecretRef) {
176
+ const described = await options.credentials.describe(appSecretRef);
177
+ appSecret = {
178
+ configured: described.configured,
179
+ writable: described.writable,
180
+ source: described.source,
181
+ };
182
+ }
183
+ return {
184
+ configured: appId && appSecret.configured,
185
+ fields: {
186
+ appId: { configured: appId, writable: true, value: snapshot.appId },
187
+ appSecret: {
188
+ configured: appSecret.configured,
189
+ writable: appSecret.writable,
190
+ source: appSecret.source,
191
+ },
192
+ },
193
+ };
194
+ },
195
+ async saveConfig(patch) {
196
+ for (const key of Object.keys(patch)) {
197
+ if (SAVABLE_KEYS.includes(key)) {
198
+ applyPatch(snapshot, key, patch[key]);
199
+ }
200
+ // Unknown (incl. secret) keys are ignored; the control plane already
201
+ // rejects secret field names before reaching the definition.
202
+ }
203
+ refreshSetup();
204
+ if (patch.appId !== undefined)
205
+ await options.persistSetup?.({ appId: snapshot.appId });
206
+ },
207
+ conversationScopeFingerprint: () => qqConversationScopeFingerprint(snapshot.appId),
208
+ snapshotConfig: () => cloneSnapshot(snapshot),
209
+ async restoreConfig(saved) {
210
+ const restored = cloneSnapshot(saved);
211
+ Object.assign(snapshot, restored);
212
+ snapshot.streaming = restored.streaming;
213
+ snapshot.dedup = restored.dedup;
214
+ refreshSetup();
215
+ await options.persistSetup?.({ appId: snapshot.appId });
216
+ },
217
+ async createAdapter() {
218
+ const credential = appSecretRef !== undefined ? await options.credentials.resolve(appSecretRef) : undefined;
219
+ if (!credential) {
220
+ // Stable machine-readable error; never echoes the secret, only the ref.
221
+ throw new ControlError('CONTROL_ERROR', `QQ credential "${appSecretRef}" is not configured`);
222
+ }
223
+ return new QQAdapter(snapshot, {
224
+ ...options.deps,
225
+ appSecret: credential.value,
226
+ onOwnerDiscovered: (ownerId) => options.persistOwnerIdentity?.(ownerId, snapshot.accountId, snapshot.appId),
227
+ });
228
+ },
229
+ resolveOwnerIdentity: async (accountId) => options.resolveOwnerIdentity?.(accountId, snapshot.appId),
230
+ };
231
+ }
232
+ //# sourceMappingURL=definition.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"definition.js","sourceRoot":"","sources":["../src/definition.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * One-version compatibility shim.
3
+ *
4
+ * The QQ inbound hydrator was renamed from `image-hydrator.ts` to
5
+ * `media-hydrator.ts` (it now covers image / file / audio / video). This entry
6
+ * re-exports the new implementation under the legacy names so existing imports
7
+ * of `hydrateImageParts` / `ImageHydratorOptions` keep working for one release.
8
+ *
9
+ * New code should import `hydrateMediaParts` / `MediaHydratorOptions` from
10
+ * `./media-hydrator.js`.
11
+ */
12
+ export { hydrateMediaParts as hydrateImageParts, type MediaHydratorOptions as ImageHydratorOptions, } from './media-hydrator.js';
13
+ //# sourceMappingURL=image-hydrator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"image-hydrator.d.ts","sourceRoot":"","sources":["../src/image-hydrator.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,EACL,iBAAiB,IAAI,iBAAiB,EACtC,KAAK,oBAAoB,IAAI,oBAAoB,GAClD,MAAM,qBAAqB,CAAC"}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * One-version compatibility shim.
3
+ *
4
+ * The QQ inbound hydrator was renamed from `image-hydrator.ts` to
5
+ * `media-hydrator.ts` (it now covers image / file / audio / video). This entry
6
+ * re-exports the new implementation under the legacy names so existing imports
7
+ * of `hydrateImageParts` / `ImageHydratorOptions` keep working for one release.
8
+ *
9
+ * New code should import `hydrateMediaParts` / `MediaHydratorOptions` from
10
+ * `./media-hydrator.js`.
11
+ */
12
+ export { hydrateMediaParts as hydrateImageParts, } from './media-hydrator.js';
13
+ //# sourceMappingURL=image-hydrator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"image-hydrator.js","sourceRoot":"","sources":["../src/image-hydrator.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,EACL,iBAAiB,IAAI,iBAAiB,GAEvC,MAAM,qBAAqB,CAAC"}
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Inbound processing: dedup window + structured mapping + media hydration +
3
+ * emit.
4
+ *
5
+ * Input is the Tencent SDK's `QQBotInboundMessage`. Dedup keys on `messageId`
6
+ * (DSH keeps its own dedup policy; the SDK middleware chain is unused). Only
7
+ * `kind === 'c2c'` and `kind === 'group'` are accepted in V1 — anything else
8
+ * (guild/dm) is logged and dropped.
9
+ *
10
+ * Binary hydration runs AFTER mapping and dedup,
11
+ * BEFORE emit: every `image` (M2A), `file` (M7B), `audio` and `video` part
12
+ * with a genuine `http(s)` `url` is downloaded through the injectable
13
+ * `SecureRemoteMediaFetcher` and its bytes are placed on `localData`
14
+ * (file/audio/video parts also carry the hydrated byte length in `size`).
15
+ * A download failure never blocks text delivery — the part keeps its `url`
16
+ * and records a stable `ingressFailure` code.
17
+ */
18
+ import type { ChannelAdapterContext } from '@krischoichoi/channel-core';
19
+ import { SecureRemoteMediaFetcher } from '@krischoichoi/channel-core';
20
+ import type { QQBotInboundMessage } from '@tencent-connect/qqbot-nodejs';
21
+ import { type QQInboundMeta } from './mapper.js';
22
+ import { type MediaHydratorOptions } from './media-hydrator.js';
23
+ export interface InboundProcessorOptions {
24
+ ctx: ChannelAdapterContext;
25
+ meta: QQInboundMeta;
26
+ dedupEnabled: boolean;
27
+ dedupWindowMs: number;
28
+ /** Optional secure remote media fetcher (tests inject a fake; defaults to real). */
29
+ secureFetch?: SecureRemoteMediaFetcher;
30
+ /** Media hydration tuning (image / file / audio / video). */
31
+ mediaHydration?: MediaHydratorOptions;
32
+ /** @deprecated use mediaHydration. */
33
+ imageHydration?: MediaHydratorOptions;
34
+ /** Injectable clock (tests). */
35
+ now?: () => number;
36
+ }
37
+ export declare class InboundProcessor {
38
+ private readonly options;
39
+ private readonly now;
40
+ private readonly secureFetch;
41
+ private readonly mediaHydration;
42
+ /** messageId -> last-seen timestamp, pruned on every handle. */
43
+ private readonly seen;
44
+ constructor(options: InboundProcessorOptions);
45
+ /** Process one SDK inbound message; dedup, hydrate media, then emit. */
46
+ handle(raw: QQBotInboundMessage): Promise<void>;
47
+ private prune;
48
+ }
49
+ //# sourceMappingURL=inbound.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"inbound.d.ts","sourceRoot":"","sources":["../src/inbound.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AACH,OAAO,KAAK,EAAE,qBAAqB,EAAgC,MAAM,4BAA4B,CAAC;AACtG,OAAO,EAAE,wBAAwB,EAAE,MAAM,4BAA4B,CAAC;AA+CtE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,+BAA+B,CAAC;AACzE,OAAO,EAAc,KAAK,aAAa,EAAE,MAAM,aAAa,CAAC;AAC7D,OAAO,EAAqB,KAAK,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAEnF,MAAM,WAAW,uBAAuB;IACtC,GAAG,EAAE,qBAAqB,CAAC;IAC3B,IAAI,EAAE,aAAa,CAAC;IACpB,YAAY,EAAE,OAAO,CAAC;IACtB,aAAa,EAAE,MAAM,CAAC;IACtB,oFAAoF;IACpF,WAAW,CAAC,EAAE,wBAAwB,CAAC;IACvC,6DAA6D;IAC7D,cAAc,CAAC,EAAE,oBAAoB,CAAC;IACtC,sCAAsC;IACtC,cAAc,CAAC,EAAE,oBAAoB,CAAC;IACtC,gCAAgC;IAChC,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;CACpB;AAED,qBAAa,gBAAgB;IAOf,OAAO,CAAC,QAAQ,CAAC,OAAO;IANpC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAe;IACnC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAA2B;IACvD,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAuB;IACtD,gEAAgE;IAChE,OAAO,CAAC,QAAQ,CAAC,IAAI,CAA6B;gBAErB,OAAO,EAAE,uBAAuB;IAM7D,wEAAwE;IAClE,MAAM,CAAC,GAAG,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC;IAoCrD,OAAO,CAAC,KAAK;CAKd"}
package/lib/inbound.js ADDED
@@ -0,0 +1,98 @@
1
+ import { SecureRemoteMediaFetcher } from '@krischoichoi/channel-core';
2
+ /** Compact per-part summary for inbound message logs (debug diagnostics). */
3
+ function summarizeParts(parts) {
4
+ return parts.map((part) => {
5
+ switch (part.type) {
6
+ case 'text':
7
+ return { type: 'text', text: part.text.slice(0, 80) };
8
+ case 'image':
9
+ return {
10
+ type: 'image',
11
+ url: part.url,
12
+ resourceRef: part.resourceRef,
13
+ mimeType: part.mimeType,
14
+ localDataBytes: part.localData?.byteLength,
15
+ ingressFailure: part.ingressFailure,
16
+ };
17
+ case 'file':
18
+ return {
19
+ type: 'file',
20
+ name: part.name,
21
+ size: part.size,
22
+ mimeType: part.mimeType,
23
+ localDataBytes: part.localData?.byteLength,
24
+ ingressFailure: part.ingressFailure,
25
+ };
26
+ case 'audio':
27
+ return {
28
+ type: 'audio',
29
+ durationMs: part.durationMs,
30
+ mimeType: part.mimeType,
31
+ localDataBytes: part.localData?.byteLength,
32
+ ingressFailure: part.ingressFailure,
33
+ };
34
+ case 'video':
35
+ return {
36
+ type: 'video',
37
+ durationMs: part.durationMs,
38
+ mimeType: part.mimeType,
39
+ localDataBytes: part.localData?.byteLength,
40
+ ingressFailure: part.ingressFailure,
41
+ };
42
+ default:
43
+ return { type: part.type };
44
+ }
45
+ });
46
+ }
47
+ import { mapInbound } from './mapper.js';
48
+ import { hydrateMediaParts } from './media-hydrator.js';
49
+ export class InboundProcessor {
50
+ options;
51
+ now;
52
+ secureFetch;
53
+ mediaHydration;
54
+ /** messageId -> last-seen timestamp, pruned on every handle. */
55
+ seen = new Map();
56
+ constructor(options) {
57
+ this.options = options;
58
+ this.now = options.now ?? Date.now;
59
+ this.secureFetch = options.secureFetch ?? new SecureRemoteMediaFetcher();
60
+ this.mediaHydration = options.mediaHydration ?? options.imageHydration ?? {};
61
+ }
62
+ /** Process one SDK inbound message; dedup, hydrate media, then emit. */
63
+ async handle(raw) {
64
+ if (raw.kind !== 'c2c' && raw.kind !== 'group') {
65
+ this.options.ctx.logger.debug(`[channel-qq] dropping unsupported inbound kind '${raw.kind}'`);
66
+ return;
67
+ }
68
+ const key = raw.messageId;
69
+ if (this.options.dedupEnabled) {
70
+ const now = this.now();
71
+ const last = this.seen.get(key);
72
+ if (last !== undefined && now - last < this.options.dedupWindowMs) {
73
+ this.options.ctx.logger.debug(`[channel-qq] dropped duplicate message '${key}'`);
74
+ return;
75
+ }
76
+ this.seen.set(key, now);
77
+ this.prune(now);
78
+ }
79
+ const event = mapInbound(raw, this.options.meta);
80
+ // Hydrate media bytes before emit so the harness attachment paths receive
81
+ // real bytes. Never throws; failures degrade the part.
82
+ await hydrateMediaParts(event.message.content, this.secureFetch, {
83
+ ...this.mediaHydration,
84
+ signal: this.options.ctx.signal,
85
+ });
86
+ // Inbound message log (debug diagnostics): visible in web:debug with
87
+ // DSH_CHANNELS_DEBUG=1, shows mapped parts incl. hydration result.
88
+ this.options.ctx.logger.info(`[channel-qq] inbound message ${event.message.id} from ${event.sender.id} in ${event.conversation.id}`, { parts: summarizeParts(event.message.content) });
89
+ await this.options.ctx.emit(event);
90
+ }
91
+ prune(now) {
92
+ for (const [key, ts] of this.seen) {
93
+ if (now - ts >= this.options.dedupWindowMs)
94
+ this.seen.delete(key);
95
+ }
96
+ }
97
+ }
98
+ //# sourceMappingURL=inbound.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"inbound.js","sourceRoot":"","sources":["../src/inbound.ts"],"names":[],"mappings":"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"}
package/lib/index.d.ts ADDED
@@ -0,0 +1,53 @@
1
+ /**
2
+ * @krischoichoi/channel-qq — QQ channel adapter for DeepSeek Harness.
3
+ *
4
+ * Maps the QQ platform (via the official Tencent SDK
5
+ * `@tencent-connect/qqbot-nodejs`) to the stable Channel Contract. The SDK
6
+ * owns Token acquisition/refresh, the WebSocket gateway, media upload and C2C
7
+ * streaming; DSH keeps its own dedup policy and reply routing.
8
+ *
9
+ * The adapter requires `ctx.channels` and `ctx.credentials`: the QQ AppSecret
10
+ * is resolved at startup through `ctx.credentials` (`appSecretRef`) and passed
11
+ * to the adapter as `deps.appSecret`. The secret value never enters the
12
+ * profile config — only its reference name does (v1.1 §7, QQ-R5).
13
+ *
14
+ * Since M2B, apply() is channel-control-aware (doc §25/§27/§47): when the
15
+ * universal Channel Control Plane (ctx.channelControl) is present it registers
16
+ * the QQ `ChannelDefinition` and lets the plane drive setup / credentials /
17
+ * auto-start. When channel-control is absent it falls back to the legacy
18
+ * headless mount — mounting ONLY when configured, and never throwing on an
19
+ * unconfigured channel.
20
+ *
21
+ * Streaming is target-aware: C2C with a triggering message id streams natively
22
+ * (replace-semantics full-text); groups are buffered and delivered once at
23
+ * `turn/end`.
24
+ */
25
+ import { type Context } from '@deepseek-ai/cordis';
26
+ import { Config, QQ_APP_SECRET_REF } from './config.js';
27
+ import type { QQConfig, QQConfigLive } from './config.js';
28
+ import { type QQAdapterDeps } from './adapter.js';
29
+ export declare const name = "channel-qq";
30
+ export declare const inject: string[];
31
+ export { Config, QQ_APP_SECRET_REF };
32
+ export type { QQConfig, QQConfigLive } from './config.js';
33
+ export { QQAdapter, type QQAdapterDeps } from './adapter.js';
34
+ export { createQQDefinition } from './definition.js';
35
+ export type { CredentialSeam, QQDefinitionOptions } from './definition.js';
36
+ export { TencentQQSdkClient, FakeQQSdkClient, FakeStreamSession, adaptLogger, mediaOpts, decodeDataUri, QQ_MINIMAL_INTENTS, QQ_BUTTON_ACTION_TYPE, QQ_BUTTON_PERMISSION_TYPE, QQ_BUTTON_STYLE_PRIMARY, QQ_BUTTON_STYLE_DEFAULT, type QQSdkClient, type QQReplyTarget, type QQStreamTarget, type QQStreamSession, type QQInteractionLike, type QQInlineKeyboardLike, type QQKeyboardButton, type MediaOptions, } from './sdk-client.js';
37
+ export { InboundProcessor } from './inbound.js';
38
+ export { hydrateMediaParts, type MediaHydratorOptions } from './media-hydrator.js';
39
+ export { hydrateImageParts, type ImageHydratorOptions } from './image-hydrator.js';
40
+ export { OutboundSender, toReplyTarget, toQqKeyboard } from './outbound.js';
41
+ export { QQStreamingReply } from './streaming-reply.js';
42
+ export { mapInbound, mapMessageParts, type QQInboundMeta } from './mapper.js';
43
+ export { mapInteraction, type QQInteractionMapping, type QQInteractionDropReason } from './interaction-mapper.js';
44
+ export { manifest, type QQManifest } from './manifest.js';
45
+ /**
46
+ * Entry plugin apply. Accepts both the activation-time live shape (volatile
47
+ * fields resolve to `Volatile<T>` handles under Harness 0.2.0) and a plain
48
+ * snapshot (tests / programmatic mounting); plain fields pass through
49
+ * `resolveVolatileConfig` untouched. The shape is validated by Schemastery at
50
+ * the Loader boundary via the attached `Config` schema.
51
+ */
52
+ export declare function apply(ctx: Context, rawConfig: QQConfig | QQConfigLive, deps?: QQAdapterDeps): void;
53
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,OAAO,EAAE,KAAK,OAAO,EAAE,MAAM,qBAAqB,CAAC;AAInD,OAAO,EAAE,MAAM,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AACxD,OAAO,KAAK,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC1D,OAAO,EAAa,KAAK,aAAa,EAAE,MAAM,cAAc,CAAC;AAK7D,eAAO,MAAM,IAAI,eAAe,CAAC;AACjC,eAAO,MAAM,MAAM,EAAE,MAAM,EAAgC,CAAC;AAE5D,OAAO,EAAE,MAAM,EAAE,iBAAiB,EAAE,CAAC;AACrC,YAAY,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC1D,OAAO,EAAE,SAAS,EAAE,KAAK,aAAa,EAAE,MAAM,cAAc,CAAC;AAC7D,OAAO,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AACrD,YAAY,EAAE,cAAc,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AAC3E,OAAO,EACL,kBAAkB,EAClB,eAAe,EACf,iBAAiB,EACjB,WAAW,EACX,SAAS,EACT,aAAa,EACb,kBAAkB,EAClB,qBAAqB,EACrB,yBAAyB,EACzB,uBAAuB,EACvB,uBAAuB,EACvB,KAAK,WAAW,EAChB,KAAK,aAAa,EAClB,KAAK,cAAc,EACnB,KAAK,eAAe,EACpB,KAAK,iBAAiB,EACtB,KAAK,oBAAoB,EACzB,KAAK,gBAAgB,EACrB,KAAK,YAAY,GAClB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAChD,OAAO,EAAE,iBAAiB,EAAE,KAAK,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAGnF,OAAO,EAAE,iBAAiB,EAAE,KAAK,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AACnF,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAC5E,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AACxD,OAAO,EAAE,UAAU,EAAE,eAAe,EAAE,KAAK,aAAa,EAAE,MAAM,aAAa,CAAC;AAC9E,OAAO,EAAE,cAAc,EAAE,KAAK,oBAAoB,EAAE,KAAK,uBAAuB,EAAE,MAAM,yBAAyB,CAAC;AAClH,OAAO,EAAE,QAAQ,EAAE,KAAK,UAAU,EAAE,MAAM,eAAe,CAAC;AAE1D;;;;;;GAMG;AACH,wBAAgB,KAAK,CACnB,GAAG,EAAE,OAAO,EACZ,SAAS,EAAE,QAAQ,GAAG,YAAY,EAClC,IAAI,GAAE,aAAkB,GACvB,IAAI,CAuEN"}