hiwork-core 0.0.18 → 0.0.19

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.
@@ -0,0 +1,357 @@
1
+ /**
2
+ * 「PMS 个人待办」客户端取数层:经 `/hiwork-pms-todo` loopback RPC 拿到自己的待办、计数,
3
+ * 并托管登录 / 登出(凭据本身**不经过本层**,见下),交给工作台插件(hiwork-workbench)的
4
+ * 右栏 tab 渲染。
5
+ *
6
+ * 契约见 spec `docs/superpowers/specs/2026-09-17-pms-todo-integration-design.md` §4.1(Host 频道)、
7
+ * §4.2(`TodoItem`)、§4.3(本面)、§3 D7(**我的任务**:待办之外还要直接查未完成的项目任务)。
8
+ * 本文件与 `src/client/managed-workbench.ts` 同款,是同一副骨架的**第四条链**,差别只有一处:
9
+ * 那条链是纯只读,这一条还带「登录 / 登出」两个动作。
10
+ *
11
+ * 铁律:
12
+ * - **本层不持有任何凭据**:账号密码只在 `login()` 的入参里出现一次,随即交给 Host;access /
13
+ * refresh token、PMS 的域名与路径**全都不在本文件里**(client bundle 里搜不到 PMS 凭据,§7);
14
+ * - **不 fetch、不直连 PMS**:一切经 Host 频道——这正是 §3 D6「凭据与数据流都在 Host」在
15
+ * 客户端一侧的落地形状;
16
+ * - **错误码原样透传**:Host 的六个稳定码(`NOT_CONFIGURED` / `UNAUTHORIZED` / `FORBIDDEN` /
17
+ * `UPSTREAM_UNAVAILABLE` / `BAD_REQUEST` / `INTERNAL`)由消费方决定文案(未配置 → 引导填
18
+ * 凭据;`UNAUTHORIZED` → 要求重新输入;`FORBIDDEN` → 「到 PMS 操作」,**不是**重新登录)。
19
+ * 本层**不改写、不吞掉**它们。
20
+ *
21
+ * client bundle 纯度:本文件不 import 任何 `@deepseek-ai/*` 运行时值,也不 import Host 代码
22
+ * (类型按结构重声明,字段名与契约一致)。
23
+ */
24
+ /** RPC 频道;必须与 Host `src/pms-todo-bridge.ts` 的 `PMS_TODO_RPC_CHANNEL` 一致(由测试锁定)。 */
25
+ export declare const PMS_TODO_CHANNEL = "/hiwork-pms-todo";
26
+ /**
27
+ * 端点白名单;必须与 Host `PMS_TODO_RPC_ENDPOINTS` 一致(由测试锁定)。
28
+ * 顺序即冻结契约里的顺序(`status` / `login` / `logout` 是凭据生命周期,
29
+ * `today` / `statistics` / `setStatus` 是待办数据,`myTasks` / `completeTask` 是项目任务 —— §3 D7)。
30
+ */
31
+ export declare const PMS_TODO_ENDPOINTS: readonly ["status", "login", "logout", "today", "statistics", "setStatus", "myTasks", "completeTask"];
32
+ export type PmsTodoEndpoint = (typeof PMS_TODO_ENDPOINTS)[number];
33
+ /** 勾选用的两个字面量(PMS 只认这两个)。 */
34
+ export type PmsTodoStatusValue = 'PENDING' | 'COMPLETED';
35
+ /**
36
+ * 一条待办(**冻结契约**,spec §4.2 的 `TodoItem` 投影,字段名与 Host 一一对应)。
37
+ *
38
+ * 两处开放的形状(消费方按它降级,与工作台清单的 `executor.kind` 同款纪律):
39
+ * - `status` / `priority` / `category` 都是**开放集合**:不认识的取值必须原样出现在这里,
40
+ * 由消费方自己决定怎么渲染(例如不认识的 `priority` 就不显示徽标),**绝不**因为它不认识
41
+ * 就把这条待办判为无效——那会让用户看到一份莫名变少的列表;
42
+ * - 可选字段按服务端给没给来(没给就没有这个键)。
43
+ */
44
+ export interface PmsTodoItem {
45
+ id: number;
46
+ title: string;
47
+ description?: string;
48
+ /** `TODO` | `TEMP` | 将来新增的值。 */
49
+ category?: string;
50
+ /** `HIGH` | `MEDIUM` | `LOW` | 将来新增的值。 */
51
+ priority?: string;
52
+ /** `PENDING` | `COMPLETED` | 将来新增的值(**原样透传**)。 */
53
+ status: string;
54
+ startDate?: string;
55
+ dueDate?: string;
56
+ completedTime?: string;
57
+ /** 服务端判定(客户端不重算)。 */
58
+ isOverdue?: boolean;
59
+ isDueSoon?: boolean;
60
+ }
61
+ /** `status` / `login` / `logout` 的返回值(spec §4.1)。 */
62
+ export interface PmsTodoStatus {
63
+ configured: boolean;
64
+ loggedIn: boolean;
65
+ username?: string;
66
+ /** access token 的绝对过期时刻(ISO 8601)。**不是** token 本身。 */
67
+ expiresAt?: string;
68
+ }
69
+ /** `today` 的返回值。 */
70
+ export interface PmsTodoToday {
71
+ items: PmsTodoItem[];
72
+ /** 本次成功拉取的时刻(ISO 8601);界面用它显示「更新于 …」。 */
73
+ fetchedAt: string;
74
+ }
75
+ /** `statistics` 的返回值。 */
76
+ export interface PmsTodoStatistics {
77
+ total: number;
78
+ pending: number;
79
+ completed: number;
80
+ overdue: number;
81
+ }
82
+ /** setStatus 的返回值(面里折成 void,仓库里保留给诊断与测试)。 */
83
+ export interface PmsTodoSetStatusResult {
84
+ id: number;
85
+ status: PmsTodoStatusValue;
86
+ completedTime?: string;
87
+ }
88
+ /**
89
+ * 一条**项目任务**(spec §3 D7;Host `PmsTaskItem` 的**结构重声明**——本层不 import Host 代码)。
90
+ * 数字 / 字符串字段与 PMS `TaskVO` 一一对应;`projectStageName` / `requirementName` /
91
+ * `moduleName` / `estimatedHours` / `actualHours` 是设计里明确可选的五项(服务端没给就没有这个键)。
92
+ *
93
+ * 演进纪律(与 {@link PmsTodoItem} 同款):`status`(0-待办 / 1-进行中 / 2-已完成 / 3-逾期)与
94
+ * `priority` 是**数字**,将来 PMS 加档位照样原样透传;`statusName` / `priorityName` 是给人看的
95
+ * 名字,同样不校验取值。**未知字段忽略**(老 client 不认识的新字段不进契约面)。
96
+ */
97
+ export interface PmsTaskItem {
98
+ id: number;
99
+ taskName: string;
100
+ description: string;
101
+ projectId: number;
102
+ projectName: string;
103
+ projectStageName?: string;
104
+ assigneeId: number;
105
+ assigneeName: string;
106
+ /** `yyyy-MM-dd HH:mm:ss`;没有截止时间时是空串。 */
107
+ deadline: string;
108
+ /** 0-待办 / 1-进行中 / 2-已完成 / 3-逾期。 */
109
+ status: number;
110
+ statusName: string;
111
+ /** 1-低 / 2-中 / 3-高。 */
112
+ priority: number;
113
+ priorityName: string;
114
+ taskType: string;
115
+ taskTypeName: string;
116
+ /** 0-待审核 / 1-已通过 / 2-已驳回。 */
117
+ auditStatus: number;
118
+ auditStatusName: string;
119
+ requirementName?: string;
120
+ moduleName?: string;
121
+ estimatedHours?: number;
122
+ actualHours?: number;
123
+ }
124
+ /**
125
+ * `myTasks` 的返回值(§3 D7):**分页对象**,不是数组。
126
+ * 与 `today`(`{items, fetchedAt}`)形状不同是有意的:任务那边服务端就是分页的(`PageResult`),
127
+ * 本层把 `records` 收进 `items` 但**保留** total / current / size,免得界面想显示「共 N 条」时
128
+ * 只能数当前这一页。
129
+ */
130
+ export interface PmsTaskList {
131
+ items: PmsTaskItem[];
132
+ total: number;
133
+ current: number;
134
+ size: number;
135
+ }
136
+ /** `completeTask` 的返回值(面里折成 void,仓库里保留给诊断与测试)。 */
137
+ export interface PmsTaskCompleteResult {
138
+ id: number;
139
+ /** 任务状态数字(完成是 2)。 */
140
+ status: number;
141
+ }
142
+ /** 登录入参。 */
143
+ export interface PmsTodoLoginInput {
144
+ username: string;
145
+ password: string;
146
+ /** 是否把凭据交给 credentials 服务加密保存(Host 侧语义,见 spec §4.4)。 */
147
+ remember?: boolean;
148
+ }
149
+ /**
150
+ * 「PMS 待办」面(spec §4.3 冻结的七项 + §3 D7 追加的两项)。
151
+ *
152
+ * 它挂在 `HiWorkCoreClientFace` 上作为**可选成员** `pmsTodo?`:老 core 没有这个面,老
153
+ * workbench 也不会来取;两侧因此不必同发一个版本——老 core 遇新 workbench 就是
154
+ * 「请升级 HiWork 客户端」(spec §6 的降级矩阵第一行)。
155
+ *
156
+ * ⚠️ **`myTasks` / `completeTask` 是这一面里的两级可选**(版本闸口):§3 D7 是 2026-09-18 追加的,
157
+ * 装着的旧 core 有 `pmsTodo` 面但**没有**这两项。所以它们是**可选成员**,消费方必须按
158
+ * `face.pmsTodo.myTasks !== undefined` 判断(拿不到就只显示待办那一半,而不是崩页、也不是把
159
+ * 「我的任务」渲染成空列表——那会让人以为任务都完成了)。反过来说,**不许**把它们改成必填:
160
+ * 那会让新插件在旧 core 上直接抛 TypeError(测试用「只实现旧七项的 face 仍满足本接口」钉死这一点)。
161
+ */
162
+ export interface PmsTodoFace {
163
+ /**
164
+ * 本地镜像(**同步读**,可能是 `undefined`)。
165
+ *
166
+ * ⚠️ 它是镜像是**有意的**:界面渲染是同步的,而「有没有配置凭据」是异步来的。镜像由
167
+ * `login` / `logout` 的返回值、以及**首次调用本方法时的一次惰性探测**写入(结果到达后通知
168
+ * 订阅者),所以调用方必须 `subscribe()` ——首次渲染很可能拿到 `undefined`,等通知到了再重渲。
169
+ */
170
+ readonly status: () => PmsTodoStatus | undefined;
171
+ /** 登录(凭据经 Host 换成 JWT;密码只在这一次调用里出现,本层不留存)。失败抛错。 */
172
+ readonly login: (input: PmsTodoLoginInput) => Promise<PmsTodoStatus>;
173
+ /** 登出(Host 清内存凭据 + 抹掉 credentials 记录)。 */
174
+ readonly logout: () => Promise<PmsTodoStatus>;
175
+ /** 今日待办(⚠️ 口径比名字宽:今天 + 明天 + 无截止日期,spec §D4)。失败抛错。 */
176
+ readonly today: (signal?: AbortSignal) => Promise<PmsTodoToday>;
177
+ /** total / pending / completed / overdue 计数。失败抛错。 */
178
+ readonly statistics: (signal?: AbortSignal) => Promise<PmsTodoStatistics>;
179
+ /**
180
+ * 勾完成 / 取消完成。
181
+ *
182
+ * ⚠️ 副作用(spec §D5):这是某个任务的最后一条待办时,**PMS 会把项目任务一并标记完成**。
183
+ * 确认文案是 UI 的职责,本层不替它决定要不要问。
184
+ */
185
+ readonly setStatus: (input: {
186
+ id: number;
187
+ status: PmsTodoStatusValue;
188
+ }) => Promise<void>;
189
+ /**
190
+ * **我的未完成项目任务**(spec §3 D7)。**可选成员**——旧 core 上没有它(见本接口的抬头)。
191
+ *
192
+ * 「谁的任务」由 Host 从凭据记录里取(客户端传不进来),所以这里没有 `userId` 入参。
193
+ * 失败抛错,错误码可判 `FORBIDDEN`(没有完成任务权限的角色会在**完成**时报它)。
194
+ */
195
+ readonly myTasks?: (signal?: AbortSignal) => Promise<PmsTaskList>;
196
+ /**
197
+ * 完成一条项目任务(`status=2`)。**可选成员**——旧 core 上没有它(见本接口的抬头)。
198
+ *
199
+ * ⚠️ 与 `setStatus`(待办)是**两条不同的上游路径**:待办那条没有权限注解,这一条需要
200
+ * `task:list:edit`;缺权限时抛 `FORBIDDEN`,界面要按 §3 D7 说「在 HiWork 里没有完成任务的权限,
201
+ * 请到 PMS 操作」,**不要**当成「登录过期」让用户重新输密码。
202
+ */
203
+ readonly completeTask?: (input: {
204
+ id: number;
205
+ }) => Promise<void>;
206
+ /** 订阅镜像变化(登出 / 换账号时会被清空)。返回退订函数。 */
207
+ readonly subscribe: (listener: () => void) => () => void;
208
+ }
209
+ /** 宿主 loopback RPC 调用面(结构兼容 `ctx.connection.rpc`)。 */
210
+ export interface PmsTodoRpcPort {
211
+ call(channel: string, endpoint: string, payload: unknown, signal?: AbortSignal): Promise<unknown>;
212
+ }
213
+ /** 带稳定 code 的取数错误(message 已由 Host 消毒,可直接展示)。 */
214
+ export declare class PmsTodoRpcError extends Error {
215
+ readonly code: string;
216
+ constructor(code: string, message: string);
217
+ }
218
+ /**
219
+ * 解开 DSH Connection 的结果信封。
220
+ * 宿主返回 `{ok:true,value}` / `{ok:false,error:{code,message}}`;形状不合法时抛
221
+ * 通用失败,绝不把未知结构当数据用。
222
+ *
223
+ * `ok:false` 一律**抛错**——这是「拉取失败」与「拉到一份空列表」之间的那道闸:把它读成
224
+ * `value`,一次网络抖动就会变成「今天没有待办」(用户以为活都干完了)。返回 `value` 的唯一
225
+ * 前提是 `ok === true`。
226
+ */
227
+ export declare function unwrapPmsTodoResult<T>(value: unknown): T;
228
+ /** 任意异常 → 可展示的 `{code, message}`(传输层异常不暴露原始 message)。 */
229
+ export declare function pmsTodoErrorOf(error: unknown): {
230
+ code: string;
231
+ message: string;
232
+ };
233
+ /**
234
+ * PMS 待办的取数仓库。
235
+ *
236
+ * 本地镜像(`status()` 同步读)是有必要的:DSH 的 client 半边在不同插件之间只通过这个面同步取
237
+ * 状态,而「配没配凭据」是异步来的。镜像**只由成功响应覆写**,失败时不改动它。
238
+ */
239
+ export declare class PmsTodoStore {
240
+ private readonly rpc;
241
+ private current;
242
+ private readonly listeners;
243
+ /**
244
+ * 在飞请求**连同它所属的世代与端点**一起记账。
245
+ *
246
+ * 世代:`clear()`(登出 / 换账号)之后,账上那条是**上一个人**发起的,它的待办属于上一个人
247
+ * ——复用它等于让新账号看到上一个人的待办。端点:`today` 与 `statistics` 的返回值形状不同,
248
+ * 只有同一个端点的并发调用才允许合并。
249
+ */
250
+ private inFlight;
251
+ /** 世代号:`clear()` 每调用一次 +1,用来作废它之前发起的在飞请求。 */
252
+ private generation;
253
+ /** 是否已经为「镜像还空着」的情况探过一次 `status`(避免每次同步读都发一次 RPC)。 */
254
+ private statusProbed;
255
+ constructor(rpc: PmsTodoRpcPort);
256
+ /**
257
+ * 本地镜像(同步)。从没同步过返回 `undefined`。
258
+ *
259
+ * ⚠️ 本方法有一次**惰性探测**:镜像还空着且从没探过时,它顺手发起一次 `status` 端点调用
260
+ * (结果到达后写镜像 + 通知订阅者)。理由——渲染是同步的,而「配没配凭据」只有主机知道;
261
+ * 没有这一步,`status` 端点就永远没有调用方,界面也只能靠一次失败的 `today()` 去反推。
262
+ * 因此调用方看到 `undefined` **不是**「未配置」,而是「还不知道」:必须订阅。
263
+ */
264
+ status(): PmsTodoStatus | undefined;
265
+ /**
266
+ * 显式去主机取一次状态并写镜像(`status()` 的异步入口)。
267
+ *
268
+ * 失败**不改镜像**(保留上一次已知状态),也**不在镜像里记错误**:错误属于这一次调用,
269
+ * 调用方(或下一次 `today()`)自己决定怎么展示。
270
+ */
271
+ refreshStatus(signal?: AbortSignal): Promise<PmsTodoStatus>;
272
+ /**
273
+ * 登录(凭据交给 Host;**本层不留存密码、不写任何缓存**)。
274
+ *
275
+ * 成功时把返回的状态写进镜像——登录是「身份刚变过」,镜像必须跟着变。
276
+ */
277
+ login(input: PmsTodoLoginInput): Promise<PmsTodoStatus>;
278
+ /** 登出(Host 侧清凭据);返回登出后的状态并写镜像。 */
279
+ logout(): Promise<PmsTodoStatus>;
280
+ /**
281
+ * 今日待办。
282
+ *
283
+ * 不做本地缓存(返回**刚拉到的**那一份):待办的时效性就是它的全部价值,把上一次的结果当
284
+ * 「今日」交出去正是这个功能最容易犯的错;界面要离线态时自己留一份 + 标陈旧(spec §6)。
285
+ */
286
+ today(signal?: AbortSignal): Promise<PmsTodoToday>;
287
+ /** total / pending / completed / overdue 计数。 */
288
+ statistics(signal?: AbortSignal): Promise<PmsTodoStatistics>;
289
+ /**
290
+ * 勾完成 / 取消完成。
291
+ *
292
+ * 返回值里带服务端回声的 id / status(`PmsTodoFace.setStatus` 折成 `void`;面里不需要它,
293
+ * 但仓库保留着——回滚那一条待办时用它认人最稳)。
294
+ */
295
+ setStatus(input: {
296
+ id: number;
297
+ status: PmsTodoStatusValue;
298
+ }): Promise<PmsTodoSetStatusResult>;
299
+ /**
300
+ * **我的未完成项目任务**(spec §3 D7):`myTasks` 端点的取数。
301
+ *
302
+ * 入参是**空对象**,没有 `userId`——「谁的任务」由 Host 从凭据记录里取(客户端连自己的
303
+ * PMS userId 都拿不到,这是 §3 D6 形状的一部分)。
304
+ *
305
+ * 与 `today` 同款:**不做本地缓存**(返回刚拉到的那一份),但**并发调用合并**(同一时刻问两遍
306
+ * 没有意义;世代判据同其他端点,换账号之后不复用)。
307
+ */
308
+ myTasks(signal?: AbortSignal): Promise<PmsTaskList>;
309
+ /**
310
+ * **完成一条项目任务**(spec §3 D7)。
311
+ *
312
+ * 返回值里带服务端回声的 id / status(`PmsTodoFace.completeTask` 折成 `void`;仓库保留着
313
+ * ——回滚那一条任务时用它认人最稳)。缺权限时抛 `PmsTodoRpcError('FORBIDDEN')`。
314
+ *
315
+ * ⚠️ **刻意不走 {@link PmsTodoStore.call}**(即不参与并发合并):`dedupe` 的键是**端点名**,
316
+ * 而这是带参数的命令——把它并进同一格会让「几乎同时完成 A 和 B」的第二次调用**复用第一次的
317
+ * 结果**,用户以为 B 完成了,其实请求根本没发出去。Host 侧的 `completeTask` 同样不合并
318
+ * (那里也注明「点了两次就是两次动作」)。
319
+ */
320
+ completeTask(input: {
321
+ id: number;
322
+ }): Promise<PmsTaskCompleteResult>;
323
+ /** 订阅镜像变化(右栏 tab 据此重渲染)。返回退订函数。 */
324
+ subscribe(listener: () => void): () => void;
325
+ /**
326
+ * 清空镜像(登出 / 换账号:状态是**按人**的,留着会串号),并**作废在飞的请求**。
327
+ *
328
+ * 只清镜像是不够的:清空那一刻正有一次拉取在飞时,那次拉取是**上一个人**发起的。所以世代
329
+ * +1:那之后的调用者一律不复用旧世代的那条,自己重新发一次 RPC。用世代号而**不是**把
330
+ * `inFlight` 直接抹成 undefined,理由与 `src/client/managed-workbench.ts` 的同名方法逐字相同
331
+ * (旧 promise 的 `finally` 会回头把新发起的那条记账清掉,两者互相踩)。
332
+ *
333
+ * 同时把 `statusProbed` 复位:新账号的镜像必须能再探一次,否则换账号后 `status()` 会一直
334
+ * 返回 `undefined` 且再也不去问主机。
335
+ */
336
+ clear(): void;
337
+ /**
338
+ * 发一次 RPC、解开信封、过第二道形状校验。
339
+ *
340
+ * 返回值里带上**发起时的世代**:只有它等于当前世代时,调用方才写镜像(见 {@link adopt})。
341
+ */
342
+ private call;
343
+ /**
344
+ * 并发合并:同一世代 + 同一端点撞在一起(面板挂载与手动刷新常常同拍)时共用一次请求,
345
+ * 且**共用同一个返回值**——两边对「这一次拉到了什么」的判断必须一致。
346
+ */
347
+ private dedupe;
348
+ /**
349
+ * 采纳一份成功状态:写镜像 + 通知订阅者。
350
+ *
351
+ * 世代不符的那一份**不写镜像**(但仍原样返回给调用方):它是 `clear()` 之前发起的请求,
352
+ * 属于上一个人。允许它写回等于「清空之后又被上一个人的状态占住」,而换账号时界面正好靠
353
+ * `status()` 判断该显示引导还是列表。
354
+ */
355
+ private adopt;
356
+ private emit;
357
+ }
@@ -0,0 +1,293 @@
1
+ /**
2
+ * PMS 服务基址 —— **全仓唯一一处**写死这个域名的地方。
3
+ *
4
+ * 为什么固定而不是配置项:PMS 是组织自建且**只有这一个生产部署**的内部系统(不像 ai-router 那样
5
+ * 需要自建/多环境),让每台终端在设置里填一个 URL 只会多一个能填错、又没人能集中改的地方;真要
6
+ * 换部署,改这一个常量、发一版即可。反过来说,**任何别处都不许再写这个域名**(包括文档示例之外
7
+ * 的代码、错误消息、日志),否则「唯一的基址」立刻变成两处能漂移的真相。
8
+ */
9
+ export declare const PMS_BASE_URL = "https://pms.hivery.cn";
10
+ /**
11
+ * 单次请求超时:10 秒(spec §7 的冻结决策)。
12
+ * Host 是唯一会替界面等 PMS 的地方,超时不给足会让右栏面板一直转圈;给太多则用户以为卡死。
13
+ */
14
+ export declare const PMS_REQUEST_TIMEOUT_MS = 10000;
15
+ /**
16
+ * access token 的**兜底**有效期:10 小时(= PMS `jwt.expiration=36000000`)。
17
+ * 只在响应缺 `expiresIn` 时用;正常路径一律按服务端给的 `expiresIn` 算过期时刻。
18
+ */
19
+ export declare const PMS_ACCESS_TOKEN_TTL_MS: number;
20
+ /** 本客户端只有三个错误码:凭据失效 / 上游不可用 / 权限不足。其余(输入不合法等)由 Bridge 层判。 */
21
+ export type PmsErrorCode = 'UNAUTHORIZED' | 'UPSTREAM_UNAVAILABLE' | 'FORBIDDEN';
22
+ /** PMS 调用失败。`message` 一律是稳定文案,可直接展示给用户。 */
23
+ export declare class PmsError extends Error {
24
+ readonly code: PmsErrorCode;
25
+ readonly status?: number | undefined;
26
+ constructor(code: PmsErrorCode, message: string, status?: number | undefined);
27
+ }
28
+ /** 未登录 / 凭据失效的文案(与 Bridge 侧 `UNAUTHORIZED` 同口径:引导重新输入,不静默失败)。 */
29
+ export declare const PMS_UNAUTHORIZED_MESSAGE = "\u767B\u5F55\u5DF2\u8FC7\u671F\uFF0C\u8BF7\u91CD\u65B0\u8F93\u5165 PMS \u8D26\u53F7\u5BC6\u7801";
30
+ /** 通用失败文案:不暴露任何 PMS 实现细节(baseURL / 路径 / 状态码 / 响应体)。 */
31
+ export declare const PMS_GENERIC_FAILURE_MESSAGE = "PMS \u6682\u65F6\u4E0D\u53EF\u7528\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5";
32
+ /**
33
+ * 权限不足的**固定**文案(spec §3 D7 逐字要求):UI 必须明说「在 HiWork 里没有完成任务的权限」,
34
+ * 而不是静默失败或回滚得莫名其妙——用户该做的是去 PMS 页面操作。
35
+ *
36
+ * 与 `UNAUTHORIZED` 分开的理由:重新登录**不会**解决它(权限是角色给的),把两者混成一个码,
37
+ * 界面就只能给出「请重新输入密码」这种无效引导。
38
+ */
39
+ export declare const PMS_FORBIDDEN_MESSAGE = "\u5728 HiWork \u91CC\u6CA1\u6709\u5B8C\u6210\u4EFB\u52A1\u7684\u6743\u9650\uFF0C\u8BF7\u5230 PMS \u64CD\u4F5C";
40
+ /**
41
+ * 完成任务时用的状态值(PMS `TaskVO.status`:0-待办 / 1-进行中 / 2-已完成 / 3-逾期)。
42
+ * 完成与激活走同一个 `PUT /{id}/status`,差别只有这个 query 值。
43
+ */
44
+ export declare const PMS_TASK_STATUS_COMPLETED = 2;
45
+ /**
46
+ * 「我的任务」只取**未完成**的三种状态(spec §3 D7 的 `status=0&status=1&status=3`)。
47
+ * 2(已完成)不取:面板是待办视图,把做完的任务一起拉回来只会让列表变长。
48
+ * 顺序即 query 里的出现顺序(由测试钉死)。
49
+ */
50
+ export declare const PMS_TASK_OPEN_STATUSES: readonly [0, 1, 3];
51
+ /** 「我的任务」的默认页大小(spec §3 D7)。50 条足够覆盖一个人的未完成任务,且一次请求就够。 */
52
+ export declare const PMS_TASK_PAGE_SIZE = 50;
53
+ /**
54
+ * 一次成功登录 / 刷新换来的会话。
55
+ *
56
+ * `accessToken` **绝不出 Host**;`expiresAt` 是绝对时刻(epoch ms)而不是剩余秒数——剩余秒数
57
+ * 一旦跨过任何一个 await 就开始漂,而「过期了没有」是授权路径上唯一的判据。
58
+ */
59
+ export interface PmsTokenSet {
60
+ accessToken: string;
61
+ /** 7 天有效;PMS 没回时缺省(此时 access token 过期就只能重新输入账号密码)。 */
62
+ refreshToken?: string;
63
+ expiresAt: number;
64
+ /**
65
+ * PMS 的**用户 id**(登录响应 `data.userId`,spec §3 D7 要求登录时捕获)。
66
+ *
67
+ * 它**不是**凭据(是个人数字 id,PMS 侧内部的 `user_id`),但它是「我的任务」唯一的查询键
68
+ * (`assigneeId`),所以必须跟着会话一起存下来。`refresh` 的响应里也可能有,但**不以它为准**
69
+ * ——见 `src/pms-todo-bridge.ts` 的 `authorize`(刷新不改变 userId)。
70
+ */
71
+ userId?: number;
72
+ }
73
+ /**
74
+ * 一条待办(**冻结契约**,spec §4.2 的 `TodoItem`,字段名与 PMS 的 `TodoTaskVO` 一一对应)。
75
+ *
76
+ * 演进纪律(§4.2):**未知字段忽略、未知枚举原样透传**。`status` / `priority` 刻意不写成联合
77
+ * 类型——PMS 将来加一个 `URGENT` 优先级不该让老客户端崩,也不该让 Host 把它当成脏数据丢掉。
78
+ * 可选字段**按服务端给的有没有来**(没给就不写这个键),不由本层补 `undefined`。
79
+ */
80
+ export interface PmsTodoItem {
81
+ id: number;
82
+ title: string;
83
+ description?: string;
84
+ /** `TODO` | `TEMP`(原样透传)。 */
85
+ category?: string;
86
+ /** `HIGH` | `MEDIUM` | `LOW` | 服务端将来新增的值,一律原样透传。 */
87
+ priority?: string;
88
+ /** `PENDING` | `COMPLETED` | 服务端将来新增的值,一律原样透传。 */
89
+ status: string;
90
+ /** `yyyy-MM-dd`。 */
91
+ startDate?: string;
92
+ dueDate?: string;
93
+ completedTime?: string;
94
+ /** 服务端判定(`status='PENDING' AND due_date < CURDATE()`),**客户端不重算**。 */
95
+ isOverdue?: boolean;
96
+ /** 服务端判定(今日视图里 = 今天到期或无截止日期),**客户端不重算**。 */
97
+ isDueSoon?: boolean;
98
+ }
99
+ /** 勾选用的两个字面量(PMS 的 `updateTaskStatus` 只认这两个值)。 */
100
+ export type PmsTodoStatusValue = 'PENDING' | 'COMPLETED';
101
+ /**
102
+ * 一条**项目任务**(spec §3 D7;服务端 `pms-task` 的 `TaskVO` 投影)。字段类型按 PMS 源码定:
103
+ * `id` / `projectId` / `assigneeId` / `status` / `priority` / `auditStatus` 是数字,
104
+ * `deadline` 是 `yyyy-MM-dd HH:mm:ss`(PMS 的 `@JsonFormat`),其余是字符串,
105
+ * `estimatedHours` / `actualHours` 是 `BigDecimal`(Jackson 序列化成数字)。
106
+ *
107
+ * 演进纪律与 {@link PmsTodoItem} 同款,但**多一条**:任务的状态与优先级在 PMS 侧是**数字**
108
+ * (0/1/2/3 与 1/2/3),所以这里没有「未知枚举」可言——数字照发,`statusName` / `priorityName`
109
+ * 这两个给人看的名字原样透传(将来 PMS 加档位不该让老客户端崩)。未知**字段**忽略。
110
+ *
111
+ * 必填项(设计里没标 `?` 的那些)**一定会出现**,服务端没给时按类型兜底(`''` / `0`):
112
+ * 界面按 `status === 3` 判逾期、按 `projectName` 渲染徽标,少一个键就得让每个消费方各写一遍兜底。
113
+ * 设计里明确标可选的那五个(阶段名 / 需求名 / 模块名 / 两个工时)**只按服务端给没给来**。
114
+ */
115
+ export interface PmsTaskItem {
116
+ id: number;
117
+ taskName: string;
118
+ description: string;
119
+ projectId: number;
120
+ projectName: string;
121
+ /** 所属项目阶段名(服务端没给就没有这个键)。 */
122
+ projectStageName?: string;
123
+ assigneeId: number;
124
+ assigneeName: string;
125
+ /** `yyyy-MM-dd HH:mm:ss`;没有截止时间时是空串。 */
126
+ deadline: string;
127
+ /** 0-待办 / 1-进行中 / 2-已完成 / 3-逾期。 */
128
+ status: number;
129
+ statusName: string;
130
+ /** 1-低 / 2-中 / 3-高。 */
131
+ priority: number;
132
+ priorityName: string;
133
+ taskType: string;
134
+ taskTypeName: string;
135
+ /** 0-待审核 / 1-已通过 / 2-已驳回。 */
136
+ auditStatus: number;
137
+ auditStatusName: string;
138
+ /** 关联需求名(服务端没给就没有这个键)。 */
139
+ requirementName?: string;
140
+ /** 关联功能模块名(服务端没给就没有这个键)。 */
141
+ moduleName?: string;
142
+ /** 预估工时(小时)。 */
143
+ estimatedHours?: number;
144
+ /** 实际工时(小时,PMS 从 `work_hour` 汇总)。 */
145
+ actualHours?: number;
146
+ }
147
+ /**
148
+ * 「我的任务」的返回(spec §3 D7 的**分页对象**投影,服务端是 `PageResult<TaskVO>`)。
149
+ *
150
+ * ⚠️ 与 `today` 的区别就在这里:待办的 `data` 是**数组**,任务的 `data` 是**分页对象**
151
+ * (`{records, total, current, size}`)——把两者当成同一个形状读,轻则列表永远空,重则把
152
+ * `records` 当 `undefined` 抛错。
153
+ */
154
+ export interface PmsTaskList {
155
+ items: PmsTaskItem[];
156
+ /** 总条数(服务端缺省时回退成 `items.length`,免得界面显示「共 0 条」却列着内容)。 */
157
+ total: number;
158
+ current: number;
159
+ size: number;
160
+ }
161
+ /** 「我的任务」的可选项(`size` 默认 {@link PMS_TASK_PAGE_SIZE},非正整数一律回退缺省值)。 */
162
+ export interface PmsMyTasksOptions {
163
+ size?: number;
164
+ }
165
+ /** `PUT /api/task/tasks/{id}/status` 的结果(PMS 这个接口回 void,所以按请求值回退)。 */
166
+ export interface PmsTaskStatusResult {
167
+ id: number;
168
+ /** 任务状态数字(完成是 {@link PMS_TASK_STATUS_COMPLETED})。 */
169
+ status: number;
170
+ }
171
+ /** `GET /api/todo/statistics` 的投影(spec §D4)。 */
172
+ export interface PmsTodoStatistics {
173
+ total: number;
174
+ pending: number;
175
+ completed: number;
176
+ overdue: number;
177
+ }
178
+ /** `PUT /api/todo/{id}/status` 的结果(服务端缺 `completedTime` 时缺省)。 */
179
+ export interface PmsTodoStatusResult {
180
+ id: number;
181
+ status: PmsTodoStatusValue;
182
+ completedTime?: string;
183
+ }
184
+ /** 构造选项(全部可选;生产只传 `log`)。 */
185
+ export interface PmsClientOptions {
186
+ /** 基址覆盖(**测试用**);缺省 {@link PMS_BASE_URL}。生产不许传。 */
187
+ baseURL?: string;
188
+ /** 超时覆盖(测试用);缺省 {@link PMS_REQUEST_TIMEOUT_MS}。 */
189
+ timeoutMs?: number;
190
+ /** 时钟注入(测试用);缺省 `Date.now`。 */
191
+ now?: () => number;
192
+ /**
193
+ * Host 日志出口。**只记事件与业务 code**——用户可见的 message 与响应体都不许进来
194
+ * (§7:账号密码与 JWT 不进日志;业务 message 可能带用户数据)。
195
+ */
196
+ log?: ((message: string) => void) | undefined;
197
+ }
198
+ /**
199
+ * PMS HTTP 客户端。零 DSH 依赖、零凭据——它只负责「把一次调用发出去、把结果或稳定错误返回」。
200
+ *
201
+ * @param options - 见 {@link PmsClientOptions}。
202
+ */
203
+ export declare class PmsClient {
204
+ private readonly baseURL;
205
+ private readonly timeoutMs;
206
+ private readonly now;
207
+ private readonly log;
208
+ constructor(options?: PmsClientOptions);
209
+ /**
210
+ * `POST /api/auth/login`:用账号密码换 JWT(access + refresh)。
211
+ *
212
+ * 失败语义(由 {@link PmsClient.call} 统一给出):
213
+ * - 账号密码不对 → `UNAUTHORIZED`(HTTP 401 **或** 信封 `code:401`,两者同义);
214
+ * - 其余(网络 / 超时 / 5xx / 非业务信封)→ `UPSTREAM_UNAVAILABLE`。
215
+ * **本方法不重试**,调用方也不许替它重试(§7:避免用错密码连续触发 PMS 的失败锁定)。
216
+ */
217
+ login(username: string, password: string): Promise<PmsTokenSet>;
218
+ /**
219
+ * `POST /api/auth/refresh`:用 refresh token 续一次 access token。
220
+ *
221
+ * refresh token 失效(7 天到期 / 已被 PMS 撤销)→ `UNAUTHORIZED`,此时**只能**重新输入账号密码;
222
+ * 网络问题 → `UPSTREAM_UNAVAILABLE`(那是「稍后重试」,不是「要重新登录」——两者对用户是两件事)。
223
+ */
224
+ refresh(refreshToken: string): Promise<PmsTokenSet>;
225
+ /**
226
+ * `GET /api/todo/today`:今日待办(⚠️ 口径比名字宽:**今天 + 明天 + 无截止日期**都算,spec §D4)。
227
+ *
228
+ * `data` 必须是数组;不是数组时按**上游不可用**处理(绝不读成「今天没有待办」——那是把中间层的
229
+ * 维护页当成空清单,用户会以为自己清空了所有待办)。缺 `id` 的条目跳过:没有 id 就没法勾选,
230
+ * 渲染出来只会是一个点了没反应的复选框。
231
+ */
232
+ today(accessToken: string): Promise<PmsTodoItem[]>;
233
+ /** `GET /api/todo/statistics`:total / pending / completed / overdue 四个计数。 */
234
+ statistics(accessToken: string): Promise<PmsTodoStatistics>;
235
+ /**
236
+ * `PUT /api/todo/{id}/status?status=PENDING|COMPLETED`:勾完成 / 取消完成。
237
+ *
238
+ * `status` 走 **query**(PMS 侧就是这么定的),所以必须 `encodeURIComponent`——虽然本层只放行
239
+ * 两个字面量、编码后与原值相同,但「拼进 URL 的值一律编码」是纪律:少一次编码在参数集合变化时
240
+ * 就是一个注入面。
241
+ *
242
+ * ⚠️ 副作用(spec §D5):勾完成时若来源是项目任务且该任务下再无 PENDING 待办,**PMS 会把项目
243
+ * 任务一并标记完成**;反向不成立。本层只负责发请求,提示文案属 UI 的职责。
244
+ *
245
+ * 返回值以服务端回声为准(`data.id` / `data.status`),`data` 为空(PMS 这个接口可能回 void)
246
+ * 时回退成请求里的 id / status——调用方要的是「我勾的是哪条、现在是什么状态」,两者都知道。
247
+ */
248
+ setStatus(accessToken: string, id: number, status: PmsTodoStatusValue): Promise<PmsTodoStatusResult>;
249
+ /**
250
+ * `GET /api/task/tasks?assigneeId={userId}&status=0&status=1&status=3&current=1&size=50`
251
+ * (spec §3 D7):**我的未完成项目任务**。
252
+ *
253
+ * 为什么必须直接查任务而不是只读待办:PMS 只在**任务创建那一刻**生成待办
254
+ * (`TaskService.createFromTask`),转派、走 SQL 批量建的历史任务、以及机制上线前的任务都
255
+ * 没有待办——只读 `todo_task` 会让这些任务在面板上凭空消失(§3 D7 的缺口表)。
256
+ *
257
+ * `status` 在 PMS 侧是 `List<Integer>`,所以是**多个同名参数**而不是逗号分隔(`status=0&status=1&status=3`);
258
+ * 顺序按 {@link PMS_TASK_OPEN_STATUSES},`assigneeId` 是**当前登录人的 PMS userId**
259
+ * (由调用方从凭据记录里取,见 `src/pms-todo-bridge.ts`)。
260
+ *
261
+ * ⚠️ 响应的 `data` 是**分页对象**(`{records, total, current, size}`)**不是数组**:
262
+ * 不是对象或 `records` 不是数组时按**上游不可用**处理(绝不读成「我没有任务」——那会让用户
263
+ * 以为活都干完了,而这正是本次最要避免的误读)。
264
+ *
265
+ * @param accessToken - Host 内存里的 PMS JWT。
266
+ * @param userId - 我的 PMS userId(凭据记录里的 `userId`)。
267
+ * @param opts - 见 {@link PmsMyTasksOptions}。
268
+ */
269
+ myTasks(accessToken: string, userId: number, opts?: PmsMyTasksOptions): Promise<PmsTaskList>;
270
+ /**
271
+ * `PUT /api/task/tasks/{taskId}/status?status=2`:**完成一条项目任务**(spec §3 D7)。
272
+ *
273
+ * 与 {@link PmsClient.setStatus} 同款:`status` 走 **query** 且被编码;要求业务 `code === 200`,
274
+ * `data` 容许为 `null`(PMS 这个接口的返回类型是 `Result<Void>`,实测就是 null),此时回退成
275
+ * 请求里的 id / 状态数字——调用方要的是「我完成的是哪条、现在是什么状态」,两者都知道。
276
+ *
277
+ * ⚠️ 权限:这个端点带 `@PreAuthorize("hasAuthority('task:list:edit')")`,没有该权限的角色会失败
278
+ * (`FORBIDDEN`,见 {@link PMS_FORBIDDEN_MESSAGE})。这与待办那条不同——**不要**把它当成「凭据
279
+ * 过期」让用户重新登录。
280
+ */
281
+ completeTask(accessToken: string, taskId: number): Promise<PmsTaskStatusResult>;
282
+ /**
283
+ * 发一次调用并解开 `Result` 信封。**所有错误都在这里收敛**,返回值只有 `data`。
284
+ *
285
+ * 顺序是刻意的:**先看 HTTP 401,再解信封**——PMS 未登录时回的是 Spring Security 形状
286
+ * (`{path, code:"AUTH_001", message}`),它的 `message` 是给浏览器看跳登录页的,既不是业务文案
287
+ * 也可能带 `path` 等实现细节。把它当业务信封解析,轻则文案变成一句英文,重则把「凭据失效」
288
+ * 误判成「上游故障」而让用户不知道要重新输入密码。
289
+ */
290
+ private call;
291
+ /** 登录 / 刷新的 `data` → {@link PmsTokenSet}。200 却不给 token 视为契约破坏(上游不可用)。 */
292
+ private tokenSet;
293
+ }