Skip to content

Releases: sweetcornna/corlinman

v1.36.1 — automatic QQ login identity

Choose a tag to compare

@sweetcornna sweetcornna released this 22 Jul 04:37
2d51c5a

Automatic QQ login identity

  • Derives the active Bot QQ ID from live OneBot heartbeat, lifecycle, notice, request, and message events—no manual ID entry required.
  • Tracks NapCat account switches immediately and prevents stale account IDs from acting as mention targets.
  • Displays the detected QQ account as a read-only managed field; saving unrelated QQ settings never writes the runtime identity.
  • Keeps existing channels.qq.self_ids only as a compatibility fallback before a valid event arrives.

Behavior proof: an isolated running gateway received one synthetic heartbeat (no QQ message) and /admin/channels/qq/status reported account_qq=246813579, self_ids=[246813579], and runtime=connected while persisted config_keys.self_ids remained unchanged.

Validation: 7,286 Python tests passed, full UI tests passed, production UI build passed, Ruff/mypy/typecheck/import boundaries passed, and all PR #163 required checks were green.

v1.36.0 — durable scheduled agents + Tencent safety

Choose a tag to compare

@sweetcornna sweetcornna released this 22 Jul 01:47
fa52464

v1.36.0 — durable scheduled agents + Tencent safety boundaries

Highlights

  • Durable runtime scheduler jobs with source identity, explicit timezone, shadow/live modes, restart rehydration, structured results, and unique occurrence keys.
  • Effect-safe Telegram and QZone delivery: ambiguous sends become unknown and block automatic replay until reconciled.
  • Opt-in out-of-repository scheduler extensions for operator-owned jobs; partial imports roll back and private exception text is not logged.
  • Shared scheduled-agent driver using the production chat/tool path with bounded consumption and shadow transport suppression.
  • Default-on, fail-closed Tencent freeze-risk policy across QQ, QQ Official, and QZone, including obfuscation-resistant text classification and deny-by-default unclassified media.
  • One QQ editor toggle controls the policy immediately across both gateway and agent processes; only literal boolean false disables it.
  • QZone publish/comment/feed/friend boundaries now apply policy before I/O and redact unsafe source content before model exposure.
  • HTTP-first OneBot action calls now fall back to authenticated forward WebSocket actions for WS-only NapCat deployments.
  • SQLite/Postgres journal parity for scoped time-range message queries, deterministic cursors, and session-turn cost/aggregate projections.

Privacy boundary

This release contains only generic reusable infrastructure. Migrated operator-specific job implementations, prompts, schedules, targets, personal data, continuity state, and credentials remain outside the public repository.

Validation

  • Python: 7276 passed, 7 skipped
  • Ruff, mypy, import-linter: passed
  • UI: typecheck passed; 1259 passed, 2 skipped; production export passed
  • Proto generation: no drift
  • Production shadow execution confirmed zero public effects and zero continuity writes; all migrated runtime rows remain shadow and the intentionally paused agenda remains disabled.

Upgrade note

Protection is enabled when the QQ setting is absent or malformed. While enabled, unclassified image/audio/video output and scheduled QZone artwork are suppressed. Operators can deliberately opt out in the QQ channel editor.

Full changelog: v1.35.0...v1.36.0

v1.35.0 — gap-close 积压落地:7 个 PR + 排序修复

Choose a tag to compare

@sweetcornna sweetcornna released this 20 Jul 02:33

[1.35.0] — 2026-07-20 — gap-close backlog lands: 7 PRs + ordering fix

