hiwork-core 0.0.30 → 0.0.37

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.
Files changed (41) hide show
  1. package/cordis.patch.yml +15 -1
  2. package/lib/ai-router-client.js +394 -2
  3. package/lib/browser-login.js +42 -16
  4. package/lib/client-policy.js +2 -2
  5. package/lib/client.js +2496 -500
  6. package/lib/image-rpc.js +306 -0
  7. package/lib/imagegen.js +424 -0
  8. package/lib/index.js +18639 -7060
  9. package/lib/knowledge-config.js +91 -1
  10. package/lib/provision-session.js +10 -2
  11. package/lib/tool-names.js +10 -0
  12. package/lib/tools.js +205 -0
  13. package/lib/types/ai-router-client.d.ts +255 -2
  14. package/lib/types/browser-login.d.ts +10 -1
  15. package/lib/types/client/about-section.d.ts +27 -7
  16. package/lib/types/client/controller.d.ts +17 -9
  17. package/lib/types/client/daily-data.d.ts +31 -3
  18. package/lib/types/client/feature-center.d.ts +46 -1
  19. package/lib/types/client/feature-registry.d.ts +36 -3
  20. package/lib/types/client/image-cards.d.ts +218 -0
  21. package/lib/types/client/image-styles.d.ts +9 -0
  22. package/lib/types/client/images-data.d.ts +127 -0
  23. package/lib/types/client/images-page.d.ts +97 -0
  24. package/lib/types/client/index.d.ts +0 -1
  25. package/lib/types/client/login-gate.d.ts +2 -2
  26. package/lib/types/client/logo.d.ts +7 -2
  27. package/lib/types/client/menu-preferences.d.ts +163 -0
  28. package/lib/types/client/menu-section.d.ts +5 -0
  29. package/lib/types/client/menu-store.d.ts +101 -0
  30. package/lib/types/client/styles.d.ts +16 -0
  31. package/lib/types/client/types.d.ts +9 -0
  32. package/lib/types/image-rpc.d.ts +142 -0
  33. package/lib/types/imagegen.d.ts +241 -0
  34. package/lib/types/index.d.ts +89 -8
  35. package/lib/types/knowledge-config.d.ts +50 -2
  36. package/lib/types/tool-names.d.ts +11 -0
  37. package/lib/types/tools.d.ts +80 -0
  38. package/lib/types/user-agent.d.ts +31 -0
  39. package/lib/user-agent.js +54 -0
  40. package/lib/web-search.js +2 -2
  41. package/package.json +34 -24
