hiwork-core 0.0.38 → 0.0.40
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/types/index.d.ts
CHANGED
|
@@ -53,6 +53,11 @@ export interface Config {
|
|
|
53
53
|
keyId?: string;
|
|
54
54
|
username?: string;
|
|
55
55
|
displayName?: string;
|
|
56
|
+
/**
|
|
57
|
+
* 网关会话的绝对寿命(ISO 8601;登录/授权兑换响应解析而来,Host 在供给成功时写入)。
|
|
58
|
+
* 未知时为 undefined——SessionSupervisor 退回「按假定寿命估算」而不是放弃保活。
|
|
59
|
+
*/
|
|
60
|
+
sessionExpiresAt?: string;
|
|
56
61
|
lastLogin?: {
|
|
57
62
|
at: string;
|
|
58
63
|
modelCount: number;
|
|
@@ -15,8 +15,8 @@ export declare function runLogin(opts: {
|
|
|
15
15
|
ensureWorkspace?: () => Promise<void>;
|
|
16
16
|
value: AiRouterNamespaceValue;
|
|
17
17
|
}): Promise<LoginError | null>;
|
|
18
|
-
/**
|
|
19
|
-
|
|
18
|
+
/** `runRefresh` 的入参(也原样传给内部的 `refreshWith`)。 */
|
|
19
|
+
interface RunRefreshOptions {
|
|
20
20
|
gateway: GatewayClient;
|
|
21
21
|
credentials: CredentialsPort;
|
|
22
22
|
piAi: PiAiPort;
|
|
@@ -26,7 +26,9 @@ export declare function runRefresh(opts: {
|
|
|
26
26
|
/** 透传给重登路径 runLogin 的可选端口。 */
|
|
27
27
|
ensureWorkspace?: () => Promise<void>;
|
|
28
28
|
value: AiRouterNamespaceValue;
|
|
29
|
-
}
|
|
29
|
+
}
|
|
30
|
+
/** 用已存会话刷新模型列表(会话过期时先试 refresh grant 静默重认证,再回落记住密码/登录门)。 */
|
|
31
|
+
export declare function runRefresh(opts: RunRefreshOptions): Promise<LoginError | null>;
|
|
30
32
|
/** 退出登录:best-effort 登出 + 清凭据与 pi-ai 配置。 */
|
|
31
33
|
export declare function runLogout(opts: {
|
|
32
34
|
gateway: GatewayClient;
|
|
@@ -48,6 +48,11 @@ export interface AiRouterNamespaceValue {
|
|
|
48
48
|
passwordRef: string;
|
|
49
49
|
};
|
|
50
50
|
rememberPassword?: boolean;
|
|
51
|
+
/**
|
|
52
|
+
* 网关会话的绝对寿命(ISO 8601;登录/授权兑换响应解析而来)。
|
|
53
|
+
* 未知(旧网关不下发过期属性)时不写该键——保活决策会退回「按假定寿命估算」。
|
|
54
|
+
*/
|
|
55
|
+
sessionExpiresAt?: string;
|
|
51
56
|
refreshRequest?: {
|
|
52
57
|
at?: string;
|
|
53
58
|
};
|
|
@@ -101,6 +106,8 @@ export declare function provisionSession(opts: {
|
|
|
101
106
|
user: GatewayUser;
|
|
102
107
|
/** 写入状态的账号名;缺省则不写(浏览器登录在网关未回传用户名时保留旧值)。 */
|
|
103
108
|
username?: string | undefined;
|
|
109
|
+
/** 网关回传的会话绝对寿命(ISO);缺省则不写该键(旧网关无过期信息)。 */
|
|
110
|
+
sessionExpiresAt?: string | undefined;
|
|
104
111
|
/** 成功时一并清掉的请求字段(表单登录 pendingLogin / 浏览器登录 browserLoginRequest)。 */
|
|
105
112
|
requestUnset?: readonly string[];
|
|
106
113
|
/** API_KEY 写好后触发的表单专属凭据整理(记住密码 / 临时密码);浏览器登录不传。 */
|
package/lib/types/provision.d.ts
CHANGED
|
@@ -7,6 +7,11 @@ export declare const SESSION_REF = "AI_ROUTER_SESSION";
|
|
|
7
7
|
export declare const PENDING_PASSWORD_REF = "AI_ROUTER_PENDING_PASSWORD";
|
|
8
8
|
/** 勾选“记住密码”后保存的凭据 ref(由 credentials provider 加密存储)。 */
|
|
9
9
|
export declare const REMEMBERED_PASSWORD_REF = "AI_ROUTER_REMEMBERED_PASSWORD";
|
|
10
|
+
/**
|
|
11
|
+
* 网关 refresh grant 的凭据 ref 名(M2 起 login/token 响应下发,登出/换新时清理)。
|
|
12
|
+
* 仅供 `runRefresh` 的静默重认证使用:绝不出现在状态或日志里,老网关没有它时静默弃用。
|
|
13
|
+
*/
|
|
14
|
+
export declare const REFRESH_TOKEN_REF = "AI_ROUTER_REFRESH_TOKEN";
|
|
10
15
|
/**
|
|
11
16
|
* 注册到 llm-pi-ai 的协议路由表:路由名 → pi-ai 协议。
|
|
12
17
|
* 网关按「入站协议 + 模型」匹配模型映射(internal/service/provider_service.go),
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 会话供给成功后的联动刷新(spec §4.2)。
|
|
3
|
+
*
|
|
4
|
+
* 会话刚建立 / 刚续上时,**按人 + 按会话**下发的派生清单必须立刻对齐一次:client policy、
|
|
5
|
+
* 受管 MCP、受管技能、工作台应用。这些清单各自的 30 分钟(策略是 6 小时)预热只保证
|
|
6
|
+
* 「最迟那么久之后」收敛;换了账号、会话重建、或长期挂机后重新登录,旧清单在预热窗口内
|
|
7
|
+
* 就是错的。所以供给成功是一条事件,而不是等下一个定时器。
|
|
8
|
+
*
|
|
9
|
+
* 这里把「四件事依次触发」收成一个纯函数:任一 handler 抛异常只记日志、不影响其余
|
|
10
|
+
* ——刷新是尽力而为的旁路,绝不能反过来把登录 / 续期主链路带崩。
|
|
11
|
+
*/
|
|
12
|
+
export declare function buildSessionFanout(handlers: {
|
|
13
|
+
onPolicyRefresh(): void;
|
|
14
|
+
onManagedMcpRefresh(): void;
|
|
15
|
+
onManagedSkillsRefresh(): void;
|
|
16
|
+
onWorkbenchRefresh(): void;
|
|
17
|
+
/** 中文日志出口;缺省静默(handler 自身已各有吞错日志,这是第二道网)。 */
|
|
18
|
+
log?: (message: string) => void;
|
|
19
|
+
}): () => void;
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 会话心跳保活(spec §4.1b)。
|
|
3
|
+
*
|
|
4
|
+
* 网关会话是**服务端滑动续期**的:任意已认证请求在剩余寿命 < TTL/2 时会被续满。但桌面端
|
|
5
|
+
* 长时间挂机(不点任何功能)就不会发出任何已认证请求,续期永远不会触发,24h 后会话必过期。
|
|
6
|
+
* 本模块用一个兜底定时器(每小时醒一次),在「近期没有任何已认证请求成功」且「会话可能快
|
|
7
|
+
* 到期」时主动打一个轻量会话请求,把会话按住。
|
|
8
|
+
*
|
|
9
|
+
* 寿命预算:客户端不一定知道服务端真实 TTL(旧网关的 Set-Cookie 不带过期属性、`/api/auth/token`
|
|
10
|
+
* 与表单登录也不回 expires_at),所以用 {@link SESSION_TTL_ASSUMPTION_MS}(24h,与服务端缺省
|
|
11
|
+
* `session.duration` 一致)作为**安全上界**估算:只要在「剩余不足假定寿命一半」时打一次心跳,
|
|
12
|
+
* 服务端就一定会把它续满。新网关在带会话的成功响应里下发 `X-Session-Expires-At`,
|
|
13
|
+
* 心跳响应也可能带精确寿命——这些值先进内存估计({@link SessionSupervisor.noteAuthedSuccess}),
|
|
14
|
+
* 用来判定「还要不要打心跳」;只有心跳成功才写 settings(热路径零写入)。
|
|
15
|
+
*
|
|
16
|
+
* 只读端口 + 单向动作:本模块不持有任何凭据、不写日志里的会话值;会话已失效(401)的恢复
|
|
17
|
+
* 动作由调用方经 401 汇聚接线(Task A3),这里只记中文日志、绝不向宿主抛。
|
|
18
|
+
*/
|
|
19
|
+
/** 假定会话总寿命(服务端 `session.duration` 缺省 24h;滑动续期的安全上界)。 */
|
|
20
|
+
export declare const SESSION_TTL_ASSUMPTION_MS: number;
|
|
21
|
+
/** 心跳兜底检查间隔(1h)。 */
|
|
22
|
+
export declare const SESSION_CHECK_INTERVAL_MS: number;
|
|
23
|
+
/**
|
|
24
|
+
* 会话恢复的冷却窗口(60s):多通道并发 401(voice / usage / MCP …)只触发一次恢复,
|
|
25
|
+
* 且恢复动作本身(写 refreshRequest → 静默重登或开登录门)不会在窗口内被反复排队。
|
|
26
|
+
*/
|
|
27
|
+
export declare const SESSION_RECOVERY_COOLDOWN_MS = 60000;
|
|
28
|
+
export interface SessionSupervisorDeps {
|
|
29
|
+
/** 解析当前会话 cookie(未登录返回 undefined)。 */
|
|
30
|
+
getSession: () => Promise<string | undefined>;
|
|
31
|
+
/** 当前已知的会话绝对寿命(ISO 8601);未知返回 undefined。 */
|
|
32
|
+
getSessionExpiresAt: () => string | undefined;
|
|
33
|
+
/** 网关根地址;未配置返回 undefined(没配置就一个请求都不该发)。 */
|
|
34
|
+
getBaseURL: () => string | undefined;
|
|
35
|
+
/** 轻量会话请求:会话已失效时抛 `GatewayError('SESSION_EXPIRED')`;带服务端下发的寿命时回传。 */
|
|
36
|
+
ping: (sessionCookie: string) => Promise<{
|
|
37
|
+
expiresAt?: string;
|
|
38
|
+
} | undefined>;
|
|
39
|
+
/** 心跳成功后回写会话寿命(ISO):服务端给了精确值就用它,否则是估算出的安全上界。 */
|
|
40
|
+
patchSessionExpiresAt: (iso: string) => Promise<void>;
|
|
41
|
+
/**
|
|
42
|
+
* 会话已失效时触发一次恢复:宿主写 `refreshRequest`,由既有 200ms 消费循环里的
|
|
43
|
+
* `runRefresh` 接手(记住密码静默重登 / 清会话 / 写 lastError 开登录门)。
|
|
44
|
+
* 本模块**不新增恢复逻辑**,只负责去抖后调一次。
|
|
45
|
+
*/
|
|
46
|
+
requestRecovery: () => Promise<void>;
|
|
47
|
+
/** 时钟注入(测试用);缺省 `Date.now`。 */
|
|
48
|
+
now?: () => number;
|
|
49
|
+
/** 中文日志出口;**绝不记录会话 cookie 值**。缺省静默。 */
|
|
50
|
+
log?: (message: string) => void;
|
|
51
|
+
}
|
|
52
|
+
export declare class SessionSupervisor {
|
|
53
|
+
private readonly deps;
|
|
54
|
+
private timer;
|
|
55
|
+
/** 上次「已知会话存活」的时刻;undefined = 本进程内还没确认过(重启后的首次检查照打)。 */
|
|
56
|
+
private lastAuthedSuccessAt;
|
|
57
|
+
/**
|
|
58
|
+
* 最近一次带会话成功响应里 `X-Session-Expires-At` 给出的**内存**寿命估计(原样时刻串)。
|
|
59
|
+
* 只活在内存里——热路径绝不写 settings(Review Focus 5),只有心跳成功才落盘。
|
|
60
|
+
*/
|
|
61
|
+
private lastHeaderExpiresAt;
|
|
62
|
+
/** 恢复请求是否在途(单飞去抖)。 */
|
|
63
|
+
private recoveryInFlight;
|
|
64
|
+
/** 上次触发恢复的时刻(冷却窗口基准)。 */
|
|
65
|
+
private lastRecoveryAt;
|
|
66
|
+
constructor(deps: SessionSupervisorDeps);
|
|
67
|
+
/** 开始兜底检查;重复调用不叠加定时器。 */
|
|
68
|
+
start(): void;
|
|
69
|
+
/** 停止兜底检查(调用后零活动)。 */
|
|
70
|
+
stop(): void;
|
|
71
|
+
/**
|
|
72
|
+
* 带会话的请求成功 → 记活动时间(由 ai-router-client 的活动汇聚驱动)。
|
|
73
|
+
* `expiresAt` 为响应头 `X-Session-Expires-At` 给出的会话寿命:**只更新内存估计**,
|
|
74
|
+
* 绝不在此写 settings——热路径上每个成功请求都写盘会打爆设置面(Review Focus 5)。
|
|
75
|
+
* 不可解析的值直接丢弃(当作没给),不覆盖既有估计。
|
|
76
|
+
*/
|
|
77
|
+
noteAuthedSuccess(expiresAt?: string): void;
|
|
78
|
+
/**
|
|
79
|
+
* 带会话的请求收到 401 → 去抖后触发一次会话恢复。
|
|
80
|
+
* 去抖有两层:`recoveryInFlight`(单飞,覆盖「恢复请求还没落地,同刻又来了 5 条 401」)
|
|
81
|
+
* 与 `SESSION_RECOVERY_COOLDOWN_MS` 冷却(覆盖恢复完成后短时间内的余波 401)。
|
|
82
|
+
* 恢复失败只记日志——apply 后写入面需要几秒才就绪,那一侧的失败不该惊动宿主。
|
|
83
|
+
*/
|
|
84
|
+
reportAuthed401(source: string): void;
|
|
85
|
+
private now;
|
|
86
|
+
private log;
|
|
87
|
+
/**
|
|
88
|
+
* 一次兜底检查。判定顺序(早退即不发请求):
|
|
89
|
+
* 无会话 / 无 baseURL → 返回;距上次已认证成功不足 TTL/2 → 返回;
|
|
90
|
+
* 已知剩余寿命 > TTL/2 → 返回;否则 ping。
|
|
91
|
+
* 寿命未知也 ping——重启后的首次检查即校验会话是否还活着。
|
|
92
|
+
* 「已知剩余寿命」取 settings 值与内存估计(响应头)的**较大者**:头值只会更晚更准,
|
|
93
|
+
* 用较大者才不会因为 settings 里的旧估算偏早而白打心跳。
|
|
94
|
+
*/
|
|
95
|
+
private wake;
|
|
96
|
+
/**
|
|
97
|
+
* 已知的会话到期时刻(epoch ms):settings 值与内存头值里**有效且较晚**的那个;
|
|
98
|
+
* 两者都没有(或都不可解析)返回 undefined = 寿命未知。
|
|
99
|
+
*/
|
|
100
|
+
private knownExpiresAtMs;
|
|
101
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 会话绑定的 TTL 缓存原语(spec §4.4)。
|
|
3
|
+
*
|
|
4
|
+
* knowledge / memory / image / usage 各自手写过一遍「会话配 TTL」的内存缓存,语义已经
|
|
5
|
+
* 开始漂移。原语只做三件事,把「成功才 put、失败不 put、force 旁路」留给调用方:
|
|
6
|
+
* - 值与**会话键**绑定:读别人的会话键一定 miss(换账号绝不串号);
|
|
7
|
+
* - 命中条件是 `now - putAt < ttlMs`(严格小于,与既有实现逐字一致);
|
|
8
|
+
* - `invalidate` 支持全清与单键清理。
|
|
9
|
+
*
|
|
10
|
+
* **只保留一条条目**(不是每个键一条):这与迁移前的既有实现逐字等价——写入新会话的
|
|
11
|
+
* 缓存会顶掉旧会话那条。这条语义被 knowledge 的既有测试钉着(换账号再换回来必须重新
|
|
12
|
+
* 出网),也是「一次只服务一个登录账号」的诚实表达,故不改成 Map。
|
|
13
|
+
*
|
|
14
|
+
* 余量(skew)不在这里做:不同调用方的提前量不同(如 knowledge 令牌要扣 30s),
|
|
15
|
+
* 由调用方在 `ttlMs` 里自己扣——原语只认一个 TTL,不猜语义。
|
|
16
|
+
*/
|
|
17
|
+
export declare class TtlCache<V> {
|
|
18
|
+
private readonly ttlMs;
|
|
19
|
+
private entry;
|
|
20
|
+
constructor(opts: {
|
|
21
|
+
ttlMs: number;
|
|
22
|
+
});
|
|
23
|
+
/** 命中返回缓存值;会话键不同或已到期返回 undefined。`now` 可注入(缺省 Date.now)。 */
|
|
24
|
+
get(sessionKey: string, now?: number): V | undefined;
|
|
25
|
+
/** 写入/覆盖缓存(新会话键会顶掉旧会话那条)。 */
|
|
26
|
+
put(sessionKey: string, value: V, now?: number): void;
|
|
27
|
+
/** 清掉指定会话键的条目(不影响别的键,虽然只会存在一条);不传参数则清空。 */
|
|
28
|
+
invalidate(sessionKey?: string): void;
|
|
29
|
+
}
|
package/lib/types/types.d.ts
CHANGED
|
@@ -11,5 +11,11 @@ export interface DeviceSession {
|
|
|
11
11
|
client_ip: string;
|
|
12
12
|
created_at: string;
|
|
13
13
|
last_seen_at?: string | null;
|
|
14
|
+
/**
|
|
15
|
+
* 该会话的绝对到期时刻(ISO 8601,网关将来下发时才有)。
|
|
16
|
+
* 当前服务端不回该字段:字段缺失时保持现有行为(不据此做任何判定),将来下发后
|
|
17
|
+
* 心跳即可顺带校验「本机设备会话是否仍在列表且未过期」。
|
|
18
|
+
*/
|
|
19
|
+
expires_at?: string;
|
|
14
20
|
current: boolean;
|
|
15
21
|
}
|