dsh-connect 0.7.2 → 0.8.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 +68 -32
- package/README.zh.md +67 -31
- package/client/client.js +387 -0
- package/client/client.js.map +7 -0
- package/client/settings-client.mjs +177 -0
- package/examples/minimal.config.json +16 -0
- package/lib/channels/dingtalk/adapter.d.ts +59 -0
- package/lib/channels/dingtalk/adapter.d.ts.map +1 -0
- package/lib/channels/dingtalk/adapter.js +123 -0
- package/lib/channels/dingtalk/adapter.js.map +1 -0
- package/lib/channels/dingtalk/i18n.d.ts +14 -0
- package/lib/channels/dingtalk/i18n.d.ts.map +1 -0
- package/lib/channels/dingtalk/i18n.js +16 -0
- package/lib/channels/dingtalk/i18n.js.map +1 -0
- package/lib/channels/dingtalk/index.d.ts +127 -0
- package/lib/channels/dingtalk/index.d.ts.map +1 -0
- package/lib/channels/dingtalk/index.js +144 -0
- package/lib/channels/dingtalk/index.js.map +1 -0
- package/lib/channels/dingtalk/message.d.ts +49 -0
- package/lib/channels/dingtalk/message.d.ts.map +1 -0
- package/lib/channels/dingtalk/message.js +43 -0
- package/lib/channels/dingtalk/message.js.map +1 -0
- package/lib/channels/dingtalk/stomp.d.ts +36 -0
- package/lib/channels/dingtalk/stomp.d.ts.map +1 -0
- package/lib/channels/dingtalk/stomp.js +107 -0
- package/lib/channels/dingtalk/stomp.js.map +1 -0
- package/lib/channels/dingtalk/stream.d.ts +53 -0
- package/lib/channels/dingtalk/stream.d.ts.map +1 -0
- package/lib/channels/dingtalk/stream.js +199 -0
- package/lib/channels/dingtalk/stream.js.map +1 -0
- package/lib/channels/dingtalk/webhook.d.ts +84 -0
- package/lib/channels/dingtalk/webhook.d.ts.map +1 -0
- package/lib/channels/dingtalk/webhook.js +143 -0
- package/lib/channels/dingtalk/webhook.js.map +1 -0
- package/lib/channels/feishu/adapter.d.ts +139 -0
- package/lib/channels/feishu/adapter.d.ts.map +1 -0
- package/lib/channels/feishu/adapter.js +677 -0
- package/lib/channels/feishu/adapter.js.map +1 -0
- package/lib/channels/feishu/i18n.d.ts +28 -0
- package/lib/channels/feishu/i18n.d.ts.map +1 -0
- package/lib/channels/feishu/i18n.js +40 -0
- package/lib/channels/feishu/i18n.js.map +1 -0
- package/lib/channels/feishu/index.d.ts +61 -0
- package/lib/channels/feishu/index.d.ts.map +1 -0
- package/lib/channels/feishu/index.js +77 -0
- package/lib/channels/feishu/index.js.map +1 -0
- package/lib/channels/feishu/onboard.d.ts +16 -0
- package/lib/channels/feishu/onboard.d.ts.map +1 -0
- package/lib/channels/feishu/onboard.js +86 -0
- package/lib/channels/feishu/onboard.js.map +1 -0
- package/lib/channels/telegram/adapter.d.ts +70 -0
- package/lib/channels/telegram/adapter.d.ts.map +1 -0
- package/lib/channels/telegram/adapter.js +454 -0
- package/lib/channels/telegram/adapter.js.map +1 -0
- package/lib/channels/telegram/client.d.ts +115 -0
- package/lib/channels/telegram/client.d.ts.map +1 -0
- package/lib/channels/telegram/client.js +198 -0
- package/lib/channels/telegram/client.js.map +1 -0
- package/lib/channels/telegram/i18n.d.ts +14 -0
- package/lib/channels/telegram/i18n.d.ts.map +1 -0
- package/lib/channels/telegram/i18n.js +18 -0
- package/lib/channels/telegram/i18n.js.map +1 -0
- package/lib/channels/telegram/index.d.ts +46 -0
- package/lib/channels/telegram/index.d.ts.map +1 -0
- package/lib/channels/telegram/index.js +54 -0
- package/lib/channels/telegram/index.js.map +1 -0
- package/lib/channels/web/adapter.d.ts +103 -0
- package/lib/channels/web/adapter.d.ts.map +1 -0
- package/lib/channels/web/adapter.js +161 -0
- package/lib/channels/web/adapter.js.map +1 -0
- package/lib/channels/web/index.d.ts +47 -0
- package/lib/channels/web/index.d.ts.map +1 -0
- package/lib/channels/web/index.js +59 -0
- package/lib/channels/web/index.js.map +1 -0
- package/lib/index.d.ts +68 -11
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +141 -8
- package/lib/index.js.map +1 -1
- package/lib/runner.d.ts +1 -2
- package/lib/runner.d.ts.map +1 -1
- package/lib/runner.js.map +1 -1
- package/lib/service.d.ts +1 -1
- package/lib/service.d.ts.map +1 -1
- package/lib/service.js +1 -1
- package/lib/service.js.map +1 -1
- package/lib/settings/channels.d.ts +51 -0
- package/lib/settings/channels.d.ts.map +1 -0
- package/lib/settings/channels.js +101 -0
- package/lib/settings/channels.js.map +1 -0
- package/lib/settings/credential-store.d.ts +39 -0
- package/lib/settings/credential-store.d.ts.map +1 -0
- package/lib/settings/credential-store.js +68 -0
- package/lib/settings/credential-store.js.map +1 -0
- package/lib/settings/index.d.ts +14 -0
- package/lib/settings/index.d.ts.map +1 -0
- package/lib/settings/index.js +14 -0
- package/lib/settings/index.js.map +1 -0
- package/lib/settings/rpc-client.d.ts +36 -0
- package/lib/settings/rpc-client.d.ts.map +1 -0
- package/lib/settings/rpc-client.js +45 -0
- package/lib/settings/rpc-client.js.map +1 -0
- package/lib/settings/settings-model.d.ts +107 -0
- package/lib/settings/settings-model.d.ts.map +1 -0
- package/lib/settings/settings-model.js +110 -0
- package/lib/settings/settings-model.js.map +1 -0
- package/lib/settings/settings-rpc.d.ts +62 -0
- package/lib/settings/settings-rpc.d.ts.map +1 -0
- package/lib/settings/settings-rpc.js +90 -0
- package/lib/settings/settings-rpc.js.map +1 -0
- package/lib/settings/settings-service.d.ts +27 -0
- package/lib/settings/settings-service.d.ts.map +1 -0
- package/lib/settings/settings-service.js +103 -0
- package/lib/settings/settings-service.js.map +1 -0
- package/package.json +48 -6
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dsh-connect client settings plugin.
|
|
3
|
+
*
|
|
4
|
+
* Registers a "settings.section" in the DeepSeek Harness web UI, mirroring
|
|
5
|
+
* xmanrui/dsh-im (plugin-src/client/index.js). The pane talks to the host over
|
|
6
|
+
* the `/dsh-connect` channel via `ctx.connection.rpc.call`. All the data logic
|
|
7
|
+
* (snapshot<->form mapping, save-payload building) lives in the tested
|
|
8
|
+
* `settings-model.js` / `rpc-client.js` helpers; this component is a thin
|
|
9
|
+
* renderer. Build the lib first (`pnpm --filter dsh-connect build`), then
|
|
10
|
+
* bundle this in a `dsh web` shell (Vite).
|
|
11
|
+
*/
|
|
12
|
+
import * as React from 'react';
|
|
13
|
+
|
|
14
|
+
import { SETTINGS_RPC_CHANNEL } from '../lib/settings/settings-rpc.js';
|
|
15
|
+
import { loadSettings, saveSettings, saveCredentials } from '../lib/settings/rpc-client.js';
|
|
16
|
+
import { snapshotToForm, buildConfigSave, buildCredentialSaves, CHANNEL_SECRET_FIELDS, CHANNEL_CONFIG_FIELDS, CHANNEL_DEFAULT_FIELDS, coerceConfigValue } from '../lib/settings/settings-model.js';
|
|
17
|
+
|
|
18
|
+
export const name = 'dsh-connect-settings';
|
|
19
|
+
export const inject = ['slots', 'connection', 'locale'];
|
|
20
|
+
export const NS = 'dsh-connect';
|
|
21
|
+
|
|
22
|
+
export const locale = {
|
|
23
|
+
zh: { title: '连接设置', channels: '渠道', save: '保存', saved: '已保存', error: '保存失败', loading: '加载中', defaults: '公共默认(channelDefaults)', statePath: '设置文件', secret: '密钥', config: '配置', reachable: '已连接凭据', unreachable: '未配置凭据' },
|
|
24
|
+
en: { title: 'Connection Settings', channels: 'Channels', save: 'Save', saved: 'Saved', error: 'Save failed', loading: 'Loading', defaults: 'Defaults (channelDefaults)', statePath: 'Settings file', secret: 'Secret', config: 'Config', reachable: 'Credential set', unreachable: 'Credential missing' },
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const h = React.createElement;
|
|
28
|
+
|
|
29
|
+
// Self-contained scoped stylesheet for the pane (injected once). The host's own
|
|
30
|
+
// settings panes use per-feature CSS modules we can't import into this separate
|
|
31
|
+
// bundle, so we ship our own consistent, theme-aware styles instead. Everything
|
|
32
|
+
// is scoped under `.dsh-connect-settings` so nothing leaks into the shell.
|
|
33
|
+
const STYLE = `
|
|
34
|
+
.dsh-connect-settings{--ds-bg:#ffffff;--ds-bg-sub:#f6f7f9;--ds-text:#1f2329;--ds-muted:#646a73;--ds-border:#e2e4e8;--ds-border-2:#c8cbd0;--ds-accent:#3b82f6;--ds-accent-fg:#ffffff;--ds-focus:rgba(59,130,246,.22);display:flex;flex-direction:column;gap:14px;min-width:280px;max-width:760px;color:var(--ds-text);font:13px/1.6 -apple-system,"Segoe UI","PingFang SC","Microsoft YaHei",sans-serif;padding:4px 2px}
|
|
35
|
+
@media(prefers-color-scheme:dark){.dsh-connect-settings{--ds-bg:#1a1b1e;--ds-bg-sub:#232428;--ds-text:#e8eaed;--ds-muted:#9aa0a6;--ds-border:#3b3d42;--ds-border-2:#5c5f66;--ds-accent:#5b9dff;--ds-accent-fg:#0f1115;--ds-focus:rgba(91,157,255,.25)}}
|
|
36
|
+
.dsh-connect-settings .ds-card{border:1px solid var(--ds-border);border-radius:10px;background:var(--ds-bg);padding:12px 14px;display:flex;flex-direction:column;gap:10px}
|
|
37
|
+
.dsh-connect-settings .ds-card-title{margin:0;font-size:13px;font-weight:600}
|
|
38
|
+
.dsh-connect-settings .ds-channel{border:1px dashed var(--ds-border-2);border-radius:8px;padding:8px 12px;margin:0;background:var(--ds-bg-sub)}
|
|
39
|
+
.dsh-connect-settings .ds-legend{padding:0 6px}
|
|
40
|
+
.dsh-connect-settings .ds-legend label{display:flex;align-items:center;gap:6px;font-size:12px;font-weight:600}
|
|
41
|
+
.dsh-connect-settings .ds-badge{margin-left:4px;font-size:11px;font-weight:500;padding:1px 8px;border-radius:99px;background:var(--ds-bg);color:var(--ds-muted);border:1px solid var(--ds-border-2)}
|
|
42
|
+
.dsh-connect-settings .ds-fields{display:grid;grid-template-columns:repeat(auto-fill,minmax(180px,1fr));gap:10px 14px;margin-top:8px}
|
|
43
|
+
.dsh-connect-settings .ds-field{display:flex;flex-direction:column;gap:4px;font-size:12px;color:var(--ds-muted)}
|
|
44
|
+
.dsh-connect-settings .ds-field.ds-check-field{flex-direction:row;align-items:center;gap:6px}
|
|
45
|
+
.dsh-connect-settings .ds-check{width:16px;height:16px;accent-color:var(--ds-accent)}
|
|
46
|
+
.dsh-connect-settings .ds-input{height:30px;width:100%;padding:0 9px;border:1px solid var(--ds-border-2);border-radius:6px;background:var(--ds-bg);color:var(--ds-text);font:inherit}
|
|
47
|
+
.dsh-connect-settings select.ds-input{cursor:pointer}
|
|
48
|
+
.dsh-connect-settings .ds-input:focus{outline:none;border-color:var(--ds-accent);box-shadow:0 0 0 2px var(--ds-focus)}
|
|
49
|
+
.dsh-connect-settings .ds-actions{display:flex;align-items:center;gap:12px;margin-top:2px}
|
|
50
|
+
.dsh-connect-settings .ds-btn{height:32px;padding:0 18px;border:none;border-radius:6px;background:var(--ds-accent);color:var(--ds-accent-fg);font:inherit;font-weight:500;cursor:pointer}
|
|
51
|
+
.dsh-connect-settings .ds-btn:disabled{opacity:.55;cursor:default}
|
|
52
|
+
.dsh-connect-settings .ds-status{font-size:12px;color:var(--ds-muted)}
|
|
53
|
+
`;
|
|
54
|
+
|
|
55
|
+
function injectStyles() {
|
|
56
|
+
if (typeof document === 'undefined') return;
|
|
57
|
+
if (document.getElementById('dsh-connect-settings-style')) return;
|
|
58
|
+
const el = document.createElement('style');
|
|
59
|
+
el.id = 'dsh-connect-settings-style';
|
|
60
|
+
el.textContent = STYLE;
|
|
61
|
+
document.head.appendChild(el);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Render a single config field control per its `kind` (text / number / boolean / select).
|
|
65
|
+
function renderConfigField(field, value, onChange) {
|
|
66
|
+
const label = field.label ?? field.key;
|
|
67
|
+
if (field.kind === 'boolean') {
|
|
68
|
+
return h('label', { className: 'ds-field ds-check-field', key: `cfg-${field.key}` },
|
|
69
|
+
h('input', { className: 'ds-check', type: 'checkbox', checked: !!value, onChange: (e) => onChange(e.target.checked) }),
|
|
70
|
+
' ' + label);
|
|
71
|
+
}
|
|
72
|
+
if (field.kind === 'select') {
|
|
73
|
+
return h('label', { className: 'ds-field', key: `cfg-${field.key}` },
|
|
74
|
+
label + ' ',
|
|
75
|
+
h('select', { className: 'ds-input', value: value ?? '', onChange: (e) => onChange(e.target.value) },
|
|
76
|
+
h('option', { value: '' }, '(default)'),
|
|
77
|
+
...(field.options ?? []).map((opt) => h('option', { key: opt, value: opt }, opt))));
|
|
78
|
+
}
|
|
79
|
+
if (field.kind === 'number') {
|
|
80
|
+
return h('label', { className: 'ds-field', key: `cfg-${field.key}` },
|
|
81
|
+
label + ' ',
|
|
82
|
+
h('input', { className: 'ds-input', type: 'number', value: value ?? '', onChange: (e) => onChange(e.target.value) }));
|
|
83
|
+
}
|
|
84
|
+
return h('label', { className: 'ds-field', key: `cfg-${field.key}` },
|
|
85
|
+
label + ' ',
|
|
86
|
+
h('input', { className: 'ds-input', type: 'text', value: value ?? '', onChange: (e) => onChange(e.target.value) }));
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Thin React renderer over the tested settings-model helpers.
|
|
90
|
+
export function ConnectSettingsTab({ rpcCall, t }) {
|
|
91
|
+
const [form, setForm] = React.useState(null);
|
|
92
|
+
const [status, setStatus] = React.useState('loading');
|
|
93
|
+
const [creds, setCreds] = React.useState({});
|
|
94
|
+
const rpc = (endpoint, payload) => rpcCall(endpoint, payload);
|
|
95
|
+
|
|
96
|
+
React.useEffect(() => {
|
|
97
|
+
let alive = true;
|
|
98
|
+
loadSettings(rpc).then((snap) => {
|
|
99
|
+
if (!alive) return;
|
|
100
|
+
setForm(snapshotToForm(snap));
|
|
101
|
+
setCreds(snap.credentials ?? {});
|
|
102
|
+
setStatus('idle');
|
|
103
|
+
}).catch(() => alive && setStatus('error'));
|
|
104
|
+
return () => { alive = false; };
|
|
105
|
+
}, [rpcCall]);
|
|
106
|
+
|
|
107
|
+
const onSave = async () => {
|
|
108
|
+
if (!form) return;
|
|
109
|
+
setStatus('saving');
|
|
110
|
+
try {
|
|
111
|
+
await saveSettings(rpc, buildConfigSave(form));
|
|
112
|
+
for (const c of buildCredentialSaves(form)) await saveCredentials(rpc, c.channel, c.values);
|
|
113
|
+
setStatus('saved');
|
|
114
|
+
} catch { setStatus('error'); }
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
const setChannels = (ch, on) => setForm((f) => ({ ...f, channels: on ? [...f.channels, ch] : f.channels.filter((x) => x !== ch) }));
|
|
118
|
+
const setField = (ch, field, value) => setForm((f) => ({ ...f, secrets: { ...f.secrets, [ch]: { ...(f.secrets[ch] ?? {}), [field]: value } } }));
|
|
119
|
+
const setChannelConfig = (ch, key, raw) => setForm((f) => {
|
|
120
|
+
const descriptor = (CHANNEL_CONFIG_FIELDS[ch] ?? []).find((x) => x.key === key);
|
|
121
|
+
const value = coerceConfigValue(descriptor?.kind ?? 'text', raw);
|
|
122
|
+
const cfg = { ...(f.channelConfigs[ch] ?? {}) };
|
|
123
|
+
if (value === undefined) delete cfg[key]; else cfg[key] = value;
|
|
124
|
+
return { ...f, channelConfigs: { ...f.channelConfigs, [ch]: cfg } };
|
|
125
|
+
});
|
|
126
|
+
const setDefault = (key, raw) => setForm((f) => {
|
|
127
|
+
const descriptor = CHANNEL_DEFAULT_FIELDS.find((x) => x.key === key);
|
|
128
|
+
const value = coerceConfigValue(descriptor?.kind ?? 'text', raw);
|
|
129
|
+
const defaults = { ...(f.channelDefaults ?? {}) };
|
|
130
|
+
if (value === undefined) delete defaults[key]; else defaults[key] = value;
|
|
131
|
+
return { ...f, channelDefaults: defaults };
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
if (!form) return h('div', { className: 'dsh-connect-settings' }, t('loading'));
|
|
135
|
+
return h('div', { className: 'dsh-connect-settings' },
|
|
136
|
+
h('section', { className: 'ds-card' },
|
|
137
|
+
h('h4', { className: 'ds-card-title' }, t('channels')),
|
|
138
|
+
// One card per built-in channel: enable toggle + cred badge + secret + config fields.
|
|
139
|
+
...Object.keys(CHANNEL_SECRET_FIELDS).map((ch) => h('fieldset', { className: 'ds-channel', key: ch },
|
|
140
|
+
h('legend', { className: 'ds-legend' },
|
|
141
|
+
h('label', null,
|
|
142
|
+
h('input', { className: 'ds-check', type: 'checkbox', checked: form.channels.includes(ch), onChange: (e) => setChannels(ch, e.target.checked) }),
|
|
143
|
+
h('span', null, ch),
|
|
144
|
+
h('span', { className: 'ds-badge' }, creds[ch] ? t('reachable') : t('unreachable'))),
|
|
145
|
+
),
|
|
146
|
+
h('div', { className: 'ds-fields' },
|
|
147
|
+
...CHANNEL_SECRET_FIELDS[ch].map((field) => h('label', { className: 'ds-field', key: `sec-${field}` },
|
|
148
|
+
field,
|
|
149
|
+
h('input', { className: 'ds-input', type: 'password', placeholder: field, value: form.secrets?.[ch]?.[field] ?? '', onChange: (e) => setField(ch, field, e.target.value) }))),
|
|
150
|
+
...(CHANNEL_CONFIG_FIELDS[ch] ?? []).map((field) => renderConfigField(field, form.channelConfigs?.[ch]?.[field.key], (raw) => setChannelConfig(ch, field.key, raw))),
|
|
151
|
+
),
|
|
152
|
+
)),
|
|
153
|
+
),
|
|
154
|
+
h('section', { className: 'ds-card' },
|
|
155
|
+
h('h4', { className: 'ds-card-title' }, t('defaults')),
|
|
156
|
+
h('div', { className: 'ds-fields' },
|
|
157
|
+
...CHANNEL_DEFAULT_FIELDS.map((field) => renderConfigField(field, form.channelDefaults?.[field.key], (raw) => setDefault(field.key, raw))),
|
|
158
|
+
h('label', { className: 'ds-field' }, t('statePath'),
|
|
159
|
+
h('input', { className: 'ds-input', value: form.settingsStatePath ?? '', onChange: (e) => setForm((f) => ({ ...f, settingsStatePath: e.target.value })) })),
|
|
160
|
+
),
|
|
161
|
+
h('div', { className: 'ds-actions' },
|
|
162
|
+
h('button', { className: 'ds-btn', onClick: onSave, disabled: status === 'saving' }, t('save')),
|
|
163
|
+
h('span', { className: 'ds-status' }, status === 'saved' ? t('saved') : status === 'error' ? t('error') : status),
|
|
164
|
+
),
|
|
165
|
+
),
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export function apply(ctx) {
|
|
170
|
+
injectStyles();
|
|
171
|
+
ctx.effect(() => ctx.locale.register(NS, locale), 'dsh-connect: locale');
|
|
172
|
+
const t = ctx.locale.bind(NS);
|
|
173
|
+
const rpcCall = (endpoint, payload, signal) => ctx.connection.rpc.call(SETTINGS_RPC_CHANNEL, endpoint, payload, signal);
|
|
174
|
+
ctx.effect(() => ctx.slots.inject('settings.section', () => ctx.slots.register({
|
|
175
|
+
name: 'settings.section', id: 'dsh-connect', order: 20, label: () => t('title'), locale: NS, inject: () => ({ rpcCall, t }),
|
|
176
|
+
}, ConnectSettingsTab)), 'dsh-connect: settings.section');
|
|
177
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DingTalk stream-mode channel adapter: bidirectional bot over the stream
|
|
3
|
+
* gateway. Inbound messages arrive via STOMP push, replies are SENT back to
|
|
4
|
+
* the triggering message's msgId.
|
|
5
|
+
*
|
|
6
|
+
* Capability notes (honest scope):
|
|
7
|
+
* - Group replies require an @-mention (requireMention, default true) — the
|
|
8
|
+
* gateway pushes every group message, so the adapter gates on isInAtList.
|
|
9
|
+
* - Interactive menus render as a numbered text list: the user answers with a
|
|
10
|
+
* number and the adapter resolves the choice. Real action-card buttons are
|
|
11
|
+
* out of scope for this release.
|
|
12
|
+
* - `streamText` accumulates the full reply and sends it once (the stream
|
|
13
|
+
* gateway has no progressive card editing).
|
|
14
|
+
* - Proactive pushes (reminders / broadcasts) still use the webhook service;
|
|
15
|
+
* the stream adapter can only reply to inbound messages.
|
|
16
|
+
* @module dsh-connect/channels/dingtalk/adapter
|
|
17
|
+
*/
|
|
18
|
+
import type { ChannelAdapter, ChoicePrompt, ChoiceResult, InboundMessage, OutboundTarget, SummaryCard } from "../../types.js";
|
|
19
|
+
import type { Language } from "../../i18n.js";
|
|
20
|
+
export interface DingtalkStreamAdapterConfig {
|
|
21
|
+
clientId: string;
|
|
22
|
+
clientSecret: string;
|
|
23
|
+
/** Gateway URL override (default wss://api.dingtalk.com/connect). */
|
|
24
|
+
url?: string;
|
|
25
|
+
/** Group messages must @-mention the bot (default true). */
|
|
26
|
+
requireMention?: boolean;
|
|
27
|
+
language?: Language;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Bidirectional DingTalk adapter (stream mode). Deliberately text-forward:
|
|
31
|
+
* no card editing, no button callbacks — just normalized text both ways.
|
|
32
|
+
*/
|
|
33
|
+
export declare class DingtalkStreamAdapter implements ChannelAdapter {
|
|
34
|
+
readonly id = "dingtalk";
|
|
35
|
+
private readonly client;
|
|
36
|
+
private readonly t;
|
|
37
|
+
private readonly requireMention;
|
|
38
|
+
private handler?;
|
|
39
|
+
private readonly pendingMenus;
|
|
40
|
+
constructor(config: DingtalkStreamAdapterConfig, logger?: {
|
|
41
|
+
warn?: (...args: unknown[]) => void;
|
|
42
|
+
});
|
|
43
|
+
start(): Promise<void>;
|
|
44
|
+
stop(): Promise<void>;
|
|
45
|
+
onInbound(handler: (msg: InboundMessage) => void | Promise<void>): void;
|
|
46
|
+
/** Gate + normalize + menu-answer routing, then hand to the core. */
|
|
47
|
+
private dispatch;
|
|
48
|
+
/**
|
|
49
|
+
* A pending text menu is answered when the user replies with a number.
|
|
50
|
+
* Returns true when the message was consumed as a menu answer.
|
|
51
|
+
*/
|
|
52
|
+
private tryResolveMenu;
|
|
53
|
+
sendText(target: OutboundTarget, text: string): Promise<void>;
|
|
54
|
+
sendCard(target: OutboundTarget, card: SummaryCard): Promise<void>;
|
|
55
|
+
streamText(target: OutboundTarget, chunks: AsyncIterable<string>): Promise<void>;
|
|
56
|
+
promptChoice(target: OutboundTarget, prompt: ChoicePrompt, _updateMessageId?: string): Promise<ChoiceResult>;
|
|
57
|
+
closeMenu(_messageId: string, _summary: string): Promise<void>;
|
|
58
|
+
}
|
|
59
|
+
//# sourceMappingURL=adapter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../../../src/channels/dingtalk/adapter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AACH,OAAO,KAAK,EACV,cAAc,EAEd,YAAY,EACZ,YAAY,EACZ,cAAc,EACd,cAAc,EACd,WAAW,EACZ,MAAM,gBAAgB,CAAC;AACxB,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAW9C,MAAM,WAAW,2BAA2B;IAC1C,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,qEAAqE;IACrE,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,4DAA4D;IAC5D,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,QAAQ,CAAC,EAAE,QAAQ,CAAC;CACrB;AAYD;;;GAGG;AACH,qBAAa,qBAAsB,YAAW,cAAc;IAC1D,QAAQ,CAAC,EAAE,cAAc;IACzB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAuB;IAC9C,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAmB;IACrC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAU;IACzC,OAAO,CAAC,OAAO,CAAC,CAAgD;IAChE,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAkC;gBAEnD,MAAM,EAAE,2BAA2B,EAAE,MAAM,CAAC,EAAE;QAAE,IAAI,CAAC,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,IAAI,CAAA;KAAE;IAW3F,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAOtB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAI3B,SAAS,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,cAAc,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI;IAIvE,qEAAqE;YACvD,QAAQ;IAQtB;;;OAGG;IACH,OAAO,CAAC,cAAc;IAgBhB,QAAQ,CAAC,MAAM,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAK7D,QAAQ,CAAC,MAAM,EAAE,cAAc,EAAE,IAAI,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IAKlE,UAAU,CAAC,MAAM,EAAE,cAAc,EAAE,MAAM,EAAE,aAAa,CAAC,MAAM,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAOhF,YAAY,CAAC,MAAM,EAAE,cAAc,EAAE,MAAM,EAAE,YAAY,EAAE,gBAAgB,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC;IA4B5G,SAAS,CAAC,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAIrE"}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { DingtalkStreamClient } from "./stream.js";
|
|
2
|
+
import { buildMarkdownReplyBody, buildTextReplyBody, isAtMentioned, normalizeBotMessage, } from "./message.js";
|
|
3
|
+
import { dingtalkMessages } from "./i18n.js";
|
|
4
|
+
const CHOICE_TIMEOUT_MS = 60_000;
|
|
5
|
+
/**
|
|
6
|
+
* Bidirectional DingTalk adapter (stream mode). Deliberately text-forward:
|
|
7
|
+
* no card editing, no button callbacks — just normalized text both ways.
|
|
8
|
+
*/
|
|
9
|
+
export class DingtalkStreamAdapter {
|
|
10
|
+
id = "dingtalk";
|
|
11
|
+
client;
|
|
12
|
+
t;
|
|
13
|
+
requireMention;
|
|
14
|
+
handler;
|
|
15
|
+
pendingMenus = new Map();
|
|
16
|
+
constructor(config, logger) {
|
|
17
|
+
this.requireMention = config.requireMention ?? true;
|
|
18
|
+
this.t = dingtalkMessages(config.language ?? "zh");
|
|
19
|
+
this.client = new DingtalkStreamClient({
|
|
20
|
+
clientId: config.clientId,
|
|
21
|
+
clientSecret: config.clientSecret,
|
|
22
|
+
url: config.url,
|
|
23
|
+
logger,
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
async start() {
|
|
27
|
+
this.client.onInbound((raw) => {
|
|
28
|
+
void this.dispatch(raw).catch(() => undefined);
|
|
29
|
+
});
|
|
30
|
+
await this.client.connect();
|
|
31
|
+
}
|
|
32
|
+
async stop() {
|
|
33
|
+
this.client.close();
|
|
34
|
+
}
|
|
35
|
+
onInbound(handler) {
|
|
36
|
+
this.handler = handler;
|
|
37
|
+
}
|
|
38
|
+
/** Gate + normalize + menu-answer routing, then hand to the core. */
|
|
39
|
+
async dispatch(raw) {
|
|
40
|
+
if (this.requireMention && raw.conversationType === "2" && !isAtMentioned(raw))
|
|
41
|
+
return;
|
|
42
|
+
const msg = normalizeBotMessage(raw);
|
|
43
|
+
if (msg === undefined)
|
|
44
|
+
return;
|
|
45
|
+
if (this.tryResolveMenu(msg.chatKey, msg.text))
|
|
46
|
+
return;
|
|
47
|
+
await this.handler?.(msg);
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* A pending text menu is answered when the user replies with a number.
|
|
51
|
+
* Returns true when the message was consumed as a menu answer.
|
|
52
|
+
*/
|
|
53
|
+
tryResolveMenu(conversationId, text) {
|
|
54
|
+
if (this.pendingMenus.size === 0)
|
|
55
|
+
return false;
|
|
56
|
+
const index = Number(text.trim());
|
|
57
|
+
if (!Number.isFinite(index))
|
|
58
|
+
return false;
|
|
59
|
+
for (const [messageId, pending] of this.pendingMenus) {
|
|
60
|
+
if (pending.conversationId !== conversationId)
|
|
61
|
+
continue;
|
|
62
|
+
const option = pending.options[index - 1];
|
|
63
|
+
if (option === undefined)
|
|
64
|
+
return false;
|
|
65
|
+
clearTimeout(pending.timer);
|
|
66
|
+
this.pendingMenus.delete(messageId);
|
|
67
|
+
pending.resolve(option.id);
|
|
68
|
+
return true;
|
|
69
|
+
}
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
async sendText(target, text) {
|
|
73
|
+
if (target.replyRef === undefined)
|
|
74
|
+
return; // proactive push → webhook service
|
|
75
|
+
await this.client.sendReply(target.replyRef, buildTextReplyBody(target.replyRef, text));
|
|
76
|
+
}
|
|
77
|
+
async sendCard(target, card) {
|
|
78
|
+
if (target.replyRef === undefined)
|
|
79
|
+
return;
|
|
80
|
+
await this.client.sendReply(target.replyRef, buildMarkdownReplyBody(target.replyRef, this.t.cardTitle, card.markdown));
|
|
81
|
+
}
|
|
82
|
+
async streamText(target, chunks) {
|
|
83
|
+
let full = "";
|
|
84
|
+
for await (const chunk of chunks)
|
|
85
|
+
full += chunk;
|
|
86
|
+
if (full.trim() === "" || target.replyRef === undefined)
|
|
87
|
+
return;
|
|
88
|
+
await this.client.sendReply(target.replyRef, buildTextReplyBody(target.replyRef, full));
|
|
89
|
+
}
|
|
90
|
+
async promptChoice(target, prompt, _updateMessageId) {
|
|
91
|
+
const lines = [prompt.title, ""];
|
|
92
|
+
prompt.options.forEach((option, i) => lines.push(`${i + 1}. ${option.label}`));
|
|
93
|
+
if (prompt.footer !== undefined)
|
|
94
|
+
lines.push("", prompt.footer);
|
|
95
|
+
const text = lines.join("\n");
|
|
96
|
+
if (target.replyRef !== undefined) {
|
|
97
|
+
await this.client.sendReply(target.replyRef, buildTextReplyBody(target.replyRef, text));
|
|
98
|
+
}
|
|
99
|
+
const messageId = `${target.chatKey}:${Date.now()}`;
|
|
100
|
+
const result = await new Promise((resolve) => {
|
|
101
|
+
const timer = setTimeout(() => {
|
|
102
|
+
this.pendingMenus.delete(messageId);
|
|
103
|
+
resolve({ choice: undefined, messageId });
|
|
104
|
+
}, CHOICE_TIMEOUT_MS);
|
|
105
|
+
this.pendingMenus.set(messageId, {
|
|
106
|
+
conversationId: target.chatKey,
|
|
107
|
+
options: [...prompt.options],
|
|
108
|
+
timer,
|
|
109
|
+
resolve: (choice) => {
|
|
110
|
+
this.pendingMenus.delete(messageId);
|
|
111
|
+
clearTimeout(timer);
|
|
112
|
+
resolve({ choice, messageId });
|
|
113
|
+
},
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
return result;
|
|
117
|
+
}
|
|
118
|
+
async closeMenu(_messageId, _summary) {
|
|
119
|
+
// Text menus have no card to replace; a completion notice is unnecessary
|
|
120
|
+
// because the menu prompt is just a numbered list in the thread.
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
//# sourceMappingURL=adapter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"adapter.js","sourceRoot":"","sources":["../../../src/channels/dingtalk/adapter.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DingTalk-adapter user-facing strings (zh/en).
|
|
3
|
+
* @module dsh-connect/channels/dingtalk/i18n
|
|
4
|
+
*/
|
|
5
|
+
import type { Language } from "../../i18n.js";
|
|
6
|
+
export interface DingtalkMessages {
|
|
7
|
+
webhookMissing: string;
|
|
8
|
+
webhookInvalid: string;
|
|
9
|
+
sendFailed(error: string): string;
|
|
10
|
+
/** Title used by markdown replies (stream mode). */
|
|
11
|
+
cardTitle: string;
|
|
12
|
+
}
|
|
13
|
+
export declare function dingtalkMessages(lang: Language): DingtalkMessages;
|
|
14
|
+
//# sourceMappingURL=i18n.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"i18n.d.ts","sourceRoot":"","sources":["../../../src/channels/dingtalk/i18n.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAE9C,MAAM,WAAW,gBAAgB;IAC/B,cAAc,EAAE,MAAM,CAAC;IACvB,cAAc,EAAE,MAAM,CAAC;IACvB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC;IAClC,oDAAoD;IACpD,SAAS,EAAE,MAAM,CAAC;CACnB;AAgBD,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,QAAQ,GAAG,gBAAgB,CAEjE"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
const zh = {
|
|
2
|
+
webhookMissing: "connect-dingtalk: 未配置 webhookUrl(钉钉群自定义机器人的 Webhook 地址),无法推送。",
|
|
3
|
+
webhookInvalid: "connect-dingtalk: webhookUrl 必须是 https:// 开头的钉钉机器人地址。",
|
|
4
|
+
sendFailed: (error) => `connect-dingtalk: 推送失败 — ${error}`,
|
|
5
|
+
cardTitle: "DSH 任务摘要",
|
|
6
|
+
};
|
|
7
|
+
const en = {
|
|
8
|
+
webhookMissing: "connect-dingtalk: webhookUrl is not configured (the DingTalk group custom robot's webhook URL) — nothing to push to.",
|
|
9
|
+
webhookInvalid: "connect-dingtalk: webhookUrl must be an https:// DingTalk robot URL.",
|
|
10
|
+
sendFailed: (error) => `connect-dingtalk: push failed — ${error}`,
|
|
11
|
+
cardTitle: "DSH Summary",
|
|
12
|
+
};
|
|
13
|
+
export function dingtalkMessages(lang) {
|
|
14
|
+
return lang === "en" ? en : zh;
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=i18n.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"i18n.js","sourceRoot":"","sources":["../../../src/channels/dingtalk/i18n.ts"],"names":[],"mappings":"AAcA,MAAM,EAAE,GAAqB;IAC3B,cAAc,EAAE,+DAA+D;IAC/E,cAAc,EAAE,uDAAuD;IACvE,UAAU,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,4BAA4B,KAAK,EAAE;IAC1D,SAAS,EAAE,UAAU;CACtB,CAAC;AAEF,MAAM,EAAE,GAAqB;IAC3B,cAAc,EAAE,sHAAsH;IACtI,cAAc,EAAE,sEAAsE;IACtF,UAAU,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,mCAAmC,KAAK,EAAE;IACjE,SAAS,EAAE,aAAa;CACzB,CAAC;AAEF,MAAM,UAAU,gBAAgB,CAAC,IAAc;IAC7C,OAAO,IAAI,KAAK,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;AACjC,CAAC"}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DingTalk channel for DeepSeek Harness — two modes:
|
|
3
|
+
*
|
|
4
|
+
* 1. Webhook push (one-way): the DingTalk group custom robot (群自定义机器人)
|
|
5
|
+
* webhook can send text / markdown / @mention messages into a group. This
|
|
6
|
+
* exposes a push service (`ctx.dingtalk`) for task progress, results and
|
|
7
|
+
* alerts.
|
|
8
|
+
* 2. Stream mode (bidirectional, zero dependencies): with `stream.clientId`
|
|
9
|
+
* / `stream.clientSecret` set, a STOMP-over-WebSocket adapter is
|
|
10
|
+
* registered into `dsh-connect`, so group @-mentions / DMs trigger the
|
|
11
|
+
* agent and replies stream back. Menus are numbered text lists (the user
|
|
12
|
+
* answers with a number); proactive pushes still use the webhook service.
|
|
13
|
+
*
|
|
14
|
+
* @module dsh-connect/dingtalk
|
|
15
|
+
*/
|
|
16
|
+
import { Service, type Context } from "@deepseek-ai/cordis";
|
|
17
|
+
import z from "@deepseek-ai/schemastery";
|
|
18
|
+
import { type DingtalkAt } from "./webhook.js";
|
|
19
|
+
import { type DingtalkStreamAdapterConfig } from "./adapter.js";
|
|
20
|
+
export { DingtalkWebhook, signDingtalk, verifyDingtalkSignature } from "./webhook.js";
|
|
21
|
+
export type { DingtalkAt, DingtalkBody, DingtalkResponse, DingtalkWebhookConfig } from "./webhook.js";
|
|
22
|
+
export { dingtalkMessages } from "./i18n.js";
|
|
23
|
+
export type { DingtalkMessages } from "./i18n.js";
|
|
24
|
+
export { DingtalkStreamAdapter } from "./adapter.js";
|
|
25
|
+
export type { DingtalkStreamAdapterConfig } from "./adapter.js";
|
|
26
|
+
export { DingtalkStreamClient, DEFAULT_STREAM_URL } from "./stream.js";
|
|
27
|
+
export { encodeFrame, decodeFrames, escapeHeader, unescapeHeader } from "./stomp.js";
|
|
28
|
+
export type { StompFrame } from "./stomp.js";
|
|
29
|
+
export { buildConnectBody, buildMarkdownReplyBody, buildTextReplyBody, isAtMentioned, normalizeBotMessage, INBOUND_DESTINATION, REPLY_DESTINATION, } from "./message.js";
|
|
30
|
+
export type { DingtalkBotMessage } from "./message.js";
|
|
31
|
+
/** Plugin config; secrets may come from config or the DINGTALK_* environment. */
|
|
32
|
+
export declare const Config: z<Schemastery.ObjectS<{
|
|
33
|
+
webhookUrl: z<string, string>;
|
|
34
|
+
secret: z<string, string>;
|
|
35
|
+
language: z<"zh" | "en", "zh" | "en">;
|
|
36
|
+
/** Optional default @-mentions for every push (mobiles / user ids / all). */
|
|
37
|
+
defaultAt: z<Schemastery.ObjectS<{
|
|
38
|
+
mobiles: z<string[], string[]>;
|
|
39
|
+
userIds: z<string[], string[]>;
|
|
40
|
+
all: z<boolean, boolean>;
|
|
41
|
+
}>, Schemastery.ObjectT<{
|
|
42
|
+
mobiles: z<string[], string[]>;
|
|
43
|
+
userIds: z<string[], string[]>;
|
|
44
|
+
all: z<boolean, boolean>;
|
|
45
|
+
}>>;
|
|
46
|
+
/** Stream mode (bidirectional): the DingTalk app's Client ID / Client Secret. */
|
|
47
|
+
stream: z<Schemastery.ObjectS<{
|
|
48
|
+
clientId: z<string, string>;
|
|
49
|
+
clientSecret: z<string, string>;
|
|
50
|
+
url: z<string, string>;
|
|
51
|
+
requireMention: z<boolean, boolean>;
|
|
52
|
+
}>, Schemastery.ObjectT<{
|
|
53
|
+
clientId: z<string, string>;
|
|
54
|
+
clientSecret: z<string, string>;
|
|
55
|
+
url: z<string, string>;
|
|
56
|
+
requireMention: z<boolean, boolean>;
|
|
57
|
+
}>>;
|
|
58
|
+
}>, Schemastery.ObjectT<{
|
|
59
|
+
webhookUrl: z<string, string>;
|
|
60
|
+
secret: z<string, string>;
|
|
61
|
+
language: z<"zh" | "en", "zh" | "en">;
|
|
62
|
+
/** Optional default @-mentions for every push (mobiles / user ids / all). */
|
|
63
|
+
defaultAt: z<Schemastery.ObjectS<{
|
|
64
|
+
mobiles: z<string[], string[]>;
|
|
65
|
+
userIds: z<string[], string[]>;
|
|
66
|
+
all: z<boolean, boolean>;
|
|
67
|
+
}>, Schemastery.ObjectT<{
|
|
68
|
+
mobiles: z<string[], string[]>;
|
|
69
|
+
userIds: z<string[], string[]>;
|
|
70
|
+
all: z<boolean, boolean>;
|
|
71
|
+
}>>;
|
|
72
|
+
/** Stream mode (bidirectional): the DingTalk app's Client ID / Client Secret. */
|
|
73
|
+
stream: z<Schemastery.ObjectS<{
|
|
74
|
+
clientId: z<string, string>;
|
|
75
|
+
clientSecret: z<string, string>;
|
|
76
|
+
url: z<string, string>;
|
|
77
|
+
requireMention: z<boolean, boolean>;
|
|
78
|
+
}>, Schemastery.ObjectT<{
|
|
79
|
+
clientId: z<string, string>;
|
|
80
|
+
clientSecret: z<string, string>;
|
|
81
|
+
url: z<string, string>;
|
|
82
|
+
requireMention: z<boolean, boolean>;
|
|
83
|
+
}>>;
|
|
84
|
+
}>>;
|
|
85
|
+
export interface DingtalkConfig {
|
|
86
|
+
webhookUrl?: string;
|
|
87
|
+
secret?: string;
|
|
88
|
+
language?: "zh" | "en";
|
|
89
|
+
defaultAt?: Partial<DingtalkAt>;
|
|
90
|
+
/** Stream mode (bidirectional) credentials; secrets also via DINGTALK_STREAM_CLIENT_ID / _SECRET. */
|
|
91
|
+
stream?: Partial<DingtalkStreamAdapterConfig>;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* The push service exposed on the Cordis context. Other plugins can inject
|
|
95
|
+
* `dingtalk` and call `sendMarkdown` / `sendText` to deliver into the group.
|
|
96
|
+
*/
|
|
97
|
+
export declare class DingtalkService extends Service {
|
|
98
|
+
private readonly webhook;
|
|
99
|
+
private readonly t;
|
|
100
|
+
readonly defaultAt?: Partial<DingtalkAt>;
|
|
101
|
+
constructor(ctx: Context, config: DingtalkConfig, logger?: {
|
|
102
|
+
warn?: (...args: unknown[]) => void;
|
|
103
|
+
});
|
|
104
|
+
/** Push a markdown card into the DingTalk group. */
|
|
105
|
+
sendMarkdown(title: string, text: string, at?: DingtalkAt): Promise<void>;
|
|
106
|
+
/** Push plain text into the DingTalk group. */
|
|
107
|
+
sendText(content: string, at?: DingtalkAt): Promise<void>;
|
|
108
|
+
}
|
|
109
|
+
interface ConnectLike {
|
|
110
|
+
registerAdapter(adapter: unknown): void;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Register and start the DingTalk channel. `connect` may be undefined: when the
|
|
114
|
+
* stream credentials are absent, or when `connect` is not provided, DingTalk
|
|
115
|
+
* degrades to one-way webhook push (`ctx.dingtalk`). The webhook service is
|
|
116
|
+
* always constructed. `connect` is the already-constructed `ConnectService`
|
|
117
|
+
* instance (passed from the merged entry, not resolved via `ctx.get`).
|
|
118
|
+
*/
|
|
119
|
+
export declare function register(connect: ConnectLike | undefined, config: (DingtalkConfig | null) | undefined, ctx: Context): void;
|
|
120
|
+
/**
|
|
121
|
+
* Compatibility shell for the old split-plugin registration path (when `connect`
|
|
122
|
+
* is an already-active, separate plugin). The merged entry calls `register`
|
|
123
|
+
* directly instead, because `ctx.get("connect")` returns undefined inside this
|
|
124
|
+
* plugin's own `apply`.
|
|
125
|
+
*/
|
|
126
|
+
export declare function apply(ctx: Context, config?: DingtalkConfig | null): void;
|
|
127
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/channels/dingtalk/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,EAAE,OAAO,EAAE,KAAK,OAAO,EAAE,MAAM,qBAAqB,CAAC;AAC5D,OAAO,CAAC,MAAM,0BAA0B,CAAC;AACzC,OAAO,EAAmB,KAAK,UAAU,EAAE,MAAM,cAAc,CAAC;AAEhE,OAAO,EAAyB,KAAK,2BAA2B,EAAE,MAAM,cAAc,CAAC;AAEvF,OAAO,EAAE,eAAe,EAAE,YAAY,EAAE,uBAAuB,EAAE,MAAM,cAAc,CAAC;AACtF,YAAY,EAAE,UAAU,EAAE,YAAY,EAAE,gBAAgB,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAC;AACtG,OAAO,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAC7C,YAAY,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAClD,OAAO,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAC;AACrD,YAAY,EAAE,2BAA2B,EAAE,MAAM,cAAc,CAAC;AAChE,OAAO,EAAE,oBAAoB,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AACvE,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AACrF,YAAY,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAC7C,OAAO,EACL,gBAAgB,EAChB,sBAAsB,EACtB,kBAAkB,EAClB,aAAa,EACb,mBAAmB,EACnB,mBAAmB,EACnB,iBAAiB,GAClB,MAAM,cAAc,CAAC;AACtB,YAAY,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAEvD,iFAAiF;AACjF,eAAO,MAAM,MAAM;;;;IAIjB,6EAA6E;;;;;;;;;;IAM7E,iFAAiF;;;;;;;;;;;;;;;;IANjF,6EAA6E;;;;;;;;;;IAM7E,iFAAiF;;;;;;;;;;;;GAOjF,CAAC;AAEH,MAAM,WAAW,cAAc;IAC7B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC;IACvB,SAAS,CAAC,EAAE,OAAO,CAAC,UAAU,CAAC,CAAC;IAChC,qGAAqG;IACrG,MAAM,CAAC,EAAE,OAAO,CAAC,2BAA2B,CAAC,CAAC;CAC/C;AAED;;;GAGG;AACH,qBAAa,eAAgB,SAAQ,OAAO;IAC1C,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAkB;IAC1C,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAmB;IACrC,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,CAAC,UAAU,CAAC,CAAC;gBAE7B,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,CAAC,EAAE;QAAE,IAAI,CAAC,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,IAAI,CAAA;KAAE;IAmBlG,oDAAoD;IAC9C,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,CAAC,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC;IAS/E,+CAA+C;IACzC,QAAQ,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,CAAC,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC;CAQhE;AAED,UAAU,WAAW;IACnB,eAAe,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAAC;CACzC;AAED;;;;;;GAMG;AACH,wBAAgB,QAAQ,CAAC,OAAO,EAAE,WAAW,GAAG,SAAS,EAAE,MAAM,GAAE,cAAc,GAAG,IAAI,aAAK,EAAE,GAAG,EAAE,OAAO,GAAG,IAAI,CAkCjH;AAED;;;;;GAKG;AACH,wBAAgB,KAAK,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,GAAE,cAAc,GAAG,IAAS,GAAG,IAAI,CAG5E"}
|