@wenbin_wb/dsh-bridge 2.2.9 → 2.3.1
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.en.md +39 -1
- package/README.md +39 -1
- package/client/client.js +99 -156
- package/client/index.js +87 -118
- package/docs/feishu-usage.md +93 -0
- package/lib/feishu/gateway.js +389 -0
- package/lib/feishu/index.js +219 -0
- package/lib/feishu/node.js +351 -0
- package/lib/index.js +50 -0
- package/lib/platform/conversation-bridge.js +29 -4
- package/lib/qq/gateway.js +1 -1
- package/lib/wechat/gateway.js +3 -0
- package/package.json +5 -3
package/client/index.js
CHANGED
|
@@ -348,6 +348,8 @@ function PlatformCard({ platformId, platformName, platformDesc, rpcCall, onStatu
|
|
|
348
348
|
appId: platform.config.appId ?? '',
|
|
349
349
|
// Secret 不由后端回传;空值表示沿用已保存密钥
|
|
350
350
|
clientSecret: '',
|
|
351
|
+
appSecret: '',
|
|
352
|
+
domain: platform.config.domain ?? 'feishu',
|
|
351
353
|
});
|
|
352
354
|
}
|
|
353
355
|
}, [platform?.config]);
|
|
@@ -436,10 +438,14 @@ function PlatformCard({ platformId, platformName, platformDesc, rpcCall, onStatu
|
|
|
436
438
|
maxMessageChars: Number(cfgDraft.maxMessageChars),
|
|
437
439
|
sendChunkDelayMs: Number(cfgDraft.sendChunkDelayMs),
|
|
438
440
|
};
|
|
439
|
-
// QQ 平台额外携带凭证
|
|
441
|
+
// QQ / 飞书 平台额外携带凭证
|
|
440
442
|
if (platformId === 'qq') {
|
|
441
443
|
payload.appId = cfgDraft.appId.trim();
|
|
442
444
|
payload.clientSecret = cfgDraft.clientSecret.trim();
|
|
445
|
+
} else if (platformId === 'feishu') {
|
|
446
|
+
payload.appId = cfgDraft.appId.trim();
|
|
447
|
+
payload.appSecret = cfgDraft.appSecret.trim();
|
|
448
|
+
payload.domain = cfgDraft.domain || 'feishu';
|
|
443
449
|
}
|
|
444
450
|
await act(BRIDGE_ENDPOINTS.platformSetConfig, payload);
|
|
445
451
|
}, [act, cfgDraft, platformId]);
|
|
@@ -451,6 +457,10 @@ function PlatformCard({ platformId, platformName, platformDesc, rpcCall, onStatu
|
|
|
451
457
|
(platformId === 'qq' && (
|
|
452
458
|
cfgDraft.appId !== (platform.config.appId ?? '') ||
|
|
453
459
|
cfgDraft.clientSecret !== (platform.config.clientSecret ?? '')
|
|
460
|
+
)) ||
|
|
461
|
+
(platformId === 'feishu' && (
|
|
462
|
+
cfgDraft.appId !== (platform.config.appId ?? '') ||
|
|
463
|
+
cfgDraft.appSecret !== (platform.config.appSecret ?? '')
|
|
454
464
|
))
|
|
455
465
|
);
|
|
456
466
|
|
|
@@ -503,6 +513,16 @@ function PlatformCard({ platformId, platformName, platformDesc, rpcCall, onStatu
|
|
|
503
513
|
target: '_blank', rel: 'noopener noreferrer',
|
|
504
514
|
style: s.btnGhost,
|
|
505
515
|
}, '🌐 QQ 开放平台'),
|
|
516
|
+
platformId === 'feishu' && React.createElement('a', {
|
|
517
|
+
href: 'https://github.com/wenbin-wb/dsh-bridge/blob/main/docs/feishu-usage.md',
|
|
518
|
+
target: '_blank', rel: 'noopener noreferrer',
|
|
519
|
+
style: s.btnGhost,
|
|
520
|
+
}, '📖 飞书使用说明'),
|
|
521
|
+
platformId === 'feishu' && React.createElement('a', {
|
|
522
|
+
href: 'https://open.feishu.cn/app',
|
|
523
|
+
target: '_blank', rel: 'noopener noreferrer',
|
|
524
|
+
style: s.btnGhost,
|
|
525
|
+
}, '🌐 飞书开放平台'),
|
|
506
526
|
React.createElement('button', {
|
|
507
527
|
style: s.btnGhost,
|
|
508
528
|
onClick: () => setShowHelp(v => !v),
|
|
@@ -613,19 +633,49 @@ function PlatformCard({ platformId, platformName, platformDesc, rpcCall, onStatu
|
|
|
613
633
|
),
|
|
614
634
|
React.createElement('div', { style: { display: 'flex', gap: 8, marginTop: 10, flexWrap: 'wrap', alignItems: 'center' } },
|
|
615
635
|
platform.status !== 'connected' && platform.status !== 'starting' &&
|
|
616
|
-
React.createElement('button', { style: s.btnPri, onClick: onLogin, disabled: busy }, '
|
|
636
|
+
React.createElement('button', { style: s.btnPri, onClick: onLogin, disabled: busy }, '重新连接'),
|
|
617
637
|
(platform.status === 'connected' || platform.status === 'starting') &&
|
|
618
638
|
React.createElement('button', { style: s.btnGhost, onClick: onStop, disabled: busy }, '断开'),
|
|
619
639
|
React.createElement('button', {
|
|
620
640
|
style: { ...s.btnGhost, color: 'var(--dsw-alias-state-error-primary,#dc2626)', borderColor: 'var(--dsw-alias-state-error-primary,#dc2626)', opacity: busy ? 0.5 : 1 },
|
|
621
641
|
disabled: busy,
|
|
622
|
-
onClick: () => { if (window.confirm('
|
|
623
|
-
title: '
|
|
642
|
+
onClick: () => { if (window.confirm('确认解绑?这将清除保存的凭证。')) act(BRIDGE_ENDPOINTS.platformUnbind, {}); },
|
|
643
|
+
title: '清除登录凭证,下次需重新配置',
|
|
624
644
|
}, '解绑账号'),
|
|
625
645
|
),
|
|
646
|
+
// 飞书专属:手机扫码直达 Bot 对话
|
|
647
|
+
platformId === 'feishu' && platform.botQr && React.createElement('div', {
|
|
648
|
+
style: {
|
|
649
|
+
marginTop: 12,
|
|
650
|
+
padding: 12,
|
|
651
|
+
background: 'var(--dsw-alias-bg-layer-1,#ffffff)',
|
|
652
|
+
border: '1px solid var(--dsw-alias-border-l2,#e5e7eb)',
|
|
653
|
+
borderRadius: 8,
|
|
654
|
+
display: 'flex',
|
|
655
|
+
gap: 14,
|
|
656
|
+
alignItems: 'center',
|
|
657
|
+
flexWrap: 'wrap',
|
|
658
|
+
},
|
|
659
|
+
},
|
|
660
|
+
React.createElement('img', { src: platform.botQr, alt: 'Feishu Bot QR', style: { width: 110, height: 110, borderRadius: 8, border: '1px solid var(--dsw-alias-border-l2,#e5e7eb)', padding: 4, background: '#fff' } }),
|
|
661
|
+
React.createElement('div', { style: { flex: 1, minWidth: 160 } },
|
|
662
|
+
React.createElement('div', { style: { ...s.label, fontSize: 13, fontWeight: 600 } }, '📱 手机飞书扫码直达对话'),
|
|
663
|
+
React.createElement('div', { style: { ...s.muted, fontSize: 12, marginTop: 4, lineHeight: 1.5 } },
|
|
664
|
+
'用飞书手机 App 扫描左侧二维码,立即打开与 Bot 对话;发送首条消息自动完成白名单授权。'
|
|
665
|
+
),
|
|
666
|
+
platform.botLink && React.createElement('div', { style: { display: 'flex', gap: 8, marginTop: 8 } },
|
|
667
|
+
React.createElement('a', {
|
|
668
|
+
href: platform.botLink,
|
|
669
|
+
target: '_blank',
|
|
670
|
+
rel: 'noopener noreferrer',
|
|
671
|
+
style: { ...s.btnGhost, textDecoration: 'none', display: 'inline-flex', alignItems: 'center', gap: 4, padding: '4px 10px', fontSize: 12 },
|
|
672
|
+
}, '在飞书客户端打开 ↗'),
|
|
673
|
+
),
|
|
674
|
+
),
|
|
675
|
+
),
|
|
626
676
|
),
|
|
627
677
|
|
|
628
|
-
// 未配置 /
|
|
678
|
+
// 未配置 / 登录中:表单(QQ / 飞书)或二维码(微信)
|
|
629
679
|
(!platform?.configured || showQr) && React.createElement('div', { style: s.block },
|
|
630
680
|
showQr && login.qr
|
|
631
681
|
? React.createElement('div', null,
|
|
@@ -637,28 +687,45 @@ function PlatformCard({ platformId, platformName, platformDesc, rpcCall, onStatu
|
|
|
637
687
|
),
|
|
638
688
|
login.error && React.createElement('div', { style: { ...s.muted, marginTop: 4, color: 'var(--dsw-alias-state-warn-primary,#92400e)' } }, login.error),
|
|
639
689
|
)
|
|
640
|
-
: platformId === 'qq'
|
|
690
|
+
: (platformId === 'qq' || platformId === 'feishu')
|
|
641
691
|
? React.createElement('div', { style: { display: 'flex', flexDirection: 'column', gap: 10, marginTop: 4 } },
|
|
692
|
+
platformId === 'feishu' && React.createElement('div', {
|
|
693
|
+
style: {
|
|
694
|
+
background: 'var(--dsw-alias-bg-layer-1,#ffffff)',
|
|
695
|
+
border: '1px dashed var(--dsw-alias-border-l2,#e5e7eb)',
|
|
696
|
+
borderRadius: 6,
|
|
697
|
+
padding: '8px 10px',
|
|
698
|
+
fontSize: 12,
|
|
699
|
+
lineHeight: 1.5,
|
|
700
|
+
},
|
|
701
|
+
},
|
|
702
|
+
React.createElement('span', { style: s.label }, '💡 扫码自动创建引导:'),
|
|
703
|
+
React.createElement('span', { style: s.muted }, ' 可在终端运行 '),
|
|
704
|
+
React.createElement('code', { style: { ...s.code, fontSize: 11, background: 'var(--dsw-alias-bg-layer-2,#f3f4f6)', padding: '2px 4px', borderRadius: 4 } }, 'npx feishu-bot-bootstrap'),
|
|
705
|
+
React.createElement('span', { style: s.muted }, ' 手机扫码一键自动创建应用并输出凭证;或在下方手动填入凭证。')
|
|
706
|
+
),
|
|
642
707
|
React.createElement('div', null,
|
|
643
|
-
React.createElement('div', { style: { ...s.muted, marginBottom: 4 } },
|
|
708
|
+
React.createElement('div', { style: { ...s.muted, marginBottom: 4 } },
|
|
709
|
+
platformId === 'qq' ? 'AppID — QQ 开放平台机器人应用 ID' : 'App ID — 飞书开放平台自建应用 ID (cli_xxx)'
|
|
710
|
+
),
|
|
644
711
|
React.createElement('input', {
|
|
645
712
|
style: { ...s.input, width: '100%' },
|
|
646
|
-
placeholder: '请输入 AppID',
|
|
713
|
+
placeholder: platformId === 'qq' ? '请输入 AppID' : '请输入 App ID (如 cli_a1b2c3d4...)',
|
|
647
714
|
value: cfgDraft?.appId ?? '',
|
|
648
715
|
onChange: (e) => setCfgDraft(d => ({ ...d, appId: e.target.value })),
|
|
649
|
-
onKeyDown: (e) => { if (e.key === 'Enter' && cfgDraft?.appId?.trim() && cfgDraft?.clientSecret?.trim() && !busy) saveConfig(); },
|
|
650
716
|
}),
|
|
651
717
|
),
|
|
652
718
|
React.createElement('div', null,
|
|
653
|
-
React.createElement('div', { style: { ...s.muted, marginBottom: 4 } },
|
|
719
|
+
React.createElement('div', { style: { ...s.muted, marginBottom: 4 } },
|
|
720
|
+
platformId === 'qq' ? 'ClientSecret — QQ 开放平台机器人密钥' : 'App Secret — 飞书开放平台应用密钥'
|
|
721
|
+
),
|
|
654
722
|
React.createElement('div', { style: { display: 'flex', gap: 6, alignItems: 'center' } },
|
|
655
723
|
React.createElement('input', {
|
|
656
724
|
style: { ...s.input, flex: 1 },
|
|
657
725
|
type: showSecret ? 'text' : 'password',
|
|
658
|
-
placeholder: '请输入 ClientSecret',
|
|
659
|
-
value: cfgDraft?.clientSecret ?? '',
|
|
660
|
-
onChange: (e) => setCfgDraft(d => ({ ...d, clientSecret: e.target.value })),
|
|
661
|
-
onKeyDown: (e) => { if (e.key === 'Enter' && cfgDraft?.appId?.trim() && cfgDraft?.clientSecret?.trim() && !busy) saveConfig(); },
|
|
726
|
+
placeholder: platformId === 'qq' ? '请输入 ClientSecret' : '请输入 App Secret',
|
|
727
|
+
value: platformId === 'qq' ? (cfgDraft?.clientSecret ?? '') : (cfgDraft?.appSecret ?? ''),
|
|
728
|
+
onChange: (e) => setCfgDraft(d => platformId === 'qq' ? ({ ...d, clientSecret: e.target.value }) : ({ ...d, appSecret: e.target.value })),
|
|
662
729
|
}),
|
|
663
730
|
React.createElement('button', {
|
|
664
731
|
style: { ...s.btnGhost, height: 32, padding: '0 10px', fontSize: 13, flexShrink: 0 },
|
|
@@ -670,15 +737,16 @@ function PlatformCard({ platformId, platformName, platformDesc, rpcCall, onStatu
|
|
|
670
737
|
),
|
|
671
738
|
React.createElement('div', null,
|
|
672
739
|
React.createElement('a', {
|
|
673
|
-
href: 'https://bot.q.qq.com/wiki/develop/api-v2/',
|
|
740
|
+
href: platformId === 'qq' ? 'https://bot.q.qq.com/wiki/develop/api-v2/' : 'https://open.feishu.cn/app',
|
|
674
741
|
target: '_blank', rel: 'noopener noreferrer',
|
|
675
742
|
style: s.btnLink,
|
|
676
|
-
}, '📖 前往 QQ 开放平台申请机器人'),
|
|
743
|
+
}, platformId === 'qq' ? '📖 前往 QQ 开放平台申请机器人' : '📖 前往飞书开放平台创建企业自建应用'),
|
|
677
744
|
),
|
|
678
745
|
React.createElement('div', { style: { display: 'flex', gap: 8, flexWrap: 'wrap', alignItems: 'center' } },
|
|
679
746
|
React.createElement('button', {
|
|
680
747
|
style: { ...s.btnPri, opacity: busy ? 0.5 : 1 },
|
|
681
|
-
onClick: saveConfig,
|
|
748
|
+
onClick: saveConfig,
|
|
749
|
+
disabled: busy || !cfgDraft?.appId?.trim() || (platformId === 'qq' ? !cfgDraft?.clientSecret?.trim() : !cfgDraft?.appSecret?.trim()),
|
|
682
750
|
}, busy ? '保存中…' : '保存并连接'),
|
|
683
751
|
login.phase === 'error' && React.createElement('div', { style: { ...s.muted, fontSize: 12 } }, login.error ?? '连接失败'),
|
|
684
752
|
),
|
|
@@ -692,106 +760,7 @@ function PlatformCard({ platformId, platformName, platformDesc, rpcCall, onStatu
|
|
|
692
760
|
),
|
|
693
761
|
),
|
|
694
762
|
|
|
695
|
-
|
|
696
|
-
cfgDraft && React.createElement('div', { style: s.block },
|
|
697
|
-
React.createElement('button', {
|
|
698
|
-
style: { ...s.btnLink, fontSize: 12, marginBottom: showAdvanced ? 10 : 0 },
|
|
699
|
-
onClick: () => setShowAdvanced(v => !v),
|
|
700
|
-
}, showAdvanced ? '▾ 高级设置' : '▸ 高级设置'),
|
|
701
|
-
showAdvanced && React.createElement('div', { style: { display: 'flex', flexDirection: 'column', gap: 10 } },
|
|
702
|
-
// 2x2 参数网格
|
|
703
|
-
React.createElement('div', {
|
|
704
|
-
style: {
|
|
705
|
-
display: 'grid',
|
|
706
|
-
gridTemplateColumns: 'repeat(auto-fit, minmax(200px, 1fr))',
|
|
707
|
-
gap: 10,
|
|
708
|
-
},
|
|
709
|
-
},
|
|
710
|
-
// 心跳间隔
|
|
711
|
-
React.createElement('div', null,
|
|
712
|
-
React.createElement('div', { style: { ...s.muted, marginBottom: 4 } }, '心跳进度间隔 (秒)'),
|
|
713
|
-
React.createElement('input', {
|
|
714
|
-
style: { ...s.input, width: '100%' },
|
|
715
|
-
type: 'number', min: 30, max: 3600,
|
|
716
|
-
value: cfgDraft.digestIntervalSec,
|
|
717
|
-
onChange: (e) => setCfgDraft(d => ({ ...d, digestIntervalSec: e.target.value })),
|
|
718
|
-
}),
|
|
719
|
-
),
|
|
720
|
-
// 审批超时
|
|
721
|
-
React.createElement('div', null,
|
|
722
|
-
React.createElement('div', { style: { ...s.muted, marginBottom: 4 } }, '审批超时自动拒绝 (秒)'),
|
|
723
|
-
React.createElement('input', {
|
|
724
|
-
style: { ...s.input, width: '100%' },
|
|
725
|
-
type: 'number', min: 30, max: 86400,
|
|
726
|
-
value: cfgDraft.approvalTimeoutSec,
|
|
727
|
-
onChange: (e) => setCfgDraft(d => ({ ...d, approvalTimeoutSec: e.target.value })),
|
|
728
|
-
}),
|
|
729
|
-
),
|
|
730
|
-
// 每气泡字数
|
|
731
|
-
React.createElement('div', null,
|
|
732
|
-
React.createElement('div', { style: { ...s.muted, marginBottom: 4 } }, '单条最大字数限制 (字)'),
|
|
733
|
-
React.createElement('input', {
|
|
734
|
-
style: { ...s.input, width: '100%' },
|
|
735
|
-
type: 'number', min: 100, max: 10000,
|
|
736
|
-
value: cfgDraft.maxMessageChars,
|
|
737
|
-
onChange: (e) => setCfgDraft(d => ({ ...d, maxMessageChars: e.target.value })),
|
|
738
|
-
}),
|
|
739
|
-
),
|
|
740
|
-
// 分块延迟
|
|
741
|
-
React.createElement('div', null,
|
|
742
|
-
React.createElement('div', { style: { ...s.muted, marginBottom: 4 } }, '分块发送延迟 (毫秒)'),
|
|
743
|
-
React.createElement('input', {
|
|
744
|
-
style: { ...s.input, width: '100%' },
|
|
745
|
-
type: 'number', min: 0, max: 10000,
|
|
746
|
-
value: cfgDraft.sendChunkDelayMs,
|
|
747
|
-
onChange: (e) => setCfgDraft(d => ({ ...d, sendChunkDelayMs: e.target.value })),
|
|
748
|
-
}),
|
|
749
|
-
),
|
|
750
|
-
),
|
|
751
|
-
// QQ 平台凭证
|
|
752
|
-
platformId === 'qq' && React.createElement('div', { style: { display: 'flex', flexDirection: 'column', gap: 10, marginTop: 4 } },
|
|
753
|
-
React.createElement('div', null,
|
|
754
|
-
React.createElement('div', { style: { ...s.muted, marginBottom: 4 } }, 'AppID — QQ 开放平台机器人应用 ID'),
|
|
755
|
-
React.createElement('input', {
|
|
756
|
-
style: { ...s.input, width: '100%' },
|
|
757
|
-
placeholder: '请输入 AppID',
|
|
758
|
-
value: cfgDraft.appId,
|
|
759
|
-
onChange: (e) => setCfgDraft(d => ({ ...d, appId: e.target.value })),
|
|
760
|
-
}),
|
|
761
|
-
),
|
|
762
|
-
React.createElement('div', null,
|
|
763
|
-
React.createElement('div', { style: { ...s.muted, marginBottom: 4 } }, 'ClientSecret — QQ 开放平台机器人密钥(留空则保持已保存密钥)'),
|
|
764
|
-
React.createElement('div', { style: { display: 'flex', gap: 6, alignItems: 'center' } },
|
|
765
|
-
React.createElement('input', {
|
|
766
|
-
style: { ...s.input, flex: 1 },
|
|
767
|
-
type: showSecret ? 'text' : 'password',
|
|
768
|
-
placeholder: '留空保持已保存密钥',
|
|
769
|
-
value: cfgDraft.clientSecret,
|
|
770
|
-
onChange: (e) => setCfgDraft(d => ({ ...d, clientSecret: e.target.value })),
|
|
771
|
-
}),
|
|
772
|
-
React.createElement('button', {
|
|
773
|
-
style: { ...s.btnGhost, height: 32, padding: '0 10px', fontSize: 13, flexShrink: 0 },
|
|
774
|
-
onClick: () => setShowSecret(v => !v),
|
|
775
|
-
type: 'button',
|
|
776
|
-
title: showSecret ? '隐藏密钥' : '显示明文',
|
|
777
|
-
}, showSecret ? '🙈 隐藏' : '👁️ 显示'),
|
|
778
|
-
),
|
|
779
|
-
),
|
|
780
|
-
),
|
|
781
|
-
React.createElement('div', { style: { display: 'flex', gap: 8, alignItems: 'center', marginTop: 4 } },
|
|
782
|
-
React.createElement('button', {
|
|
783
|
-
style: { ...s.btnPri, opacity: (cfgDirty && !busy) ? 1 : 0.5 },
|
|
784
|
-
disabled: !cfgDirty || busy,
|
|
785
|
-
onClick: saveConfig,
|
|
786
|
-
}, busy ? '保存中…' : '保存设置'),
|
|
787
|
-
React.createElement('button', {
|
|
788
|
-
style: { ...s.btnGhost, height: 32, fontSize: 12 },
|
|
789
|
-
onClick: resetDefaults,
|
|
790
|
-
title: '恢复推荐默认配置',
|
|
791
|
-
}, '↺ 恢复推荐默认'),
|
|
792
|
-
),
|
|
793
|
-
),
|
|
794
|
-
),
|
|
763
|
+
|
|
795
764
|
|
|
796
765
|
React.createElement('div', { style: s.block },
|
|
797
766
|
React.createElement('div', { style: { ...s.tip, fontSize: 12 } },
|
|
@@ -1221,7 +1190,7 @@ function BridgePanel({ rpcCall }) {
|
|
|
1221
1190
|
const IM_PLATFORMS = [
|
|
1222
1191
|
{ id: 'wechat', label: '微信', icon: Icons.wechat, brandColor: '#07C160', desc: 'ClawBot 扫码直连 · 无需公网' },
|
|
1223
1192
|
{ id: 'qq', label: 'QQ', icon: Icons.qq, brandColor: '#12B7F5', desc: '官方机器人 · 私聊/群聊/按钮' },
|
|
1224
|
-
{ id: 'feishu', label: '飞书', icon: Icons.feishu, brandColor: '#00D6B9', desc: '
|
|
1193
|
+
{ id: 'feishu', label: '飞书', icon: Icons.feishu, brandColor: '#00D6B9', desc: '官方 WebSocket 长连接 · 免公网' },
|
|
1225
1194
|
{ id: 'telegram', label: 'Telegram', icon: Icons.telegram, brandColor: '#24A1DE', desc: '官方 Bot API' },
|
|
1226
1195
|
];
|
|
1227
1196
|
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# 飞书 (Feishu / Lark) 机器人接入指南
|
|
2
|
+
|
|
3
|
+
> 通过飞书官方开放平台 **WebSocket 长连接(免公网 IP)** 模式,将本地 DeepSeek Harness 接入飞书,支持单聊、群聊、表格命令与原生交互卡片权限审批。
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 🌟 核心优势
|
|
8
|
+
|
|
9
|
+
- **100% 免公网 IP**:基于飞书官方最新 WebSocket 长连接协议,无需配置公网服务器、无需域名、无需内网穿透工具。
|
|
10
|
+
- **免验签与加密配置**:长连接由飞书官方 SDK 在建立通道时完成认证,无需开发者手动处理 HTTP 回调验签。
|
|
11
|
+
- **飞书原生交互卡片**:Agent 触发敏感工具审批时,飞书端下发带点击按钮的交互卡片,手机/电脑端单手一键点击即可完成批准或拒绝。
|
|
12
|
+
- **多工作区与会话持久化**:支持 `/sessions` 表格查看历史会话、`/use <序号>` 极速切换、`/workspaces` 工作区调度。
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## 🛠️ 第一步:创建飞书企业自建应用
|
|
17
|
+
|
|
18
|
+
1. 登录 [飞书开放平台开发者后台](https://open.feishu.cn/app);
|
|
19
|
+
2. 点击右上角 **「创建企业自建应用」**,填写应用名称(如 `DeepSeek Harness`)与应用描述,上传机器人头像;
|
|
20
|
+
3. 创建完成后,在左侧导航栏点击 **「凭证与基础信息」**,即可查看到:
|
|
21
|
+
- **App ID**(格式如 `cli_a1b2c3d4...`)
|
|
22
|
+
- **App Secret**(点击复制密钥)
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## 🤖 第二步:添加机器人能力
|
|
27
|
+
|
|
28
|
+
1. 在应用详情页左侧导航栏,点击 **「添加应用能力」**;
|
|
29
|
+
2. 找到 **「机器人」**,点击 **「添加」** 开启机器人能力。
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## 🔐 第三步:开通必要权限
|
|
34
|
+
|
|
35
|
+
在左侧导航栏点击 **「权限管理」**,搜索并开通以下权限:
|
|
36
|
+
|
|
37
|
+
| 权限名称 | 权限 Key | 权限说明 |
|
|
38
|
+
| :--- | :--- | :--- |
|
|
39
|
+
| **获取与发送单聊/群聊消息** | `im:message` | 基础消息收发权限 |
|
|
40
|
+
| **以应用的身份发消息** | `im:message:send_as_bot` | 允许机器人向用户/群回复消息 |
|
|
41
|
+
| **获取群组中所有消息** | `im:message.group_msg` | 允许机器人在群聊中被 `@` 时接收消息 |
|
|
42
|
+
| **获取用户 user ID** | `contact:user.id:readonly` | (可选) 获取用户信息 |
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## ⚡ 第四步:开启 WebSocket 长连接事件订阅
|
|
47
|
+
|
|
48
|
+
1. 在左侧导航栏点击 **「事件与回调」**;
|
|
49
|
+
2. 在 **「事件配置」** 页面,将事件接收方式切换为 **「使用长连接接收事件」**(WebSocket 模式);
|
|
50
|
+
3. 点击 **「添加事件」**,勾选并添加:
|
|
51
|
+
- **`im.message.receive_v1`**(接收消息)
|
|
52
|
+
- **`card.action.trigger`**(消息卡片回传交互 / 审批按钮点击)
|
|
53
|
+
4. 保存配置。
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## 🚀 第五步:版本发布
|
|
58
|
+
|
|
59
|
+
1. 在左侧导航栏点击 **「版本管理与发布」**;
|
|
60
|
+
2. 点击 **「创建版本」**,填写版本号(如 `1.0.0`),设置应用可用范围(如「所有员工」或「仅自己」);
|
|
61
|
+
3. 点击 **「申请发布」**(自建应用通常由企业管理员直接免审或一键通过)。
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
## 📱 第六步:在 DSH 客户端中连接
|
|
66
|
+
|
|
67
|
+
1. 打开 DeepSeek Harness,进入「设置」➔「远程访问」➔「IM 机器人」➔ 选择 **「飞书」**;
|
|
68
|
+
2. 在表单中填入刚才复制的 **App ID** 和 **App Secret**;
|
|
69
|
+
3. 点击 **「保存并连接」**;
|
|
70
|
+
4. 状态显示为绿色 **「已连接」** 即表示长连接成功建立!
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
## 💬 常用操作与指令
|
|
75
|
+
|
|
76
|
+
在飞书与机器人单聊或在群里 `@机器人` 即可开始对话:
|
|
77
|
+
|
|
78
|
+
### 1. 会话与工作区管理
|
|
79
|
+
| 指令 | 说明 | 示例 |
|
|
80
|
+
| :--- | :--- | :--- |
|
|
81
|
+
| `/sessions` | 查看所有历史会话表格 | `/sessions` 或 `/list` |
|
|
82
|
+
| `/use <编号>` | 切换到指定编号会话 | `/use 1` 或 `/resume 1` |
|
|
83
|
+
| `/new <提示词>` | 在当前工作区创建新会话并开始 | `/new 帮我写个脚本` |
|
|
84
|
+
| `/new <词> @N` | 在指定工作区新建会话 | `/new 帮我写个脚本 @1` |
|
|
85
|
+
| `/workspaces` | 查看所有已注册的工作区列表 | `/workspaces` |
|
|
86
|
+
| `/status` | 查看 Agent 运行状态看板 | `/status` |
|
|
87
|
+
| `/stop` | 中断停止当前正在执行的任务 | `/stop` |
|
|
88
|
+
| `/end` | 结束当前会话回到空闲状态 | `/end` |
|
|
89
|
+
|
|
90
|
+
### 2. 权限审批交互
|
|
91
|
+
当 Agent 尝试执行需授权的操作(如终端命令、写敏感文件)时,飞书端会自动推送 **原生交互卡片**:
|
|
92
|
+
- 点击卡片上的 **「✓ 批准执行」** 或 **「✕ 拒绝执行」** 按钮即可一键处理;
|
|
93
|
+
- 亦可直接回复文字 `/yes` (或 `1`) / `/no` (或 `2`)。
|