hiwork-core 0.0.10 → 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,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.10",
3
+ "version": "0.0.11",
4
4
  "type": "module",
5
5
  "description": "HiWork 企业统一登录与模型自动供给插件(ai-router 网关登录,Web 表单,无 CLI 认证)",
6
6
  "license": "UNLICENSED",