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.
- package/lib/ai-router-client.js +55 -0
- package/lib/client-policy.js +13 -1
- package/lib/client.js +314 -23
- package/lib/index.js +310 -4
- 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/controller.d.ts +13 -0
- package/lib/types/client/daily-card.d.ts +32 -1
- package/lib/types/client/managed-mcp.d.ts +133 -0
- package/lib/types/client/session-cost.d.ts +19 -13
- package/lib/types/client/styles.d.ts +10 -2
- package/lib/types/client/types.d.ts +7 -1
- package/lib/types/client-policy.d.ts +8 -0
- package/lib/types/index.d.ts +12 -3
- 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-policy.js
CHANGED
|
@@ -15,6 +15,8 @@ export const DEFAULT_LOGIN_MODE = 'both';
|
|
|
15
15
|
export const CLIENT_POLICY_PATH = '/api/client/policy';
|
|
16
16
|
/** 策略拉取默认超时:免登录只读接口,超过 5s 视同不可达(fail-open)。 */
|
|
17
17
|
export const CLIENT_POLICY_TIMEOUT_MS = 5_000;
|
|
18
|
+
/** fail-open 缺省:旧网关不返回 passwordLogin 时,密码表单仍视作可用。 */
|
|
19
|
+
export const DEFAULT_PASSWORD_LOGIN = true;
|
|
18
20
|
/**
|
|
19
21
|
* 拼策略 URL:`{baseURL}/api/client/policy?platform=&arch=&version=&device_id=`。
|
|
20
22
|
* 参数固定顺序、逐个 `encodeURIComponent`(device_id 可能是任意 ASCII,含 `+/=` 也必须安全);
|
|
@@ -47,6 +49,16 @@ function parseLoginMode(payload) {
|
|
|
47
49
|
const value = payload.loginMode;
|
|
48
50
|
return value === 'browser' || value === 'form' || value === 'both' ? value : undefined;
|
|
49
51
|
}
|
|
52
|
+
/**
|
|
53
|
+
* 解析 `passwordLogin`:只认布尔值;缺失 / 非法值一律 {@link DEFAULT_PASSWORD_LOGIN}
|
|
54
|
+
* (fail-open:旧网关不返回该字段,不能因此收起密码入口)。
|
|
55
|
+
*/
|
|
56
|
+
function parsePasswordLogin(payload) {
|
|
57
|
+
if (typeof payload !== 'object' || payload === null)
|
|
58
|
+
return DEFAULT_PASSWORD_LOGIN;
|
|
59
|
+
const value = payload.passwordLogin;
|
|
60
|
+
return typeof value === 'boolean' ? value : DEFAULT_PASSWORD_LOGIN;
|
|
61
|
+
}
|
|
50
62
|
/**
|
|
51
63
|
* 拉取并解析客户端策略。**永不抛异常**:任何失败都返回 undefined(fail-open)。
|
|
52
64
|
*
|
|
@@ -73,7 +85,7 @@ export async function fetchClientPolicy(opts) {
|
|
|
73
85
|
return undefined;
|
|
74
86
|
}
|
|
75
87
|
const loginMode = parseLoginMode(payload);
|
|
76
|
-
return loginMode === undefined ? undefined : { loginMode };
|
|
88
|
+
return loginMode === undefined ? undefined : { loginMode, passwordLogin: parsePasswordLogin(payload) };
|
|
77
89
|
}
|
|
78
90
|
catch {
|
|
79
91
|
// 网络异常 / 超时(AbortSignal.timeout 抛 TimeoutError)/ 非法 URL:一律 fail-open。
|
package/lib/client.js
CHANGED
|
@@ -78,7 +78,11 @@ var textInput = {
|
|
|
78
78
|
var frameSurfaceSelector = '[class*="_frame"]:has(> [class*="_sidebarCol"])';
|
|
79
79
|
var frameCss = [
|
|
80
80
|
`${frameSurfaceSelector} { background: var(--dsw-specific-sidebar-fill, #f9fafb); }`,
|
|
81
|
+
// 原生会话区根节点:两代链路并存,见上方注释。
|
|
81
82
|
`html[data-dsh-shell="win32"] [class*="_centerCol"] > div > [class*="_root"] { border-top-left-radius: 16px; }`,
|
|
83
|
+
`html[data-dsh-shell="win32"] [class*="_centerCol"] [data-slot="main.conversation"] > [class*="_root"] { border-top-left-radius: 16px; }`,
|
|
84
|
+
// HiWork 扩展页 takeover 根节点(中心列直接子元素)。
|
|
85
|
+
`html[data-dsh-shell="win32"] [class*="_centerCol"] > [data-hiwork-feature-root] { border-top-left-radius: 16px; }`,
|
|
82
86
|
`[class*="_sidebarCol"] { border-right: 0 !important; }`
|
|
83
87
|
].join("\n");
|
|
84
88
|
var hostHideCss = [
|
|
@@ -125,6 +129,9 @@ function authorizeUrlOf(value) {
|
|
|
125
129
|
function browserLoginPendingOf(value) {
|
|
126
130
|
return authorizeUrlOf(value) !== void 0 || value?.browserLoginRequest !== void 0;
|
|
127
131
|
}
|
|
132
|
+
function passwordLoginOf(value) {
|
|
133
|
+
return value?.passwordLogin !== false;
|
|
134
|
+
}
|
|
128
135
|
function browserLoginViewOf(value) {
|
|
129
136
|
const loginMode = normalizeLoginMode(value?.loginMode);
|
|
130
137
|
if (loginMode === "form") return { loginMode, browserLoginUrl: void 0, browserLoginPending: false };
|
|
@@ -162,7 +169,8 @@ function project(value) {
|
|
|
162
169
|
budgets: value?.budgets,
|
|
163
170
|
keys: value?.keys,
|
|
164
171
|
activeKeyId: value?.keyId ?? value?.keys?.[0]?.id,
|
|
165
|
-
...browserLoginViewOf(value)
|
|
172
|
+
...browserLoginViewOf(value),
|
|
173
|
+
passwordLogin: passwordLoginOf(value)
|
|
166
174
|
};
|
|
167
175
|
if (value?.pendingLogin !== void 0) return { ...base, status: "logging-in" };
|
|
168
176
|
if (value?.lastLogin !== void 0) {
|
|
@@ -189,6 +197,7 @@ var AiRouterCardController = class {
|
|
|
189
197
|
keys: void 0,
|
|
190
198
|
activeKeyId: void 0,
|
|
191
199
|
loginMode: "both",
|
|
200
|
+
passwordLogin: true,
|
|
192
201
|
browserLoginUrl: void 0,
|
|
193
202
|
browserLoginPending: false
|
|
194
203
|
});
|
|
@@ -1016,12 +1025,15 @@ function removeStaleHeroGreeting(doc) {
|
|
|
1016
1025
|
|
|
1017
1026
|
// src/client/daily-card.ts
|
|
1018
1027
|
var DAILY_CARD_STYLE_ID = "dcu-daily-card-style";
|
|
1028
|
+
var DAILY_MAIN_FONT_SIZE_MAX = 64;
|
|
1029
|
+
var DAILY_MAIN_FONT_SIZE_MIN = 24;
|
|
1019
1030
|
var DAILY_CARD_STYLE = `
|
|
1020
1031
|
[data-dcu-daily-card]{box-sizing:border-box;display:flex;flex-direction:column;align-items:center;width:100%;max-width:min(880px,100%);margin:6px auto 44px;cursor:pointer;user-select:none;text-align:center}
|
|
1021
1032
|
[data-dcu-daily-prefix]{display:inline-flex;align-items:center;gap:14px;font-size:20px;line-height:1.4;font-weight:500;letter-spacing:.22em;text-indent:.22em;color:rgba(24,24,27,.35);white-space:nowrap}
|
|
1022
1033
|
[data-dcu-daily-prefix]::before,[data-dcu-daily-prefix]::after{content:"";display:inline-block;width:44px;height:2px;border-radius:1px;background:currentColor;opacity:.5}
|
|
1023
1034
|
[data-dcu-daily-prefix][hidden]{display:none}
|
|
1024
|
-
[data-dcu-daily-main]{margin-top:6px;font-size
|
|
1035
|
+
[data-dcu-daily-main]{margin-top:6px;max-width:100%;overflow:hidden;white-space:nowrap;text-overflow:ellipsis;font-size:${DAILY_MAIN_FONT_SIZE_MAX}px;line-height:1.25;font-weight:650;letter-spacing:.005em;background:linear-gradient(100deg,#2f6bf0,#1d4ed8 45%,#0e7fc2);-webkit-background-clip:text;background-clip:text;color:transparent;transition:filter .18s ease}
|
|
1036
|
+
[data-dcu-daily-main][data-dcu-daily-truncated]{text-align:left}
|
|
1025
1037
|
[data-dcu-daily-card]:hover [data-dcu-daily-main]{filter:brightness(1.12)}
|
|
1026
1038
|
[data-dcu-daily-card]:focus-visible{outline:2px solid var(--dsw-alias-button-info-fill,#4c8dff);outline-offset:4px;border-radius:6px}
|
|
1027
1039
|
[data-dcu-daily-shuffle]{flex:none;display:inline-flex;align-items:center;justify-content:center;gap:5px;margin-top:10px;padding:3px 10px;border-radius:8px;color:var(--dsw-alias-label-tertiary,#71717a);font-size:13px;line-height:20px;letter-spacing:.08em;text-indent:.08em;cursor:pointer;transition:color .18s ease}
|
|
@@ -1060,6 +1072,31 @@ function ensureDailyCardStyle(doc) {
|
|
|
1060
1072
|
style.textContent = DAILY_CARD_STYLE;
|
|
1061
1073
|
doc.head.append(style);
|
|
1062
1074
|
}
|
|
1075
|
+
function solveDailyMainFontSize(availableWidth, measure) {
|
|
1076
|
+
if (!(availableWidth > 0)) return null;
|
|
1077
|
+
if (measure(DAILY_MAIN_FONT_SIZE_MAX) <= availableWidth) return null;
|
|
1078
|
+
if (measure(DAILY_MAIN_FONT_SIZE_MIN) > availableWidth) return DAILY_MAIN_FONT_SIZE_MIN;
|
|
1079
|
+
let low = DAILY_MAIN_FONT_SIZE_MIN;
|
|
1080
|
+
let high = DAILY_MAIN_FONT_SIZE_MAX;
|
|
1081
|
+
while (high - low > 1) {
|
|
1082
|
+
const mid = Math.floor((low + high) / 2);
|
|
1083
|
+
if (measure(mid) <= availableWidth) low = mid;
|
|
1084
|
+
else high = mid;
|
|
1085
|
+
}
|
|
1086
|
+
return low;
|
|
1087
|
+
}
|
|
1088
|
+
function fitDailyMainText(card) {
|
|
1089
|
+
const main = card.querySelector("[data-dcu-daily-main]");
|
|
1090
|
+
if (main === null) return null;
|
|
1091
|
+
const availableWidth = card.clientWidth;
|
|
1092
|
+
const size = solveDailyMainFontSize(availableWidth, (fontSize) => {
|
|
1093
|
+
main.style.fontSize = `${fontSize}px`;
|
|
1094
|
+
return main.scrollWidth;
|
|
1095
|
+
});
|
|
1096
|
+
main.style.fontSize = size === null ? "" : `${size}px`;
|
|
1097
|
+
main.toggleAttribute("data-dcu-daily-truncated", size === DAILY_MAIN_FONT_SIZE_MIN && main.scrollWidth > availableWidth);
|
|
1098
|
+
return size;
|
|
1099
|
+
}
|
|
1063
1100
|
function findDailyCardMount(doc) {
|
|
1064
1101
|
const hero = doc.querySelector('[data-phase="hero"]');
|
|
1065
1102
|
if (hero === null) return null;
|
|
@@ -1068,6 +1105,7 @@ function findDailyCardMount(doc) {
|
|
|
1068
1105
|
return headline;
|
|
1069
1106
|
}
|
|
1070
1107
|
var modalKeyHandlers = /* @__PURE__ */ new WeakMap();
|
|
1108
|
+
var cardHandles = /* @__PURE__ */ new WeakMap();
|
|
1071
1109
|
function modalHeadline(item) {
|
|
1072
1110
|
return item.title !== "" ? item.title : item.content;
|
|
1073
1111
|
}
|
|
@@ -1107,6 +1145,36 @@ function buildCard(doc, initial, date, quotes) {
|
|
|
1107
1145
|
shuffle.setAttribute("aria-label", "\u6362\u4E00\u53E5");
|
|
1108
1146
|
shuffle.append(createRefreshIcon(doc), doc.createTextNode("\u6362\u4E00\u53E5"));
|
|
1109
1147
|
root.append(prefix, main, shuffle);
|
|
1148
|
+
let lastFitWidth = -1;
|
|
1149
|
+
let fitFrame;
|
|
1150
|
+
const fit = (reflow = false) => {
|
|
1151
|
+
const width = root.clientWidth;
|
|
1152
|
+
if (!reflow && width === lastFitWidth) return;
|
|
1153
|
+
lastFitWidth = width;
|
|
1154
|
+
fitDailyMainText(root);
|
|
1155
|
+
};
|
|
1156
|
+
const resizeObserver = typeof ResizeObserver === "function" ? new ResizeObserver(() => {
|
|
1157
|
+
if (fitFrame !== void 0) return;
|
|
1158
|
+
fitFrame = window.requestAnimationFrame(() => {
|
|
1159
|
+
fitFrame = void 0;
|
|
1160
|
+
fit();
|
|
1161
|
+
});
|
|
1162
|
+
}) : null;
|
|
1163
|
+
if (resizeObserver !== null) resizeObserver.observe(root);
|
|
1164
|
+
void doc.fonts?.ready.then(() => {
|
|
1165
|
+
fit(true);
|
|
1166
|
+
}).catch(() => {
|
|
1167
|
+
});
|
|
1168
|
+
cardHandles.set(root, {
|
|
1169
|
+
fit,
|
|
1170
|
+
dispose: () => {
|
|
1171
|
+
if (resizeObserver !== null) resizeObserver.disconnect();
|
|
1172
|
+
if (fitFrame !== void 0) {
|
|
1173
|
+
window.cancelAnimationFrame(fitFrame);
|
|
1174
|
+
fitFrame = void 0;
|
|
1175
|
+
}
|
|
1176
|
+
}
|
|
1177
|
+
});
|
|
1110
1178
|
let current = initial;
|
|
1111
1179
|
const applyItem = (next) => {
|
|
1112
1180
|
current = next;
|
|
@@ -1114,6 +1182,7 @@ function buildCard(doc, initial, date, quotes) {
|
|
|
1114
1182
|
prefix.hidden = next.subtitle === "";
|
|
1115
1183
|
main.textContent = next.content;
|
|
1116
1184
|
root.setAttribute("aria-label", `\u6BCF\u65E5\u4E00\u53E5\uFF1A${modalHeadline(next)}`);
|
|
1185
|
+
fit(true);
|
|
1117
1186
|
};
|
|
1118
1187
|
applyItem(initial);
|
|
1119
1188
|
root.addEventListener("click", (e) => {
|
|
@@ -1228,12 +1297,16 @@ function decorateDailyCard(doc, quotes) {
|
|
|
1228
1297
|
removeStaleDailyCard(doc);
|
|
1229
1298
|
const card = buildCard(doc, view, view.date, quotes);
|
|
1230
1299
|
parent.insertBefore(card, anchor);
|
|
1300
|
+
cardHandles.get(card)?.fit(true);
|
|
1231
1301
|
}).catch(() => {
|
|
1232
1302
|
});
|
|
1233
1303
|
}
|
|
1234
1304
|
function removeStaleDailyCard(doc) {
|
|
1235
1305
|
closeDailyModal(doc);
|
|
1236
|
-
for (const stale of Array.from(doc.querySelectorAll("[data-dcu-daily-card]")))
|
|
1306
|
+
for (const stale of Array.from(doc.querySelectorAll("[data-dcu-daily-card]"))) {
|
|
1307
|
+
cardHandles.get(stale)?.dispose();
|
|
1308
|
+
stale.remove();
|
|
1309
|
+
}
|
|
1237
1310
|
}
|
|
1238
1311
|
function observeHeroDecorations(doc, quotes) {
|
|
1239
1312
|
if (doc.head === null) return () => {
|
|
@@ -1450,9 +1523,10 @@ function LoginGateModal(props) {
|
|
|
1450
1523
|
}, [props.browserLoginUrl]);
|
|
1451
1524
|
const showBrowserLogin = props.loginMode !== "form";
|
|
1452
1525
|
const waiting = showBrowserLogin && props.browserLoginPending;
|
|
1453
|
-
const
|
|
1526
|
+
const passwordFormOffered = props.passwordLogin || props.loginMode === "form";
|
|
1527
|
+
const formVisible = props.loginMode === "form" || passwordFormOffered && formOpen;
|
|
1454
1528
|
const noKey = props.lastError?.code === "NO_KEY_ASSIGNED";
|
|
1455
|
-
const error = describeLoginError(props.lastError) ?? submitError;
|
|
1529
|
+
const error = (props.lastError?.code === "AUTH_FAILED" && !passwordFormOffered ? "\u5F53\u524D\u7F51\u5173\u5DF2\u542F\u7528\u7EDF\u4E00\u8BA4\u8BC1\uFF0C\u8BF7\u70B9\u51FB\u300C\u4F7F\u7528\u6D4F\u89C8\u5668\u767B\u5F55\u300D\u5B8C\u6210\u767B\u5F55" : describeLoginError(props.lastError)) ?? submitError;
|
|
1456
1530
|
const loginDisabled = props.loggingIn || username === "" || password === "";
|
|
1457
1531
|
const submit = () => {
|
|
1458
1532
|
if (loginDisabled) return;
|
|
@@ -1486,7 +1560,7 @@ function LoginGateModal(props) {
|
|
|
1486
1560
|
] }),
|
|
1487
1561
|
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("button", { "data-hiwork-core-brand": true, type: "submit", style: loginDisabled ? loginButtonDisabled : loginButton, disabled: loginDisabled, children: props.loggingIn ? "\u767B\u5F55\u4E2D\u2026" : "\u767B \u5F55" }),
|
|
1488
1562
|
noKey ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("button", { "data-hiwork-core-secondary": true, type: "button", style: linkButton, onClick: () => void props.scope.set("refreshRequest", { at: (/* @__PURE__ */ new Date()).toISOString() }), children: "\u6211\u5DF2\u5206\u914D KEY\uFF0C\u5237\u65B0" }) : null
|
|
1489
|
-
] }) : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("button", { "data-hiwork-core-secondary": true, "data-hiwork-core-form-toggle": true, type: "button", style: toggleStyle, onClick: () => setFormOpen(true), children: props.loginMode === "browser" ? "\u6D4F\u89C8\u5668\u767B\u5F55\u4E0D\u53EF\u7528\uFF1F\u4F7F\u7528\u8D26\u53F7\u5BC6\u7801\u767B\u5F55" : "\u4F7F\u7528\u8D26\u53F7\u5BC6\u7801\u767B\u5F55" });
|
|
1563
|
+
] }) : !passwordFormOffered ? null : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("button", { "data-hiwork-core-secondary": true, "data-hiwork-core-form-toggle": true, type: "button", style: toggleStyle, onClick: () => setFormOpen(true), children: props.loginMode === "browser" ? "\u6D4F\u89C8\u5668\u767B\u5F55\u4E0D\u53EF\u7528\uFF1F\u4F7F\u7528\u8D26\u53F7\u5BC6\u7801\u767B\u5F55" : "\u4F7F\u7528\u8D26\u53F7\u5BC6\u7801\u767B\u5F55" });
|
|
1490
1564
|
return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { style: overlayStyle, children: [
|
|
1491
1565
|
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(OceanWaves, {}),
|
|
1492
1566
|
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("main", { id: "hiwork-core-frame", "data-hiwork-core-frame": true, style: contentFrameStyle, children: [
|
|
@@ -1553,7 +1627,7 @@ function mountLoginGate(scope, storeCredential) {
|
|
|
1553
1627
|
root = (0, import_client.createRoot)(gateContainer);
|
|
1554
1628
|
if (appRoot !== null) appRoot.inert = true;
|
|
1555
1629
|
}
|
|
1556
|
-
root.render(/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(LoginGateModal, { scope, storeCredential, lastError: value?.lastError, loggingIn: value?.pendingLogin !== void 0, initialUsername: value?.username, initialRememberPassword: value?.rememberPassword === true, ...browserLoginViewOf(value) }));
|
|
1630
|
+
root.render(/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(LoginGateModal, { scope, storeCredential, lastError: value?.lastError, loggingIn: value?.pendingLogin !== void 0, initialUsername: value?.username, initialRememberPassword: value?.rememberPassword === true, passwordLogin: passwordLoginOf(value), ...browserLoginViewOf(value) }));
|
|
1557
1631
|
};
|
|
1558
1632
|
render();
|
|
1559
1633
|
const dispose = scope.subscribe(() => render());
|
|
@@ -2120,8 +2194,15 @@ function rfc3339Of(time) {
|
|
|
2120
2194
|
const date = new Date(time);
|
|
2121
2195
|
return Number.isNaN(date.getTime()) ? void 0 : date.toISOString();
|
|
2122
2196
|
}
|
|
2123
|
-
function
|
|
2124
|
-
|
|
2197
|
+
function closingTurnTailData(snapshot, messageId) {
|
|
2198
|
+
for (const key of snapshot.order) {
|
|
2199
|
+
const node = snapshot.nodes.get(key);
|
|
2200
|
+
if (node === void 0 || node.kind !== "turn-tail") continue;
|
|
2201
|
+
const data = node.data;
|
|
2202
|
+
if (data === void 0) continue;
|
|
2203
|
+
if (data.closing?.finalNode?.messageId === messageId) return data;
|
|
2204
|
+
}
|
|
2205
|
+
return void 0;
|
|
2125
2206
|
}
|
|
2126
2207
|
function sessionCostKey(sessionId, match) {
|
|
2127
2208
|
return `${sessionId}\0${match.turn}\0${JSON.stringify(match.items)}`;
|
|
@@ -2178,30 +2259,35 @@ var SessionCostStore = class {
|
|
|
2178
2259
|
}
|
|
2179
2260
|
};
|
|
2180
2261
|
var costStyle = {
|
|
2262
|
+
// 字号/颜色与同一行的「用量」「用时」胶囊同源变量,保证在动作行里不显得突兀。
|
|
2181
2263
|
color: textTertiary,
|
|
2182
|
-
fontSize:
|
|
2264
|
+
fontSize: "var(--dsh-content-font-size-secondary, 13px)",
|
|
2183
2265
|
lineHeight: "18px",
|
|
2184
2266
|
whiteSpace: "nowrap",
|
|
2185
2267
|
cursor: "default"
|
|
2186
2268
|
};
|
|
2187
|
-
function SessionCost({
|
|
2188
|
-
const
|
|
2189
|
-
const
|
|
2269
|
+
function SessionCost({ messageId, sessionId, useChat, store }) {
|
|
2270
|
+
const data = useChat((snapshot) => closingTurnTailData(snapshot, messageId));
|
|
2271
|
+
const match = sessionCostMatchOf(data);
|
|
2272
|
+
const key = match === null ? null : sessionCostKey(sessionId, match);
|
|
2273
|
+
const [shown, setShown] = (0, import_react6.useState)(void 0);
|
|
2190
2274
|
(0, import_react6.useEffect)(() => {
|
|
2275
|
+
if (match === null || key === null) return;
|
|
2191
2276
|
let disposed = false;
|
|
2192
2277
|
const controller = new AbortController();
|
|
2193
|
-
void store.estimate({ sessionId, turn:
|
|
2194
|
-
if (!disposed)
|
|
2278
|
+
void store.estimate({ sessionId, turn: match.turn, items: match.items, signal: controller.signal }).then((value) => {
|
|
2279
|
+
if (!disposed) setShown({ key, result: value });
|
|
2195
2280
|
}).catch(() => {
|
|
2196
|
-
if (!disposed)
|
|
2281
|
+
if (!disposed) setShown({ key, result: void 0 });
|
|
2197
2282
|
});
|
|
2198
2283
|
return () => {
|
|
2199
2284
|
disposed = true;
|
|
2200
2285
|
controller.abort();
|
|
2201
2286
|
};
|
|
2202
2287
|
}, [store, key]);
|
|
2203
|
-
if (result === void 0) return null;
|
|
2204
|
-
const
|
|
2288
|
+
if (match === null || key === null || shown === void 0 || shown.key !== key || shown.result === void 0) return null;
|
|
2289
|
+
const result = shown.result;
|
|
2290
|
+
const estimated = !result.channelResolved || match.ambiguous;
|
|
2205
2291
|
return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { style: costStyle, "data-hiwork-session-cost": estimated ? "estimated" : "exact", title: SESSION_COST_HINT, children: `\u2248 ${formatYuan(result.cost)}${estimated ? SESSION_COST_ESTIMATED_SUFFIX : ""}` });
|
|
2206
2292
|
}
|
|
2207
2293
|
|
|
@@ -2726,6 +2812,198 @@ function mountFeatureCenter(options) {
|
|
|
2726
2812
|
// src/client/contracts.ts
|
|
2727
2813
|
var HIWORK_FEATURE_CENTER_SERVICE = "hiworkFeatureCenter";
|
|
2728
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
|
+
|
|
2729
3007
|
// src/client/index.ts
|
|
2730
3008
|
var inject = ["slots", "locale", "remote", "remote.credentials", "settingsScope", "connection"];
|
|
2731
3009
|
var INTERACTION_STYLE_ID = "hiwork-core-interaction-styles";
|
|
@@ -2763,6 +3041,7 @@ function apply(ctx) {
|
|
|
2763
3041
|
const usage = new UsageDataStore(connection.rpc);
|
|
2764
3042
|
const sessionCost = new SessionCostStore(connection.rpc);
|
|
2765
3043
|
const dailyQuotes = new DailyDataStore(connection.rpc);
|
|
3044
|
+
const managedMcp = new ManagedMcpStore(connection.rpc);
|
|
2766
3045
|
const appVersion = createAppVersion(connection.rpc);
|
|
2767
3046
|
ctx.slots.inject("settings.section", () => ctx.slots.register({
|
|
2768
3047
|
name: "settings.section",
|
|
@@ -2771,14 +3050,16 @@ function apply(ctx) {
|
|
|
2771
3050
|
label: () => "\u7528\u91CF\u4E0E\u6210\u672C",
|
|
2772
3051
|
inject: () => ({ hooks: controller.inject().hooks, usage })
|
|
2773
3052
|
}, UsageSection));
|
|
2774
|
-
ctx.slots.inject("conversation.chat.
|
|
2775
|
-
name: "conversation.chat.
|
|
2776
|
-
|
|
3053
|
+
ctx.slots.inject("conversation.chat.assistant-actions", () => ctx.slots.register({
|
|
3054
|
+
name: "conversation.chat.assistant-actions",
|
|
3055
|
+
id: "hiwork-session-cost",
|
|
3056
|
+
order: 20,
|
|
2777
3057
|
inject: () => ({ store: sessionCost })
|
|
2778
3058
|
}, SessionCost));
|
|
2779
3059
|
ctx.effect(() => observeAccountChange(controller.inject().hooks.aiRouterCard, () => {
|
|
2780
3060
|
usage.clear();
|
|
2781
3061
|
sessionCost.clear();
|
|
3062
|
+
managedMcp.clear();
|
|
2782
3063
|
}), "hiwork-core: \u7528\u91CF\u7F13\u5B58\u968F\u8D26\u53F7 / Key \u53D8\u5316\u6E05\u7A7A");
|
|
2783
3064
|
ctx.effect(() => observeAboutNavIcon(), "hiwork-core: about nav icon");
|
|
2784
3065
|
ctx.slots.inject("sidebar.brand.mark", () => ctx.slots.register({
|
|
@@ -2797,9 +3078,9 @@ function apply(ctx) {
|
|
|
2797
3078
|
}, UserBadge));
|
|
2798
3079
|
ctx.effect(() => mountLoginGate(scope, storeCredential), "hiwork-core: login gate");
|
|
2799
3080
|
ctx.effect(() => observeHeroDecorations(document, dailyQuotes), "hiwork-core: hero decorations");
|
|
2800
|
-
installFeatureCenter(ctx);
|
|
3081
|
+
installFeatureCenter(ctx, managedMcp);
|
|
2801
3082
|
}
|
|
2802
|
-
function installFeatureCenter(ctx) {
|
|
3083
|
+
function installFeatureCenter(ctx, managedMcp) {
|
|
2803
3084
|
const featureRegistry = createFeatureRegistry();
|
|
2804
3085
|
let center;
|
|
2805
3086
|
const sessions = ctx.get("sessions");
|
|
@@ -2818,6 +3099,16 @@ function installFeatureCenter(ctx) {
|
|
|
2818
3099
|
return;
|
|
2819
3100
|
}
|
|
2820
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)
|
|
2821
3112
|
}
|
|
2822
3113
|
};
|
|
2823
3114
|
ctx.provide(HIWORK_FEATURE_CENTER_SERVICE, face);
|