dsh-feishu-notifier 0.1.2 → 0.1.4
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 +7 -1
- package/lib/client.js +34 -7
- package/lib/client.js.map +1 -1
- package/lib/index.js +5 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,12 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
一个 DeepSeek Harness Bundle:当智能体请求用户批准、等待用户回答问题或确认计划,以及一轮对话结束时,向飞书机器人发送文本通知。
|
|
4
4
|
|
|
5
|
+

|
|
6
|
+
|
|
5
7
|
## 使用要求
|
|
6
8
|
|
|
7
9
|
- DeepSeek Harness `0.1.0-rc.5` 或兼容版本。
|
|
8
10
|
- Node.js `22.19+`。
|
|
9
11
|
- 在 Web 设置中配置飞书自定义机器人 Webhook。
|
|
10
12
|
|
|
13
|
+
|
|
11
14
|
## 本地开发
|
|
12
15
|
|
|
13
16
|
```sh
|
|
@@ -45,12 +48,15 @@ pnpm dsh --profile web
|
|
|
45
48
|
|
|
46
49
|
填写飞书机器人 Webhook,点击“保存 Webhook”,然后点击“发送测试消息”验证连接。配置会通过插件自己的 Host API 持久化到 DSH 的 `settings.yaml`,通常位于 `C:\Users\<用户名>\.dsh\settings.yaml`。真实 Webhook 不会包含在 npm 包中,每位用户都需要配置自己的地址。
|
|
47
50
|
|
|
51
|
+

|
|
52
|
+
|
|
53
|
+
|
|
48
54
|
## 从 GitHub 安装
|
|
49
55
|
|
|
50
56
|
建议使用 release tag 或 commit SHA,确保安装内容可复现:
|
|
51
57
|
|
|
52
58
|
```sh
|
|
53
|
-
pnpm dsh plugin --profile demo add github:blooming-fang/dsh-feishu-notifier
|
|
59
|
+
pnpm dsh plugin --profile demo add github:blooming-fang/dsh-feishu-notifier
|
|
54
60
|
```
|
|
55
61
|
|
|
56
62
|
从 GitHub 安装时会执行 `prepare` 构建插件。只应安装可信仓库中的代码;pnpm 可能要求在 Profile 的 workspace 配置中加入 `allowBuilds` 授权。
|
package/lib/client.js
CHANGED
|
@@ -94,7 +94,7 @@ window.__ModuleLoader__.load({
|
|
|
94
94
|
borderRadius: 16
|
|
95
95
|
},
|
|
96
96
|
children: [
|
|
97
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("
|
|
97
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
98
98
|
style: {
|
|
99
99
|
display: "flex",
|
|
100
100
|
alignItems: "center",
|
|
@@ -107,14 +107,41 @@ window.__ModuleLoader__.load({
|
|
|
107
107
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("small", {
|
|
108
108
|
style: { color: "var(--dsw-alias-label-secondary)" },
|
|
109
109
|
children: "关闭后不会发送任何飞书消息。"
|
|
110
|
-
})] }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("
|
|
110
|
+
})] }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
111
|
+
type: "button",
|
|
112
|
+
role: "switch",
|
|
113
|
+
"aria-checked": config.enabled,
|
|
111
114
|
"aria-label": "启用通知",
|
|
112
|
-
type: "checkbox",
|
|
113
|
-
checked: config.enabled,
|
|
114
115
|
disabled: busy || view.status === "loading",
|
|
115
|
-
|
|
116
|
-
save({ enabled:
|
|
117
|
-
}
|
|
116
|
+
onClick: () => {
|
|
117
|
+
save({ enabled: !config.enabled }, !config.enabled ? "通知已开启" : "通知已关闭");
|
|
118
|
+
},
|
|
119
|
+
style: {
|
|
120
|
+
position: "relative",
|
|
121
|
+
width: 44,
|
|
122
|
+
height: 26,
|
|
123
|
+
padding: 2,
|
|
124
|
+
borderRadius: 999,
|
|
125
|
+
border: "1px solid var(--dsw-alias-border-l1)",
|
|
126
|
+
background: config.enabled ? "var(--dsw-alias-bg-accent, #165DFF)" : "var(--dsw-alias-bg-layer-3, #e5e7eb)",
|
|
127
|
+
cursor: busy || view.status === "loading" ? "not-allowed" : "pointer",
|
|
128
|
+
opacity: busy || view.status === "loading" ? .6 : 1,
|
|
129
|
+
transition: "background 150ms ease, opacity 150ms ease",
|
|
130
|
+
flexShrink: 0
|
|
131
|
+
},
|
|
132
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
133
|
+
"aria-hidden": true,
|
|
134
|
+
style: {
|
|
135
|
+
display: "block",
|
|
136
|
+
width: 20,
|
|
137
|
+
height: 20,
|
|
138
|
+
borderRadius: 999,
|
|
139
|
+
background: "#fff",
|
|
140
|
+
boxShadow: "0 1px 3px rgba(0,0,0,0.2)",
|
|
141
|
+
transform: config.enabled ? "translateX(18px)" : "translateX(0)",
|
|
142
|
+
transition: "transform 150ms ease"
|
|
143
|
+
}
|
|
144
|
+
})
|
|
118
145
|
})]
|
|
119
146
|
}),
|
|
120
147
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
package/lib/client.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.js","names":["useState","Button"],"sources":["../src/client/FeishuSettingsSection.tsx","../src/client/index.ts"],"sourcesContent":["import { useEffect, useState } from 'react'\nimport type { PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots'\nimport { Button } from '@deepseek-ai/dsh-client-ui-primitives'\n\nexport interface FeishuConfigView {\n enabled: boolean\n webhookConfigured: boolean\n}\n\nexport interface FeishuController {\n config?: FeishuConfigView\n load: () => Promise<FeishuConfigView>\n update: (patch: { enabled?: boolean; webhook?: string }) => Promise<FeishuConfigView>\n test: () => Promise<string>\n}\n\ntype Props = PropsRuntime<'settings.section'> & { controller: FeishuController }\n\ntype ViewState =\n | { status: 'loading' }\n | { status: 'ready'; config: FeishuConfigView }\n\nexport function FeishuSettingsSection({ controller }: Props) {\n const [view, setView] = useState<ViewState>({ status: 'loading' })\n const [webhook, setWebhook] = useState('')\n const [busy, setBusy] = useState(false)\n const [status, setStatus] = useState('')\n\n useEffect(() => {\n let active = true\n void controller.load()\n .then(config => {\n if (active) setView({ status: 'ready', config })\n })\n .catch(error => {\n if (active) setStatus(error instanceof Error ? error.message : String(error))\n })\n return () => { active = false }\n }, [controller])\n\n const config = view.status === 'ready' ? view.config : { enabled: true, webhookConfigured: false }\n\n const save = async (patch: { enabled?: boolean; webhook?: string }, message: string) => {\n setBusy(true)\n setStatus('正在保存…')\n try {\n const next = await controller.update(patch)\n setView({ status: 'ready', config: next })\n setStatus(message)\n } catch (error) {\n setStatus(error instanceof Error ? error.message : String(error))\n } finally {\n setBusy(false)\n }\n }\n\n const saveWebhook = async () => {\n const value = webhook.trim()\n if (value === '') {\n setStatus('请输入 Webhook 地址')\n return\n }\n await save({ webhook: value }, 'Webhook 已保存到 DSH 设置文件')\n setWebhook('')\n }\n\n const sendTest = async () => {\n setBusy(true)\n setStatus('正在发送测试消息…')\n try {\n setStatus(await controller.test())\n } catch (error) {\n setStatus(error instanceof Error ? error.message : String(error))\n } finally {\n setBusy(false)\n }\n }\n\n return (\n <section style={{ maxWidth: 680, display: 'grid', gap: 24 }}>\n <div>\n <h2 style={{ margin: 0, fontSize: 24 }}>飞书通知</h2>\n <p style={{ color: 'var(--dsw-alias-label-secondary)', lineHeight: 1.6 }}>\n 当智能体等待你的批准、需要回答问题,或一轮对话结束时,向飞书机器人发送提醒。\n </p>\n </div>\n <div style={{ display: 'grid', gap: 16, padding: 20, border: '1px solid var(--dsw-alias-border-l1)', borderRadius: 16 }}>\n <label style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 16 }}>\n <span>\n <strong style={{ display: 'block' }}>启用通知</strong>\n <small style={{ color: 'var(--dsw-alias-label-secondary)' }}>关闭后不会发送任何飞书消息。</small>\n </span>\n <input\n aria-label=\"启用通知\"\n type=\"checkbox\"\n checked={config.enabled}\n disabled={busy || view.status === 'loading'}\n onChange={event => { void save({ enabled: event.target.checked }, event.target.checked ? '通知已开启' : '通知已关闭') }}\n />\n </label>\n <label style={{ display: 'grid', gap: 8 }}>\n <span><strong>Webhook 地址</strong></span>\n <input\n aria-label=\"Webhook 地址\"\n type=\"url\"\n value={webhook}\n placeholder={config.webhookConfigured ? '已配置地址;输入新地址以替换' : '请输入飞书机器人 Webhook'}\n disabled={busy || view.status === 'loading'}\n onChange={event => { setWebhook(event.target.value); setStatus('') }}\n style={{ minHeight: 40, padding: '0 12px', borderRadius: 10, border: '1px solid var(--dsw-alias-border-l1)', background: 'var(--dsw-alias-bg-layer-1)', color: 'inherit' }}\n />\n </label>\n <div style={{ display: 'flex', flexWrap: 'wrap', gap: 10 }}>\n <Button variant=\"primary\" size=\"sm\" disabled={busy || view.status === 'loading'} onClick={() => { void saveWebhook() }}>保存 Webhook</Button>\n <Button variant=\"outline\" size=\"sm\" disabled={busy || view.status === 'loading' || !config.webhookConfigured} onClick={() => { void sendTest() }}>发送测试消息</Button>\n </div>\n {status !== '' && <p role=\"status\" style={{ margin: 0, color: 'var(--dsw-alias-label-secondary)' }}>{status}</p>}\n </div>\n </section>\n )\n}\n","import type { ClientContext } from '@deepseek-ai/dsh-client-runtime/client'\nimport type {} from '@deepseek-ai/dsh-client-ui-settings/client'\nimport type {} from '@deepseek-ai/dsh-client-ui-slots'\nimport { FeishuSettingsSection, type FeishuController } from './FeishuSettingsSection.tsx'\n\nexport const inject = ['slots']\n\nexport function apply(ctx: ClientContext): void {\n const controller: FeishuController = {\n load: async () => {\n const response = await fetch('/api/feishu-notifier/config')\n const result = await response.json() as { ok?: boolean; message?: string; config?: FeishuController['config'] }\n if (!response.ok || result.ok !== true || result.config === undefined) {\n throw new Error(result.message ?? `HTTP ${String(response.status)}`)\n }\n return result.config\n },\n update: async (patch) => {\n const response = await fetch('/api/feishu-notifier/config', {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(patch),\n })\n const result = await response.json() as { ok?: boolean; message?: string; config?: FeishuController['config'] }\n if (!response.ok || result.ok !== true || result.config === undefined) {\n throw new Error(result.message ?? `HTTP ${String(response.status)}`)\n }\n return result.config\n },\n test: async () => {\n const response = await fetch('/api/feishu-notifier/test', { method: 'POST' })\n const result = await response.json() as { ok?: boolean; message?: string }\n if (!response.ok || result.ok !== true) throw new Error(result.message ?? `HTTP ${String(response.status)}`)\n return result.message ?? '测试消息已发送'\n },\n }\n\n ctx.slots.inject('settings.section', () => ctx.slots.register({\n name: 'settings.section',\n id: 'feishu-notifier',\n order: 25,\n label: () => '飞书通知',\n inject: (): { controller: FeishuController } => ({ controller }),\n }, FeishuSettingsSection))\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"client.js","names":["useState","Button"],"sources":["../src/client/FeishuSettingsSection.tsx","../src/client/index.ts"],"sourcesContent":["import { useEffect, useState } from 'react'\nimport type { PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots'\nimport { Button } from '@deepseek-ai/dsh-client-ui-primitives'\n\nexport interface FeishuConfigView {\n enabled: boolean\n webhookConfigured: boolean\n}\n\nexport interface FeishuController {\n config?: FeishuConfigView\n load: () => Promise<FeishuConfigView>\n update: (patch: { enabled?: boolean; webhook?: string }) => Promise<FeishuConfigView>\n test: () => Promise<string>\n}\n\ntype Props = PropsRuntime<'settings.section'> & { controller: FeishuController }\n\ntype ViewState =\n | { status: 'loading' }\n | { status: 'ready'; config: FeishuConfigView }\n\nexport function FeishuSettingsSection({ controller }: Props) {\n const [view, setView] = useState<ViewState>({ status: 'loading' })\n const [webhook, setWebhook] = useState('')\n const [busy, setBusy] = useState(false)\n const [status, setStatus] = useState('')\n\n useEffect(() => {\n let active = true\n void controller.load()\n .then(config => {\n if (active) setView({ status: 'ready', config })\n })\n .catch(error => {\n if (active) setStatus(error instanceof Error ? error.message : String(error))\n })\n return () => { active = false }\n }, [controller])\n\n const config = view.status === 'ready' ? view.config : { enabled: true, webhookConfigured: false }\n\n const save = async (patch: { enabled?: boolean; webhook?: string }, message: string) => {\n setBusy(true)\n setStatus('正在保存…')\n try {\n const next = await controller.update(patch)\n setView({ status: 'ready', config: next })\n setStatus(message)\n } catch (error) {\n setStatus(error instanceof Error ? error.message : String(error))\n } finally {\n setBusy(false)\n }\n }\n\n const saveWebhook = async () => {\n const value = webhook.trim()\n if (value === '') {\n setStatus('请输入 Webhook 地址')\n return\n }\n await save({ webhook: value }, 'Webhook 已保存到 DSH 设置文件')\n setWebhook('')\n }\n\n const sendTest = async () => {\n setBusy(true)\n setStatus('正在发送测试消息…')\n try {\n setStatus(await controller.test())\n } catch (error) {\n setStatus(error instanceof Error ? error.message : String(error))\n } finally {\n setBusy(false)\n }\n }\n\n return (\n <section style={{ maxWidth: 680, display: 'grid', gap: 24 }}>\n <div>\n <h2 style={{ margin: 0, fontSize: 24 }}>飞书通知</h2>\n <p style={{ color: 'var(--dsw-alias-label-secondary)', lineHeight: 1.6 }}>\n 当智能体等待你的批准、需要回答问题,或一轮对话结束时,向飞书机器人发送提醒。\n </p>\n </div>\n <div style={{ display: 'grid', gap: 16, padding: 20, border: '1px solid var(--dsw-alias-border-l1)', borderRadius: 16 }}>\n <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 16 }}>\n <span>\n <strong style={{ display: 'block' }}>启用通知</strong>\n <small style={{ color: 'var(--dsw-alias-label-secondary)' }}>关闭后不会发送任何飞书消息。</small>\n </span>\n <button\n type=\"button\"\n role=\"switch\"\n aria-checked={config.enabled}\n aria-label=\"启用通知\"\n disabled={busy || view.status === 'loading'}\n onClick={() => { void save({ enabled: !config.enabled }, !config.enabled ? '通知已开启' : '通知已关闭') }}\n style={{\n position: 'relative',\n width: 44,\n height: 26,\n padding: 2,\n borderRadius: 999,\n border: '1px solid var(--dsw-alias-border-l1)',\n background: config.enabled ? 'var(--dsw-alias-bg-accent, #165DFF)' : 'var(--dsw-alias-bg-layer-3, #e5e7eb)',\n cursor: busy || view.status === 'loading' ? 'not-allowed' : 'pointer',\n opacity: busy || view.status === 'loading' ? 0.6 : 1,\n transition: 'background 150ms ease, opacity 150ms ease',\n flexShrink: 0,\n }}\n >\n <span\n aria-hidden\n style={{\n display: 'block',\n width: 20,\n height: 20,\n borderRadius: 999,\n background: '#fff',\n boxShadow: '0 1px 3px rgba(0,0,0,0.2)',\n transform: config.enabled ? 'translateX(18px)' : 'translateX(0)',\n transition: 'transform 150ms ease',\n }}\n />\n </button>\n </div>\n <label style={{ display: 'grid', gap: 8 }}>\n <span><strong>Webhook 地址</strong></span>\n <input\n aria-label=\"Webhook 地址\"\n type=\"url\"\n value={webhook}\n placeholder={config.webhookConfigured ? '已配置地址;输入新地址以替换' : '请输入飞书机器人 Webhook'}\n disabled={busy || view.status === 'loading'}\n onChange={event => { setWebhook(event.target.value); setStatus('') }}\n style={{ minHeight: 40, padding: '0 12px', borderRadius: 10, border: '1px solid var(--dsw-alias-border-l1)', background: 'var(--dsw-alias-bg-layer-1)', color: 'inherit' }}\n />\n </label>\n <div style={{ display: 'flex', flexWrap: 'wrap', gap: 10 }}>\n <Button variant=\"primary\" size=\"sm\" disabled={busy || view.status === 'loading'} onClick={() => { void saveWebhook() }}>保存 Webhook</Button>\n <Button variant=\"outline\" size=\"sm\" disabled={busy || view.status === 'loading' || !config.webhookConfigured} onClick={() => { void sendTest() }}>发送测试消息</Button>\n </div>\n {status !== '' && <p role=\"status\" style={{ margin: 0, color: 'var(--dsw-alias-label-secondary)' }}>{status}</p>}\n </div>\n </section>\n )\n}\n","import type { ClientContext } from '@deepseek-ai/dsh-client-runtime/client'\nimport type {} from '@deepseek-ai/dsh-client-ui-settings/client'\nimport type {} from '@deepseek-ai/dsh-client-ui-slots'\nimport { FeishuSettingsSection, type FeishuController } from './FeishuSettingsSection.tsx'\n\nexport const inject = ['slots']\n\nexport function apply(ctx: ClientContext): void {\n const controller: FeishuController = {\n load: async () => {\n const response = await fetch('/api/feishu-notifier/config')\n const result = await response.json() as { ok?: boolean; message?: string; config?: FeishuController['config'] }\n if (!response.ok || result.ok !== true || result.config === undefined) {\n throw new Error(result.message ?? `HTTP ${String(response.status)}`)\n }\n return result.config\n },\n update: async (patch) => {\n const response = await fetch('/api/feishu-notifier/config', {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(patch),\n })\n const result = await response.json() as { ok?: boolean; message?: string; config?: FeishuController['config'] }\n if (!response.ok || result.ok !== true || result.config === undefined) {\n throw new Error(result.message ?? `HTTP ${String(response.status)}`)\n }\n return result.config\n },\n test: async () => {\n const response = await fetch('/api/feishu-notifier/test', { method: 'POST' })\n const result = await response.json() as { ok?: boolean; message?: string }\n if (!response.ok || result.ok !== true) throw new Error(result.message ?? `HTTP ${String(response.status)}`)\n return result.message ?? '测试消息已发送'\n },\n }\n\n ctx.slots.inject('settings.section', () => ctx.slots.register({\n name: 'settings.section',\n id: 'feishu-notifier',\n order: 25,\n label: () => '飞书通知',\n inject: (): { controller: FeishuController } => ({ controller }),\n }, FeishuSettingsSection))\n}\n"],"mappings":"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"}
|
package/lib/index.js
CHANGED
|
@@ -163,8 +163,11 @@ function apply(ctx, config) {
|
|
|
163
163
|
notify(current, "approval", request.reason ?? `工具 ${request.toolName} 正在等待批准。`);
|
|
164
164
|
return next();
|
|
165
165
|
});
|
|
166
|
-
ctx.on("session/event", (
|
|
167
|
-
if (event.type === "turn/end")
|
|
166
|
+
ctx.on("session/event", (session, event) => {
|
|
167
|
+
if (event.type === "turn/end") {
|
|
168
|
+
if (session.header.origin === "subagent") return;
|
|
169
|
+
notify(current, "turn-end", `第 ${String(event.data.turn)} 轮:${turnReasonText(event.data.reason)}`);
|
|
170
|
+
}
|
|
168
171
|
if (event.type === "tool/call" && (event.data.name === "ask_user_question" || event.data.name === "exit_plan_mode")) notify(current, "approval", event.data.name === "exit_plan_mode" ? "智能体正在等待你确认计划。" : "智能体正在等待你回答问题。");
|
|
169
172
|
});
|
|
170
173
|
ctx.effect(() => ctx.webServer.register({
|