Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
21 changes: 15 additions & 6 deletions .github/workflows/checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -790,17 +790,19 @@ jobs:
desktop:
- 'app/desktop/**'
- 'app/shared/**'
- 'app/workbench/**'
- 'app/package.json'
- 'app/pnpm-lock.yaml'
- '.github/workflows/checks.yml'
web:
- 'app/web/**'
- 'app/shared/**'
- 'app/workbench/**'
- 'app/package.json'
- 'app/pnpm-lock.yaml'
- '.github/workflows/checks.yml'
shell:
- 'app/shared/src/workbench/**'
- 'app/workbench/**'
- 'app/shared/src/styles/**'
- 'app/shared/src/theme*.ts'
- 'app/shared/src/themePresets.*'
Expand All @@ -827,14 +829,15 @@ jobs:
- 'app/package.json'
- 'app/pnpm-lock.yaml'
- '.github/workflows/checks.yml'
# frontend = desktop ∪ web ∪ shared (coverage gate + vuln-scan-js
# trigger). Splits the heavy vitest coverage job out of the
# unconditional validate lane so a Go-only PR no longer pays for
# pnpm install + coverage.
# frontend = desktop ∪ web ∪ shared ∪ workbench (coverage gate +
# vuln-scan-js trigger). Splits the heavy vitest coverage job out
# of the unconditional validate lane so a Go-only PR no longer
# pays for pnpm install + coverage.
frontend:
- 'app/desktop/**'
- 'app/web/**'
- 'app/shared/**'
- 'app/workbench/**'
- 'app/package.json'
- 'app/pnpm-lock.yaml'
- 'app/test-config/**'
Expand Down Expand Up @@ -1420,6 +1423,12 @@ jobs:
- name: Verify Shared Edge-free boundary
run: python ./scripts/verify/verify-shared-boundary.py

