From 097177f4c023ddb9144be0c396642f6d1d297fcd Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Mon, 14 Sep 2026 06:10:35 +0000 Subject: [PATCH 1/2] fix(xyai-desktop): stop overlapping chrome and wire space nav Hide the official DSH hero beside the XYAI welcome using stable data-attribute CSS, stack sidebar product-nav as a column, and show an in-shell copyable URL when Electron dsh-app cannot embed remote https pages. About Us keeps srcDoc. Model hub and knowledge stay in the base patch; AI-employee Host loads without Agent Teams and refuses team RPC. Co-authored-by: XYAI Labs --- ...-09-14-xyai-desktop-shell-chrome.i18n.yaml | 6 + .../2026-09-14-xyai-desktop-shell-chrome.md | 31 +++++ ...2026-09-14-xyai-desktop-shell-chrome.zh.md | 31 +++++ .../xyai-ai-employees/README.i18n.yaml | 4 +- packages-xyai/xyai-ai-employees/README.md | 5 +- packages-xyai/xyai-ai-employees/README.zh.md | 5 +- .../xyai-ai-employees/src/client/plugin.tsx | 5 + packages-xyai/xyai-ai-employees/src/index.ts | 41 ++++-- .../xyai-ai-employees/tests/backend.spec.ts | 20 ++- .../xyai-brand-pack/README.i18n.yaml | 6 +- packages-xyai/xyai-brand-pack/README.md | 1 + packages-xyai/xyai-brand-pack/README.zh.md | 1 + packages-xyai/xyai-brand-pack/package.json | 3 +- .../xyai-brand-pack/src/client/hero-layout.ts | 75 +++++++++++ .../xyai-brand-pack/src/client/locales.ts | 2 + .../xyai-brand-pack/src/client/plugin.tsx | 17 +-- .../xyai-brand-pack/tests/hero-layout.spec.ts | 16 +++ .../xyai-brand-pack/vitest.config.ts | 15 +++ packages-xyai/xyai-collab/README.md | 4 +- packages-xyai/xyai-dev-shell/README.i18n.yaml | 6 +- packages-xyai/xyai-dev-shell/README.md | 6 +- packages-xyai/xyai-dev-shell/README.zh.md | 6 +- .../xyai-dev-shell/src/client/chrome.ts | 107 +++++++++++++++ .../xyai-dev-shell/src/client/locales.ts | 8 ++ .../xyai-dev-shell/src/client/plugin.tsx | 126 ++++++++++++++---- .../xyai-dev-shell/tests/chrome.spec.ts | 56 ++++++++ .../tests/plugin.client.spec.tsx | 87 ++++++++++++ packages-xyai/xyai-dev-shell/vitest.config.ts | 1 + .../xyai-knowledge/src/client/plugin.tsx | 5 + .../xyai-model-hub/src/client/index.ts | 6 + 30 files changed, 636 insertions(+), 66 deletions(-) create mode 100644 .agents/notes/implemented/bug-fix/2026-09-14-xyai-desktop-shell-chrome.i18n.yaml create mode 100644 .agents/notes/implemented/bug-fix/2026-09-14-xyai-desktop-shell-chrome.md create mode 100644 .agents/notes/implemented/bug-fix/2026-09-14-xyai-desktop-shell-chrome.zh.md create mode 100644 packages-xyai/xyai-brand-pack/src/client/hero-layout.ts create mode 100644 packages-xyai/xyai-brand-pack/tests/hero-layout.spec.ts create mode 100644 packages-xyai/xyai-brand-pack/vitest.config.ts create mode 100644 packages-xyai/xyai-dev-shell/src/client/chrome.ts create mode 100644 packages-xyai/xyai-dev-shell/tests/chrome.spec.ts diff --git a/.agents/notes/implemented/bug-fix/2026-09-14-xyai-desktop-shell-chrome.i18n.yaml b/.agents/notes/implemented/bug-fix/2026-09-14-xyai-desktop-shell-chrome.i18n.yaml new file mode 100644 index 00000000..0ae50f68 --- /dev/null +++ b/.agents/notes/implemented/bug-fix/2026-09-14-xyai-desktop-shell-chrome.i18n.yaml @@ -0,0 +1,6 @@ +# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each +# side as of the last confirmed-consistent state. Both languages carry equal authority; +# after editing either side, bring the other along and re-record with: +# pnpm run verify-translation-pairing --write .agents/notes/implemented/bug-fix/2026-09-14-xyai-desktop-shell-chrome.md +2026-09-14-xyai-desktop-shell-chrome.md: b621aa06d43537e7bebec373d753dcd6aec25c5f +2026-09-14-xyai-desktop-shell-chrome.zh.md: 2ac5d4d9fed84de7809d04f329cfd28d8c3caa77 diff --git a/.agents/notes/implemented/bug-fix/2026-09-14-xyai-desktop-shell-chrome.md b/.agents/notes/implemented/bug-fix/2026-09-14-xyai-desktop-shell-chrome.md new file mode 100644 index 00000000..b621aa06 --- /dev/null +++ b/.agents/notes/implemented/bug-fix/2026-09-14-xyai-desktop-shell-chrome.md @@ -0,0 +1,31 @@ +# Agent Note: XYAI desktop shell chrome + +Status: implemented + +English | [中文](2026-09-14-xyai-desktop-shell-chrome.zh.md) + +## Problem + +XYAI Studio 0.4 desktop cold-starts with the window title already branded, but the renderer shows two overlapping product UIs: official DSH hero copy under the XYAI welcome, a leftover boot ring, a floating 模型广场 chip in the sidebar, and product space tabs that do nothing except 关于我们. + +## Decision + +Hero layout CSS keys off `[data-xyai-hero-welcome]`: it expands the fish hitbox, hides the adjacent official headline group, and hides leftover `[data-dsh-boot]` only when `[data-slot="root"]` exists. Sidebar footer `action` slots that hold product-nav or the interact list override `display: contents` into a column so 模型广场 cannot sit beside an empty interact block. Space overlays are siblings of the top bar, not children of a zero-height pointer-events-none wrapper. Electron `dsh-app:` windows cannot host remote `https:` iframes, so 业务/生态/浏览器 render an in-shell copyable address; 关于我们 keeps its `srcDoc` document. Model hub and knowledge stay in `@xyai/dsh-product-base` because their Host inject lists do not wait on `webServer`. AI employees stay in `@xyai/dsh-product-collab`; Host `agentTeams` is optional at load so the employee library activates on desktop, while team spawn, mailbox, and task RPC refuse with `AGENT_TEAMS_UNAVAILABLE` when Agent Teams is absent. Each of those three client plugins sets `data-xyai-surface-*` on ``; product nav disables a destination until that attribute appears. + +## Alternatives considered + +**Hashed CSS-module class selectors.** Rebuilds change `HeroShell.module.css` hashes, so `.pXSMma_headline` and the removed `.pXSMma_headlineText` cannot hide official copy after a DSH UI rebuild. + +**Emptying `xyai-collab/cordis.patch.yml` on desktop.** The web XYAI profile still mounts AI employees through that bundle, and composition tests require the insert. Optional `agentTeams` keeps the library alive without deleting the collab layer. + +**`shell.openExternal` IPC for remote spaces.** The desktop Electron main process has no such channel yet. A copyable in-shell address is honest without inventing a Host API in this change. + +**Forking DSH EmptyHero.** XYAI remains a patch-layer product; data-attribute CSS plus a locale-owned kicker keep the official conversation hero intact for other profiles. + +## Consequences + +开发空间 keeps DSH new-session, workspace, and composer chrome without a second headline. 关于我们 continues to work offline. 业务/生态/浏览器 are reachable as copyable URLs on desktop and as iframes on ordinary `http(s)` pages. Sidebar destinations degrade to disabled controls instead of stacked dead chips when a plugin is not in the composition. Desktop still does not compose experimental Agent Teams, so collaboration team operations remain unavailable there until a later profile change. Electron still has no `openExternal` path. + +## Testing + +Brand-pack `hero-layout.spec.ts` pins selector stability. Dev-shell `chrome.spec.ts` and `plugin.client.spec.tsx` cover remote-embed refusal, About `srcDoc`, nav disable/enable, and empty interact compaction. AI-employees `backend.spec.ts` covers library RPC without Agent Teams. diff --git a/.agents/notes/implemented/bug-fix/2026-09-14-xyai-desktop-shell-chrome.zh.md b/.agents/notes/implemented/bug-fix/2026-09-14-xyai-desktop-shell-chrome.zh.md new file mode 100644 index 00000000..2ac5d4d9 --- /dev/null +++ b/.agents/notes/implemented/bug-fix/2026-09-14-xyai-desktop-shell-chrome.zh.md @@ -0,0 +1,31 @@ +# Agent Note: XYAI 桌面壳层 chrome + +Status: implemented + +[English](2026-09-14-xyai-desktop-shell-chrome.md) | 中文 + +## Problem + +XYAI Studio 0.4 桌面冷启动后窗口标题已经品牌化,但渲染层叠了两套产品界面:XYAI 欢迎区下面露出官方 DSH 首屏文案、残留启动环、侧栏中悬浮的「模型广场」,以及除「关于我们」外无响应的产品空间页。 + +## Decision + +首屏布局 CSS 以 `[data-xyai-hero-welcome]` 为键:撑开鱼标命中盒,隐藏相邻官方标题组,并且仅在存在 `[data-slot="root"]` 时隐藏残留的 `[data-dsh-boot]`。侧栏底部承载产品导航或互动列表的 `action` 插槽把 `display: contents` 改回纵向排列,避免「模型广场」贴在空的互动块旁边。空间浮层是顶栏的兄弟节点,而不是零高度且 `pointer-events: none` 包裹器的子节点。Electron `dsh-app:` 窗口无法承载远程 `https:` iframe,因此业务/生态/浏览器渲染可复制地址的壳内面板;关于我们继续使用 `srcDoc` 文档。模型广场与知识库留在 `@xyai/dsh-product-base`,因为它们的 Host inject 并不等待 `webServer`。AI 员工留在 `@xyai/dsh-product-collab`;Host 加载时 `agentTeams` 可选,以便桌面上激活员工库,而在缺少 Agent Teams 时队友拉起、邮箱和任务 RPC 以 `AGENT_TEAMS_UNAVAILABLE` 拒绝。这三个客户端插件各自在 `` 上设置 `data-xyai-surface-*`;产品导航在该属性出现之前禁用对应入口。 + +## Alternatives considered + +**哈希后的 CSS module 类选择器。** 重新构建会改变 `HeroShell.module.css` 哈希,因此 `.pXSMma_headline` 以及已经不存在的 `.pXSMma_headlineText` 无法在 DSH UI 重建后隐藏官方文案。 + +**在桌面上清空 `xyai-collab/cordis.patch.yml`。** Web 版 XYAI Profile 仍通过该包挂载 AI 员工,组合测试也要求这条 insert。让 `agentTeams` 可选可以保住员工库,而不删除协作层。 + +**为远程空间增加 `shell.openExternal` IPC。** 桌面 Electron 主进程尚无该通道。可复制的壳内地址在这次改动里不虚构 Host API。 + +**分叉 DSH EmptyHero。** XYAI 仍是补丁层产品;数据属性 CSS 加上语言包所有的导语,让其他 Profile 继续使用官方对话首屏。 + +## Consequences + +开发空间保留 DSH 的新建会话、工作区和输入框,而不再出现第二套标题。「关于我们」在离线时仍可用。业务/生态/浏览器在桌面上以可复制 URL 到达,在普通 `http(s)` 页面上仍用 iframe。插件不在组合中时,侧栏入口降级为禁用控件,而不是叠在一起的失效芯片。桌面仍不组合实验性 Agent Teams,因此在后续 Profile 变更之前,协作中的团队操作在桌面上仍不可用。Electron 仍没有 `openExternal` 路径。 + +## Testing + +品牌包 `hero-layout.spec.ts` 锁定选择器稳定性。开发壳 `chrome.spec.ts` 与 `plugin.client.spec.tsx` 覆盖拒绝远程嵌入、关于我们 `srcDoc`、导航禁用/启用,以及空互动列表的压缩。AI 员工 `backend.spec.ts` 覆盖没有 Agent Teams 时的员工库 RPC。 diff --git a/packages-xyai/xyai-ai-employees/README.i18n.yaml b/packages-xyai/xyai-ai-employees/README.i18n.yaml index 2cafaa50..4ef713e4 100644 --- a/packages-xyai/xyai-ai-employees/README.i18n.yaml +++ b/packages-xyai/xyai-ai-employees/README.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write packages-xyai/xyai-ai-employees/README.md -README.md: 90bc8a6a10f99b16d2983a523626bf5a73d2001b -README.zh.md: 9e339e738a5f147113b6673f327f2eba75741df3 +README.md: 17cd99e7e59809b2b52cef259dfa32a8bd2c4088 +README.zh.md: 2b910e0907c196db36a724f4fb6b9a9ca44a2ea4 diff --git a/packages-xyai/xyai-ai-employees/README.md b/packages-xyai/xyai-ai-employees/README.md index 90bc8a6a..17cd99e7 100644 --- a/packages-xyai/xyai-ai-employees/README.md +++ b/packages-xyai/xyai-ai-employees/README.md @@ -38,7 +38,7 @@ Choose this plugin when an XYAI desktop profile already composes DSH Agent Teams name: '@xyai/dsh-ai-employees' ``` -This package has no plugin config fields. It requires the `connection`, `settings`, `agents`, and `agentTeams` Host services; the browser half requires slots, locale, connection, and Session controllers. The XYAI profile supplies these dependencies. +This package has no plugin config fields. It requires the `connection`, `settings`, and `agents` Host services; the browser half requires slots, locale, connection, and Session controllers. Team spawn, mailbox, and task RPC need Host `agentTeams` and refuse with a clear error when that service is absent. The XYAI web profile supplies Agent Teams; the desktop seed does not. ----- @@ -48,7 +48,7 @@ This package has no plugin config fields. It requires the `connection`, `setting
Implementation internals — click to expand -The Host stores the employee library, draft revisions, and single-chat bindings in the `xyai-ai-team` settings namespace. Its Connection RPC validates browser payloads and delegates teammate, mailbox, and task operations to `ctx.agentTeams`. The browser registers a full conversation View, a composer entry that opens the collaboration dialog, and a team-aware rename control. +The Host stores the employee library, draft revisions, and single-chat bindings in the `xyai-ai-team` settings namespace. Its Connection RPC validates browser payloads and, when `agentTeams` is present, delegates teammate, mailbox, and task operations to it. The browser registers a full conversation View, a composer entry that opens the collaboration dialog, and a team-aware rename control. | File | Purpose | |---|---| @@ -92,6 +92,7 @@ Stable published employee instructions remain at the front of a fresh teammate S - Scheduled routines remain employee configuration; this package does not register a scheduler job. - Marketplace installation and online employee synchronization require their owning commerce or online adapters. - Single-chat reuse applies while the bound Session remains in the browser Session list; a deleted Session creates a new binding target on the next start. +- Team spawn, mailbox, and task operations require Host `agentTeams` and refuse when the desktop seed omits experimental Agent Teams. - The package publishes no `./invariant` because Loader composition tests and the DSH registries directly observe every owned registration. diff --git a/packages-xyai/xyai-ai-employees/README.zh.md b/packages-xyai/xyai-ai-employees/README.zh.md index 9e339e73..2b910e09 100644 --- a/packages-xyai/xyai-ai-employees/README.zh.md +++ b/packages-xyai/xyai-ai-employees/README.zh.md @@ -38,7 +38,7 @@ kind: "package-reference" name: '@xyai/dsh-ai-employees' ``` -本包没有插件配置字段。它需要 Host 侧的 `connection`、`settings`、`agents` 和 `agentTeams` 服务;浏览器半需要插槽、语言、连接和 Session 控制器。XYAI Profile 提供这些依赖。 +本包没有插件配置字段。它需要 Host 侧的 `connection`、`settings` 和 `agents` 服务;浏览器半需要插槽、语言、连接和 Session 控制器。队友拉起、邮箱和任务 RPC 需要 Host `agentTeams`,该服务缺失时以明确错误拒绝。XYAI Web Profile 提供 Agent Teams;桌面种子不提供。 ----- @@ -48,7 +48,7 @@ kind: "package-reference"
实现内部——点击展开 -Host 把员工库、草稿版本和单聊绑定存入 `xyai-ai-team` 设置命名空间。Connection RPC 校验浏览器载荷,并把队友、邮箱和任务操作委托给 `ctx.agentTeams`。浏览器注册完整的对话 View、打开协作对话框的输入框入口,以及团队感知的改名控件。 +Host 把员工库、草稿版本和单聊绑定存入 `xyai-ai-team` 设置命名空间。Connection RPC 校验浏览器载荷,并在存在 `agentTeams` 时把队友、邮箱和任务操作委托给它。浏览器注册完整的对话 View、打开协作对话框的输入框入口,以及团队感知的改名控件。 | 文件 | 用途 | |---|---| @@ -92,6 +92,7 @@ Host 把员工库、草稿版本和单聊绑定存入 `xyai-ai-team` 设置命 - 例行任务仍是员工配置;本包不注册调度器任务。 - 市场安装和线上员工同步需要所属的商业或线上适配器。 - 当绑定的 Session 仍在浏览器 Session 列表中时才复用单聊;删除 Session 后,下次启动会创建新的绑定目标。 +- 队友拉起、邮箱和任务操作需要 Host `agentTeams`;桌面种子省略实验性 Agent Teams 时这些操作会被拒绝。 - 本包不发布 `./invariant`,因为 Loader 组合测试和 DSH 注册表直接观测每项自有注册。 diff --git a/packages-xyai/xyai-ai-employees/src/client/plugin.tsx b/packages-xyai/xyai-ai-employees/src/client/plugin.tsx index 85c4fa6d..c8d33917 100644 --- a/packages-xyai/xyai-ai-employees/src/client/plugin.tsx +++ b/packages-xyai/xyai-ai-employees/src/client/plugin.tsx @@ -233,4 +233,9 @@ export function apply(ctx: Context): void { ctx.slots.inject('conversation.view', () => ctx.slots.register({ name: 'conversation.view', id: 'xyai-ai-team', order: 35, locale: NS, label: () => ctx.locale.bind(NS)('view.title'), inject: shared }, function AiTeamView(props) { return })) ctx.slots.inject('conversation.input.left', () => ctx.slots.register({ name: 'conversation.input.left', id: 'xyai-ai-employee-picker', order: 10, locale: NS }, function PickerChip(props) { return })) ctx.slots.inject('conversation.session.header.utilities', () => ctx.slots.register({ name: 'conversation.session.header.utilities', id: 'xyai-ai-team-rename', order: 15, locale: NS, inject: shared }, RenameGroup)) + ctx.effect(() => { + if (typeof document === 'undefined') return () => {} + document.documentElement.setAttribute('data-xyai-surface-employees', '') + return () => document.documentElement.removeAttribute('data-xyai-surface-employees') + }, 'xyai-ai-employees: surface flag') } diff --git a/packages-xyai/xyai-ai-employees/src/index.ts b/packages-xyai/xyai-ai-employees/src/index.ts index 8d9fe5ec..05fc2c39 100644 --- a/packages-xyai/xyai-ai-employees/src/index.ts +++ b/packages-xyai/xyai-ai-employees/src/index.ts @@ -22,6 +22,9 @@ const MAX_DOCUMENT_BYTES = 1_000_000 const MAX_EMPLOYEES_PER_TEAM = 6 const MAX_OUTCOMES = 200 +/** Thrown when team spawn/send/task RPC runs without a Host Agent Teams service. */ +export const AGENT_TEAMS_UNAVAILABLE = 'Agent Teams is not available on this Host' + const defaults = (instructions: string, knowledge: string[], tools: string[]): EmployeeSettings => ({ instructions, memory: '', @@ -140,11 +143,29 @@ export class AiTeamBackend { private readonly section: SettingsScope private writes: Promise = Promise.resolve() - /** @param ctx - Host context providing settings, live agents, and Agent Teams. */ + /** @param ctx - Host context providing settings, live agents, and optional Agent Teams. */ constructor(private readonly ctx: Context) { this.section = ctx.settings.register('xyai-ai-team', sectionSchema) } + private agentTeams() { + let teams: Context['agentTeams'] | undefined + if (typeof this.ctx.get === 'function') { + teams = this.ctx.get('agentTeams') as Context['agentTeams'] | undefined + } + if (teams === undefined) { + try { + teams = this.ctx.agentTeams + } catch { + // Host did not provide Agent Teams; employee library RPC still runs. + } + } + if (teams === undefined || typeof teams.listMembers !== 'function') { + throw new Error(AGENT_TEAMS_UNAVAILABLE) + } + return teams + } + /** Dispatch one validated browser request. */ async dispatch(endpoint: string, payload: unknown): Promise> { try { @@ -245,7 +266,8 @@ export class AiTeamBackend { private teamView(payload: unknown) { const agent = this.agent(payload) - return { members: this.ctx.agentTeams.listMembers(agent), tasks: this.ctx.agentTeams.listTasks(agent) } + const teams = this.agentTeams() + return { members: teams.listMembers(agent), tasks: teams.listTasks(agent) } } private async start(payload: unknown): Promise { @@ -256,7 +278,8 @@ export class AiTeamBackend { const context = fields.context === 'fork' ? 'fork' : 'fresh' const library = this.library() const byId = new Map(library.employees.map(employee => [employee.id, employee])) - const existingNames = new Set(this.ctx.agentTeams.listMembers(agent).map(member => member.name)) + const teams = this.agentTeams() + const existingNames = new Set(teams.listMembers(agent).map(member => member.name)) const result: TeamStartResult = { started: [], existing: [], failed: [] } for (const id of ids) { const employee = byId.get(id) @@ -265,7 +288,7 @@ export class AiTeamBackend { const settings = employee.published const prompt = [settings.instructions, settings.memory && `项目记忆:\n${settings.memory}`, settings.skills.length > 0 && `优先技能:${settings.skills.join('、')}`, settings.routines.length > 0 && `例行职责:${settings.routines.join(';')}`, settings.integrations.length > 0 && `可用集成:${settings.integrations.join('、')}`].filter(Boolean).join('\n\n') try { - await this.ctx.agentTeams.spawnTeammate(agent, { name: employee.id, description: employee.description, prompt: [{ type: 'text', text: prompt }], context, provider: context === 'fork' ? 'fork' : 'spawn', signal: AbortSignal.timeout(60_000) }) + await teams.spawnTeammate(agent, { name: employee.id, description: employee.description, prompt: [{ type: 'text', text: prompt }], context, provider: context === 'fork' ? 'fork' : 'spawn', signal: AbortSignal.timeout(60_000) }) result.started.push(id); existingNames.add(employee.id) } catch (error) { result.failed.push({ employeeId: id, message: error instanceof Error ? error.message : String(error) }) } } @@ -274,12 +297,12 @@ export class AiTeamBackend { private async send(payload: unknown) { const fields = fieldsOf(payload) - return await this.ctx.agentTeams.sendMessage(this.agent(payload), { target: requiredString(fields, 'target', 80), content: [{ type: 'text', text: requiredString(fields, 'message', 65_000) }], signal: AbortSignal.timeout(30_000) }) + return await this.agentTeams().sendMessage(this.agent(payload), { target: requiredString(fields, 'target', 80), content: [{ type: 'text', text: requiredString(fields, 'message', 65_000) }], signal: AbortSignal.timeout(30_000) }) } private async createTask(payload: unknown) { const fields = fieldsOf(payload) - return await this.ctx.agentTeams.createTask(this.agent(payload), { subject: requiredString(fields, 'subject', 200), description: requiredString(fields, 'description', 4_000), writeScopes: strings(fields.writeScopes ?? [], 'writeScopes') }) + return await this.agentTeams().createTask(this.agent(payload), { subject: requiredString(fields, 'subject', 200), description: requiredString(fields, 'description', 4_000), writeScopes: strings(fields.writeScopes ?? [], 'writeScopes') }) } private async updateTask(payload: unknown) { @@ -293,7 +316,7 @@ export class AiTeamBackend { action: action as typeof allowed[number], ...(action === 'edit' ? { subject: requiredString(fields, 'subject', 200), description: requiredString(fields, 'description', 4_000) } : {}), } - return await this.ctx.agentTeams.updateTask(this.agent(payload), request) + return await this.agentTeams().updateTask(this.agent(payload), request) } private outcomes(payload: unknown): TeamOutcome[] { @@ -341,8 +364,8 @@ export class AiTeamBackend { } } -/** Services required for employee persistence and executable team operations. */ -export const inject = ['connection', 'settings', 'agents', 'agentTeams'] +/** Services required for employee persistence. Agent Teams is optional at load. */ +export const inject = ['connection', 'settings', 'agents'] /** Register the AI Team RPC channel on the plugin lifetime. */ export function apply(ctx: Context): void { diff --git a/packages-xyai/xyai-ai-employees/tests/backend.spec.ts b/packages-xyai/xyai-ai-employees/tests/backend.spec.ts index 9dce72a8..73e21c46 100644 --- a/packages-xyai/xyai-ai-employees/tests/backend.spec.ts +++ b/packages-xyai/xyai-ai-employees/tests/backend.spec.ts @@ -1,5 +1,5 @@ import { describe, expect, it, vi } from 'vitest' -import { AiTeamBackend } from '../lib/index.js' +import { AGENT_TEAMS_UNAVAILABLE, AiTeamBackend } from '../src/index.ts' import type { AiTeamAnswer, EmployeeLibrary, TeamOutcome } from '../src/protocol.ts' function bench() { @@ -94,4 +94,22 @@ describe('AiTeamBackend', () => { expect(accepted).toMatchObject({ revision: 4, status: 'accepted' }) expect(await value(b.backend.dispatch('outcomes/list', { sessionId: 'lead' }))).toEqual([accepted]) }) + + it('serves the employee library without Agent Teams and refuses team operations', async () => { + let section: { revision?: number; employeesJson?: string; outcomesJson?: string } = {} + const lead = { id: 'lead' } + const backend = new AiTeamBackend({ + settings: { register: vi.fn(() => ({ get: () => section, replace: vi.fn(), update: vi.fn(async (next: object) => { section = { ...section, ...next } }) })) }, + agents: { get: vi.fn((id: string) => id === 'lead' ? lead : undefined) }, + } as never) + const library = await value(backend.dispatch('employees/list', null)) + expect(library.employees).toHaveLength(6) + const started = await backend.dispatch('team/start', { sessionId: 'lead', employeeIds: ['architect'], context: 'fork' }) + expect(started.ok).toBe(true) + if (!started.ok) throw new Error(started.error.message) + const answer = started.value as AiTeamAnswer + expect(answer.ok).toBe(false) + if (answer.ok) throw new Error('expected refusal') + expect(answer.error.message).toBe(AGENT_TEAMS_UNAVAILABLE) + }) }) diff --git a/packages-xyai/xyai-brand-pack/README.i18n.yaml b/packages-xyai/xyai-brand-pack/README.i18n.yaml index b0dbda45..9983fc88 100644 --- a/packages-xyai/xyai-brand-pack/README.i18n.yaml +++ b/packages-xyai/xyai-brand-pack/README.i18n.yaml @@ -1,6 +1,6 @@ # Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write packages/client/xyai-brand-pack/README.md -README.md: 69e3854423a2493e084f601ebf5095196719cc30 -README.zh.md: 5ebc7061b4ed438ae141daa55b203b4a328a4bcd +# pnpm run verify-translation-pairing --write packages-xyai/xyai-brand-pack/README.md +README.md: 7f73b76a1eb2c58794bba50a468583ea703e1257 +README.zh.md: a1de37a4f6f19469c5e05136410d1d8d4e45fb1e diff --git a/packages-xyai/xyai-brand-pack/README.md b/packages-xyai/xyai-brand-pack/README.md index 1a42c97b..7f73b76a 100644 --- a/packages-xyai/xyai-brand-pack/README.md +++ b/packages-xyai/xyai-brand-pack/README.md @@ -29,6 +29,7 @@ None; this package neither assembles nor sends a provider request. ## Known Limitations and Deferred Work - **Writable settings required** — brand editing is disabled on connections without a writable Host settings scope. +- **Hero layout uses data attributes** — welcome CSS targets `[data-xyai-hero-welcome]` and hides the adjacent official headline group; hashed CSS-module class names are not selectors. - **Per-tenant branding not provided** — tenant-isolated brand provisioning belongs to the `xyai-tenancy` seam. - **Body text color is single-valued** — when set it applies to both light and dark themes; leave empty to keep the theme defaults. - **No `./invariant`** — slot ownership and settings validation are owned by their DSH services; this package owns no independently diverging runtime observations. diff --git a/packages-xyai/xyai-brand-pack/README.zh.md b/packages-xyai/xyai-brand-pack/README.zh.md index 9b0f5408..a1de37a4 100644 --- a/packages-xyai/xyai-brand-pack/README.zh.md +++ b/packages-xyai/xyai-brand-pack/README.zh.md @@ -29,6 +29,7 @@ Host 注册经过验证的 `xyai-brand` 设置命名空间:品牌名、标识 ## 已知限制与延期工作 - **需要可写设置**:连接没有可写的 Host 设置作用域时禁用品牌编辑。 +- **首屏布局使用数据属性**:欢迎区 CSS 针对 `[data-xyai-hero-welcome]`,并隐藏相邻的官方标题组;不以哈希后的 CSS module 类名为选择器。 - **未提供租户级品牌**:按租户隔离的品牌配置属于 `xyai-tenancy` 边界。 - **正文色为单值**:配置后同时应用于明/暗主题;留空则跟随主题默认。 - **未发布 `./invariant`**:插槽归属与设置验证由对应的 DSH 服务负责,本包没有可独立发生偏离的运行时观测关系。 diff --git a/packages-xyai/xyai-brand-pack/package.json b/packages-xyai/xyai-brand-pack/package.json index 1417b697..b748075c 100644 --- a/packages-xyai/xyai-brand-pack/package.json +++ b/packages-xyai/xyai-brand-pack/package.json @@ -33,7 +33,8 @@ }, "scripts": { "bundle": "tsdown", - "watch": "tsdown --watch" + "watch": "tsdown --watch", + "test": "vitest run --config vitest.config.ts" }, "license": "MIT", "peerDependencies": { diff --git a/packages-xyai/xyai-brand-pack/src/client/hero-layout.ts b/packages-xyai/xyai-brand-pack/src/client/hero-layout.ts new file mode 100644 index 00000000..52de6dc4 --- /dev/null +++ b/packages-xyai/xyai-brand-pack/src/client/hero-layout.ts @@ -0,0 +1,75 @@ +/** + * Hero layout that does not depend on hashed CSS-module class names. + * + * XYAI welcome occupies `conversation.hero.brand.mark` inside DSH EmptyHero's + * fish hitbox. Adjacent official headline + preview badge stay in the next + * sibling span. Selectors key off `[data-xyai-hero-welcome]` so a rebuilt + * `HeroShell.module.css` hash cannot restore the double headline. + */ + +/** Injected `