fix: deterministic turn ordering on same-ms ties (#161)

  • get_session_turn_ids (SQLite + Postgres) and find_resumable_turn
    now carry the same (started_at_ms DESC, turn_id DESC) contract as
    list_session_turns — two turns seeded within one wall-clock ms no
    longer come back in scrambled natural-row order (surfaced as a
    full-suite-only flake in the fork faithful-copy test).

Dim 5 MCP leftovers 2/2 — .mcp.json layered scopes + MCP client resources (#116)

claude-code parity Dim 5 收官:四项残留全部关闭。
Dim 5 leftovers, part 2 of 2 — the last two verified-open items close.

Added

  • .mcp.json 分层作用域corlinman_mcp_server.scoped_config)——
    claude-code 式 server 配置发现:local<项目>/.mcp.local.json
    gitignored)> project<项目>/.mcp.json,入库)> user
    ~/.corlinman/mcp.json)> 网关内联 [mcp] 配置;按 server 名去重、
    强作用域胜出。文件形态兼容 claude-code 的 {"mcpServers": {…}}
    mcp_servers/servers 别名亦可)。坏文件/坏条目只跳过不崩溃。
    网关 lifespan 与嵌入式控制台两个启动点同时接线。
  • MCP client resourcesresources/list + resources/read)——
    连接期分页发现(无该能力的 server 干净地降级为空);每个有资源的
    server 合成一个 {server}_read_resource 工具进入现有广告/路由通道
    (schema 带资源清单、上限 10 条防膨胀;server 自带 literal
    read_resource 工具时合成让位、走正常 tools/call——advertise 与
    bridge 两侧同一 literal-wins 规则);McpToolBridge 新增
    resources/read 分支,text 内容折叠、blob 以占位说明呈现;
    allow/deny server 策略同样约束资源面。

