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.
- package/lib/ai-router-client.js +55 -0
- package/lib/client.js +206 -2
- package/lib/index.js +296 -1
- package/lib/managed-mcp.js +334 -0
- package/lib/types/ai-router-client.d.ts +70 -0
- package/lib/types/client/contracts.d.ts +25 -0
- package/lib/types/client/managed-mcp.d.ts +133 -0
- package/lib/types/client/types.d.ts +7 -1
- package/lib/types/managed-mcp.d.ts +177 -0
- package/package.json +1 -1
package/lib/ai-router-client.js
CHANGED
|
@@ -12,6 +12,15 @@ const ME_COST_ESTIMATE_PATH = '/api/me/cost-estimate';
|
|
|
12
12
|
/** 每日一句端点:当日条目(东八区取模轮换)与换一句(排除当前条目的随机)。 */
|
|
13
13
|
const ME_DAILY_QUOTE_PATH = '/api/me/daily-quote';
|
|
14
14
|
const ME_DAILY_QUOTE_RANDOM_PATH = '/api/me/daily-quote/random';
|
|
15
|
+
/**
|
|
16
|
+
* MCP 分发的自助端点(管理员在 ai-hub 登记 MCP、下发给全员或某人后,员工端从这里拉清单)。
|
|
17
|
+
* 两条都在网关 `/api/me` 会话组下,鉴权是 `SessionAuth`(只读 Cookie)。
|
|
18
|
+
*
|
|
19
|
+
* 清单响应里**没有任何令牌**:客户端拿断言去 MCP 服务自己换 per-user 令牌,
|
|
20
|
+
* 网关全程不经手明文,所以这里也没有「查看我的令牌」这类端点。
|
|
21
|
+
*/
|
|
22
|
+
const ME_MCP_ASSIGNMENTS_PATH = '/api/me/mcp-assignments';
|
|
23
|
+
const ME_MCP_ASSERTIONS_PATH = '/api/me/mcp-assertions';
|
|
15
24
|
/** 授权请求默认值:桌面端 client_id 与自定义协议回调地址(网关侧精确匹配)。 */
|
|
16
25
|
const DEFAULT_CLIENT_ID = 'hiwork-desktop';
|
|
17
26
|
const DEFAULT_REDIRECT_URI = 'hiwork://auth/callback';
|
|
@@ -208,6 +217,52 @@ export class AiRouterClient {
|
|
|
208
217
|
throw new GatewayError('GATEWAY_UNREACHABLE', `网关返回错误(HTTP ${res.status})`, res.status);
|
|
209
218
|
return await parseMeJson(res);
|
|
210
219
|
}
|
|
220
|
+
/**
|
|
221
|
+
* GET /api/me/mcp-assignments:管理员下发给当前账号的 MCP 清单。
|
|
222
|
+
*
|
|
223
|
+
* 不复用 {@link getMeJson}:那条路径把 403 映射成 `NO_KEY_ASSIGNED`(「尚未被分配
|
|
224
|
+
* 虚拟 KEY」),而本端点的鉴权只有会话——一个还没被分配 Key 的账号照样应该有
|
|
225
|
+
* 清单(分 MCP 与分 Key 是两件事)。沿用它的 403 文案会把「没有 Key」错报成原因。
|
|
226
|
+
*/
|
|
227
|
+
async getMyMcpAssignments(sessionCookie) {
|
|
228
|
+
const res = await this.requestOrUnreachable(ME_MCP_ASSIGNMENTS_PATH, { method: 'GET', sessionCookie });
|
|
229
|
+
if (res.status === 401)
|
|
230
|
+
throw new GatewayError('SESSION_EXPIRED', '网关会话已过期,请重新登录', 401);
|
|
231
|
+
// 403 / 5xx / 反代错误页一律「网关不可达」:这条路径没有需要单独解释的拒绝语义。
|
|
232
|
+
if (!res.ok)
|
|
233
|
+
throw new GatewayError('GATEWAY_UNREACHABLE', `网关返回错误(HTTP ${res.status})`, res.status);
|
|
234
|
+
return await parseMeJson(res);
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* POST /api/me/mcp-assertions:为某条分配签一张 120 秒断言。
|
|
238
|
+
*
|
|
239
|
+
* 三种拒绝各有确定含义(服务端 handler 的 404/409/503),必须分开映射——否则
|
|
240
|
+
* 客户端只会看到一句「取断言失败」,既分不清「被撤销了」(该卸连接)还是
|
|
241
|
+
* 「服务停用了」(该重试)还是「部署没配私钥」(找管理员)。
|
|
242
|
+
*/
|
|
243
|
+
async issueMyMcpAssertion(sessionCookie, assignmentId) {
|
|
244
|
+
const res = await this.requestOrUnreachable(ME_MCP_ASSERTIONS_PATH, {
|
|
245
|
+
method: 'POST',
|
|
246
|
+
sessionCookie,
|
|
247
|
+
body: { assignmentId },
|
|
248
|
+
});
|
|
249
|
+
if (res.status === 401)
|
|
250
|
+
throw new GatewayError('SESSION_EXPIRED', '网关会话已过期,请重新登录', 401);
|
|
251
|
+
// 400 与 404 合并成同一个 code:对客户端而言两者的行为一致(这条 id 用不了了,
|
|
252
|
+
// 只能等下一次成功同步),文案也不该暗示「是你发错了 id」。
|
|
253
|
+
if (res.status === 400 || res.status === 404) {
|
|
254
|
+
throw new GatewayError('MCP_ASSIGNMENT_GONE', '这条分配已失效,稍后同步会重新核对', res.status);
|
|
255
|
+
}
|
|
256
|
+
if (res.status === 409) {
|
|
257
|
+
throw new GatewayError('MCP_NOT_DELIVERABLE', '该 MCP 服务当前不可下发,请联系管理员', 409);
|
|
258
|
+
}
|
|
259
|
+
if (res.status === 503) {
|
|
260
|
+
throw new GatewayError('MCP_ASSERTION_DISABLED', '本部署未启用 MCP 断言签发,请联系管理员', 503);
|
|
261
|
+
}
|
|
262
|
+
if (!res.ok)
|
|
263
|
+
throw new GatewayError('GATEWAY_UNREACHABLE', `网关返回错误(HTTP ${res.status})`, res.status);
|
|
264
|
+
return await parseMeJson(res);
|
|
265
|
+
}
|
|
211
266
|
/**
|
|
212
267
|
* /api/me 自助端点的 GET 公共路径:网络异常 → GATEWAY_UNREACHABLE,
|
|
213
268
|
* 401/403/400 映射为稳定 code,响应体非 JSON 时不把原文带进异常。
|
package/lib/client.js
CHANGED
|
@@ -2812,6 +2812,198 @@ function mountFeatureCenter(options) {
|
|
|
2812
2812
|
// src/client/contracts.ts
|
|
2813
2813
|
var HIWORK_FEATURE_CENTER_SERVICE = "hiworkFeatureCenter";
|
|
2814
2814
|
|
|
2815
|
+
// src/client/managed-mcp.ts
|
|
2816
|
+
var MANAGED_MCP_CHANNEL = "/hiwork-managed-mcp";
|
|
2817
|
+
var MANAGED_MCP_STALE_MS = 5 * 6e4;
|
|
2818
|
+
var ManagedMcpRpcError = class extends Error {
|
|
2819
|
+
constructor(code, message) {
|
|
2820
|
+
super(message);
|
|
2821
|
+
this.code = code;
|
|
2822
|
+
this.name = "ManagedMcpRpcError";
|
|
2823
|
+
}
|
|
2824
|
+
};
|
|
2825
|
+
function unwrapManagedMcpResult(value) {
|
|
2826
|
+
if (typeof value !== "object" || value === null || !("ok" in value)) {
|
|
2827
|
+
throw new ManagedMcpRpcError("internal", "MCP \u6E05\u5355\u4E3B\u673A\u8FD4\u56DE\u4E86\u65E0\u6548\u54CD\u5E94\u3002");
|
|
2828
|
+
}
|
|
2829
|
+
const result = value;
|
|
2830
|
+
if (result.ok === true) return result.value;
|
|
2831
|
+
if (result.ok === false) {
|
|
2832
|
+
const code = typeof result.error?.code === "string" ? result.error.code : "internal";
|
|
2833
|
+
const message = typeof result.error?.message === "string" && result.error.message.trim() !== "" ? result.error.message : "MCP \u6E05\u5355\u6682\u65F6\u65E0\u6CD5\u83B7\u53D6\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5";
|
|
2834
|
+
throw new ManagedMcpRpcError(code, message);
|
|
2835
|
+
}
|
|
2836
|
+
throw new ManagedMcpRpcError("internal", "MCP \u6E05\u5355\u4E3B\u673A\u8FD4\u56DE\u4E86\u65E0\u6548\u54CD\u5E94\u3002");
|
|
2837
|
+
}
|
|
2838
|
+
function toSnapshot(value) {
|
|
2839
|
+
if (typeof value !== "object" || value === null) {
|
|
2840
|
+
throw new ManagedMcpRpcError("internal", "MCP \u6E05\u5355\u4E3B\u673A\u8FD4\u56DE\u4E86\u65E0\u6548\u54CD\u5E94\u3002");
|
|
2841
|
+
}
|
|
2842
|
+
const record = value;
|
|
2843
|
+
const rawList = record["assignments"];
|
|
2844
|
+
if (!Array.isArray(rawList)) {
|
|
2845
|
+
throw new ManagedMcpRpcError("internal", "MCP \u6E05\u5355\u4E3B\u673A\u8FD4\u56DE\u4E86\u65E0\u6548\u54CD\u5E94\u3002");
|
|
2846
|
+
}
|
|
2847
|
+
const assignments = [];
|
|
2848
|
+
for (const item of rawList) {
|
|
2849
|
+
const normalized = toAssignment(item);
|
|
2850
|
+
if (normalized !== void 0) assignments.push(normalized);
|
|
2851
|
+
}
|
|
2852
|
+
const lastError = record["lastError"];
|
|
2853
|
+
const snapshot = {
|
|
2854
|
+
revision: typeof record["revision"] === "number" ? record["revision"] : 0,
|
|
2855
|
+
fetchedAt: typeof record["fetchedAt"] === "string" ? record["fetchedAt"] : "",
|
|
2856
|
+
assignments,
|
|
2857
|
+
stale: record["stale"] === true
|
|
2858
|
+
};
|
|
2859
|
+
if (typeof lastError === "object" && lastError !== null) {
|
|
2860
|
+
const detail = lastError;
|
|
2861
|
+
snapshot.lastError = {
|
|
2862
|
+
code: typeof detail.code === "string" ? detail.code : "internal",
|
|
2863
|
+
message: typeof detail.message === "string" ? detail.message : "MCP \u6E05\u5355\u6682\u65F6\u65E0\u6CD5\u83B7\u53D6\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5"
|
|
2864
|
+
};
|
|
2865
|
+
}
|
|
2866
|
+
return snapshot;
|
|
2867
|
+
}
|
|
2868
|
+
function toAssignment(value) {
|
|
2869
|
+
if (typeof value !== "object" || value === null) return void 0;
|
|
2870
|
+
const record = value;
|
|
2871
|
+
const authRaw = record["auth"];
|
|
2872
|
+
const auth = typeof authRaw === "object" && authRaw !== null ? authRaw : void 0;
|
|
2873
|
+
const assignmentId = text(record["assignmentId"]);
|
|
2874
|
+
const serverName = text(record["serverName"]);
|
|
2875
|
+
const url = text(record["url"]);
|
|
2876
|
+
const kind = text(auth?.["kind"]);
|
|
2877
|
+
const mintUrl = text(auth?.["mintUrl"]);
|
|
2878
|
+
if (assignmentId === void 0 || serverName === void 0 || url === void 0 || kind === void 0 || mintUrl === void 0) {
|
|
2879
|
+
return void 0;
|
|
2880
|
+
}
|
|
2881
|
+
return {
|
|
2882
|
+
assignmentId,
|
|
2883
|
+
revision: typeof record["revision"] === "number" ? record["revision"] : 0,
|
|
2884
|
+
slug: text(record["slug"]) ?? serverName,
|
|
2885
|
+
displayName: text(record["displayName"]) ?? serverName,
|
|
2886
|
+
serverName,
|
|
2887
|
+
transport: text(record["transport"]) ?? "",
|
|
2888
|
+
url,
|
|
2889
|
+
extraHeaders: stringMap(record["extraHeaders"]),
|
|
2890
|
+
auth: { kind, mintUrl, revokeUrl: text(auth?.["revokeUrl"]) ?? "" },
|
|
2891
|
+
updatedAt: text(record["updatedAt"]) ?? ""
|
|
2892
|
+
};
|
|
2893
|
+
}
|
|
2894
|
+
function text(value) {
|
|
2895
|
+
return typeof value === "string" && value.trim() !== "" ? value : void 0;
|
|
2896
|
+
}
|
|
2897
|
+
function stringMap(raw) {
|
|
2898
|
+
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) return {};
|
|
2899
|
+
const out = {};
|
|
2900
|
+
for (const [key, value] of Object.entries(raw)) {
|
|
2901
|
+
if (typeof value === "string") out[key] = value;
|
|
2902
|
+
}
|
|
2903
|
+
return out;
|
|
2904
|
+
}
|
|
2905
|
+
var ManagedMcpStore = class {
|
|
2906
|
+
constructor(rpc) {
|
|
2907
|
+
this.rpc = rpc;
|
|
2908
|
+
}
|
|
2909
|
+
current;
|
|
2910
|
+
listeners = /* @__PURE__ */ new Set();
|
|
2911
|
+
inFlight;
|
|
2912
|
+
/** 本地镜像(同步)。从没成功同步过返回 undefined。**不要拿它做删除决策**(见文件头)。 */
|
|
2913
|
+
snapshot() {
|
|
2914
|
+
return this.current;
|
|
2915
|
+
}
|
|
2916
|
+
/** 本地镜像是否已经陈旧(或从未同步过)。 */
|
|
2917
|
+
isStale(ttlMs = MANAGED_MCP_STALE_MS, now = Date.now()) {
|
|
2918
|
+
if (this.current === void 0 || this.current.fetchedAt === "") return true;
|
|
2919
|
+
const fetchedAt = Date.parse(this.current.fetchedAt);
|
|
2920
|
+
if (Number.isNaN(fetchedAt)) return true;
|
|
2921
|
+
return now - fetchedAt >= ttlMs;
|
|
2922
|
+
}
|
|
2923
|
+
/**
|
|
2924
|
+
* 强制同步一次并返回**刚拉到的**清单。
|
|
2925
|
+
*
|
|
2926
|
+
* 返回值(而不是让人随后去读 `snapshot()`)是刻意的:只有这个返回值保证「这一次
|
|
2927
|
+
* 网络请求成功了」,用它做删除决策才是安全的;读镜像可能读到上一次的结果。
|
|
2928
|
+
* 失败时抛错且**不动镜像**——调用方据此保持现有的连接一条不删。
|
|
2929
|
+
*/
|
|
2930
|
+
async refresh(signal) {
|
|
2931
|
+
return await this.dedupe(async () => {
|
|
2932
|
+
const raw = await this.rpc.call(MANAGED_MCP_CHANNEL, "refresh", {}, signal);
|
|
2933
|
+
return this.adopt(unwrapManagedMcpResult(raw));
|
|
2934
|
+
});
|
|
2935
|
+
}
|
|
2936
|
+
/**
|
|
2937
|
+
* 取一份清单,**能用 Host 的预热缓存就用**(Host 后台每 30 分钟拉一次)。
|
|
2938
|
+
*
|
|
2939
|
+
* 与 {@link refresh} 的分工:这里是「启动时先把界面填上」的便宜入口——命中缓存时
|
|
2940
|
+
* 一次网络请求都不发;Host 缓存也是冷的才会真的去拉。返回值仍保证是「某一次成功
|
|
2941
|
+
* 拉取的结果」,但**可能比服务端现状旧**(最旧不超过 Host 的上一次成功拉取),
|
|
2942
|
+
* 所以调用方按 `stale` / `isStale()` 判定它够不够权威,不够就再 `refresh()` 一次。
|
|
2943
|
+
*/
|
|
2944
|
+
async warm(signal) {
|
|
2945
|
+
return await this.dedupe(async () => {
|
|
2946
|
+
const raw = await this.rpc.call(MANAGED_MCP_CHANNEL, "snapshot", {}, signal);
|
|
2947
|
+
return this.adopt(unwrapManagedMcpResult(raw));
|
|
2948
|
+
});
|
|
2949
|
+
}
|
|
2950
|
+
/** 取一张新鲜断言(120 秒)。失败抛错;本层不缓存、不换令牌。 */
|
|
2951
|
+
async assertion(assignmentId, signal) {
|
|
2952
|
+
const raw = await this.rpc.call(MANAGED_MCP_CHANNEL, "assertion", { assignmentId }, signal);
|
|
2953
|
+
const value = unwrapManagedMcpResult(raw);
|
|
2954
|
+
if (typeof value !== "object" || value === null) {
|
|
2955
|
+
throw new ManagedMcpRpcError("internal", "MCP \u6E05\u5355\u4E3B\u673A\u8FD4\u56DE\u4E86\u65E0\u6548\u54CD\u5E94\u3002");
|
|
2956
|
+
}
|
|
2957
|
+
const record = value;
|
|
2958
|
+
const assertion = text(record["assertion"]);
|
|
2959
|
+
const mintUrl = text(record["mintUrl"]);
|
|
2960
|
+
if (assertion === void 0 || mintUrl === void 0) {
|
|
2961
|
+
throw new ManagedMcpRpcError("internal", "MCP \u6E05\u5355\u4E3B\u673A\u8FD4\u56DE\u4E86\u65E0\u6548\u54CD\u5E94\u3002");
|
|
2962
|
+
}
|
|
2963
|
+
return {
|
|
2964
|
+
assertion,
|
|
2965
|
+
mintUrl,
|
|
2966
|
+
revokeUrl: text(record["revokeUrl"]) ?? "",
|
|
2967
|
+
expiresAt: text(record["expiresAt"]) ?? "",
|
|
2968
|
+
serverName: text(record["serverName"]) ?? ""
|
|
2969
|
+
};
|
|
2970
|
+
}
|
|
2971
|
+
/** 订阅镜像变化(连接器域据此重渲染 / 重排同步)。返回退订函数。 */
|
|
2972
|
+
subscribe(listener) {
|
|
2973
|
+
this.listeners.add(listener);
|
|
2974
|
+
return () => {
|
|
2975
|
+
this.listeners.delete(listener);
|
|
2976
|
+
};
|
|
2977
|
+
}
|
|
2978
|
+
/** 清空镜像(登出 / 换账号:清单是按人的,留着会串号)。 */
|
|
2979
|
+
clear() {
|
|
2980
|
+
this.current = void 0;
|
|
2981
|
+
this.emit();
|
|
2982
|
+
}
|
|
2983
|
+
/**
|
|
2984
|
+
* 并发去重:`warm` 与 `refresh` 撞在一起(启动同步与定时同步常常同拍)时共用一次
|
|
2985
|
+
* 请求,且**共用同一个返回值**——两边对「这一次拉到了什么」的判断必须一致。
|
|
2986
|
+
*/
|
|
2987
|
+
async dedupe(task) {
|
|
2988
|
+
if (this.inFlight !== void 0) return await this.inFlight;
|
|
2989
|
+
const promise = task();
|
|
2990
|
+
this.inFlight = promise;
|
|
2991
|
+
return await promise.finally(() => {
|
|
2992
|
+
this.inFlight = void 0;
|
|
2993
|
+
});
|
|
2994
|
+
}
|
|
2995
|
+
/** 采纳一份成功响应:写镜像 + 通知订阅者(失败路径永远不会走到这里)。 */
|
|
2996
|
+
adopt(value) {
|
|
2997
|
+
const snapshot = toSnapshot(value);
|
|
2998
|
+
this.current = snapshot;
|
|
2999
|
+
this.emit();
|
|
3000
|
+
return snapshot;
|
|
3001
|
+
}
|
|
3002
|
+
emit() {
|
|
3003
|
+
for (const listener of [...this.listeners]) listener();
|
|
3004
|
+
}
|
|
3005
|
+
};
|
|
3006
|
+
|
|
2815
3007
|
// src/client/index.ts
|
|
2816
3008
|
var inject = ["slots", "locale", "remote", "remote.credentials", "settingsScope", "connection"];
|
|
2817
3009
|
var INTERACTION_STYLE_ID = "hiwork-core-interaction-styles";
|
|
@@ -2849,6 +3041,7 @@ function apply(ctx) {
|
|
|
2849
3041
|
const usage = new UsageDataStore(connection.rpc);
|
|
2850
3042
|
const sessionCost = new SessionCostStore(connection.rpc);
|
|
2851
3043
|
const dailyQuotes = new DailyDataStore(connection.rpc);
|
|
3044
|
+
const managedMcp = new ManagedMcpStore(connection.rpc);
|
|
2852
3045
|
const appVersion = createAppVersion(connection.rpc);
|
|
2853
3046
|
ctx.slots.inject("settings.section", () => ctx.slots.register({
|
|
2854
3047
|
name: "settings.section",
|
|
@@ -2866,6 +3059,7 @@ function apply(ctx) {
|
|
|
2866
3059
|
ctx.effect(() => observeAccountChange(controller.inject().hooks.aiRouterCard, () => {
|
|
2867
3060
|
usage.clear();
|
|
2868
3061
|
sessionCost.clear();
|
|
3062
|
+
managedMcp.clear();
|
|
2869
3063
|
}), "hiwork-core: \u7528\u91CF\u7F13\u5B58\u968F\u8D26\u53F7 / Key \u53D8\u5316\u6E05\u7A7A");
|
|
2870
3064
|
ctx.effect(() => observeAboutNavIcon(), "hiwork-core: about nav icon");
|
|
2871
3065
|
ctx.slots.inject("sidebar.brand.mark", () => ctx.slots.register({
|
|
@@ -2884,9 +3078,9 @@ function apply(ctx) {
|
|
|
2884
3078
|
}, UserBadge));
|
|
2885
3079
|
ctx.effect(() => mountLoginGate(scope, storeCredential), "hiwork-core: login gate");
|
|
2886
3080
|
ctx.effect(() => observeHeroDecorations(document, dailyQuotes), "hiwork-core: hero decorations");
|
|
2887
|
-
installFeatureCenter(ctx);
|
|
3081
|
+
installFeatureCenter(ctx, managedMcp);
|
|
2888
3082
|
}
|
|
2889
|
-
function installFeatureCenter(ctx) {
|
|
3083
|
+
function installFeatureCenter(ctx, managedMcp) {
|
|
2890
3084
|
const featureRegistry = createFeatureRegistry();
|
|
2891
3085
|
let center;
|
|
2892
3086
|
const sessions = ctx.get("sessions");
|
|
@@ -2905,6 +3099,16 @@ function installFeatureCenter(ctx) {
|
|
|
2905
3099
|
return;
|
|
2906
3100
|
}
|
|
2907
3101
|
featureRegistry.openNative();
|
|
3102
|
+
},
|
|
3103
|
+
// 受管 MCP 面:连接器域(hiwork-capabilities)按这个成员判断「有没有这个能力」,
|
|
3104
|
+
// 老 core 上它是 undefined,能力中心照常工作、只是没有受管连接。
|
|
3105
|
+
managedMcp: {
|
|
3106
|
+
snapshot: () => managedMcp.snapshot(),
|
|
3107
|
+
isStale: (ttlMs) => managedMcp.isStale(ttlMs),
|
|
3108
|
+
warm: async (signal) => await managedMcp.warm(signal),
|
|
3109
|
+
refresh: async (signal) => await managedMcp.refresh(signal),
|
|
3110
|
+
assertion: async (assignmentId, signal) => await managedMcp.assertion(assignmentId, signal),
|
|
3111
|
+
subscribe: (listener) => managedMcp.subscribe(listener)
|
|
2908
3112
|
}
|
|
2909
3113
|
};
|
|
2910
3114
|
ctx.provide(HIWORK_FEATURE_CENTER_SERVICE, face);
|
package/lib/index.js
CHANGED
|
@@ -3137,7 +3137,7 @@ function isUnloading(ctx) {
|
|
|
3137
3137
|
}
|
|
3138
3138
|
|
|
3139
3139
|
// src/version.ts
|
|
3140
|
-
var PKG_VERSION = false ? "dev" : "0.0.
|
|
3140
|
+
var PKG_VERSION = false ? "dev" : "0.0.11";
|
|
3141
3141
|
|
|
3142
3142
|
// src/ai-router-client.ts
|
|
3143
3143
|
var AUTH_AUTHORIZE_PATH = "/api/auth/authorize";
|
|
@@ -3150,6 +3150,8 @@ var ME_KEY_RECHARGES_PATH = "/api/me/key/recharges";
|
|
|
3150
3150
|
var ME_COST_ESTIMATE_PATH = "/api/me/cost-estimate";
|
|
3151
3151
|
var ME_DAILY_QUOTE_PATH = "/api/me/daily-quote";
|
|
3152
3152
|
var ME_DAILY_QUOTE_RANDOM_PATH = "/api/me/daily-quote/random";
|
|
3153
|
+
var ME_MCP_ASSIGNMENTS_PATH = "/api/me/mcp-assignments";
|
|
3154
|
+
var ME_MCP_ASSERTIONS_PATH = "/api/me/mcp-assertions";
|
|
3153
3155
|
var DEFAULT_CLIENT_ID = "hiwork-desktop";
|
|
3154
3156
|
var DEFAULT_REDIRECT_URI = "hiwork://auth/callback";
|
|
3155
3157
|
var GatewayError = class extends Error {
|
|
@@ -3308,6 +3310,45 @@ var AiRouterClient = class {
|
|
|
3308
3310
|
if (!res.ok) throw new GatewayError("GATEWAY_UNREACHABLE", `\u7F51\u5173\u8FD4\u56DE\u9519\u8BEF\uFF08HTTP ${res.status}\uFF09`, res.status);
|
|
3309
3311
|
return await parseMeJson(res);
|
|
3310
3312
|
}
|
|
3313
|
+
/**
|
|
3314
|
+
* GET /api/me/mcp-assignments:管理员下发给当前账号的 MCP 清单。
|
|
3315
|
+
*
|
|
3316
|
+
* 不复用 {@link getMeJson}:那条路径把 403 映射成 `NO_KEY_ASSIGNED`(「尚未被分配
|
|
3317
|
+
* 虚拟 KEY」),而本端点的鉴权只有会话——一个还没被分配 Key 的账号照样应该有
|
|
3318
|
+
* 清单(分 MCP 与分 Key 是两件事)。沿用它的 403 文案会把「没有 Key」错报成原因。
|
|
3319
|
+
*/
|
|
3320
|
+
async getMyMcpAssignments(sessionCookie) {
|
|
3321
|
+
const res = await this.requestOrUnreachable(ME_MCP_ASSIGNMENTS_PATH, { method: "GET", sessionCookie });
|
|
3322
|
+
if (res.status === 401) throw new GatewayError("SESSION_EXPIRED", "\u7F51\u5173\u4F1A\u8BDD\u5DF2\u8FC7\u671F\uFF0C\u8BF7\u91CD\u65B0\u767B\u5F55", 401);
|
|
3323
|
+
if (!res.ok) throw new GatewayError("GATEWAY_UNREACHABLE", `\u7F51\u5173\u8FD4\u56DE\u9519\u8BEF\uFF08HTTP ${res.status}\uFF09`, res.status);
|
|
3324
|
+
return await parseMeJson(res);
|
|
3325
|
+
}
|
|
3326
|
+
/**
|
|
3327
|
+
* POST /api/me/mcp-assertions:为某条分配签一张 120 秒断言。
|
|
3328
|
+
*
|
|
3329
|
+
* 三种拒绝各有确定含义(服务端 handler 的 404/409/503),必须分开映射——否则
|
|
3330
|
+
* 客户端只会看到一句「取断言失败」,既分不清「被撤销了」(该卸连接)还是
|
|
3331
|
+
* 「服务停用了」(该重试)还是「部署没配私钥」(找管理员)。
|
|
3332
|
+
*/
|
|
3333
|
+
async issueMyMcpAssertion(sessionCookie, assignmentId) {
|
|
3334
|
+
const res = await this.requestOrUnreachable(ME_MCP_ASSERTIONS_PATH, {
|
|
3335
|
+
method: "POST",
|
|
3336
|
+
sessionCookie,
|
|
3337
|
+
body: { assignmentId }
|
|
3338
|
+
});
|
|
3339
|
+
if (res.status === 401) throw new GatewayError("SESSION_EXPIRED", "\u7F51\u5173\u4F1A\u8BDD\u5DF2\u8FC7\u671F\uFF0C\u8BF7\u91CD\u65B0\u767B\u5F55", 401);
|
|
3340
|
+
if (res.status === 400 || res.status === 404) {
|
|
3341
|
+
throw new GatewayError("MCP_ASSIGNMENT_GONE", "\u8FD9\u6761\u5206\u914D\u5DF2\u5931\u6548\uFF0C\u7A0D\u540E\u540C\u6B65\u4F1A\u91CD\u65B0\u6838\u5BF9", res.status);
|
|
3342
|
+
}
|
|
3343
|
+
if (res.status === 409) {
|
|
3344
|
+
throw new GatewayError("MCP_NOT_DELIVERABLE", "\u8BE5 MCP \u670D\u52A1\u5F53\u524D\u4E0D\u53EF\u4E0B\u53D1\uFF0C\u8BF7\u8054\u7CFB\u7BA1\u7406\u5458", 409);
|
|
3345
|
+
}
|
|
3346
|
+
if (res.status === 503) {
|
|
3347
|
+
throw new GatewayError("MCP_ASSERTION_DISABLED", "\u672C\u90E8\u7F72\u672A\u542F\u7528 MCP \u65AD\u8A00\u7B7E\u53D1\uFF0C\u8BF7\u8054\u7CFB\u7BA1\u7406\u5458", 503);
|
|
3348
|
+
}
|
|
3349
|
+
if (!res.ok) throw new GatewayError("GATEWAY_UNREACHABLE", `\u7F51\u5173\u8FD4\u56DE\u9519\u8BEF\uFF08HTTP ${res.status}\uFF09`, res.status);
|
|
3350
|
+
return await parseMeJson(res);
|
|
3351
|
+
}
|
|
3311
3352
|
/**
|
|
3312
3353
|
* /api/me 自助端点的 GET 公共路径:网络异常 → GATEWAY_UNREACHABLE,
|
|
3313
3354
|
* 401/403/400 映射为稳定 code,响应体非 JSON 时不把原文带进异常。
|
|
@@ -4407,6 +4448,232 @@ function registerUsageRpc(ctx, deps) {
|
|
|
4407
4448
|
};
|
|
4408
4449
|
}
|
|
4409
4450
|
|
|
4451
|
+
// src/managed-mcp.ts
|
|
4452
|
+
var MANAGED_MCP_RPC_CHANNEL = "/hiwork-managed-mcp";
|
|
4453
|
+
var MANAGED_MCP_RPC_ENDPOINTS = ["snapshot", "refresh", "assertion"];
|
|
4454
|
+
var MANAGED_MCP_REFRESH_INTERVAL_MS = 30 * 6e4;
|
|
4455
|
+
var ManagedMcpRequestError = class extends Error {
|
|
4456
|
+
constructor(code, message) {
|
|
4457
|
+
super(message);
|
|
4458
|
+
this.code = code;
|
|
4459
|
+
this.name = "ManagedMcpRequestError";
|
|
4460
|
+
}
|
|
4461
|
+
};
|
|
4462
|
+
var MANAGED_MCP_MESSAGE_FALLBACK = {
|
|
4463
|
+
SESSION_EXPIRED: "\u767B\u5F55\u72B6\u6001\u5DF2\u8FC7\u671F\uFF0C\u8BF7\u91CD\u65B0\u767B\u5F55",
|
|
4464
|
+
GATEWAY_UNREACHABLE: "\u7F51\u5173\u6682\u65F6\u4E0D\u53EF\u7528\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5",
|
|
4465
|
+
MCP_ASSIGNMENT_GONE: "\u8FD9\u6761\u5206\u914D\u5DF2\u5931\u6548\uFF0C\u7A0D\u540E\u540C\u6B65\u4F1A\u91CD\u65B0\u6838\u5BF9",
|
|
4466
|
+
MCP_NOT_DELIVERABLE: "\u8BE5 MCP \u670D\u52A1\u5F53\u524D\u4E0D\u53EF\u4E0B\u53D1\uFF0C\u8BF7\u8054\u7CFB\u7BA1\u7406\u5458",
|
|
4467
|
+
MCP_ASSERTION_DISABLED: "\u672C\u90E8\u7F72\u672A\u542F\u7528 MCP \u65AD\u8A00\u7B7E\u53D1\uFF0C\u8BF7\u8054\u7CFB\u7BA1\u7406\u5458",
|
|
4468
|
+
NO_KEY_ASSIGNED: "\u5F53\u524D\u8D26\u53F7\u5C1A\u672A\u88AB\u5206\u914D\u865A\u62DF KEY\uFF0C\u8BF7\u8054\u7CFB\u7BA1\u7406\u5458"
|
|
4469
|
+
};
|
|
4470
|
+
var MANAGED_MCP_GENERIC_FAILURE_MESSAGE = "MCP \u6E05\u5355\u6682\u65F6\u65E0\u6CD5\u83B7\u53D6\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5";
|
|
4471
|
+
var MANAGED_MCP_SESSION_EXPIRED_MESSAGE = "\u767B\u5F55\u72B6\u6001\u5DF2\u8FC7\u671F\uFF0C\u8BF7\u91CD\u65B0\u767B\u5F55";
|
|
4472
|
+
function displayManagedMcpMessage(code, message) {
|
|
4473
|
+
const text = typeof message === "string" ? message.trim() : "";
|
|
4474
|
+
if (text === "") return MANAGED_MCP_MESSAGE_FALLBACK[code] ?? MANAGED_MCP_GENERIC_FAILURE_MESSAGE;
|
|
4475
|
+
const leaksImplementationDetail = /https?:\/\//i.test(text) || /\bHTTP\s*\d{3}\b/i.test(text) || /\bbaseURL\b/i.test(text);
|
|
4476
|
+
if (leaksImplementationDetail) return MANAGED_MCP_MESSAGE_FALLBACK[code] ?? MANAGED_MCP_GENERIC_FAILURE_MESSAGE;
|
|
4477
|
+
return text;
|
|
4478
|
+
}
|
|
4479
|
+
var ASSIGNMENT_ID_PATTERN = /^[0-9a-fA-F-]{1,64}$/;
|
|
4480
|
+
function parseManagedMcpPayload(endpoint, raw) {
|
|
4481
|
+
if (raw === void 0 || raw === null) {
|
|
4482
|
+
if (endpoint === "assertion") throw new ManagedMcpRequestError("bad-request", "\u53D6\u65AD\u8A00\u8BF7\u6C42\u7F3A\u5C11 assignmentId\u3002");
|
|
4483
|
+
return {};
|
|
4484
|
+
}
|
|
4485
|
+
if (typeof raw !== "object" || Array.isArray(raw)) {
|
|
4486
|
+
throw new ManagedMcpRequestError("bad-request", "MCP \u6E05\u5355\u8BF7\u6C42\u53C2\u6570\u4E0D\u5408\u6CD5\u3002");
|
|
4487
|
+
}
|
|
4488
|
+
const record = raw;
|
|
4489
|
+
const keys = Object.keys(record);
|
|
4490
|
+
if (endpoint !== "assertion") {
|
|
4491
|
+
if (keys.length > 0) throw new ManagedMcpRequestError("bad-request", `MCP \u6E05\u5355\u8BF7\u6C42\u5305\u542B\u672A\u77E5\u5B57\u6BB5 '${keys[0]}'\u3002`);
|
|
4492
|
+
return {};
|
|
4493
|
+
}
|
|
4494
|
+
for (const key of keys) {
|
|
4495
|
+
if (key !== "assignmentId") throw new ManagedMcpRequestError("bad-request", `\u53D6\u65AD\u8A00\u8BF7\u6C42\u5305\u542B\u672A\u77E5\u5B57\u6BB5 '${key}'\u3002`);
|
|
4496
|
+
}
|
|
4497
|
+
const rawId = record["assignmentId"];
|
|
4498
|
+
if (typeof rawId !== "string" || !ASSIGNMENT_ID_PATTERN.test(rawId.trim())) {
|
|
4499
|
+
throw new ManagedMcpRequestError("bad-request", "\u53D6\u65AD\u8A00\u8BF7\u6C42\u7684 assignmentId \u4E0D\u5408\u6CD5\u3002");
|
|
4500
|
+
}
|
|
4501
|
+
return { assignmentId: rawId.trim() };
|
|
4502
|
+
}
|
|
4503
|
+
function isManagedMcpRpcEndpoint(endpoint) {
|
|
4504
|
+
return MANAGED_MCP_RPC_ENDPOINTS.includes(endpoint);
|
|
4505
|
+
}
|
|
4506
|
+
function textOrUndefined(value) {
|
|
4507
|
+
return typeof value === "string" && value.trim() !== "" ? value : void 0;
|
|
4508
|
+
}
|
|
4509
|
+
function normalizeAssignment(raw, log) {
|
|
4510
|
+
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) return void 0;
|
|
4511
|
+
const record = raw;
|
|
4512
|
+
const assignmentId = textOrUndefined(record["assignmentId"]);
|
|
4513
|
+
const serverName = textOrUndefined(record["serverName"]);
|
|
4514
|
+
const url = textOrUndefined(record["url"]);
|
|
4515
|
+
const transport = textOrUndefined(record["transport"]);
|
|
4516
|
+
const authRaw = record["auth"];
|
|
4517
|
+
const auth = typeof authRaw === "object" && authRaw !== null && !Array.isArray(authRaw) ? authRaw : void 0;
|
|
4518
|
+
const kind = textOrUndefined(auth?.["kind"]);
|
|
4519
|
+
const mintUrl = textOrUndefined(auth?.["mintUrl"]);
|
|
4520
|
+
if (assignmentId === void 0 || serverName === void 0 || url === void 0 || transport === void 0 || kind === void 0 || mintUrl === void 0) {
|
|
4521
|
+
log?.("hiwork-core: MCP \u6E05\u5355\u91CC\u6709\u4E00\u6761\u7ED3\u6784\u4E0D\u5B8C\u6574\u7684\u5206\u914D\uFF0C\u5DF2\u8DF3\u8FC7");
|
|
4522
|
+
return void 0;
|
|
4523
|
+
}
|
|
4524
|
+
const revision = record["revision"];
|
|
4525
|
+
return {
|
|
4526
|
+
assignmentId,
|
|
4527
|
+
revision: typeof revision === "number" && Number.isFinite(revision) ? revision : 0,
|
|
4528
|
+
slug: textOrUndefined(record["slug"]) ?? serverName,
|
|
4529
|
+
displayName: textOrUndefined(record["displayName"]) ?? serverName,
|
|
4530
|
+
serverName,
|
|
4531
|
+
transport,
|
|
4532
|
+
url,
|
|
4533
|
+
extraHeaders: stringMap(record["extraHeaders"]),
|
|
4534
|
+
auth: { kind, mintUrl, revokeUrl: textOrUndefined(auth?.["revokeUrl"]) ?? "" },
|
|
4535
|
+
updatedAt: textOrUndefined(record["updatedAt"]) ?? ""
|
|
4536
|
+
};
|
|
4537
|
+
}
|
|
4538
|
+
function stringMap(raw) {
|
|
4539
|
+
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) return {};
|
|
4540
|
+
const out = {};
|
|
4541
|
+
for (const [key, value] of Object.entries(raw)) {
|
|
4542
|
+
if (typeof value === "string") out[key] = value;
|
|
4543
|
+
}
|
|
4544
|
+
return out;
|
|
4545
|
+
}
|
|
4546
|
+
function failureOf(error) {
|
|
4547
|
+
if (error instanceof ManagedMcpRequestError) return { code: error.code, message: error.message, details: {} };
|
|
4548
|
+
if (error instanceof GatewayError) {
|
|
4549
|
+
return { code: error.code, message: displayManagedMcpMessage(error.code, error.message), details: {} };
|
|
4550
|
+
}
|
|
4551
|
+
return { code: "internal", message: MANAGED_MCP_GENERIC_FAILURE_MESSAGE, details: {} };
|
|
4552
|
+
}
|
|
4553
|
+
var ManagedMcpStore = class {
|
|
4554
|
+
constructor(deps, now = () => Date.now()) {
|
|
4555
|
+
this.deps = deps;
|
|
4556
|
+
this.now = now;
|
|
4557
|
+
}
|
|
4558
|
+
cached;
|
|
4559
|
+
lastError;
|
|
4560
|
+
inFlight;
|
|
4561
|
+
/**
|
|
4562
|
+
* 当前缓存(同步读,不发请求)。从没成功过返回 undefined;
|
|
4563
|
+
* 最近一次失败会以 `stale + lastError` 标注在上一次成功的结果上。
|
|
4564
|
+
*/
|
|
4565
|
+
peek() {
|
|
4566
|
+
if (this.cached === void 0) return void 0;
|
|
4567
|
+
return this.lastError === void 0 ? this.cached : { ...this.cached, stale: true, lastError: this.lastError };
|
|
4568
|
+
}
|
|
4569
|
+
/**
|
|
4570
|
+
* 去网关拉一次清单并更新缓存。失败时抛异常,且**缓存原样保留**。
|
|
4571
|
+
*
|
|
4572
|
+
* 并发调用合并成一次请求:后台预热与客户端手动同步撞在一起时不必打两遍网关。
|
|
4573
|
+
*/
|
|
4574
|
+
async load() {
|
|
4575
|
+
if (this.inFlight !== void 0) return await this.inFlight;
|
|
4576
|
+
const task = this.fetchOnce();
|
|
4577
|
+
this.inFlight = task;
|
|
4578
|
+
try {
|
|
4579
|
+
return await task;
|
|
4580
|
+
} finally {
|
|
4581
|
+
this.inFlight = void 0;
|
|
4582
|
+
}
|
|
4583
|
+
}
|
|
4584
|
+
async fetchOnce() {
|
|
4585
|
+
try {
|
|
4586
|
+
const sessionCookie = await this.deps.resolveSession();
|
|
4587
|
+
if (sessionCookie === void 0 || sessionCookie === "") {
|
|
4588
|
+
throw new GatewayError("SESSION_EXPIRED", MANAGED_MCP_SESSION_EXPIRED_MESSAGE);
|
|
4589
|
+
}
|
|
4590
|
+
const gateway = this.deps.gateway();
|
|
4591
|
+
if (gateway === void 0) {
|
|
4592
|
+
throw new GatewayError("GATEWAY_UNREACHABLE", "\u7F51\u5173\u5730\u5740\u672A\u914D\u7F6E\uFF0C\u8BF7\u5148\u5728\u300C\u7F51\u5173\u6A21\u578B\u300D\u91CC\u5B8C\u6210\u767B\u5F55");
|
|
4593
|
+
}
|
|
4594
|
+
const view = await gateway.getMyMcpAssignments(sessionCookie);
|
|
4595
|
+
const assignments = [];
|
|
4596
|
+
for (const item of Array.isArray(view?.assignments) ? view.assignments : []) {
|
|
4597
|
+
const normalized = normalizeAssignment(item, this.deps.log);
|
|
4598
|
+
if (normalized !== void 0) assignments.push(normalized);
|
|
4599
|
+
}
|
|
4600
|
+
const snapshot = {
|
|
4601
|
+
revision: typeof view?.revision === "number" && Number.isFinite(view.revision) ? view.revision : 0,
|
|
4602
|
+
fetchedAt: new Date(this.now()).toISOString(),
|
|
4603
|
+
assignments,
|
|
4604
|
+
stale: false
|
|
4605
|
+
};
|
|
4606
|
+
this.cached = snapshot;
|
|
4607
|
+
this.lastError = void 0;
|
|
4608
|
+
return snapshot;
|
|
4609
|
+
} catch (error) {
|
|
4610
|
+
this.lastError = failureOf(error);
|
|
4611
|
+
throw error;
|
|
4612
|
+
}
|
|
4613
|
+
}
|
|
4614
|
+
};
|
|
4615
|
+
function isAborted4(signal) {
|
|
4616
|
+
return signal?.aborted === true;
|
|
4617
|
+
}
|
|
4618
|
+
async function handleManagedMcpEndpoint(endpoint, rawPayload, deps, store, signal) {
|
|
4619
|
+
try {
|
|
4620
|
+
if (isAborted4(signal)) return failure4("cancelled", "MCP \u6E05\u5355\u8BF7\u6C42\u5DF2\u53D6\u6D88\u3002");
|
|
4621
|
+
if (!isManagedMcpRpcEndpoint(endpoint)) {
|
|
4622
|
+
throw new ManagedMcpRequestError("bad-request", `\u672A\u77E5\u7684 MCP \u6E05\u5355\u7AEF\u70B9 '${endpoint}'`);
|
|
4623
|
+
}
|
|
4624
|
+
const payload = parseManagedMcpPayload(endpoint, rawPayload);
|
|
4625
|
+
if (endpoint === "assertion") {
|
|
4626
|
+
const assignmentId = payload.assignmentId;
|
|
4627
|
+
if (assignmentId === void 0) throw new ManagedMcpRequestError("bad-request", "\u53D6\u65AD\u8A00\u8BF7\u6C42\u7F3A\u5C11 assignmentId\u3002");
|
|
4628
|
+
return await handleAssertion(assignmentId, deps, signal);
|
|
4629
|
+
}
|
|
4630
|
+
const cached = store.peek();
|
|
4631
|
+
if (endpoint === "snapshot" && cached !== void 0) return { ok: true, value: cached };
|
|
4632
|
+
return { ok: true, value: await store.load() };
|
|
4633
|
+
} catch (error) {
|
|
4634
|
+
deps.log?.(`hiwork-core: MCP \u6E05\u5355 RPC '${endpoint}' \u5931\u8D25\uFF08${error instanceof Error ? error.name : typeof error}\uFF09`);
|
|
4635
|
+
return { ok: false, error: failureOf(error) };
|
|
4636
|
+
}
|
|
4637
|
+
}
|
|
4638
|
+
async function handleAssertion(assignmentId, deps, signal) {
|
|
4639
|
+
const sessionCookie = await deps.resolveSession();
|
|
4640
|
+
if (sessionCookie === void 0 || sessionCookie === "") {
|
|
4641
|
+
return failure4("SESSION_EXPIRED", MANAGED_MCP_SESSION_EXPIRED_MESSAGE);
|
|
4642
|
+
}
|
|
4643
|
+
const gateway = deps.gateway();
|
|
4644
|
+
if (gateway === void 0) {
|
|
4645
|
+
return failure4("GATEWAY_UNREACHABLE", "\u7F51\u5173\u5730\u5740\u672A\u914D\u7F6E\uFF0C\u8BF7\u5148\u5728\u300C\u7F51\u5173\u6A21\u578B\u300D\u91CC\u5B8C\u6210\u767B\u5F55");
|
|
4646
|
+
}
|
|
4647
|
+
if (isAborted4(signal)) return failure4("cancelled", "MCP \u6E05\u5355\u8BF7\u6C42\u5DF2\u53D6\u6D88\u3002");
|
|
4648
|
+
const ticket = await gateway.issueMyMcpAssertion(sessionCookie, assignmentId);
|
|
4649
|
+
const assertion = textOrUndefined(ticket?.assertion);
|
|
4650
|
+
const mintUrl = textOrUndefined(ticket?.mintUrl);
|
|
4651
|
+
if (assertion === void 0 || mintUrl === void 0) {
|
|
4652
|
+
deps.log?.("hiwork-core: \u53D6\u65AD\u8A00\u6536\u5230\u65E0\u6548\u54CD\u5E94\uFF08\u7F3A assertion / mintUrl\uFF09");
|
|
4653
|
+
return failure4("internal", MANAGED_MCP_GENERIC_FAILURE_MESSAGE);
|
|
4654
|
+
}
|
|
4655
|
+
return {
|
|
4656
|
+
ok: true,
|
|
4657
|
+
value: {
|
|
4658
|
+
assertion,
|
|
4659
|
+
mintUrl,
|
|
4660
|
+
revokeUrl: textOrUndefined(ticket?.revokeUrl) ?? "",
|
|
4661
|
+
expiresAt: textOrUndefined(ticket?.expiresAt) ?? "",
|
|
4662
|
+
serverName: textOrUndefined(ticket?.serverName) ?? ""
|
|
4663
|
+
}
|
|
4664
|
+
};
|
|
4665
|
+
}
|
|
4666
|
+
function failure4(code, message) {
|
|
4667
|
+
return { ok: false, error: { code, message, details: {} } };
|
|
4668
|
+
}
|
|
4669
|
+
function registerManagedMcpRpc(ctx, deps, store) {
|
|
4670
|
+
const handler = (endpoint, payload, signal) => handleManagedMcpEndpoint(endpoint, payload, deps, store, signal);
|
|
4671
|
+
const remove = ctx.connection.rpc.handle(MANAGED_MCP_RPC_CHANNEL, handler, { authority: "loopback" });
|
|
4672
|
+
return async () => {
|
|
4673
|
+
await remove();
|
|
4674
|
+
};
|
|
4675
|
+
}
|
|
4676
|
+
|
|
4410
4677
|
// src/index.ts
|
|
4411
4678
|
var name = "hiwork-core";
|
|
4412
4679
|
var inject = ["settings", "credentials", "workspaceRegistry", "connection"];
|
|
@@ -4762,6 +5029,34 @@ function apply(ctx, config) {
|
|
|
4762
5029
|
ctx.logger.warn(message);
|
|
4763
5030
|
}
|
|
4764
5031
|
}), "hiwork-core: daily rpc");
|
|
5032
|
+
const managedMcpDeps = {
|
|
5033
|
+
resolveSession: async () => (await ports.credentials.resolve(SESSION_REF))?.value,
|
|
5034
|
+
gateway: () => {
|
|
5035
|
+
const baseURL = current().baseURL;
|
|
5036
|
+
return baseURL === void 0 || baseURL === "" ? void 0 : gatewayFor(baseURL);
|
|
5037
|
+
},
|
|
5038
|
+
log: (message) => {
|
|
5039
|
+
ctx.logger.warn(message);
|
|
5040
|
+
}
|
|
5041
|
+
};
|
|
5042
|
+
const managedMcp = new ManagedMcpStore(managedMcpDeps);
|
|
5043
|
+
ctx.effect(
|
|
5044
|
+
() => registerManagedMcpRpc({ connection, logger: ctx.logger }, managedMcpDeps, managedMcp),
|
|
5045
|
+
"hiwork-core: managed mcp rpc"
|
|
5046
|
+
);
|
|
5047
|
+
ctx.effect(() => {
|
|
5048
|
+
const warm = () => {
|
|
5049
|
+
void managedMcp.load().catch((error) => {
|
|
5050
|
+
ctx.logger.warn(`hiwork-core: \u53D7\u7BA1 MCP \u6E05\u5355\u9884\u70ED\u5931\u8D25\uFF08${error instanceof Error ? error.name : typeof error}\uFF09`);
|
|
5051
|
+
});
|
|
5052
|
+
};
|
|
5053
|
+
warm();
|
|
5054
|
+
const timer = setInterval(warm, MANAGED_MCP_REFRESH_INTERVAL_MS);
|
|
5055
|
+
timer.unref?.();
|
|
5056
|
+
return () => {
|
|
5057
|
+
clearInterval(timer);
|
|
5058
|
+
};
|
|
5059
|
+
}, "hiwork-core: managed mcp warmup");
|
|
4765
5060
|
ctx.effect(() => registerAppVersionRpc({ connection, logger: ctx.logger }, {
|
|
4766
5061
|
appVersion: () => pushedAppVersion(),
|
|
4767
5062
|
log: (message) => {
|