@@ -0,0 +1,127 @@
1
+ /**
2
+ * 「我的图片」画廊页的客户端取数层:经 `/hiwork-image` loopback RPC 读**既有**的网关
3
+ * 员工自助端点(`/api/me/images`、`/api/me/images/:id/file`),做信封拆解与形状归一。
4
+ *
5
+ * 铁律(与 `usage-data.ts` 同一套):
6
+ * - 客户端**不持有**会话 cookie,也**不直接 fetch 网关**——一切经 `connection.rpc`;
7
+ * - **不缓存图片字节**:`fetch` 每次现取、用完即弃(不落 DSH 附件存储、不进会话日志);
8
+ * `save` 只在用户显式点击时写盘(写盘在 Host 半边);
9
+ * - 失败绝**不**降级成空数据:错误带稳定 code,视图把 message 直接显示在行内。
10
+ *
11
+ * client bundle 纯度:本文件不 import 任何 `@deepseek-ai/*` 运行时值,也不 import
12
+ * Host 代码(`ImageRow` 等按结构重声明,字段名与网关 JSON / Host 契约一致)。
13
+ */
14
+ /**
15
+ * RPC 频道;必须与 Host `src/image-rpc.ts` 的频道常量一致(冻结契约 `/hiwork-image`)。
16
+ * 由本文件与 `tests/image-gallery.spec.tsx` 的字面量断言锁定。
17
+ */
18
+ export declare const IMAGE_CHANNEL = "/hiwork-image";
19
+ /**
20
+ * 端点白名单(**冻结契约**,与 Host 的 `IMAGE_RPC_ENDPOINTS` 逐字同序)。
21
+ *
22
+ * `status` / `generate` 供会话内工具链路(那个链路不经过本文件),画廊页只用后三个:
23
+ * `list` / `fetch` / `save`。这里整表列出是为了让「一个能力一个频道、端点表只有一处」
24
+ * 这条约定在客户端也看得见。
25
+ */
26
+ export declare const IMAGE_RPC_ENDPOINTS: readonly ["status", "generate", "list", "fetch", "save"];
27
+ /** 端点名。 */
28
+ export type ImageEndpoint = (typeof IMAGE_RPC_ENDPOINTS)[number];
29
+ /** 画廊页默认分页(spec §6.5:默认 20)。 */
30
+ export declare const IMAGE_LIST_PAGE_SIZE = 20;
31
+ /** 分页上限(与 Host 侧一致;再大也只是白拉流量)。 */
32
+ export declare const IMAGE_LIST_MAX_PAGE_SIZE = 100;
33
+ /** 拿不到可展示原因时的兜底文案。 */
34
+ export declare const IMAGE_DATA_ERROR_TEXT = "\u56FE\u7247\u8BB0\u5F55\u6682\u65F6\u65E0\u6CD5\u83B7\u53D6\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5";
35
+ /**
36
+ * 一条历史记录(**生产形状**:网关 `/api/me/images` 的 item 在补了 `size` 投影之后
37
+ * 的字段集;`tests/image-gallery.spec.tsx` 的夹具必须按这个形状构造)。
38
+ */
39
+ export interface ImageRow {
40
+ readonly id: number;
41
+ readonly prompt: string;
42
+ readonly revisedPrompt?: string | undefined;
43
+ readonly model: string;
44
+ readonly size: string;
45
+ readonly format: string;
46
+ readonly mimeType: string;
47
+ readonly byteSize: number;
48
+ readonly generationMs: number;
49
+ /** RFC3339(网关 `created_at`)。 */
50
+ readonly createdAt: string;
51
+ }
52
+ /** `list` 端点出参。 */
53
+ export interface ImageListPage {
54
+ readonly items: readonly ImageRow[];
55
+ readonly total: number;
56
+ readonly page: number;
57
+ readonly pageSize: number;
58
+ }
59
+ /** `fetch` 端点出参(图片本体,base64 无 data URL 前缀)。 */
60
+ export interface ImageBytes {
61
+ readonly dataBase64: string;
62
+ readonly mimeType: string;
63
+ readonly byteSize: number;
64
+ }
65
+ /** 宿主 loopback RPC 调用面(结构兼容 `ctx.connection.rpc`)。 */
66
+ export interface ImageRpcPort {
67
+ call(channel: string, endpoint: string, payload: unknown, signal?: AbortSignal): Promise<unknown>;
68
+ }
69
+ /** 画廊页消费的最小取数面(便于测试注入假实现)。 */
70
+ export interface ImageDataSource {
71
+ list(page: number, pageSize?: number): Promise<ImageListPage>;
72
+ fetch(id: number): Promise<ImageBytes>;
73
+ save(id: number, workspaceId?: string): Promise<{
74
+ path: string;
75
+ }>;
76
+ }
77
+ /** 带稳定 code 的取数错误(message 已由 Host 消毒,可直接展示)。 */
78
+ export declare class ImageRpcError extends Error {
79
+ readonly code: string;
80
+ constructor(code: string, message: string);
81
+ }
82
+ /**
83
+ * 解开 DSH Connection 的结果信封(与 `usage-data.ts` 的 `unwrapUsageResult` 同语义)。
84
+ * 宿主返回 `{ok:true,value}` / `{ok:false,error:{code,message}}`;形状不合法时抛通用失败,
85
+ * 绝不把未知结构当数据渲染。
86
+ */
87
+ export declare function unwrapImageResult<T>(value: unknown): T;
88
+ /** 任意异常 → 可展示的 `{code, message}`(传输层异常不暴露原始 message)。 */
89
+ export declare function imageErrorOf(error: unknown): {
90
+ code: string;
91
+ message: string;
92
+ };
93
+ /**
94
+ * 归一 `list` 的返回:字段逐个显式取(**不**把未知结构当数据)。
95
+ * `content` 的形状由网关决定,这里只保证「视图拿到的一定是生产形状」。
96
+ */
97
+ export declare function normalizeImageListPage(value: unknown, fallbackPage: number, fallbackPageSize: number): ImageListPage;
98
+ /** 归一 `fetch` 的返回:base64 为空视为失败(空图不是可展示的结果)。 */
99
+ export declare function normalizeImageBytes(value: unknown): ImageBytes;
100
+ /**
101
+ * `/hiwork-image` 的三个只读/落盘端点(画廊页用)。**无缓存**:每次调用现取。
102
+ */
103
+ export declare class ImageGallerySource implements ImageDataSource {
104
+ private readonly rpc;
105
+ constructor(rpc: ImageRpcPort);
106
+ /** 分页拉取历史记录(`page` 从 1 开始;`pageSize` 上限 100)。 */
107
+ list(page?: number, pageSize?: number): Promise<ImageListPage>;
108
+ /** 取一张图的本体(**点哪张拉哪张**;超 20MiB 由 Host 回 `IMAGE_TOO_LARGE`)。 */
109
+ fetch(id: number): Promise<ImageBytes>;
110
+ /** 把一张图存到工作区(缺省落默认工作区,由 Host 决定)。 */
111
+ save(id: number, workspaceId?: string): Promise<{
112
+ path: string;
113
+ }>;
114
+ }
115
+ /**
116
+ * 图片本体 → data URL(预览用)。**不写盘、不指向本地文件**:字节是本次 `fetch` 的结果,
117
+ * `<img src>` 里只能是它自己。
118
+ * @param bytes - `fetch` 的返回。
119
+ * @returns `data:<mime>;base64,<本体>`。
120
+ */
121
+ export declare function imageDataUrl(bytes: ImageBytes): string;
122
+ /** 体积展示(1 位小数);缺失/非法返回 `—` 之外的空串由调用方决定。 */
123
+ export declare function formatImageBytes(bytes: number | undefined): string;
124
+ /** 列表行的提示词摘要(超长截断;空提示词给「(无提示词)」)。 */
125
+ export declare function imagePromptSummary(prompt: string, limit?: number): string;
126
+ /** 记录时间:非法时间串返回空串(视图渲染 `—`)。 */
127
+ export declare function imageTimeLabel(createdAt: string): string;
@@ -0,0 +1,97 @@
1
+ /**
2
+ * 「我的图片」画廊页(中央 feature `id: 'images'`、`order: 60`,**core 自己注册**)。
3
+ *
4
+ * 形态(spec §6.5):**左列表 + 右预览**。
5
+ * - 列表:分页(默认 20,「加载更多」追加),每行是提示词摘要 + 尺寸 + 时间 + 大小;
6
+ * - 预览:**只有选中某一条才 `fetch` 那张图**(这是「不一次拉几十 MB」的唯一守卫——
7
+ * 用例里断言未选中时零 `fetch` 调用);拿回来的字节用 **data URL** 渲染,
8
+ * **不写盘、不建指向本地文件的 `<img src>`**;
9
+ * - 动作:**「存到工作区」**(成功后行内显示落点路径)与**「复制提示词」**;
10
+ * **不做**「再来一张」——画廊是中央 takeover,退到会话才能发消息,跨屏填草稿的收益
11
+ * 不抵复杂度(列入 M1 评估)。
12
+ *
13
+ * 为什么注册在 core 而不是新插件:这两条 RPC 只走 `connection.rpc`(`/hiwork-image`),
14
+ * 而网关访问必须留在 core 的 Host 半边;本页除了 `connection.rpc` 与 feature 注册表
15
+ * 之外不依赖任何其它插件。历史读的是**既有**的员工自助端点,与 `image.enabled` 开关
16
+ * 无关(关开关只关「生成」,不关「查看」)。
17
+ *
18
+ * client bundle 纯度:不 import 任何 `@deepseek-ai/*` 运行时值,也不 import Host 代码。
19
+ */
20
+ import { type ReactElement } from 'react';
21
+ import type { FeatureRegistry, HiWorkFeature } from './feature-registry.ts';
22
+ import { type ImageDataSource } from './images-data.ts';
23
+ /** 中央 feature id(冻结契约)。 */
24
+ export declare const IMAGES_FEATURE_ID = "images";
25
+ /** 中央 feature 排序:跟在 workbench 10 / automation 20 / knowledge 30 / capabilities 40 / office 50 之后。 */
26
+ export declare const IMAGES_FEATURE_ORDER = 60;
27
+ /** 侧栏菜单文案(core 未注册 locale 命名空间,硬编码中文)。 */
28
+ export declare const IMAGES_FEATURE_LABEL = "\u6211\u7684\u56FE\u7247";
29
+ /** 注册段的 effect 标签(诊断用;也是接线守卫的锚点)。 */
30
+ export declare const IMAGES_FEATURE_EFFECT_LABEL = "hiwork-core: images feature";
31
+ /**
32
+ * 菜单图标:16 网格、`currentColor`、描边 1.2(官方图标集里没有「我的图片」这一格,
33
+ * 按官方比例自绘,惯例同 hiwork-im / feature-center 的自绘图标)。
34
+ */
35
+ export declare const IMAGES_FEATURE_ICON = "<svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\"><rect x=\"1.6\" y=\"2.6\" width=\"12.8\" height=\"10.8\" rx=\"1.8\" stroke=\"currentColor\" stroke-width=\"1.2\"></rect><circle cx=\"5.7\" cy=\"6.3\" r=\"1.05\" fill=\"currentColor\"></circle><path d=\"M2.6 11.4l3.1-2.9c.4-.4 1-.4 1.4 0l2.1 2 1.4-1.3c.4-.4 1-.4 1.4 0l1.4 1.3\" stroke=\"currentColor\" stroke-width=\"1.2\" stroke-linecap=\"round\"></path></svg>";
36
+ /** 空态(spec §6.5 逐字)。 */
37
+ export declare const IMAGES_EMPTY_TEXT = "\u8FD8\u6CA1\u6709\u751F\u6210\u8FC7\u56FE\u7247";
38
+ /** 空态的第二句(告诉用户怎么产生第一张)。 */
39
+ export declare const IMAGES_EMPTY_HINT = "\u5728\u4F1A\u8BDD\u91CC\u8BF4\u300E\u753B\u4E00\u5F20\u2026\u300F\u5C31\u80FD\u751F\u6210";
40
+ /** 加载中。 */
41
+ export declare const IMAGES_LOADING_TEXT = "\u6B63\u5728\u52A0\u8F7D\u56FE\u7247\u8BB0\u5F55\u2026";
42
+ /** 预览区未选中任何一条时的占位。 */
43
+ export declare const IMAGES_PREVIEW_PLACEHOLDER = "\u4ECE\u5DE6\u4FA7\u9009\u4E00\u5F20\u56FE\u7247\u67E5\u770B";
44
+ /** 预览图正在拉取(这是唯一会拉几十 MB 的地方,必须让用户看见)。 */
45
+ export declare const IMAGES_PREVIEW_LOADING_TEXT = "\u6B63\u5728\u52A0\u8F7D\u56FE\u7247\u2026";
46
+ /** `image.enabled=false` 时仍可看历史,但要说明「生成」被关掉了(spec §7.2 V11)。 */
47
+ export declare const IMAGES_DISABLED_NOTE = "\u7BA1\u7406\u5458\u8FD8\u6CA1\u5F00\u542F\u56FE\u7247\u751F\u6210";
48
+ /** 缺失字段的占位符(**不是 0**)。 */
49
+ export declare const IMAGES_MISSING_TEXT = "\u2014";
50
+ /** 动作文案。 */
51
+ export declare const IMAGES_SAVE_TEXT = "\u5B58\u5230\u5DE5\u4F5C\u533A";
52
+ export declare const IMAGES_SAVE_SAVED_PREFIX = "\u5DF2\u4FDD\u5B58\u5230 ";
53
+ export declare const IMAGES_SAVE_RUNNING_TEXT = "\u6B63\u5728\u4FDD\u5B58\u2026";
54
+ export declare const IMAGES_COPY_TEXT = "\u590D\u5236\u63D0\u793A\u8BCD";
55
+ export declare const IMAGES_MORE_TEXT = "\u52A0\u8F7D\u66F4\u591A";
56
+ export declare const IMAGES_RETRY_TEXT = "\u91CD\u8BD5";
57
+ export declare const IMAGES_SAVE_HINT = "\u56FE\u7247\u4F1A\u5B58\u5230\u4F60\u7684\u9ED8\u8BA4\u5DE5\u4F5C\u533A\uFF08~/HiWork/Default\uFF09\u7684\u300C\u56FE\u7247\u300D\u76EE\u5F55\u3002";
58
+ /** 复制反馈(同卡片口径:不可用只把提示切到「复制失败」)。 */
59
+ export declare const IMAGES_COPY_HINTS: {
60
+ readonly copied: "已复制";
61
+ readonly failed: "复制失败";
62
+ };
63
+ /** 画廊页属性:只依赖一个窄取数面(测试可注入假实现)。 */
64
+ export interface ImagesPageProps {
65
+ readonly source: ImageDataSource;
66
+ }
67
+ /**
68
+ * 「我的图片」画廊页。
69
+ * @param props - 取数面。
70
+ * @returns 页面元素。
71
+ */
72
+ export declare function ImagesPage({ source }: ImagesPageProps): ReactElement;
73
+ /**
74
+ * 注册所需的上下文面(只需要 `effect`)。
75
+ *
76
+ * 为什么写成窄面而不是要求真实 `ClientContext`:注册的**生命周期**才是这条链路的
77
+ * 不变量——`ctx.effect(() => registry.register(...))` 让注销随插件 fiber 走,插件热替换
78
+ * 后注册表里不会留下指向旧实例的条目。窄面让这条不变量可以单测(假 scope 记录
79
+ * factory 并手动触发清理),也不必在测试里拉起整个 cordis。
80
+ */
81
+ export interface ImagesFeatureScope {
82
+ /** cordis 的 effect 体**必须**返回 disposer(`SyncEffect` 不含纯 void),与真实 ctx.effect 同形。 */
83
+ effect(factory: () => () => void, label?: string): unknown;
84
+ }
85
+ /**
86
+ * 构造 feature 定义(纯函数:测试可直接断言 id / order / label / icon)。
87
+ * @param source - 画廊取数面。
88
+ * @returns feature 定义。
89
+ */
90
+ export declare function imagesFeature(source: ImageDataSource): HiWorkFeature;
91
+ /**
92
+ * 注册「我的图片」中央页(**用 `ctx.effect` 包住**:disposer 幂等,注销后从注册表消失)。
93
+ * @param scope - 带 `effect` 的上下文(`apply` 传真实 client ctx)。
94
+ * @param registry - core 自己的 Feature Center 注册表。
95
+ * @param source - 画廊取数面(经 `connection.rpc` 指向 `/hiwork-image`)。
96
+ */
97
+ export declare function installImagesFeature(scope: ImagesFeatureScope, registry: FeatureRegistry, source: ImageDataSource): void;
@@ -1,7 +1,6 @@
1
1
  import type { Context as ClientContext } from '@deepseek-ai/cordis';
