kczx-user-management
v1.5.6
Published
dsh 插件 · 登录门 + 账号 / 权限 / 配额 / 审计台账(dsh-passwords 的功能已并入):HTTPS 登录网关与自签证书、用户增删改查(可一并创建工作区)、工作区白名单与归属跟踪、每小时 token 与每日时长配额、沙盒档位下限、上传/git 开关、三本审计台账(登录/访问/操作)、IP 封禁、TOTP 两步验证。
Maintainers
Readme
kczx-user-management
本包是本地扩展版(1.0.0),已把 dsh-passwords 的账号 / 权限 / 配额 / 台账能力并入,不再是上游 0.9.5 的功能集。 上游同名包(npm 上的
@weibaohui/user-management)仅提供登录门禁与两步验证;本版额外提供工作区白名单与归属跟踪、新建账号即建工作区、 token 与时长配额、沙盒档位下限、上传 / git / 运维面门控、历史沙盒降级与隐藏字符清洗。 为便于从 registry 安装,本版以独立包名kczx-user-management发布,与上游互不覆盖。
给 dsh web 装一扇门:HTTPS 登录网关 + 账号管理 + 权限与配额执行 + 三本审计台账,一个插件完成。
安装
# 从 registry(发布之后;-w = pnpm 的 --workspace-root)
dsh plugin --profile web add kczx-user-management -w
# 从 tarball(离线 / 内网部署)
dsh plugin --profile web add ./kczx-user-management-1.0.0.tgz -w
# 或直接从目录 / 仓库(相对路径以你执行命令的目录为锚)
dsh plugin --profile web add link:E:\path\to\dsh-user-management -w
dsh plugin --profile web add github:you/dsh-user-management -w
# 装完重启 dsh web 生效dsh plugin --profile <name> <args...> 就是在 ~/.dsh/profiles/<name> 里转发 pnpm <args...>;退出码 0 时按已安装状态
对账 dsh.profile.bundles——凡是声明了 dsh.bundle.patch 的依赖会自动进 layer 列表(本包含),所以不必手工改 bundles。
用 pnpm 直接安装
包内自带 dsh.bundle.patch,运行期依赖(@deepseek-ai/schemastery、bcryptjs、selfsigned)都在公共 registry 上,因此也可以当普通 npm 包装:
pnpm add kczx-user-management # 已发布到 registry 时
pnpm add ./kczx-user-management-1.0.0.tgz # 从 tarball
pnpm add /abs/path/to/dsh-user-management # 从目录装进 dsh profile(把下面几行加进 ~/.dsh/profiles/web/package.json,再在该目录 pnpm install,然后重启 dsh web):
{
"dependencies": { "kczx-user-management": "file:./kczx-user-management-1.0.0.tgz" },
"dsh": { "profile": { "bundles": ["kczx-user-management"] } }
}本包不依赖安装期脚本:postinstall 已移除(pnpm ≥10 默认拦截依赖构建脚本,留着只会让安装以
ERR_PNPM_IGNORED_BUILDS 退出码 1 收场),ensure:deps 保留为手动脚本,仅供「内网无 registry
且以 link: 挂载」的场景,从宿主补齐 @deepseek-ai/schemastery:
pnpm add ./kczx-user-management-1.0.0.tgz # 正常路径:依赖从 registry 装齐
node scripts/ensure-deps.mjs # 仅当上述解析不到 schemastery 时才需要首次使用(初始化)
自助注册是关闭的:账号只能由管理员在设置页创建,第一个管理员走"初始化"流程。
启动 dsh。库里还没有任何账号时,启动日志会打印一行初始化密钥:
[user-management] first-run setup key 3f0c… (also in ~/.dsh/user-management/setup-key.txt)密钥同时写在
$DSH_HOME/user-management/setup-key.txt;也可以用settings.yaml的user-management.setupKey自行指定(那样不会往磁盘写密钥文件)。浏览器打开
https://<主机>:8088/login—— 此时显示初始化页:填初始化密钥 + 用户名 + 密码。提交后该账号直接成为管理员并登录,初始化密钥当场销毁(删文件、清内存);此后
/login只显示登录表单,/user-management/api/setup拒绝一切后续调用。
登录页样式与 dsh-passwords 一致(光晕背景 + 网格 + 玻璃卡片 + 渐变按钮),跟随宿主主题深浅色,右上角可切换中文 / English。 品牌可配:标题 / 副标题 / 页脚 / LOGO / 主色 / 默认语言都在
user-management:段里,见「配置」一节。
它做什么
一扇门
独立的 node:https 监听器(默认 0.0.0.0:8088),自签证书(SAN 覆盖本机全部 IP + sslip.io / nip.io 别名),
未登录的页面访问跳转 /login、API 请求 401;登录页自带深浅色。设置页提供证书下载(PEM/DER)、指纹核对与
各系统导入向导。
账号与权限(管理员)
| 能力 | 说明 |
|---|---|
| 用户增删改查 | 用户表每行一个「编辑」全屏页:基本资料(部门 / 角色)+ 账号操作(重置密码 / 重置两步验证 / 禁用启用 / 删除)+ 权限与配额,集中一屏;改动类走「保存」,破坏性操作仍是各自带确认的按钮 |
| 所属部门 | 账号资料字段(trim 后 ≤ 64 字,控制字符剔除):新增用户时可填,编辑页可随时改或清空;变更写进操作日志 |
| 新建账号即建工作区 | 新增用户时勾选「同时创建该用户的工作区」:路径预填 <harness 根>/<部门>/<用户名>(可改成任意绝对路径);提交后目录按需创建、登记进 dsh 工作区列表,并把该路径写进这个账号的工作区白名单 |
| 工作区白名单 | 留空 = 不允许任何工作区(白名单默认拒绝);填写后只允许这些目录下的工作区;条目 * = 允许全部。Windows 路径折叠大小写,末尾斜杠自动忽略 |
| 工作区归属跟踪 | 谁创建的工作区归谁:别人看不到、也不能删改;删除后释放,账号删除后清理 |
| 每小时 token 配额 | 由浏览器上报 dsh 自身的用量投影增量,服务端按小时窗口累计并拦截 |
| 每日时长配额 | 按真实活跃时间累计(不是请求数),限流不因轮询膨胀 |
| 沙盒档位下限 | read-only / workspace-write / danger-full-access:AI 无法通过设置、slash 命令或审批把自己提权 |
| 上传 / git 开关 | 上传与「把数据带走」的通道分别控制(git RPC、会话导出、SSH 下载等) |
| 策略封禁 | 与「禁用」不同:执行层每次请求都读,封禁即刻踢出会话 |
三本台账
| 台账 | 内容 |
|---|---|
| 登录记录 | 登录 / 失败 / 登出 / 改密 / 初始化(首次建号)/ 建号 / 删号 / 角色变更 / 资料变更 / 权限变更(含紧凑差异串);按页浏览 |
| 访问记录 | 页面级访问(保留真实客户端 IP);按页浏览 |
| 操作日志 | 经过网关的每次 API 调用与 WebSocket 连接(方法 / 路径 / 状态 / 来源 IP);对话类请求连内容一起记:session.prompt 的提示词、session.rename 的会话标题写进同一行(最多 500 字,点开展开;粘贴的图片只记数量、不存 base64)。列表多一列「对话内容」,并按页浏览(默认每页 50,可选 20/50/100/200) |
另有 IP 封禁(回环与当前 IP 拒绝封禁,防自锁)与 TOTP 两步验证(默认关闭,可选开启)。
分页:登录记录 / 访问记录 / 操作日志 / 用户表四张表都是服务端分页——查询参数带 limit + offset,
响应回 { ..., total },前端底部是共用的分页控件(上一页 / 第 X/Y 页 · 共 N 条 / 下一页 / 每页 20·50·100·200,
默认 50)。筛选同样在服务端执行,所以「共 N 条」永远是匹配总数而不是当前页条数。账号表只有在调用方
传了 limit 时才分页,不传仍然整份返回(后端脚本的老用法不受影响)。
配置
走 ~/.dsh/settings.yaml 的 user-management: 段。改完即时生效的字段无需重启;只有监听器字段
(listenHost / port / plaintext / sites)会触发监听器重建,其余一律下一个请求就用新值。
user-management:
enabled: true
listenHost: '0.0.0.0' # 只本机可达就填 127.0.0.1
port: 8088
plaintext: false # true 时只允许回环(明文服务观察层用,见 MERGED-DEPLOYMENT.md)
sites: [] # 空 = 自动枚举本机 IP;配域名 + 证书则按 SNI 选择
setupKey: '' # 首次初始化密钥;留空 = 启动时随机生成并写到 setup-key.txt
historyMaxBytes: 67108864 # 历史改写缓冲上限(字节,默认 64 MiB,最小 1 MiB)
# ── 登录页品牌(全部即时生效,改完刷新 /login 即可) ──
title: 'DSH 控制台' # 部署名:标签页标题后缀 + 标题兜底
brandName: '' # 卡片主标题;只填它,门面就是你的产品名
loginTitle: '' # 逐行覆盖内置文案('' = 用内置的「登录」/ Sign in)
loginSub: ''
setupTitle: '' # 同上,作用于首次「初始化平台」表单
setupSub: ''
footerText: '' # '' = 内置的「dsh 插件 · user-management」
showFooter: true
logoUrl: '' # http(s)、data: URI,或本机图片文件;'' = 内置标记
logoSize: 48 # 标记方框边长(px,24–96);图片按 contain 铺满方框
auditConversationText: true # 操作日志记录 session.prompt 文字与会话标题(见下)
brandColor: '' # '#rrggbb';只重染品牌色 token,danger/ok/warn 不动
defaultLang: 'zh' # 不带 ?lang= 时的门面语言(zh | en)
showLangSwitch: true # 是否显示页内中英切换
# ── 会话与请求策略(全部即时生效) ──
sessionDays: 7 # 会话有效期(天,滑动续期:过半后续一次)
maxBodyBytes: 65536 # 本插件 API 的 JSON 请求体上限(字节)
workspaceRoot: '' # 新建账号工作区的根路径;'' = 问宿主(dsh 的工作区根,默认即 harness 所在目录)
otpFailLimit: 5 # 两步验证连续错误几次后锁定
otpLockoutSeconds: 60 # 锁定多久登录页的品牌只吃
brandName/loginTitle这类字段,改名不会影响账号与台账;brandColor只重染 品牌色那几个 token(按钮、LOGO 底、聚焦环及其阴影),状态色保持不变——颜色承载语义,不该被顺手改掉。LOGO 支持本机图片(1.1.2 起):
logoUrl填绝对路径(E:/brand/logo.png)或$DSH_HOME/user-management/下的文件名(把图片放到 users.json 旁边,然后填logo.png)。裸路径本身不是 URL,浏览器会拿它当相对路径去 请求网关,所以本机图片由插件用/user-management/branding/logo这条公开路由回吐给登录页(和 /login 一样 必须免登录,否则图片是 401);该路由只吐这一个配置文件,且限定图片扩展名、非空、≤ 512 KiB,其余一律 404, 路径下的其它子路径在本机就直接 404,不会被匿名转发到 dsh 上游。改图不用重启——路由每次现读文件。图片铺满方框:
logoSize控制方框边长(默认 48px,24–96),图片用object-fit: contain填进去——方图正好 铺满,长图/竖图留白而不裁切。这是品牌字段里唯一会改布局的一个,调大小在设置页里改完刷新即可,不用重启。 未接线的占位字段仍有loginFailLimit/lockoutSeconds(登录失败不自动锁定,用 IP 封禁代替), 保留只是为了老的settings.yaml继续可解析。
操作日志的分页(1.3.0 起)
台账上限 5000 行、每行还带对话文字,一次全拉进浏览器既慢又没必要。现在「操作日志」是服务端分页:
GET /user-management/api/audit?limit=50&offset=100 返回 { entries, total, limit, offset }——除了这一页,
还给出匹配总数,前端才说得出「第 3/40 页 · 共 1987 条」。表格下方有上一页 / 下一页和每页条数(20/50/100/200)。
两个容易做错的点,都按服务端口径处理:
- 筛选也走服务端,和分页窗口是同一组参数:
username/method/path/statusClass。若在前端过滤已取回的 那一页,翻页翻的就是"某个结果的子集",而且"共 N 条"只能是这一页的数字——那是错的。筛选条件改动会回到第 1 页 (换条件后停在旧页码没有意义),输入框有 250ms 去抖,不会每敲一个字发一次请求。 - 删掉最后一条时若当前页只剩那一行,会退回上一页,而不是停在"第 N 页(空的)"。
操作日志里的对话内容(1.2.0 起)
「用户管理 → 操作日志」每一行是经过网关的一次 API 调用。以前只看得到 POST /api/session.prompt——可这一行不论用户说了什么都长一个样,
日志于是回答不了「这个账号到底做了什么」。现在网关把人打的那句话从请求体里取出来,随行写进台账:
session.prompt→content[]里所有type: 'text'片段按行拼接;type: 'image'只记数量(截图是 base64,不是台账该存的东西)。session.rename→ 会话标题。- 其它 RPC 完全不碰请求体,仍按原来的方式流式转发。
- 存储侧每行最多 500 字(
MAX_AUDIT_TEXT,超长截断);前端那一列默认单行省略、点一下就地展开全文,图片数量显示为徽标。 - 只观察、不拦截:抓取只是顺路把请求体缓一下再原样重放给上游,字节不变;超过 2 MiB 的请求体(例如带大图的提示词)原样透传、不记录文字。
- 隐私开关:
user-management.auditConversationText: false(设置页「会话与请求策略」里也有这一项)关掉即完全不抓。台账是$DSH_HOME/user-management/audit.jsonl明文文件,提示词可能含口令 / 密钥时请关闭。
新建用户时一并创建工作区(1.5.0 起)
「用户 → 新增用户」多了一个复选框「同时创建该用户的工作区」。勾上后出现路径框,预填默认路径
<harness 根>/<部门>/<用户名>(部门留空就少一层,只到用户名),可以直接改成任意绝对路径;下方一行小字写着默认值,
改过之后还有「恢复默认」。不勾选则一切照旧。
提交时服务端按这个顺序做事,能失败的都在写账号之前失败:
- 校验路径:自定义路径必须以盘符或
/开头(相对路径会报错,不会被悄悄拼到进程 cwd 上);根路径本身 (harness 目录)不能当工作区;路径长度 ≤ 1024。部门是管理员随手写的自由文本,所以它会被折成一段合法的目录名 (\/:*?"<>|与控制字符 →-,纯点号(..)直接丢掉),绝不允许它多出一层目录或跳出根路径。 - 归属冲突:这个路径已经属于别的账号 → 409,账号和目录都不会建。既有的目录(别人放的文件)会被领养而不是重新创建。
- 建目录(
mkdir -p,已存在则复用);失败则 400,不建账号。 - 建账号,并把该路径写进它的工作区白名单(
allowedFolders)。白名单是默认拒绝的:不放开的话,账号 根本看不到自己那块地。第 3 步建出来的空目录在账号写入失败时会被回收(只删空目录)。 - 登记进 dsh 工作区列表:通过宿主的
workspaceRegistry服务(运行期ctx.inject,不写死依赖)。 这一步失败不回滚——目录和账号都是真的,接口把registered: false和原因一起返回,界面提示 「用户已创建、目录已建好,但未登记进工作区列表」。dsh 的工作区列表用 realpath 后的规范路径,所以登记返回的 规范路径也会一并加进白名单(软链接根目录 / Windows 大小写差异下,账号才看得到自己的那块地)。 - 认领归属:把该路径记到这个账号名下——别人看不到、也不能删改(见「工作区归属跟踪」)。
接口:POST /user-management/api/users 增加 createWorkspace: true 与可选的 workspacePath(留空 = 服务端按默认规则生成),
响应多一个 workspace 报告({ ok, path, source, directory, registered, owner, error },没勾选时为 null);
GET /user-management/api/workspace-root(管理员)回 { root, sep },新增用户弹窗用它预填路径。
根路径优先取 user-management.workspaceRoot,未配置则问宿主的 sandboxPolicy.workspaceRoot(dsh 默认就是进程 cwd,
即 harness 所在目录),最后兜底 process.cwd()。
被拒绝时提示什么(1.5.1 起)
被网关拦下的请求(没有建工作区的权限、目录不在白名单、配额用完、上传 / git 被关、沙盒提权被拒……)过去一律回 HTTP 403。 而 dsh 的浏览器客户端把任何非 2xx 都当成传输故障、根本不读响应体,于是用户看到的是:
workspace create failed: internal: transport failure for /api/workspace.create: HTTP 403
这读起来像程序坏了,而不像「你的账号没有这个权限」。现在这类拒绝改走 RPC 协议自带的错误位:HTTP 200 + result.ok: false,
原因写在 error.message 里,各处 dsh 界面都会原样显示:
workspace create failed: internal: 本账号没有创建 / 管理工作区的权限(请联系管理员在「用户管理 → 编辑用户 → 权限与配额」中开启「允许创建工作区」)
只有不是 RPC 调用的请求(没有 rpcId 可回显的裸请求、GET)仍回原来的 403 JSON —— 给它们编一个 rpcId 只会把「被拒绝」
变成客户端的协议错误,比原样回报更糟。
顺带修好一个盲区:被拒绝的请求以前完全不进操作日志(它没到上游,写日志的钩子永远不会触发)。现在会记一行
403 + 已拒绝:<原因>,管理员在「用户管理 → 操作日志」里直接看得到谁、想做什么、为什么被挡下来。
控制台页头的位置(1.5.3 起)
「用户管理」整页原来的页头是一条横贯屏幕的标题栏:宽屏上标题孤零零贴在左上角、按钮贴在右上角,和下面的 tab 条与内容列毫无关系。现在标题与 tab 收进居中内容列,而「退出登录」单独钉在窗口的右上角:
[⏻ 退出登录] ← 贴窗口右上角(与「编辑用户」页那颗同一位置)
用户管理 ← 内容列内,与 tab 条左对齐
[用户][登录记录][访问记录][操作日志][IP 封禁][证书][2FA] [返回设置]
…表格 / 台账…「用户管理」这一行是「这一页是什么」,与内容左对齐;「返回设置」与 tab 条同一条线、右边缘对齐。退出登录之所以 不跟着内容列走,是因为上一层「编辑用户」页把自己的退出登录放在窗口右上角——同一个按钮不能因为进出一层就横向挪位。 普通用户没有 tab 条,但「返回设置」仍然落在同一条线上(右对齐),两个视图因此读起来还是同一页。观察者模式 (dsh-passwords 在前)没有退出登录,只有「返回设置」。
「退出登录」现在先问一句(确定退出登录?退出后需要重新输入用户名和密码。):控制台页头和账号菜单里各有一个入口,
且都离用户正在做的事只差一次误点——确认放在共用的 doLogout() 里,谁都绕不过去。
「编辑用户」整页自带一个退出登录,钉在页面最右上角(关闭 / 保存之后隔一条竖线,不跟表单按钮混在一起):
┌ 编辑用户 · bob ─────────────────────────────────────────────┐
│ [关闭] [保存] │ [⏻ 退出登录] │
└─────────────────────────────────────────────────────────────┘同一时刻屏幕上只有一个退出登录:打开这一页时,控制台那个窗口右上角的退出登录收起来(位置本来就是同一个,只是避免两颗同时存在)。不透明浮层确实盖住了它,
但「盖住」不等于「不在文档里」——那时它离一次 Tab+Enter 只有一步,而这颗按钮结束的是整个会话。收放由一个模块级的
小订阅(setFullPageOverlay)广播,编辑页挂载时置位、卸载时清除。
欢迎页右上角的账号信息(1.5.4 起)
会话头部只在有会话时才存在,于是落地页(欢迎页 / 新建会话页)成了唯一一个不说「你是谁」的页面——连账号菜单里
仅有的两件事(改密码、退出登录)也没地方点。现在账号芯片(头像 + 用户名,点开即账号菜单)还会落在shell 的全局
overlay 座位(ui-layout 的 shell.overlay,一个可叠加的 list 槽)上,固定在窗口右上角;一旦有会话成为当前会话,
这个芯片立刻消失,把位置让回会话头部那一个,不会出现两个。
实现上用的是该座位给每个全局占用者注入的 useSessions:state.current 就是框架自己对「现在有没有打开会话」的回答,
不需要去偷看 DOM。hero 上的三个座位(品牌标记 / 工作区选择器 / 代理预设)都是单占位且已被占用,所以走 overlay 座位。
在设置页里改(1.1.0 起)
上面这些字段也可以在 设置 → 通用设置 里改,就在「语言」「外观」那几行下面:「登录页与策略配置」一行,
默认收起着(通用列里都是紧凑的单行偏好,十六个字段全展开会把这一页压垮),行内摘要写着登录页当前显示
什么、有几项被改过,点「展开」出现完整表单。本插件在浏览器侧把这个设置命名空间绑成
settingsScope.bind({ namespace: 'user-management' }),并注册进 settings.general.item 槽——宿主注册
命名空间、浏览器注册控件,两边经由同一份 settings 文档同步。
表单行为:改过没存的字段标「未保存」;写入过 settings.yaml 的字段标「已自定义」并带「恢复默认」
(unset,清掉覆盖回到内置值,也让 settings.yaml 保持干净);文本框留空等于清除覆盖;数字字段按 schema
的下限校验(比如请求体上限 ≥ 1024);保存按字段顺序逐个写入,任何一个值非法就整体不落盘并提示是哪一项。
远程浏览器访问时设置是只读的(settings RPC 只走回环),这一行会直接说明并让你改服务器上的 settings.yaml;
装了旧版插件时它也会明说「这些新字段保存了也不会生效」。
刻意没放进设置页的字段:listenHost / port / plaintext / sites / enabled 会重启监听器——那正是承载
这个设置页的东西,从 UI 里翻可能把自己关在门外;trustedSecret / setupKey 是密钥,不该经浏览器表单往返。
这些继续只在 settings.yaml 里配。
数据文件
都在 $DSH_HOME/user-management/(默认 ~/.dsh/user-management/,0600,原子写):
| 文件 | 内容 |
|---|---|
| users.json | 账号(scrypt 哈希;迁移进来的账号保留 bcrypt,首次登录成功后自动升级为 scrypt;含 department 所属部门) |
| sessions.json | 会话令牌(重启不掉线,7 天滑动过期) |
| activity.jsonl / audit.jsonl | 登录与访问台账 / 操作日志(滚动保留最近若干条) |
| bans.json | IP 封禁 |
| usage.json | 按账号的当日活跃秒数与小时窗口 token(隔日自动清理) |
| workspaces.json | 工作区归属(路径 → 账号) |
从 dsh-passwords 迁移
账号、权限与配额可由 scripts/import-from-dsh-passwords.mjs 一次性导入(读取源库时复用 dsh-passwords 自己的模块,
因为它的用户名是 AES-GCM 密文 + HMAC 索引):
node scripts/import-from-dsh-passwords.mjs --from E:\path\to\dsh-passwords --dry-run
node scripts/import-from-dsh-passwords.mjs --from E:\path\to\dsh-passwords
# 同名账号两边密码不同时,以源端为准(迁移期间通常正是你要的)
node scripts/import-from-dsh-passwords.mjs --from E:\path\to\dsh-passwords --overwrite-credentials迁移是幂等的:重复运行只会更新权限,不会重复建号;默认不覆盖已有账号的密码。
测试
npm test # 203 项:策略纯函数、store、API 层、客户端契约、以及真实网关的端到端用例
npm run check # 全量语法检查端到端用例会起真实网关(明文模式 + 假上游)验证:工作区过滤含 gzip、目录门与请求体重放、沙盒提权拦截与审批改写、 上传 / git / 运维面门控、历史降级与隐藏字符清洗、配额拦截、归属认领与释放。
卸载 / 回滚
dsh plugin --profile web remove kczx-user-management数据文件不会被删除,重新装回即恢复。若在合并部署中回退到「观察层」形态,见 MERGED-DEPLOYMENT.md。
已知边界
- 执行只对本地存在的账号生效:签名身份 / 未知用户名按「不限制」处理,避免迁移期误锁人。
- 门只管走门的人:若另有插件把 dsh 宿主绑到
0.0.0.0(例如 dsh-lan-access),直连宿主端口即可绕过登录, 且绕过流量不会出现在任何一本台账里。要真正的强制约束,宿主端口必须保持回环。 - 工作区白名单的语义:空名单 = 全部禁止(1.0.4 起;此前是"允许全部")。升级时会对老记录做一次性迁移——把"空名单"写成显式
的
*(允许全部),保持账号原有的可达范围;迁移只跑一次(留下workspace-scope-migration.json标记),之后你手工清空白名单 才等于全部禁止。新建账号默认空 = 无工作区权限。 - token 配额依赖客户端上报:浏览器侧被禁用或页面未加载时不计费(时长配额不受影响,它在服务端计时)。
- 会话历史要缓冲后才能改写:历史响应是一个 JSON 文档(长会话实测 9–16 MB),网关必须先缓冲才能做沙盒降级与隐藏字符
清洗,因此有
historyMaxBytes上限(默认 64 MiB)。超过上限的会话会 fail-closed 返回 502,UI 显示「历史加载失败」—— 调大该值即可;1.0.0 之前用代理默认的 8 MiB,长会话必然踩中。 - 上游
@weibaohui/user-management(0.9.5)与本包kczx-user-management是两个不同的包,互不覆盖,pnpm update不会把两者串在一起。
License
MIT(沿用上游许可)。
