dsh-projects-panel 0.1.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.md +96 -0
- package/cordis.patch.yml +4 -0
- package/lib/client.d.ts +462 -0
- package/lib/client.js +7288 -0
- package/lib/index.d.ts +464 -0
- package/lib/index.js +1025 -0
- package/package.json +77 -0
package/README.md
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# dsh-projects-panel
|
|
2
|
+
|
|
3
|
+
DeepSeek Harness(DSH)项目面板插件。宿主(Node)侧管理插件数据根
|
|
4
|
+
(`DSH_PROJECTS_ROOT_DIR`,缺省 `~/.dsh/dsh-projects-panel`)下的项目树并
|
|
5
|
+
提供 HTTP 接口;客户端(浏览器)侧向 `sidebar.workspaces`
|
|
6
|
+
槽位注入「项目 / 工作区」混合树浏览器(搜索、筛选、新建、归档)。添加工作区统一调用官方幂等 `create`,再按 `workspaceId` 判断项目归属;已存在工作区直接在其下新建/复用会话。
|
|
7
|
+
|
|
8
|
+
版本:0.1.0(peer 依赖
|
|
9
|
+
`@deepseek-ai/cordis ^4.0.2`、`@deepseek-ai/dsh-client-locale ^0.1.5-rc.2`)。
|
|
10
|
+
React / React DOM 为浏览器第三方依赖(`devDependencies`),由 Web 壳平台表提供
|
|
11
|
+
共享实例,client 产物经 `tsdown.config.ts` 显式 `external` 外部化。
|
|
12
|
+
|
|
13
|
+
## 架构
|
|
14
|
+
|
|
15
|
+
双端插件:Host(Node,构建为 `lib/index.js`)+ Client(浏览器,构建为
|
|
16
|
+
`lib/client.js`,经 `scripts/wrap-client.mjs` 包裹为模块加载器格式)。
|
|
17
|
+
|
|
18
|
+
### Host 侧(数据与服务)
|
|
19
|
+
|
|
20
|
+
| 模块 | 职责 |
|
|
21
|
+
| --- | --- |
|
|
22
|
+
| `src/index.ts` | 插件入口:注册 `/projects-panel/*` 路由,解析并校验请求体(wire 边界),汇聚公共导出 |
|
|
23
|
+
| `src/host-tree.ts` | 项目树持久层:结构校验(循环/重复/归档一致性)、原子 JSON 读写、首启与损坏时的目录扫描恢复、工作区归属挂载/摘除(attach/detach,支持项目内排序锚点)、项目移动(moveProject,UUID 不变,只改树不碰目录)、整树工作区唯一与换父拒绝断言 |
|
|
24
|
+
| `src/host-project.ts` | 项目生命周期:创建 / 更新(改名只改 name 字段,不碰目录)/ 归档(只改树,不动目录)、link 文件白名单读取 |
|
|
25
|
+
| `src/host-settings.ts` | `settings.json` 读写(读侧与默认值合并并容忍损坏) |
|
|
26
|
+
| `src/core/` | `types.ts`(纯类型,只放类型)+ `workspace.ts`(工作区稳定键解析) |
|
|
27
|
+
|
|
28
|
+
数据根目录 `projectsRoot()`:优先读取 `DSH_PROJECTS_ROOT_DIR`(支持 `~` 缩写),
|
|
29
|
+
缺省时回落到 `DSH_HOME`(缺省 `~/.dsh`)下的 `dsh-projects-panel` 目录。
|
|
30
|
+
布局:根下 `projects.json`(嵌套项目树,项目身份为 UUID)与 `settings.json`,
|
|
31
|
+
项目目录统一存放于根下 `projects/` 子目录,目录名 = 项目 ID(UUID),
|
|
32
|
+
项目目录之间无层级,父子关系只记录在 `projects.json`。
|
|
33
|
+
|
|
34
|
+
HTTP 路由(host webServer,精确匹配;仅 GET 与 POST):
|
|
35
|
+
|
|
36
|
+
| 路由 | 方法 | 语义 |
|
|
37
|
+
| --- | --- | --- |
|
|
38
|
+
| `/projects-panel/tree` | GET / POST | 读 / 整树改写(仅排序与字段,换父请走移动路由;写边界含后代环与工作区唯一兜底) |
|
|
39
|
+
| `/projects-panel/project` | POST | 建 / 归档(携带 `action: "archive"`) |
|
|
40
|
+
| `/projects-panel/project-update` | POST | 改(改名只改 name 字段,顶层与子级均做同级重名检查 / 描述 / 关联目录) |
|
|
41
|
+
| `/projects-panel/project-move` | POST | 移动项目归属与排序(UUID 不变;纯排序与换父均只改树,不碰磁盘目录;`parentId` 省略=回顶层,`beforeId` 省略=末尾) |
|
|
42
|
+
| `/projects-panel/workspace-attach` | POST | 挂载工作区到项目(`parentId` 省略=移到顶层,此时不允许 `beforeId`;顶层排序请走官方工作区排序) |
|
|
43
|
+
| `/projects-panel/workspace-detach` | POST | 摘除工作区归属(官方删除后清理) |
|
|
44
|
+
| `/projects-panel/settings` | GET / POST | 读 / 写设置 |
|
|
45
|
+
| `/projects-panel/version` | GET | DSH 官方版本号(解析 `@deepseek-ai/dsh` 清单,与 `dsh --version` 同源;解析失败回退插件版本) |
|
|
46
|
+
| `/projects-panel/health` | GET | 健康检查(插件存活 + 版本) |
|
|
47
|
+
|
|
48
|
+
### Client 侧(浏览器 UI)
|
|
49
|
+
|
|
50
|
+
| 模块 | 职责 |
|
|
51
|
+
| --- | --- |
|
|
52
|
+
| `src/client/index.tsx` | 渲染层:注册 `projects-panel` 文案字典与样式,把官方 `ctx.workspaces`/`ctx.sessions` 绑定为 `BrowserActions`,向 `sidebar.workspaces`(priority -1)挂载 `ProjectsBrowser` |
|
|
53
|
+
| `src/client/tree.ts` | 树视图推导纯函数:聚合会话/工作区、展开/收起、搜索裁剪、筛选裁剪 |
|
|
54
|
+
| `src/client/tree-dnd.ts` | 拖拽落点推导纯函数:三类节点可拖性、落点合法性(自身/后代/归档/跨父/跨工作区拒绝)、排序锚点解析(DOM-insertBefore 语义) |
|
|
55
|
+
| `src/client/stores.ts` | 浏览器端视图状态 store(不可变更新 + 残留键清理) |
|
|
56
|
+
| `src/client/dialogs.tsx` | 对话框:项目表单、删除确认、文本输入(重命名)、移动失败单确认、应用内目录浏览/手工路径选择 |
|
|
57
|
+
|
|
58
|
+
## 开发
|
|
59
|
+
|
|
60
|
+
环境:Node `^22.19 || >=24`;pnpm 由 `packageManager: pnpm@11.25.0` 声明
|
|
61
|
+
(corepack 自动选用)。
|
|
62
|
+
|
|
63
|
+
```sh
|
|
64
|
+
pnpm install # 安装;prepare 自动安装 lefthook git 钩子
|
|
65
|
+
pnpm typecheck # 严格类型检查
|
|
66
|
+
pnpm test # 单元测试(vitest)
|
|
67
|
+
pnpm test:coverage # 覆盖率门禁:src 非渲染逻辑逐文件 100%(v8 per-file 阈值)
|
|
68
|
+
pnpm lint # oxlint 静态检查
|
|
69
|
+
pnpm check:style # 行长 ≤140 / 文件尾单换行 / 无行尾空白
|
|
70
|
+
pnpm build # typecheck + tsdown(host/client)+ wrap-client + 产物守卫
|
|
71
|
+
pnpm check:bundle # 单独跑产物守卫(需先 build):无内联 React / 平台表闭包 / 体积上限
|
|
72
|
+
pnpm verify:i18n # 校验 zh/en 字典已注册
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
门禁(全部强制):
|
|
76
|
+
|
|
77
|
+
- **pre-commit**(lefthook,仅暂存文件):oxlint、check-style、空白检查;
|
|
78
|
+
- **pre-push**(lefthook):typecheck + test:coverage + build(build 末尾内置产物守卫);
|
|
79
|
+
- **CI**(`.github/workflows/ci.yml`,GitHub Actions):typecheck、lint、
|
|
80
|
+
check:style、test:coverage、build,推送与 PR 触发。
|
|
81
|
+
|
|
82
|
+
门禁设施:`vitest.config.ts`、`.oxlintrc.json`、`scripts/check-style.mjs`、
|
|
83
|
+
`scripts/check-client-bundle.mjs`、`lefthook.yml`、`.github/workflows/ci.yml`。
|
|
84
|
+
|
|
85
|
+
依赖注意:`pnpm-workspace.yaml` 已放行 lefthook/esbuild 构建脚本并关闭
|
|
86
|
+
运行前依赖自检;若镜像对个别 tarball 报 integrity 错误,改用官方 registry
|
|
87
|
+
(`pnpm install --registry=https://registry.npmjs.org`)或
|
|
88
|
+
`pnpm install --update-checksums`。
|
|
89
|
+
|
|
90
|
+
## 文档入口
|
|
91
|
+
|
|
92
|
+
- **AI 入口**:根 `AGENTS.md`(standing orders,指向开发规范);
|
|
93
|
+
- **开发规范**:`docs/CODING_STANDARDS.md`(注释 / 风格命名 / 测试 / 文档
|
|
94
|
+
通用标准 + AI 加载约定);
|
|
95
|
+
- **设计文档索引**:`docs/README.md`(需求与设计 00–09,含官方源码落点映射,
|
|
96
|
+
实现核验以 `docs/设计/08` 为准)。
|
package/cordis.patch.yml
ADDED
package/lib/client.d.ts
ADDED
|
@@ -0,0 +1,462 @@
|
|
|
1
|
+
import { ReactElement } from "react";
|
|
2
|
+
import { Context } from "@deepseek-ai/cordis";
|
|
3
|
+
|
|
4
|
+
//#region src/core/types.d.ts
|
|
5
|
+
|
|
6
|
+
/** 会话级待用户应答交互的类型(官方 ui-approval / ui-user-questions 注册的 kind 收敛)。 */
|
|
7
|
+
type SessionPendingKind = 'approval' | 'plan-review' | 'question';
|
|
8
|
+
/** 目录浏览的一行(官方 host.listDirectory 投影):只承载展示字段。 */
|
|
9
|
+
interface DirectoryEntryLike {
|
|
10
|
+
name: string;
|
|
11
|
+
path: string;
|
|
12
|
+
hidden: boolean;
|
|
13
|
+
}
|
|
14
|
+
/** 目录浏览结果(官方 host.listDirectory 响应投影)。 */
|
|
15
|
+
interface DirectoryListingLike {
|
|
16
|
+
path: string;
|
|
17
|
+
home: string;
|
|
18
|
+
crumbs: readonly DirectoryEntryLike[];
|
|
19
|
+
entries: readonly DirectoryEntryLike[];
|
|
20
|
+
truncated: boolean;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* 工作区/会话的官方客户端动作集(渲染层注入,纯接口)。
|
|
24
|
+
*
|
|
25
|
+
* 方法语义与官方 IWorkspaces/ISessions 一致;实现绑定在插件入口
|
|
26
|
+
* (client/index.tsx),由宿主注册的官方运行时提供。目录浏览能力
|
|
27
|
+
* 缺失(browse 能力未启用)时 listDirectory/createDirectory 为
|
|
28
|
+
* undefined,界面降级为手工输入路径。
|
|
29
|
+
*/
|
|
30
|
+
interface BrowserActions {
|
|
31
|
+
/** 打开(并选中)会话。 */
|
|
32
|
+
openSession(sessionId: string): void;
|
|
33
|
+
/** 在指定工作区(或默认目标)新建/复用空白会话并打开。 */
|
|
34
|
+
startSession(workspaceId?: string): Promise<void>;
|
|
35
|
+
/** 幂等创建或解析已存在目录的工作区;返回官方 workspaceId。 */
|
|
36
|
+
createWorkspace(path: string): Promise<string>;
|
|
37
|
+
/** 重命名工作区(官方持久化标题)。 */
|
|
38
|
+
renameWorkspace(workspaceId: string, title: string): Promise<void>;
|
|
39
|
+
/** 删除工作区注册(目录与会话日志保留)。 */
|
|
40
|
+
deleteWorkspace(workspaceId: string): Promise<void>;
|
|
41
|
+
/** 重命名会话(官方持久化标题)。 */
|
|
42
|
+
renameSession(sessionId: string, title: string): Promise<void>;
|
|
43
|
+
/** 分叉会话并在完成后打开子会话。 */
|
|
44
|
+
forkSession(sessionId: string): Promise<void>;
|
|
45
|
+
/** 归档会话(registry 全局归档集)。 */
|
|
46
|
+
archiveSession(sessionId: string): Promise<void>;
|
|
47
|
+
/**
|
|
48
|
+
* 官方工作区排序(顶层悬浮工作区与按工作区分组模式专用)。
|
|
49
|
+
*
|
|
50
|
+
* 语义与官方 insertBefore 一致:省略锚点追加末尾;缺失时界面降级为不可排序。
|
|
51
|
+
*/
|
|
52
|
+
insertWorkspaceBefore?(workspaceId: string, beforeId?: string): Promise<void>;
|
|
53
|
+
/**
|
|
54
|
+
* 官方会话排序(同工作区内专用,不写插件树)。
|
|
55
|
+
*
|
|
56
|
+
* 语义与官方 insertSessionBefore 一致;缺失时界面降级为不可排序。
|
|
57
|
+
*/
|
|
58
|
+
insertSessionBefore?(workspaceId: string, sessionId: string, beforeId?: string): Promise<void>;
|
|
59
|
+
/** 应用内目录浏览能力(可选)。 */
|
|
60
|
+
listDirectory?(path?: string, signal?: AbortSignal): Promise<DirectoryListingLike>;
|
|
61
|
+
/** 应用内新建目录能力(可选)。 */
|
|
62
|
+
createDirectory?(path: string, name: string): Promise<string>;
|
|
63
|
+
}
|
|
64
|
+
//#endregion
|
|
65
|
+
//#region src/client/locales.d.ts
|
|
66
|
+
/**
|
|
67
|
+
* 客户端文案字典(zh / en)与翻译函数。
|
|
68
|
+
*
|
|
69
|
+
* 职责:集中维护全部客户端可见文案,供 apply 注册到宿主 locale;
|
|
70
|
+
* 导出 ProjectsTranslate 翻译函数类型与 dictTranslate 包装器(把字典
|
|
71
|
+
* 包成官方 Translate 形状,供组件独立测试与无宿主 locale 时兜底)。
|
|
72
|
+
* 渲染层一律经翻译函数取词,禁止硬编码文案。
|
|
73
|
+
* 边界:只承载文案与翻译包装,不含组件逻辑。
|
|
74
|
+
*/
|
|
75
|
+
/** 文案键:按界面区域分组,键名即语义。 */
|
|
76
|
+
interface ProjectsKey {
|
|
77
|
+
'title': string;
|
|
78
|
+
'all': string;
|
|
79
|
+
'filter.searchPlaceholder': string;
|
|
80
|
+
'filter.empty': string;
|
|
81
|
+
'searchSessions.placeholder': string;
|
|
82
|
+
'newProject': string;
|
|
83
|
+
'newWorkspace': string;
|
|
84
|
+
'newSession': string;
|
|
85
|
+
'session.new': string;
|
|
86
|
+
'session.untitled': string;
|
|
87
|
+
'session.status.running': string;
|
|
88
|
+
'session.status.subagents.one': string;
|
|
89
|
+
'session.status.subagents.other': string;
|
|
90
|
+
'session.status.idle': string;
|
|
91
|
+
'session.status.completed': string;
|
|
92
|
+
'session.status.archived': string;
|
|
93
|
+
'session.status.waitingApproval': string;
|
|
94
|
+
'session.status.planReview': string;
|
|
95
|
+
'session.status.waitingAnswer': string;
|
|
96
|
+
'session.schedule.active': string;
|
|
97
|
+
'session.hover.copy': string;
|
|
98
|
+
'session.hover.copied': string;
|
|
99
|
+
'toolbar.actions': string;
|
|
100
|
+
'viewOptions': string;
|
|
101
|
+
'view.grouping': string;
|
|
102
|
+
'view.byProject': string;
|
|
103
|
+
'view.byWorkspace': string;
|
|
104
|
+
'view.showArchived': string;
|
|
105
|
+
'view.other': string;
|
|
106
|
+
'empty': string;
|
|
107
|
+
'project.menu.newChildProject': string;
|
|
108
|
+
'project.menu.newChildWorkspace': string;
|
|
109
|
+
'project.menu.edit': string;
|
|
110
|
+
'project.menu.delete': string;
|
|
111
|
+
'workspace.menu.rename': string;
|
|
112
|
+
'workspace.menu.delete': string;
|
|
113
|
+
'session.menu.rename': string;
|
|
114
|
+
'session.menu.fork': string;
|
|
115
|
+
'session.menu.archive': string;
|
|
116
|
+
'project.dialog.create.title': string;
|
|
117
|
+
'project.dialog.edit.title': string;
|
|
118
|
+
'project.dialog.name': string;
|
|
119
|
+
'project.dialog.description': string;
|
|
120
|
+
'project.dialog.link': string;
|
|
121
|
+
'project.dialog.linkBrowse': string;
|
|
122
|
+
'project.dialog.submit.create': string;
|
|
123
|
+
'project.dialog.submit.save': string;
|
|
124
|
+
'project.dialog.nameRequired': string;
|
|
125
|
+
'project.dialog.nameInvalid': string;
|
|
126
|
+
'project.dialog.descriptionTooLong': string;
|
|
127
|
+
'project.delete.title': string;
|
|
128
|
+
'project.delete.message': string;
|
|
129
|
+
'project.delete.submit': string;
|
|
130
|
+
'dialog.cancel': string;
|
|
131
|
+
'dialog.save': string;
|
|
132
|
+
'dialog.delete': string;
|
|
133
|
+
'dialog.confirm': string;
|
|
134
|
+
'move.failed.title': string;
|
|
135
|
+
'field.nameLabel': string;
|
|
136
|
+
'field.required': string;
|
|
137
|
+
'notReady.message': string;
|
|
138
|
+
'workspace.create.title': string;
|
|
139
|
+
'workspace.create.titleInProject': string;
|
|
140
|
+
'workspace.create.submit': string;
|
|
141
|
+
'workspace.rename.title': string;
|
|
142
|
+
'workspace.delete.title': string;
|
|
143
|
+
'workspace.delete.message': string;
|
|
144
|
+
'workspace.delete.submit': string;
|
|
145
|
+
'session.rename.title': string;
|
|
146
|
+
'directory.select': string;
|
|
147
|
+
'directory.enter': string;
|
|
148
|
+
'directory.newFolder': string;
|
|
149
|
+
'directory.newFolderPlaceholder': string;
|
|
150
|
+
'directory.createFolder': string;
|
|
151
|
+
'directory.empty': string;
|
|
152
|
+
'directory.listFailed': string;
|
|
153
|
+
'directory.createFailed': string;
|
|
154
|
+
'directory.unavailable': string;
|
|
155
|
+
'directory.manualPlaceholder': string;
|
|
156
|
+
'browser.title': string;
|
|
157
|
+
'browser.home': string;
|
|
158
|
+
'browser.newFolder': string;
|
|
159
|
+
'browser.folderName': string;
|
|
160
|
+
'browser.createIn': string;
|
|
161
|
+
'browser.untitledFolder': string;
|
|
162
|
+
'browser.create': string;
|
|
163
|
+
'browser.cancel': string;
|
|
164
|
+
'browser.open': string;
|
|
165
|
+
'browser.editPath': string;
|
|
166
|
+
'browser.loading': string;
|
|
167
|
+
'browser.truncated': string;
|
|
168
|
+
'browser.showHidden': string;
|
|
169
|
+
'time.now': string;
|
|
170
|
+
'time.ago': string;
|
|
171
|
+
'time.minutes': string;
|
|
172
|
+
'time.hours': string;
|
|
173
|
+
'time.days': string;
|
|
174
|
+
'time.months': string;
|
|
175
|
+
'time.years': string;
|
|
176
|
+
/** 绝对日期模板(hover 卡创建时间;与官方 date.ymd 同语义)。 */
|
|
177
|
+
'date.ymd': string;
|
|
178
|
+
/** hover 卡创建时间包装(含时分)。 */
|
|
179
|
+
'hover.created': string;
|
|
180
|
+
'error.loadTree': string;
|
|
181
|
+
'error.createProject': string;
|
|
182
|
+
'error.updateProject': string;
|
|
183
|
+
'error.archiveProject': string;
|
|
184
|
+
'error.startSession': string;
|
|
185
|
+
'error.createWorkspace': string;
|
|
186
|
+
'error.renameWorkspace': string;
|
|
187
|
+
'error.deleteWorkspace': string;
|
|
188
|
+
'error.renameSession': string;
|
|
189
|
+
'error.forkSession': string;
|
|
190
|
+
'error.archiveSession': string;
|
|
191
|
+
'error.attachWorkspace': string;
|
|
192
|
+
'error.moveProject': string;
|
|
193
|
+
'error.reorderProject': string;
|
|
194
|
+
'error.reorderOfficial': string;
|
|
195
|
+
'user.menu.language': string;
|
|
196
|
+
'user.menu.theme': string;
|
|
197
|
+
'user.menu.notification': string;
|
|
198
|
+
'user.menu.workspace': string;
|
|
199
|
+
'user.menu.version': string;
|
|
200
|
+
'user.menu.logout': string;
|
|
201
|
+
'user.menu.anonymous': string;
|
|
202
|
+
'user.menu.settings': string;
|
|
203
|
+
'user.menu.language.zh': string;
|
|
204
|
+
'user.menu.language.en': string;
|
|
205
|
+
'user.menu.theme.system': string;
|
|
206
|
+
'user.menu.theme.light': string;
|
|
207
|
+
'user.menu.theme.dark': string;
|
|
208
|
+
'user.menu.notification.on': string;
|
|
209
|
+
'user.menu.notification.off': string;
|
|
210
|
+
'user.menu.workspace.default': string;
|
|
211
|
+
'user.menu.workspace.projects': string;
|
|
212
|
+
'notification.title': string;
|
|
213
|
+
'notification.session.completed': string;
|
|
214
|
+
'notification.session.pending': string;
|
|
215
|
+
'notification.test.enabled': string;
|
|
216
|
+
'notification.test.disabled': string;
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* 翻译函数:读取当前语言的字典并解析键,支持 `{name}` 模板参数。
|
|
220
|
+
* 由宿主 locale 注入(与官方 Translate 形状一致),语言切换时返回新函数
|
|
221
|
+
* 触发重渲染。
|
|
222
|
+
*/
|
|
223
|
+
type ProjectsTranslate = (key: keyof ProjectsKey, params?: Record<string, unknown>) => string;
|
|
224
|
+
//#endregion
|
|
225
|
+
//#region src/client/session-facts.d.ts
|
|
226
|
+
|
|
227
|
+
/** 会话实时展示事实:树会话节点在其上叠加(仅含有的字段参与覆盖)。 */
|
|
228
|
+
interface SessionLiveFacts {
|
|
229
|
+
running: boolean;
|
|
230
|
+
completed: boolean;
|
|
231
|
+
runningSubagentCount: number;
|
|
232
|
+
pendingInteraction?: SessionPendingKind;
|
|
233
|
+
hasActiveSchedule: boolean;
|
|
234
|
+
/** 官方客户端空会话位(实时真源;RPC 快照可能停留在创建时的 true)。 */
|
|
235
|
+
blank: boolean;
|
|
236
|
+
/** 官方持久标题(非空才投影;空标题时保留 RPC 权威补读值)。 */
|
|
237
|
+
title?: string;
|
|
238
|
+
/** 官方列表的最后活动时间(新于 RPC 快照时覆盖行尾相对时间)。 */
|
|
239
|
+
updatedAt?: number;
|
|
240
|
+
}
|
|
241
|
+
//#endregion
|
|
242
|
+
//#region src/client/ProjectsBrowser.d.ts
|
|
243
|
+
/** 项目浏览器组件属性。 */
|
|
244
|
+
interface ProjectsBrowserProps {
|
|
245
|
+
/** 打开会话(官方客户端会话服务);未提供时仅做选中高亮。 */
|
|
246
|
+
openSession?: (sessionId: string) => void;
|
|
247
|
+
/**
|
|
248
|
+
* 当前打开的会话 id(官方会话列表快照 current)。
|
|
249
|
+
* 首次加载时收起全部节点、只展开包含它的工作区与上级项目并选中它;
|
|
250
|
+
* 之后 current 变化只展开对应链路并跟随选中。
|
|
251
|
+
*/
|
|
252
|
+
currentSessionId?: string | null;
|
|
253
|
+
/**
|
|
254
|
+
* 官方客户端动作(会话/工作区 CRUD 与目录浏览),由插件入口注入。
|
|
255
|
+
* 缺失时对应动作回退为占位提示(组件可独立测试)。
|
|
256
|
+
*/
|
|
257
|
+
actions?: BrowserActions;
|
|
258
|
+
/**
|
|
259
|
+
* 客户端实时事实表(官方 sessions.list + uiSession 推导,见 index.tsx)。
|
|
260
|
+
* 结构/成员仍以 /tree RPC 为准;本表叠加 running/completed/子代理计数/
|
|
261
|
+
* 待应答/闹钟等客户端域事实,并覆盖空会话位与标题、最后活动时间——
|
|
262
|
+
* RPC 只在面板动作后重拉,官方侧的新建会话与首轮对话只有本表是实时的。
|
|
263
|
+
* undefined 表示无客户端快照(独立渲染/测试兜底走 RPC 字段)。
|
|
264
|
+
*/
|
|
265
|
+
liveFacts?: ReadonlyMap<string, SessionLiveFacts>;
|
|
266
|
+
/**
|
|
267
|
+
* 官方客户端列表的成员键(`membershipKeyOf` 产出,见 index.tsx)。
|
|
268
|
+
* 结构/成员以 /tree RPC 为准,而 RPC 只在挂载与面板动作后重拉;本键与
|
|
269
|
+
* 快照成员不一致(面板外入口新建、会话被删除/移出工作区)时防抖重拉
|
|
270
|
+
* 树,让成员变化即时反映。undefined 表示无客户端快照,不做成员跟踪。
|
|
271
|
+
*/
|
|
272
|
+
membershipKey?: string;
|
|
273
|
+
/**
|
|
274
|
+
* 当前语言翻译函数,由宿主 slot 经 locale 声明注入(官方 Translate 形状);
|
|
275
|
+
* 语言切换时注入新函数,组件随之重渲染。未注入时回退为中文。
|
|
276
|
+
*/
|
|
277
|
+
t?: ProjectsTranslate;
|
|
278
|
+
}
|
|
279
|
+
/** 项目浏览器主组件。 */
|
|
280
|
+
declare function ProjectsBrowser({
|
|
281
|
+
openSession,
|
|
282
|
+
currentSessionId,
|
|
283
|
+
actions,
|
|
284
|
+
liveFacts,
|
|
285
|
+
membershipKey,
|
|
286
|
+
t
|
|
287
|
+
}?: ProjectsBrowserProps): ReactElement;
|
|
288
|
+
//#endregion
|
|
289
|
+
//#region src/client/settings/user-prefs.d.ts
|
|
290
|
+
/**
|
|
291
|
+
* 用户菜单偏好存储(官方 settings.yaml 主通道 + localStorage 兜底)。
|
|
292
|
+
*
|
|
293
|
+
* 职责:读写系统通知开关、工作区模式开关。主通道为官方 projects-panel
|
|
294
|
+
* 设置命名空间 scope(落盘 $DSH_HOME/settings.yaml,client 侧经
|
|
295
|
+
* settingsScope.bind + getSnapshot/set);scope 不可用(宿主未装配/
|
|
296
|
+
* 尚未就绪)时回退 localStorage,保证 UI 可用性。
|
|
297
|
+
* 边界:纯存储,不含业务逻辑。
|
|
298
|
+
*/
|
|
299
|
+
/** 设置命名空间 scope 的最小投影(settingsScope.bind 返回)。 */
|
|
300
|
+
interface PrefsScope {
|
|
301
|
+
/** 当前快照:value = 命名空间解析值(schema 默认 + base + 用户层)。 */
|
|
302
|
+
getSnapshot(): {
|
|
303
|
+
value?: unknown;
|
|
304
|
+
status?: string;
|
|
305
|
+
revision?: number;
|
|
306
|
+
};
|
|
307
|
+
/** 订阅快照变化。 */
|
|
308
|
+
subscribe(listener: () => void): () => void;
|
|
309
|
+
/** 写入一个顶层字段(整值覆盖)。 */
|
|
310
|
+
set(field: string, value: unknown): Promise<void>;
|
|
311
|
+
}
|
|
312
|
+
//#endregion
|
|
313
|
+
//#region src/client/index.d.ts
|
|
314
|
+
type LocaleService = {
|
|
315
|
+
register(namespace: string, dictionaries: {
|
|
316
|
+
zh: ProjectsKey;
|
|
317
|
+
en: ProjectsKey;
|
|
318
|
+
}): void;
|
|
319
|
+
};
|
|
320
|
+
type SlotService = {
|
|
321
|
+
/** 声明依赖注册:等待槽声明就绪后执行 callback,返回可注销的 disposer。 */
|
|
322
|
+
inject(name: string, callback: () => () => void): () => void;
|
|
323
|
+
register(options: Record<string, unknown>, component: (props: Record<string, unknown>) => ReactElement): () => void;
|
|
324
|
+
};
|
|
325
|
+
/** 会话列表快照:官方会话列表投影(含 current/ids/byId/phase)。 */
|
|
326
|
+
type SessionListSnapshot = {
|
|
327
|
+
current?: string | null;
|
|
328
|
+
ids?: readonly string[];
|
|
329
|
+
byId?: Record<string, SessionSummaryLike>;
|
|
330
|
+
phase?: string;
|
|
331
|
+
};
|
|
332
|
+
/** 会话行摘要最小投影(官方 SessionSummary 子集)。 */
|
|
333
|
+
type SessionSummaryLike = {
|
|
334
|
+
blank?: boolean;
|
|
335
|
+
cwd?: string;
|
|
336
|
+
running?: boolean;
|
|
337
|
+
/** 完成提醒:官方 manager 边沿事实(真值才投影)。 */
|
|
338
|
+
completed?: boolean;
|
|
339
|
+
parentId?: string;
|
|
340
|
+
/** 官方 origin:子代理会话为 'subagent'。 */
|
|
341
|
+
origin?: string;
|
|
342
|
+
/** 官方持久标题投影(实时真源;面板树据此覆盖 RPC 快照里的空标题)。 */
|
|
343
|
+
title?: string;
|
|
344
|
+
/** 官方列表最后活动时间(覆盖 RPC 快照里落后的相对时间)。 */
|
|
345
|
+
updatedAt?: number;
|
|
346
|
+
/** 官方投影值(schedule 键携带活动定时任务数组)。 */
|
|
347
|
+
projectionValues?: Record<string, unknown>;
|
|
348
|
+
};
|
|
349
|
+
/** ctx.sessions 客户端服务的本地最小投影(官方 ClientSessions 子集)。 */
|
|
350
|
+
type SessionsService = {
|
|
351
|
+
open(sessionId: string): void;
|
|
352
|
+
clear(): void;
|
|
353
|
+
/** 创建/复用空白会话并返回会话 id(官方 ClientSessions.create)。 */
|
|
354
|
+
create(opts: {
|
|
355
|
+
workspaceId?: string;
|
|
356
|
+
cwd?: string;
|
|
357
|
+
}): Promise<string>;
|
|
358
|
+
/** 官方列表快照(含 current);缺失时视为无当前会话。 */
|
|
359
|
+
list?: {
|
|
360
|
+
getSnapshot(): SessionListSnapshot;
|
|
361
|
+
subscribe(fn: () => void): () => void;
|
|
362
|
+
};
|
|
363
|
+
/** 分叉会话:返回子会话 id。 */
|
|
364
|
+
fork(opts: {
|
|
365
|
+
sessionId: string;
|
|
366
|
+
increaseTitle?: boolean;
|
|
367
|
+
}): Promise<string>;
|
|
368
|
+
/** 会话绑定(含重命名 face);未知 id 返回 undefined。 */
|
|
369
|
+
binding(sessionId: string): {
|
|
370
|
+
session?: SessionRenameFace;
|
|
371
|
+
} | undefined;
|
|
372
|
+
};
|
|
373
|
+
/** 会话重命名 face 的最小投影(官方 SessionFace.rename 的 RpcResult)。 */
|
|
374
|
+
type SessionRenameFace = {
|
|
375
|
+
rename(title: string): Promise<{
|
|
376
|
+
ok: boolean;
|
|
377
|
+
error?: {
|
|
378
|
+
message: string;
|
|
379
|
+
};
|
|
380
|
+
}>;
|
|
381
|
+
};
|
|
382
|
+
/** 官方工作区行(WorkspaceView)最小投影。 */
|
|
383
|
+
type WorkspaceRowLike = {
|
|
384
|
+
workspaceId: string;
|
|
385
|
+
path: string;
|
|
386
|
+
title?: string;
|
|
387
|
+
sessionIds?: readonly string[];
|
|
388
|
+
};
|
|
389
|
+
/** 官方工作区快照最小投影(WorkspaceSnapshot 子集)。 */
|
|
390
|
+
type WorkspaceListSnapshot = {
|
|
391
|
+
items: readonly WorkspaceRowLike[];
|
|
392
|
+
archivedSessionIds: readonly string[];
|
|
393
|
+
phase?: string;
|
|
394
|
+
};
|
|
395
|
+
/** 官方 workspace 域服务(ctx.workspaces = 纯工作区控制器)的最小投影。 */
|
|
396
|
+
type WorkspacesService = {
|
|
397
|
+
create(input: {
|
|
398
|
+
path: string;
|
|
399
|
+
}): Promise<{
|
|
400
|
+
workspaceId: string;
|
|
401
|
+
}>;
|
|
402
|
+
rename(workspaceId: string, title: string): Promise<unknown>;
|
|
403
|
+
delete(workspaceId: string): Promise<void>;
|
|
404
|
+
archiveSession(sessionId: string): Promise<void>;
|
|
405
|
+
insertBefore?(workspaceId: string, beforeId?: string): Promise<unknown>;
|
|
406
|
+
insertSessionBefore?(workspaceId: string, sessionId: string, beforeId?: string): Promise<unknown>;
|
|
407
|
+
/** 官方工作区列表快照(items + 归档集);缺失时视为空。 */
|
|
408
|
+
list?: {
|
|
409
|
+
getSnapshot(): WorkspaceListSnapshot;
|
|
410
|
+
subscribe(fn: () => void): () => void;
|
|
411
|
+
};
|
|
412
|
+
};
|
|
413
|
+
/** 目录选择远程命名空间(ctx.remote.directoryPicker)的最小投影。 */
|
|
414
|
+
type DirectoryPickerRemote = {
|
|
415
|
+
list(path?: string, signal?: AbortSignal): Promise<{
|
|
416
|
+
ok: true;
|
|
417
|
+
value: DirectoryListingLike;
|
|
418
|
+
} | {
|
|
419
|
+
ok: false;
|
|
420
|
+
error: {
|
|
421
|
+
message: string;
|
|
422
|
+
};
|
|
423
|
+
}>;
|
|
424
|
+
createDirectory(path: string, name: string): Promise<{
|
|
425
|
+
ok: true;
|
|
426
|
+
value: {
|
|
427
|
+
path: string;
|
|
428
|
+
};
|
|
429
|
+
} | {
|
|
430
|
+
ok: false;
|
|
431
|
+
error: {
|
|
432
|
+
message: string;
|
|
433
|
+
};
|
|
434
|
+
}>;
|
|
435
|
+
};
|
|
436
|
+
type ClientContext = Context & {
|
|
437
|
+
locale: LocaleService;
|
|
438
|
+
slots: SlotService;
|
|
439
|
+
sessions: SessionsService;
|
|
440
|
+
workspaces: WorkspacesService;
|
|
441
|
+
/** 远程命名空间装配(dsh-api-remotes);目录选择在其中。 */
|
|
442
|
+
remote?: {
|
|
443
|
+
directoryPicker?: DirectoryPickerRemote;
|
|
444
|
+
};
|
|
445
|
+
/** 官方 settings scope(绑定 projects-panel 命名空间读写 settings.yaml)。 */
|
|
446
|
+
settingsScope: {
|
|
447
|
+
bind(opts: {
|
|
448
|
+
namespace: string;
|
|
449
|
+
}): PrefsScope;
|
|
450
|
+
};
|
|
451
|
+
effect(effect: () => void | (() => void), label?: string): void;
|
|
452
|
+
/**
|
|
453
|
+
* Cordis 上下文反射读取(官方服务未注入时抛错,故先探测再读;
|
|
454
|
+
* 本项目不从 inject 硬依赖 uiSession,缺失时降级无待应答态)。
|
|
455
|
+
*/
|
|
456
|
+
get<T = unknown>(name: string): T | undefined;
|
|
457
|
+
};
|
|
458
|
+
declare const inject: string[];
|
|
459
|
+
/** 插件入口:注册字典、注入样式,向侧栏槽位挂载浏览器并注入官方动作。 */
|
|
460
|
+
declare function apply(ctx: ClientContext): void;
|
|
461
|
+
//#endregion
|
|
462
|
+
export { ProjectsBrowser, apply, inject };
|