diff --git a/README.i18n.yaml b/README.i18n.yaml index 23094d8e96..53b0af8024 100644 --- a/README.i18n.yaml +++ b/README.i18n.yaml @@ -1,5 +1,2 @@ -# Bilingual-pair consistency record: the git blob hash of each side as of the last -# confirmed-consistent state. Both languages carry equal authority. Update both files -# and re-record their hashes after editing either side. -README.md: 88fa1de7794bccc9841e53719e4542272472f871 -README.zh-CN.md: 4c409b2075e4b9acd264958908d0b1b353f66ba4 +README.md: 0001e932629d1d60393f7d5dfab8da9ac5a681fa +README.zh-CN.md: 2739465b697fd7ae4beaa39f230964b25b82b32b diff --git a/README.md b/README.md index 861ea6c4f2..0001e93262 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@
WorkDSH brings material, experts, skills, and connectors into one workspace, with the SkillHub catalog and installable DSH community plugins.
Download Desktop · Explore the workflow · Personal and enterprise · User guide · 简体中文
-[](https://github.com/techflag/workdsh/releases/tag/desktop-v2.0.6-alpha.2) [](https://github.com/techflag/workdsh) [](LICENSE) +[](https://github.com/techflag/workdsh/releases/tag/desktop-v2.0.6-alpha.3) [](https://github.com/techflag/workdsh) [](LICENSE)  @@ -81,7 +81,14 @@ The enterprise backend is developed and deployed independently in **[techflag/wo Administrators should follow the [workdsh-admin deployment and usage guide](https://github.com/techflag/workdsh-admin#readme), then provide members with the backend address and company accounts. Members install this repository's desktop app and Enterprise Connection plugin and connect to that address; the Agent and tools continue to run on their computer. -### Install enterprise plugins +### Enterprise Connection: download, install and sign in + +**Enterprise features are enabled by an explicitly installed Enterprise Connection plugin, not bundled in the base Desktop.** One package provides company accounts, colleague collaboration and authenticated requests for independent business plugins. + +1. Download `workdsh-enterprise-connection-0.1.0-alpha.2.tgz` from this release and retain the file. +2. In personal mode, open Plugins → Add plugin, enter the full tgz path, install and click Enable now. +3. Open Settings → Enterprise account → Connect enterprise and sign in using the company backend address and member account. +4. Open Collaboration to review shares and configure the company API manually in model settings. Developers can inject `workdshEnterprise`; see the [integration guide](docs/ENTERPRISE-PLUGIN-AUTH.md). The Enterprise Connection package is delivered independently through our [GitHub Releases](https://github.com/techflag/workdsh/releases), without a third-party marketplace. Download `workdsh-enterprise-connection-WorkDSH 将资料、专家、技能和连接器带入同一工作台;接入 SkillHub 技能目录,并支持安装 DSH 社区插件。
下载桌面版 · 了解工作流 · 个人与企业 · 使用指南 · English
-[](https://github.com/techflag/workdsh/releases/tag/desktop-v2.0.6-alpha.2) [](https://github.com/techflag/workdsh) [](LICENSE) +[](https://github.com/techflag/workdsh/releases/tag/desktop-v2.0.6-alpha.3) [](https://github.com/techflag/workdsh) [](LICENSE)  @@ -81,7 +81,14 @@ Desktop 的个人与企业空间分别保存数据和凭据。企业账号插件 管理员请按 [workdsh-admin 的部署与使用说明](https://github.com/techflag/workdsh-admin#readme) 部署后台,并向成员提供后台地址和公司账号。成员安装本仓库的桌面端与企业连接插件后连接该地址;Agent 与工具仍在成员本机执行。 -### 安装企业插件 +### 企业连接插件:下载、安装与登录 + +**企业功能通过企业连接插件按需启用,基础桌面包不预装。** 一个包提供企业账号、@同事协作,并向独立业务插件提供认证请求服务。 + +1. 下载本次发行的 `workdsh-enterprise-connection-0.1.0-alpha.2.tgz`,保留文件。 +2. 在个人空间打开“插件 → 添加插件”,填写 tgz 完整路径,安装后点击“立即启用”。 +3. 打开“设置 → 企业账号 → 连接企业”,填写公司后台地址和成员账号登录。 +4. 在“协作”查看分享,在模型设置中手动配置公司内部 API。开发者可注入 `workdshEnterprise` 复用认证,详见[调用说明](docs/ENTERPRISE-PLUGIN-AUTH.md)。 企业连接包由本项目 [GitHub Releases](https://github.com/techflag/workdsh/releases) 独立交付,不依赖第三方插件市场。下载 `workdsh-enterprise-connection-<版本>.tgz`,保留文件并在添加插件时填写完整路径;发行附件包含兼容 DSH 版本的清单及 `SHA256SUMS`。 @@ -156,13 +163,13 @@ WorkDSH Admin:组织概览。 ## 下载桌面版 -计划发布的桌面安装包版本为 **2.0.6-alpha.2**。发布 [GitHub Release](https://github.com/techflag/workdsh/releases/tag/desktop-v2.0.6-alpha.2) 后,以下下载链接才会生效: +计划发布的桌面安装包版本为 **2.0.6-alpha.3**。发布 [GitHub Release](https://github.com/techflag/workdsh/releases/tag/desktop-v2.0.6-alpha.3) 后,以下下载链接才会生效: | 平台 | 下载 | | --- | --- | -| Windows x64 | [WorkDSH Setup.exe](https://github.com/techflag/workdsh/releases/download/desktop-v2.0.6-alpha.2/dsh-plugin-desktop-windows-x64--WorkDSH-2.0.6-alpha.2-x64-Setup.exe) | -| macOS Apple Silicon | [WorkDSH arm64.dmg](https://github.com/techflag/workdsh/releases/download/desktop-v2.0.6-alpha.2/dsh-plugin-desktop-macos-arm64--WorkDSH-2.0.6-alpha.2-arm64.dmg) | -| macOS Intel | [WorkDSH x64.dmg](https://github.com/techflag/workdsh/releases/download/desktop-v2.0.6-alpha.2/dsh-plugin-desktop-macos-x64--WorkDSH-2.0.6-alpha.2-x64.dmg) | +| Windows x64 | [WorkDSH Setup.exe](https://github.com/techflag/workdsh/releases/download/desktop-v2.0.6-alpha.3/dsh-plugin-desktop-windows-x64--WorkDSH-2.0.6-alpha.3-x64-Setup.exe) | +| macOS Apple Silicon | [WorkDSH arm64.dmg](https://github.com/techflag/workdsh/releases/download/desktop-v2.0.6-alpha.3/dsh-plugin-desktop-macos-arm64--WorkDSH-2.0.6-alpha.3-arm64.dmg) | +| macOS Intel | [WorkDSH x64.dmg](https://github.com/techflag/workdsh/releases/download/desktop-v2.0.6-alpha.3/dsh-plugin-desktop-macos-x64--WorkDSH-2.0.6-alpha.3-x64.dmg) | Desktop 安装包默认内置 Node.js、pnpm 和 Python 运行时,普通用户无需单独安装。当前为 **Alpha 版**:项目资料引用、专家执行及不同 Office 格式的端到端体验仍在验收中。macOS DMG 未签名;更新请从 [Releases](https://github.com/techflag/workdsh/releases) 下载。开始使用前请阅读[用户指南](docs/user-guide.md)和[常见问题](docs/faq.md)。 @@ -170,6 +177,10 @@ Desktop 基础包不预装企业插件,安装企业插件后启用企业登录 桌面菜单“工具 → 终端命令 dsh(可选)”可查看、安装、修复和移除终端命令。普通桌面聊天无需安装此命令,只有主动点击菜单才显示管理弹窗。安装只创建命令入口,使用随包的 Node、pnpm 和官方 CLI;移除命令不会删除应用或工作区。命令默认操作当前 Desktop 工作区,启动时显示空间名称;`--workdsh-space=personal` 或 `--workdsh-space=enterprise` 可明确选择。企业空间须保持 Desktop 登录,首次使用前先在 Desktop 打开对应空间。插件安装、更新和移除遵循该空间的官方 Profile。 +### 开发需要企业认证的插件 + +企业业务插件注入 `workdshEnterprise`,通过 `request({ plugin: "reports", operation: "list", method: "POST", body: { page: 1 } })` 请求公司后台。Desktop 自动携带当前成员认证,插件不读取或保存 Token;后台仍检查成员和业务权限。调用示例、SDK 类型依赖及新旧版本要求见[企业插件认证接入](docs/ENTERPRISE-PLUGIN-AUTH.md)。 + ## 开发与文档 源码分工:[WorkDSH 功能包与 Web](apps/web/README.zh-CN.md) · [Desktop 外壳](apps/desktop/README.zh.md) · [架构](docs/architecture.md) · [全部文档](docs/README.md)。从源码运行需要 Node.js 22.19+ 或 24+、Corepack 和 Yarn 4.18.0: diff --git a/apps/desktop/package.json b/apps/desktop/package.json index 8381256add..b64c134b46 100644 --- a/apps/desktop/package.json +++ b/apps/desktop/package.json @@ -1,6 +1,6 @@ { "name": "dsh-plugin-desktop", - "version": "2.0.6-alpha.2", + "version": "2.0.6-alpha.3", "description": "WorkDSH Electron carrier for a pinned DeepSeek Harness runtime Profile", "license": "MIT", "publishConfig": { diff --git a/apps/desktop/src/enterprise-auth.ts b/apps/desktop/src/enterprise-auth.ts index 94337bc1d5..72c72b3a9d 100644 --- a/apps/desktop/src/enterprise-auth.ts +++ b/apps/desktop/src/enterprise-auth.ts @@ -117,7 +117,8 @@ export async function startEnterpriseAuthority(login: EnterpriseLogin, deviceId: const auth = ['/auth/me', '/api/auth/me'].includes(url.pathname) && req.method === 'GET' && !url.search const ingest = url.pathname === '/visible-sessions/ingest' && ['GET', 'POST', 'DELETE'].includes(req.method ?? '') const collaboration = !url.search && ((req.method === 'GET' && /^\/api\/collaboration\/(?:contract|colleagues|inbox|sent|notifications|(?:materials|handoffs)\/[\w-]{1,160}|handoffs\/[\w-]{1,160}\/messages)$/.test(url.pathname)) || (req.method === 'POST' && /^\/api\/collaboration\/(?:materials|handoffs|notifications\/[\w-]{1,160}\/read|handoffs\/[\w-]{1,160}\/(?:messages|complete))$/.test(url.pathname))); - if (!auth && !ingest && !collaboration) { reject(404, 'Unknown Desktop operation'); return } + const extension = !url.search && ['GET', 'POST'].includes(req.method ?? '') && /^\/api\/extensions\/[a-z][a-z0-9-]{0,63}\/[a-z][a-z0-9-]{0,63}$/.test(url.pathname) + if (!auth && !ingest && !collaboration && !extension) { reject(404, 'Unknown Desktop operation'); return } let current: EnterpriseMember try { current = await login.verify() } catch { reject(401, 'Enterprise authorization unavailable') @@ -128,7 +129,7 @@ export async function startEnterpriseAuthority(login: EnterpriseLogin, deviceId: if (auth) { res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ ...current, deviceId, backendUrl: login.backendUrl })); return } - if (collaboration) { + if (collaboration || extension) { try { let body: string | undefined; if(req.method==='POST') { @@ -139,9 +140,9 @@ export async function startEnterpriseAuthority(login: EnterpriseLogin, deviceId: } const response=await login.request(url.pathname,req.method,body); if(closing)return; - if(!response.ok){reject(response.status,'Enterprise collaboration rejected');return} + if(!response.ok){reject(response.status, extension ? 'Enterprise extension rejected' : 'Enterprise collaboration rejected');return} res.writeHead(200,{'Content-Type':'application/json'});res.end(JSON.stringify(await response.json())); - } catch {reject(502,'Enterprise collaboration unavailable')} + } catch {reject(502, extension ? 'Enterprise extension unavailable' : 'Enterprise collaboration unavailable')} return; } try { diff --git a/apps/desktop/tests/enterprise-auth.spec.ts b/apps/desktop/tests/enterprise-auth.spec.ts index 76ea2aa3a6..4ce6fd0cb8 100644 --- a/apps/desktop/tests/enterprise-auth.spec.ts +++ b/apps/desktop/tests/enterprise-auth.spec.ts @@ -127,3 +127,33 @@ describe('Desktop Main enterprise authority', () => { await expect(login.verify()).rejects.toThrow('已退出') }) }) + + describe('enterprise extension transport', () => { + it('forwards only extension operations with Main-owned authentication and rejects other routes', async () => { + const calls: Array<{url: string; init?: RequestInit}> = []; + let active = true; + const request = (async (url: string | URL | Request, init?: RequestInit) => { + calls.push({url: String(url), ...(init ? {init} : {})}); + if (String(url).endsWith('/login')) return json({token, member: actor}); + if (String(url).endsWith('/me')) return json(actor, active ? 200 : 401); + return json({reports: [1]}); + }) as typeof fetch; + const login = await EnterpriseLogin.login('https://company.test', actor.email, 'password', request); + bridge = await startEnterpriseAuthority(login, 'device-a', () => {}, () => {}); + const headers = {Authorization: `Bearer ${bridge.key}`, 'Content-Type': 'application/json'}; + const response = await fetch(`${bridge.url}/api/extensions/reports/list`, {method: 'POST', headers, body: '{"page":1}'}); + expect(await response.json()).toEqual({reports: [1]}); + const forwarded = calls.find(call => call.url.endsWith('/api/extensions/reports/list'))!; + expect(forwarded.init?.headers).toMatchObject({Authorization: `Bearer ${token}`}); + expect(forwarded.init?.body).toBe('{"page":1}'); + for (const path of ['/api/admin/members', '/api/extensions/reports/list?url=https://evil.test', '/api/extensions/reports/list/extra']) { + expect((await fetch(bridge.url + path, {headers})).status).toBe(404); + } + expect((await fetch(`${bridge.url}/api/extensions/reports/list`, {headers: {...headers, Origin: 'https://evil.test'}})).status).toBe(403); + expect((await fetch(`${bridge.url}/api/extensions/reports/list`, {method: 'DELETE', headers})).status).toBe(404); + expect(calls.filter(call => call.url.includes('/api/extensions/'))).toHaveLength(1); + active = false; + expect((await fetch(`${bridge.url}/api/extensions/reports/list`, {headers})).status).toBe(401); + expect(calls.filter(call => call.url.includes('/api/extensions/'))).toHaveLength(1); + }); + }); diff --git a/docs/ENTERPRISE-PLUGIN-AUTH.md b/docs/ENTERPRISE-PLUGIN-AUTH.md new file mode 100644 index 0000000000..e016bee7b5 --- /dev/null +++ b/docs/ENTERPRISE-PLUGIN-AUTH.md @@ -0,0 +1,49 @@ +# 企业业务插件认证接入 + +企业 Desktop 的业务插件注入 `workdshEnterprise`,复用当前成员登录。服务不返回后台 Token、本机桥接密钥或账号密码。此接口由企业身份插件的 Desktop 入口提供;普通个人空间不提供此服务,页面可提示安装企业连接插件并登录。它不是远程企业 Web Host 的认证接口。 + +## 独立插件调用 + +以下代码在插件的 Host 入口执行,不在浏览器页面直接执行。类型从 `workdsh-contracts/enterprise` 导入,Cordis 版本和 DSH 版本须与当前发行包一致。 + +```ts +import type { Context } from '@deepseek-ai/cordis' +import type { EnterpriseService } from 'workdsh-contracts/enterprise' + +declare module '@deepseek-ai/cordis' { + interface Context { workdshEnterprise: EnterpriseService } +} + +export default { + name: 'company-reports', + inject: ['workdshEnterprise'], + async apply(ctx: Context) { + const member = await ctx.workdshEnterprise.identity() + const reports = await ctx.workdshEnterprise.request<{ items: unknown[] }>({ + plugin: 'reports', + operation: 'list', + method: 'POST', + body: { page: 1 }, + }) + // 将结果交给你的业务服务;不要把身份当作后台授权凭据。 + }, +} +``` + +`workdsh-contracts` 当前是仓库内的私有包,并非已发布 npm SDK。仓库内使用 workspace 依赖;独立项目可构建并打包该包,再使用生成的 tgz 作为类型依赖。不要运行 `npm install workdsh-contracts` 并假设公共 registry 已发布。企业插件运行包按现有 GitHub Releases 独立交付。 + +## 后台接口约定 + +上述请求映射为当前公司后台的 `POST /api/extensions/reports/list`,Main 自动补上 `Authorization: Bearer <当前成员登录凭据>`。后台开发者需要实现这个接口,沿用现有成员认证,并从认证上下文确定组织与成员,再检查具体业务权限。SDK 不自动创建报表后台接口。 + +第一版只接受 GET、POST。插件标识和操作名必须是小写字母开头、后续为小写字母/数字/短横线,长度 1~64;不允许任意 URL、查询字符串或嵌套路径。GET 不携带 body;分页与过滤建议使用 POST JSON。请求体上限 8 MiB,返回 JSON;请求超时 10 秒,无自动重试。写操作如需重试,由业务后台实现幂等键。 + +`plugin` 是路由标识,不是可信的插件身份,也不授予权限。所有已安装 Host 插件可调用这个服务,后台必须依当前成员校验权限,不能凭插件名称、客户端传来的组织 ID 或角色授权。安装插件本身不提供成员资格;插件仍属于可信本机代码,服务不是恶意代码沙箱。 + +## 页面与生命周期 + +业务页面通过自己的 `connection.fetch` 受控接口调用 Host 业务服务,再由业务服务调用 `workdshEnterprise`。不要把 Token 下发到页面,不开放一个让页面任意指定操作和路径的转发接口。页面接口应限定业务操作、校验参数及会话访问权限。 + +服务在每次业务请求前后核验当前成员。退出、撤权、身份变化或桥接关闭时请求拒绝;可传入 AbortSignal 取消请求。不要复制凭据或缓存身份作为持久授权。管理员权限接口不在桥接范围内,公司模型内部 Key 不能替代成员登录。 + +此能力从 Desktop `2.0.6-alpha.3` 与企业连接包 `0.1.0-alpha.2` 开始提供;已发布的 2.0.6-alpha.2 不含该接口,仅升级独立业务插件不能使旧 Desktop 获得扩展桥接能力。 diff --git a/packages/contracts/package.json b/packages/contracts/package.json index 16a652527c..c4ff9b4f96 100644 --- a/packages/contracts/package.json +++ b/packages/contracts/package.json @@ -34,6 +34,10 @@ "./projects": { "types": "./dist/projects.d.ts", "default": "./dist/projects.js" + }, + "./enterprise": { + "types": "./dist/enterprise.d.ts", + "default": "./dist/enterprise.js" } }, "files": [ diff --git a/packages/contracts/src/enterprise.ts b/packages/contracts/src/enterprise.ts new file mode 100644 index 0000000000..c19f8b9a52 --- /dev/null +++ b/packages/contracts/src/enterprise.ts @@ -0,0 +1,18 @@ +/** Host-only authenticated enterprise transport. Never exposes backend or bridge credentials. */ +export interface EnterpriseRequest { + plugin: string; + operation: string; + method: 'GET' | 'POST'; + body?: unknown; + signal?: AbortSignal; +} +export interface EnterpriseService { + identity(signal?: AbortSignal): Promise<{ memberId: string; organizationId: string }>; + request