hiwork-core 0.0.8 → 0.0.11

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,133 @@
1
+ /**
2
+ * 「管理员下发的 MCP」客户端取数层:经 `/hiwork-managed-mcp` loopback RPC 拿到
3
+ * 清单与短期断言,交给连接器域(hiwork-capabilities)去换令牌并落地。
4
+ *
5
+ * 铁律:
6
+ * - 客户端**不持有**会话 cookie,也**不直接 fetch 网关**——一切经 Host 频道;
7
+ * - 客户端**不做 exchange、不持有 MCP 令牌**:本层交出去的只有「清单 + 一张刚取的
8
+ * 断言 + mintUrl」,per-user 令牌只在连接器域内部存在;
9
+ * - **只有 {@link ManagedMcpStore.refresh} 成功返回的清单才允许用作删除本地受管连接的
10
+ * 依据**;{@link ManagedMcpStore.snapshot} 可能与界面展示共享,但它可能是上一次
11
+ * 成功的结果(服务端刚撤掉的分配还在里面),拿它做删除决策就会把「撤销」误做成
12
+ * 「没同步上」——反过来,拉取失败时它又是唯一的真相来源,所以两个入口必须分开。
13
+ *
14
+ * client bundle 纯度:本文件不 import 任何 `@deepseek-ai/*` 运行时值,也不 import
15
+ * Host 代码(类型按结构重声明,字段名与契约一致)。
16
+ */
17
+ /** RPC 频道;必须与 Host `src/managed-mcp.ts` 的 `MANAGED_MCP_RPC_CHANNEL` 一致(由测试锁定)。 */
18
+ export declare const MANAGED_MCP_CHANNEL = "/hiwork-managed-mcp";
19
+ /** 端点白名单;必须与 Host `MANAGED_MCP_RPC_ENDPOINTS` 一致(由测试锁定)。 */
20
+ export declare const MANAGED_MCP_ENDPOINTS: readonly ["snapshot", "refresh", "assertion"];
21
+ export type ManagedMcpEndpoint = (typeof MANAGED_MCP_ENDPOINTS)[number];
22
+ /**
23
+ * 本地镜像的「陈旧」阈值:5 分钟。
24
+ * 触发时机里的「打开连接器页且 >5min stale 就同步一次」用它判定——短于它只会让
25
+ * 用户每次切页都打一遍网关,长于它则界面上的清单明显落后于管理员的推送。
26
+ */
27
+ export declare const MANAGED_MCP_STALE_MS: number;
28
+ /** 一条分配(对齐 ai-hub spec §4.1 的冻结契约)。 */
29
+ export interface ManagedMcpAssignment {
30
+ assignmentId: string;
31
+ revision: number;
32
+ slug: string;
33
+ displayName: string;
34
+ /** 服务端权威下发的装载名:工具前缀 `mcp__<serverName>__` 跨版本稳定的锚点。 */
35
+ serverName: string;
36
+ /** `streamable-http | stdio`;认不认识的判断在连接器域(不认识的整条跳过并报冲突)。 */
37
+ transport: string;
38
+ url: string;
39
+ extraHeaders: Record<string, string>;
40
+ auth: {
41
+ kind: string;
42
+ mintUrl: string;
43
+ revokeUrl: string;
44
+ };
45
+ updatedAt: string;
46
+ }
47
+ /** 一份清单快照。`stale` 为 true 表示最近一次拉取失败,展示的是上一次成功的结果。 */
48
+ export interface ManagedMcpSnapshot {
49
+ revision: number;
50
+ fetchedAt: string;
51
+ assignments: ManagedMcpAssignment[];
52
+ stale: boolean;
53
+ lastError?: {
54
+ code: string;
55
+ message: string;
56
+ };
57
+ }
58
+ /** 取断言的结果:一张 120 秒的门票 + 去哪换令牌。 */
59
+ export interface ManagedMcpAssertion {
60
+ assertion: string;
61
+ mintUrl: string;
62
+ revokeUrl: string;
63
+ expiresAt: string;
64
+ serverName: string;
65
+ }
66
+ /** 宿主 loopback RPC 调用面(结构兼容 `ctx.connection.rpc`)。 */
67
+ export interface ManagedMcpRpcPort {
68
+ call(channel: string, endpoint: string, payload: unknown, signal?: AbortSignal): Promise<unknown>;
69
+ }
70
+ /** 带稳定 code 的取数错误(message 已由 Host 消毒,可直接展示)。 */
71
+ export declare class ManagedMcpRpcError extends Error {
72
+ readonly code: string;
73
+ constructor(code: string, message: string);
74
+ }
75
+ /**
76
+ * 解开 DSH Connection 的结果信封。
77
+ * 宿主返回 `{ok:true,value}` / `{ok:false,error:{code,message}}`;形状不合法时抛
78
+ * 通用失败,绝不把未知结构当数据用。
79
+ */
80
+ export declare function unwrapManagedMcpResult<T>(value: unknown): T;
81
+ /** 任意异常 → 可展示的 `{code, message}`(传输层异常不暴露原始 message)。 */
82
+ export declare function managedMcpErrorOf(error: unknown): {
83
+ code: string;
84
+ message: string;
85
+ };
86
+ /**
87
+ * 受管 MCP 的取数仓库。
88
+ *
89
+ * 本地镜像(`snapshot()` 同步读)是有必要的:DSH 的 client 半边在不同插件之间只通过
90
+ * 这个面同步取状态,而清单本身是异步来的。镜像**只由成功响应覆写**,失败时不改动它。
91
+ */
92
+ export declare class ManagedMcpStore {
93
+ private readonly rpc;
94
+ private current;
95
+ private readonly listeners;
96
+ private inFlight;
97
+ constructor(rpc: ManagedMcpRpcPort);
98
+ /** 本地镜像(同步)。从没成功同步过返回 undefined。**不要拿它做删除决策**(见文件头)。 */
99
+ snapshot(): ManagedMcpSnapshot | undefined;
100
+ /** 本地镜像是否已经陈旧(或从未同步过)。 */
101
+ isStale(ttlMs?: number, now?: number): boolean;
102
+ /**
103
+ * 强制同步一次并返回**刚拉到的**清单。
104
+ *
105
+ * 返回值(而不是让人随后去读 `snapshot()`)是刻意的:只有这个返回值保证「这一次
106
+ * 网络请求成功了」,用它做删除决策才是安全的;读镜像可能读到上一次的结果。
107
+ * 失败时抛错且**不动镜像**——调用方据此保持现有的连接一条不删。
108
+ */
109
+ refresh(signal?: AbortSignal): Promise<ManagedMcpSnapshot>;
110
+ /**
111
+ * 取一份清单,**能用 Host 的预热缓存就用**(Host 后台每 30 分钟拉一次)。
112
+ *
113
+ * 与 {@link refresh} 的分工:这里是「启动时先把界面填上」的便宜入口——命中缓存时
114
+ * 一次网络请求都不发;Host 缓存也是冷的才会真的去拉。返回值仍保证是「某一次成功
115
+ * 拉取的结果」,但**可能比服务端现状旧**(最旧不超过 Host 的上一次成功拉取),
116
+ * 所以调用方按 `stale` / `isStale()` 判定它够不够权威,不够就再 `refresh()` 一次。
117
+ */
118
+ warm(signal?: AbortSignal): Promise<ManagedMcpSnapshot>;
119
+ /** 取一张新鲜断言(120 秒)。失败抛错;本层不缓存、不换令牌。 */
120
+ assertion(assignmentId: string, signal?: AbortSignal): Promise<ManagedMcpAssertion>;
121
+ /** 订阅镜像变化(连接器域据此重渲染 / 重排同步)。返回退订函数。 */
122
+ subscribe(listener: () => void): () => void;
123
+ /** 清空镜像(登出 / 换账号:清单是按人的,留着会串号)。 */
124
+ clear(): void;
125
+ /**
126
+ * 并发去重:`warm` 与 `refresh` 撞在一起(启动同步与定时同步常常同拍)时共用一次
127
+ * 请求,且**共用同一个返回值**——两边对「这一次拉到了什么」的判断必须一致。
128
+ */
129
+ private dedupe;
130
+ /** 采纳一份成功响应:写镜像 + 通知订阅者(失败路径永远不会走到这里)。 */
131
+ private adopt;
132
+ private emit;
133
+ }
@@ -1,5 +1,5 @@
1
1
  import type { InjectFace, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots';
2
- import type { TurnTailChatData, TurnTailOwnerProps } from '@deepseek-ai/dsh-client-ui-chat/client';
2
+ import type { ChatSnapshot, TurnTailChatData } from '@deepseek-ai/dsh-client-ui-chat/client';
3
3
  /** 计价端点名;必须与 Host `src/usage-rpc.ts` 的 `USAGE_RPC_ENDPOINTS` 一致(由测试锁定)。 */
4
4
  export declare const SESSION_COST_ENDPOINT = "estimate";
5
5
  /** 缓存 TTL:5 分钟(与设置页取数一致;同一轮的金额不会随滚动反复请求)。 */
@@ -50,11 +50,19 @@ export interface SessionCostRpcPort {
50
50
  */
51
51
  export declare function sessionCostMatchOf(data: TurnTailChatData | undefined): SessionCostMatch | null;
52
52
  /**
53
- * 链槽 `conversation.chat.turnTail` 的路由选择器:只有该轮拿得到用量时才占用位置。
54
- * @param owner - turn-tail owner(turn / seq / openFile)。
55
- * @returns 计价项;取不到用量时 null(框架不挂载组件)。
53
+ * 在该会话的 Chat 快照里,按「收尾消息 id」找到该轮的 turn-tail 数据。
54
+ *
55
+ * 为什么按 messageId 反查:本模块挂在 `conversation.chat.assistant-actions`,owner 只有
56
+ * `messageId`;而用量挂在同一轮的 `'turn-tail'` 节点上。ui-chat 正是拿
57
+ * `closing.finalNode.messageId` 去 renderSlot 这个槽的(见 `TurnTailNodeView`),
58
+ * 所以两个 id 是同一个值——不需要再猜轮号,也不会跨轮串号。
59
+ *
60
+ * 该轮尚未收尾、或节点不在已加载窗口内 → 返回 undefined(不显示金额)。
61
+ * @param snapshot - 当前 Chat 快照(`useChat` 的选择器入参)。
62
+ * @param messageId - 收尾 assistant 消息 id(槽位 owner 下发)。
63
+ * @returns 该轮 turn-tail 数据;找不到时 undefined。
56
64
  */
57
- export declare function selectSessionCost(owner: TurnTailOwnerProps): SessionCostMatch | null;
65
+ export declare function closingTurnTailData(snapshot: ChatSnapshot, messageId: string): TurnTailChatData | undefined;
58
66
  /** 缓存键:会话 + 轮次 + 计价项内容(滚动/重挂载不会重复请求同一轮)。 */
59
67
  export declare function sessionCostKey(sessionId: string, match: Pick<SessionCostMatch, 'turn' | 'items'>): string;
60
68
  /**
@@ -102,18 +110,16 @@ export interface SessionCostFace {
102
110
  store: SessionCostStore;
103
111
  }
104
112
  /**
105
- * 链槽组件 props:owner(turn/seq/openFile)+ 会话标准件(`sessionId`)+ 注入面 + `matched`。
106
- * `sessionId` 由框架下发(ui-session 的 SessionStandardProps),不在这里重复声明。
113
+ * 组件 props:owner(`messageId`)+ 会话标准件(`sessionId` / `useChat`)+ 注入面。
114
+ * `sessionId`(ui-session)与 `useChat`(ui-chat)都由框架下发,不在这里重复声明。
107
115
  */
108
- export type SessionCostProps = PropsRuntime<'conversation.chat.turnTail'> & InjectFace<SessionCostFace> & {
109
- matched: SessionCostMatch;
110
- };
116
+ export type SessionCostProps = PropsRuntime<'conversation.chat.assistant-actions'> & InjectFace<SessionCostFace>;
111
117
  /**
112
- * 渲染一轮的估算金额。
118
+ * 渲染一轮的估算金额(挂在该轮收尾消息的动作行里,与 DSH 的「用量」胶囊同一行)。
113
119
  *
114
120
  * 三种状态:全部计价且渠道已解析、单组合 → `≈ ¥x`;渠道未解析或该轮多组合 →
115
121
  * `≈ ¥x(估算)`;其余(未计价 / 失败 / 加载中)→ **不渲染任何内容**。
116
- * @param props - 链槽 props。
122
+ * @param props - 槽位 props(owner 的 `messageId` + 标准件 `sessionId` / `useChat` + 注入面)。
117
123
  * @returns 金额文本节点,或 null(不影响对话渲染)。
118
124
  */
119
- export declare function SessionCost({ matched, sessionId, store }: SessionCostProps): import("react").JSX.Element | null;
125
+ export declare function SessionCost({ messageId, sessionId, useChat, store }: SessionCostProps): import("react").JSX.Element | null;
@@ -46,8 +46,16 @@ export declare const textInput: CSSProperties;
46
46
  * data-dsh-shell=process.platform;Windows 顶栏与侧栏同色,圆角呼应顶栏,
47
47
  * 其余平台(含无该属性的纯浏览器预览)默认关闭,回到直角。
48
48
  * 内容区根节点类名是 CSS module 哈希前缀(如 wSkVaW_root),语义后缀稳定;
49
- * 用直接子链 `[class*="_centerCol"] > div > [class*="_root"]` 恰好命中会话区
50
- * 根节点(实测唯一),避免误伤内容区里 10 个嵌套组件的 _root。
49
+ * 命中内容区根节点一律走「中心列 + 直接子链」,避免误伤内容区里 10 个嵌套组件
50
+ * 的 _root。中心列到根节点中间隔几层,两代宿主不同,故规则并存:
51
+ * - `0.1.2-rc.1` 及更早:main 槽直接把会话区根节点渲染进中心列,链路是
52
+ * `centerCol > 槽位 div > _root`;
53
+ * - `0.1.5-rc.1` 起:main 槽先渲染 ConversationPanel,再由它声明的
54
+ * `main.conversation` 子槽渲染根节点,中间多一层 `data-slot` 槽位 div,
55
+ * 旧直接子链静默失配(实测表现:Windows 内容区左上角圆角消失),改用槽位属性定位。
56
+ * 中心列里除了原生会话还有 HiWork 扩展页(feature-center 的 takeover 根节点,
57
+ * 是中心列的直接子元素):同一处「内容区左上角」在两种页面上都要有圆角,否则
58
+ * 切到扩展页又变回直角。
51
59
  * 框架底色同理不能只认 `_frame` 后缀:该后缀被 6 个 CSS module 复用(主框架
52
60
  * AppFrame 之外还有会话右侧快速索引 TurnNavigator、附件图 MessageImage、
53
61
  * 提问面板 QuestionComposer/PlanReviewPanel、只读输入框 SubagentReadOnlyComposer),
@@ -1,5 +1,11 @@
1
1
  /** 稳定错误码,供 settings card 分支渲染。 */
2
- export type LoginErrorCode = 'AUTH_FAILED' | 'RATE_LIMITED' | 'GATEWAY_UNREACHABLE' | 'NO_KEY_ASSIGNED' | 'SESSION_EXPIRED' | 'KEY_DISABLED' | 'KEY_INVALID' | 'NOT_FOUND';
2
+ export type LoginErrorCode = 'AUTH_FAILED' | 'RATE_LIMITED' | 'GATEWAY_UNREACHABLE' | 'NO_KEY_ASSIGNED' | 'SESSION_EXPIRED' | 'KEY_DISABLED' | 'KEY_INVALID' | 'NOT_FOUND'
3
+ /** 该分配已被管理员撤销(或从未属于当前账号):取断言时 404。 */
4
+ | 'MCP_ASSIGNMENT_GONE'
5
+ /** 服务或分配已停用(或被改成了不可下发的凭证方式):取断言时 409。 */
6
+ | 'MCP_NOT_DELIVERABLE'
7
+ /** 本部署未配置断言签名私钥(`mcp.assertion_key`):取断言时 503。 */
8
+ | 'MCP_ASSERTION_DISABLED';
3
9
  /** 一个稳定的、可安全展示的登录/刷新错误。 */
4
10
  export interface LoginError {
5
11
  code: LoginErrorCode;
@@ -22,7 +22,15 @@ export declare const CLIENT_POLICY_TIMEOUT_MS = 5000;
22
22
  /** 策略响应中本插件消费的部分。 */
23
23
  export interface ClientPolicy {
24
24
  loginMode: LoginMode;
25
+ /**
26
+ * 账号密码直连表单是否仍是受支持的登录方式(网关身份源切到统一认证后为 false)。
27
+ * 缺省(旧网关不返回该字段)/ 非法值一律按 true——绝不因为策略不全就收起可用的
28
+ * 登录入口,把用户锁在门外。
29
+ */
30
+ passwordLogin: boolean;
25
31
  }
32
+ /** fail-open 缺省:旧网关不返回 passwordLogin 时,密码表单仍视作可用。 */
33
+ export declare const DEFAULT_PASSWORD_LOGIN = true;
26
34
  /** 拉取策略所需入参(deviceId 由 `resolveDeviceInfo` 解析,永非空)。 */
27
35
  export interface FetchClientPolicyOptions {
28
36
  /** 网关地址;缺省 / 空串直接返回 undefined(不发起请求)。 */
@@ -56,6 +56,13 @@ export interface Config {
56
56
  * 一律写 `both`(fail-open,绝不把用户锁在门外)。
57
57
  */
58
58
  loginMode?: LoginMode;
59
+ /**
60
+ * 账号密码直连表单是否仍是受支持的登录方式:Host 从同一策略投影。
61
+ * 网关身份源切到统一认证(IAM)后为 false——密码由 IAM 持有,直连表单必然失败,
62
+ * 客户端据此收起密码入口,避免「密码正确却提示密码错误」。缺省 / 拉取失败一律
63
+ * 写 true(fail-open:绝不收起可用入口)。
64
+ */
65
+ passwordLogin?: boolean;
59
66
  budgets?: Array<{
60
67
  model_name: string;
61
68
  total_budget?: number | null;
@@ -140,11 +147,13 @@ export interface IpcCallbackPorts {
140
147
  */
141
148
  export declare function registerIpcMessageHandler(ports: IpcCallbackPorts): () => void;
142
149
  /**
143
- * 拉取客户端策略并把 `loginMode` 投影进 ai-router 命名空间(spec §5.4 / §6.1)。
150
+ * 拉取客户端策略并把 `loginMode` / `passwordLogin` 投影进 ai-router 命名空间
151
+ * (spec §5.4 / §6.1)。
144
152
  *
145
153
  * fail-open 是硬要求:缺 baseURL / 设备信息取不到 / 网络失败 / 响应非法一律投影
146
- * `DEFAULT_LOGIN_MODE`(`both`),**且只写 `loginMode` 一个字段**——不碰 lastError、
147
- * 不碰任何登录请求字段,策略拉取失败绝不影响登录与供给。**本函数永不抛异常**。
154
+ * `DEFAULT_LOGIN_MODE`(`both`)与 `DEFAULT_PASSWORD_LOGIN`(`true`),**且只写这两个
155
+ * 字段**——不碰 lastError、不碰任何登录请求字段,策略拉取失败绝不影响登录与供给。
156
+ * **本函数永不抛异常**。
148
157
  *
149
158
  * @returns 最终投影的登录方式(测试与调用方均可断言)。
150
159
  */
@@ -0,0 +1,177 @@
1
+ /**
2
+ * `/hiwork-managed-mcp` loopback RPC 的 Host 实现:管理员下发的 MCP 清单 + 换令牌用的短期断言。
3
+ *
4
+ * 设计要点(与 `src/usage-rpc.ts` / `src/daily-rpc.ts` 同构,差异都在下面写清):
5
+ * - 只注册一个 channel,endpoint 白名单在 {@link MANAGED_MCP_RPC_ENDPOINTS},未知
6
+ * endpoint 直接 `bad-request`;
7
+ * - payload 严格校验:`snapshot` / `refresh` 只接受空对象,`assertion` 只接受
8
+ * `{ assignmentId }`(这是唯一一处客户端可控输入,形状卡死在 Host);
9
+ * - **网关会话 cookie 绝不离开 Host**:调用方拿到的是清单与断言,cookie 只在
10
+ * `resolveSession()` 内部出现;
11
+ * - **本频道不换令牌、不持有令牌**。断言只是一张 120 秒的门票,拿它去 MCP 服务的
12
+ * `/mcp-credential-exchange` 换 per-user 令牌是客户端(hiwork-capabilities)的事。
13
+ * 这样 ai-hub 与 hiwork-core 全程经手不了任何 MCP 凭据,被攻破也拿不到东西。
14
+ * - **拉清单失败绝不清空上一次成功的清单**:客户端只有在拿到一份「成功拉到的清单」
15
+ * 时才允许删除本地受管连接,所以失败必须以「保留旧值 + 标注失败原因」的形状返回,
16
+ * 否则一次网络抖动就会让员工端把自己卸载干净。
17
+ *
18
+ * 本模块不 import cordis 运行时,只依赖结构化的 {@link ManagedMcpRpcContext} 端口,
19
+ * 便于单测直接伪造。
20
+ */
21
+ import { type GatewayMcpAssignmentsView, type GatewayMcpAssertionTicket } from './ai-router-client.ts';
22
+ /** RPC 频道;必须与 client 半边 `src/client/managed-mcp.ts` 的 `MANAGED_MCP_CHANNEL` 一致(由测试锁定)。 */
23
+ export declare const MANAGED_MCP_RPC_CHANNEL = "/hiwork-managed-mcp";
24
+ /**
25
+ * 端点白名单。
26
+ *
27
+ * `snapshot` 是「你现在有什么」(有缓存就直接给,冷启动才拉一次),`refresh` 是
28
+ * 「现在去拉一次」。分两个端点的理由:客户端的同步策略(启动 / 30 分钟 / 打开连接器页
29
+ * 且 >5min stale)由它自己定,Host 不该替它决定什么时候必须联网。
30
+ */
31
+ export declare const MANAGED_MCP_RPC_ENDPOINTS: readonly ["snapshot", "refresh", "assertion"];
32
+ export type ManagedMcpRpcEndpoint = (typeof MANAGED_MCP_RPC_ENDPOINTS)[number];
33
+ /**
34
+ * Host 侧后台预热间隔:30 分钟(与客户端同步节奏同量级)。
35
+ * 它**不是**权威节奏——只为让客户端首次 `snapshot` 不必等一次网络往返。
36
+ */
37
+ export declare const MANAGED_MCP_REFRESH_INTERVAL_MS: number;
38
+ /** 一条分配交给客户端的形状(字段名与网关响应一致,是冻结契约的一部分)。 */
39
+ export interface ManagedMcpAssignment {
40
+ assignmentId: string;
41
+ revision: number;
42
+ slug: string;
43
+ displayName: string;
44
+ serverName: string;
45
+ transport: string;
46
+ url: string;
47
+ extraHeaders: Record<string, string>;
48
+ auth: {
49
+ kind: string;
50
+ mintUrl: string;
51
+ revokeUrl: string;
52
+ };
53
+ updatedAt: string;
54
+ }
55
+ /**
56
+ * 一份清单快照。
57
+ *
58
+ * `stale` 为 true 表示「这是上一次成功的结果,最近一次拉取失败了」——调用方可以照常
59
+ * 展示,但**不得**把它用作删除本地连接的依据(那条路只允许走成功响应)。
60
+ */
61
+ export interface ManagedMcpSnapshot {
62
+ /** 清单级序号(服务端任何影响清单的写入都 +1),客户端先比它再决定要不要深比对。 */
63
+ revision: number;
64
+ /** 本次成功拉取的时刻(RFC3339)。 */
65
+ fetchedAt: string;
66
+ assignments: ManagedMcpAssignment[];
67
+ stale: boolean;
68
+ /** 最近一次拉取失败的原因(成功时缺省)。 */
69
+ lastError?: ManagedMcpFailure;
70
+ }
71
+ /** 客户端可安全展示的失败。 */
72
+ export interface ManagedMcpFailure {
73
+ code: string;
74
+ message: string;
75
+ /** DSH Connection 信封要求 `details` 是对象;协议层没有额外细节时补空对象。 */
76
+ details: object;
77
+ }
78
+ /** 端点结果信封(与 DSH Connection 的 `ConnectionRpcResult` 同形)。 */
79
+ export type ManagedMcpRpcResult<T> = {
80
+ readonly ok: true;
81
+ readonly value: T;
82
+ } | {
83
+ readonly ok: false;
84
+ readonly error: ManagedMcpFailure;
85
+ };
86
+ /** 请求侧错误(只有 `bad-request`:未知端点 / 非法 payload)。 */
87
+ export declare class ManagedMcpRequestError extends Error {
88
+ readonly code: 'bad-request';
89
+ constructor(code: 'bad-request', message: string);
90
+ }
91
+ /** Host 侧 RPC 依赖的最小上下文(结构兼容 cordis Context,便于单测伪造)。 */
92
+ export interface ManagedMcpRpcConnection {
93
+ rpc: {
94
+ handle(channel: string, handler: (endpoint: string, payload: unknown, signal: AbortSignal) => Promise<unknown>, options: {
95
+ authority: 'loopback';
96
+ }): () => Promise<void> | void;
97
+ };
98
+ }
99
+ export interface ManagedMcpRpcContext {
100
+ readonly connection: ManagedMcpRpcConnection;
101
+ readonly logger: {
102
+ warn(message: string): void;
103
+ };
104
+ }
105
+ /** 网关客户端的 MCP 子集:只声明本频道真正调用的方法(结构兼容 `GatewayClient`)。 */
106
+ export interface ManagedMcpGatewayPort {
107
+ getMyMcpAssignments(sessionCookie: string): Promise<GatewayMcpAssignmentsView>;
108
+ issueMyMcpAssertion(sessionCookie: string, assignmentId: string): Promise<GatewayMcpAssertionTicket>;
109
+ }
110
+ /** Host 侧取数依赖:会话解析 + 当前 baseURL 下的网关客户端。 */
111
+ export interface ManagedMcpRpcDeps {
112
+ /**
113
+ * 解析网关会话 cookie(`AI_ROUTER_SESSION`)。未登录 / 会话缺失返回 undefined。
114
+ * **返回值只在本模块内部使用,绝不出现在 RPC 响应里。**
115
+ */
116
+ resolveSession(): Promise<string | undefined>;
117
+ /** 当前 baseURL 对应的网关客户端;baseURL 未配置时返回 undefined。 */
118
+ gateway(): ManagedMcpGatewayPort | undefined;
119
+ /** Host 日志出口(只记事件与错误类型,不记会话值 / 断言)。 */
120
+ log?: ((message: string) => void) | undefined;
121
+ }
122
+ /** 通用失败文案:不暴露任何网关细节。 */
123
+ export declare const MANAGED_MCP_GENERIC_FAILURE_MESSAGE = "MCP \u6E05\u5355\u6682\u65F6\u65E0\u6CD5\u83B7\u53D6\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5";
124
+ /** 未登录 / 会话失效的统一文案(与用量、每日一句两个频道同口径)。 */
125
+ export declare const MANAGED_MCP_SESSION_EXPIRED_MESSAGE = "\u767B\u5F55\u72B6\u6001\u5DF2\u8FC7\u671F\uFF0C\u8BF7\u91CD\u65B0\u767B\u5F55";
126
+ /**
127
+ * 网关 message 消毒(与 `usage-rpc.ts` 的 `displayGatewayMessage` 同实现):
128
+ * 含 baseURL / HTTP 状态码 / URL 的原始消息一律换成稳定文案,其余(网关自己写好的
129
+ * 中文业务文案)原样保留。code 不受影响,始终原样透传。
130
+ */
131
+ export declare function displayManagedMcpMessage(code: string, message: unknown): string;
132
+ /** 端点入参:只有 `assertion` 带 `assignmentId`。 */
133
+ export interface ManagedMcpRpcPayload {
134
+ assignmentId?: string | undefined;
135
+ }
136
+ /**
137
+ * payload 校验:`snapshot` / `refresh` 只放行空对象,`assertion` 只放行
138
+ * `{ assignmentId }`;其余字段 / 类型不符一律 `bad-request`。
139
+ */
140
+ export declare function parseManagedMcpPayload(endpoint: ManagedMcpRpcEndpoint, raw: unknown): ManagedMcpRpcPayload;
141
+ /** 是否为白名单端点。 */
142
+ export declare function isManagedMcpRpcEndpoint(endpoint: string): endpoint is ManagedMcpRpcEndpoint;
143
+ /**
144
+ * 清单缓存:**只有成功响应才覆写**,失败只记原因。
145
+ *
146
+ * 进程内的(不落盘)是有意的:清单是「服务端此刻认什么」的投影,跨重启的旧清单
147
+ * 没有任何价值——重启后客户端会立刻同步一次,拿到新的比拿到旧的更安全。
148
+ */
149
+ export declare class ManagedMcpStore {
150
+ private readonly deps;
151
+ private readonly now;
152
+ private cached;
153
+ private lastError;
154
+ private inFlight;
155
+ constructor(deps: ManagedMcpRpcDeps, now?: () => number);
156
+ /**
157
+ * 当前缓存(同步读,不发请求)。从没成功过返回 undefined;
158
+ * 最近一次失败会以 `stale + lastError` 标注在上一次成功的结果上。
159
+ */
160
+ peek(): ManagedMcpSnapshot | undefined;
161
+ /**
162
+ * 去网关拉一次清单并更新缓存。失败时抛异常,且**缓存原样保留**。
163
+ *
164
+ * 并发调用合并成一次请求:后台预热与客户端手动同步撞在一起时不必打两遍网关。
165
+ */
166
+ load(): Promise<ManagedMcpSnapshot>;
167
+ private fetchOnce;
168
+ }
169
+ /**
170
+ * 处理一次端点调用:返回 DSH Connection 的结果信封,**永不抛异常**。
171
+ * 导出以便单测直接驱动(无需伪造 `ctx.connection`)。
172
+ */
173
+ export declare function handleManagedMcpEndpoint(endpoint: string, rawPayload: unknown, deps: ManagedMcpRpcDeps, store: ManagedMcpStore, signal?: AbortSignal): Promise<ManagedMcpRpcResult<unknown>>;
174
+ /**
175
+ * 注册 `/hiwork-managed-mcp` 频道,返回异步 disposer(交给 `ctx.effect` 管理生命周期)。
176
+ */
177
+ export declare function registerManagedMcpRpc(ctx: ManagedMcpRpcContext, deps: ManagedMcpRpcDeps, store: ManagedMcpStore): () => Promise<void>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hiwork-core",
3
- "version": "0.0.8",
3
+ "version": "0.0.11",
4
4
  "type": "module",
5
5
  "description": "HiWork 企业统一登录与模型自动供给插件(ai-router 网关登录,Web 表单,无 CLI 认证)",
6
6
  "license": "UNLICENSED",