- name: Verify frontend package boundary (workbench -> shared only, #1759)
run: python ./scripts/verify/verify-frontend-package-boundary.py

- name: Self-test frontend package boundary gate (negative)
run: python scripts/verify/tests/verify-frontend-package-boundary.Tests.py

- name: Verify Shared barrel Edge-export ban
run: python ./scripts/verify/verify-shared-barrel.py

Expand Down Expand Up @@ -1496,7 +1505,7 @@ jobs:
# The negative include-contract self-test (mobile vitest run) executes
# once, on the mobile leg.
matrix:
package: ['@agenthub/shared', 'agenthub-web', 'agenthub-desktop', 'agenthub-mobile-rn']
package: ['@agenthub/shared', '@agenthub/workbench', 'agenthub-web', 'agenthub-desktop', 'agenthub-mobile-rn']
steps:
- uses: actions/checkout@v7
with:
Expand Down
4 changes: 2 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
| 目录 | 分类 | 职责 |
|---|---|---|
| `api/` | 活 | API 契约 SSOT(openapi.yaml/events.md/conventions.md) |
| `app/` | 活 | 前端 monorepo(web/desktop/mobile-rn/shared) |
| `app/` | 活 | 前端 monorepo(web/desktop/mobile-rn/shared/workbench) |
| artifacts/ dist/ tmp/ | 产物 | 本地构建/临时输出(gitignored) |
| `docs/` | 活 | 知识库(architecture/governance/reference/archives) |
| `edge-server/` | 活 | Go Edge 服务(local runtime、adapters、lifecycle) |
Expand Down Expand Up @@ -62,7 +62,7 @@ Mobile 主线是 Expo + React Native development build。旧 Tauri Mobile 不再
共享边界:

- API 契约写在 `api/`。
- 通用 UI、transcript、composer、inspector、platform contract 放 `app/shared/`。
- 通用 UI、transcript、composer、inspector、platform contract 放 `app/shared/`;端级 workbench shell 放 `app/workbench/`(`@agenthub/workbench`,依赖方向 workbench → shared 单向,#1759)
- Hub REST/WS 方法与 DTO 的 SSOT 是 `app/shared/src/hub/hubClient.ts`(及拆出的 payload/extended 模块)。Desktop/Web/Mobile 的 `app/{desktop,web,mobile-rn}/src/api/hubClient.ts` 只能是 thin shell(平台默认 baseUrl、Tauri proxy、SecureStore token、fixture snapshot、WS URL 等胶水);禁止在客户端再分叉 REST 实现。
- Desktop 只能把 Tauri/Rust native 能力放在 `app/desktop/src-tauri/`。
- Web 只能通过 Hub/Web adapter 访问远端能力,不能直连 Local Edge 或 runtime。
Expand Down
1 change: 1 addition & 0 deletions app/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ python scripts/verify/verify-design-token-ssot.py # 设计 token 门禁(改
## 前端边界(速查,权威在根 AGENTS §2/§5)

- 通用 UI 只在 `shared/src/ui/`;Desktop/Web/Mobile 从 shared 导入,禁止复制本地副本。
- 端级 workbench shell 只在 `workbench/src/`(`@agenthub/workbench`,#1759 独立成包);web/desktop 从 `@agenthub/workbench` 导入,mobile-rn 不依赖。依赖方向 workbench → shared 单向,shared 禁止 import workbench(eslint + `scripts/verify/verify-frontend-package-boundary.py` 双重门禁)。
- `app/shared/src/hub/hubClient.ts` 是 Hub REST/WS 唯一 SSOT;各平台 `api/hubClient.ts` 只做 thin shell,禁止分叉 REST 实现。
- Desktop 原生能力只在 `app/desktop/src-tauri/`;Web/Mobile 是 Hub-only,不直连 Local Edge。
- CSS Modules + OKLCH tokens,避免硬编码颜色。
Expand Down
2 changes: 2 additions & 0 deletions app/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,13 @@ WORKDIR /app
# Install dependencies first (cache layer)
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./
COPY shared/package.json ./shared/package.json
COPY workbench/package.json ./workbench/package.json
COPY web/package.json ./web/package.json
RUN pnpm install --frozen-lockfile

# Copy source and build (vite only — tsc runs in CI)
COPY shared ./shared
COPY workbench ./workbench
COPY web ./web
RUN pnpm --filter agenthub-web exec vite build

Expand Down
2 changes: 2 additions & 0 deletions app/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,14 @@ AgentHub 的全部前端代码,pnpm workspace 管理。Go 后端在 `../hub-se
| `desktop/` | Tauri 桌面端 + Local Edge 宿主(Vite,5173 strict) | `desktop/README.md` |
| `mobile-rn/` | Expo/RN 移动端(Hub-only,5177) | `mobile-rn/README.md` |
| `shared/` | **`@agenthub/shared` SSOT**:通用 UI、`hubClient`、transcript/composer/inspector、platform contract、design token | `shared/README.md` |
| `workbench/` | **`@agenthub/workbench`**:端级工作台 shell(#1759 从 shared 独立);依赖方向 workbench → shared 单向 | `workbench/README.md` |
| `e2e/` | 真实全栈 E2E(`chat-real.spec.ts` 等,需 live Hub+Edge,CI 沙箱不跑) | `e2e/playwright.config.ts` |
| `test-config/` | 前端 coverage 契约 factory(生产源码全量进分母) | `test-config/coverage.ts` |

## SSOT 边界

- 通用 UI 只在 `shared/src/ui/`,web/desktop 从 shared 导入,禁止复制本地 UI 副本。
- 端级 workbench shell 只在 `workbench/src/`(`@agenthub/workbench`);web/desktop 从 `@agenthub/workbench` 导入,mobile-rn 不依赖本包。依赖方向 workbench → shared 单向,shared 禁止 import workbench(机器门禁:`scripts/verify/verify-frontend-package-boundary.py`)。
- Hub REST/WS 方法与 DTO 的 SSOT 是 `shared/src/hub/hubClient.ts`;web/desktop/mobile 的 `api/hubClient.ts` 只能是 thin shell(平台胶水),禁止分叉 REST 实现。
- 设计 token 在 `shared/src/styles/` 与 `shared/src/designTokens.ts`(`--td-*`)。
- 新 shared 组件三件套:`.test.tsx` + `.stories.tsx` + 对照 `../docs/component-acceptance.md` 验收表。
Expand Down
2 changes: 1 addition & 1 deletion app/desktop/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ Desktop UI -> Local Edge Server -> Agent Runtime adapter -> Claude Code / Codex
| Views and slots | `src/views/` |
| State/hooks | `src/stores/`, `src/hooks/` |
| Tauri host | `src-tauri/` |
| Shared UI/workbench/chat | `../shared/src/ui/`, `../shared/src/workbench/`, `../shared/src/chatview/` |
| Shared UI/workbench/chat | `../shared/src/ui/`, `../workbench/src/`, `../shared/src/chatview/` |

## Local Preview

Expand Down
1 change: 1 addition & 0 deletions app/desktop/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
},
"dependencies": {
"@agenthub/shared": "workspace:*",
"@agenthub/workbench": "workspace:*",
"@lobehub/icons": "^5.10.0",
"@tanstack/react-query": "^5.101.4",
"@tauri-apps/api": "^2.11.1",
Expand Down
4 changes: 2 additions & 2 deletions app/desktop/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {
import { CHATVIEW_I18N_NAMESPACE } from '@shared/chatview/i18n/resources';
import type { UnreadDividerDescriptor } from '@shared/chatview';
import { toggleAppliedAgentHubTheme } from '@shared/theme';
import { AgentHubWorkbench } from '@shared/workbench';
import { AgentHubWorkbench } from '@agenthub/workbench';
import { resolveCurrentTranscriptRunId } from '@shared/transcript';
import { getAgentActivityStore } from '@shared/transcript/agentActivity';
import type { ApprovalDecisionAction } from '@shared/transcript';
Expand Down Expand Up @@ -46,7 +46,7 @@ import {
hubAgentProfileToWorkbenchAgent,
} from '@/api/agentProfileQueries';
import { getHubClient } from '@/api/hubQueries';
import type { AgentConfig, ConnectionStatusKind, DocRow, SkillMarketItem, MCPMarketItem } from '@shared/workbench';
import type { AgentConfig, ConnectionStatusKind, DocRow, SkillMarketItem, MCPMarketItem } from '@agenthub/workbench';
import { getDemoRuntimeEvidence } from '@/demo/demoEvidence';
import { useToastStore, ToastContainer } from '@shared/ui/toast';
import { useGlobalKeyboardShortcuts } from '@/hooks/useGlobalKeyboardShortcuts';
Expand Down
2 changes: 1 addition & 1 deletion app/desktop/src/api/agentProfileQueries.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// React Query hooks for Edge Agent Profiles — CRUD operations.
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import type { AgentConfig } from '@shared/workbench';
import type { AgentConfig } from '@agenthub/workbench';
import {
fetchAgentProfiles,
createAgentProfile,
Expand Down
2 changes: 1 addition & 1 deletion app/desktop/src/api/documentQueries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { createHubClient, type HubDocumentListItem } from '@/api/hubClient';
import { getAccessToken } from '@/hooks/useAuth';
import type { DocRow } from '@shared/workbench/pages';
import type { DocRow } from '@agenthub/workbench/pages';

// Lazy singleton — avoids creating the client on module load when Hub is not needed.
let _hubClient: ReturnType<typeof createHubClient> | null = null;
Expand Down
4 changes: 2 additions & 2 deletions app/desktop/src/components/DesktopChrome.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import type { ReactNode } from 'react';
import { getCurrentWindow } from '@tauri-apps/api/window';
import { DesignNavIcon } from '@shared/workbench/designIcons';
import { DesignNavIcon } from '@agenthub/workbench/designIcons';
import {
DESKTOP_NAVIGATE_BACK_EVENT,
DESKTOP_NAVIGATE_FORWARD_EVENT,
DESKTOP_TOGGLE_SIDEBAR_EVENT,
} from '@shared/workbench/desktopChromeEvents';
} from '@agenthub/workbench/desktopChromeEvents';
import styles from './DesktopChrome.module.css';

type WindowCommand = 'minimize' | 'toggleMaximize' | 'close';
Expand Down
2 changes: 1 addition & 1 deletion app/desktop/src/components/DesktopEntryGate.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useState, useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import { DesignNavIcon } from '@shared/workbench';
import { DesignNavIcon } from '@agenthub/workbench';
import { useAuth } from '@/hooks/useAuth';
import { OidcError } from '@/api/hubAuth';
import agentHubLogo from '@/assets/agenthub-product-icon-rounded.svg';
Expand Down
2 changes: 1 addition & 1 deletion app/desktop/src/platform/desktopRuntimeSessions.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { RuntimeSessionImportItem } from '@shared/workbench';
import type { RuntimeSessionImportItem } from '@agenthub/workbench';

/**
* Desktop host: Edge GET /v1/runtime-sessions via typed fetch.
Expand Down
4 changes: 2 additions & 2 deletions app/desktop/src/platform/desktopWorkbenchProjectsPort.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import type {
WorkbenchProjectsPage,
WorkbenchProjectsPort,
} from '@shared/workbench';
import { workspaceProjectToProjectInfo } from '@shared/workbench/hubDataMapping';
} from '@agenthub/workbench';
import { workspaceProjectToProjectInfo } from '@agenthub/workbench/hubDataMapping';
import { getHubClient } from '@/api/hubQueries';

/**
Expand Down
8 changes: 4 additions & 4 deletions app/desktop/src/platform/useDesktopWorkbenchModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,15 @@ import { getAgentActivityStore, type AgentActivitySnapshot } from '@shared/trans
import { computeTranscriptUnreadMarker, type TranscriptUnreadMarker } from '@/components/IM/transcriptUnreadMarker';
import type { WorkbenchAgent, WorkbenchConversation } from '@shared/platform';
import type { ThreadInfo, ThreadItemInfo, ThreadPinInfo } from '@shared/types';
import type { ProjectDraft, ProjectInfo } from '@shared/workbench';
import type { WorkbenchContactsData } from '@shared/workbench';
import type { WorkbenchContactsActions } from '@shared/workbench/WorkbenchRoutes';
import type { ProjectDraft, ProjectInfo } from '@agenthub/workbench';
import type { WorkbenchContactsData } from '@agenthub/workbench';
import type { WorkbenchContactsActions } from '@agenthub/workbench/WorkbenchRoutes';
import {
resolveHubContacts,
resolveHubProjects,
hubSessionToConversation,
type HubContactLike,
} from '@shared/workbench/hubDataMapping';
} from '@agenthub/workbench/hubDataMapping';
import { useThreadMessages, useThreadPins, useThreads } from '@/api/threadQueries';
import { useHubSessions, useHubMessages, useHubPinnedMessages, useHubSendMessage, useHubRecallMessage, useHubEditMessage, useHubPinMessage, useHubUnpinMessage, useHubMarkRead } from '@/api/sessionQueries';
import {
Expand Down
2 changes: 1 addition & 1 deletion app/desktop/vitest.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export default defineConfig({
test: {
globals: true,
environment: 'jsdom',
include: ['src/**/*.test.ts', 'src/**/*.test.tsx', '../shared/src/**/*.test.ts', '../shared/src/**/*.test.tsx'],
include: ['src/**/*.test.ts', 'src/**/*.test.tsx', '../shared/src/**/*.test.ts', '../shared/src/**/*.test.tsx', '../workbench/src/**/*.test.ts', '../workbench/src/**/*.test.tsx'],
setupFiles: ['./src/__tests__/setup.ts'],
execArgv: ['--max-old-space-size=8192'],
maxWorkers: 4,
Expand Down
9 changes: 8 additions & 1 deletion app/desktop/vitest.shared-ci.config.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { defineConfig } from 'vitest/config';
import path from 'path';

// 兄弟包单测宿主配置(desktop 环境跑 shared + workbench 两套 suite)。
// 名字叫 shared-ci 是历史原因(#1759 第二阶段起同时承载 workbench)。
export default defineConfig({
resolve: {
dedupe: ['react', 'react-dom'],
Expand All @@ -21,7 +23,12 @@ export default defineConfig({
test: {
globals: true,
environment: 'jsdom',
include: ['../shared/src/**/*.test.ts', '../shared/src/**/*.test.tsx'],
include: [
'../shared/src/**/*.test.ts',
'../shared/src/**/*.test.tsx',
'../workbench/src/**/*.test.ts',
'../workbench/src/**/*.test.tsx',
],
setupFiles: ['./src/__tests__/setup.ts'],
execArgv: ['--max-old-space-size=4096'],
memoryLimit: '512MB',
Expand Down
21 changes: 21 additions & 0 deletions app/eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,27 @@ export default tseslint.config(
},
},

// 依赖方向门禁(#1759):shared 是跨端原语层,永远不得 import workbench
// 包(workbench → shared 单向)。相对路径越界由
// scripts/verify/verify-frontend-package-boundary.py 兜底。
{
files: ["shared/**/*.{ts,tsx,js,jsx,mjs}"],
rules: {
"no-restricted-imports": [
"error",
{
patterns: [
{
group: ["@agenthub/workbench", "@agenthub/workbench/*", "@workbench", "@workbench/*"],
message:
"shared must never import workbench — dependency direction is workbench -> shared only (#1759).",
},
],
},
],
},
},

// React Hooks rules — only for UI packages
{
files: ["desktop/**/*.{tsx,jsx}", "web/**/*.{tsx,jsx}", "mobile-rn/**/*.{tsx,jsx}"],
Expand Down
3 changes: 3 additions & 0 deletions app/mobile-rn/scripts/verify-boundaries.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ const forbiddenImportFragments = [
'.module.css',
'@agenthub/shared/ui',
'@agenthub/shared/workbench',
// #1759:workbench 独立成包后 Mobile 同样不得依赖(Hub-only,
// bundle 不得包含端级 workbench 巨石)。
'@agenthub/workbench',
];

const forbiddenRuntimePatterns = [
Expand Down
5 changes: 4 additions & 1 deletion app/mobile-rn/src/importBoundary.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ const projectRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '
const boundaryScript = path.join(projectRoot, 'scripts', 'verify-boundaries.mjs');

describe('Mobile RN import boundary verifier', () => {
it('rejects shared workbench imports and browser storage in runtime source', async () => {
it('rejects workbench package imports and browser storage in runtime source', async () => {
const fixtureRoot = await mkdtemp(path.join(tmpdir(), 'agenthub-mobile-boundary-'));

try {
Expand All @@ -18,10 +18,12 @@ describe('Mobile RN import boundary verifier', () => {
path.join(fixtureRoot, 'src', 'bad-runtime.ts'),
[
['im', "port { AgentHubWorkbench } from '@agenthub/shared/workbench';"].join(''),
['im', "port { WorkbenchRoutes } from '@agenthub/workbench';"].join(''),
'export function readUnsafeStorage() {',
` return ${['local', 'Storage'].join('')}.getItem("hub");`,
'}',
'void AgentHubWorkbench;',
'void WorkbenchRoutes;',
'',
].join('\n'),
'utf8',
Expand All @@ -31,6 +33,7 @@ describe('Mobile RN import boundary verifier', () => {

expect(result.exitCode).not.toBe(0);
expect(result.stderr).toContain('@agenthub/shared/workbench');
expect(result.stderr).toContain('@agenthub/workbench');
expect(result.stderr).toContain('localStorage');
} finally {
await rm(fixtureRoot, { force: true, recursive: true });
Expand Down
Loading
Loading