Environment seam + Docker per-process sandbox backend (Wave D) (#120)

claude-code parity Wave D(Dim 4 sandbox,phase 1):三个代码执行工具
(前台 run_shell、后台 shell 任务注册表、持久 REPL)的进程孵化统一
收敛到一个 Environment 缝合层——默认 local 后端与历史行为字节等价,
新增 docker 后端把每个孵化进程放进自己的一次性容器。
Wave D of the parity program (Dim 4 sandbox, phase 1): all three
code-execution tools now spawn through a single Environment seam — the
default local backend is byte-identical to historical behavior, and a new
docker backend runs each spawned process in its own throwaway container.

Added

  • Environment 缝合层(D1,corlinman_agent.coding.environment)——
    spawn_shell / spawn_repl 返回 SpawnedProcess 句柄(.proc +
    kill() + reap()):终止逻辑随孵化的子进程走而不是挂在后端上——
    持久 REPL 会话比孵化它的调用活得久,kill 不能依赖事后重新解析后端。
    LocalEnvironment(默认)逐字节复刻历史 create_subprocess_* 调用
    (workspace cwd、env 白名单、POSIX rlimits + setsid preexec),全部
    既有行为与测试不变;_build_child_env / _preexec_apply_rlimits /
    kill_process_group / reap_orphan_group 原样从 shell.py 迁入并
    re-export(shell 依赖 environment,留在原处会循环导入)。
  • Docker 后端(D2,CORLINMAN_SANDBOX_BACKEND=docker)——
    一进程一容器:每次孵化 = 一个 docker run --rm,容器名
    corlinman-sbx-<uuid> 即 kill token(docker kill <name> 原生精确,
    宿主 killpg 够不到容器内 PID;不用长驻容器 + exec,其 PID 追踪
    脆弱)。加固旗标:no-new-privileges--pids-limit=64
    --memory=2g(swap 同额)、--ulimit nofile/cpu/fsize 对齐本地
    rlimits;只转发 LANG/LC_ALL/TZ(宿主 PATH/HOME/USER
    一切密钥永不进容器);REPL 直接跑镜像内解释器,只 -i 绝不 -t
    (tty 会破坏 marker 帧协议)。镜像知
    CORLINMAN_SANDBOX_IMAGE(默认 python:3.12-slim-bookworm)、
    用户重映射知 CORLINMAN_SANDBOX_USER(默认镜像自身用户;Linux
    宿主想避免 workspace 出现 root 属主文件时设 uid:gid)。docker
    故障以 SandboxSpawnError(OSError) / DaemonUnavailableError 抛出
    ——既有 except-OSError → spawn_failed 信封路径零改动复用;docker
    二进制缺失在孵化时就大声失败,绝不静默回落到宿主执行。
  • 后端选择器(D3,get_environment)—— CORLINMAN_SANDBOX_BACKEND
    默认 local,未知值抛出点名该变量的 RuntimeError;每次调用现读、
    不缓存,与 open_backend_from_env 同款形状。

Fixed / Hardening(审查阶段预先消除的二阶边界)

  • docker 客户端自建会话——docker run 客户端不 fork 时与服务器同
    进程组,后台任务终局 reap 的 killpg 会连服务器一起 SIGKILL;现在
    docker 客户端一律 preexec_fn=os.setsid(刻意不套 rlimits——那会
    绑住宿主 docker CLI,容器限额由 --ulimit 负责),且注册表 7 个
    终局路径全部改走句柄感知的 _reap_task(非本地句柄先
    handle.reap() 做容器原生清理,本地路径字节不变)。
  • 前台超时不再可能挂死——DockerSpawnedProcess.kill()
    docker kill 之后同时清扫 setsid 后的客户端进程组:即使 daemon
    挂死,dispatcher 的 await proc.wait() 也必然解除(该极端下容器
    可能泄漏——挂死的 daemon 无法受托删除它——属已记录的取舍)。
  • 已记录取舍:docker kill 为兼容 atexit 走同步 subprocess.run
    (超时 10s 有界),daemon 挂死时单次终局清理最多阻塞事件循环 10s
    ——仅 opt-in 的 docker 后端受影响,local 默认路径零变化。

Notes

  • 沙箱不是安全边界的替身(见 shell.py 模块文档):workspace
    bind-mount 刻意可读写、容器联网保持开启(与本地 shell 对齐,构建要
    拉依赖)、_DENY 前置筛查照旧对所有后端生效。docker 后端收窄的是
    宿主 workspace 之外的面与网关进程内密钥的暴露。

Wave E parity — settings persistence, exit_plan_mode, --fork-session, fallback signature strip, hermetic tests (#119)

claude-code parity Wave E(小批量五连):权限设置终于能落盘分层加载、
模型可以自己结束 plan mode、会话可以无损分叉、跨模型 fallback 不再被
thinking 签名炸出 400、测试套件对宿主路由环境免疫。
Wave E of the parity program: durable layered permission settings, a
model-callable exit_plan_mode, --fork-session, cross-model
thinking-signature stripping on fallback, and hermetic tests.

Added

  • 分层权限设置持久化(E1,corlinman_agent.permission_settings)——
    from_layered_sources 此前零生产调用方:所有部署只从 env 建 gate,
    durable 规则只能写环境变量。新增加载器按
    <data_dir>/settings.json(用户层)→ <项目>/.corlinman/ settings.local.json(项目层,gitignored 惯例同 claude-code)→
    CORLINMAN_AGENT_PERMISSIONS env(最终话语权)叠层;mode/strict
    同样 env > 项目 > 用户。无任何设置文件时与旧 from_env() 字节等价
    (含 first-match-wins 默认)。坏文件/坏块只跳过不崩溃。
    ApprovalGate 与 servicer 的默认 gate 构造双双切换到新加载器。
  • 控制台审批第三答案 p/persist(E1)—— 交互审批在
    y(一次)/ a(本会话)之外新增 persist:等同 always 并通过
    persist_allow_rule 原子写入(tmp + rename、幂等去重)用户层
    settings.json,未来会话不再询问;persist 钩子失败降级为会话级
    授权而不是拒绝(操作员明明答了 allow)。a 保持会话级语义不变
    (Codex #104 —— 授权不得越过其上下文边界)。
  • exit_plan_mode 内置工具(E2,claude-code 对标)—— plan mode
    会拒掉全部 mutating 工具,此前只能靠人 /permissions 手动放行。
    现在模型计划就绪后自己调用:plan → default 翻转 + 交互审批缓存
    重置(两个 resolver 来源 —— set_approval_resolver
    app_state.approval_resolver —— 都重置,plan 期间答的 always
    不得带进实施阶段);非 plan mode 下干净 no-op;可选 plan 摘要
    回显给用户。子代理拒绝:权限 mode 是 servicer 级全局的,
    plan 模式下派生的 subagent 不得替父回合结束 plan mode(child
    executor 与递归 spawn / 后台 shell 同款拒绝信封);skill
    allowed-tools 控制工具直通名单同步加入,防止 skill 把模型困死在
    plan mode。
  • --fork-session(E3,claude-code 对标)——
    AgentJournal.fork_session 把源会话的 completed 回合按时间序
    复制到全新 session key(in_progress 在别处直播、errored 带 T4.4
    面包屑,均不复制;单回合损坏只跳过不废整个 fork;源会话全程只读)。
    控制台 --fork-session 旗标在恢复前铸新 console:<id> key 并
    fork,原会话零污染;attach 模式回显不支持说明后不 fork 继续。
  • fallback 跨模型 thinking 签名剥离(E4,reasoning_loop)——
    thinking 块签名按模型铸造;OpenAI 兼容入口带进来的客户端历史在
    model-not-found / 持续过载两条 fallback 分支切换模型重放时,会把
    可恢复错误变成硬 400。现在两条分支在重放前剥掉
    thinking/redacted_thinking 块与 signature 键(纯 thinking
    消息内容折叠为 "",部分后端拒绝空块列表)。
  • 测试封闭性(E5,根 conftest.py)—— 宿主机泄漏的
    ANTHROPIC_BASE_URL/代理 env 会把 respx-mock 的 provider 测试
    重路由到真网络造成假失败;autouse fixture 对非 live 标记的测试
    统一清洗 7 个路由变量(测试自身 monkeypatch.setenv 仍生效,
    live_llm/live_transport 不受影响)。CI 里原先的
    env -u ANTHROPIC_BASE_URL 包装不再需要。

#108 residuals — always-on subagent registry feed + resume user bubble + crashed-turn replay (#118)

Closes the last three #108 items. The live-subagents panel no longer
depends on someone having a chat page open, a resumed in-flight turn
shows the question it is answering, and a crashed turn's messages stop
vanishing from the transcript.

Added

  • Process-wide subagent journal tail (#108 item 2). A gateway
    background task (run_journal_subagent_tail) now tails the journal's
    Subagent* lifecycle events into the LiveSubagentRegistry
    continuously. Previously the ONLY cross-process feed point was the
    per-session SSE poll, so in grpc_agent mode /admin/subagents stayed
    empty unless that exact session's chat page was open. The tail is
    forward-only (cursor seeded at the boot high-water mark), idempotent
    with the SSE-poll feed, race-free (bounded scan against a snapshotted
    MAX(rowid)), and best-effort throughout. New journal surface:
    latest_event_rowid() + load_subagent_events_since() (SQLite;
    Postgres degrades to a no-op alongside the rest of the event plane).
    进程级子代理事件尾随:/admin/subagents 不再依赖恰好有人打开该会话的
    聊天页才能看到 grpc_agent 模式下的子代理。
  • Resume user bubble (#108 item 3). Returning to a chat with an
    in-flight turn now renders the user's message (from the turn's
    user_text_preview) above the live streaming bubble — previously the
    assistant streamed a reply to nothing, because the settled transcript
    excludes the in-progress turn wholesale. The synthetic bubble is washed
    out by the authoritative transcript refetch when the turn settles.
    回到进行中的会话时,问题气泡随直播气泡一起渲染。

Fixed

  • L-103 — crashed in-progress turns vanished from the transcript.
    _replay_from_journal skipped EVERY in_progress turn; for a turn
    that crashed mid-run (never completed) and was followed by newer turns,
    the user's message and any partial answer disappeared from the thread
    forever. The skip is now scoped to the one turn that can legitimately
    still be live — the newest turn of the first page; older in_progress
    rows are crash artifacts and replay as real history.
    崩溃的历史 turn 不再从会话记录中消失(跳过逻辑收窄到最新 turn)。

Dim 5 MCP leftovers 1/2 — sampling completer prod wiring + /mcp command + embedded MCP plane (#115)

claude-code parity Dim 5 剩余四项中的两个 M 级项。
Dim 5 leftovers, part 1 of 2: the sampling responder finally runs real
completions, and the console gains claude-code's /mcp — backed by a
real MCP client plane in embedded (Mode A) sessions.

Added

  • MCP sampling completer(生产接线) — v1.26.0 的 SamplingResponder
    机制此前没有任何 completer 写入(state.extras["mcp_sampling_completer"]
    零写入方 → 永远 sampling_unavailable)。新增
    gateway/mcp/sampling_completer.py:包装网关 live provider
    registry(逐调用读取 —— provider bootstrap 晚于 MCP 块、热重载会换
    handle),流式 token 折叠为单结果,reasoning 增量绝不泄漏给请求方
    MCP server;finish_reason → MCP stopReason 映射。registry 未就绪
    时干净地降级为单请求 INTERNAL_ERROR
  • /mcp 控制台命令(claude-code 对标)——
    list / tools [server] / add <name> <cmd|url> [args…] / remove / restart / test / enable / disable。attach 模式与降级路径回显
    unavailable(与 /hooks 同款约定)。
  • 嵌入式(Mode A)MCP 客户端面 — 控制台"全脑"此前
    mcp_manager=None(外部 MCP 工具在 console 完全不可用)。...
Read more

v1.34.0 — QZone 并入 QQ 渠道页 + 参考图描述互通

Choose a tag to compare

@sweetcornna sweetcornna released this 19 Jul 07:46

亮点

QQ 空间发布搬进了 QQ 渠道页。 原来独立的"QZone 发布"页面(每日说说、定时器、参考图选择、自动回评论子任务区)整体并入 /channels/qq,就在渠道配置下方——QZone 发布借用的本来就是 NapCat 的登录态,现在一个页面管完 QQ 的所有事。旧地址自动跳转,书签不失效;侧栏独立入口移除,搜索关键词并入 QQ 项。

QQ 渠道配置不再一脸问号。 access_token(OneBot WS 鉴权)和 napcat_access_token(NapCat WebUI 鉴权)其实各管一个端口、都是活字段,但内置 NapCat 场景下根本不用填——现在和两个 URL 一起收进"高级设置"折叠区,配上人话标签和"内置 NapCat 留空即可"的说明。默认视图只剩回复策略/白名单/开关这些真正常用的项。顺手修复:群白名单/主动发言群/两个开关/六个数字字段保存后从不回显,表单每次打开都显示空值盖住已存配置——现在正确回填。

参考图可以写"这张图是什么、怎么参考"了。 persona 全局设置里每张参考图新增描述编辑框(上限 500 字,老库自动迁移);QZone 任务页按 label 引用全局图库,描述自动跟过去(tooltip+摘要行),改一处全局生效。描述同时注入图像生成链路两处:image_with_refs 的图例(Reference image N = label (desc))和每日说说任务的系统提示词块(label(描述)),模型真的知道每张参考图该怎么用。

日食珠吉祥物微整形:去掉嘴巴,只保留眨眼的双眼,眼睛回归视觉中心。

完整变更见 CHANGELOG.md。#160

v1.33.1 — 顶栏不再透明

Choose a tag to compare

@sweetcornna sweetcornna released this 19 Jul 06:32

修复

顶栏透明度问题。 .c-appbar 的 post-utility background: transparent 静默盖掉了顶栏的 bg-sg-space-0——暗色纯黑画布下看不出来,浅色 Paper 主题下滚动内容直接从 sticky 条底透出、悬浮缝隙里还会滚过内容,视觉混乱。现在顶栏为画布色实底(暗色主题视觉不变)+ 缝隙遮条 + 守卫测试锁定永不回透明。

完整变更见 CHANGELOG.md。#159

v1.33.0 — QZone 多样性引擎 + 定时器 UI 重做 + 自动回评论

Choose a tag to compare

@sweetcornna sweetcornna released this 19 Jul 06:22

亮点

说说不再"每天都是那点东西"。 日常发布多样性引擎上线:每次发布前注入一条灵感种子(persona_life_event_seed 抽取)、列出最近已发内容并明令禁止重复主题/场景/开头句式(新的 per-persona post-log sidecar,原子写、留 30 条)、生活节奏三档提醒(≥13 天没出门/同状态过久/在外过久)优先驱动当天内容并顺手推进 persona 生活状态;可选发布时间抖动(jitter_minutes 0-180,runner 级随机延迟)让发布时刻不再天天准点。metadata 一键 diversity=false 可整体回退。

定时器不用再手写 cron 了。 QZone 调度页重做:任务行点编辑就地回填表单走 PATCH(不再"重填覆盖");触发时间改为 每天/每周/高级 三模式选择器(纯库 compose/parse,周日发 0 兼容预览引擎);启停/删除/立即跑都在行内;暂停恢复走后端重验证。

配图支持上传参考图。 任务里可直接上传/多选 persona 参考图(与 persona studio 共享缓存,≤8 张,label 校验),image_ref_labels/jitter_minutes 升为调度 API 顶层字段(metadata 为权威存储);image_with_refs 还移植了抓拍式构图指令——不同动作朝向/偏轴取景/动作进行时/环境杂物,配图更像随手拍的生活切片而非摆拍合影(CORLINMAN_IMAGE_REFS_INTRO=off 可关)。

新增定时自动回评论。 qzone.reply_comments builtin(hermes 没有的净新增):定时扫描自己最近说说下的新评论,人设口吻回复,seen sidecar 去重、max_replies/lookback_posts 可调,管理页有独立子任务区。

修复:日常发布成功后 tid/qzone_url 永远为 None(ToolResultEvent 增 payload_json 转发,管理页历史现在能看到真实链接);左下角用户头像首字母溢出日食珠。

完整变更见 CHANGELOG.md。#148 #149 #150 #151 #152 #153 #154 #155 #156 #157 #158

v1.32.0 — ask_user 问题卡片 + 按模型族思考档位

Choose a tag to compare

@sweetcornna sweetcornna released this 18 Jul 16:20

亮点

ask_user 提问在 web 端可见可点了。 此前 agent 的 ask_user 追问在 Telegram 有按钮、QQ 有列表,web 端选项整个丢失。现在问题选项渲染为气泡下方的 pill:单选点击即发送、多选勾选后提交、历史轮次静默展示;流式中调用保留在工具 trace 里可见。设计系统新增 question-card 组件卡。

思考档位按模型族全量精确映射。 思考控件不再是硬编码三档:后端注册表按解析后的真实模型 id 判族出档(中继别名如 cornna→gpt-5.6-sol 也能解析)——gpt-5.6 六档 none…max、5.2/5.4/5.5 五档、5.1 四档、o 系三档、Claude 4.6+/Fable 四/五档(output_config.effort + adaptive)、Gemini 3 系 thinking_level / 2.5 系预算、DeepSeek V4 / GLM / Qwen / Kimi / Grok 各按官方档;/admin/models 按别名下发档位,前端只渲染真实选项(无档模型隐藏控件),请求就近钳制永不 400。Anthropic/Google provider 的 thinking 入参从无到有。

修复:流式竖线不再悬出气泡圆角(设计系统 15 卡同步);网关鉴权中间件不再 401 掉 CORS 预检(跨域浏览器客户端此前必死)。

完整变更见 CHANGELOG.md。#147

v1.31.1 — reasoning summaries stay in the thinking block

Choose a tag to compare

@sweetcornna sweetcornna released this 18 Jul 14:00

修复 OpenAI 兼容中继(Responses→chat.completions,如 gpt-5.x)下思考摘要正文漏进正文气泡的问题:标题后缓冲 content,按步骤终态分流(tool_calls→思考块;stop/超限/无终态→真答案照常流式)。DeepSeek-R1 类推理不受影响。详见 PR #146

v1.31.0 — Eclipse Minimal v2 design language

Choose a tag to compare

@sweetcornna sweetcornna released this 18 Jul 11:19

全前端设计语言替换:Spatial Glass → Eclipse Minimal v2(claude.ai/design corlinman 设计语言)。

  • 纯黑画布 + 月升光晕、哑光炭面、月白五级墨阶;全仓禁 backdrop-filter
  • tint 管线(月光白默认 + 晨昏/冰蓝/玫瑰/苔绿/鸢尾预设 + 自定 hue)——只染"光",不染骨架
  • 日食珠签名元素:品牌标、聊天身份、流式指示、hero;README 吉祥物 = 动画黑洞小脸
  • 聊天全面重绘:炭面气泡 + 尾角、流式 thread + caret、凹面工具卡、凹面 composer
  • 自绘图标 sprite(149 符号,24 网格/1.8 描边),移除 lucide-react;自托管 M PLUS 1 / MiSans / JetBrains Mono
  • 字重纪律 400/500;设计规则 vitest 强制

详见 CHANGELOG 与 PR #145

v1.30.0 — human-paced QQ groups + truthful admin UI

Choose a tag to compare

@sweetcornna sweetcornna released this 18 Jul 09:05

QQ group behaviour goes from "replies to everything, always" to
whitelist-gated, human-paced speech with an opt-in proactive voice —
and the admin UI stops lying: statuses now reflect reality (timezones,
connection badges, dates in your language), the whole surface is
properly bilingual, and forms shed derivable/dead fields.

Added

  • QQ group whitelist ([channels.qq].group_whitelist) — hard gate:
    only listed groups are ever answered; @mentions and slash commands do
    NOT bypass it. Empty list mutes every group; key absent = no whitelist.
  • QQ proactive speech (default off) — proactive_enabled plus
    humanized pacing: random gaps (proactive_min/max_gap_minutes,
    45–180 default), active-hours window (9–23, overnight supported),
    per-group daily budget (proactive_daily_max, 4), persona voice via
    the normal chat pipeline in a dedicated per-group session; skips
    automatically while the QQ account is offline.
  • QQ emergency mutegroup_replies_enabled = false drops every
    group message before any gate; private chat unaffected.
  • Scheduler job timezones — cron evaluation honours each job's IANA
    timezone; the QZone form sends the browser zone so the "next fire"
    preview matches the real firing instant (previously 0 9 * * *
    fired 09:00 UTC = 17:00 Beijing).
  • ui/lib/format.ts — all admin timestamps/numbers now follow the
    SELECTED UI language, not the OS locale (~25 call sites converted).
  • FieldHint primitive — single-line field help with the long-form
    contract behind a hover tooltip; the copy-diet standard for forms.

Changed

  • QQ group replies are quiet by default — new
    group_reply_policy = "mention_or_keyword": groups without an
    explicit keyword list answer only @mentions and slash commands
    (legacy reply-to-everything is "all"); non-mention replies respect
    a per-group cooldown (group_reply_cooldown_secs, 20s default).
  • Image generation: default timeout 120s → 300s
    (CORLINMAN_IMAGE_TIMEOUT_SECS); auth/config rejections
    (401/403/404) and connection failures now fail fast as
    unrecoverable — the error tells the model to stop retrying and to
    tell the user the feature is unavailable, and a 10-minute
    per-endpoint breaker short-circuits follow-up calls. Timeouts and
    5xx/429 stay retryable.
  • Admin UI language: zh-CN stays the default unless the operator
    explicitly toggles — navigator.language sniffing removed (a zh
    operator on an en-US browser was silently flipped to English).
  • Admin copy diet (wave 3): page ledes cut to one plain sentence — no
    more raw /admin/* paths, regex patterns, config keys, or backend
    module names in user-facing help across the worst-offender pages.
  • Forms shed derivable/expert fields (wave 4): QZone job name is
    auto-derived (<persona>.daily_qzone) and cron became presets +
    advanced; provider dialog shows base_url only for OpenAI-compatible
    kinds and prefills the key env-var; channel endpoint URLs, evolution
    tunables, and tenant display-name collapsed behind "advanced";
    persona id auto-slugs from the display name.

Fixed

  • NapCat "QQ Is Logined" wedge — after a session drop NapCat
    refused to mint a login QR while reporting offline; QR refresh now
    detects the stale state via CheckLoginStatus and drives the restart
    fallback (was a bare 502). Genuinely-logged-in returns a clear 409.
  • QQ channel connection badge was hardcoded "unknown" — now derived
    from the live health watcher; removed the stats tile that fabricated
    a "throttled" count from the connection enum.
  • QQ recent-message + log-stream timestamps rendered UTC wall-clock
    (hours off local); channel enable-switch invalidated dead query keys
    leaving status panels stale.
  • 121 missing locale keys per language (evolution settings, skills
    drawer, agent picker rendered raw keys; identity page fell back to
    English); Chinese fragments purged from the English bundle and
    English leaks from the Chinese one; Telegram page's ~50 hardcoded
    English strings wired to existing translations.
  • RAG tag filter removed — the UI collected tags but the backend never
    received them; persona editor's permanently-disabled test box
    removed.
  • deploy/install.sh wrote colored log output into the NapCat systemd
    unit's ExecStart (unit failed to load as bad-setting) — logs now go
    to stderr.