@xmanrui/dsh-im 4.17.1 → 4.18.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.en.md +17 -13
- package/README.md +16 -15
- package/lib/client.js +424 -246
- package/lib/index.js +280 -258
- package/package.json +7 -2
- package/plugin-src/client/access-policy-settings.js +5 -0
- package/plugin-src/client/channel-logos.js +10 -0
- package/plugin-src/client/channels/imessage/api.js +12 -0
- package/plugin-src/client/channels/imessage/index.js +45 -0
- package/plugin-src/client/channels/imessage/styles.js +19 -0
- package/plugin-src/client/channels/shared/token-api.js +1 -0
- package/plugin-src/client/channels/shared/token-channel.js +5 -1
- package/plugin-src/client/delivery-settings.js +5 -0
- package/plugin-src/client/i18n.js +23 -1
- package/plugin-src/client/index.js +20 -0
- package/plugin-src/client/session-channel-logos.js +2 -0
- package/plugin-src/client/styles.js +1 -0
- package/plugin-src/host/channels/imessage/index.mjs +21 -0
- package/plugin-src/host/channels/imessage/production.mjs +13 -0
- package/plugin-src/host/channels/imessage/rpc.mjs +70 -0
- package/plugin-src/host/channels/shared/access-policy-production.mjs +1 -1
- package/plugin-src/host/delivery-adapter.mjs +4 -0
- package/plugin-src/host/index.mjs +3 -0
- package/plugin-src/host/modern-harness-api.mjs +1 -1
- package/src/channels/imessage/config-store.mjs +24 -0
- package/src/channels/imessage/controller.mjs +37 -0
- package/src/channels/imessage/harness-client.mjs +7 -0
- package/src/channels/imessage/imessage-api.mjs +194 -0
- package/src/channels/imessage/imessage-bridge.mjs +16 -0
- package/src/channels/imessage/runtime.mjs +98 -0
- package/src/channels/imessage/state-store.mjs +3 -0
- package/src/channels/shared/i18n-en/shared-a.mjs +2 -2
- package/src/channels/shared/message-failure.mjs +5 -5
- package/src/channels/shared/session-channel-labels.mjs +1 -0
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xmanrui/dsh-im",
|
|
3
|
-
"version": "4.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "4.18.0",
|
|
4
|
+
"description": "把十一种 IM 渠道和公网 AI Office 接入本机 DeepSeek Harness。 Connect eleven IM channels and a public AI Office to a local DeepSeek Harness.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"deepseek-harness",
|
|
7
7
|
"dsh",
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
"telegram",
|
|
21
21
|
"discord",
|
|
22
22
|
"whatsapp",
|
|
23
|
+
"imessage",
|
|
23
24
|
"ai-office"
|
|
24
25
|
],
|
|
25
26
|
"author": "xmanrui (https://github.com/xmanrui)",
|
|
@@ -43,6 +44,10 @@
|
|
|
43
44
|
{
|
|
44
45
|
"name": "masucc",
|
|
45
46
|
"url": "https://github.com/masucc"
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
"name": "cherryFloris",
|
|
50
|
+
"url": "https://github.com/cherryFloris"
|
|
46
51
|
}
|
|
47
52
|
],
|
|
48
53
|
"license": "MIT",
|
|
@@ -69,6 +69,11 @@ export const ACCESS_CHANNEL_DEFINITIONS = Object.freeze({
|
|
|
69
69
|
groupUserLabel: '群成员电话号码或 JID',
|
|
70
70
|
groupPlaceholder: '8613800000000 或完整 JID',
|
|
71
71
|
}),
|
|
72
|
+
imessage: Object.freeze({
|
|
73
|
+
directUserLabel: 'iMessage Chat GUID',
|
|
74
|
+
directPlaceholder: 'iMessage;+;chat123',
|
|
75
|
+
groupSupported: false,
|
|
76
|
+
}),
|
|
72
77
|
});
|
|
73
78
|
|
|
74
79
|
function clonePolicy(policy) {
|
|
@@ -132,6 +132,16 @@ export function WhatsappLogoGlyph({ size } = {}) {
|
|
|
132
132
|
}));
|
|
133
133
|
}
|
|
134
134
|
|
|
135
|
+
export function IMessageLogoGlyph({ size } = {}) {
|
|
136
|
+
return h('svg', {
|
|
137
|
+
...dimensions(size), viewBox: '0 0 24 24', focusable: 'false',
|
|
138
|
+
'aria-hidden': 'true', 'data-im-channel-logo': 'imessage',
|
|
139
|
+
}, h('path', {
|
|
140
|
+
fill: 'currentColor',
|
|
141
|
+
d: 'M12 2.2c-5.52 0-10 3.75-10 8.38 0 2.63 1.52 4.98 3.9 6.52l-.92 3.43a.5.5 0 0 0 .72.56l3.54-1.96c.87.23 1.8.35 2.76.35 5.52 0 10-3.75 10-8.9S17.52 2.2 12 2.2Zm-3.4 8.2a1.2 1.2 0 1 1 0 2.4 1.2 1.2 0 0 1 0-2.4Zm3.4 0a1.2 1.2 0 1 1 0 2.4 1.2 1.2 0 0 1 0-2.4Zm3.4 0a1.2 1.2 0 1 1 0 2.4 1.2 1.2 0 0 1 0-2.4Z',
|
|
142
|
+
}));
|
|
143
|
+
}
|
|
144
|
+
|
|
135
145
|
export function OfficeLogoGlyph({ size } = {}) {
|
|
136
146
|
return h('svg', {
|
|
137
147
|
...dimensions(size),
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { TOKEN_BOT_ENDPOINTS, createTokenChannelApi } from '../shared/token-api.js';
|
|
2
|
+
|
|
3
|
+
export const IMESSAGE_RPC_CHANNEL = '/imessage';
|
|
4
|
+
export const IMESSAGE_ENDPOINTS = Object.freeze({
|
|
5
|
+
...TOKEN_BOT_ENDPOINTS,
|
|
6
|
+
bindCredentials: 'bot.bind-native',
|
|
7
|
+
});
|
|
8
|
+
const api = createTokenChannelApi('iMessage', ' macOS Messages 连接');
|
|
9
|
+
export { api as imessageClientApi };
|
|
10
|
+
export const unwrapRpcResult = api.unwrapRpcResult;
|
|
11
|
+
export const normalizeSnapshot = api.normalizeSnapshot;
|
|
12
|
+
export const presentError = api.presentError;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { IMessageLogoGlyph } from '../../channel-logos.js';
|
|
3
|
+
import { createTokenChannelSettings } from '../shared/token-channel.js';
|
|
4
|
+
import { IMESSAGE_ENDPOINTS, imessageClientApi } from './api.js';
|
|
5
|
+
import { installIMessageStyles } from './styles.js';
|
|
6
|
+
import { h } from '../../i18n.js';
|
|
7
|
+
|
|
8
|
+
function PermissionPanel({ permissions, busy, error, onSubmit, onCancel }) {
|
|
9
|
+
const databaseReady = permissions?.database === 'granted';
|
|
10
|
+
const automationReady = permissions?.automation === 'granted';
|
|
11
|
+
return h('section', { className: 'ddt-card dim-surfaceCard dim-imessagePermissionPanel' },
|
|
12
|
+
h('h3', null, '启用 macOS 原生 iMessage'),
|
|
13
|
+
h('p', null, '支持同账号自聊:在 iPhone 或 Mac 上给自己已登录的 iMessage 邮箱或号码发指令。AI 回复以 🤖 DSH 开头,不会再次触发机器人。'),
|
|
14
|
+
h('p', null, 'DeepSeek Harness 通过 macOS Messages.app 收发文本消息。首次使用需要授予以下权限:'),
|
|
15
|
+
h('ol', { className: 'dim-imessagePermissionSteps' },
|
|
16
|
+
h('li', null,
|
|
17
|
+
h('strong', null, databaseReady ? '✓ 完全磁盘访问权限已授予' : '1. 授予完全磁盘访问权限'),
|
|
18
|
+
databaseReady ? null : h('p', null, '在“系统设置 → 隐私与安全性 → 完全磁盘访问权限”中,打开运行 DeepSeek Harness 的终端或应用。'),
|
|
19
|
+
databaseReady ? null : h('a', { href: 'x-apple.systempreferences:com.apple.preference.security?Privacy_AllFiles' }, '打开完全磁盘访问权限设置')),
|
|
20
|
+
h('li', null,
|
|
21
|
+
h('strong', null, automationReady ? '✓ Messages 自动化权限已授予' : '2. 允许自动化控制 Messages'),
|
|
22
|
+
automationReady ? null : h('p', null, '在“系统设置 → 隐私与安全性 → 自动化”中,允许运行 DeepSeek Harness 的应用控制 Messages。'),
|
|
23
|
+
automationReady ? null : h('a', { href: 'x-apple.systempreferences:com.apple.preference.security?Privacy_Automation' }, '打开自动化设置'))),
|
|
24
|
+
error ? h('p', { className: 'dim-inlineError', role: 'alert' }, error.message ?? String(error)) : null,
|
|
25
|
+
h('div', { className: 'ddt-actions dim-viewActions' },
|
|
26
|
+
h('button', { type: 'button', className: 'ddt-button', onClick: onCancel, disabled: busy }, '取消'),
|
|
27
|
+
h('button', { type: 'button', className: 'ddt-button', 'data-kind': 'primary', onClick: () => onSubmit({}), disabled: busy },
|
|
28
|
+
busy ? '正在检查…' : '检查权限并启用')));
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const channel = createTokenChannelSettings({
|
|
32
|
+
channel: 'iMessage', endpoints: IMESSAGE_ENDPOINTS, api: imessageClientApi,
|
|
33
|
+
LogoGlyph: IMessageLogoGlyph, installStyles: installIMessageStyles,
|
|
34
|
+
pageClass: 'dim-pageIMessage', avatarClass: 'dim-avatarIMessage',
|
|
35
|
+
connectionLabel: ' macOS Messages 连接',
|
|
36
|
+
tokenPlaceholder: '', emptyTitle: '接入 macOS 原生 iMessage',
|
|
37
|
+
emptyDescription: '无需安装第三方服务;请按指引授予 macOS Messages 访问权限。',
|
|
38
|
+
platformLabel: 'macOS Messages', credentialPayload: () => ({}),
|
|
39
|
+
CredentialPanel: PermissionPanel,
|
|
40
|
+
credentialAriaLabel: '配置 macOS Messages 权限', credentialOpenLabel: '配置本机权限',
|
|
41
|
+
credentialNoun: 'macOS Messages 权限', emptyActionLabel: '配置本机权限',
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
export const IMessageSettingsTab = channel.SettingsTab;
|
|
45
|
+
export const IMessageAccountCard = channel.AccountCard;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export const IMESSAGE_STYLE_ID = 'xmanrui-dsh-im-imessage-settings';
|
|
2
|
+
const CSS = String.raw`
|
|
3
|
+
.dim-pageIMessage { --ddt-accent: #32a852; --ddt-accent-deep: #248a40; --ddt-accent-wash: #edf9f0; }
|
|
4
|
+
.dim-avatarIMessage { color: #fff; background: #32a852; }
|
|
5
|
+
.dim-avatarIMessage svg { display: block; }
|
|
6
|
+
.dim-imessagePermissionPanel { padding: 20px; }
|
|
7
|
+
.dim-imessagePermissionPanel p { color: var(--dsw-alias-label-secondary, #646a73); }
|
|
8
|
+
.dim-imessagePermissionSteps { margin: 16px 0; padding-left: 22px; }
|
|
9
|
+
.dim-imessagePermissionSteps li { margin: 12px 0; }
|
|
10
|
+
.dim-imessagePermissionSteps a { color: var(--ddt-accent-deep); }
|
|
11
|
+
`;
|
|
12
|
+
export function installIMessageStyles() {
|
|
13
|
+
if (typeof document === 'undefined') return () => {};
|
|
14
|
+
const existing = document.querySelector(`style[data-plugin-css="${IMESSAGE_STYLE_ID}"]`);
|
|
15
|
+
if (existing) return () => {};
|
|
16
|
+
const style = document.createElement('style'); style.dataset.plugin = '@xmanrui/dsh-im';
|
|
17
|
+
style.dataset.pluginCss = IMESSAGE_STYLE_ID; style.textContent = CSS; document.head.appendChild(style);
|
|
18
|
+
return () => style.remove();
|
|
19
|
+
}
|
|
@@ -101,6 +101,7 @@ export function createTokenChannelApi(channel, connectionSummary, {
|
|
|
101
101
|
totals: { configured: bots.length, connected: bots.filter((bot) => bot.connected).length },
|
|
102
102
|
agentPresetCatalog: normalizeAgentPresetCatalog(source.agentPresetCatalog),
|
|
103
103
|
modelCatalog: normalizeModelCatalog(source.modelCatalog),
|
|
104
|
+
...(isRecord(source.permissions) ? { permissions: source.permissions } : {}),
|
|
104
105
|
};
|
|
105
106
|
};
|
|
106
107
|
|
|
@@ -182,7 +182,7 @@ export function createTokenChannelSettings(definition) {
|
|
|
182
182
|
const [model, setModel] = React.useState({
|
|
183
183
|
phase: 'loading', bots: [], totals: { configured: 0, connected: 0 }, error: null,
|
|
184
184
|
agentPresetCatalog: EMPTY_AGENT_PRESET_CATALOG,
|
|
185
|
-
modelCatalog: EMPTY_MODEL_CATALOG,
|
|
185
|
+
modelCatalog: EMPTY_MODEL_CATALOG, permissions: null,
|
|
186
186
|
});
|
|
187
187
|
const [credentialOpen, setCredentialOpen] = React.useState(false);
|
|
188
188
|
const [credentialError, setCredentialError] = React.useState(null);
|
|
@@ -221,6 +221,7 @@ export function createTokenChannelSettings(definition) {
|
|
|
221
221
|
phase: 'ready', bots: snapshot.bots, totals: snapshot.totals, error: null,
|
|
222
222
|
agentPresetCatalog: snapshot.agentPresetCatalog ?? EMPTY_AGENT_PRESET_CATALOG,
|
|
223
223
|
modelCatalog: snapshot.modelCatalog ?? EMPTY_MODEL_CATALOG,
|
|
224
|
+
permissions: snapshot.permissions ?? null,
|
|
224
225
|
});
|
|
225
226
|
} catch (error) {
|
|
226
227
|
if (error?.name !== 'AbortError' && mounted.current && !signal?.aborted
|
|
@@ -268,6 +269,7 @@ export function createTokenChannelSettings(definition) {
|
|
|
268
269
|
phase: 'ready', bots: snapshot.bots, totals: snapshot.totals, error: null,
|
|
269
270
|
agentPresetCatalog: snapshot.agentPresetCatalog ?? EMPTY_AGENT_PRESET_CATALOG,
|
|
270
271
|
modelCatalog: snapshot.modelCatalog ?? EMPTY_MODEL_CATALOG,
|
|
272
|
+
permissions: snapshot.permissions ?? null,
|
|
271
273
|
});
|
|
272
274
|
}
|
|
273
275
|
setCredentialOpen(false);
|
|
@@ -418,6 +420,8 @@ export function createTokenChannelSettings(definition) {
|
|
|
418
420
|
: h(React.Fragment, null,
|
|
419
421
|
credentialOpen ? (CredentialPanel
|
|
420
422
|
? h(CredentialPanel, {
|
|
423
|
+
channel,
|
|
424
|
+
permissions: model.permissions,
|
|
421
425
|
busy,
|
|
422
426
|
error: credentialError,
|
|
423
427
|
onSubmit: bindCredentials,
|
|
@@ -115,6 +115,11 @@ const CHANNEL_DEFINITIONS = Object.freeze({
|
|
|
115
115
|
group: [{ key: 'jid', label: '群 JID', placeholder: '1234567890@g.us' }],
|
|
116
116
|
},
|
|
117
117
|
},
|
|
118
|
+
imessage: {
|
|
119
|
+
label: 'iMessage',
|
|
120
|
+
kinds: [{ value: 'user', label: '私聊' }],
|
|
121
|
+
fields: { user: [{ key: 'chatGuid', label: 'Chat GUID', placeholder: 'iMessage;+;chat123' }] },
|
|
122
|
+
},
|
|
118
123
|
});
|
|
119
124
|
|
|
120
125
|
export const DELIVERY_CHANNEL_DEFINITIONS = CHANNEL_DEFINITIONS;
|
|
@@ -4,6 +4,27 @@ export const IM_LOCALE_NAMESPACE = 'dsh-im';
|
|
|
4
4
|
|
|
5
5
|
const EN = Object.freeze({
|
|
6
6
|
'$locale': 'en',
|
|
7
|
+
' macOS Messages 连接': ' macOS Messages connection',
|
|
8
|
+
'接入 iMessage': 'Connect iMessage',
|
|
9
|
+
'先在 macOS 系统设置中授予 Messages 权限。': 'Grant Messages permissions in macOS System Settings first.',
|
|
10
|
+
'配置 macOS Messages 权限': 'Configure macOS Messages permissions',
|
|
11
|
+
'配置本机权限': 'Configure local permissions',
|
|
12
|
+
'启用 macOS 原生 iMessage': 'Enable native macOS iMessage',
|
|
13
|
+
'支持同账号自聊:在 iPhone 或 Mac 上给自己已登录的 iMessage 邮箱或号码发指令。AI 回复以 🤖 DSH 开头,不会再次触发机器人。': 'Self-chat is supported: send instructions from your iPhone or Mac to your signed-in iMessage email or phone number. AI replies start with 🤖 DSH and do not trigger the bot again.',
|
|
14
|
+
'检查权限并启用': 'Check permissions and enable',
|
|
15
|
+
'DeepSeek Harness 通过 macOS Messages.app 收发文本消息。首次使用需要授予以下权限:': 'DeepSeek Harness sends and receives messages through macOS Messages.app. The following permissions are required the first time:',
|
|
16
|
+
'✓ 完全磁盘访问权限已授予': '✓ Full Disk Access granted',
|
|
17
|
+
'1. 授予完全磁盘访问权限': '1. Grant Full Disk Access',
|
|
18
|
+
'在“系统设置 → 隐私与安全性 → 完全磁盘访问权限”中,打开运行 DeepSeek Harness 的终端或应用。': 'In System Settings → Privacy & Security → Full Disk Access, enable the terminal or app running DeepSeek Harness.',
|
|
19
|
+
'打开完全磁盘访问权限设置': 'Open Full Disk Access settings',
|
|
20
|
+
'✓ Messages 自动化权限已授予': '✓ Messages automation granted',
|
|
21
|
+
'2. 允许自动化控制 Messages': '2. Allow automation control of Messages',
|
|
22
|
+
'在“系统设置 → 隐私与安全性 → 自动化”中,允许运行 DeepSeek Harness 的应用控制 Messages。': 'In System Settings → Privacy & Security → Automation, allow the app running DeepSeek Harness to control Messages.',
|
|
23
|
+
'打开自动化设置': 'Open Automation settings',
|
|
24
|
+
'正在检查…': 'Checking…',
|
|
25
|
+
'接入 macOS 原生 iMessage': 'Connect native macOS iMessage',
|
|
26
|
+
'无需安装第三方服务;请按指引授予 macOS Messages 访问权限。': 'No third-party service is required; follow the guide to grant macOS Messages access.',
|
|
27
|
+
'macOS Messages 权限': 'macOS Messages permissions',
|
|
7
28
|
'IM机器人': 'IM bots',
|
|
8
29
|
'IM机器人设置': 'IM bot settings',
|
|
9
30
|
'更多机器人设置': 'More bot settings',
|
|
@@ -1062,7 +1083,8 @@ function translateDynamic(text) {
|
|
|
1062
1083
|
|
|
1063
1084
|
const phrases = [
|
|
1064
1085
|
['企业微信', 'WeCom'], ['DeepSeek Harness', 'DeepSeek Harness'],
|
|
1065
|
-
['WhatsApp', 'WhatsApp'], ['
|
|
1086
|
+
['WhatsApp', 'WhatsApp'], ['iMessage', 'iMessage'], ['macOS Messages', 'macOS Messages'],
|
|
1087
|
+
['Telegram', 'Telegram'], ['Discord', 'Discord'],
|
|
1066
1088
|
['Slack', 'Slack'], ['飞书', 'Feishu'], ['钉钉', 'DingTalk'], ['微信', 'WeChat'],
|
|
1067
1089
|
['机器人', 'bot'], ['账号', 'account'], ['应用', 'app'], ['凭据', 'credentials'],
|
|
1068
1090
|
['服务返回了无法识别的响应', 'service returned an unrecognized response'],
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
WecomLogoGlyph,
|
|
14
14
|
WeixinLogoGlyph,
|
|
15
15
|
WhatsappLogoGlyph,
|
|
16
|
+
IMessageLogoGlyph,
|
|
16
17
|
} from './channel-logos.js';
|
|
17
18
|
import { DINGTALK_RPC_CHANNEL } from './channels/dingtalk/api.js';
|
|
18
19
|
import { DingtalkSettingsTab } from './channels/dingtalk/index.js';
|
|
@@ -46,6 +47,9 @@ import { installWeixinStyles } from './channels/weixin/styles.js';
|
|
|
46
47
|
import { WHATSAPP_RPC_CHANNEL } from './channels/whatsapp/api.js';
|
|
47
48
|
import { WhatsappSettingsTab } from './channels/whatsapp/index.js';
|
|
48
49
|
import { installWhatsappStyles } from './channels/whatsapp/styles.js';
|
|
50
|
+
import { IMESSAGE_RPC_CHANNEL } from './channels/imessage/api.js';
|
|
51
|
+
import { IMessageSettingsTab } from './channels/imessage/index.js';
|
|
52
|
+
import { installIMessageStyles } from './channels/imessage/styles.js';
|
|
49
53
|
import { en, h, IM_LOCALE_NAMESPACE, setImTranslator, zh } from './i18n.js';
|
|
50
54
|
import { BotSettingsContext } from './channel-card-meta.js';
|
|
51
55
|
import {
|
|
@@ -92,6 +96,7 @@ const CHANNELS = Object.freeze([
|
|
|
92
96
|
{ id: 'discord', label: 'Discord' },
|
|
93
97
|
{ id: 'whatsapp', label: 'WhatsApp' },
|
|
94
98
|
{ id: 'wecomApp', label: '企业微信应用', note: '(实验功能)' },
|
|
99
|
+
{ id: 'imessage', label: 'iMessage', note: '(实验功能)' },
|
|
95
100
|
{ id: 'office', label: 'AI Office', note: '(实验功能)' },
|
|
96
101
|
]);
|
|
97
102
|
|
|
@@ -142,6 +147,11 @@ function WhatsappLogo() {
|
|
|
142
147
|
h(WhatsappLogoGlyph));
|
|
143
148
|
}
|
|
144
149
|
|
|
150
|
+
function IMessageLogo() {
|
|
151
|
+
return h('span', { className: 'dim-logo dim-logoIMessage', 'aria-hidden': 'true' },
|
|
152
|
+
h(IMessageLogoGlyph));
|
|
153
|
+
}
|
|
154
|
+
|
|
145
155
|
function OfficeLogo() {
|
|
146
156
|
return h('span', { className: 'dim-logo dim-logoOffice', 'aria-hidden': 'true' },
|
|
147
157
|
h(OfficeLogoGlyph));
|
|
@@ -158,6 +168,7 @@ function ChannelLogo({ channel }) {
|
|
|
158
168
|
if (channel === 'telegram') return h(TelegramLogo);
|
|
159
169
|
if (channel === 'discord') return h(DiscordLogo);
|
|
160
170
|
if (channel === 'whatsapp') return h(WhatsappLogo);
|
|
171
|
+
if (channel === 'imessage') return h(IMessageLogo);
|
|
161
172
|
return h(OfficeLogo);
|
|
162
173
|
}
|
|
163
174
|
|
|
@@ -181,6 +192,7 @@ export function IMSettingsTab({
|
|
|
181
192
|
dingtalkRpcCall,
|
|
182
193
|
discordRpcCall,
|
|
183
194
|
feishuRpcCall,
|
|
195
|
+
imessageRpcCall,
|
|
184
196
|
qqRpcCall,
|
|
185
197
|
slackRpcCall,
|
|
186
198
|
telegramRpcCall,
|
|
@@ -231,6 +243,7 @@ export function IMSettingsTab({
|
|
|
231
243
|
updateRpcCall,
|
|
232
244
|
deliveryRpcCall,
|
|
233
245
|
globalSettingsRpcCall,
|
|
246
|
+
imessageRpcCall,
|
|
234
247
|
}, {
|
|
235
248
|
location: browserLocation,
|
|
236
249
|
onRecovery: reportLoopbackRecovery,
|
|
@@ -241,6 +254,7 @@ export function IMSettingsTab({
|
|
|
241
254
|
deliveryRpcCall,
|
|
242
255
|
feishuRpcCall,
|
|
243
256
|
globalSettingsRpcCall,
|
|
257
|
+
imessageRpcCall,
|
|
244
258
|
officeRpcCall,
|
|
245
259
|
qqRpcCall,
|
|
246
260
|
reportLoopbackRecovery,
|
|
@@ -369,6 +383,8 @@ export function IMSettingsTab({
|
|
|
369
383
|
? h(DiscordSettingsTab, { rpcCall: rpcCalls.discordRpcCall })
|
|
370
384
|
: active.id === 'whatsapp'
|
|
371
385
|
? h(WhatsappSettingsTab, { rpcCall: rpcCalls.whatsappRpcCall })
|
|
386
|
+
: active.id === 'imessage'
|
|
387
|
+
? h(IMessageSettingsTab, { rpcCall: rpcCalls.imessageRpcCall })
|
|
372
388
|
: h(OfficeSettingsTab, { rpcCall: rpcCalls.officeRpcCall }))),
|
|
373
389
|
),
|
|
374
390
|
));
|
|
@@ -395,6 +411,7 @@ export function apply(ctx) {
|
|
|
395
411
|
installTelegramStyles(),
|
|
396
412
|
installDiscordStyles(),
|
|
397
413
|
installWhatsappStyles(),
|
|
414
|
+
installIMessageStyles(),
|
|
398
415
|
installOfficeStyles(),
|
|
399
416
|
installImStyles(),
|
|
400
417
|
];
|
|
@@ -421,6 +438,8 @@ export function apply(ctx) {
|
|
|
421
438
|
callManagementRpc(ctx.connection, DISCORD_RPC_CHANNEL, endpoint, payload, signal);
|
|
422
439
|
const whatsappRpcCall = (endpoint, payload, signal) =>
|
|
423
440
|
callManagementRpc(ctx.connection, WHATSAPP_RPC_CHANNEL, endpoint, payload, signal);
|
|
441
|
+
const imessageRpcCall = (endpoint, payload, signal) =>
|
|
442
|
+
callManagementRpc(ctx.connection, IMESSAGE_RPC_CHANNEL, endpoint, payload, signal);
|
|
424
443
|
const slackRpcCall = (endpoint, payload, signal) =>
|
|
425
444
|
callManagementRpc(ctx.connection, SLACK_RPC_CHANNEL, endpoint, payload, signal);
|
|
426
445
|
const officeRpcCall = (endpoint, payload, signal) =>
|
|
@@ -454,6 +473,7 @@ export function apply(ctx) {
|
|
|
454
473
|
wecomAppRpcCall,
|
|
455
474
|
weixinRpcCall,
|
|
456
475
|
whatsappRpcCall,
|
|
476
|
+
imessageRpcCall,
|
|
457
477
|
officeRpcCall,
|
|
458
478
|
updateRpcCall,
|
|
459
479
|
deliveryRpcCall,
|
|
@@ -2,6 +2,7 @@ import { parseSessionChannelTitle } from '../../src/channels/shared/session-chan
|
|
|
2
2
|
import {
|
|
3
3
|
WeixinLogoGlyph, FeishuLogoGlyph, DingtalkLogoGlyph, WecomLogoGlyph, QqLogoGlyph,
|
|
4
4
|
SlackLogoGlyph, TelegramLogoGlyph, DiscordLogoGlyph, WhatsappLogoGlyph, OfficeLogoGlyph,
|
|
5
|
+
IMessageLogoGlyph,
|
|
5
6
|
} from './channel-logos.js';
|
|
6
7
|
|
|
7
8
|
const GLYPHS = Object.freeze({
|
|
@@ -14,6 +15,7 @@ const GLYPHS = Object.freeze({
|
|
|
14
15
|
telegram: [TelegramLogoGlyph, '#229ed9'],
|
|
15
16
|
discord: [DiscordLogoGlyph, '#5865f2'],
|
|
16
17
|
whatsapp: [WhatsappLogoGlyph, '#25d366'],
|
|
18
|
+
imessage: [IMessageLogoGlyph, '#3478f6'],
|
|
17
19
|
office: [OfficeLogoGlyph, '#f97316'],
|
|
18
20
|
});
|
|
19
21
|
const CHANNEL_ATTR = 'data-dsh-im-session-channel';
|
|
@@ -97,6 +97,7 @@ const CSS = String.raw`
|
|
|
97
97
|
.dim-logoSlack { color: white; background: #4a154b; }
|
|
98
98
|
.dim-logoSlack svg { width: 21px; height: 21px; }
|
|
99
99
|
.dim-logoWhatsapp { color: white; background: #25d366; }
|
|
100
|
+
.dim-logoIMessage { color: white; background: #32a852; }
|
|
100
101
|
.dim-logoWhatsapp svg { width: 21px; height: 21px; }
|
|
101
102
|
.dim-channelCopy { min-width: 0; display: grid; }
|
|
102
103
|
.dim-channelCopy strong { overflow: hidden; color: inherit; font-size: 14px; line-height: 20px; font-weight: 680; text-overflow: ellipsis; white-space: nowrap; }
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { createProductionController } from './production.mjs';
|
|
2
|
+
import { installIMessageRpc } from './rpc.mjs';
|
|
3
|
+
|
|
4
|
+
export const name = 'dsh-im-imessage-host';
|
|
5
|
+
export const inject = ['connection', 'credentials', 'typertGateway'];
|
|
6
|
+
|
|
7
|
+
export async function apply(ctx, config = {}) {
|
|
8
|
+
if (config?.controller) return installIMessageRpc(ctx, config.controller, config.rpcAuthority);
|
|
9
|
+
const production = await createProductionController(ctx, config, config.internals ?? {});
|
|
10
|
+
const unregisterDelivery = config.deliveryService && production.deliveryAdapter
|
|
11
|
+
? config.deliveryService.registerAdapter(production.deliveryAdapter) : undefined;
|
|
12
|
+
const disposeRpc = installIMessageRpc(ctx, production.controller, config.rpcAuthority);
|
|
13
|
+
ctx.effect(() => async () => {
|
|
14
|
+
await unregisterDelivery?.(); await production.close();
|
|
15
|
+
}, 'dsh-im: close iMessage connections');
|
|
16
|
+
return disposeRpc;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export { createProductionController } from './production.mjs';
|
|
20
|
+
export { IMESSAGE_ENDPOINTS, IMESSAGE_RPC_CHANNEL, IMESSAGE_RPC_ENDPOINTS,
|
|
21
|
+
createIMessageRpcHandler, installIMessageRpc } from './rpc.mjs';
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { IMessageConfigStore } from '../../../../src/channels/imessage/config-store.mjs';
|
|
2
|
+
import { IMessageController } from '../../../../src/channels/imessage/controller.mjs';
|
|
3
|
+
import { IMessageHarnessClient } from '../../../../src/channels/imessage/harness-client.mjs';
|
|
4
|
+
import { IMessageRuntime } from '../../../../src/channels/imessage/runtime.mjs';
|
|
5
|
+
import { IMessageStateStore } from '../../../../src/channels/imessage/state-store.mjs';
|
|
6
|
+
import { createTokenProductionController } from '../shared/production.mjs';
|
|
7
|
+
|
|
8
|
+
export function createProductionController(ctx, config = {}, internals = {}) {
|
|
9
|
+
return createTokenProductionController(ctx, config, internals, {
|
|
10
|
+
channel: 'imessage', ConfigStore: IMessageConfigStore, StateStore: IMessageStateStore,
|
|
11
|
+
HarnessClient: IMessageHarnessClient, Controller: IMessageController, Runtime: IMessageRuntime,
|
|
12
|
+
});
|
|
13
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { createTokenBotRpcHandler } from '../shared/rpc.mjs';
|
|
2
|
+
import { registerManagementRpc } from '../../../management-rpc.mjs';
|
|
3
|
+
import { resolveRpcAuthority } from '../../rpc-authority.mjs';
|
|
4
|
+
|
|
5
|
+
export const IMESSAGE_RPC_CHANNEL = '/imessage';
|
|
6
|
+
export const IMESSAGE_ENDPOINTS = Object.freeze({
|
|
7
|
+
status: 'connection.status',
|
|
8
|
+
bindNative: 'bot.bind-native',
|
|
9
|
+
bindCredentials: 'bot.bind-native',
|
|
10
|
+
permissions: 'permissions.status',
|
|
11
|
+
reconnectBot: 'bot.reconnect',
|
|
12
|
+
deleteBot: 'bot.delete',
|
|
13
|
+
setWorkspace: 'bot.workspace.set',
|
|
14
|
+
setModel: 'bot.model.set',
|
|
15
|
+
setAgentPreset: 'bot.agent-preset.set',
|
|
16
|
+
setContextEnhancement: 'bot.context-enhancement.set',
|
|
17
|
+
setAccessPolicy: 'bot.access-policy.set',
|
|
18
|
+
});
|
|
19
|
+
export const IMESSAGE_RPC_ENDPOINTS = Object.freeze(Object.values(IMESSAGE_ENDPOINTS));
|
|
20
|
+
|
|
21
|
+
function withRpcDetails(result) {
|
|
22
|
+
if (result?.ok !== false) return result;
|
|
23
|
+
return {
|
|
24
|
+
...result,
|
|
25
|
+
error: { ...result.error, details: result.error?.details ?? {} },
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function createIMessageRpcHandler(controller) {
|
|
30
|
+
const tokenHandler = createTokenBotRpcHandler(controller, {
|
|
31
|
+
channel: 'iMessage',
|
|
32
|
+
});
|
|
33
|
+
return async (endpoint, payload, signal) => {
|
|
34
|
+
if (endpoint === IMESSAGE_ENDPOINTS.status) {
|
|
35
|
+
const value = await controller.status();
|
|
36
|
+
return { ok: true, value: { ...value, permissions: await controller.permissions() } };
|
|
37
|
+
}
|
|
38
|
+
if (endpoint === IMESSAGE_ENDPOINTS.permissions) {
|
|
39
|
+
try {
|
|
40
|
+
return { ok: true, value: await controller.permissions() };
|
|
41
|
+
} catch (error) {
|
|
42
|
+
return withRpcDetails({
|
|
43
|
+
ok: false,
|
|
44
|
+
error: { code: 'permissions-check-failed', message: error.message },
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
if (endpoint === IMESSAGE_ENDPOINTS.bindNative) {
|
|
49
|
+
try {
|
|
50
|
+
return { ok: true, value: await controller.bindNative() };
|
|
51
|
+
} catch (error) {
|
|
52
|
+
return withRpcDetails({
|
|
53
|
+
ok: false,
|
|
54
|
+
error: { code: error.code ?? 'imessage-bind-failed', message: error.message },
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
const translated = endpoint === IMESSAGE_ENDPOINTS.setModel ? 'bot.model.set' : endpoint;
|
|
59
|
+
return withRpcDetails(await tokenHandler(translated, payload, signal));
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function installIMessageRpc(ctx, controller, authority) {
|
|
64
|
+
return registerManagementRpc(
|
|
65
|
+
ctx,
|
|
66
|
+
IMESSAGE_RPC_CHANNEL,
|
|
67
|
+
createIMessageRpcHandler(controller),
|
|
68
|
+
{ authority: resolveRpcAuthority(authority) },
|
|
69
|
+
);
|
|
70
|
+
}
|
|
@@ -91,7 +91,7 @@ export function initialAccessPolicyFor(channel, config = {}) {
|
|
|
91
91
|
group: allowlistScope(),
|
|
92
92
|
});
|
|
93
93
|
}
|
|
94
|
-
if (['dingtalk', 'wecom', 'wecom-app', 'slack', 'discord'].includes(key)) {
|
|
94
|
+
if (['dingtalk', 'wecom', 'wecom-app', 'slack', 'discord', 'imessage'].includes(key)) {
|
|
95
95
|
return createAccessPolicy({ direct: openScope(), group: openScope() });
|
|
96
96
|
}
|
|
97
97
|
throw new TypeError(`Unsupported access-policy channel: ${channel}`);
|
|
@@ -15,6 +15,7 @@ const CHANNELS = new Set([
|
|
|
15
15
|
'telegram',
|
|
16
16
|
'discord',
|
|
17
17
|
'whatsapp',
|
|
18
|
+
'imessage',
|
|
18
19
|
]);
|
|
19
20
|
|
|
20
21
|
export function supportsDeliveryChannel(channel) {
|
|
@@ -122,6 +123,9 @@ function normalizeRoute(channel, kind, route) {
|
|
|
122
123
|
}
|
|
123
124
|
return normalized;
|
|
124
125
|
}
|
|
126
|
+
case 'imessage':
|
|
127
|
+
oneOf(kind, ['user']);
|
|
128
|
+
return routeWithStrings(route, ['chatGuid']);
|
|
125
129
|
default:
|
|
126
130
|
throw new TypeError(`Unsupported delivery channel: ${channel}`);
|
|
127
131
|
}
|
|
@@ -9,6 +9,7 @@ import { apply as applyWecom } from './channels/wecom/index.mjs';
|
|
|
9
9
|
import { apply as applyWecomApp } from './channels/wecom-app/index.mjs';
|
|
10
10
|
import { apply as applyWeixin } from './channels/weixin/index.mjs';
|
|
11
11
|
import { apply as applyWhatsapp } from './channels/whatsapp/index.mjs';
|
|
12
|
+
import { apply as applyIMessage } from './channels/imessage/index.mjs';
|
|
12
13
|
import { installOutboundArtifactTool } from '../../src/channels/shared/semantic/artifact.mjs';
|
|
13
14
|
import { setImHostLanguage } from '../../src/channels/shared/i18n.mjs';
|
|
14
15
|
import { installDeliveryRpc } from './delivery-rpc.mjs';
|
|
@@ -53,6 +54,7 @@ export function createImHostPlugin(internals = {}) {
|
|
|
53
54
|
const startDiscord = internals.applyDiscord ?? applyDiscord;
|
|
54
55
|
const startOffice = internals.applyOffice ?? applyOffice;
|
|
55
56
|
const startWhatsapp = internals.applyWhatsapp ?? applyWhatsapp;
|
|
57
|
+
const startIMessage = internals.applyIMessage ?? applyIMessage;
|
|
56
58
|
const channels = [
|
|
57
59
|
['feishu', startFeishu],
|
|
58
60
|
['weixin', startWeixin],
|
|
@@ -64,6 +66,7 @@ export function createImHostPlugin(internals = {}) {
|
|
|
64
66
|
['telegram', startTelegram],
|
|
65
67
|
['discord', startDiscord],
|
|
66
68
|
['whatsapp', startWhatsapp],
|
|
69
|
+
['imessage', startIMessage],
|
|
67
70
|
['office', startOffice],
|
|
68
71
|
];
|
|
69
72
|
return Object.freeze({
|
|
@@ -5,7 +5,7 @@ import { hasActiveHarnessInteractionOwner } from '../../src/channels/shared/harn
|
|
|
5
5
|
const modernApis = new WeakMap();
|
|
6
6
|
|
|
7
7
|
function failureOf(error) {
|
|
8
|
-
const failure = error?.failure;
|
|
8
|
+
const failure = error?.failure ?? (error?.isDSHRemoteError === true ? error : undefined);
|
|
9
9
|
if (failure && typeof failure === 'object'
|
|
10
10
|
&& typeof failure.code === 'string'
|
|
11
11
|
&& typeof failure.message === 'string') {
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import {
|
|
2
|
+
deriveTokenBotIdentity,
|
|
3
|
+
maskPlatformId,
|
|
4
|
+
TokenBotConfigStore,
|
|
5
|
+
} from '../shared/token-config-store.mjs';
|
|
6
|
+
|
|
7
|
+
const IDENTITY_OPTIONS = Object.freeze({
|
|
8
|
+
botPrefix: 'imessage',
|
|
9
|
+
tokenRefPrefix: 'DSH_IMESSAGE_PASSWORD',
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
export function deriveIMessageBotIdentity(platformId) {
|
|
13
|
+
return deriveTokenBotIdentity(platformId, IDENTITY_OPTIONS);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function maskIMessageBotId(platformId) {
|
|
17
|
+
return maskPlatformId(platformId, 'iMessage 网关');
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export class IMessageConfigStore extends TokenBotConfigStore {
|
|
21
|
+
constructor(path) {
|
|
22
|
+
super(path, { channel: 'iMessage', ...IDENTITY_OPTIONS });
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { TokenBotController } from '../shared/token-bot-controller.mjs';
|
|
2
|
+
import { MacOSMessagesApi } from './imessage-api.mjs';
|
|
3
|
+
import {
|
|
4
|
+
deriveIMessageBotIdentity,
|
|
5
|
+
maskIMessageBotId,
|
|
6
|
+
} from './config-store.mjs';
|
|
7
|
+
import { IMESSAGE_DESCRIPTOR } from './imessage-bridge.mjs';
|
|
8
|
+
|
|
9
|
+
const NATIVE_CREDENTIAL = 'macos-messages-native';
|
|
10
|
+
|
|
11
|
+
export async function inspectIMessageCredential(value, { api = new MacOSMessagesApi() } = {}) {
|
|
12
|
+
if (value !== NATIVE_CREDENTIAL) throw new TypeError('Invalid native iMessage credential');
|
|
13
|
+
const permissions = await api.getPermissions();
|
|
14
|
+
return { platformId: 'macos-messages', name: 'Mac Messages', permissions };
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export class IMessageController extends TokenBotController {
|
|
18
|
+
constructor(options) {
|
|
19
|
+
super({
|
|
20
|
+
...options,
|
|
21
|
+
descriptor: IMESSAGE_DESCRIPTOR,
|
|
22
|
+
inspectToken: options.inspectToken ?? inspectIMessageCredential,
|
|
23
|
+
deriveIdentity: deriveIMessageBotIdentity,
|
|
24
|
+
maskPlatformId: maskIMessageBotId,
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async bindNative() {
|
|
29
|
+
return super.bindCredentials({ token: NATIVE_CREDENTIAL });
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async permissions() {
|
|
33
|
+
return new MacOSMessagesApi().getPermissions();
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export { NATIVE_CREDENTIAL };
|