2
2
  export type { FeatureRenderContext, FeatureRegistry, HiWorkFeature } from './feature-registry.ts';
3
3
  export type { HiWorkCoreClientFace } from './contracts.ts';
4
- /** 浏览器插件契约(dsh client module system)。connection 承载 loopback RPC(见 src/client/usage-data.ts)。 */
5
4
  export declare const inject: string[];
6
5
  /** 挂载网关模型设置 section(一级 tab)与未登录弹窗。 */
7
6
  export declare function apply(ctx: ClientContext): void;
@@ -1,4 +1,4 @@
1
- import type { SettingsScope } from '@deepseek-ai/dsh-client-ui-settings/client';
1
+ import type { ConfigForm } from '@deepseek-ai/dsh-client-ui-settings/client';
2
2
  import type { CredentialStorePort } from './controller.ts';
3
3
  import type { LoginError } from './types.ts';
4
4
  import { type AiRouterCardSettings } from './controller.ts';
@@ -19,4 +19,4 @@ export declare function loginGateVisible(snapshot: {
19
19
  };
20
20
  } | undefined;
21
21
  } | undefined): boolean;
22
- export declare function mountLoginGate(scope: SettingsScope<AiRouterCardSettings>, storeCredential: CredentialStorePort): () => void;
22
+ export declare function mountLoginGate(form: ConfigForm<AiRouterCardSettings>, storeCredential: CredentialStorePort): () => void;
@@ -2,5 +2,10 @@
2
2
  export declare const HIWORK_LOGO: string;
