@krischoichoi/channel-dingtalk 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 +139 -0
- package/lib/adapter.d.ts +123 -0
- package/lib/adapter.d.ts.map +1 -0
- package/lib/adapter.js +355 -0
- package/lib/adapter.js.map +1 -0
- package/lib/ai-card.d.ts +64 -0
- package/lib/ai-card.d.ts.map +1 -0
- package/lib/ai-card.js +150 -0
- package/lib/ai-card.js.map +1 -0
- package/lib/auth/device-registration.d.ts +18 -0
- package/lib/auth/device-registration.d.ts.map +1 -0
- package/lib/auth/device-registration.js +147 -0
- package/lib/auth/device-registration.js.map +1 -0
- package/lib/config.d.ts +119 -0
- package/lib/config.d.ts.map +1 -0
- package/lib/config.js +75 -0
- package/lib/config.js.map +1 -0
- package/lib/definition.d.ts +53 -0
- package/lib/definition.d.ts.map +1 -0
- package/lib/definition.js +227 -0
- package/lib/definition.js.map +1 -0
- package/lib/image-hydrator.d.ts +213 -0
- package/lib/image-hydrator.d.ts.map +1 -0
- package/lib/image-hydrator.js +244 -0
- package/lib/image-hydrator.js.map +1 -0
- package/lib/inbound.d.ts +38 -0
- package/lib/inbound.d.ts.map +1 -0
- package/lib/inbound.js +158 -0
- package/lib/inbound.js.map +1 -0
- package/lib/index.d.ts +49 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +123 -0
- package/lib/index.js.map +1 -0
- package/lib/manifest.d.ts +23 -0
- package/lib/manifest.d.ts.map +1 -0
- package/lib/manifest.js +34 -0
- package/lib/manifest.js.map +1 -0
- package/lib/mapper.d.ts +36 -0
- package/lib/mapper.d.ts.map +1 -0
- package/lib/mapper.js +175 -0
- package/lib/mapper.js.map +1 -0
- package/lib/media-mime.d.ts +14 -0
- package/lib/media-mime.d.ts.map +1 -0
- package/lib/media-mime.js +32 -0
- package/lib/media-mime.js.map +1 -0
- package/lib/official-upstream.d.ts +143 -0
- package/lib/official-upstream.d.ts.map +1 -0
- package/lib/official-upstream.js +425 -0
- package/lib/official-upstream.js.map +1 -0
- package/lib/openapi-port.d.ts +190 -0
- package/lib/openapi-port.d.ts.map +1 -0
- package/lib/openapi-port.js +2 -0
- package/lib/openapi-port.js.map +1 -0
- package/lib/outbound.d.ts +69 -0
- package/lib/outbound.d.ts.map +1 -0
- package/lib/outbound.js +148 -0
- package/lib/outbound.js.map +1 -0
- package/lib/stream-upstream.d.ts +77 -0
- package/lib/stream-upstream.d.ts.map +1 -0
- package/lib/stream-upstream.js +303 -0
- package/lib/stream-upstream.js.map +1 -0
- package/lib/transport.d.ts +37 -0
- package/lib/transport.d.ts.map +1 -0
- package/lib/transport.js +73 -0
- package/lib/transport.js.map +1 -0
- package/lib/upstream.d.ts +60 -0
- package/lib/upstream.d.ts.map +1 -0
- package/lib/upstream.js +61 -0
- package/lib/upstream.js.map +1 -0
- package/package.json +46 -0
package/README.md
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
# @krischoichoi/channel-dingtalk
|
|
2
|
+
|
|
3
|
+
DingTalk / 钉钉 channel adapter for DeepSeek Harness.
|
|
4
|
+
|
|
5
|
+
Maps DingTalk to the stable Channel Contract with two selectable upstream
|
|
6
|
+
drivers:
|
|
7
|
+
|
|
8
|
+
- **`sdk`** — inbound via the official `dingtalk-stream` SDK (WebSocket stream
|
|
9
|
+
mode); outbound via `sessionWebhook` and DingTalk AI Card OpenAPI.
|
|
10
|
+
- **`gateway`** — self-hosted HTTP gateway long-poll driver (legacy).
|
|
11
|
+
|
|
12
|
+
## Install
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
pnpm add @krischoichoi/channel-dingtalk
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Or install the whole bundle:
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npx @deepseek-ai/dsh plugin --profile web add -w @krischoichoi/dsh-channels@latest
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Configuration
|
|
25
|
+
|
|
26
|
+
SDK mode:
|
|
27
|
+
|
|
28
|
+
```yaml
|
|
29
|
+
- id: channels-dingtalk
|
|
30
|
+
name: '@krischoichoi/channel-dingtalk'
|
|
31
|
+
inject: [channels, credentials, channelControl]
|
|
32
|
+
config:
|
|
33
|
+
enabled: true
|
|
34
|
+
accountId: main
|
|
35
|
+
upstream:
|
|
36
|
+
mode: sdk
|
|
37
|
+
clientId: "ding-xxx" # AppKey(非机密)
|
|
38
|
+
# clientSecretRef 默认 DSH_CHANNEL_DINGTALK_MAIN_CLIENT_SECRET
|
|
39
|
+
# 真实 AppSecret 只存 ctx.credentials
|
|
40
|
+
card:
|
|
41
|
+
createOnFirstDelta: true
|
|
42
|
+
# 问答按钮是**可选**能力:必须填你自己卡片平台里已发布、且含 text/actions
|
|
43
|
+
# 变量的模板 ID。留空(默认)= 不声明 interactiveActions,问题走编号文字回复
|
|
44
|
+
# (与微信一致)。填了但模板未发布/无权限,Harness 会自动降级为文字回复。
|
|
45
|
+
# interactiveTemplateId: "dac1dbec-55af-40c3-be91-6d6882ef3b66.schema"
|
|
46
|
+
# 下面两个变量名必须与模板中的变量绑定一致
|
|
47
|
+
interactiveTextParam: text
|
|
48
|
+
interactiveActionsParam: actions
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Gateway mode (legacy, self-hosted HTTP gateway):
|
|
52
|
+
|
|
53
|
+
```yaml
|
|
54
|
+
- id: channels-dingtalk
|
|
55
|
+
name: '@krischoichoi/channel-dingtalk'
|
|
56
|
+
inject: [channels, credentials, channelControl]
|
|
57
|
+
config:
|
|
58
|
+
enabled: true
|
|
59
|
+
accountId: main
|
|
60
|
+
upstream:
|
|
61
|
+
mode: gateway
|
|
62
|
+
baseUrl: http://127.0.0.1:9100
|
|
63
|
+
longPollTimeoutMs: 25000
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
The AppSecret is resolved through `ctx.credentials` at startup and injected as
|
|
67
|
+
`deps.clientSecret`. A legacy plaintext `upstream.clientSecret` is migrated into
|
|
68
|
+
the credentials seam once and then deleted.
|
|
69
|
+
|
|
70
|
+
## Streaming
|
|
71
|
+
|
|
72
|
+
DingTalk uses `edit` streaming: the adapter creates an AI Card, updates it with
|
|
73
|
+
each delta, and finalizes (or marks it failed) at turn end.
|
|
74
|
+
|
|
75
|
+
## Interactive buttons (opt-in)
|
|
76
|
+
|
|
77
|
+
`OutboundMessage.actions` uses the official interactive-card APIs:
|
|
78
|
+
|
|
79
|
+
- `POST /v1.0/im/interactiveCards/send`
|
|
80
|
+
- `PUT /v1.0/im/interactiveCards`
|
|
81
|
+
- Stream callback topic `/v1.0/card/instances/callback` (the `TOPIC_CARD`
|
|
82
|
+
constant of `dingtalk-stream`)
|
|
83
|
+
|
|
84
|
+
This is not the legacy webhook `actionCard` API, whose buttons only open URLs
|
|
85
|
+
and cannot return a selection to the Harness.
|
|
86
|
+
|
|
87
|
+
Buttons are **opt-in and off by default**. A card only renders interactive
|
|
88
|
+
components when its template is published in *your* organization's Card
|
|
89
|
+
Platform, exposes the configured `text`/`actions` variables, and its buttons
|
|
90
|
+
return `cardPrivateData.params.action` (the action id). A built-in third-party
|
|
91
|
+
template id cannot satisfy that, so `card.interactiveTemplateId` has no default:
|
|
92
|
+
without it the adapter reports `interactiveActions: false` and Harness questions
|
|
93
|
+
use the numbered-text reply path (same as Weixin). When the template *is*
|
|
94
|
+
configured but the send still fails (unpublished template, missing permission),
|
|
95
|
+
`channel-harness` degrades that question to numbered text instead of cancelling
|
|
96
|
+
it.
|
|
97
|
+
|
|
98
|
+
LIVE-REQUIRED before advertising buttons as verified: a real-account round trip
|
|
99
|
+
that the card renders, that a click arrives on the STREAM callback topic, and
|
|
100
|
+
that the answer returns to the agent. The official troubleshooting note
|
|
101
|
+
requires `callbackType="STREAM"` at card creation *and* the registered callback
|
|
102
|
+
topic ([钉钉卡片示例 · 注意事项](https://github.com/open-dingtalk/dingtalk-card-examples));
|
|
103
|
+
whether `POST /v1.0/im/interactiveCards/send` honors `callbackType` is still
|
|
104
|
+
unverified here and is the first item to check in that gate.
|
|
105
|
+
|
|
106
|
+
## Capabilities
|
|
107
|
+
|
|
108
|
+
| Capability | Value |
|
|
109
|
+
| --- | --- |
|
|
110
|
+
| text / image / file / audio | ✅ |
|
|
111
|
+
| markdown / cards | ✅ (AI Card streaming replies) |
|
|
112
|
+
| interactive actions | ⚙️ opt-in: SDK mode **and** `card.interactiveTemplateId` set |
|
|
113
|
+
| video / reactions / threads | ❌ |
|
|
114
|
+
| streaming | `edit` (AI Card) |
|
|
115
|
+
|
|
116
|
+
## Upstream
|
|
117
|
+
|
|
118
|
+
| Field | Value |
|
|
119
|
+
| --- | --- |
|
|
120
|
+
| SDK | `dingtalk-stream` |
|
|
121
|
+
| Tested version | `2.1.5` |
|
|
122
|
+
| Status | `tested` (offline contract + fixture + SDK-mode E2E suites) |
|
|
123
|
+
|
|
124
|
+
## Development
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
pnpm --filter @krischoichoi/channel-dingtalk build
|
|
128
|
+
pnpm --filter @krischoichoi/channel-dingtalk typecheck
|
|
129
|
+
pnpm --filter @krischoichoi/channel-dingtalk test
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
## Related
|
|
133
|
+
|
|
134
|
+
- [Repository root](../../README.md)
|
|
135
|
+
- [Adapter authoring guide](../../docs/adapter-authoring.md)
|
|
136
|
+
|
|
137
|
+
## License
|
|
138
|
+
|
|
139
|
+
[MIT](../../LICENSE)
|
package/lib/adapter.d.ts
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DingTalk channel adapter.
|
|
3
|
+
*
|
|
4
|
+
* Maps the DingTalk platform (through the upstream driver) to the Channel
|
|
5
|
+
* Contract. All network/lifecycle resources live behind the upstream driver
|
|
6
|
+
* and are aborted via the adapter context signal. The adapter never touches
|
|
7
|
+
* Harness Agent APIs.
|
|
8
|
+
*
|
|
9
|
+
* The upstream driver is selected by `config.upstream.mode`:
|
|
10
|
+
* - 'sdk' → `DingTalkStreamUpstream`: inbound via the official
|
|
11
|
+
* `dingtalk-stream` SDK, outbound via DingTalk's official HTTP APIs. The stream
|
|
12
|
+
* client comes from `deps.sdkClient` / `deps.sdkClientFactory`, defaulting
|
|
13
|
+
* to a real `DWClient` built from `upstream.clientId` (AppKey) and the
|
|
14
|
+
* resolved AppSecret at start time (missing credentials fail start loudly).
|
|
15
|
+
* The AppSecret is resolved via `ctx.credentials` and injected as
|
|
16
|
+
* `deps.clientSecret` — it never lives in config and is never read from
|
|
17
|
+
* `config.upstream` (mirrors how channel-qq injects `deps.appSecret`).
|
|
18
|
+
* - 'gateway' → `HttpDingTalkUpstream` over the transport (legacy).
|
|
19
|
+
*
|
|
20
|
+
* Auth is connection-state driven: the upstream driver owns the platform
|
|
21
|
+
* credentials, so the adapter derives its auth state from the connection
|
|
22
|
+
* (connected → authenticated). `beginAuth`/`pollAuth` are intentionally not
|
|
23
|
+
* implemented in M2 — a real QR/token flow can slot in later.
|
|
24
|
+
*/
|
|
25
|
+
import type { ChannelAdapter, ChannelAdapterContext, ChannelCapabilities, ChannelHealth, ChannelTarget, CreateReplyOptions, OutboundMessage, ReplyHandle, SendResult } from '@krischoichoi/channel-core';
|
|
26
|
+
import type { RemoteMediaFetchLike } from './image-hydrator.js';
|
|
27
|
+
import type { DingTalkConfig } from './config.js';
|
|
28
|
+
import { type HttpTransport } from './transport.js';
|
|
29
|
+
import type { MediaResolverLike, OutboxCapabilities } from './openapi-port.js';
|
|
30
|
+
import { type DingTalkStreamClient } from './stream-upstream.js';
|
|
31
|
+
import { type DingTalkManifest } from './manifest.js';
|
|
32
|
+
export interface DingTalkAdapterDeps {
|
|
33
|
+
transport?: HttpTransport;
|
|
34
|
+
/**
|
|
35
|
+
* Resolved DingTalk AppSecret (SDK mode). Never from config — the plugin
|
|
36
|
+
* resolves it via `ctx.credentials` (`upstream.clientSecretRef`) and
|
|
37
|
+
* injects it here. Used only to build the default `DWClient`.
|
|
38
|
+
*/
|
|
39
|
+
clientSecret?: string;
|
|
40
|
+
/**
|
|
41
|
+
* Pre-built stream client for SDK mode (offline tests). Overrides
|
|
42
|
+
* `sdkClientFactory`; when neither is given a real `DWClient` is built
|
|
43
|
+
* from `config.upstream.clientId` and `deps.clientSecret` at start time.
|
|
44
|
+
*/
|
|
45
|
+
sdkClient?: DingTalkStreamClient;
|
|
46
|
+
/** Lazy stream client factory for SDK mode; overrides the default DWClient. */
|
|
47
|
+
sdkClientFactory?: (config: DingTalkConfig) => DingTalkStreamClient;
|
|
48
|
+
/**
|
|
49
|
+
* Secure remote media fetcher used to hydrate inbound image URLs (plan
|
|
50
|
+
* §32A). Injectable for offline tests; defaults to a real
|
|
51
|
+
* `SecureRemoteMediaFetcher` bound to the global fetch.
|
|
52
|
+
*/
|
|
53
|
+
secureFetch?: RemoteMediaFetchLike;
|
|
54
|
+
/**
|
|
55
|
+
* DingTalk OpenAPI media resolver used to hydrate opaque file mediaIds into
|
|
56
|
+
* trusted bytes during inbound file ingress. Injectable for
|
|
57
|
+
* offline tests; defaults to the running official port in sdk mode.
|
|
58
|
+
*/
|
|
59
|
+
resolveMedia?: MediaResolverLike;
|
|
60
|
+
/** Injectable clock (tests). */
|
|
61
|
+
now?: () => number;
|
|
62
|
+
}
|
|
63
|
+
export declare class DingTalkAdapter implements ChannelAdapter {
|
|
64
|
+
private readonly config;
|
|
65
|
+
readonly id = "dingtalk";
|
|
66
|
+
/** Upstream compatibility manifest (read structurally by `channels doctor`). */
|
|
67
|
+
readonly manifest: DingTalkManifest;
|
|
68
|
+
readonly capabilities: ChannelCapabilities;
|
|
69
|
+
private static readonly BASE_CAPABILITIES;
|
|
70
|
+
private ctx?;
|
|
71
|
+
/** Built in `start()` (driver selection needs the resolved deps/credentials). */
|
|
72
|
+
private upstream;
|
|
73
|
+
private readonly transport;
|
|
74
|
+
private readonly deps;
|
|
75
|
+
private readonly secureFetch;
|
|
76
|
+
private inbound;
|
|
77
|
+
private outbound;
|
|
78
|
+
/**
|
|
79
|
+
* Official OpenAPI port built in sdk mode (proactive path for the outbox).
|
|
80
|
+
* Undefined in gateway mode — proactive capabilities then fail CLOSED.
|
|
81
|
+
*/
|
|
82
|
+
private officialPort?;
|
|
83
|
+
/**
|
|
84
|
+
* Proactive outbox capabilities. Read by the harness outbox;
|
|
85
|
+
* fails CLOSED (all-false) in gateway mode / when not wired.
|
|
86
|
+
*/
|
|
87
|
+
private capProactive;
|
|
88
|
+
private started;
|
|
89
|
+
private stopped;
|
|
90
|
+
private receiveLoop?;
|
|
91
|
+
private connected;
|
|
92
|
+
/** Connection-state-driven auth; the upstream driver owns credentials. */
|
|
93
|
+
private authState;
|
|
94
|
+
private readonly now;
|
|
95
|
+
/** Internal stop signal merged with the context signal for prompt teardown. */
|
|
96
|
+
private stopController?;
|
|
97
|
+
private receiveSignal?;
|
|
98
|
+
constructor(config: DingTalkConfig, deps?: DingTalkAdapterDeps);
|
|
99
|
+
start(ctx: ChannelAdapterContext): Promise<void>;
|
|
100
|
+
stop(): Promise<void>;
|
|
101
|
+
send(target: ChannelTarget, message: OutboundMessage): Promise<SendResult>;
|
|
102
|
+
edit(target: ChannelTarget, messageId: string, message: OutboundMessage): Promise<SendResult>;
|
|
103
|
+
createReply(target: ChannelTarget, _options?: CreateReplyOptions): Promise<ReplyHandle>;
|
|
104
|
+
getHealth(): Promise<ChannelHealth>;
|
|
105
|
+
/** Long-poll receive loop with exponential backoff on failure. */
|
|
106
|
+
startReceiveLoop(): void;
|
|
107
|
+
/** Select and build the upstream driver for the configured mode. */
|
|
108
|
+
private buildUpstream;
|
|
109
|
+
/**
|
|
110
|
+
* Proactive outbox capabilities exposed for the harness outbox.
|
|
111
|
+
* Fails CLOSED: all-false in gateway mode / when not wired.
|
|
112
|
+
*/
|
|
113
|
+
get outboxCapabilities(): OutboxCapabilities;
|
|
114
|
+
private resolveStreamClient;
|
|
115
|
+
private runReceiveLoop;
|
|
116
|
+
/** Flip connection/auth state once the upstream proves reachable. */
|
|
117
|
+
private markConnected;
|
|
118
|
+
private aborted;
|
|
119
|
+
private setAuth;
|
|
120
|
+
private emitAuth;
|
|
121
|
+
private emitConnection;
|
|
122
|
+
}
|
|
123
|
+
//# sourceMappingURL=adapter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../src/adapter.ts"],"names":[],"mappings":"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"}
|
package/lib/adapter.js
ADDED
|
@@ -0,0 +1,355 @@
|
|
|
1
|
+
import { ChannelError, SecureRemoteMediaFetcher } from '@krischoichoi/channel-core';
|
|
2
|
+
import { DWClient } from 'dingtalk-stream';
|
|
3
|
+
import { FetchTransport } from './transport.js';
|
|
4
|
+
import { HttpDingTalkUpstream } from './upstream.js';
|
|
5
|
+
import { DingTalkOpenApiPortImpl } from './official-upstream.js';
|
|
6
|
+
import { DingTalkStreamUpstream, } from './stream-upstream.js';
|
|
7
|
+
import { InboundProcessor } from './inbound.js';
|
|
8
|
+
import { OutboundSender } from './outbound.js';
|
|
9
|
+
import { DingTalkCardReply } from './ai-card.js';
|
|
10
|
+
import { manifest as dingTalkManifest } from './manifest.js';
|
|
11
|
+
export class DingTalkAdapter {
|
|
12
|
+
config;
|
|
13
|
+
id = 'dingtalk';
|
|
14
|
+
/** Upstream compatibility manifest (read structurally by `channels doctor`). */
|
|
15
|
+
manifest = dingTalkManifest;
|
|
16
|
+
capabilities;
|
|
17
|
+
static BASE_CAPABILITIES = {
|
|
18
|
+
text: true,
|
|
19
|
+
image: true,
|
|
20
|
+
file: true,
|
|
21
|
+
audio: true,
|
|
22
|
+
video: false,
|
|
23
|
+
markdown: true,
|
|
24
|
+
cards: true,
|
|
25
|
+
reactions: false,
|
|
26
|
+
threads: false,
|
|
27
|
+
streaming: 'edit',
|
|
28
|
+
// Directional media precision. Inbound
|
|
29
|
+
// image/file have an established official download path. Audio/video are
|
|
30
|
+
// conservatively declared as locators until official callback/download
|
|
31
|
+
// documentation and a real-account live gate prove byte hydration; the
|
|
32
|
+
// inbound processor may still hydrate them as a best-effort enhancement.
|
|
33
|
+
// Outbound: only
|
|
34
|
+
// image/file bytes are actually sendable (proactive uploadMedia + sendMedia,
|
|
35
|
+
// msgKey sampleImageMsg/sampleFile); there is no robot audio/video send
|
|
36
|
+
// path, so outbound audio/video are 'unsupported' (the legacy coarse
|
|
37
|
+
// `audio: true` / `video: false` flags remain unchanged).
|
|
38
|
+
media: {
|
|
39
|
+
inbound: {
|
|
40
|
+
image: 'bytes',
|
|
41
|
+
file: 'bytes',
|
|
42
|
+
audio: 'locator',
|
|
43
|
+
video: 'locator',
|
|
44
|
+
},
|
|
45
|
+
outbound: {
|
|
46
|
+
image: 'bytes',
|
|
47
|
+
file: 'bytes',
|
|
48
|
+
audio: 'unsupported',
|
|
49
|
+
video: 'unsupported',
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
ctx;
|
|
54
|
+
/** Built in `start()` (driver selection needs the resolved deps/credentials). */
|
|
55
|
+
upstream;
|
|
56
|
+
transport;
|
|
57
|
+
deps;
|
|
58
|
+
secureFetch;
|
|
59
|
+
inbound;
|
|
60
|
+
outbound;
|
|
61
|
+
/**
|
|
62
|
+
* Official OpenAPI port built in sdk mode (proactive path for the outbox).
|
|
63
|
+
* Undefined in gateway mode — proactive capabilities then fail CLOSED.
|
|
64
|
+
*/
|
|
65
|
+
officialPort;
|
|
66
|
+
/**
|
|
67
|
+
* Proactive outbox capabilities. Read by the harness outbox;
|
|
68
|
+
* fails CLOSED (all-false) in gateway mode / when not wired.
|
|
69
|
+
*/
|
|
70
|
+
capProactive = { proactiveText: false, proactiveMedia: false };
|
|
71
|
+
started = false;
|
|
72
|
+
stopped = false;
|
|
73
|
+
receiveLoop;
|
|
74
|
+
connected = false;
|
|
75
|
+
/** Connection-state-driven auth; the upstream driver owns credentials. */
|
|
76
|
+
authState = 'unknown';
|
|
77
|
+
now;
|
|
78
|
+
/** Internal stop signal merged with the context signal for prompt teardown. */
|
|
79
|
+
stopController;
|
|
80
|
+
receiveSignal;
|
|
81
|
+
constructor(config, deps = {}) {
|
|
82
|
+
this.config = config;
|
|
83
|
+
this.capabilities = {
|
|
84
|
+
...DingTalkAdapter.BASE_CAPABILITIES,
|
|
85
|
+
// Declared ONLY when the operator opted into a card template that must
|
|
86
|
+
// already be published in their own Card Platform with matching
|
|
87
|
+
// `text`/`actions` variables. A capability claimed without that
|
|
88
|
+
// precondition is a lie the question presenter pays for.
|
|
89
|
+
interactiveActions: config.upstream.mode === 'sdk' && Boolean(config.card.interactiveTemplateId?.trim()),
|
|
90
|
+
};
|
|
91
|
+
this.now = deps.now ?? Date.now;
|
|
92
|
+
this.deps = deps;
|
|
93
|
+
this.secureFetch = deps.secureFetch ?? new SecureRemoteMediaFetcher();
|
|
94
|
+
this.transport = deps.transport ?? new FetchTransport(config.baseUrl, { timeoutMs: config.timeoutMs });
|
|
95
|
+
}
|
|
96
|
+
async start(ctx) {
|
|
97
|
+
if (this.started)
|
|
98
|
+
return;
|
|
99
|
+
this.ctx = ctx;
|
|
100
|
+
this.stopped = false;
|
|
101
|
+
this.buildUpstream();
|
|
102
|
+
this.stopController = new AbortController();
|
|
103
|
+
this.receiveSignal = AbortSignal.any([ctx.signal, this.stopController.signal]);
|
|
104
|
+
this.inbound = new InboundProcessor({
|
|
105
|
+
ctx,
|
|
106
|
+
meta: { channel: this.id, accountId: this.config.accountId },
|
|
107
|
+
dedupEnabled: this.config.dedup.enabled,
|
|
108
|
+
dedupWindowMs: this.config.dedup.windowMs,
|
|
109
|
+
now: this.now,
|
|
110
|
+
secureFetch: this.secureFetch,
|
|
111
|
+
resolveMedia: this.deps.resolveMedia ?? this.officialPort,
|
|
112
|
+
});
|
|
113
|
+
// REPLY keeps the sessionWebhook upstream; PROACTIVE uses the official port.
|
|
114
|
+
this.outbound = new OutboundSender({
|
|
115
|
+
reply: this.upstream,
|
|
116
|
+
logger: ctx.logger,
|
|
117
|
+
proactive: this.officialPort,
|
|
118
|
+
capabilities: this.capProactive,
|
|
119
|
+
interactiveTemplateId: this.config.card.interactiveTemplateId,
|
|
120
|
+
interactiveTextParam: this.config.card.interactiveTextParam,
|
|
121
|
+
interactiveActionsParam: this.config.card.interactiveActionsParam,
|
|
122
|
+
});
|
|
123
|
+
this.connected = false;
|
|
124
|
+
this.authState = 'unknown';
|
|
125
|
+
this.emitAuth(this.authState);
|
|
126
|
+
this.emitConnection('connecting');
|
|
127
|
+
this.startReceiveLoop();
|
|
128
|
+
this.started = true;
|
|
129
|
+
}
|
|
130
|
+
async stop() {
|
|
131
|
+
if (!this.started || this.stopped)
|
|
132
|
+
return;
|
|
133
|
+
this.stopped = true;
|
|
134
|
+
this.started = false;
|
|
135
|
+
this.connected = false;
|
|
136
|
+
// The owning fiber aborts the context signal first; the loop also exits
|
|
137
|
+
// on our internal stop signal so stop() never depends on an external
|
|
138
|
+
// abort (contract tests stop without disposing the context).
|
|
139
|
+
this.stopController?.abort();
|
|
140
|
+
const loop = this.receiveLoop;
|
|
141
|
+
this.receiveLoop = undefined;
|
|
142
|
+
if (loop)
|
|
143
|
+
await loop.catch(() => undefined);
|
|
144
|
+
this.emitConnection('closed');
|
|
145
|
+
}
|
|
146
|
+
async send(target, message) {
|
|
147
|
+
if (!this.started || !this.outbound) {
|
|
148
|
+
throw new ChannelError('CHANNEL_NOT_STARTED', 'dingtalk adapter is not started');
|
|
149
|
+
}
|
|
150
|
+
return this.outbound.send(target, message);
|
|
151
|
+
}
|
|
152
|
+
async edit(target, messageId, message) {
|
|
153
|
+
if (!this.started || !this.outbound) {
|
|
154
|
+
throw new ChannelError('CHANNEL_NOT_STARTED', 'dingtalk adapter is not started');
|
|
155
|
+
}
|
|
156
|
+
return this.outbound.edit(target, messageId, message);
|
|
157
|
+
}
|
|
158
|
+
async createReply(target, _options) {
|
|
159
|
+
if (!this.started || !this.ctx) {
|
|
160
|
+
throw new ChannelError('CHANNEL_NOT_STARTED', 'dingtalk adapter is not started');
|
|
161
|
+
}
|
|
162
|
+
return new DingTalkCardReply({
|
|
163
|
+
upstream: this.upstream,
|
|
164
|
+
target,
|
|
165
|
+
logger: this.ctx.logger,
|
|
166
|
+
createOnFirstDelta: this.config.card.createOnFirstDelta,
|
|
167
|
+
now: this.now,
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
async getHealth() {
|
|
171
|
+
if (!this.started) {
|
|
172
|
+
return { status: 'down', detail: 'dingtalk adapter is not started', authenticated: false };
|
|
173
|
+
}
|
|
174
|
+
if (this.connected) {
|
|
175
|
+
return { status: 'ok', detail: 'connected', connection: 'connected', authenticated: true };
|
|
176
|
+
}
|
|
177
|
+
return {
|
|
178
|
+
status: 'degraded',
|
|
179
|
+
detail: 'receive loop down',
|
|
180
|
+
connection: 'disconnected',
|
|
181
|
+
authenticated: false,
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
/** Long-poll receive loop with exponential backoff on failure. */
|
|
185
|
+
startReceiveLoop() {
|
|
186
|
+
if (this.receiveLoop)
|
|
187
|
+
return;
|
|
188
|
+
this.receiveLoop = this.runReceiveLoop();
|
|
189
|
+
}
|
|
190
|
+
/** Select and build the upstream driver for the configured mode. */
|
|
191
|
+
buildUpstream() {
|
|
192
|
+
const httpUpstream = new HttpDingTalkUpstream({
|
|
193
|
+
transport: this.transport,
|
|
194
|
+
longPollTimeoutMs: this.config.longPollTimeoutMs,
|
|
195
|
+
});
|
|
196
|
+
if (this.config.upstream.mode !== 'sdk') {
|
|
197
|
+
this.upstream = httpUpstream;
|
|
198
|
+
// Gateway mode has no official proactive path — capabilities fail CLOSED.
|
|
199
|
+
this.officialPort = undefined;
|
|
200
|
+
this.capProactive = { proactiveText: false, proactiveMedia: false };
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
// The official OpenAPI port implements BOTH the `DingTalkUpstream` outbound
|
|
204
|
+
// surface (sessionWebhook reply) and the `DingTalkOpenApiPort`
|
|
205
|
+
// proactive/media surface. Proactive is implemented in sdk mode,
|
|
206
|
+
// so the outbox capabilities are enabled (capability true only when
|
|
207
|
+
// actually implemented — never faked).
|
|
208
|
+
this.officialPort = new DingTalkOpenApiPortImpl({
|
|
209
|
+
transport: this.transport,
|
|
210
|
+
clientId: this.config.upstream.clientId,
|
|
211
|
+
clientSecret: this.deps.clientSecret,
|
|
212
|
+
secureFetch: this.secureFetch,
|
|
213
|
+
now: this.now,
|
|
214
|
+
});
|
|
215
|
+
this.capProactive = { proactiveText: true, proactiveMedia: true };
|
|
216
|
+
const options = {
|
|
217
|
+
client: this.resolveStreamClient(),
|
|
218
|
+
outbound: this.officialPort,
|
|
219
|
+
onConnected: () => this.markConnected(),
|
|
220
|
+
};
|
|
221
|
+
this.upstream = new DingTalkStreamUpstream(options);
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Proactive outbox capabilities exposed for the harness outbox.
|
|
225
|
+
* Fails CLOSED: all-false in gateway mode / when not wired.
|
|
226
|
+
*/
|
|
227
|
+
get outboxCapabilities() {
|
|
228
|
+
return { ...this.capProactive };
|
|
229
|
+
}
|
|
230
|
+
resolveStreamClient() {
|
|
231
|
+
if (this.deps.sdkClient)
|
|
232
|
+
return this.deps.sdkClient;
|
|
233
|
+
if (this.deps.sdkClientFactory)
|
|
234
|
+
return this.deps.sdkClientFactory(this.config);
|
|
235
|
+
return createDefaultDWClient(this.config, this.deps.clientSecret);
|
|
236
|
+
}
|
|
237
|
+
async runReceiveLoop() {
|
|
238
|
+
let attempt = 0;
|
|
239
|
+
while (!this.stopped && !this.aborted()) {
|
|
240
|
+
try {
|
|
241
|
+
await this.upstream.receive(this.receiveSignal, (raw) => {
|
|
242
|
+
void this.inbound.handle(raw).catch((error) => {
|
|
243
|
+
this.ctx.logger.error('[channel-dingtalk] inbound handling failed', error);
|
|
244
|
+
});
|
|
245
|
+
});
|
|
246
|
+
attempt = 0;
|
|
247
|
+
// A completed receive cycle proves the upstream is reachable; auth
|
|
248
|
+
// follows the connection state (the driver owns credentials). In sdk
|
|
249
|
+
// mode the stream driver also reports connectivity via onConnected.
|
|
250
|
+
this.markConnected();
|
|
251
|
+
}
|
|
252
|
+
catch (error) {
|
|
253
|
+
if (this.stopped || this.aborted())
|
|
254
|
+
break;
|
|
255
|
+
attempt += 1;
|
|
256
|
+
this.connected = false;
|
|
257
|
+
this.emitConnection('reconnecting');
|
|
258
|
+
if (this.config.reconnect.enabled && attempt > this.config.reconnect.maxRetries) {
|
|
259
|
+
this.ctx.logger.warn('[channel-dingtalk] reconnect budget exhausted');
|
|
260
|
+
this.setAuth('failed');
|
|
261
|
+
this.emitConnection('disconnected');
|
|
262
|
+
break;
|
|
263
|
+
}
|
|
264
|
+
const delay = Math.min(this.config.reconnect.baseDelayMs * 2 ** Math.min(attempt - 1, 8), this.config.reconnect.maxDelayMs);
|
|
265
|
+
this.ctx.logger.warn(`[channel-dingtalk] receive loop error; retry in ${delay}ms`, error);
|
|
266
|
+
await sleep(delay, this.receiveSignal);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
this.connected = false;
|
|
270
|
+
if (!this.stopped && !this.aborted()) {
|
|
271
|
+
this.emitConnection('disconnected');
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
/** Flip connection/auth state once the upstream proves reachable. */
|
|
275
|
+
markConnected() {
|
|
276
|
+
// Never report connected while stopping; an external abort is not
|
|
277
|
+
// sufficient (a long-poll cycle may legitimately end on the abort while
|
|
278
|
+
// having already proven reachability — see the gateway lifecycle tests).
|
|
279
|
+
if (this.stopped)
|
|
280
|
+
return;
|
|
281
|
+
if (this.connected)
|
|
282
|
+
return;
|
|
283
|
+
this.connected = true;
|
|
284
|
+
this.setAuth('authenticated');
|
|
285
|
+
this.emitConnection('connected');
|
|
286
|
+
}
|
|
287
|
+
aborted() {
|
|
288
|
+
return this.receiveSignal?.aborted ?? true;
|
|
289
|
+
}
|
|
290
|
+
setAuth(state) {
|
|
291
|
+
if (this.authState === state)
|
|
292
|
+
return;
|
|
293
|
+
this.authState = state;
|
|
294
|
+
this.emitAuth(state);
|
|
295
|
+
}
|
|
296
|
+
emitAuth(state) {
|
|
297
|
+
if (!this.ctx)
|
|
298
|
+
return;
|
|
299
|
+
void this.ctx
|
|
300
|
+
.emit({
|
|
301
|
+
type: 'auth.changed',
|
|
302
|
+
channel: this.id,
|
|
303
|
+
accountId: this.config.accountId,
|
|
304
|
+
state,
|
|
305
|
+
})
|
|
306
|
+
.catch(() => undefined);
|
|
307
|
+
}
|
|
308
|
+
emitConnection(state) {
|
|
309
|
+
if (!this.ctx)
|
|
310
|
+
return;
|
|
311
|
+
void this.ctx
|
|
312
|
+
.emit({
|
|
313
|
+
type: 'connection.changed',
|
|
314
|
+
channel: this.id,
|
|
315
|
+
accountId: this.config.accountId,
|
|
316
|
+
state,
|
|
317
|
+
})
|
|
318
|
+
.catch(() => undefined);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
/**
|
|
322
|
+
* Build the real SDK client from SDK-mode credentials; never logs them.
|
|
323
|
+
*
|
|
324
|
+
* Takes the RESOLVED AppSecret (injected by the plugin via ctx.credentials)
|
|
325
|
+
* rather than reading it from config. A missing secret fails start loudly.
|
|
326
|
+
*/
|
|
327
|
+
function createDefaultDWClient(config, clientSecret) {
|
|
328
|
+
const { clientId } = config.upstream;
|
|
329
|
+
if (!clientId || !clientSecret) {
|
|
330
|
+
throw new ChannelError('CHANNEL_ERROR', 'dingtalk upstream mode "sdk" requires resolved clientId and clientSecret credentials');
|
|
331
|
+
}
|
|
332
|
+
const client = new DWClient({ clientId, clientSecret, keepAlive: false });
|
|
333
|
+
// The adapter owns retry/backoff. The SDK otherwise swallows a failed
|
|
334
|
+
// connection and retries independently, which makes health unreliable.
|
|
335
|
+
client.getConfig().autoReconnect = false;
|
|
336
|
+
return client;
|
|
337
|
+
}
|
|
338
|
+
function sleep(ms, signal) {
|
|
339
|
+
return new Promise((resolve) => {
|
|
340
|
+
if (signal?.aborted) {
|
|
341
|
+
resolve();
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
344
|
+
const timer = setTimeout(() => {
|
|
345
|
+
signal?.removeEventListener('abort', onAbort);
|
|
346
|
+
resolve();
|
|
347
|
+
}, ms);
|
|
348
|
+
const onAbort = () => {
|
|
349
|
+
clearTimeout(timer);
|
|
350
|
+
resolve();
|
|
351
|
+
};
|
|
352
|
+
signal?.addEventListener('abort', onAbort, { once: true });
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
//# sourceMappingURL=adapter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"adapter.js","sourceRoot":"","sources":["../src/adapter.ts"],"names":[],"mappings":"AAmCA,OAAO,EAAE,YAAY,EAAE,wBAAwB,EAAE,MAAM,4BAA4B,CAAC;AAEpF,OAAO,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAE3C,OAAO,EAAE,cAAc,EAAsB,MAAM,gBAAgB,CAAC;AACpE,OAAO,EAAE,oBAAoB,EAAyB,MAAM,eAAe,CAAC;AAC5E,OAAO,EAAE,uBAAuB,EAAE,MAAM,wBAAwB,CAAC;AAEjE,OAAO,EACL,sBAAsB,GAGvB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAChD,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAC/C,OAAO,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AACjD,OAAO,EAAE,QAAQ,IAAI,gBAAgB,EAAyB,MAAM,eAAe,CAAC;AAkCpF,MAAM,OAAO,eAAe;IAyEG;IAxEpB,EAAE,GAAG,UAAU,CAAC;IAEzB,gFAAgF;IACvE,QAAQ,GAAqB,gBAAgB,CAAC;IAE9C,YAAY,CAAsB;IACnC,MAAM,CAAU,iBAAiB,GAAoD;QAC3F,IAAI,EAAE,IAAI;QACV,KAAK,EAAE,IAAI;QACX,IAAI,EAAE,IAAI;QACV,KAAK,EAAE,IAAI;QACX,KAAK,EAAE,KAAK;QACZ,QAAQ,EAAE,IAAI;QACd,KAAK,EAAE,IAAI;QACX,SAAS,EAAE,KAAK;QAChB,OAAO,EAAE,KAAK;QACd,SAAS,EAAE,MAAM;QACjB,uCAAuC;QACvC,yEAAyE;QACzE,uEAAuE;QACvE,uEAAuE;QACvE,yEAAyE;QACzE,iBAAiB;QACjB,6EAA6E;QAC7E,wEAAwE;QACxE,qEAAqE;QACrE,0DAA0D;QAC1D,KAAK,EAAE;YACL,OAAO,EAAE;gBACP,KAAK,EAAE,OAAO;gBACd,IAAI,EAAE,OAAO;gBACb,KAAK,EAAE,SAAS;gBAChB,KAAK,EAAE,SAAS;aACjB;YACD,QAAQ,EAAE;gBACR,KAAK,EAAE,OAAO;gBACd,IAAI,EAAE,OAAO;gBACb,KAAK,EAAE,aAAa;gBACpB,KAAK,EAAE,aAAa;aACrB;SACF;KACF,CAAC;IAEM,GAAG,CAAyB;IACpC,iFAAiF;IACzE,QAAQ,CAAoB;IACnB,SAAS,CAAgB;IACzB,IAAI,CAAsB;IAC1B,WAAW,CAAuB;IAC3C,OAAO,CAAoB;IAC3B,QAAQ,CAAkB;IAClC;;;OAGG;IACK,YAAY,CAA2B;IAC/C;;;OAGG;IACK,YAAY,GAAuB,EAAE,aAAa,EAAE,KAAK,EAAE,cAAc,EAAE,KAAK,EAAE,CAAC;IACnF,OAAO,GAAG,KAAK,CAAC;IAChB,OAAO,GAAG,KAAK,CAAC;IAChB,WAAW,CAAiB;IAC5B,SAAS,GAAG,KAAK,CAAC;IAC1B,0EAA0E;IAClE,SAAS,GAA2C,SAAS,CAAC;IACrD,GAAG,CAAe;IACnC,+EAA+E;IACvE,cAAc,CAAmB;IACjC,aAAa,CAAe;IAEpC,YAA6B,MAAsB,EAAE,OAA4B,EAAE;QAAtD,WAAM,GAAN,MAAM,CAAgB;QACjD,IAAI,CAAC,YAAY,GAAG;YAClB,GAAG,eAAe,CAAC,iBAAiB;YACpC,uEAAuE;YACvE,gEAAgE;YAChE,gEAAgE;YAChE,yDAAyD;YACzD,kBAAkB,EAAE,MAAM,CAAC,QAAQ,CAAC,IAAI,KAAK,KAAK,IAAI,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,qBAAqB,EAAE,IAAI,EAAE,CAAC;SACzG,CAAC;QACF,IAAI,CAAC,GAAG,GAAG,IAAI,CAAC,GAAG,IAAI,IAAI,CAAC,GAAG,CAAC;QAChC,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC,WAAW,IAAI,IAAI,wBAAwB,EAAE,CAAC;QACtE,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,SAAS,IAAI,IAAI,cAAc,CAAC,MAAM,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,MAAM,CAAC,SAAS,EAAE,CAAC,CAAC;IACzG,CAAC;IAED,KAAK,CAAC,KAAK,CAAC,GAA0B;QACpC,IAAI,IAAI,CAAC,OAAO;YAAE,OAAO;QACzB,IAAI,CAAC,GAAG,GAAG,GAAG,CAAC;QACf,IAAI,CAAC,OAAO,GAAG,KAAK,CAAC;QACrB,IAAI,CAAC,aAAa,EAAE,CAAC;QACrB,IAAI,CAAC,cAAc,GAAG,IAAI,eAAe,EAAE,CAAC;QAC5C,IAAI,CAAC,aAAa,GAAG,WAAW,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,MAAM,EAAE,IAAI,CAAC,cAAc,CAAC,MAAM,CAAC,CAAC,CAAC;QAC/E,IAAI,CAAC,OAAO,GAAG,IAAI,gBAAgB,CAAC;YAClC,GAAG;YACH,IAAI,EAAE,EAAE,OAAO,EAAE,IAAI,CAAC,EAAW,EAAE,SAAS,EAAE,IAAI,CAAC,MAAM,CAAC,SAAkB,EAAE;YAC9E,YAAY,EAAE,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,OAAO;YACvC,aAAa,EAAE,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,QAAQ;YACzC,GAAG,EAAE,IAAI,CAAC,GAAG;YACb,WAAW,EAAE,IAAI,CAAC,WAAW;YAC7B,YAAY,EAAE,IAAI,CAAC,IAAI,CAAC,YAAY,IAAI,IAAI,CAAC,YAAY;SAC1D,CAAC,CAAC;QACH,6EAA6E;QAC7E,IAAI,CAAC,QAAQ,GAAG,IAAI,cAAc,CAAC;YACjC,KAAK,EAAE,IAAI,CAAC,QAAQ;YACpB,MAAM,EAAE,GAAG,CAAC,MAAM;YAClB,SAAS,EAAE,IAAI,CAAC,YAAY;YAC5B,YAAY,EAAE,IAAI,CAAC,YAAY;YAC/B,qBAAqB,EAAE,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,qBAAqB;YAC7D,oBAAoB,EAAE,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,oBAAoB;YAC3D,uBAAuB,EAAE,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,uBAAuB;SAClE,CAAC,CAAC;QAEH,IAAI,CAAC,SAAS,GAAG,KAAK,CAAC;QACvB,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;QAC3B,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QAC9B,IAAI,CAAC,cAAc,CAAC,YAAY,CAAC,CAAC;QAClC,IAAI,CAAC,gBAAgB,EAAE,CAAC;QACxB,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC;IACtB,CAAC;IAED,KAAK,CAAC,IAAI;QACR,IAAI,CAAC,IAAI,CAAC,OAAO,IAAI,IAAI,CAAC,OAAO;YAAE,OAAO;QAC1C,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC;QACpB,IAAI,CAAC,OAAO,GAAG,KAAK,CAAC;QACrB,IAAI,CAAC,SAAS,GAAG,KAAK,CAAC;QACvB,wEAAwE;QACxE,qEAAqE;QACrE,6DAA6D;QAC7D,IAAI,CAAC,cAAc,EAAE,KAAK,EAAE,CAAC;QAC7B,MAAM,IAAI,GAAG,IAAI,CAAC,WAAW,CAAC;QAC9B,IAAI,CAAC,WAAW,GAAG,SAAS,CAAC;QAC7B,IAAI,IAAI;YAAE,MAAM,IAAI,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;QAC5C,IAAI,CAAC,cAAc,CAAC,QAAQ,CAAC,CAAC;IAChC,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,MAAqB,EAAE,OAAwB;QACxD,IAAI,CAAC,IAAI,CAAC,OAAO,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC;YACpC,MAAM,IAAI,YAAY,CAAC,qBAAqB,EAAE,iCAAiC,CAAC,CAAC;QACnF,CAAC;QACD,OAAO,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC7C,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,MAAqB,EAAE,SAAiB,EAAE,OAAwB;QAC3E,IAAI,CAAC,IAAI,CAAC,OAAO,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC;YACpC,MAAM,IAAI,YAAY,CAAC,qBAAqB,EAAE,iCAAiC,CAAC,CAAC;QACnF,CAAC;QACD,OAAO,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,MAAM,EAAE,SAAS,EAAE,OAAO,CAAC,CAAC;IACxD,CAAC;IAED,KAAK,CAAC,WAAW,CAAC,MAAqB,EAAE,QAA6B;QACpE,IAAI,CAAC,IAAI,CAAC,OAAO,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC;YAC/B,MAAM,IAAI,YAAY,CAAC,qBAAqB,EAAE,iCAAiC,CAAC,CAAC;QACnF,CAAC;QACD,OAAO,IAAI,iBAAiB,CAAC;YAC3B,QAAQ,EAAE,IAAI,CAAC,QAAQ;YACvB,MAAM;YACN,MAAM,EAAE,IAAI,CAAC,GAAG,CAAC,MAAM;YACvB,kBAAkB,EAAE,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,kBAAkB;YACvD,GAAG,EAAE,IAAI,CAAC,GAAG;SACd,CAAC,CAAC;IACL,CAAC;IAED,KAAK,CAAC,SAAS;QACb,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC;YAClB,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,iCAAiC,EAAE,aAAa,EAAE,KAAK,EAAE,CAAC;QAC7F,CAAC;QACD,IAAI,IAAI,CAAC,SAAS,EAAE,CAAC;YACnB,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,UAAU,EAAE,WAAW,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC;QAC7F,CAAC;QACD,OAAO;YACL,MAAM,EAAE,UAAU;YAClB,MAAM,EAAE,mBAAmB;YAC3B,UAAU,EAAE,cAAc;YAC1B,aAAa,EAAE,KAAK;SACrB,CAAC;IACJ,CAAC;IAED,kEAAkE;IAClE,gBAAgB;QACd,IAAI,IAAI,CAAC,WAAW;YAAE,OAAO;QAC7B,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC,cAAc,EAAE,CAAC;IAC3C,CAAC;IAED,oEAAoE;IAC5D,aAAa;QACnB,MAAM,YAAY,GAAG,IAAI,oBAAoB,CAAC;YAC5C,SAAS,EAAE,IAAI,CAAC,SAAS;YACzB,iBAAiB,EAAE,IAAI,CAAC,MAAM,CAAC,iBAAiB;SACjD,CAAC,CAAC;QACH,IAAI,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,KAAK,KAAK,EAAE,CAAC;YACxC,IAAI,CAAC,QAAQ,GAAG,YAAY,CAAC;YAC7B,0EAA0E;YAC1E,IAAI,CAAC,YAAY,GAAG,SAAS,CAAC;YAC9B,IAAI,CAAC,YAAY,GAAG,EAAE,aAAa,EAAE,KAAK,EAAE,cAAc,EAAE,KAAK,EAAE,CAAC;YACpE,OAAO;QACT,CAAC;QACD,4EAA4E;QAC5E,+DAA+D;QAC/D,iEAAiE;QACjE,oEAAoE;QACpE,uCAAuC;QACvC,IAAI,CAAC,YAAY,GAAG,IAAI,uBAAuB,CAAC;YAC9C,SAAS,EAAE,IAAI,CAAC,SAAS;YACzB,QAAQ,EAAE,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,QAAQ;YACvC,YAAY,EAAE,IAAI,CAAC,IAAI,CAAC,YAAY;YACpC,WAAW,EAAE,IAAI,CAAC,WAAW;YAC7B,GAAG,EAAE,IAAI,CAAC,GAAG;SACd,CAAC,CAAC;QACH,IAAI,CAAC,YAAY,GAAG,EAAE,aAAa,EAAE,IAAI,EAAE,cAAc,EAAE,IAAI,EAAE,CAAC;QAClE,MAAM,OAAO,GAAkC;YAC7C,MAAM,EAAE,IAAI,CAAC,mBAAmB,EAAE;YAClC,QAAQ,EAAE,IAAI,CAAC,YAAY;YAC3B,WAAW,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,aAAa,EAAE;SACxC,CAAC;QACF,IAAI,CAAC,QAAQ,GAAG,IAAI,sBAAsB,CAAC,OAAO,CAAC,CAAC;IACtD,CAAC;IAED;;;OAGG;IACH,IAAI,kBAAkB;QACpB,OAAO,EAAE,GAAG,IAAI,CAAC,YAAY,EAAE,CAAC;IAClC,CAAC;IAEO,mBAAmB;QACzB,IAAI,IAAI,CAAC,IAAI,CAAC,SAAS;YAAE,OAAO,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC;QACpD,IAAI,IAAI,CAAC,IAAI,CAAC,gBAAgB;YAAE,OAAO,IAAI,CAAC,IAAI,CAAC,gBAAgB,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QAC/E,OAAO,qBAAqB,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;IACpE,CAAC;IAEO,KAAK,CAAC,cAAc;QAC1B,IAAI,OAAO,GAAG,CAAC,CAAC;QAChB,OAAO,CAAC,IAAI,CAAC,OAAO,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,EAAE,CAAC;YACxC,IAAI,CAAC;gBACH,MAAM,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC,aAAc,EAAE,CAAC,GAAG,EAAE,EAAE;oBACvD,KAAK,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE;wBAC5C,IAAI,CAAC,GAAI,CAAC,MAAM,CAAC,KAAK,CAAC,4CAA4C,EAAE,KAAK,CAAC,CAAC;oBAC9E,CAAC,CAAC,CAAC;gBACL,CAAC,CAAC,CAAC;gBACH,OAAO,GAAG,CAAC,CAAC;gBACZ,mEAAmE;gBACnE,qEAAqE;gBACrE,oEAAoE;gBACpE,IAAI,CAAC,aAAa,EAAE,CAAC;YACvB,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,IAAI,IAAI,CAAC,OAAO,IAAI,IAAI,CAAC,OAAO,EAAE;oBAAE,MAAM;gBAC1C,OAAO,IAAI,CAAC,CAAC;gBACb,IAAI,CAAC,SAAS,GAAG,KAAK,CAAC;gBACvB,IAAI,CAAC,cAAc,CAAC,cAAc,CAAC,CAAC;gBACpC,IAAI,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,OAAO,IAAI,OAAO,GAAG,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,UAAU,EAAE,CAAC;oBAChF,IAAI,CAAC,GAAI,CAAC,MAAM,CAAC,IAAI,CAAC,+CAA+C,CAAC,CAAC;oBACvE,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;oBACvB,IAAI,CAAC,cAAc,CAAC,cAAc,CAAC,CAAC;oBACpC,MAAM;gBACR,CAAC;gBACD,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CACpB,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,WAAW,GAAG,CAAC,IAAI,IAAI,CAAC,GAAG,CAAC,OAAO,GAAG,CAAC,EAAE,CAAC,CAAC,EACjE,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,UAAU,CACjC,CAAC;gBACF,IAAI,CAAC,GAAI,CAAC,MAAM,CAAC,IAAI,CAAC,mDAAmD,KAAK,IAAI,EAAE,KAAK,CAAC,CAAC;gBAC3F,MAAM,KAAK,CAAC,KAAK,EAAE,IAAI,CAAC,aAAc,CAAC,CAAC;YAC1C,CAAC;QACH,CAAC;QACD,IAAI,CAAC,SAAS,GAAG,KAAK,CAAC;QACvB,IAAI,CAAC,IAAI,CAAC,OAAO,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,EAAE,CAAC;YACrC,IAAI,CAAC,cAAc,CAAC,cAAc,CAAC,CAAC;QACtC,CAAC;IACH,CAAC;IAED,qEAAqE;IAC7D,aAAa;QACnB,kEAAkE;QAClE,wEAAwE;QACxE,yEAAyE;QACzE,IAAI,IAAI,CAAC,OAAO;YAAE,OAAO;QACzB,IAAI,IAAI,CAAC,SAAS;YAAE,OAAO;QAC3B,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC;QACtB,IAAI,CAAC,OAAO,CAAC,eAAe,CAAC,CAAC;QAC9B,IAAI,CAAC,cAAc,CAAC,WAAW,CAAC,CAAC;IACnC,CAAC;IAEO,OAAO;QACb,OAAO,IAAI,CAAC,aAAa,EAAE,OAAO,IAAI,IAAI,CAAC;IAC7C,CAAC;IAEO,OAAO,CAAC,KAA6C;QAC3D,IAAI,IAAI,CAAC,SAAS,KAAK,KAAK;YAAE,OAAO;QACrC,IAAI,CAAC,SAAS,GAAG,KAAK,CAAC;QACvB,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;IACvB,CAAC;IAEO,QAAQ,CAAC,KAA6C;QAC5D,IAAI,CAAC,IAAI,CAAC,GAAG;YAAE,OAAO;QACtB,KAAK,IAAI,CAAC,GAAG;aACV,IAAI,CAAC;YACJ,IAAI,EAAE,cAAc;YACpB,OAAO,EAAE,IAAI,CAAC,EAAW;YACzB,SAAS,EAAE,IAAI,CAAC,MAAM,CAAC,SAAkB;YACzC,KAAK;SACN,CAAC;aACD,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;IAC5B,CAAC;IAEO,cAAc,CAAC,KAA8E;QACnG,IAAI,CAAC,IAAI,CAAC,GAAG;YAAE,OAAO;QACtB,KAAK,IAAI,CAAC,GAAG;aACV,IAAI,CAAC;YACJ,IAAI,EAAE,oBAAoB;YAC1B,OAAO,EAAE,IAAI,CAAC,EAAW;YACzB,SAAS,EAAE,IAAI,CAAC,MAAM,CAAC,SAAkB;YACzC,KAAK;SACN,CAAC;aACD,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;IAC5B,CAAC;;AAGH;;;;;GAKG;AACH,SAAS,qBAAqB,CAC5B,MAAsB,EACtB,YAAgC;IAEhC,MAAM,EAAE,QAAQ,EAAE,GAAG,MAAM,CAAC,QAAQ,CAAC;IACrC,IAAI,CAAC,QAAQ,IAAI,CAAC,YAAY,EAAE,CAAC;QAC/B,MAAM,IAAI,YAAY,CACpB,eAAe,EACf,sFAAsF,CACvF,CAAC;IACJ,CAAC;IACD,MAAM,MAAM,GAAG,IAAI,QAAQ,CAAC,EAAE,QAAQ,EAAE,YAAY,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC,CAAC;IAC1E,sEAAsE;IACtE,uEAAuE;IACvE,MAAM,CAAC,SAAS,EAAE,CAAC,aAAa,GAAG,KAAK,CAAC;IACzC,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,SAAS,KAAK,CAAC,EAAU,EAAE,MAAoB;IAC7C,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;QAC7B,IAAI,MAAM,EAAE,OAAO,EAAE,CAAC;YACpB,OAAO,EAAE,CAAC;YACV,OAAO;QACT,CAAC;QACD,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE;YAC5B,MAAM,EAAE,mBAAmB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;YAC9C,OAAO,EAAE,CAAC;QACZ,CAAC,EAAE,EAAE,CAAC,CAAC;QACP,MAAM,OAAO,GAAG,GAAG,EAAE;YACnB,YAAY,CAAC,KAAK,CAAC,CAAC;YACpB,OAAO,EAAE,CAAC;QACZ,CAAC,CAAC;QACF,MAAM,EAAE,gBAAgB,CAAC,OAAO,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;IAC7D,CAAC,CAAC,CAAC;AACL,CAAC"}
|