@qzsy/dsh-yunxiao-panel
v1.4.1
Published
Yunxiao (阿里云云效) panel for the DeepSeek Harness Web client: a cross-project workbench with charts, project/repository navigation, Projex work items, Codeup commits and merge requests.
Readme
@qzsy/dsh-yunxiao-panel
A DeepSeek Harness Web panel for 阿里云云效. The left column starts with 我的工作台 (a cross-project workbench with charts), then a switchable 项目 / 仓库 list; a project opens its Projex work items (缺陷 / 需求 / 任务) and a repository chip row for its merge requests.
What it does
- Two left-menu list modes — 项目 and 仓库 — switched by a segmented
control at the top of the navigation. The selected mode is remembered per
browser.
- 项目: flat project list with
customCodeand mapped repository count. - 仓库: flat repository list; the second line shows
pathWithNamespaceplus its project. Selecting a repository opens its project with that repository active for merge requests; an unbound repository opens its own repository page instead. - Every row leads with its icon: the project's Projex
icon(also used for a repository without its ownavatarUrl). Yunxiao serves those images behind a session, so when the browser cannot load one the row falls back to a deterministic two-letter monogram tile tinted with a theme color.
- 项目: flat project list with
- Mixed Chinese/English quick search on both lists, powered by the vendored
text-search-engine(MIT, dynamic-programming fuzzy match): full pinyin (qunxing), pinyin initials (qxkj), project codes (QXERP), and mixed forms all match, and the hit ranges are highlighted in the rows. - 我的工作台 — a cross-project workbench with two scopes, switched at the top
and remembered per browser:
- 我的 aggregates the work items assigned to the token's user; 组织 drops that filter and covers every open item in the organization (up to 400, open ones first), so an organization manager can triage work unrelated to them.
- a hero card with a status ring (completion in the middle), a plain-language summary, and a 优先处理 row that picks the most urgent open item (bugs first) and opens it in one click,
- four clickable KPI cards (全部工作项 / 未完成 / 缺陷 / 待评审), then status, project, type and trend charts whose bars, slices and legends filter the list; organization scope adds a 按负责人 chart,
- a scrollable work-item list (serial, project code + Chinese project name, assignee in organization scope, type, status, relative time) with removable filter chips; rows open the detail pane,
- a 列表 / 看板 switch: the board groups the filtered items into one column per status (ordered by workflow stage, empty columns still shown), and dragging a card onto another column changes its status through the same workflow lookup the detail pane uses — on touch screens a column becomes a full-width swipe page and a tap opens the detail pane instead,
- merge requests waiting for review (all open ones in organization scope), with a direct link to Yunxiao when the list is empty.
- Detail sheet — opening a work item slides in a right-hand sheet (up to 820 px wide, always on top of a dimmed panel) so descriptions, images, comments and the footer actions get room; Esc, the close button or a click on the dimmed area dismisses it, and it stays full-width on phones.
- Change status from the detail pane — the pane shows the current status, a dropdown of the whole workflow for that work item type, and a 标记完成 button that picks the type's own done status (缺陷 → 已修复, 需求 → 已完成, else 已关闭). The item is repainted in the detail, every project list and the workbench (including its status chart) before the request returns, and a failure rolls every snapshot back.
- 在对话中处理 opens a workspace picker first: it lists the local DSH workspaces (suggesting the one whose name matches the repository or project), remembers the choice per Yunxiao project, and shows it on the button so the next handoff is a single click with a 切换 action beside it.
- Repository page — clicking any repository (in the repository list or under
未绑定的仓库) opens its own page with four tabs:
概览 / 提交 / 工作项 / 合并请求.
- 概览 shows the repository icon, description, default branch, visibility,
last activity and creation date, a 贡献者 avatar stack (from the recent
commit authors, avatars resolved through repository members, monogram
fallback), a 克隆仓库 card with ready-to-copy
git clonecommands for both SSH and HTTPS, plus the root README rendered as Markdown: headings, lists, quotes, code, tables, strikethrough, nested badge images, and images from absolute URLs, relative paths, or embedded HTML<img>tags (relative paths go through the Host file proxy so private repositories render without a Yunxiao session). - 提交 lists the latest 30 commits of the default branch with author avatar, title, short SHA and relative time; each row opens the commit in Yunxiao.
- 工作项 uses the bound project; an unbound repository explains that a project is required and offers 绑定项目 in both the empty state and the header. Binding from that page keeps you there and loads the work items in place.
- 合并请求 works without a project, scoped to the repository.
- 概览 shows the repository icon, description, default branch, visibility,
last activity and creation date, a 贡献者 avatar stack (from the recent
commit authors, avatars resolved through repository members, monogram
fallback), a 克隆仓库 card with ready-to-copy
- Project view — the project's Projex work items plus a repository picker that scopes the merge-request tab. Repository chips and filter chips share one active style (brand-tinted fill + brand border), and the tab switch is a segmented control with a sliding thumb and the loaded-item count on each side. The 绑定仓库 action in the project header binds unbound repositories to the project (useful when repositories live in the shared root code group instead of a project-named group).
- Project identification per repository. The repository's Codeup namespace
path (for example
QXERP) is matched against the Projex projectcustomCode; unmatched repositories are collected under 未绑定的仓库 and can be bound manually. Bindings are kept in browser local storage asrepoId → projectId. - Issues = the project's Projex work items, filtered by category (缺陷 / 需求 / 任务), with a title filter and paging.
- Pull requests = the selected repository's Codeup change requests, filtered by state (进行中 / 已合并 / 已关闭).
- Detail pane for a work item (description, comments, status, assignee) or a
merge request (branches, author, reviewers, description, conflicts).
Descriptions and comments may be plain Markdown, raw HTML, or Yunxiao's rich
payload
{"htmlValue": "...", "jsonMLValue": ["root", ...]}; all three are rendered through an allow-list sanitizer (scripts, styles, iframes, event handlers and inline styles are dropped), with inline images lazy loaded and clickable. Images embedded in a work item use the web-onlyworkitem/file/url?fileIdentifier=…URL, so they are rewritten to/api/yunxiao/workitem-image: the Host resolves the identifier through the Projex API to a signed OSS URL and serves real image bytes, avoiding both the browser-session requirement and the attachment download that breaks<img>. - 转为 Issue on a work item opens a prefilled form (type, title, Markdown description quoting the source, assignee from project members) and creates a new Projex work item in the owning project. A Yunxiao "issue" is a project work item — Codeup has no repository-level issue API.
- 在对话中处理 puts a structured context block into a new session draft
through
ctx.uiWorkspace.startSession; nothing is sent automatically. - Registered as a
sidebar.panellistentry withid=yunxiao, order=25— right below the command palette — and as themainpanel keyedyunxiao.
Mobile
The panel is usable down to phone widths (≤ 760 px):
- the left menu becomes a drawer behind a hamburger button in a compact top bar; it slides in with a scrim and closes on navigation, scrim click or Escape;
- workbench KPIs collapse to two columns and the charts stack vertically;
- the kanban board becomes one swipeable column per screen with snap scrolling;
- unbound repositories render as a single-column card list;
- the work-item detail pane becomes a full-width overlay with its own close button, and dialogs use the full width with a scrollable body;
- filter rows wrap, the keyword input takes the full width, and wide Markdown tables scroll horizontally instead of overflowing the card.
Search engine
The browser module table only resolves React and client-plugin rows, so a plain
JS plugin cannot require('text-search-engine'). The package's MIT browser IIFE
is vendored at vendor/text-search-engine.min.js and injected into the factory
scope of client.js by scripts/sync-vendor-engine.mjs
(node scripts/sync-vendor-engine.mjs after updating the vendor file). The
license is kept at vendor/text-search-engine.LICENSE.
Caching
The client runs a vendored @tanstack/react-query v5 (MIT) client shared by the
whole page, so data survives view switches and stale data renders immediately
while it revalidates in the background.
| Data | Query key | staleTime | gcTime |
|---|---|---|---|
| bootstrap (repos / namespaces / projects) | ['bootstrap'] | 30s | 10min |
| workbench aggregation (per scope) | ['workbench', scope] | 30s (+90s poll while visible) | 10min |
| work items page | ['workitems', spaceId, category] | 30s | 10min |
| merge requests page | ['prs', repoId, state] | 30s | 10min |
| one work item | ['workitem', id] | 15s | 10min |
| workflow statuses | ['workflow', spaceId, typeId] | 10min | 10min |
| repository detail / activity | ['repo-detail' | 'repo-activity', …] | 2min | 10min |
| DSH workspaces | ['workspaces'] | 60s | 10min |
| types / members / me | ['workitem-types' | 'members' | 'me', …] | 5min | 10min |
- Window focus and reconnect revalidate stale queries (
refetchOnWindowFocus). - 刷新 clears the Host cache, drops the localStorage snapshot, resets every query, and tells other open tabs to refresh too.
- A status write paints the new status into the detail, every project list and the workbench immediately, broadcasts it to other tabs, and rolls back on failure.
- The Host keeps its own short-lived cache and overlays recent status writes on stale search results for 3 minutes, so a list refetched right after a write cannot resurrect the old status.
- localStorage persistence: 700ms after the cache changes, the freshest
successful queries are snapshotted under
dsh.yunxiao.queryCache.v2(2 MB cap, largest entries skipped, failures clear the snapshot). Snapshots older than 24 hours are discarded on load; the v1 key is removed automatically. Hydrated data keeps its originalupdatedAt, so the first paint after a reload is instant and a background revalidation follows when the data is stale. - The bundle is generated by
scripts/sync-vendor-query.mjsfromvendor/react-query.min.cjs(React stays external and resolves through the host module table); the license is kept atvendor/react-query.LICENSE.
Data path
The browser never receives the personal access token. The Host half registers a
loopback-only same-origin facade at /api/yunxiao:
| Route | Purpose |
|---|---|
| GET /bootstrap | repositories (all pages), namespaces, projects, connection state |
| GET /repos?page&search | one page of repositories |
| GET /workbench?scope=mine\|org | cross-project aggregation: items, stats, reviews per scope |
| GET /repo-detail?id | repository detail + decoded root README |
| GET /repo-activity?id&ref | recent commits + contributors (avatars) |
| GET /file?repoId&ref&path | raw file bytes (README images, relative links) |
| GET /workitem-image?workitemId&fileId | bytes for images embedded in a work item |
| GET /workitems?spaceId&category&page | projex .../workitems:search |
| GET /workitem?id | one work item plus its comments |
| GET /workitem-types?spaceId&category | work item types of a project |
| GET /members?spaceId | project members for the assignee picker |
| GET /me | the user behind the personal access token |
| POST /workitems | create a work item (ProjexCreateWorkitem) |
| GET /workflow?spaceId&typeId | workflow statuses of a work item type |
| POST /workitem-status | move a work item to another status (PUT /workitems/{id}) |
| GET /workspaces | local DSH workspaces for the chat handoff picker |
| GET /prs?repoId&state&page | codeup .../changeRequests |
| GET /status | token / organization presence |
| POST /refresh | drop the short-lived cache |
Credentials resolve from the process environment first, then (Windows) from the persisted user and machine environment:
ALIBABA_CLOUD_YUNXIAO_ACCESS_TOKEN(required)ALIBABA_CLOUD_YUNXIAO_ORGANIZATION_ID(center version)ALIBABA_CLOUD_YUNXIAO_API_BASE_URL(region version; used instead of the organization id)
The center-version endpoint defaults to https://openapi-rdc.aliyuncs.com.
The token is attached as x-yunxiao-token on the request from the Host; it is
never returned by any route.
Install
Published on npm as @qzsy/dsh-yunxiao-panel. Install it from the Harness
Plugin Manager by package name, or during development install this directory
with plugin_manager install_bundle (absolute package directory).
Each person needs their own Yunxiao personal access token in the environment (see Data path); the panel never ships a credential. The token needs Codeup read access and Projex read/write access (to change status and create work items). After installation and setting the environment variables, restart the Harness and reload the Web page; the 云效 icon appears in the left rail between the command palette and the Git entry.
Project↔repository bindings, the remembered chat workspace and the workbench scope live in the browser's local storage, so each teammate binds and picks once per browser.
Test
The browser half has a jsdom render test that mounts the real component against a mocked facade and exercises repository switching, the unbound-project state, the work-item list and detail pane, status changes (including the list repaint), the chat handoff with its workspace picker and remembered choice, the workbench scopes, and the merge-request tab.
npm install --no-save react@18 react-dom@18 jsdom@24
node test/render-test.mjsSecurity notes
- The facade answers requests whose socket address is loopback only. The panel is a local tool; it does not widen the Host's reach to a network.
- Short-lived caches: repositories 45s, work items 20s, merge requests 20s.