3
3
  export declare const HIWORK_LOGO_TEXT: string;
4
4
  export declare const HIWORK_LOGO_WITH_TEXT: string;
5
- /** HiWork 产品版本:品牌展示元素,与 hiwork-dsh-ui 的 HIWORK_VERSION 保持一致。 */
6
- export declare const HIWORK_VERSION = "1.0.4";
5
+ /**
6
+ * HiWork 产品版本兜底常量:只在**无桌面壳**(纯 web / 浏览器 dev,`/hiwork-app` 频道
7
+ * 取不到值)时显示——侧栏 brand 行与设置「关于」分区的回退值。桌面端在壳内永远走
8
+ * RPC 动态取真实版本,本常量不参与;跟随桌面端当前发布线手工维护(2026-09-25 对齐
9
+ * 1.0.7)。
10
+ */
11
+ export declare const HIWORK_VERSION = "1.0.7";
@@ -0,0 +1,163 @@
1
+ /**
2
+ * 侧栏菜单的每用户偏好(settings 命名空间 `hiwork-menu` 的读侧模型与纯函数)。
3
+ *
4
+ * 为什么单独一份:菜单适配器(`feature-center.ts`)与设置分区(`menu-section.tsx`)必须按**同一套**
5
+ * 规则回答「谁在侧栏、谁进『更多』、谁被隐藏、按什么顺序」,规则只允许有一处实现——这里。
6
+ * 本模块不碰 DOM、不碰 cordis,全部是可单测的纯函数。
7
+ *
8
+ * 命名空间字面量 `hiwork-menu` 在 host 半边(`src/index.ts` 的 `MENU_NS`)另有一份:client 半边的
9
+ * 打包 rootDir 是 `src/client`,不能 import host 模块(与 `ai-router` 同款处理)。两侧一致性由
10
+ * `tests/menu-namespace.spec.ts` 静态守着,改一处必须改两处。
11
+ */
12
+ import { type MenuFeature } from './feature-registry.ts';
13
+ /** settings 命名空间(host 侧同名注册;两侧字面量必须逐字一致)。 */
14
+ export declare const HIWORK_MENU_NS = "hiwork-menu";
15
+ /**
16
+ * 未自定义时侧栏直显的条数上限(不含原生的「新会话」)。
17
+ *
18
+ * 达到上限之后,用户没有明确标记的入口一律进「更多」——这正是「以后再装插件也不会把侧栏顶高」
19
+ * 的机制(新插件的 id 不在偏好里 = 未标记 = 超出上限就走「更多」)。
20
+ */
21
+ export declare const DEFAULT_SIDEBAR_LIMIT = 5;
22
+ /** 菜单里「更多」那一行的固定 id(与 feature id 同域,但永不来自注册表)。 */
23
+ export declare const MORE_MENU_ITEM_ID = "__hiwork_more__";
24
+ /** 「更多」这一行的文案(hiwork-core 未注册 locale 命名空间,沿用硬编码中文)。 */
25
+ export declare const MORE_MENU_LABEL = "\u66F4\u591A";
26
+ /**
27
+ * 设置分区与分区标题共用文案。
28
+ *
29
+ * `settings.section` 注册的 label 同时是设置弹窗导航按钮的可见文本,`openSettingsSection`
30
+ * 按它精确匹配跳转(见 `settings-navigation.ts`),改措辞要一起改。
31
+ */
32
+ export declare const MENU_SECTION_LABEL = "\u4FA7\u680F\u83DC\u5355";
33
+ /** 一个入口的三种去向。 */
34
+ export declare const MENU_PLACEMENTS: readonly ["sidebar", "more", "hidden"];
35
+ export type MenuPlacement = (typeof MENU_PLACEMENTS)[number];
36
+ /**
37
+ * 一个用户的侧栏菜单偏好(settings 命名空间的解析值)。
38
+ *
39
+ * 四个字段都是 `string[]`(feature id),不用三态对象数组:读写都发生在「把某个 id 挪一下」这一个
40
+ * 动作上,分开存可以直接把「一次移动」写成一次原子 `mutate`,也不怕以后加态。
41
+ */
42
+ export interface MenuPreferences {
43
+ /** 展示顺序;未列出的排在后面,按插件注册的 `order`。 */
44
+ readonly order: readonly string[];
45
+ /** 用户明确要求显示在侧栏的 id。 */
46
+ readonly sidebar: readonly string[];
47
+ /** 用户明确要求收进「更多」的 id。 */
48
+ readonly more: readonly string[];
49
+ /** 用户明确要求隐藏的 id(侧栏与「更多」都不出现)。 */
50
+ readonly hidden: readonly string[];
51
+ }
52
+ /** 空偏好:等价于「用户从未自定义」,分组按 {@link DEFAULT_SIDEBAR_LIMIT} 与注册顺序推导。 */
53
+ export declare const EMPTY_MENU_PREFERENCES: MenuPreferences;
54
+ /** 一次原子写入要落的一个字段操作(结构上与 settings 的 path op 相同,保持本模块无依赖)。 */
55
+ export interface MenuPreferenceOp {
56
+ readonly op: 'set';
57
+ readonly path: readonly string[];
58
+ /** 四个字段都是 id 数组,所以这里能给出比 settings 的 `unknown` 更紧的形状。 */
59
+ readonly value: readonly string[];
60
+ }
61
+ /** 分组结果:侧栏直显项与「更多」项(均按有效顺序,都不含原生的「新会话」)。 */
62
+ export interface MenuLayout {
63
+ readonly sidebar: readonly MenuFeature[];
64
+ readonly more: readonly MenuFeature[];
65
+ }
66
+ /** 当前偏好的可编辑快照(设置分区与菜单共用;`status` 决定控件是否可用)。 */
67
+ export interface MenuPreferencesView {
68
+ readonly status: 'loading' | 'ready' | 'unavailable';
69
+ readonly writable: boolean;
70
+ readonly value: MenuPreferences;
71
+ }
72
+ /**
73
+ * 把 settings 的解析值归一成偏好。
74
+ *
75
+ * 宽容优先:settings 文档可能被外部编辑过(旧版本、手工改过的 JSON、缺字段),这里只取认得的形状,
76
+ * 剩下的当空——菜单是导航入口,宁可退回默认分组,也不能因为偏好坏了就不出菜单。
77
+ * @param raw - 命名空间解析值(任意形状)。
78
+ * @returns 归一后的偏好。
79
+ */
80
+ export declare function normalizeMenuPreferences(raw: unknown): MenuPreferences;
81
+ /**
82
+ * 某个入口当前被明确标记的去向。
83
+ *
84
+ * 同一 id 同时出现在多个数组时按 `hidden` > `more` > `sidebar` 取舍(正常路径不会产生这种数据,
85
+ * 这是读侧兜底)。
86
+ * @param preferences - 偏好。
87
+ * @param id - feature id。
88
+ * @returns 明确标记时返回该去向,未标记返回 `undefined`。
89
+ */
90
+ export declare function placementOf(preferences: MenuPreferences, id: string): MenuPlacement | undefined;
91
+ /**
92
+ * 有效顺序:用户 `order` 里出现且当前在册的 id(按用户次序),再补上其余在册 id(按注册顺序)。
93
+ * @param features - 当前菜单条目全集(已注册 feature + 非注册表代理项,结构上都是 {@link MenuFeature})。
94
+ * @param preferences - 偏好。
95
+ * @returns 归一后的 id 序列(长度 = 在册条目数)。
96
+ */
97
+ export declare function orderedFeatureIds(features: readonly MenuFeature[], preferences: MenuPreferences): string[];
98
+ /**
99
+ * 按偏好把当前注册的 feature 分成「侧栏直显」与「更多」两组。
100
+ *
101
+ * 规则(spec §2,实施期不得另立一套):
102
+ * - `hidden` 的既不进侧栏也不进「更多」(**不占名额**:腾出的位置由后面的入口补上);
103
+ * - 明确标记为侧栏的**永远直显**;
104
+ * - 明确标记为「更多」的**永远进更多,并且占掉一个侧栏名额**(`budget -= 1`)——
105
+ * 这条是「用户在设置页把一项挪进『更多』,侧栏就真的少一行」的实现:不占名额的话,
106
+ * 腾出的位置会被后面的入口立刻补上,行数不变,用户会看到「点了没反应」;
107
+ * - 未标记的按有效顺序填空,填到侧栏凑满 `budget` 为止,其余进「更多」。
108
+ *
109
+ * 名额(`budget`)只统计**已注册**的标记项:偏好里残留的未知 id(别的设备装的插件)不该
110
+ * 在本机吃掉名额。
111
+ * @param features - 当前菜单条目全集。
112
+ * @param preferences - 偏好。
113
+ * @returns 两组(各自保持有效顺序)。
114
+ */
115
+ export declare function menuLayout(features: readonly MenuFeature[], preferences: MenuPreferences): MenuLayout;
116
+ /**
117
+ * 设置某个入口的去向(设置分区的一次点击)。
118
+ *
119
+ * **只动去向数组,不碰 `order`**:`order` 只由显式排序(拖拽 / 键盘上·下移)写。
120
+ * 早先这里会顺手把 id 追加进 `order`,后果是「点一下『更多』」会把该项钉到有效顺序首位
121
+ * (`orderedFeatureIds` 先排 `order` 里的 id)——设置页的列表会毫无理由地重排,实机可见。
122
+ * @param preferences - 当前偏好。
123
+ * @param id - feature id。
124
+ * @param placement - 目标去向。
125
+ * @returns 新偏好;与该去向已经一致时原样返回(调用方据此跳过写盘)。
126
+ */
127
+ export declare function setMenuPlacement(preferences: MenuPreferences, id: string, placement: MenuPlacement): MenuPreferences;
128
+ /**
129
+ * 把某个入口移到有效顺序的第 `targetIndex` 位(拖拽落点与键盘上/下移共用)。
130
+ *
131
+ * 只写 `order` 一个字段:去向(侧栏/更多/隐藏)是另一维,排序动作不碰它。
132
+ * @param preferences - 当前偏好。
133
+ * @param features - 当前注册的 feature。
134
+ * @param id - 被移动的 feature id。
135
+ * @param targetIndex - 目标下标(越界自动收敛到两端)。
136
+ * @returns 新偏好;位置未变时原样返回。
137
+ */
138
+ export declare function moveMenuEntry(preferences: MenuPreferences, features: readonly MenuFeature[], id: string, targetIndex: number): MenuPreferences;
139
+ /**
140
+ * 把某个入口放到另一个入口之前/之后(设置页拖拽落点)。
141
+ *
142
+ * 与 {@link moveMenuEntry} 的下标语义区分开:拖拽时用户看到的落点是「在谁的前/后」,
143
+ * 换算成下标要考虑自身被摘掉后的位移,直接用下标会差一位(拖到目标行下方却落在上方)。
144
+ * @param preferences - 当前偏好。
145
+ * @param features - 当前注册的 feature。
146
+ * @param id - 被拖动的 feature id。
147
+ * @param targetId - 落点所在的 feature id。
148
+ * @param position - 落在目标之前还是之后。
149
+ * @returns 新偏好;结果与当前顺序相同时原样返回。
150
+ */
151
+ export declare function moveMenuEntryNear(preferences: MenuPreferences, features: readonly MenuFeature[], id: string, targetId: string, position: 'before' | 'after'): MenuPreferences;
152
+ /** 两份偏好的四个字段是否逐项相同(数组按内容比较)。 */
153
+ export declare function sameMenuPreferences(left: MenuPreferences, right: MenuPreferences): boolean;
154
+ /**
155
+ * 两份偏好之间真正变化的字段(只写变化的那几个)。
156
+ *
157
+ * 用 `set` 而不是整段替换:设置分区在弹窗里跑,用户改一项就走一次写盘,没必要把另外三个字段一起
158
+ * 重写(写盘带着 revision 栅栏,写得越少越不容易撞上并发的自我冲突)。
159
+ * @param current - 写盘前已知的偏好。
160
+ * @param next - 目标偏好。
161
+ * @returns 按字段的路径操作;无变化时为空数组。
162
+ */
163
+ export declare function menuPreferenceOps(current: MenuPreferences, next: MenuPreferences): MenuPreferenceOp[];
@@ -0,0 +1,5 @@
1
+ import type { InjectFace, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots';
2
+ import type { MenuSectionFace } from './menu-store.ts';
3
+ export type MenuSectionProps = PropsRuntime<'settings.section'> & InjectFace<MenuSectionFace>;
4
+ /** 设置页「侧栏菜单」分区。 */
5
+ export declare function MenuSection(props: MenuSectionProps): import("react").JSX.Element;
@@ -0,0 +1,101 @@
1
+ /**
2
+ * 「侧栏菜单」设置分区的状态控制器:把 `ai-router` 配置表单里的菜单偏好(`menu` 字段)
3
+ * 与 feature 注册表合成一份可渲染状态,并把设置页的动作翻译成一次原子写盘。
4
+ *
5
+ * rc.2 起 settings 只服务「有 profile entry 的 Config」,纯客户端偏好的 `hiwork-menu`
6
+ * 命名空间不复存在——偏好并入 `ai-router` entry 的 `menu` volatile 字段,读写都通过与
7
+ * 网关卡片**同一个** ConfigForm 实例(configForms 按 entry id 共享控制器,见
8
+ * `src/client/index.ts`),菜单适配器与设置分区因此天然同源。
9
+ * 本模块不碰 DOM,可单测。
10
+ *
11
+ * 写盘纪律(spec §4):
12
+ * - 只在 `status === 'ready' && writable` 时写;否则动作是 no-op(`menu-section` 已禁用控件)。
13
+ * - 一次动作 = 一次 `mutate`,只带真正变化的字段(见 `menuPreferenceOps`,路径带 `menu` 前缀)。
14
+ * - 目标去向与当前**生效**去向一致时不写盘:用户在设置页点到已经成立的状态不该产生写入,
15
+ * 否则「点『侧栏』」会给一项已直显的入口加上显式标记,反而挤掉后面某个入口的名额。
16
+ */
17
+ import type { SnapshotStore } from '@deepseek-ai/dsh-client-store';
18
+ import type { ConfigForm } from '@deepseek-ai/dsh-client-ui-settings/client';
19
+ import type { AiRouterCardSettings } from './controller.ts';
20
+ import type { FeatureRegistry, MenuFeature } from './feature-registry.ts';
21
+ import { type MenuPlacement, type MenuPreferences } from './menu-preferences.ts';
22
+ /** 设置页一行:一个入口的当前去向与在列表中的位置。 */
23
+ export interface MenuEntryView {
24
+ /** feature id(拖拽与写盘都按它定位)。 */
25
+ readonly id: string;
26
+ /** 菜单文案(已求值;语言变化由订阅方重建)。 */
27
+ readonly label: string;
28
+ /** 插件给的图标(内联 SVG 字符串),与侧栏菜单同源。 */
29
+ readonly icon: string | undefined;
30
+ /** **生效**去向(未标记的项也给出推导结果,控件据此显示当前状态)。 */
31
+ readonly placement: MenuPlacement;
32
+ /** 用户是否明确标记过(UI 用它区分「你选的」与「默认推导」)。 */
33
+ readonly marked: boolean;
34
+ /** 在有效顺序中的下标与总数(键盘移动与 aria-label 用)。 */
35
+ readonly index: number;
36
+ readonly total: number;
37
+ }
38
+ /** 设置分区的渲染状态。 */
39
+ export interface MenuSectionState {
40
+ /** 与 settings 作用域同义:`loading` / `ready` / `unavailable`。 */
41
+ readonly status: 'loading' | 'ready' | 'unavailable';
42
+ /** 宿主文档是否接受写入(memory 模式恒 false)。 */
43
+ readonly writable: boolean;
44
+ /** 按有效顺序的全部入口(不含原生的「新会话」)。 */
45
+ readonly entries: readonly MenuEntryView[];
46
+ /** 侧栏当前直显条数上限(= 默认上限 − 已标记「更多」的个数),UI 用来说明规则。 */
47
+ readonly limit: number;
48
+ /** 控件不可用时的原因文案;可用时为 undefined。 */
49
+ readonly reason: string | undefined;
50
+ }
51
+ /** 设置分区注入面(`settings.section#hiwork-menu`)。 */
52
+ export interface MenuSectionFace {
53
+ hooks: {
54
+ menu: SnapshotStore<MenuSectionState>;
55
+ };
56
+ actions: {
57
+ /** 设置某个入口的去向。 */
58
+ setPlacement(id: string, placement: MenuPlacement): Promise<void>;
59
+ /** 把某个入口上/下移一格(键盘路径)。 */
60
+ move(id: string, offset: number): Promise<void>;
61
+ /** 把某个入口拖到另一个入口之前/之后。 */
62
+ moveNear(id: string, targetId: string, position: 'before' | 'after'): Promise<void>;
63
+ /** 恢复默认(清掉全部四个字段,回到默认分组)。 */
64
+ reset(): Promise<void>;
65
+ };
66
+ }
67
+ /** 侧栏菜单的设置分区控制器。 */
68
+ export declare class MenuStore {
69
+ private readonly form;
70
+ private readonly registry;
71
+ private readonly additionalFeatures?;
72
+ private readonly store;
73
+ private readonly unsubscribe;
74
+ /**
75
+ * @param form - `ai-router` entry 的配置表单(与网关卡片同一个共享实例,见 `src/client/index.ts`)。
76
+ * @param registry - feature 注册表(列表内容与顺序的另一个来源)。
77
+ * @param additionalFeatures - 追加的非注册表条目(官方「插件」代理项;可用性由调用方判断)。
78
+ * 缺省时列表只有注册表成员——与旧版行为一致。
79
+ */
80
+ constructor(form: ConfigForm<AiRouterCardSettings>, registry: FeatureRegistry, additionalFeatures?: (() => readonly MenuFeature[]) | undefined);
81
+ /** 当前偏好(未就绪 / 值非法时按「从未自定义」处理)。 */
82
+ preferences(): MenuPreferences;
83
+ /** 订阅偏好变化(菜单适配器用;与 `preferences()` 同源)。 */
84
+ subscribePreferences(listener: () => void): () => void;
85
+ /** 菜单条目全集(注册表 + 追加的代理项),与侧栏适配器的来源同构。 */
86
+ private menuFeatures;
87
+ /** 当前渲染状态。 */
88
+ view(): MenuSectionState;
89
+ /** 清掉在这份 store 上的全部订阅(插件卸载时调用)。 */
90
+ dispose(): void;
91
+ /** 立即重算渲染状态(追加条目的可用性变化等非表单/注册表来源的变化走这里)。 */
92
+ refresh(): void;
93
+ /** 注入面:设置分区组件消费。 */
94
+ inject(): MenuSectionFace;
95
+ private setPlacement;
96
+ private move;
97
+ private moveNear;
98
+ private reset;
99
+ /** 未标记项的生效去向已由 `view()` 给出,这里不再另算一份。 */
100
+ private write;
101
+ }
@@ -130,6 +130,22 @@ export declare const brandCss: string;
130
130
  * 由 client 入口 apply() 注入一次(带 id 防重)。
131
131
  */
132
132
  export declare const interactionCss: string;
133
+ /**
134
+ * 生图能力的样式(会话内工具卡片 + 「我的图片」画廊页),随本插件一起分发。
135
+ *
136
+ * 类名统一 `hiwork-image-*` / `hiwork-images-*` 前缀,颜色全部走 dsh 主题变量
137
+ * (亮暗自动切换、与设置页分区同一套 token);只有「放大遮罩」是自带的半透明黑
138
+ * ——那是浮层,与宿主内部结构无关。
139
+ *
140
+ * 注入走 `image-styles.ts` 的 `ensureImagesStyle`,**必须**经 {@link ownStyleTag}
141
+ * 打归属标记(见下方事故记录):裸注入会被随后装载的插件认领并连带删除。
142
+ */
143
+ export declare const imageCardsCss: string;
144
+ /**
145
+ * 「我的图片」画廊页(中央 feature `images`)的样式:左列表 + 右预览。
146
+ * 左栏 grid 固定 240–320px、右栏自适应;两栏各自滚动,页面本身不滚。
147
+ */
148
+ export declare const imagesPageCss: string;
133
149
  /**
134
150
  * 手写注入的 <style> 必须带 DSH 的归属标记(`data-plugin`)。
135
151
  *
@@ -32,6 +32,15 @@ export type LoginErrorCode = 'AUTH_FAILED' | 'RATE_LIMITED' | 'GATEWAY_UNREACHAB
32
32
  * 消费方据此显示「本部署尚未启用工作台」而不是报错。别顺手把两边对齐。
33
33
  */
34
34
  | 'WORKBENCH_DISABLED'
35
+ /**
36
+ * 本部署未配置知识库:取配置或取令牌时 503。
37
+ *
38
+ * 语义与 `OFFICE_NOT_CONFIGURED` 同族:**部署状态**,不是故障。消费方(hiwork-knowledge)
39
+ * 据此给出「管理员还没开启知识库」并把状态判成终局——不排重试、不把员工指向「范围未分配」。
40
+ * 之前 503 一律收敛成 `GATEWAY_UNREACHABLE`,插件把「管理员没配签名私钥」读成「服务暂时不可用」
41
+ * 并白打约 10 分钟重试,员工看到的还是「管理员尚未分配知识库访问范围」,指向错误的部门。
42
+ */
43
+ | 'KNOWLEDGE_NOT_CONFIGURED'
35
44
  /** 本部署未启用语音识别(管理员把开关关了):识别时 503(或 503 无 body code)。 */
36
45
  | 'VOICE_DISABLED'
37
46
  /** 本部署未配置语音识别上游服务(网关侧没有可用 ASR 配置):识别时 503。 */