hiwork-core 0.0.35 → 0.0.38
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/browser-login.js +42 -16
- package/lib/client.js +61 -119
- package/lib/identity-context.js +50 -0
- package/lib/index.js +176 -70
- package/lib/types/browser-login.d.ts +10 -1
- package/lib/types/client/daily-data.d.ts +31 -3
- package/lib/types/client/menu-preferences.d.ts +4 -3
- package/lib/types/client/styles.d.ts +5 -4
- package/lib/types/client/user-badge.d.ts +7 -2
- package/lib/types/identity-context.d.ts +54 -0
- package/lib/types/index.d.ts +29 -3
- package/lib/types/provision-session.d.ts +2 -0
- package/package.json +26 -25
package/lib/browser-login.js
CHANGED
|
@@ -146,12 +146,23 @@ export async function startBrowserLogin(opts) {
|
|
|
146
146
|
}
|
|
147
147
|
return { authorizeUrl, state };
|
|
148
148
|
}
|
|
149
|
-
/**
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
149
|
+
/**
|
|
150
|
+
* 把失败写进状态并结束等待态(清 browserLogin 与请求字段)。
|
|
151
|
+
*
|
|
152
|
+
* ⚠️ 写回可能失败(settings 尚未可写 / entry 被 HMR 摘掉):那样等待态不会结束,界面会停在
|
|
153
|
+
* 「等待完成授权」而没有任何线索。这里不静默吞掉——`state.patch` 已带退避重试,还能抛出来
|
|
154
|
+
* 说明重试窗口也用尽了。
|
|
155
|
+
*/
|
|
156
|
+
async function failBrowserLogin(state, loginError, log) {
|
|
157
|
+
try {
|
|
158
|
+
await state.patch({
|
|
159
|
+
set: { lastError: loginError },
|
|
160
|
+
unset: ['browserLoginRequest', 'browserLogin'],
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
catch (error) {
|
|
164
|
+
log(`hiwork-core: 浏览器授权失败态写回被拒,界面可能停在「等待完成授权」(${error instanceof Error ? error.message : typeof error})`);
|
|
165
|
+
}
|
|
155
166
|
return loginError;
|
|
156
167
|
}
|
|
157
168
|
/**
|
|
@@ -161,17 +172,32 @@ async function failBrowserLogin(state, loginError) {
|
|
|
161
172
|
* `browserLogin` 是持久化的。宿主一旦重启(应用重开、DSH 崩溃重启、开发期 HMR),
|
|
162
173
|
* 两者就永久错位:所有回调都被 `completeBrowserLogin` 的「没有待完成的授权请求」丢弃,
|
|
163
174
|
* 界面却仍按持久化的 `browserLogin` 停在「等待完成授权」,用户只能靠「取消」脱困。
|
|
175
|
+
* 更糟的是登录门挂载时会拿这个**已经死掉的**授权 URL 自动打开浏览器,用户无论怎么
|
|
176
|
+
* 在浏览器里完成授权都回不来——这正是「浏览器打开了但授权回不到桌面端」的成因。
|
|
164
177
|
*
|
|
165
178
|
* 因此启动即把这种**不可能完成**的等待态收尾成失败:写一条 lastError 并清掉等待字段,
|
|
166
179
|
* 让用户回到登录入口重新发起。没有等待态时不动任何状态。
|
|
180
|
+
*
|
|
181
|
+
* ⚠️ **返回值必须被检查,调用方必须重试**:`apply()` 跑在任何 settings 写入可用之前,
|
|
182
|
+
* 此时写回会被 `settings namespace "<ns>" is not registered` 拒绝(实测 1.0.8 桌面端:
|
|
183
|
+
* apply 时刻命名空间尚未注册,约 3.7s 后才可写)。捕获后吞掉这个拒绝,等于等待态永远
|
|
184
|
+
* 清不掉——本函数原先正是 `.catch(() => {})` + 单次调用,缺陷即由此而来。
|
|
185
|
+
*
|
|
186
|
+
* @returns `true` = 已收尾(原本就没有等待态,或已成功清掉);`false` = 写回被拒,需重试。
|
|
167
187
|
*/
|
|
168
188
|
export async function reconcileStaleBrowserLogin(state, value) {
|
|
169
189
|
if (value.browserLogin === undefined)
|
|
170
|
-
return;
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
190
|
+
return true;
|
|
191
|
+
try {
|
|
192
|
+
await state.patch({
|
|
193
|
+
set: { lastError: { code: 'AUTH_FAILED', message: '上次浏览器授权未完成,请重新登录' } },
|
|
194
|
+
unset: ['browserLoginRequest', 'browserLogin'],
|
|
195
|
+
});
|
|
196
|
+
return true;
|
|
197
|
+
}
|
|
198
|
+
catch {
|
|
199
|
+
return false;
|
|
200
|
+
}
|
|
175
201
|
}
|
|
176
202
|
/**
|
|
177
203
|
* 处理 `hiwork://auth/callback`:校验 state → 兑换一次性 code → 写会话凭据 → 复用供给尾部。
|
|
@@ -201,20 +227,20 @@ export async function completeBrowserLogin(input, opts) {
|
|
|
201
227
|
try {
|
|
202
228
|
if (now() - pending.startedAt > BROWSER_LOGIN_TTL_MS) {
|
|
203
229
|
log('hiwork-core: 浏览器授权回调已超时(超过 10 分钟),已拒绝');
|
|
204
|
-
return await failBrowserLogin(opts.state, { code: 'AUTH_FAILED', message: '授权已超时,请重新登录' });
|
|
230
|
+
return await failBrowserLogin(opts.state, { code: 'AUTH_FAILED', message: '授权已超时,请重新登录' }, log);
|
|
205
231
|
}
|
|
206
232
|
if (typeof input.error === 'string' && input.error !== '') {
|
|
207
233
|
// 授权页拒绝 / 网关下发 error:不兑换 code。error 截断后入日志,防日志注入。
|
|
208
234
|
log(`hiwork-core: 浏览器授权被拒绝(${input.error.slice(0, 64)})`);
|
|
209
|
-
return await failBrowserLogin(opts.state, { code: 'AUTH_FAILED', message: '授权被取消或失败,请重新登录' });
|
|
235
|
+
return await failBrowserLogin(opts.state, { code: 'AUTH_FAILED', message: '授权被取消或失败,请重新登录' }, log);
|
|
210
236
|
}
|
|
211
237
|
if (typeof input.code !== 'string' || input.code === '') {
|
|
212
238
|
log('hiwork-core: 浏览器授权回调缺少 code,已丢弃');
|
|
213
|
-
return await failBrowserLogin(opts.state, { code: 'AUTH_FAILED', message: '授权回调不完整,请重新登录' });
|
|
239
|
+
return await failBrowserLogin(opts.state, { code: 'AUTH_FAILED', message: '授权回调不完整,请重新登录' }, log);
|
|
214
240
|
}
|
|
215
241
|
const baseURL = opts.value.baseURL;
|
|
216
242
|
if (baseURL === undefined || baseURL === '')
|
|
217
|
-
return await failBrowserLogin(opts.state, NO_BASE_URL_ERROR);
|
|
243
|
+
return await failBrowserLogin(opts.state, NO_BASE_URL_ERROR, log);
|
|
218
244
|
const { sessionCookie, user } = await opts.gateway.exchangeCode({
|
|
219
245
|
code: input.code,
|
|
220
246
|
codeVerifier: pending.codeVerifier,
|
|
@@ -240,7 +266,7 @@ export async function completeBrowserLogin(input, opts) {
|
|
|
240
266
|
}
|
|
241
267
|
catch (error) {
|
|
242
268
|
const loginError = toLoginError(error, '无法连接网关,请检查 baseURL 或网络');
|
|
243
|
-
return await failBrowserLogin(opts.state, loginError);
|
|
269
|
+
return await failBrowserLogin(opts.state, loginError, log);
|
|
244
270
|
}
|
|
245
271
|
finally {
|
|
246
272
|
pendingAuth = undefined;
|
package/lib/client.js
CHANGED
|
@@ -1065,108 +1065,6 @@ function pickSettingsSectionButton(buttons, labels) {
|
|
|
1065
1065
|
}
|
|
1066
1066
|
return void 0;
|
|
1067
1067
|
}
|
|
1068
|
-
var cancelPendingNavigation;
|
|
1069
|
-
function isVisibleSubtree(el) {
|
|
1070
|
-
if (el === null) return false;
|
|
1071
|
-
const style = getComputedStyle(el);
|
|
1072
|
-
if (style.display === "none" || style.visibility === "hidden") return false;
|
|
1073
|
-
return el.parentElement === null || isVisibleSubtree(el.parentElement);
|
|
1074
|
-
}
|
|
1075
|
-
function openSettingsSection(root, label, onMissing) {
|
|
1076
|
-
const labels = typeof label === "string" ? [label] : label;
|
|
1077
|
-
if (cancelPendingNavigation !== void 0 && document.querySelector('[role="dialog"]') !== null) {
|
|
1078
|
-
return;
|
|
1079
|
-
}
|
|
1080
|
-
const trigger = root?.querySelector('[aria-haspopup="dialog"]');
|
|
1081
|
-
if (trigger === null || trigger === void 0) {
|
|
1082
|
-
onMissing?.();
|
|
1083
|
-
return;
|
|
1084
|
-
}
|
|
1085
|
-
cancelPendingNavigation?.();
|
|
1086
|
-
let frame;
|
|
1087
|
-
let finished = false;
|
|
1088
|
-
let sawPanel = false;
|
|
1089
|
-
let reopenTimer;
|
|
1090
|
-
let watchdog;
|
|
1091
|
-
let lastSignature = "";
|
|
1092
|
-
const IDLE_MS = 1500;
|
|
1093
|
-
const finishFailure = () => {
|
|
1094
|
-
cleanup();
|
|
1095
|
-
console.warn(`[hiwork-core] \u672A\u627E\u5230\u8BBE\u7F6E\u5206\u533A\uFF1A${labels.join(" / ")}`);
|
|
1096
|
-
onMissing?.();
|
|
1097
|
-
};
|
|
1098
|
-
const armWatchdog = () => {
|
|
1099
|
-
window.clearTimeout(watchdog);
|
|
1100
|
-
watchdog = window.setTimeout(() => {
|
|
1101
|
-
if (finished) return;
|
|
1102
|
-
if (select()) return;
|
|
1103
|
-
finishFailure();
|
|
1104
|
-
}, IDLE_MS);
|
|
1105
|
-
};
|
|
1106
|
-
const observer = new MutationObserver(() => {
|
|
1107
|
-
schedule();
|
|
1108
|
-
});
|
|
1109
|
-
const cleanup = () => {
|
|
1110
|
-
if (finished) return;
|
|
1111
|
-
finished = true;
|
|
1112
|
-
observer.disconnect();
|
|
1113
|
-
window.clearTimeout(watchdog);
|
|
1114
|
-
window.clearTimeout(reopenTimer);
|
|
1115
|
-
if (frame !== void 0) window.cancelAnimationFrame(frame);
|
|
1116
|
-
if (cancelPendingNavigation === cleanup) cancelPendingNavigation = void 0;
|
|
1117
|
-
};
|
|
1118
|
-
const select = () => {
|
|
1119
|
-
if (finished) return false;
|
|
1120
|
-
const panels = Array.from(document.querySelectorAll('[role="dialog"]')).filter((panel) => isVisibleSubtree(panel));
|
|
1121
|
-
const buttons = panels.flatMap((panel) => Array.from(panel.querySelectorAll("nav button")));
|
|
1122
|
-
const target = panels.length === 0 ? void 0 : pickSettingsSectionButton(buttons, labels);
|
|
1123
|
-
const targetActive = target !== void 0 && (target.className.includes("active") || target.getAttribute("aria-current") === "true");
|
|
1124
|
-
const signature = `${panels.length}:${targetActive ? 1 : 0}`;
|
|
1125
|
-
if (signature !== lastSignature) {
|
|
1126
|
-
lastSignature = signature;
|
|
1127
|
-
armWatchdog();
|
|
1128
|
-
}
|
|
1129
|
-
if (panels.length === 0) {
|
|
1130
|
-
if (sawPanel) {
|
|
1131
|
-
sawPanel = false;
|
|
1132
|
-
reopenTimer = window.setTimeout(() => {
|
|
1133
|
-
reopenTimer = void 0;
|
|
1134
|
-
if (finished || sawPanel) return;
|
|
1135
|
-
if (typeof document === "undefined" || document.querySelector('[role="dialog"]') !== null) return;
|
|
1136
|
-
trigger.click();
|
|
1137
|
-
}, 300);
|
|
1138
|
-
return false;
|
|
1139
|
-
}
|
|
1140
|
-
trigger.click();
|
|
1141
|
-
return false;
|
|
1142
|
-
}
|
|
1143
|
-
sawPanel = true;
|
|
1144
|
-
if (target === void 0) return false;
|
|
1145
|
-
if (targetActive) {
|
|
1146
|
-
cleanup();
|
|
1147
|
-
return true;
|
|
1148
|
-
}
|
|
1149
|
-
target.click();
|
|
1150
|
-
return false;
|
|
1151
|
-
};
|
|
1152
|
-
const schedule = () => {
|
|
1153
|
-
if (finished || frame !== void 0) return;
|
|
1154
|
-
frame = window.requestAnimationFrame(() => {
|
|
1155
|
-
frame = void 0;
|
|
1156
|
-
select();
|
|
1157
|
-
});
|
|
1158
|
-
};
|
|
1159
|
-
armWatchdog();
|
|
1160
|
-
observer.observe(document.body, {
|
|
1161
|
-
childList: true,
|
|
1162
|
-
subtree: true,
|
|
1163
|
-
// 壳把激活态应用到导航钮是纯属性变更(class/aria-current),必须监听才能复核收敛
|
|
1164
|
-
attributes: true,
|
|
1165
|
-
attributeFilter: ["class", "aria-current", "aria-expanded"]
|
|
1166
|
-
});
|
|
1167
|
-
cancelPendingNavigation = cleanup;
|
|
1168
|
-
schedule();
|
|
1169
|
-
}
|
|
1170
1068
|
|
|
1171
1069
|
// src/client/user-badge.tsx
|
|
1172
1070
|
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
@@ -1388,11 +1286,27 @@ function LogoutIcon() {
|
|
|
1388
1286
|
}
|
|
1389
1287
|
);
|
|
1390
1288
|
}
|
|
1391
|
-
function openGatewaySettings() {
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1289
|
+
function openGatewaySettings(openSettings) {
|
|
1290
|
+
openSettings();
|
|
1291
|
+
selectSettingsSection([AI_ROUTER_SECTION_LABEL]);
|
|
1292
|
+
}
|
|
1293
|
+
function selectSettingsSection(labels) {
|
|
1294
|
+
const started = Date.now();
|
|
1295
|
+
const tick = () => {
|
|
1296
|
+
const dialog = document.querySelector('[role="dialog"]');
|
|
1297
|
+
if (dialog !== null) {
|
|
1298
|
+
const button = pickSettingsSectionButton(
|
|
1299
|
+
Array.from(dialog.querySelectorAll("button")),
|
|
1300
|
+
labels
|
|
1301
|
+
);
|
|
1302
|
+
if (button !== void 0) {
|
|
1303
|
+
button.click();
|
|
1304
|
+
return;
|
|
1305
|
+
}
|
|
1306
|
+
}
|
|
1307
|
+
if (Date.now() - started < 3e3) window.setTimeout(tick, 120);
|
|
1308
|
+
};
|
|
1309
|
+
tick();
|
|
1396
1310
|
}
|
|
1397
1311
|
function LogoutConfirm(props) {
|
|
1398
1312
|
const { onCancel } = props;
|
|
@@ -1500,7 +1414,9 @@ function UserBadge(props) {
|
|
|
1500
1414
|
style: railRowStyle,
|
|
1501
1415
|
title: displayName,
|
|
1502
1416
|
"aria-label": "\u6253\u5F00\u8BBE\u7F6E",
|
|
1503
|
-
onClick:
|
|
1417
|
+
onClick: () => {
|
|
1418
|
+
openGatewaySettings(props.openSettings);
|
|
1419
|
+
},
|
|
1504
1420
|
children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: avatarStyle(24), children: initial })
|
|
1505
1421
|
}
|
|
1506
1422
|
),
|
|
@@ -1517,7 +1433,9 @@ function UserBadge(props) {
|
|
|
1517
1433
|
style: profileButtonStyle,
|
|
1518
1434
|
title: "\u6253\u5F00\u8BBE\u7F6E",
|
|
1519
1435
|
"aria-label": `\u6253\u5F00\u8BBE\u7F6E\uFF08${displayName}\uFF09`,
|
|
1520
|
-
onClick:
|
|
1436
|
+
onClick: () => {
|
|
1437
|
+
openGatewaySettings(props.openSettings);
|
|
1438
|
+
},
|
|
1521
1439
|
children: [
|
|
1522
1440
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: avatarStyle(24), children: initial }),
|
|
1523
1441
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: nameTextStyle, children: displayName })
|
|
@@ -1532,7 +1450,9 @@ function UserBadge(props) {
|
|
|
1532
1450
|
style: iconButtonStyle,
|
|
1533
1451
|
title: "\u8BBE\u7F6E",
|
|
1534
1452
|
"aria-label": "\u8BBE\u7F6E",
|
|
1535
|
-
onClick:
|
|
1453
|
+
onClick: () => {
|
|
1454
|
+
openGatewaySettings(props.openSettings);
|
|
1455
|
+
},
|
|
1536
1456
|
children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_dsh_client_ui_primitives.IconSettingsOutlineMedium, { size: 16 })
|
|
1537
1457
|
}
|
|
1538
1458
|
),
|
|
@@ -1876,6 +1796,8 @@ function AboutSection({ appVersion }) {
|
|
|
1876
1796
|
// src/client/daily-data.ts
|
|
1877
1797
|
var DAILY_CHANNEL = "/hiwork-daily";
|
|
1878
1798
|
var DAILY_FAILURE_TTL_MS = 5 * 6e4;
|
|
1799
|
+
var DAILY_LOCAL_RETRY_MS = 2e3;
|
|
1800
|
+
var DAILY_LOCAL_FAILURE_CODES = ["SESSION_EXPIRED", "GATEWAY_UNREACHABLE"];
|
|
1879
1801
|
var DailyRpcError = class extends Error {
|
|
1880
1802
|
constructor(code, message) {
|
|
1881
1803
|
super(message);
|
|
@@ -1947,27 +1869,38 @@ var DailyDataStore = class {
|
|
|
1947
1869
|
this.now = options.now ?? (() => Date.now());
|
|
1948
1870
|
this.localDay = options.localDay ?? (() => localDayIndex());
|
|
1949
1871
|
this.failureTtlMs = options.failureTtlMs ?? DAILY_FAILURE_TTL_MS;
|
|
1872
|
+
this.localRetryMs = options.localRetryMs ?? DAILY_LOCAL_RETRY_MS;
|
|
1950
1873
|
}
|
|
1951
1874
|
entry;
|
|
1952
1875
|
failureAt;
|
|
1876
|
+
/** 上一次失败是否为宿主本地前置失败({@link DAILY_LOCAL_FAILURE_CODES})——决定节流用哪个 TTL。 */
|
|
1877
|
+
localFailure = false;
|
|
1953
1878
|
pending;
|
|
1954
1879
|
now;
|
|
1955
1880
|
localDay;
|
|
1956
1881
|
failureTtlMs;
|
|
1882
|
+
localRetryMs;
|
|
1957
1883
|
/** 清空缓存与防抖状态(登出 / 换账号时调用)。 */
|
|
1958
1884
|
clear() {
|
|
1959
1885
|
this.entry = void 0;
|
|
1960
1886
|
this.failureAt = void 0;
|
|
1887
|
+
this.localFailure = false;
|
|
1961
1888
|
this.pending = void 0;
|
|
1962
1889
|
}
|
|
1963
1890
|
/**
|
|
1964
|
-
* 当日条目:同一本地日内命中缓存直接返回(不发 RPC
|
|
1965
|
-
*
|
|
1891
|
+
* 当日条目:同一本地日内命中缓存直接返回(不发 RPC);节流期内返回 null 不重复打网关;
|
|
1892
|
+
* 并发调用合并为同一次 RPC。返回 null 即「暂无可展示内容」。
|
|
1893
|
+
*
|
|
1894
|
+
* 节流时长分两档:宿主本地前置失败(未登录 / 网关未配置)只等 {@link DAILY_LOCAL_RETRY_MS},
|
|
1895
|
+
* 其余失败等 {@link DAILY_FAILURE_TTL_MS}——理由见两个常量的说明(首次安装必先启动后登录)。
|
|
1966
1896
|
*/
|
|
1967
1897
|
async getToday() {
|
|
1968
1898
|
const day = this.localDay();
|
|
1969
1899
|
if (this.entry !== void 0 && this.entry.day === day) return this.entry.view;
|
|
1970
|
-
if (this.failureAt !== void 0
|
|
1900
|
+
if (this.failureAt !== void 0) {
|
|
1901
|
+
const ttl = this.localFailure ? this.localRetryMs : this.failureTtlMs;
|
|
1902
|
+
if (this.now() - this.failureAt < ttl) return null;
|
|
1903
|
+
}
|
|
1971
1904
|
if (this.pending === void 0) {
|
|
1972
1905
|
this.pending = this.fetchToday(day).finally(() => {
|
|
1973
1906
|
this.pending = void 0;
|
|
@@ -1989,17 +1922,25 @@ var DailyDataStore = class {
|
|
|
1989
1922
|
return null;
|
|
1990
1923
|
}
|
|
1991
1924
|
}
|
|
1992
|
-
/**
|
|
1925
|
+
/**
|
|
1926
|
+
* 单次 today 拉取:成功(含池空)写缓存;任何失败进入节流并返回 null。
|
|
1927
|
+
*
|
|
1928
|
+
* 失败分两档记:{@link DAILY_LOCAL_FAILURE_CODES} 是宿主在打网之前判定的(无网关流量、登录后必然变化),
|
|
1929
|
+
* 只记短节流;其余(网关透传失败、形状非法、未知错误)记满 {@link DAILY_FAILURE_TTL_MS}。
|
|
1930
|
+
*/
|
|
1993
1931
|
async fetchToday(day) {
|
|
1994
1932
|
try {
|
|
1995
1933
|
const raw = await this.rpc.call(DAILY_CHANNEL, "today", {});
|
|
1996
1934
|
const response = parseTodayResponse(unwrapDailyResult(raw));
|
|
1997
1935
|
if (response === void 0) throw new DailyRpcError("internal", "\u6BCF\u65E5\u4E00\u53E5\u4E3B\u673A\u8FD4\u56DE\u4E86\u65E0\u6548\u54CD\u5E94\u3002");
|
|
1998
1936
|
this.failureAt = void 0;
|
|
1937
|
+
this.localFailure = false;
|
|
1999
1938
|
const view = response.item === null ? null : { ...response.item, date: response.date };
|
|
2000
1939
|
this.entry = { view, day };
|
|
2001
1940
|
return view;
|
|
2002
|
-
} catch {
|
|
1941
|
+
} catch (error) {
|
|
1942
|
+
const code = error instanceof DailyRpcError ? error.code : "";
|
|
1943
|
+
this.localFailure = DAILY_LOCAL_FAILURE_CODES.includes(code);
|
|
2003
1944
|
this.failureAt = this.now();
|
|
2004
1945
|
return null;
|
|
2005
1946
|
}
|
|
@@ -3280,7 +3221,7 @@ function createFeatureRegistry() {
|
|
|
3280
3221
|
|
|
3281
3222
|
// src/client/menu-preferences.ts
|
|
3282
3223
|
var HIWORK_MENU_NS = "hiwork-menu";
|
|
3283
|
-
var DEFAULT_SIDEBAR_LIMIT =
|
|
3224
|
+
var DEFAULT_SIDEBAR_LIMIT = 6;
|
|
3284
3225
|
var MORE_MENU_ITEM_ID = "__hiwork_more__";
|
|
3285
3226
|
var MORE_MENU_LABEL = "\u66F4\u591A";
|
|
3286
3227
|
var MENU_SECTION_LABEL = "\u4FA7\u680F\u83DC\u5355";
|
|
@@ -7195,6 +7136,7 @@ function apply(ctx) {
|
|
|
7195
7136
|
ctx.effect(() => observeAccountChange(controller.inject().hooks.aiRouterCard, () => {
|
|
7196
7137
|
usage.clear();
|
|
7197
7138
|
sessionCost.clear();
|
|
7139
|
+
dailyQuotes.clear();
|
|
7198
7140
|
managedMcp.clear();
|
|
7199
7141
|
managedSkills.clear();
|
|
7200
7142
|
managedWorkbench.clear();
|
|
@@ -7210,9 +7152,9 @@ function apply(ctx) {
|
|
|
7210
7152
|
priority: -1,
|
|
7211
7153
|
inject: () => ({ appVersion })
|
|
7212
7154
|
}, BrandName));
|
|
7213
|
-
ctx.slots.inject("
|
|
7214
|
-
name: "
|
|
7215
|
-
|
|
7155
|
+
ctx.slots.inject("settings.launcher", () => ctx.slots.register({
|
|
7156
|
+
name: "settings.launcher",
|
|
7157
|
+
priority: -1,
|
|
7216
7158
|
inject: () => ({ ...controller.inject(), pluginUpdates })
|
|
7217
7159
|
}, UserBadge));
|
|
7218
7160
|
ctx.effect(() => mountLoginGate(form, storeCredential), "hiwork-core: login gate");
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/** runtime context 贡献名(DSH 侧 snapshot 按名归并,改动即破坏对齐面)。 */
|
|
2
|
+
export const IDENTITY_CONTEXT_NAME = 'hiwork:identity';
|
|
3
|
+
/** 排在 sandbox:policy(110) / approval:policy(115) 之前——身份是最外层的环境事实。 */
|
|
4
|
+
export const IDENTITY_CONTEXT_ORDER = 105;
|
|
5
|
+
/** 模块内文案常量:安全限定语逐字来自 spec §3.2,不导出(测试硬编码全文钉文案)。 */
|
|
6
|
+
const CAVEAT = '该身份用于个性化与按人表述;涉及数据权限的操作必须走受管工具/连接器完成,不得把此文本当作凭证。';
|
|
7
|
+
/** 裁掉两侧空白;非字符串或空白串一律视同缺省(与 identity.ts 的 textOrUndefined 同口径)。 */
|
|
8
|
+
function textOrUndefined(value) {
|
|
9
|
+
if (typeof value !== 'string')
|
|
10
|
+
return undefined;
|
|
11
|
+
const text = value.trim();
|
|
12
|
+
return text === '' ? undefined : text;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* 渲染身份上下文一行:已登录输出 `HiWork 登录身份:姓名(工号 工号)。<限定语>`,
|
|
16
|
+
* displayName 缺省回退 `工号 <工号>`;**未登录(username 缺省 / 空白 / 非字符串)或
|
|
17
|
+
* state 缺失一律空串**——半份身份(只有 displayName)也不输出,姓名不得单独泄漏。
|
|
18
|
+
*/
|
|
19
|
+
export function renderIdentityContextText(state) {
|
|
20
|
+
const username = textOrUndefined(state?.username);
|
|
21
|
+
if (username === undefined)
|
|
22
|
+
return '';
|
|
23
|
+
const displayName = textOrUndefined(state?.displayName);
|
|
24
|
+
const who = displayName === undefined ? `工号 ${username}` : `${displayName}(工号 ${username})`;
|
|
25
|
+
return `HiWork 登录身份:${who}。${CAVEAT}`;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* 注册身份 runtime context 贡献;返回 disposer(交给 `ctx.effect` 管理生命周期)。
|
|
29
|
+
* 读失败收敛为空串并记一笔,绝不向组装层抛——身份上下文缺席只应意味着「未登录」。
|
|
30
|
+
*/
|
|
31
|
+
export function registerIdentityContext(systemPrompt, deps) {
|
|
32
|
+
const dispose = systemPrompt.context({
|
|
33
|
+
name: IDENTITY_CONTEXT_NAME,
|
|
34
|
+
order: IDENTITY_CONTEXT_ORDER,
|
|
35
|
+
text: () => {
|
|
36
|
+
try {
|
|
37
|
+
// enabled 的判断也在 try 内:真实接线里 enabled 与 current 读同一个命名空间,
|
|
38
|
+
// 任一侧抛异常都收敛为空串(身份上下文缺席只应意味着「未登录」)。
|
|
39
|
+
if (!deps.enabled())
|
|
40
|
+
return '';
|
|
41
|
+
return renderIdentityContextText(deps.current());
|
|
42
|
+
}
|
|
43
|
+
catch (error) {
|
|
44
|
+
deps.log?.(`hiwork-core: 登录身份上下文读取失败(${error instanceof Error ? error.name : typeof error})`);
|
|
45
|
+
return '';
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
});
|
|
49
|
+
return typeof dispose === 'function' ? dispose : () => { };
|
|
50
|
+
}
|