hiwork-core 0.0.38 → 0.0.39
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 +152 -4
- package/lib/browser-login.js +9 -2
- package/lib/client-policy.js +77 -2
- package/lib/index.js +452 -44
- package/lib/knowledge-config.js +14 -12
- package/lib/memory-config.js +7 -7
- package/lib/orchestration.js +50 -3
- package/lib/provision-session.js +1 -0
- package/lib/provision.js +5 -0
- package/lib/session-fanout.js +29 -0
- package/lib/session-supervisor.js +156 -0
- package/lib/ttl-cache.js +45 -0
- package/lib/types/ai-router-client.d.ts +74 -0
- package/lib/types/client/types.d.ts +7 -1
- package/lib/types/client-policy.d.ts +30 -2
- package/lib/types/index.d.ts +5 -0
- package/lib/types/orchestration.d.ts +5 -3
- package/lib/types/provision-session.d.ts +7 -0
- package/lib/types/provision.d.ts +5 -0
- package/lib/types/session-fanout.d.ts +19 -0
- package/lib/types/session-supervisor.d.ts +101 -0
- package/lib/types/ttl-cache.d.ts +29 -0
- package/lib/types/types.d.ts +6 -0
- package/package.json +1 -1
package/lib/index.js
CHANGED
|
@@ -8267,7 +8267,7 @@ defineMethod("transform", [
|
|
|
8267
8267
|
], ({ inner }, isInner) => inner.toString(isInner));
|
|
8268
8268
|
|
|
8269
8269
|
// src/version.ts
|
|
8270
|
-
var PKG_VERSION = false ? "dev" : "0.0.
|
|
8270
|
+
var PKG_VERSION = false ? "dev" : "0.0.39";
|
|
8271
8271
|
|
|
8272
8272
|
// src/browser-login.ts
|
|
8273
8273
|
import { createHash, randomBytes, randomUUID } from "node:crypto";
|
|
@@ -8278,6 +8278,7 @@ var API_KEY_REF = "AI_ROUTER_API_KEY";
|
|
|
8278
8278
|
var SESSION_REF = "AI_ROUTER_SESSION";
|
|
8279
8279
|
var PENDING_PASSWORD_REF = "AI_ROUTER_PENDING_PASSWORD";
|
|
8280
8280
|
var REMEMBERED_PASSWORD_REF = "AI_ROUTER_REMEMBERED_PASSWORD";
|
|
8281
|
+
var REFRESH_TOKEN_REF = "AI_ROUTER_REFRESH_TOKEN";
|
|
8281
8282
|
var PROVIDER_ROUTES = {
|
|
8282
8283
|
"ai-router-chat": "openai-completions",
|
|
8283
8284
|
"ai-router-anthropic": "anthropic-messages",
|
|
@@ -8435,6 +8436,7 @@ async function provisionSession(opts) {
|
|
|
8435
8436
|
const displayName = opts.user.display_name?.trim() || void 0;
|
|
8436
8437
|
const set2 = {
|
|
8437
8438
|
...opts.username === void 0 ? {} : { username: opts.username },
|
|
8439
|
+
...opts.sessionExpiresAt === void 0 ? {} : { sessionExpiresAt: opts.sessionExpiresAt },
|
|
8438
8440
|
...displayName === void 0 ? {} : { displayName },
|
|
8439
8441
|
lastLogin: {
|
|
8440
8442
|
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -8600,12 +8602,15 @@ async function completeBrowserLogin(input, opts) {
|
|
|
8600
8602
|
}
|
|
8601
8603
|
const baseURL = opts.value.baseURL;
|
|
8602
8604
|
if (baseURL === void 0 || baseURL === "") return await failBrowserLogin(opts.state, NO_BASE_URL_ERROR, log);
|
|
8603
|
-
const { sessionCookie, user } = await opts.gateway.exchangeCode({
|
|
8605
|
+
const { sessionCookie, user, sessionExpiresAt, refreshToken } = await opts.gateway.exchangeCode({
|
|
8604
8606
|
code: input.code,
|
|
8605
8607
|
codeVerifier: pending.codeVerifier,
|
|
8606
8608
|
deviceId: pending.deviceId
|
|
8607
8609
|
});
|
|
8608
8610
|
await opts.credentials.set(SESSION_REF, sessionCookie);
|
|
8611
|
+
if (refreshToken === void 0) await opts.credentials.unset(REFRESH_TOKEN_REF).catch(() => {
|
|
8612
|
+
});
|
|
8613
|
+
else await opts.credentials.set(REFRESH_TOKEN_REF, refreshToken);
|
|
8609
8614
|
const username = typeof user.username === "string" && user.username.trim() !== "" ? user.username.trim() : void 0;
|
|
8610
8615
|
await provisionSession({
|
|
8611
8616
|
gateway: opts.gateway,
|
|
@@ -8617,6 +8622,7 @@ async function completeBrowserLogin(input, opts) {
|
|
|
8617
8622
|
sessionCookie,
|
|
8618
8623
|
user,
|
|
8619
8624
|
username,
|
|
8625
|
+
sessionExpiresAt,
|
|
8620
8626
|
requestUnset: ["browserLoginRequest", "browserLogin"],
|
|
8621
8627
|
...opts.defaultModel === void 0 ? {} : { defaultModel: opts.defaultModel },
|
|
8622
8628
|
...opts.ensureWorkspace === void 0 ? {} : { ensureWorkspace: opts.ensureWorkspace }
|
|
@@ -8647,6 +8653,13 @@ function gatewayUserAgent(version2 = desktopAppVersion()) {
|
|
|
8647
8653
|
}
|
|
8648
8654
|
|
|
8649
8655
|
// src/ai-router-client.ts
|
|
8656
|
+
var sessionActivitySink;
|
|
8657
|
+
function setSessionActivitySink(sink) {
|
|
8658
|
+
sessionActivitySink = sink;
|
|
8659
|
+
}
|
|
8660
|
+
function clearSessionActivitySink(sink) {
|
|
8661
|
+
if (sessionActivitySink === sink) sessionActivitySink = void 0;
|
|
8662
|
+
}
|
|
8650
8663
|
async function knowledgeErrorCodeFromBody(res) {
|
|
8651
8664
|
try {
|
|
8652
8665
|
const payload = await res.clone().json();
|
|
@@ -8658,6 +8671,7 @@ async function knowledgeErrorCodeFromBody(res) {
|
|
|
8658
8671
|
var AUTH_AUTHORIZE_PATH = "/api/auth/authorize";
|
|
8659
8672
|
var AUTH_TOKEN_PATH = "/api/auth/token";
|
|
8660
8673
|
var AUTH_SESSIONS_PATH = "/api/auth/sessions";
|
|
8674
|
+
var AUTH_REFRESH_PATH = "/api/auth/refresh";
|
|
8661
8675
|
var ME_ANALYTICS_SUMMARY_PATH = "/api/me/analytics/summary";
|
|
8662
8676
|
var ME_ANALYTICS_TRENDS_PATH = "/api/me/analytics/trends";
|
|
8663
8677
|
var ME_ANALYTICS_MODEL_TRENDS_PATH = "/api/me/analytics/model-trends";
|
|
@@ -8758,8 +8772,16 @@ var AiRouterClient = class {
|
|
|
8758
8772
|
if (res.status === 401) throw new GatewayError("AUTH_FAILED", "\u7528\u6237\u540D\u6216\u5BC6\u7801\u9519\u8BEF", 401);
|
|
8759
8773
|
if (!res.ok) throw new GatewayError("GATEWAY_UNREACHABLE", `\u7F51\u5173\u8FD4\u56DE\u9519\u8BEF\uFF08HTTP ${res.status}\uFF09`, res.status);
|
|
8760
8774
|
if (sid === void 0) throw new GatewayError("GATEWAY_UNREACHABLE", "\u7F51\u5173\u767B\u5F55\u6210\u529F\u4F46\u672A\u8FD4\u56DE\u4F1A\u8BDD Cookie", res.status);
|
|
8761
|
-
const user = await res.json();
|
|
8762
|
-
|
|
8775
|
+
const { expires_at: rawExpiresAt, refresh_token: rawRefreshToken, ...user } = await res.json();
|
|
8776
|
+
const bodyExpiresAt = typeof rawExpiresAt === "string" && !Number.isNaN(Date.parse(rawExpiresAt)) ? rawExpiresAt : void 0;
|
|
8777
|
+
const sessionExpiresAt = bodyExpiresAt ?? parseSessionCookieExpiry(res.headers);
|
|
8778
|
+
const refreshToken = typeof rawRefreshToken === "string" && rawRefreshToken !== "" ? rawRefreshToken : void 0;
|
|
8779
|
+
return {
|
|
8780
|
+
sessionCookie: `session_id=${sid}`,
|
|
8781
|
+
user,
|
|
8782
|
+
...sessionExpiresAt === void 0 ? {} : { sessionExpiresAt },
|
|
8783
|
+
...refreshToken === void 0 ? {} : { refreshToken }
|
|
8784
|
+
};
|
|
8763
8785
|
}
|
|
8764
8786
|
/**
|
|
8765
8787
|
* POST /api/auth/token:用一次性 code + PKCE verifier 换取会话。
|
|
@@ -8781,7 +8803,49 @@ var AiRouterClient = class {
|
|
|
8781
8803
|
if (data === void 0 || data.session_id === void 0 || data.session_id === "") {
|
|
8782
8804
|
throw new GatewayError("GATEWAY_UNREACHABLE", "\u7F51\u5173\u5151\u6362\u6210\u529F\u4F46\u672A\u8FD4\u56DE\u4F1A\u8BDD\u6807\u8BC6", res.status);
|
|
8783
8805
|
}
|
|
8784
|
-
|
|
8806
|
+
const rawExpiresAt = typeof data.expires_at === "string" ? data.expires_at : void 0;
|
|
8807
|
+
const sessionExpiresAt = rawExpiresAt !== void 0 && !Number.isNaN(Date.parse(rawExpiresAt)) ? rawExpiresAt : void 0;
|
|
8808
|
+
const refreshToken = typeof data.refresh_token === "string" && data.refresh_token !== "" ? data.refresh_token : void 0;
|
|
8809
|
+
return {
|
|
8810
|
+
sessionCookie: `session_id=${data.session_id}`,
|
|
8811
|
+
user: data.user,
|
|
8812
|
+
...sessionExpiresAt === void 0 ? {} : { sessionExpiresAt },
|
|
8813
|
+
...refreshToken === void 0 ? {} : { refreshToken }
|
|
8814
|
+
};
|
|
8815
|
+
}
|
|
8816
|
+
/**
|
|
8817
|
+
* POST /api/auth/refresh:用 refresh grant 换一份新会话(M2 网关的静默重认证)。
|
|
8818
|
+
*
|
|
8819
|
+
* 拒绝语义必须分开,调用方据此决定「弃用 grant」还是「保留待重试」:
|
|
8820
|
+
* - 400 = grant 无效(过期 / 已被轮换 / 复用检测拒绝)→ `REFRESH_GRANT_INVALID`,本地作废;
|
|
8821
|
+
* - 404 = 老网关根本没有这条路由 → `REFRESH_GRANT_ABSENT`,本地作废且**静默降级**;
|
|
8822
|
+
* - 其余非 2xx(5xx / 反代错误页)与网络层异常 → `GATEWAY_UNREACHABLE`,grant 保留,
|
|
8823
|
+
* 下次 SESSION_EXPIRED 再试。
|
|
8824
|
+
*
|
|
8825
|
+
* 任何日志/异常信息都不得包含 refresh_token / session_id 明文。
|
|
8826
|
+
*/
|
|
8827
|
+
async refreshSession(refreshToken) {
|
|
8828
|
+
const res = await this.requestOrUnreachable(AUTH_REFRESH_PATH, {
|
|
8829
|
+
method: "POST",
|
|
8830
|
+
body: { refresh_token: refreshToken }
|
|
8831
|
+
});
|
|
8832
|
+
if (res.status === 400) throw new GatewayError("REFRESH_GRANT_INVALID", "\u767B\u5F55\u5DF2\u8FC7\u671F\uFF0C\u8BF7\u91CD\u65B0\u767B\u5F55", 400);
|
|
8833
|
+
if (res.status === 404) throw new GatewayError("REFRESH_GRANT_ABSENT", "\u7F51\u5173\u4E0D\u652F\u6301\u9759\u9ED8\u7EED\u671F", 404);
|
|
8834
|
+
if (!res.ok) throw new GatewayError("GATEWAY_UNREACHABLE", `\u7F51\u5173\u8FD4\u56DE\u9519\u8BEF\uFF08HTTP ${res.status}\uFF09`, res.status);
|
|
8835
|
+
const data = await readJson(res);
|
|
8836
|
+
const sid = typeof data?.session_id === "string" && data.session_id !== "" ? data.session_id : extractSessionId(res.headers);
|
|
8837
|
+
if (data === void 0 || data.user === void 0 || sid === void 0) {
|
|
8838
|
+
throw new GatewayError("GATEWAY_UNREACHABLE", "\u7F51\u5173\u7EED\u671F\u6210\u529F\u4F46\u672A\u8FD4\u56DE\u4F1A\u8BDD\u6807\u8BC6", res.status);
|
|
8839
|
+
}
|
|
8840
|
+
const rawExpiresAt = typeof data.expires_at === "string" ? data.expires_at : void 0;
|
|
8841
|
+
const sessionExpiresAt = rawExpiresAt !== void 0 && !Number.isNaN(Date.parse(rawExpiresAt)) ? rawExpiresAt : void 0;
|
|
8842
|
+
const rotated = typeof data.refresh_token === "string" && data.refresh_token !== "" ? data.refresh_token : void 0;
|
|
8843
|
+
return {
|
|
8844
|
+
sessionCookie: `session_id=${sid}`,
|
|
8845
|
+
user: data.user,
|
|
8846
|
+
...sessionExpiresAt === void 0 ? {} : { sessionExpiresAt },
|
|
8847
|
+
...rotated === void 0 ? {} : { refreshToken: rotated }
|
|
8848
|
+
};
|
|
8785
8849
|
}
|
|
8786
8850
|
/** GET /api/auth/sessions:当前账号的登录设备列表。 */
|
|
8787
8851
|
async listSessions(sessionCookie) {
|
|
@@ -9016,6 +9080,9 @@ var AiRouterClient = class {
|
|
|
9016
9080
|
if (res.status === 503) {
|
|
9017
9081
|
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);
|
|
9018
9082
|
}
|
|
9083
|
+
if (res.status === 429) {
|
|
9084
|
+
throw new GatewayError("MCP_ASSERTION_RATE_LIMITED", "\u65AD\u8A00\u7B7E\u53D1\u8FC7\u4E8E\u9891\u7E41\uFF0C\u7A0D\u540E\u81EA\u52A8\u91CD\u8BD5", 429);
|
|
9085
|
+
}
|
|
9019
9086
|
if (!res.ok) throw new GatewayError("GATEWAY_UNREACHABLE", `\u7F51\u5173\u8FD4\u56DE\u9519\u8BEF\uFF08HTTP ${res.status}\uFF09`, res.status);
|
|
9020
9087
|
return await parseMeJson(res);
|
|
9021
9088
|
}
|
|
@@ -9389,7 +9456,19 @@ var AiRouterClient = class {
|
|
|
9389
9456
|
opts.rawBody.byteOffset + opts.rawBody.byteLength
|
|
9390
9457
|
);
|
|
9391
9458
|
} else if (opts.body !== void 0) init.body = JSON.stringify(opts.body);
|
|
9392
|
-
|
|
9459
|
+
const res = await fetch(`${this.baseURL}${path}`, init);
|
|
9460
|
+
if (opts.sessionCookie !== void 0) {
|
|
9461
|
+
try {
|
|
9462
|
+
if (res.status === 401) sessionActivitySink?.({ kind: "unauthorized" });
|
|
9463
|
+
else if (res.ok) {
|
|
9464
|
+
const headerExpiresAt = res.headers.get("x-session-expires-at");
|
|
9465
|
+
const expiresAt = headerExpiresAt !== null && !Number.isNaN(Date.parse(headerExpiresAt)) ? headerExpiresAt : void 0;
|
|
9466
|
+
sessionActivitySink?.(expiresAt === void 0 ? { kind: "success" } : { kind: "success", expiresAt });
|
|
9467
|
+
}
|
|
9468
|
+
} catch {
|
|
9469
|
+
}
|
|
9470
|
+
}
|
|
9471
|
+
return res;
|
|
9393
9472
|
}
|
|
9394
9473
|
};
|
|
9395
9474
|
function headerSafeValue(value) {
|
|
@@ -9427,6 +9506,27 @@ function extractSessionId(headers) {
|
|
|
9427
9506
|
}
|
|
9428
9507
|
return void 0;
|
|
9429
9508
|
}
|
|
9509
|
+
function parseSessionCookieExpiry(headers, now = Date.now()) {
|
|
9510
|
+
const raws = typeof headers.getSetCookie === "function" ? headers.getSetCookie() : [headers.get("set-cookie")].filter((raw) => raw !== null);
|
|
9511
|
+
for (const raw of raws) {
|
|
9512
|
+
for (const section of raw.split(/,\s*(?=[!#$%&'*+\-.^_`|~0-9A-Za-z]+=)/)) {
|
|
9513
|
+
if (!/(?:^|;\s*)session_id=/.test(section)) continue;
|
|
9514
|
+
const expires = /;\s*Expires=([^;]+)/i.exec(section);
|
|
9515
|
+
const expiresRaw = expires?.[1];
|
|
9516
|
+
if (expiresRaw !== void 0) {
|
|
9517
|
+
const ms = Date.parse(expiresRaw);
|
|
9518
|
+
if (!Number.isNaN(ms)) return new Date(ms).toISOString();
|
|
9519
|
+
}
|
|
9520
|
+
const maxAge = /;\s*Max-Age=(-?\d+)/i.exec(section);
|
|
9521
|
+
if (maxAge !== null) {
|
|
9522
|
+
const seconds = Number(maxAge[1]);
|
|
9523
|
+
if (Number.isFinite(seconds)) return new Date(now + seconds * 1e3).toISOString();
|
|
9524
|
+
}
|
|
9525
|
+
return void 0;
|
|
9526
|
+
}
|
|
9527
|
+
}
|
|
9528
|
+
return void 0;
|
|
9529
|
+
}
|
|
9430
9530
|
async function readJson(res) {
|
|
9431
9531
|
try {
|
|
9432
9532
|
return await res.json();
|
|
@@ -9577,6 +9677,58 @@ async function fetchClientPolicy(opts) {
|
|
|
9577
9677
|
return void 0;
|
|
9578
9678
|
}
|
|
9579
9679
|
}
|
|
9680
|
+
var CLIENT_POLICY_POLL_INTERVAL_MS = 6 * 60 * 6e4;
|
|
9681
|
+
var CLIENT_POLICY_RETRY_DELAYS_MS = [6e4, 10 * 6e4, 60 * 6e4];
|
|
9682
|
+
function createClientPolicyPoller(deps) {
|
|
9683
|
+
let timer;
|
|
9684
|
+
let disposed = false;
|
|
9685
|
+
let running = false;
|
|
9686
|
+
let retryIndex = 0;
|
|
9687
|
+
const schedule = (delayMs) => {
|
|
9688
|
+
if (disposed) return;
|
|
9689
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
9690
|
+
timer = setTimeout(() => {
|
|
9691
|
+
timer = void 0;
|
|
9692
|
+
void run();
|
|
9693
|
+
}, delayMs);
|
|
9694
|
+
timer.unref?.();
|
|
9695
|
+
};
|
|
9696
|
+
const run = async () => {
|
|
9697
|
+
if (disposed || running) return;
|
|
9698
|
+
running = true;
|
|
9699
|
+
let ok = false;
|
|
9700
|
+
try {
|
|
9701
|
+
ok = await deps.attempt();
|
|
9702
|
+
} catch {
|
|
9703
|
+
ok = false;
|
|
9704
|
+
} finally {
|
|
9705
|
+
running = false;
|
|
9706
|
+
}
|
|
9707
|
+
if (disposed) return;
|
|
9708
|
+
if (ok) {
|
|
9709
|
+
retryIndex = 0;
|
|
9710
|
+
schedule(CLIENT_POLICY_POLL_INTERVAL_MS);
|
|
9711
|
+
return;
|
|
9712
|
+
}
|
|
9713
|
+
const delay2 = CLIENT_POLICY_RETRY_DELAYS_MS[Math.min(retryIndex, CLIENT_POLICY_RETRY_DELAYS_MS.length - 1)];
|
|
9714
|
+
retryIndex += 1;
|
|
9715
|
+
deps.log?.(`hiwork-core: \u5BA2\u6237\u7AEF\u7B56\u7565\u62C9\u53D6\u5931\u8D25\uFF0C${Math.round(delay2 / 1e3)}s \u540E\u91CD\u8BD5`);
|
|
9716
|
+
schedule(delay2);
|
|
9717
|
+
};
|
|
9718
|
+
return {
|
|
9719
|
+
trigger: () => {
|
|
9720
|
+
if (disposed || running || timer !== void 0) return;
|
|
9721
|
+
void run();
|
|
9722
|
+
},
|
|
9723
|
+
dispose: () => {
|
|
9724
|
+
disposed = true;
|
|
9725
|
+
if (timer !== void 0) {
|
|
9726
|
+
clearTimeout(timer);
|
|
9727
|
+
timer = void 0;
|
|
9728
|
+
}
|
|
9729
|
+
}
|
|
9730
|
+
};
|
|
9731
|
+
}
|
|
9580
9732
|
|
|
9581
9733
|
// src/default-model.ts
|
|
9582
9734
|
var DEFAULT_MODEL_NS = "agent-default-model";
|
|
@@ -9783,14 +9935,18 @@ async function runLogin(opts) {
|
|
|
9783
9935
|
const password = await opts.credentials.resolve(pending.passwordRef);
|
|
9784
9936
|
if (password === void 0) throw new GatewayError("AUTH_FAILED", "\u767B\u5F55\u51ED\u636E\u7F3A\u5931\uFF0C\u8BF7\u91CD\u65B0\u767B\u5F55");
|
|
9785
9937
|
const device = await resolveDeviceInfo(opts.credentials);
|
|
9786
|
-
const { sessionCookie, user } = await opts.gateway.login(pending.username, password.value, device);
|
|
9938
|
+
const { sessionCookie, user, sessionExpiresAt, refreshToken } = await opts.gateway.login(pending.username, password.value, device);
|
|
9787
9939
|
await opts.credentials.set(SESSION_REF, sessionCookie);
|
|
9940
|
+
if (refreshToken === void 0) await opts.credentials.unset(REFRESH_TOKEN_REF).catch(() => {
|
|
9941
|
+
});
|
|
9942
|
+
else await opts.credentials.set(REFRESH_TOKEN_REF, refreshToken);
|
|
9788
9943
|
await provisionSession({
|
|
9789
9944
|
...opts,
|
|
9790
9945
|
baseURL,
|
|
9791
9946
|
sessionCookie,
|
|
9792
9947
|
user,
|
|
9793
9948
|
username: pending.username,
|
|
9949
|
+
sessionExpiresAt,
|
|
9794
9950
|
requestUnset: ["pendingLogin"],
|
|
9795
9951
|
onApiKeyWritten: async () => {
|
|
9796
9952
|
if (opts.value.rememberPassword === true) await opts.credentials.set(REMEMBERED_PASSWORD_REF, password.value);
|
|
@@ -9814,6 +9970,9 @@ async function runLogin(opts) {
|
|
|
9814
9970
|
}
|
|
9815
9971
|
}
|
|
9816
9972
|
async function runRefresh(opts) {
|
|
9973
|
+
return refreshWith(opts, false);
|
|
9974
|
+
}
|
|
9975
|
+
async function refreshWith(opts, grantRetried) {
|
|
9817
9976
|
const baseURL = opts.value.baseURL;
|
|
9818
9977
|
if (baseURL === void 0) {
|
|
9819
9978
|
await opts.state.patch({ unset: ["refreshRequest"] });
|
|
@@ -9850,6 +10009,28 @@ async function runRefresh(opts) {
|
|
|
9850
10009
|
return null;
|
|
9851
10010
|
} catch (error) {
|
|
9852
10011
|
const refreshError = toLoginError(error, "\u65E0\u6CD5\u8FDE\u63A5\u7F51\u5173\uFF0C\u8BF7\u68C0\u67E5 baseURL \u6216\u7F51\u7EDC");
|
|
10012
|
+
if (refreshError.code === "SESSION_EXPIRED" && !grantRetried) {
|
|
10013
|
+
const grant = await opts.credentials.resolve(REFRESH_TOKEN_REF).catch(() => void 0);
|
|
10014
|
+
if (grant !== void 0 && grant.value !== "") {
|
|
10015
|
+
try {
|
|
10016
|
+
const renewed = await opts.gateway.refreshSession(grant.value);
|
|
10017
|
+
await opts.credentials.set(SESSION_REF, renewed.sessionCookie);
|
|
10018
|
+
if (renewed.refreshToken === void 0) await opts.credentials.unset(REFRESH_TOKEN_REF).catch(() => {
|
|
10019
|
+
});
|
|
10020
|
+
else await opts.credentials.set(REFRESH_TOKEN_REF, renewed.refreshToken);
|
|
10021
|
+
if (renewed.sessionExpiresAt !== void 0) {
|
|
10022
|
+
await opts.state.patch({ set: { sessionExpiresAt: renewed.sessionExpiresAt } });
|
|
10023
|
+
}
|
|
10024
|
+
return refreshWith(opts, true);
|
|
10025
|
+
} catch (grantError) {
|
|
10026
|
+
const code = grantError instanceof GatewayError ? grantError.code : void 0;
|
|
10027
|
+
if (code === "REFRESH_GRANT_INVALID" || code === "REFRESH_GRANT_ABSENT") {
|
|
10028
|
+
await opts.credentials.unset(REFRESH_TOKEN_REF).catch(() => {
|
|
10029
|
+
});
|
|
10030
|
+
}
|
|
10031
|
+
}
|
|
10032
|
+
}
|
|
10033
|
+
}
|
|
9853
10034
|
if (refreshError.code === "SESSION_EXPIRED" && opts.value.rememberPassword === true && opts.value.username !== void 0) {
|
|
9854
10035
|
const remembered = await opts.credentials.resolve(REMEMBERED_PASSWORD_REF).catch(() => void 0);
|
|
9855
10036
|
if (remembered !== void 0) {
|
|
@@ -9874,6 +10055,8 @@ async function runLogout(opts) {
|
|
|
9874
10055
|
});
|
|
9875
10056
|
await opts.credentials.unset(SESSION_REF).catch(() => {
|
|
9876
10057
|
});
|
|
10058
|
+
await opts.credentials.unset(REFRESH_TOKEN_REF).catch(() => {
|
|
10059
|
+
});
|
|
9877
10060
|
await opts.credentials.unset(PENDING_PASSWORD_REF).catch(() => {
|
|
9878
10061
|
});
|
|
9879
10062
|
await opts.credentials.unset(REMEMBERED_PASSWORD_REF).catch(() => {
|
|
@@ -9892,7 +10075,9 @@ async function runLogout(opts) {
|
|
|
9892
10075
|
"lastError",
|
|
9893
10076
|
"keyId",
|
|
9894
10077
|
"budgets",
|
|
9895
|
-
"keys"
|
|
10078
|
+
"keys",
|
|
10079
|
+
// 会话寿命随会话一起清空:残留的旧 expiresAt 会让保活决策基于已失效的会话。
|
|
10080
|
+
"sessionExpiresAt"
|
|
9896
10081
|
]
|
|
9897
10082
|
});
|
|
9898
10083
|
}
|
|
@@ -18504,6 +18689,140 @@ async function readJson2(response) {
|
|
|
18504
18689
|
}
|
|
18505
18690
|
}
|
|
18506
18691
|
|
|
18692
|
+
// src/session-fanout.ts
|
|
18693
|
+
function buildSessionFanout(handlers) {
|
|
18694
|
+
const entries = [
|
|
18695
|
+
["client policy", handlers.onPolicyRefresh],
|
|
18696
|
+
["managed mcp", handlers.onManagedMcpRefresh],
|
|
18697
|
+
["managed skills", handlers.onManagedSkillsRefresh],
|
|
18698
|
+
["workbench", handlers.onWorkbenchRefresh]
|
|
18699
|
+
];
|
|
18700
|
+
return () => {
|
|
18701
|
+
for (const [label, run] of entries) {
|
|
18702
|
+
try {
|
|
18703
|
+
run();
|
|
18704
|
+
} catch (error) {
|
|
18705
|
+
handlers.log?.(`hiwork-core: \u4F1A\u8BDD\u8054\u52A8\u5237\u65B0\u300C${label}\u300D\u5931\u8D25\uFF08${error instanceof Error ? error.name : typeof error}\uFF09`);
|
|
18706
|
+
}
|
|
18707
|
+
}
|
|
18708
|
+
};
|
|
18709
|
+
}
|
|
18710
|
+
|
|
18711
|
+
// src/session-supervisor.ts
|
|
18712
|
+
var SESSION_TTL_ASSUMPTION_MS = 24 * 60 * 6e4;
|
|
18713
|
+
var SESSION_CHECK_INTERVAL_MS = 60 * 6e4;
|
|
18714
|
+
var SESSION_RECOVERY_COOLDOWN_MS = 6e4;
|
|
18715
|
+
var SessionSupervisor = class {
|
|
18716
|
+
constructor(deps) {
|
|
18717
|
+
this.deps = deps;
|
|
18718
|
+
}
|
|
18719
|
+
timer;
|
|
18720
|
+
/** 上次「已知会话存活」的时刻;undefined = 本进程内还没确认过(重启后的首次检查照打)。 */
|
|
18721
|
+
lastAuthedSuccessAt;
|
|
18722
|
+
/**
|
|
18723
|
+
* 最近一次带会话成功响应里 `X-Session-Expires-At` 给出的**内存**寿命估计(原样时刻串)。
|
|
18724
|
+
* 只活在内存里——热路径绝不写 settings(Review Focus 5),只有心跳成功才落盘。
|
|
18725
|
+
*/
|
|
18726
|
+
lastHeaderExpiresAt;
|
|
18727
|
+
/** 恢复请求是否在途(单飞去抖)。 */
|
|
18728
|
+
recoveryInFlight = false;
|
|
18729
|
+
/** 上次触发恢复的时刻(冷却窗口基准)。 */
|
|
18730
|
+
lastRecoveryAt;
|
|
18731
|
+
/** 开始兜底检查;重复调用不叠加定时器。 */
|
|
18732
|
+
start() {
|
|
18733
|
+
if (this.timer !== void 0) return;
|
|
18734
|
+
this.timer = setInterval(() => {
|
|
18735
|
+
void this.wake();
|
|
18736
|
+
}, SESSION_CHECK_INTERVAL_MS);
|
|
18737
|
+
this.timer.unref?.();
|
|
18738
|
+
}
|
|
18739
|
+
/** 停止兜底检查(调用后零活动)。 */
|
|
18740
|
+
stop() {
|
|
18741
|
+
if (this.timer === void 0) return;
|
|
18742
|
+
clearInterval(this.timer);
|
|
18743
|
+
this.timer = void 0;
|
|
18744
|
+
}
|
|
18745
|
+
/**
|
|
18746
|
+
* 带会话的请求成功 → 记活动时间(由 ai-router-client 的活动汇聚驱动)。
|
|
18747
|
+
* `expiresAt` 为响应头 `X-Session-Expires-At` 给出的会话寿命:**只更新内存估计**,
|
|
18748
|
+
* 绝不在此写 settings——热路径上每个成功请求都写盘会打爆设置面(Review Focus 5)。
|
|
18749
|
+
* 不可解析的值直接丢弃(当作没给),不覆盖既有估计。
|
|
18750
|
+
*/
|
|
18751
|
+
noteAuthedSuccess(expiresAt) {
|
|
18752
|
+
this.lastAuthedSuccessAt = this.now();
|
|
18753
|
+
if (expiresAt !== void 0 && !Number.isNaN(Date.parse(expiresAt))) this.lastHeaderExpiresAt = expiresAt;
|
|
18754
|
+
}
|
|
18755
|
+
/**
|
|
18756
|
+
* 带会话的请求收到 401 → 去抖后触发一次会话恢复。
|
|
18757
|
+
* 去抖有两层:`recoveryInFlight`(单飞,覆盖「恢复请求还没落地,同刻又来了 5 条 401」)
|
|
18758
|
+
* 与 `SESSION_RECOVERY_COOLDOWN_MS` 冷却(覆盖恢复完成后短时间内的余波 401)。
|
|
18759
|
+
* 恢复失败只记日志——apply 后写入面需要几秒才就绪,那一侧的失败不该惊动宿主。
|
|
18760
|
+
*/
|
|
18761
|
+
reportAuthed401(source) {
|
|
18762
|
+
if (this.recoveryInFlight) return;
|
|
18763
|
+
const now = this.now();
|
|
18764
|
+
if (this.lastRecoveryAt !== void 0 && now - this.lastRecoveryAt < SESSION_RECOVERY_COOLDOWN_MS) return;
|
|
18765
|
+
this.recoveryInFlight = true;
|
|
18766
|
+
this.lastRecoveryAt = now;
|
|
18767
|
+
void this.deps.requestRecovery().catch(() => {
|
|
18768
|
+
this.log(`hiwork-core: \u4F1A\u8BDD\u6062\u590D\u89E6\u53D1\u5931\u8D25\uFF08\u6765\u6E90 ${source}\uFF09\uFF0C\u7B49\u5F85\u4E0B\u4E2A 401 \u6216\u5FC3\u8DF3\u91CD\u8BD5`);
|
|
18769
|
+
}).finally(() => {
|
|
18770
|
+
this.recoveryInFlight = false;
|
|
18771
|
+
});
|
|
18772
|
+
}
|
|
18773
|
+
now() {
|
|
18774
|
+
return this.deps.now?.() ?? Date.now();
|
|
18775
|
+
}
|
|
18776
|
+
log(message) {
|
|
18777
|
+
this.deps.log?.(message);
|
|
18778
|
+
}
|
|
18779
|
+
/**
|
|
18780
|
+
* 一次兜底检查。判定顺序(早退即不发请求):
|
|
18781
|
+
* 无会话 / 无 baseURL → 返回;距上次已认证成功不足 TTL/2 → 返回;
|
|
18782
|
+
* 已知剩余寿命 > TTL/2 → 返回;否则 ping。
|
|
18783
|
+
* 寿命未知也 ping——重启后的首次检查即校验会话是否还活着。
|
|
18784
|
+
* 「已知剩余寿命」取 settings 值与内存估计(响应头)的**较大者**:头值只会更晚更准,
|
|
18785
|
+
* 用较大者才不会因为 settings 里的旧估算偏早而白打心跳。
|
|
18786
|
+
*/
|
|
18787
|
+
async wake() {
|
|
18788
|
+
try {
|
|
18789
|
+
const session = await this.deps.getSession();
|
|
18790
|
+
if (session === void 0 || session === "") return;
|
|
18791
|
+
if (this.deps.getBaseURL() === void 0) return;
|
|
18792
|
+
const now = this.now();
|
|
18793
|
+
if (this.lastAuthedSuccessAt !== void 0 && now - this.lastAuthedSuccessAt < SESSION_TTL_ASSUMPTION_MS / 2) return;
|
|
18794
|
+
const knownExpiresAtMs = this.knownExpiresAtMs();
|
|
18795
|
+
if (knownExpiresAtMs !== void 0 && knownExpiresAtMs - now > SESSION_TTL_ASSUMPTION_MS / 2) return;
|
|
18796
|
+
let pinged;
|
|
18797
|
+
try {
|
|
18798
|
+
pinged = await this.deps.ping(session);
|
|
18799
|
+
} catch (error) {
|
|
18800
|
+
const expired = error instanceof GatewayError && error.code === "SESSION_EXPIRED";
|
|
18801
|
+
this.log(expired ? "hiwork-core: \u4F1A\u8BDD\u5FC3\u8DF3\u53D1\u73B0\u4F1A\u8BDD\u5DF2\u5931\u6548\uFF0C\u7B49\u5F85 401 \u6C47\u805A\u89E6\u53D1\u6062\u590D" : "hiwork-core: \u4F1A\u8BDD\u5FC3\u8DF3\u8BF7\u6C42\u5931\u8D25\uFF0C\u5C06\u5728\u4E0B\u4E2A\u5468\u671F\u91CD\u8BD5");
|
|
18802
|
+
return;
|
|
18803
|
+
}
|
|
18804
|
+
const pingedExpiresAt = pinged?.expiresAt;
|
|
18805
|
+
const pingedMs = pingedExpiresAt === void 0 ? Number.NaN : Date.parse(pingedExpiresAt);
|
|
18806
|
+
const nextExpiresAt = pingedExpiresAt === void 0 || Number.isNaN(pingedMs) ? new Date(this.now() + SESSION_TTL_ASSUMPTION_MS).toISOString() : pingedExpiresAt;
|
|
18807
|
+
try {
|
|
18808
|
+
await this.deps.patchSessionExpiresAt(nextExpiresAt);
|
|
18809
|
+
} catch {
|
|
18810
|
+
this.log("hiwork-core: \u4F1A\u8BDD\u5FC3\u8DF3\u6210\u529F\u4F46\u5BFF\u547D\u56DE\u5199\u5931\u8D25\uFF0C\u5C06\u5728\u4E0B\u4E2A\u5468\u671F\u91CD\u8BD5");
|
|
18811
|
+
}
|
|
18812
|
+
} catch {
|
|
18813
|
+
this.log("hiwork-core: \u4F1A\u8BDD\u5FC3\u8DF3\u68C0\u67E5\u5931\u8D25\uFF0C\u5C06\u5728\u4E0B\u4E2A\u5468\u671F\u91CD\u8BD5");
|
|
18814
|
+
}
|
|
18815
|
+
}
|
|
18816
|
+
/**
|
|
18817
|
+
* 已知的会话到期时刻(epoch ms):settings 值与内存头值里**有效且较晚**的那个;
|
|
18818
|
+
* 两者都没有(或都不可解析)返回 undefined = 寿命未知。
|
|
18819
|
+
*/
|
|
18820
|
+
knownExpiresAtMs() {
|
|
18821
|
+
const values = [this.deps.getSessionExpiresAt(), this.lastHeaderExpiresAt].map((iso) => iso === void 0 ? Number.NaN : Date.parse(iso)).filter((ms) => !Number.isNaN(ms));
|
|
18822
|
+
return values.length === 0 ? void 0 : Math.max(...values);
|
|
18823
|
+
}
|
|
18824
|
+
};
|
|
18825
|
+
|
|
18507
18826
|
// src/pms-todo-bridge.ts
|
|
18508
18827
|
var PMS_TODO_RPC_CHANNEL = "/hiwork-pms-todo";
|
|
18509
18828
|
var PMS_TODO_RPC_ENDPOINTS = [
|
|
@@ -19168,6 +19487,34 @@ function registerIdentityContext(systemPrompt, deps) {
|
|
|
19168
19487
|
};
|
|
19169
19488
|
}
|
|
19170
19489
|
|
|
19490
|
+
// src/ttl-cache.ts
|
|
19491
|
+
var TtlCache = class {
|
|
19492
|
+
ttlMs;
|
|
19493
|
+
entry;
|
|
19494
|
+
constructor(opts) {
|
|
19495
|
+
this.ttlMs = opts.ttlMs;
|
|
19496
|
+
}
|
|
19497
|
+
/** 命中返回缓存值;会话键不同或已到期返回 undefined。`now` 可注入(缺省 Date.now)。 */
|
|
19498
|
+
get(sessionKey, now = Date.now()) {
|
|
19499
|
+
const hit = this.entry;
|
|
19500
|
+
if (hit === void 0 || hit.sessionKey !== sessionKey) return void 0;
|
|
19501
|
+
if (now - hit.at >= this.ttlMs) return void 0;
|
|
19502
|
+
return hit.value;
|
|
19503
|
+
}
|
|
19504
|
+
/** 写入/覆盖缓存(新会话键会顶掉旧会话那条)。 */
|
|
19505
|
+
put(sessionKey, value, now = Date.now()) {
|
|
19506
|
+
this.entry = { sessionKey, value, at: now };
|
|
19507
|
+
}
|
|
19508
|
+
/** 清掉指定会话键的条目(不影响别的键,虽然只会存在一条);不传参数则清空。 */
|
|
19509
|
+
invalidate(sessionKey) {
|
|
19510
|
+
if (sessionKey === void 0) {
|
|
19511
|
+
this.entry = void 0;
|
|
19512
|
+
return;
|
|
19513
|
+
}
|
|
19514
|
+
if (this.entry?.sessionKey === sessionKey) this.entry = void 0;
|
|
19515
|
+
}
|
|
19516
|
+
};
|
|
19517
|
+
|
|
19171
19518
|
// src/knowledge-config.ts
|
|
19172
19519
|
var HIWORK_KNOWLEDGE_SERVICE = "hiworkKnowledge";
|
|
19173
19520
|
var ME_KNOWLEDGE_CONFIG_PATH = "/api/me/knowledge-config";
|
|
@@ -19256,8 +19603,9 @@ var KnowledgeConfigProvider = class {
|
|
|
19256
19603
|
constructor(deps) {
|
|
19257
19604
|
this.deps = deps;
|
|
19258
19605
|
}
|
|
19259
|
-
|
|
19260
|
-
|
|
19606
|
+
configCache = new TtlCache({ ttlMs: KNOWLEDGE_CONFIG_TTL_MS });
|
|
19607
|
+
// 令牌缓存比配置缓存短 30s(KNOWLEDGE_TOKEN_CACHE_TTL_MS 已扣掉余量,见其注释)。
|
|
19608
|
+
tokenCache = new TtlCache({ ttlMs: KNOWLEDGE_TOKEN_CACHE_TTL_MS });
|
|
19261
19609
|
async token(opts) {
|
|
19262
19610
|
const force = opts?.force === true;
|
|
19263
19611
|
let sessionCookie;
|
|
@@ -19268,9 +19616,9 @@ var KnowledgeConfigProvider = class {
|
|
|
19268
19616
|
return failure12("error");
|
|
19269
19617
|
}
|
|
19270
19618
|
if (!sessionCookie) return failure12("unauthenticated");
|
|
19271
|
-
|
|
19272
|
-
|
|
19273
|
-
return hit
|
|
19619
|
+
if (!force) {
|
|
19620
|
+
const hit = this.tokenCache.get(sessionCookie, this.now());
|
|
19621
|
+
if (hit !== void 0) return hit;
|
|
19274
19622
|
}
|
|
19275
19623
|
const gateway = this.deps.gateway();
|
|
19276
19624
|
if (!gateway) return failure12("unreachable");
|
|
@@ -19295,7 +19643,7 @@ var KnowledgeConfigProvider = class {
|
|
|
19295
19643
|
return id ? [{ id, name: textOrUndefined7(obj["name"]) ?? "" }] : [];
|
|
19296
19644
|
}).filter((item) => knowledgeBaseIds.includes(item.id)) : [];
|
|
19297
19645
|
const view = { ok: true, token, audience, knowledgeBaseIds, knowledgeBases };
|
|
19298
|
-
this.
|
|
19646
|
+
this.tokenCache.put(sessionCookie, view, this.now());
|
|
19299
19647
|
return view;
|
|
19300
19648
|
} catch (error) {
|
|
19301
19649
|
const mapped = reasonOfErrorCode(error);
|
|
@@ -19315,10 +19663,8 @@ var KnowledgeConfigProvider = class {
|
|
|
19315
19663
|
}
|
|
19316
19664
|
if (sessionCookie === void 0 || sessionCookie === "") return failure12("unauthenticated");
|
|
19317
19665
|
if (!force) {
|
|
19318
|
-
const hit = this.
|
|
19319
|
-
if (hit !== void 0
|
|
19320
|
-
return hit.view;
|
|
19321
|
-
}
|
|
19666
|
+
const hit = this.configCache.get(sessionCookie, this.now());
|
|
19667
|
+
if (hit !== void 0) return hit;
|
|
19322
19668
|
}
|
|
19323
19669
|
const gateway = this.deps.gateway();
|
|
19324
19670
|
if (gateway === void 0) return failure12("unreachable");
|
|
@@ -19341,7 +19687,7 @@ var KnowledgeConfigProvider = class {
|
|
|
19341
19687
|
this.deps.log?.("hiwork-core: \u7F51\u5173\u8FD4\u56DE\u7684 knowledge-config \u4E0D\u5B8C\u6574\uFF08\u7F3A base_url / api_key \u4E4B\u4E00\uFF09");
|
|
19342
19688
|
return failure12("error");
|
|
19343
19689
|
}
|
|
19344
|
-
this.
|
|
19690
|
+
this.configCache.put(sessionCookie, view, this.now());
|
|
19345
19691
|
return view;
|
|
19346
19692
|
}
|
|
19347
19693
|
now() {
|
|
@@ -19465,7 +19811,7 @@ var MemoryConfigProvider = class {
|
|
|
19465
19811
|
constructor(deps) {
|
|
19466
19812
|
this.deps = deps;
|
|
19467
19813
|
}
|
|
19468
|
-
|
|
19814
|
+
cache = new TtlCache({ ttlMs: MEMORY_CONFIG_TTL_MS });
|
|
19469
19815
|
async config(opts) {
|
|
19470
19816
|
const force = opts?.force === true;
|
|
19471
19817
|
let sessionCookie;
|
|
@@ -19477,10 +19823,8 @@ var MemoryConfigProvider = class {
|
|
|
19477
19823
|
}
|
|
19478
19824
|
if (sessionCookie === void 0 || sessionCookie === "") return failure13("unauthenticated");
|
|
19479
19825
|
if (!force) {
|
|
19480
|
-
const hit = this.
|
|
19481
|
-
if (hit !== void 0
|
|
19482
|
-
return hit.view;
|
|
19483
|
-
}
|
|
19826
|
+
const hit = this.cache.get(sessionCookie, this.now());
|
|
19827
|
+
if (hit !== void 0) return hit;
|
|
19484
19828
|
}
|
|
19485
19829
|
const gateway = this.deps.gateway();
|
|
19486
19830
|
if (gateway === void 0) return failure13("unreachable", MESSAGE_NO_GATEWAY);
|
|
@@ -19503,7 +19847,7 @@ var MemoryConfigProvider = class {
|
|
|
19503
19847
|
this.deps.log?.("hiwork-core: \u7F51\u5173\u8FD4\u56DE\u7684 memory-config \u4E0D\u5B8C\u6574\uFF08\u7F3A panel_url / service_id / api_key \u4E4B\u4E00\uFF09");
|
|
19504
19848
|
return failure13("error");
|
|
19505
19849
|
}
|
|
19506
|
-
this.
|
|
19850
|
+
this.cache.put(sessionCookie, view, this.now());
|
|
19507
19851
|
return view;
|
|
19508
19852
|
}
|
|
19509
19853
|
now() {
|
|
@@ -19969,6 +20313,7 @@ var Config = Schema.object({
|
|
|
19969
20313
|
keyId: Schema.string().volatile(),
|
|
19970
20314
|
username: Schema.string().volatile(),
|
|
19971
20315
|
displayName: Schema.string().volatile(),
|
|
20316
|
+
sessionExpiresAt: Schema.string().volatile(),
|
|
19972
20317
|
lastLogin: Schema.object({ at: Schema.string().required(), modelCount: Schema.number().required(), gatewayName: Schema.string().required() }).default(void 0).volatile(),
|
|
19973
20318
|
lastError: lastErrorSchema.volatile(),
|
|
19974
20319
|
pendingLogin: Schema.object({ username: Schema.string().required(), passwordRef: Schema.string().required() }).default(void 0).volatile(),
|
|
@@ -20323,22 +20668,60 @@ function apply(ctx, config) {
|
|
|
20323
20668
|
}
|
|
20324
20669
|
};
|
|
20325
20670
|
let inFlight;
|
|
20326
|
-
|
|
20327
|
-
|
|
20328
|
-
|
|
20329
|
-
|
|
20330
|
-
|
|
20331
|
-
|
|
20332
|
-
|
|
20333
|
-
baseURL
|
|
20334
|
-
|
|
20335
|
-
|
|
20336
|
-
|
|
20337
|
-
|
|
20338
|
-
|
|
20339
|
-
|
|
20340
|
-
|
|
20671
|
+
const sessionSupervisor = new SessionSupervisor({
|
|
20672
|
+
getSession: async () => (await ports.credentials.resolve(SESSION_REF))?.value,
|
|
20673
|
+
getSessionExpiresAt: () => current().sessionExpiresAt,
|
|
20674
|
+
getBaseURL: () => current().baseURL,
|
|
20675
|
+
ping: async (sessionCookie) => {
|
|
20676
|
+
const baseURL = current().baseURL;
|
|
20677
|
+
if (baseURL === void 0 || baseURL === "") throw new Error("baseURL \u672A\u914D\u7F6E\uFF0C\u8DF3\u8FC7\u4F1A\u8BDD\u5FC3\u8DF3");
|
|
20678
|
+
const sessions = await gatewayFor(baseURL).listSessions(sessionCookie);
|
|
20679
|
+
const expiresAt = sessions.find((item) => item.current === true && item.expires_at !== void 0)?.expires_at;
|
|
20680
|
+
return expiresAt === void 0 ? {} : { expiresAt };
|
|
20681
|
+
},
|
|
20682
|
+
patchSessionExpiresAt: (iso) => ports.state.patch({ set: { sessionExpiresAt: iso } }),
|
|
20683
|
+
requestRecovery: () => ports.state.patch({ set: { refreshRequest: { at: (/* @__PURE__ */ new Date()).toISOString() } } }),
|
|
20684
|
+
log: (message) => {
|
|
20685
|
+
ctx.logger.warn(message);
|
|
20686
|
+
}
|
|
20687
|
+
});
|
|
20688
|
+
const sessionActivitySink2 = (event) => {
|
|
20689
|
+
if (event.kind === "success") sessionSupervisor.noteAuthedSuccess(event.expiresAt);
|
|
20690
|
+
else sessionSupervisor.reportAuthed401("gateway");
|
|
20341
20691
|
};
|
|
20692
|
+
ctx.effect(() => {
|
|
20693
|
+
setSessionActivitySink(sessionActivitySink2);
|
|
20694
|
+
sessionSupervisor.start();
|
|
20695
|
+
return () => {
|
|
20696
|
+
sessionSupervisor.stop();
|
|
20697
|
+
clearSessionActivitySink(sessionActivitySink2);
|
|
20698
|
+
};
|
|
20699
|
+
}, "hiwork-core: session supervisor");
|
|
20700
|
+
const policyPoller = createClientPolicyPoller({
|
|
20701
|
+
attempt: async () => {
|
|
20702
|
+
const baseURL = current().baseURL;
|
|
20703
|
+
if (baseURL === void 0 || baseURL === "") return false;
|
|
20704
|
+
let fetched = false;
|
|
20705
|
+
await projectLoginMode({
|
|
20706
|
+
baseURL,
|
|
20707
|
+
credentials: ports.credentials,
|
|
20708
|
+
state: ports.state,
|
|
20709
|
+
fetchPolicy: async (input) => {
|
|
20710
|
+
const policy = await fetchClientPolicy(input);
|
|
20711
|
+
fetched = policy !== void 0;
|
|
20712
|
+
return policy;
|
|
20713
|
+
},
|
|
20714
|
+
log: (message) => {
|
|
20715
|
+
ctx.logger.warn(message);
|
|
20716
|
+
}
|
|
20717
|
+
});
|
|
20718
|
+
return fetched;
|
|
20719
|
+
},
|
|
20720
|
+
log: (message) => {
|
|
20721
|
+
ctx.logger.warn(message);
|
|
20722
|
+
}
|
|
20723
|
+
});
|
|
20724
|
+
let notifySessionProvisioned;
|
|
20342
20725
|
const dispatchSectionChange = () => {
|
|
20343
20726
|
const value = current();
|
|
20344
20727
|
const requested = value.pendingLogin ?? value.browserLoginRequest ?? value.refreshRequest ?? value.logoutRequest;
|
|
@@ -20355,6 +20738,7 @@ function apply(ctx, config) {
|
|
|
20355
20738
|
ctx.logger.warn(message);
|
|
20356
20739
|
}
|
|
20357
20740
|
});
|
|
20741
|
+
if (value.pendingLogin !== void 0 || value.refreshRequest !== void 0) notifySessionProvisioned?.();
|
|
20358
20742
|
} catch (error) {
|
|
20359
20743
|
ctx.logger.error("hiwork-core: ai-router section change failed", error);
|
|
20360
20744
|
if (isHmrNestedWriteError(error)) {
|
|
@@ -20377,7 +20761,7 @@ function apply(ctx, config) {
|
|
|
20377
20761
|
};
|
|
20378
20762
|
ctx.effect(() => {
|
|
20379
20763
|
const consume = () => {
|
|
20380
|
-
|
|
20764
|
+
policyPoller.trigger();
|
|
20381
20765
|
dispatchSectionChange();
|
|
20382
20766
|
};
|
|
20383
20767
|
consume();
|
|
@@ -20385,6 +20769,7 @@ function apply(ctx, config) {
|
|
|
20385
20769
|
timer.unref?.();
|
|
20386
20770
|
return () => {
|
|
20387
20771
|
clearInterval(timer);
|
|
20772
|
+
policyPoller.dispose();
|
|
20388
20773
|
};
|
|
20389
20774
|
}, "hiwork-core: login request poll");
|
|
20390
20775
|
void ctx.root.loader.await().then(() => importLegacyMenuSection({
|
|
@@ -20394,7 +20779,7 @@ function apply(ctx, config) {
|
|
|
20394
20779
|
})).catch((error) => {
|
|
20395
20780
|
ctx.logger.warn(`hiwork-core: \u65E7\u7248\u83DC\u5355\u504F\u597D\u8FC1\u79FB\u672A\u6267\u884C\uFF08${error instanceof Error ? error.name : typeof error}\uFF09`);
|
|
20396
20781
|
});
|
|
20397
|
-
|
|
20782
|
+
policyPoller.trigger();
|
|
20398
20783
|
void (async () => {
|
|
20399
20784
|
for (const delay2 of STALE_BROWSER_LOGIN_RECONCILE_DELAYS_MS) {
|
|
20400
20785
|
if (delay2 > 0) await new Promise((resolve3) => {
|
|
@@ -20599,6 +20984,27 @@ function apply(ctx, config) {
|
|
|
20599
20984
|
clearInterval(timer);
|
|
20600
20985
|
};
|
|
20601
20986
|
}, "hiwork-core: workbench warmup");
|
|
20987
|
+
notifySessionProvisioned = buildSessionFanout({
|
|
20988
|
+
onPolicyRefresh: () => policyPoller.trigger(),
|
|
20989
|
+
onManagedMcpRefresh: () => {
|
|
20990
|
+
void managedMcp.load().catch((error) => {
|
|
20991
|
+
ctx.logger.warn(`hiwork-core: \u4F1A\u8BDD\u4F9B\u7ED9\u540E\u5237\u65B0\u53D7\u7BA1 MCP \u6E05\u5355\u5931\u8D25\uFF08${error instanceof Error ? error.name : typeof error}\uFF09`);
|
|
20992
|
+
});
|
|
20993
|
+
},
|
|
20994
|
+
onManagedSkillsRefresh: () => {
|
|
20995
|
+
void managedSkills.load().catch((error) => {
|
|
20996
|
+
ctx.logger.warn(`hiwork-core: \u4F1A\u8BDD\u4F9B\u7ED9\u540E\u5237\u65B0\u53D7\u7BA1\u6280\u80FD\u6E05\u5355\u5931\u8D25\uFF08${error instanceof Error ? error.name : typeof error}\uFF09`);
|
|
20997
|
+
});
|
|
20998
|
+
},
|
|
20999
|
+
onWorkbenchRefresh: () => {
|
|
21000
|
+
void workbench.load().catch((error) => {
|
|
21001
|
+
ctx.logger.warn(`hiwork-core: \u4F1A\u8BDD\u4F9B\u7ED9\u540E\u5237\u65B0\u5DE5\u4F5C\u53F0\u6E05\u5355\u5931\u8D25\uFF08${error instanceof Error ? error.name : typeof error}\uFF09`);
|
|
21002
|
+
});
|
|
21003
|
+
},
|
|
21004
|
+
log: (message) => {
|
|
21005
|
+
ctx.logger.warn(message);
|
|
21006
|
+
}
|
|
21007
|
+
});
|
|
20602
21008
|
const pmsTodoDeps = {
|
|
20603
21009
|
resolveSession: async () => (await ports.credentials.resolve(SESSION_REF))?.value,
|
|
20604
21010
|
gateway: () => {
|
|
@@ -20709,9 +21115,9 @@ function apply(ctx, config) {
|
|
|
20709
21115
|
}), "hiwork-core: plugin updates rpc");
|
|
20710
21116
|
ctx.effect(() => registerIpcMessageHandler({
|
|
20711
21117
|
source: process,
|
|
20712
|
-
complete: (input) => {
|
|
21118
|
+
complete: async (input) => {
|
|
20713
21119
|
const value = current();
|
|
20714
|
-
|
|
21120
|
+
const result = await completeBrowserLogin(input, {
|
|
20715
21121
|
...ports,
|
|
20716
21122
|
value,
|
|
20717
21123
|
gateway: gatewayFor(value.baseURL ?? ""),
|
|
@@ -20719,6 +21125,8 @@ function apply(ctx, config) {
|
|
|
20719
21125
|
ctx.logger.warn(message);
|
|
20720
21126
|
}
|
|
20721
21127
|
});
|
|
21128
|
+
if (result === null) notifySessionProvisioned?.();
|
|
21129
|
+
return result;
|
|
20722
21130
|
},
|
|
20723
21131
|
setDevice: (info) => {
|
|
20724
21132
|
setDeviceInfo(info);
|